Skip to content

task_create.py is where a Task is born with its contract attached. Two flags — --files-expected and --required-reads — turn a task row into a hook-enforced obligation.

Implementation

task_create.py

.claude/tools/task-management/task_create.py

What it is

The Task's birth point.

A ~12-flag CLI that wraps TaskManager.create_task() and writes one row to the task list.

/planning

/build

the invoker

🔧

task_create.py

parses flags, writes row

📋

task-list.json

one new Task row with contract attached

Every Task in the planner-workflow system came through this script.

Which means every Task's contract was declared here, not negotiated later.

The two flags that matter

Two flags turn a task row into an obligation.

--files-expected

repeatable JSON object

"The agent will create, modify, or delete exactly these files with these symbols."

🚨

Enforced by product-spec-stop-verify.

Agent cannot emit Stop until disk state matches.

--required-reads

repeatable path

"The agent must read these files before any Write or Edit."

🚧

Enforced by context-gate.

Blocks Write/Edit until all required reads have happened.

Without these two flags, Task is just subject + description.

With them, Task is a hook-enforced obligation.

The JSON contract shape

Strict parsing. Malformed JSON exits with error.

--files-expected examples

create

{"path": "src/foo.ts", "op": "create"}

modify

{"path": "src/bar.ts", "op": "modify",
 "symbolsRequired": ["exportedFn"],
 "symbolsForbidden": ["oldPattern"]}

delete

{"path": "src/old.ts", "op": "delete"}

No silent acceptance of typo'd flags — malformed JSON exits 1 with an error. The contract is declared or the task isn't created.

The layer gate

Unknown layers rejected at argparse, before any row is written.

--layerschema

--layerroute

--layerbuisness-logic

🛑

choices=[...]

at argparse · before write

schemaserviceroutecomponenthooktestconfiginfracontractagent

Downstream layer-dispatched verification always gets a known value to key on.

A typo like buisness-logic never reaches disk.

The output contract

JSON envelope means agents parse, not scrape.

Every invocation emits parseable JSON. No downstream regex.

$python3 task_create.py--subject "Add auth middleware" ...

stdout · success {"success": true, "taskId": "P1-T1", "task": {...}}

stderr · failure {"success": false, "error": "..."}

# wiring the next task's dependency:

TASK_ID=$(python3 task_create.py ... | jq -r .taskId)

python3 task_create.py ... --depends-on$TASK_ID

Both success and failure are JSON-parseable. taskId flows into the next call's --depends-on.

No brittle text parsing between phases.

The invocation rule

python3, not uv run. Here's why.

Task tools run inside hooks, inside subprocesses, mid-session

called from many places, many times, under time pressure

uv run task_create.py

  • resolves dependencies every call
  • slow — caller waits for env setup
  • resource-hungry inside subprocess chains
  • fails if uv isn't on PATH in this subprocess

python3 task_create.py

  • pure stdlib + one local import
  • instant — no env resolution
  • safe to call from anywhere
  • works inside any subprocess, hook, or agent session

The single import beyond stdlib is task_manager.TaskManager — itself pure-Python, reading/writing JSON directly. Zero-dependency by discipline.

The role

A Task starts life with its contract attached.

The CLI is the single gate. Every flag declared here becomes an enforcement point downstream — in hooks that block writes, in verifiers that block Stop, in dispatchers that route by layer. One script, many downstream enforcers.