T-J2CW-add-epic-entity-task-depends-on-dependencies
Status: closed/done · Impact: medium · Complexity: medium
Give the planning graph two missing primitives: an epic container that sits
between milestone and task (a bundle-of-tasks the team explicitly named), and a
depends_on field on tasks for hard “B cannot start until A is closed”
relationships. Today both concepts get smuggled into prose or into the loose
related field, which is non-directional and lifecycle-blind.
- Three entity types exist under
plugin/entities/:task,milestone,backlog. No grouping primitive between milestone and task. plugin/entities/task/schema.jsonhasrelated(loose, non-directional wikilinks) andtags(free-form). No directional dependency field; “B is blocked by A” is currently encoded informally in prose or inrelated.plugin/entities/milestone/schema.jsonhastasks:“ — a downward-containment array that accepts only task wikilinks. plugin/skills/entities-audit/andplugin/skills/entities-migrate/already walk every entity file for schema-version validation. There is no cross-file relational check yet (e.g. cycle detection).
Proposed
Section titled “Proposed”- A new
epicentity type exists atplugin/entities/epic/withschema.json,template.md, and a minimalwriting.md. Epic id shape:E<NNNN>zero-padded, immutable, filenameE<NNNN>.md. Frontmatter mirrors milestone shape (status enumopen/draft|planned|active,closed/done|partial|superseded|abandoned;title,created,last_reviewed,target_date,tags,related,relevance_note,completion_note).closed/donerequirescompletion_note(same pattern milestone uses). - Epic carries a downward
tasks:“ array. Epic-in-epic nesting is explicitly disallowed for v1 — the pattern accepts task wikilinks only. milestone.taskspattern is relaxed to accept either a task wikilink (YYYY-MM-DD-slug) or an epic wikilink (E<NNNN>). Single mixed-member field — no separatemilestone.epics.task.depends_onis added as a strict-wikilink array accepting task slugs or epic ids. Semantic: hard dependency — the target must reach aclosed/state before this task starts; if parallel work is required, the dependent task branches off the dependency’s branch.relatedstays as the soft, non-directional escape hatch and its description is tightened to make that contrast explicit.- A
plugin/scripts/new_epic.pymirrorsnew_task.py/new_milestone.py: assigns the nextE<NNNN>id, stamps frontmatter, writes the template. /sdlc:entities-auditgains a cross-file cycle check on thedepends_ongraph. Cycle prevention lives in Python (JSON Schema is single-document and cannot see across files). Audit reports any cycle as drift; resolution is manual (edit the offending file)./sdlc:setupcreatesdocs/planning/epics/alongside the existing per-type directories.
Approach
Section titled “Approach”- Add
plugin/entities/epic/schema.jsonmodeled on milestone’s schema (version: 1,schema_versionoptional with0sentinel for closed, immutableid: ^E\d{4}$, milestone-style status enum,tasksarray restricted to task wikilinks only,completion_noterequired whenstatusstarts withclosed/). - Add
plugin/entities/epic/template.mdwith frontmatter scaffold and body sections (Goal, Today, Proposed, Member tasks header, Acceptance criteria, Out of scope, Completion note placeholder). - Add
plugin/entities/epic/writing.md— short stub explaining when to spawn an epic (multiple related tasks need a shared deliverable boundary) vs. when to leave tasks bare under a milestone. - Update
plugin/entities/milestone/schema.json: relax thetasksarray item pattern to^\[\[(E\d{4}|\d{4}-\d{2}-\d{2}-[a-z0-9]+(?:-[a-z0-9]+)*)\]\]$. Update the field description to call out mixed members. - Update
plugin/entities/task/schema.json: adddepends_onarray using the same task-or-epic wikilink pattern,uniqueItems: true. Tighten therelateddescription to emphasize “loose / non-directional — usedepends_onfor hard blocks.” - Add
plugin/scripts/new_epic.pymirroringnew_task.py/new_milestone.py. Id assignment: scandocs/planning/epics/E*.md, take max + 1, zero-pad to four digits. - Extend
/sdlc:entities-audit(its Python helper) with a cycle check that builds thedepends_onadjacency from every task file, runs DFS, and reports each cycle as drift (with the offending file list). Hook into the existing audit-drift reporting path; no new top-level command. - Update
/sdlc:setup(its helper script) to createdocs/planning/epics/idempotently alongside the other per-type directories. - Add fixtures under
plugin/skills/entities-audit/tests/fixtures/:cycle-of-two/(A→B, B→A),cycle-of-three/(A→B→C→A),linear-chain/(A→B→C, no cycle). Wire into the audit test runner. - Update any plugin docs that enumerate entity types (most likely
/sdlc:setupSKILL.md and any top-level README inplugin/entities/).
Files to touch
Section titled “Files to touch”plugin/entities/epic/schema.json(new) — full epic frontmatter schema, milestone-shaped.plugin/entities/epic/template.md(new) — body skeleton + frontmatter scaffold.plugin/entities/epic/writing.md(new) — short note on epic-vs-bare-task-bundle.plugin/entities/milestone/schema.json— relaxtasksitems pattern to accept epic wikilinks; update description.plugin/entities/task/schema.json— adddepends_onarray; tightenrelateddescription.plugin/scripts/new_epic.py(new) — id-assigning creation script mirroringnew_task.py.plugin/skills/entities-audit/(audit helper) — add cross-filedepends_oncycle check; surface as drift.plugin/skills/entities-audit/tests/fixtures/cycle-of-two/(new) — fixture: A↔B.plugin/skills/entities-audit/tests/fixtures/cycle-of-three/(new) — fixture: A→B→C→A.plugin/skills/entities-audit/tests/fixtures/linear-chain/(new) — fixture: A→B→C (no cycle).plugin/skills/setup/SKILL.mdand/or its helper — createdocs/planning/epics/directory on setup.
Acceptance criteria
Section titled “Acceptance criteria”- AC-1:
plugin/entities/epic/schema.jsonexists, declaresversion: 1, and validates an epic frontmatter with the canonical fields (id, status, title, created, tasks=[]). - AC-2:
new_epic.pycreatesdocs/planning/epics/E0001.md(next-id semantics) when run with a headline; the resulting file validates viavalidate_frontmatter.py. - AC-3: A milestone frontmatter with
tasks: ['[T-FQCN-self-driving-orchestrator-loop](/planning/tasks/self-driving-orchestrator-loop/)', '2026-05-19-foo']validates without error; a malformed wikilink fails with a useful message. - AC-4: A task frontmatter with
depends_on: ['2026-05-01-other']validates;depends_on: ['[T-FQCN-self-driving-orchestrator-loop](/planning/tasks/self-driving-orchestrator-loop/)']also validates;depends_on: ['not-a-wikilink']fails. - AC-5:
/sdlc:entities-auditflags a cycle when two tasksdepends_oneach other (fixturecycle-of-two) and three-cycle (cycle-of-three); reports clean for thelinear-chainfixture. - AC-6:
/sdlc:entities-auditreports no false positives on the live corpus underdocs/planning/(no task currently hasdepends_on, so this is trivially true today — but the audit must not crash on absent field). - AC-7: Running
/sdlc:setupon a fresh project createsdocs/planning/epics/alongsidetasks/,milestones/,backlogs/. Idempotent.
Out of scope
Section titled “Out of scope”- Epic-in-epic nesting (deliberately disallowed for v1; relaxing the pattern is a future task once a real need surfaces).
- Task-in-task containment / “task is an epic if it has children” (rejected during design — conditional schema rules + tool-side branching outweighed the elegance).
/sdlc:task-workintegration ofdepends_on(warn when blockers are still open) — separate follow-up task; this one ships the schema field only./sdlc:task-reviewsurfacing of newly-unblocked tasks — separate follow-up.- A reciprocal
blocksfield on tasks (intentionally one-direction; deriveblocksby graph scan if/when needed). - Retrofitting existing tasks or milestones to use the new fields. New work uses them; existing work stays as-is.
- A new
/sdlc:epic-new//sdlc:epic-workskill family. Scripts + schemas first; skills follow.
Dependencies
Section titled “Dependencies”- none
Discovery context
Section titled “Discovery context”Design conversation on 2026-05-19 about extending the planning graph for dependency tracking and grouping (originally proposed as “task contains task” with an epic label). The unified-task model was rejected because it pushes conditional rules into every consumer (status semantics, implementation-ready contract, completion_note shape, tool-side leaf-vs-container branching). Decision: epic as a separate entity mirroring milestone’s container pattern, milestone.tasks accepts mixed members, task.depends_on for hard one-direction dependencies, cycle prevention in Python (JSON Schema is single-document by design). See conversation thread.
Post-mortem
Section titled “Post-mortem”Captured by /sdlc:task-work on 2026-05-19. PR: pending.
Acceptance criteria coverage
Section titled “Acceptance criteria coverage”- AC-1: auto — epic schema present at
plugin/entities/epic/schema.json, declaresversion: 1, validates synthetic epics viavalidate_frontmatter.py. - AC-2: agent-manual — sub-agent created
E0001vianew_epic.py, validated viavalidate_frontmatter.py, deleted the smoke artifact before commit. - AC-3: agent-manual — sub-agent validated a synthetic milestone with
tasks: ['[T-FQCN-self-driving-orchestrator-loop](/planning/tasks/self-driving-orchestrator-loop/)', '2026-05-19-foo']; malformed wikilink correctly rejected with pattern error. - AC-4: agent-manual — sub-agent validated tasks carrying
depends_on:2026-05-01-other“ anddepends_on: [T-FQCN-self-driving-orchestrator-loop](/planning/tasks/self-driving-orchestrator-loop/); non-wikilink string correctly rejected. - AC-5: auto —
plugin/skills/entities-audit/tests/run_evals.pyexercisescycle-of-two/,cycle-of-three/(cycles flagged), andlinear-chain/(clean). 7/7 eval cases passed. - AC-6: auto —
audit_entities.pyover the live worktree corpus reports only pre-existing schema_version drift onM0001.mdand2026-05-17-add-astro-docs-site.md; no false positives from cycle/broken-link checks; tasks withoutdepends_onaudited without crash. - AC-7: agent-manual — sub-agent verified
setup_planning.pyon a fresh/tmpproject createsdocs/planning/epics/alongside the other entity dirs; second run reports them as existing (idempotent). Setup is schema-discovery-driven, so the directory falls out for free from the schema’s existence.
What worked
Section titled “What worked”- Mirroring
milestone/schema.json+new_milestone.pymade the epic implementation mechanical — almost line-for-line translation with the id pattern swapped. setup_planning.py’s schema-discovery design meant AC-7 satisfied itself the moment the epic schema landed.- Eval-suite-based fixtures (cycle-of-two, cycle-of-three, linear-chain) gave AC-5 a clean automated verification path with no bespoke test harness.
- Splitting the work into six logical commits on the feature branch kept the diff legible per concern.
Friction and automation gaps
Section titled “Friction and automation gaps”/sdlc:task-workstep 3 setsstatus: in-progressBEFORE step 5 invokes/sdlc:task-ensure-ready. The implementation-ready contract (plugin/entities/task/implementation-ready.md) explicitly disqualifiesin-progress— so strict contract application makes task-work self-defeating (every invocation would downshift toproposed/needs-definitionand stop). Applied the pragmatic interpretation here. Either the contract should acceptin-progressas past-the-gate, or task-work should defer the status flip until after ensure-ready passes. → T-Z3ZZ-task-work-defers-status-flip-until-ensure-ready- Template files (
plugin/entities/*/template.md) failvalidate_frontmatter.pydirectly because the HTML comment block precedes the frontmatter and the parser’s regex is anchored to\A---. Pre-existing behavior, not introduced here, but surprised the sub-agent when it tried to validate the new epic template in place. Either relax the regex to skip a leading HTML comment or document templates as non-validatable-in-place. → T-3K8K-templates-html-comment-after-frontmatter audit_entities.pyre-parses every task file insidecollect_depends_on_graphbecauseFileReportdoesn’t carry the parsed frontmatter forward. Acceptable today (corpus is tiny), but worth memoizing if the audit ever grows to thousands of entries. → T-QIBD-audit-memoizes-parsed-frontmatter- The skill expects
just full-check/just cifor quality checks (/sdlc:task-workstep 7). This repo has nojustfile— checks here arevalidate_frontmatter.py,audit_entities.py, andrun_evals.py. Worth parameterizing the skill’s check commands per-project or making the skill discover them. → T-T879-task-work-uses-per-project-quality-checks - The Bash tool’s prompt-cache appears to occasionally return stale output for similar-looking
commands (
git branch --list "<pattern>"once returnedmain, then empty on retry — likely cached from an earliergit branch --show-currentcall). Could not reproduce reliably; flagging as observed-once-need-eyes. - Sub-agent made two off-spec edits that the spec should probably anticipate: (1)
new_milestone.py --taskflag relaxed to accept epic ids (otherwise the script would reject legitimate inputs once milestone.tasks accepted epics), and (2)setup/SKILL.mddescription updated to list the actual ships-today entities (backlog/epic/milestone/task) rather than the outdated “today: task” claim. Both reasonable; future tasks that change a schema’s contained-types should explicitly call out script-flag updates as in-scope. → T-KLIL-task-new-flags-cascading-schema-edits
Spawned follow-up tasks
Section titled “Spawned follow-up tasks”- T-Z3ZZ-task-work-defers-status-flip-until-ensure-ready — Reorder
/sdlc:task-workso ensure-ready runs before thein-progressflip (created). - T-3K8K-templates-html-comment-after-frontmatter — Move HTML comment block after frontmatter in all templates (reframed from “relax validator” via PR #18 review — leading HTML comments break standard markdown parsers, so fix the templates not the parser).
- T-QIBD-audit-memoizes-parsed-frontmatter — Carry parsed frontmatter through
FileReportsocollect_depends_on_graphdoesn’t re-parse (created). - T-T879-task-work-uses-per-project-quality-checks — Replace hard-coded
just full-check/just ciwith a per-project config (created). - T-KLIL-task-new-flags-cascading-schema-edits — Extend task-template guidance to call out the
new_*.pyflag + skill-description cascade when a schema’s contained-types change (created; flagged on PR review as misframed — pending close-or-reframe decision). - T-ZU6V-new-scripts-derive-patterns-from-schema — Eliminate regex duplication between
entities/*/schema.jsonpatterns andnew_*.pyvalidation constants (spawned from PR #18 review).