scitex_todo — Python API

scitex-todo: a canonical YAML task store with pluggable adapters.

The task store (YAML, top-level tasks: list) is the single source of truth. Adapters render or import it; the mermaid adapter (YAML -> dependency PNG) ships today. See the project roadmap for org and Web-UI adapters.

Quick Start

>>> import scitex_todo as todo
>>> tasks = todo.load_tasks("tasks.yaml")
>>> src = todo.build_mermaid(tasks)
>>> todo.render(src, "tasks.png")
'mmdc'
exception scitex_todo.TaskNotFoundError[source]

Bases: KeyError

Raised when an update/complete target id is not in the store.

exception scitex_todo.TaskValidationError[source]

Bases: ValueError

Raised when a task store fails structural validation.

scitex_todo.add_task(store=None, *, id, title, status='pending', scope=None, assignee=None, priority=None, parent=None, note=None, depends_on=None, blocks=None, repo=None)[source]

Append a new task to store and persist via save_tasks().

Returns the inserted task mapping (a fresh dict, not the underlying YAML node) for convenient round-trip use by callers — the CLI prints it, the MCP tools serialize it as the JSON result.

Raises:

TaskValidationError – On duplicate id or any other structural fault — save_tasks re-runs the full validation gate before touching disk.

Return type:

dict

scitex_todo.complete_task(store=None, task_id=None, *, by=None)[source]

Mark task_id as done and stamp _log_meta.completed_{at,by}.

Idempotent per GITIGNORED/QUESTIONS.md #3: re-completing a done task is a no-op (timestamps stay frozen from the first completion). Pass by= to override the $SCITEX_TODO_AGENT$USER"unknown" precedence chain.

Returns the (post-mutation) task mapping.

Raises:

TaskNotFoundError – If no task matches task_id.

Return type:

dict

scitex_todo.list_tasks(store=None, *, scope=None, assignee=None, status=None)[source]

Snapshot the store, then filter by scope / assignee / status.

Return type:

list[dict]

Filter semantics: - scope=None (default): use $SCITEX_TODO_SCOPE if set, else

no filter. scope="" opts out of the env default explicitly.

  • assignee / status: None = no filter; any string = exact match. (Generic Req 8 — no fuzzy / glob; callers compose.)

The returned list contains fresh dicts, safe to mutate without affecting the on-disk store (no save here).

scitex_todo.resolve_store(store=None)[source]

Return the resolved task store path and the precedence chain.

Mirrors the data the scitex-todo resolve-store CLI verb and the resolve_store MCP tool emit. Keeping a Python API by the same name as the MCP tool satisfies audit §6 (Convention A: tool_name == api_name).

Output shape:

{
  "resolved":         "/abs/path/to/tasks.yaml",
  "explicit":         <the `store` arg you passed, or None>,
  "env_tasks":        <value of $SCITEX_TODO_TASKS, or None>,
  "user_store":       "/abs/path/to/~/.scitex/todo/tasks.yaml",
  "bundled_example":  "/abs/path/to/bundled/example.yaml",
  "pkg_short":        "scitex_todo",
  "exists":           bool,
}
Return type:

dict

scitex_todo.summarize_tasks(store=None, *, scope=None, assignee=None)[source]

Return numeric progress counts grouped by status, scope, assignee.

Output shape (always present keys):

{
  "store": "/abs/path/to/tasks.yaml",
  "total": int,
  "by_status": {<status>: int, ...},  # one key per VALID_STATUSES
  "by_scope": {<scope|"">: int, ...},
  "by_assignee": {<assignee|"">: int, ...},
}

Tasks with no scope / assignee bucket under the empty string "". The by_status map is densified to all VALID_STATUSES so consumers (web UI, progress widgets) don’t have to special-case zero-count keys.

Return type:

dict

scitex_todo.update_task(store=None, task_id=None, **fields)[source]

Update fields of the task with id task_id; return the merged dict.

Any keyword argument becomes a field on the task. Passing None for a field DELETES it (matches the operator’s mental model: “clear the scope” = update_task(…, scope=None)). To leave a field untouched, just omit it.

Raises:
Return type:

dict