| name | anti-legacy:planner |
| description | Decompose the blueprint into a concrete, ordered task list for the build swarm. One task per requirement node, scoped to โค150 lines of target code. Tasks are topologically sorted by dependency order (data layer before service layer before API layer). Produces task.md for the swarm. Requires GATE_2_PLAN sign-off. Use when: "create the task list", "plan the build", "decompose the blueprint", "what order do we build things", "generate task.md".
|
anti-legacy:planner
Decomposes the approved blueprint into a concrete, ordered task list. The output
drives the anti-legacy:swarm build phase โ each task is a self-contained unit
of work for a single developer subagent.
Cross-Platform Notes
All file operations use the agent's native Read/Write tools.
Parameters
- max_lines (optional): max target lines per task. Defaults to 150. Tasks that
would exceed this are automatically split into sub-tasks.
Step 1: Verify GATE_1_DESIGN is cleared
python3 -c "
import json, sys
m = json.load(open('.anti-legacy/manifest.json'))
g = m['gates']['GATE_1_DESIGN']
if g['status'] != 'passed':
print(f'BLOCKED: GATE_1_DESIGN is {g[\"status\"]}. Run anti-legacy:gatekeeper to verify sign-off first.')
sys.exit(1)
print('GATE_1_DESIGN: cleared โ')
"
Halt if GATE_1_DESIGN is not passed.
Step 2: Read blueprint and requirements
Read:
.anti-legacy/requirements/blueprint.json โ components, build order, dependencies
.anti-legacy/requirements/requirements_graph.json โ business rules per node
Step 3: Topological sort of tasks
Compute the build order respecting inter-requirement dependencies. The ordering follows the traversal_strategy (bottom-up, top-down, vertical-slice) defined in .anti-legacy/config.json (defaults to bottom-up).
Run the sorting tool:
python3 .anti-legacy/run.py planner_utils \
--requirements-graph .anti-legacy/requirements/requirements_graph.json \
--config .anti-legacy/config.json
Or run the equivalent Python sorting logic:
python3 -c "
import json, os
from scripts.planner_utils import sort_requirements, get_dependencies_and_domains
# Load config to get traversal strategy
strategy = 'bottom-up'
if os.path.exists('.anti-legacy/config.json'):
cfg = json.load(open('.anti-legacy/config.json'))
strategy = cfg.get('traversal_strategy', 'bottom-up')
rg = json.load(open('.anti-legacy/requirements/requirements_graph.json'))
order = sort_requirements(rg, strategy)
_, req_to_domain = get_dependencies_and_domains(rg)
print(f'Build order (Strategy: {strategy}):')
for i, req_id in enumerate(order, 1):
print(f' {i}. {req_id} ({req_to_domain.get(req_id, \"unknown\")})')
"
Step 4: Estimate task scope and split if needed
For each requirement node in build order, estimate line count AND an hours figure
(the task.md contract records hours, not lines โ see Step 7). Rough mapping:
- Data model class (Layer 0): ~30โ50 lines / ~1โ2h per entity
- Repository interface (Layer 1): ~20โ40 lines / ~1โ2h
- Service class (Layer 2): ~50โ150 lines / ~3โ8h per requirement (varies by rule count)
- REST controller / batch entry point (Layer 3): ~30โ60 lines / ~2โ4h per endpoint
- Unit test class: ~50โ100 lines (folded into the owning task's hours)
No task may exceed 8h (GATE_2 checklist rejects any estimate > 8h). If a service node has >10 business rules, split into:
{req_id}-core: primary calculation/processing logic
{req_id}-validation: input validation rules
{req_id}-error-paths: error handling and rollback
Step 5: Assign task layers
Group tasks into four build layers:
- Layer 0 โ Data models: entity classes and ORM mappings (no dependencies)
- Layer 1 โ Repositories: data access interfaces and implementations
- Layer 2 โ Services: business logic components (depends on Layer 1)
- Layer 3 โ API / Entry points: controllers, batch runners, listeners (depends on Layer 2)
Step 6: Write task.md
Each checkbox is one line in the contract format, then optional sub-bullets for
the build metadata the swarm needs:
- [ ] [REQ_ID] File.java โ Nh [โ depends: REQ_OTHER]
[REQ_ID] โ the requirement node id (always set; this is the traceability anchor).
File.java โ the primary target file for the task.
Nh โ the HOURS estimate (mandatory on every task; โค 8h, see Step 5). Not a line count.
โ depends: REQ_OTHER โ optional, the requirement(s) this task depends on. A
dependency MUST resolve to a task in the same or an earlier layer โ never a later
one (topological order; asserted in Step 8).
The four layers are fixed and Layer 3 is always last:
Layer 0 = data models ยท Layer 1 = repositories ยท Layer 2 = services ยท Layer 3 = API/batch entry points.
Write .anti-legacy/task.md:
# Build Tasks โ {project_name}
**Target stack**: {target_stack}
**Total tasks**: {task_count}
**Build layers**: 4
## Layer 0 โ Data Models
These tasks have no dependencies and can be built first or in parallel.
- [ ] [{req_id}] {ClassName}.{ext} โ {N}h
- Source: `{legacy_file}`
- Target: `{target_path}/{package}/model/{ClassName}.{ext}`
- Schema: {entity schema from blueprint}
- Pattern: `{source_lang}-to-{target_stack}/entity-mapping`
- Owner: `unassigned`
- Verification Status: `UNTESTED`
- Audit Trail: `not-started`
## Layer 1 โ Repositories
Build after Layer 0.
- [ ] [{req_id}] {Name}Repository.{ext} โ {N}h โ depends: {req_id_layer0}
- Owner: `unassigned`
- Verification Status: `UNTESTED`
- Audit Trail: `not-started`
...
## Layer 2 โ Services
Build after Layer 1. These are the primary translation targets.
- [ ] [{req_id}] {ClassName}Service.{ext} โ {N}h โ depends: {req_id_layer1}
- Source: `{legacy_file}` (COBOL program / Java class / VB6 module)
- Target: `{target_path}/{package}/service/{ClassName}.{ext}`
- Business rules: {rule_count} rules (see requirements_graph.json: {req_id})
- Data access: {tables_accessed}
- Test contract: `.anti-legacy/contracts/{domain}/{req_id}.contract.json`
- Owner: `unassigned`
- Verification Status: `UNTESTED`
- Audit Trail: `not-started`
## Layer 3 โ API / Entry Points
Build after Layer 2. Always the last layer.
- [ ] [{req_id}] {ClassName}Controller.{ext} โ {N}h โ depends: {req_id_layer2}
- Owner: `unassigned`
- Verification Status: `UNTESTED`
- Audit Trail: `not-started`
...
## Completion Criteria
Each task is DONE when:
1. Target file compiles without errors
2. Unit test file exists with at least 1 happy path + 1 error scenario
3. Tests pass: `{stack_test_command}`
4. Task checkbox is ticked in this file and committed to git
Step 7: Done-gate, register artifact, and advance to GATE_2
Done-gate (BLOCKING). Before registering or advancing, assert the planner's own
contract. All three checks must pass; if any fails, do NOT run register --status draft
and do NOT run advance โ surface the specific gap to the user and stop. The user may
fix the plan and retry. The register and advance steps below are CONDITIONAL on this
assertion passing.
- One task per active requirement โ the number of checkbox tasks in
task.md
MUST equal the number of active requirements in requirements_graph.json (no more,
no fewer). Surface which requirements have no task, or which tasks have no requirement.
- Hours on every task โ every checkbox MUST carry an
Nh estimate. A task with no
hours figure is incomplete.
- Valid topological order โ no task may
depends: on a requirement that lives in a
LATER layer. Layer order is 0 โ 1 โ 2 โ 3; a dependency must point to the same or an
earlier layer.
python3 -c "
import json, re, sys
rg = json.load(open('.anti-legacy/requirements/requirements_graph.json'))
active = set()
for dom in rg.get('domains', {}).values():
for req_id, req in dom.get('requirements', {}).items():
if req.get('status', 'active') != 'inactive':
active.add(req_id)
task_md = open('.anti-legacy/task.md', encoding='utf-8').read()
# Map each task's req_id to its layer, and capture its hours + declared deps.
# Format: '- [ ] [REQ_ID] File.ext โ Nh [โ depends: REQ_A, REQ_B]'
req_layer, missing_hours, deps = {}, [], {}
cur_layer = None
for line in task_md.splitlines():
h = re.match(r'##\s*Layer\s*(\d+)', line)
if h:
cur_layer = int(h.group(1)); continue
m = re.match(r'-\s*\[[ xX]\]\s*\[([^\]]+)\]', line)
if not m:
continue
req = m.group(1)
req_layer[req] = cur_layer
if not re.search(r'\b\d+(?:\.\d+)?\s*h\b', line):
missing_hours.append(req)
dep_m = re.search(r'depends:\s*(.+)$', line)
deps[req] = [d.strip() for d in re.split(r'[,;]', dep_m.group(1))] if dep_m else []
tasks = set(req_layer)
errors = []
# 1. one task per active requirement
if tasks != active:
no_task = active - tasks
no_req = tasks - active
if no_task: errors.append(f'requirements with no task: {sorted(no_task)}')
if no_req: errors.append(f'tasks with no active requirement: {sorted(no_req)}')
# 2. hours on every task
if missing_hours:
errors.append(f'tasks missing an hours (Nh) estimate: {sorted(missing_hours)}')
# 3. valid topological order โ no dep on a later layer
for req, dlist in deps.items():
for d in dlist:
if d in req_layer and req_layer[d] > req_layer.get(req, -1):
errors.append(f'{req} (layer {req_layer.get(req)}) depends on {d} in later layer {req_layer[d]}')
if errors:
print('BLOCKED: task.md fails the planner done-gate:')
for e in errors:
print(' - ' + e)
print('Fix task.md before proceeding; do NOT advance.')
sys.exit(1)
print(f'Done-gate: {len(tasks)} tasks == {len(active)} active requirements; hours present; topological order valid โ')
"
If the done-gate passes, register and advance:
python3 .anti-legacy/run.py manifest register task-plan \
--path task.md \
--format markdown \
--produced-by anti-legacy:planner \
--status draft \
--depends-on blueprint-json
python3 .anti-legacy/run.py manifest advance planning
Tell the user:
- Task plan is at
.anti-legacy/task.md โ review it before proceeding
- Pipeline paused at GATE_2_PLAN โ share for PM + Tech Lead review
- After sign-off, record the gate with
python3 .anti-legacy/run.py manifest gate GATE_2_PLAN --opinion passed --evaluator "<reviewer>" --rationale "<note>" --evidence task-plan
(the task-plan artifact is the GATE_2_PLAN evidence), then run anti-legacy:swarm
Step 8: Adversarially self-review the task plan (advisory โ AGENTS.md ยง8)
The Step 8 done-gate is mechanical โ it counts tasks against active requirements,
checks every task carries hours, and verifies no dependency points at a later layer. It
cannot see a task whose hours are wildly under-scoped for its rule count, a layer
assignment that misreads a service as a model, or a build order that will deadlock the
swarm. Before you report done, adversarially review the task.md you just produced โ
the topological check is trusting; this is the loop that distrusts it. Resolve the
single-artifact critic target, then dispatch the read-only critic against it:
python3 .anti-legacy/run.py refine_loop descriptor --artifact task-plan --json
That resolves the rendered file + the source data the critic must cross-check (the
requirements-graph ยง2 spine + this artifact's manifest depends_on โ the blueprint).
Dispatch anti-legacy:adversarial-review (single-artifact mode) against the descriptor.
On REVISE/BLOCK, run the bounded loop โ refine_loop decide --verdict <v> --attempt <n> --artifact task-plan โ re-running anti-legacy:planner to fix at source and
re-reviewing, capped at ยง7's three attempts (then recon), or proceed under a stated
--forced override. Advisory: it clears no gate (GATE_2_PLAN is still a human
sign-off) and advances no phase.
Output
.anti-legacy/task.md โ ordered, layered task list with completion checkboxes
- Manifest: phase =
planning, artifact task-plan registered
Next step: Human review of task.md โ anti-legacy:gatekeeper for GATE_2_PLAN sign-off โ anti-legacy:swarm.