@caiqueoak/flow 0.6.0 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (173) hide show
  1. package/README.md +65 -43
  2. package/dist/artifacts/backlog.d.mts +16 -0
  3. package/dist/artifacts/backlog.d.mts.map +1 -0
  4. package/dist/artifacts/backlog.mjs +193 -0
  5. package/dist/artifacts/backlog.mjs.map +1 -0
  6. package/dist/artifacts/document.d.mts +6 -0
  7. package/dist/artifacts/document.d.mts.map +1 -0
  8. package/dist/artifacts/document.mjs +39 -0
  9. package/dist/artifacts/document.mjs.map +1 -0
  10. package/dist/artifacts/engineering.d.mts +5 -0
  11. package/dist/artifacts/engineering.d.mts.map +1 -0
  12. package/dist/artifacts/engineering.mjs +27 -0
  13. package/dist/artifacts/engineering.mjs.map +1 -0
  14. package/dist/artifacts/gates.d.mts +7 -0
  15. package/dist/artifacts/gates.d.mts.map +1 -0
  16. package/dist/artifacts/gates.mjs +42 -0
  17. package/dist/artifacts/gates.mjs.map +1 -0
  18. package/dist/artifacts/implementation-plan.d.mts +11 -0
  19. package/dist/artifacts/implementation-plan.d.mts.map +1 -0
  20. package/dist/artifacts/implementation-plan.mjs +30 -0
  21. package/dist/artifacts/implementation-plan.mjs.map +1 -0
  22. package/dist/artifacts/prd.d.mts +4 -0
  23. package/dist/artifacts/prd.d.mts.map +1 -0
  24. package/dist/artifacts/prd.mjs +13 -0
  25. package/dist/artifacts/prd.mjs.map +1 -0
  26. package/dist/artifacts/review.d.mts +11 -0
  27. package/dist/artifacts/review.d.mts.map +1 -0
  28. package/dist/artifacts/review.mjs +27 -0
  29. package/dist/artifacts/review.mjs.map +1 -0
  30. package/dist/artifacts/spec.d.mts +14 -0
  31. package/dist/artifacts/spec.d.mts.map +1 -0
  32. package/dist/artifacts/spec.mjs +50 -0
  33. package/dist/artifacts/spec.mjs.map +1 -0
  34. package/dist/artifacts/state.d.mts +44 -0
  35. package/dist/artifacts/state.d.mts.map +1 -0
  36. package/dist/artifacts/state.mjs +60 -0
  37. package/dist/artifacts/state.mjs.map +1 -0
  38. package/dist/artifacts/tasks.d.mts +10 -0
  39. package/dist/artifacts/tasks.d.mts.map +1 -0
  40. package/dist/artifacts/tasks.mjs +107 -0
  41. package/dist/artifacts/tasks.mjs.map +1 -0
  42. package/dist/artifacts/work-items.d.mts +21 -0
  43. package/dist/artifacts/work-items.d.mts.map +1 -0
  44. package/dist/artifacts/work-items.mjs +81 -0
  45. package/dist/artifacts/work-items.mjs.map +1 -0
  46. package/dist/cli/command-registry.d.mts +498 -0
  47. package/dist/cli/command-registry.d.mts.map +1 -0
  48. package/dist/cli/command-registry.mjs +230 -0
  49. package/dist/cli/command-registry.mjs.map +1 -0
  50. package/dist/cli.d.mts +3 -0
  51. package/dist/cli.d.mts.map +1 -0
  52. package/dist/cli.mjs +48 -0
  53. package/dist/cli.mjs.map +1 -0
  54. package/dist/commands/doctor.d.mts +21 -0
  55. package/dist/commands/doctor.d.mts.map +1 -0
  56. package/dist/commands/doctor.mjs +79 -0
  57. package/dist/commands/doctor.mjs.map +1 -0
  58. package/dist/commands/gates.d.mts +14 -0
  59. package/dist/commands/gates.d.mts.map +1 -0
  60. package/dist/commands/gates.mjs +136 -0
  61. package/dist/commands/gates.mjs.map +1 -0
  62. package/dist/commands/graph.d.mts +11 -0
  63. package/dist/commands/graph.d.mts.map +1 -0
  64. package/dist/commands/graph.mjs +78 -0
  65. package/dist/commands/graph.mjs.map +1 -0
  66. package/dist/commands/init.d.mts +6 -0
  67. package/dist/commands/init.d.mts.map +1 -0
  68. package/dist/commands/init.mjs +126 -0
  69. package/dist/commands/init.mjs.map +1 -0
  70. package/dist/commands/migrate.d.mts +27 -0
  71. package/dist/commands/migrate.d.mts.map +1 -0
  72. package/dist/commands/migrate.mjs +379 -0
  73. package/dist/commands/migrate.mjs.map +1 -0
  74. package/dist/commands/operations.d.mts +13 -0
  75. package/dist/commands/operations.d.mts.map +1 -0
  76. package/dist/commands/operations.mjs +340 -0
  77. package/dist/commands/operations.mjs.map +1 -0
  78. package/dist/commands/route.d.mts +12 -0
  79. package/dist/commands/route.d.mts.map +1 -0
  80. package/dist/commands/route.mjs +45 -0
  81. package/dist/commands/route.mjs.map +1 -0
  82. package/dist/commands/schemas.d.mts +5 -0
  83. package/dist/commands/schemas.d.mts.map +1 -0
  84. package/dist/commands/schemas.mjs +14 -0
  85. package/dist/commands/schemas.mjs.map +1 -0
  86. package/dist/commands/status.d.mts +4 -0
  87. package/dist/commands/status.d.mts.map +1 -0
  88. package/dist/commands/status.mjs +18 -0
  89. package/dist/commands/status.mjs.map +1 -0
  90. package/dist/commands/sync.d.mts +18 -0
  91. package/dist/commands/sync.d.mts.map +1 -0
  92. package/dist/commands/sync.mjs +29 -0
  93. package/dist/commands/sync.mjs.map +1 -0
  94. package/dist/commands/trace.d.mts +30 -0
  95. package/dist/commands/trace.d.mts.map +1 -0
  96. package/dist/commands/trace.mjs +111 -0
  97. package/dist/commands/trace.mjs.map +1 -0
  98. package/dist/commands/validate.d.mts +8 -0
  99. package/dist/commands/validate.d.mts.map +1 -0
  100. package/dist/commands/validate.mjs +82 -0
  101. package/dist/commands/validate.mjs.map +1 -0
  102. package/dist/domain/contracts.d.mts +203 -0
  103. package/dist/domain/contracts.d.mts.map +1 -0
  104. package/dist/domain/contracts.mjs +126 -0
  105. package/dist/domain/contracts.mjs.map +1 -0
  106. package/dist/domain/types.d.ts +45 -0
  107. package/dist/domain/types.d.ts.map +1 -0
  108. package/dist/domain/types.js +2 -0
  109. package/dist/domain/types.js.map +1 -0
  110. package/dist/entry.d.ts +3 -0
  111. package/dist/entry.d.ts.map +1 -0
  112. package/dist/entry.js +4 -0
  113. package/dist/entry.js.map +1 -0
  114. package/dist/shared/cli-io.d.mts +15 -0
  115. package/dist/shared/cli-io.d.mts.map +1 -0
  116. package/dist/shared/cli-io.mjs +83 -0
  117. package/dist/shared/cli-io.mjs.map +1 -0
  118. package/dist/shared/profiles.d.mts +17 -0
  119. package/dist/shared/profiles.d.mts.map +1 -0
  120. package/dist/shared/profiles.mjs +25 -0
  121. package/dist/shared/profiles.mjs.map +1 -0
  122. package/dist/shared/project-config.d.mts +21 -0
  123. package/dist/shared/project-config.d.mts.map +1 -0
  124. package/dist/shared/project-config.mjs +41 -0
  125. package/dist/shared/project-config.mjs.map +1 -0
  126. package/dist/shared/project-path.d.mts +3 -0
  127. package/dist/shared/project-path.d.mts.map +1 -0
  128. package/dist/shared/project-path.mjs +9 -0
  129. package/dist/shared/project-path.mjs.map +1 -0
  130. package/dist/shared/skill-installer.d.mts +2 -0
  131. package/dist/shared/skill-installer.d.mts.map +1 -0
  132. package/dist/shared/skill-installer.mjs +33 -0
  133. package/dist/shared/skill-installer.mjs.map +1 -0
  134. package/package.json +16 -8
  135. package/schemas/backlog.schema.json +85 -0
  136. package/schemas/config.schema.json +29 -0
  137. package/schemas/gates.schema.json +17 -0
  138. package/schemas/review.schema.json +28 -0
  139. package/schemas/tasks.schema.json +66 -0
  140. package/skills/flow/SKILL.md +10 -11
  141. package/skills/flow/build/step-01-execute-task.md +3 -3
  142. package/skills/flow/discovery/bmad-comparison.md +5 -0
  143. package/skills/flow/discovery/step-01-project.md +8 -3
  144. package/skills/flow/engineering/profiles/readability-first.md +1 -0
  145. package/skills/flow/engineering/step-02-synthesize.md +2 -2
  146. package/skills/flow/invariants.md +4 -4
  147. package/skills/flow/planning/step-01-create-tasks.md +3 -0
  148. package/skills/flow/planning/step-01-plan-work-item.md +2 -2
  149. package/skills/flow/planning/step-02-prepare-plan.md +1 -1
  150. package/skills/flow/review/step-01-review-work-item.md +2 -2
  151. package/skills/flow/specification/step-01-deepen-spec.md +7 -0
  152. package/src/artifacts/backlog.mjs +0 -157
  153. package/src/artifacts/document.mjs +0 -30
  154. package/src/artifacts/engineering.mjs +0 -26
  155. package/src/artifacts/gates.mjs +0 -28
  156. package/src/artifacts/implementation-plan.mjs +0 -27
  157. package/src/artifacts/prd.mjs +0 -12
  158. package/src/artifacts/state.mjs +0 -63
  159. package/src/artifacts/tasks.mjs +0 -90
  160. package/src/cli.mjs +0 -62
  161. package/src/commands/gates.mjs +0 -71
  162. package/src/commands/graph.mjs +0 -92
  163. package/src/commands/init.mjs +0 -111
  164. package/src/commands/migrate.mjs +0 -224
  165. package/src/commands/route.mjs +0 -135
  166. package/src/commands/status.mjs +0 -35
  167. package/src/commands/trace.mjs +0 -58
  168. package/src/commands/validate.mjs +0 -197
  169. package/src/shared/cli-io.mjs +0 -86
  170. package/src/shared/profiles.mjs +0 -27
  171. package/src/shared/project-config.mjs +0 -39
  172. package/src/shared/project-path.mjs +0 -10
  173. package/src/shared/skill-installer.mjs +0 -34
@@ -1,21 +1,20 @@
1
1
  ---
2
2
  name: flow
3
- description: Repository-resumable software delivery through discovery, approved engineering, complete backlog planning, human-approved implementation plans, serial implementation and review. Use /flow to start or continue this workflow.
3
+ description: CLI-first repository-resumable delivery through deep discovery, approved product/engineering contracts, progressive specs, atomic task commits and review. Use /flow to start or continue.
4
4
  ---
5
5
 
6
6
  # Flow
7
7
 
8
- /flow is the only public agent workflow. Incorporate current developer intent before routing; repository routing does not authorize ignoring new requests.
8
+ Read `invariants.md` once per invocation. Use only the project-local CLI (`npx --no-install flow`); never fetch or substitute another Flow version automatically.
9
9
 
10
- Read invariants.md once per invocation. All Flow CLI commands MUST use the project-local installation: `npx --no-install flow`. If unavailable, stop and ask for local installation; never use a global executable or fetch a different package automatically.
10
+ 1. Run `npx --no-install flow doctor --quick --json`. A version difference requires an explicit user decision and, when needed, `flow migrate --plan` before `--apply`.
11
+ 2. Run `npx --no-install flow sync`, then `npx --no-install flow validate --json`, then `npx --no-install flow route --json`.
12
+ 3. Read the returned instruction and every `required_context` file. Planning, implementation and review require the full approved `engineering.md` immediately before the step.
13
+ 4. Execute only that step. Use CLI commands for IDs, dependencies, blockers, approvals, task evidence and every other deterministic mutation. The agent owns qualitative product, architecture, spec, decomposition and trade-off reasoning.
14
+ 5. Run the indicated minimum validation, persist through the CLI, then route again until a real human decision, external action, unrecoverable blocker or completion.
11
15
 
12
- 1. Run `npx --no-install flow route --json`.
13
- 2. Read the returned instruction from this installed skill and every returned required_context file. Planning, implementation and review require rereading the FULL .flow/docs/engineering.md immediately before that step, including after resumption.
14
- 3. Execute only the routed step, persist canonical artifacts and run its required checks.
15
- 4. Route again. Continue until an actual human decision, external action, unrecoverable blocker or completion.
16
+ Never approve your own proposal or infer consent. Approval records the exact document revision and ISO timestamp. A revision requires new approval.
16
17
 
17
- If stopped for approval, present the exact proposal and ask explicitly. Never approve your own work or treat silence as consent. Human approval must be persisted with status: approved and approved_at: ISO timestamp in the approved document. Clear state.stop_reason only when the current message resolves its decision. Revisions require a new approval, not copying an old approved_at.
18
+ Artifact ownership: every `_flow/work-items/W###-*/` directory is canonical and always contains `spec.md`, `tasks.yaml`, `implementation-plan.md`, and `review.yaml`. Spec frontmatter owns work-item metadata and `maturity`; lifecycle is derived from it, tasks, dependencies, blockers and review. `gates.yaml` contains executable checks. `backlog.yaml` and `graph.md` under `_flow/generated/` are disposable projections: `sync` only reads canonical work-items and only writes that directory. Never commit generated projections. A task is completed by one canonical subject `type(domain): description [W###-T###]`; review completion uses `chore(domain): complete review [W###]`. A deliberate review-time spec edit belongs in that review commit, and nothing outside its work-item folder may enter it. Preserve completed history. Later corrections become a new task or maintenance work-item.
18
19
 
19
- Artifact owners: docs/prd.md product; docs/engineering.md engineering; backlog.yaml work-item DAG; work-items/W###-slug/spec.md scope; tasks.yaml task DAG; implementation-plan.md approved approach; state.yaml cursor; gates.yaml mechanical checks; docs/graph.md derived. Preserve completed history; changed completed scope becomes new maintenance work.
20
-
21
- Engineering changes go through reconcile/step-01-reconcile.md: propose the change, obtain approval and invalidate affected plans. A profile is bootstrap input, never a second policy engine competing with approved engineering.
20
+ Engineering changes go through reconcile. A profile is bootstrap input, never a competing policy engine. Product-impacting technical decisions explicitly update the affected product contract.
@@ -1,9 +1,9 @@
1
1
  # Build — execute one approved task
2
2
 
3
- Read the FULL engineering.md, spec, tasks and approved current implementation plan. Verify current route and approval hashes before application edits. Persist both selected work item and task in_progress; no other mutating task may be active.
3
+ Read the applicable global PRD rules, the FULL engineering.md, spec, tasks and approved current implementation plan. Verify current route and approval hashes before application edits. Persist both selected work item and task in_progress; no other mutating task may be active.
4
4
 
5
5
  Implement the bounded approved change for readability: semantic naming, mandatory SRP, low coupling, high cohesion, locality and justified complexity. If the approach must change materially, revise the plan and ask approval before continuing.
6
6
 
7
- Run targeted tests and npx --no-install flow validate --pre-commit W###-T###. Mark completed only after checks pass. Code tasks get exactly one primary implementation commit with separate trailers Flow-Work-Item: W### and Flow-Task: W###-T###. Non-code tasks use none. Do not assign legacy to new work.
7
+ Inspect the worktree, stage only this task's bounded implementation, focused tests, necessary contracts/migrations and required documentation, then compare the staged diff with the approved spec and plan. Reject unrelated changes. Run the smallest safe tests and `npx --no-install flow validate --pre-commit W###-T###`.
8
8
 
9
- Synchronize metadata and graph, validate traceability with npx --no-install flow trace W###-T###, validate again and route to the next task/review. Do not yield merely for status.
9
+ Commit and complete the task atomically through `npx --no-install flow task commit W###-T### --message "type(domain): description [W###-T###]" --files path/to/file,...`. The command requires the declared files to match the staged set exactly, creates the one canonical implementation commit with matching `Flow-Work-Item` and `Flow-Task` trailers, and persists task completion in that commit. Never create the implementation commit manually and never edit task completion metadata directly. On failure, recover with `flow trace`/`flow doctor` rather than recreating implementation. Validate traceability, validate again and route onward.
@@ -0,0 +1,5 @@
1
+ # BMAD comparison record
2
+
3
+ Adopted: conversational elicitation proportional to ambiguity, contextual investigation, explicit gap analysis, persistent decisions/assumptions/questions, alternatives before convergence, and a product brief before detailed requirements.
4
+
5
+ Rejected: BMAD's role topology, ceremony count, architecture workflow and large document templates. They add orchestration/context cost without improving Flow's contracts. Flow retains one resumable workflow, product-first discovery and separate engineering ownership.
@@ -1,5 +1,10 @@
1
- # Discovery — define product
1
+ # Discovery — resolve product ambiguity
2
2
 
3
- Read current intent and existing product evidence. Create .flow/docs/prd.md as a coherent proposal, not architecture. Frontmatter: schema_version: 1, status: draft. Headings: # Product Requirements; ## Purpose; ## Users; ## Scope; ## Requirements; ## Constraints; ## Non-goals.
3
+ Scale depth to ambiguity, not document length. Persist decisions, discarded alternatives, assumptions, evidence and open questions after each pass so discovery resumes without repetition.
4
4
 
5
- Create state.yaml with schema_version: 1, execution.phase: discovery, active.work_item/task: null, stop_reason: null, migration.status: not_required. Do not create incomplete work-item folders or implement code. Present scope and ask PRD approval through the next routed step.
5
+ 1. Problem: users, context, evidence, motivation and desired outcomes.
6
+ 2. Product: journeys, relevant personas, business rules, primary cases, exceptions, risks, metrics, alternatives and non-goals.
7
+ 3. Viability: investigate only technical questions capable of changing product, cost, schedule, risk or feasibility.
8
+ 4. Consolidation: close gaps that prevent a PRD, engineering analysis or backlog decomposition.
9
+
10
+ Do not define the full architecture here. If viability changes product, record the product decision explicitly. Read `bmad-comparison.md` for the adopted/rejected prompt-design rationale.
@@ -16,5 +16,6 @@ Load this reusable preference template only when synthesizing or explicitly revi
16
16
  - Modularize at meaningful boundaries. Do not introduce interfaces, factories, wrappers, layers or infrastructure without demonstrable value exceeding their complexity and maintenance cost.
17
17
  - State the concrete return on complexity before adding it. Defer speculative abstractions; optimize for reading and changing code.
18
18
  - Verify behavior, domain logic and external boundaries. Use ecosystem tooling for deterministic checks; qualitative review is not a control-plane fact.
19
+ - Organize tests by module, domain, bounded context or feature when the stack supports it. Engineering must map changed paths/contracts to the smallest safe test commands. Escalate to the broad suite for cross-cutting/shared infrastructure, broad contracts, releases, unknown impact or when no trustworthy mapping exists; never invent a narrow command.
19
20
 
20
21
  These preferences are not a fixed architecture. Reconcile them with PRD constraints, public contracts, security, stack conventions and explicit exceptions before seeking approval.
@@ -2,7 +2,7 @@
2
2
 
3
3
  Read the approved PRD, config, full existing engineering evidence, repository tooling/contracts, profiles/readability-first.md and technology-defaults.md. Use existing_code_policy improve to critique accidental structure while preserving behavior; preserve gives consistent patterns stronger weight. Neither authorizes refactoring.
4
4
 
5
- Recommend a complete but proportional contract. Frontmatter: schema_version: 1, status: draft, baseline.profile: flow/readability-first@1, baseline.existing_code_policy: improve|preserve|not_applicable. Include exact headings:
5
+ Recommend a complete but proportional technical contract. Keep global product behavior in the PRD and bounded delivery behavior in work-item specs; engineering records only the technical/code/infra constraints that realize them. Frontmatter: schema_version: 1, status: draft, baseline.profile: flow/readability-first@1, baseline.existing_code_policy: improve|preserve|not_applicable. Include exact headings:
6
6
 
7
7
  # Engineering
8
8
 
@@ -34,4 +34,4 @@ Recommend a complete but proportional contract. Frontmatter: schema_version: 1,
34
34
 
35
35
  Materialize concrete paths, responsibilities, dependency direction, naming examples, stack conventions, tests and justified exceptions. Explicitly state not applicable/deferred areas and why. Review SRP, low coupling/high cohesion, colocated vertical slices, readability and complexity ROI. Do not mandate ceremony or speculative abstraction.
36
36
 
37
- Draft gates.yaml schema_version: 1 with only command/builtin gates using real project tooling. Qualitative review stays instructions, not agentic gates or regex coverage. Do not install tooling or change application code before approval. Route to approval.
37
+ Draft gates.yaml schema_version: 2 with only command/builtin gates using real project tooling. Qualitative review stays instructions, not agentic gates or regex coverage. Do not install tooling or change application code before approval. Route to approval.
@@ -1,16 +1,16 @@
1
1
  # Flow invariants
2
2
 
3
- 1. Persist lifecycle states only pending, in_progress and completed. Ready/Blocked are derived.
3
+ 1. Persist lifecycle states only pending, in_progress and completed. Eligible/Blocked are derived.
4
4
  2. One mutating work item and task at a time across the project. Read-only research/review may be parallel; Flow does not orchestrate concurrent mutating worktrees.
5
5
  3. PRD approval precedes engineering approval; both precede complete backlog creation.
6
- 4. Create every known work-item folder, spec.md and tasks.yaml before any implementation. No just-in-time missing work-item artifacts in native projects.
7
- 5. Every work item needs a human-approved implementation-plan.md before application code changes.
6
+ 4. Keep the full outlined backlog, but create a work-item spec on demand; create tasks.yaml and implementation-plan.md only once its spec is ready.
7
+ 5. Every ready work item needs a human-approved implementation-plan.md before application code changes.
8
8
  6. Plans bind to exact SHA256 engineering and spec text; stale approval cannot authorize implementation.
9
9
  7. Read the full engineering.md before planning, implementation and review. Missing/unapproved engineering blocks code.
10
10
  8. SRP, semantic naming, cohesion, low coupling, locality and justified complexity guide qualitative review; do not fake mechanical proof with Markdown regex.
11
11
  9. Dependencies belong in depends_on. External approvals belong in structured unresolved blockers.
12
12
  10. Regenerate graph through npx --no-install flow graph; never hand-edit derived output.
13
- 11. New code tasks use implementation: commit and both Flow trailers. Non-code tasks use none. legacy is reserved for completed migrated tasks, never new work.
13
+ 11. Each mutating task is completed by `flow task commit`, which creates exactly one implementation commit with the permanent ID in the subject and both Flow trailers. Native tasks never persist traceability fields; `provenance: legacy_migration` is reserved for completed migrated tasks.
14
14
  12. Completion requires acceptance, verification, review and traceability. Completed changes become fix tasks or maintenance work.
15
15
  13. Pending migration reconciliation blocks all normal planning/build until legacy constraints reach canonical owners.
16
16
  14. Invoke only local npx --no-install flow commands. Progress alone is not a terminal stop.
@@ -0,0 +1,3 @@
1
+ # Planning — create tasks after spec readiness
2
+
3
+ Read the complete ready spec and engineering contract. Decompose repository changes into independently coherent tasks with explicit dependencies. Use `flow task create`; task identity and lifecycle belong to the CLI, while Git evidence is derived from the canonical task commit. Record non-repository work in the plan instead of inventing task fields that are not part of `tasks.yaml`. Then prepare the implementation plan and obtain explicit approval before starting a task.
@@ -1,8 +1,8 @@
1
1
  # Planning — materialize the complete backlog
2
2
 
3
- Reread the FULL approved engineering.md and PRD. Decompose all known approved scope into bounded work items and dependencies; do not invent future scope. Create backlog.yaml schema_version: 2, work_items with id W###, folder W###-kebab-case, kind feature|technical|maintenance, title, state pending, priority positive integer, depends_on and blockers.
3
+ Reread the FULL approved engineering.md and PRD. Decompose all known approved scope into bounded work items and dependencies; do not invent future scope. Create backlog.yaml schema_version: 3 with outlined work items using id W###, folder W###-kebab-case, kind feature|technical|maintenance, title, state pending, priority positive integer, spec_maturity, depends_on and blockers.
4
4
 
5
- Create EVERY known work-item folder with spec.md and tasks.yaml before implementation. Existing completed artifacts stay intact. Specs include ## Status, ## Goal, ## Scope, ## Non-goals, ## Requirements, ## Acceptance criteria, ## Decisions, ## Implementation, ## Final outcome, ## Validation and ## Follow-up. Tasks: schema_version: 1, work_item: W###, tasks with local T### IDs, title, state pending, depends_on, implementation commit|none. Preserve already completed task history.
5
+ Do not eagerly create folders, specs, tasks or plans for outlined work. When a selected item is ready, create its spec, then tasks.yaml schema_version: 2 with local T### IDs, title, state pending, depends_on and traceability commit|none. Preserve completed history and legacy_commit provenance; never use the retired implementation task field.
6
6
 
7
7
  Structured blockers: id stable-kebab-case, type external_action|consequential_decision, description, status unresolved|resolved. Do not encode dependencies as external blockers.
8
8
 
@@ -1,6 +1,6 @@
1
1
  # Planning — draft an implementation plan
2
2
 
3
- Reread the FULL approved engineering.md, selected spec/tasks, affected code and tests. Write implementation-plan.md BEFORE application edits. Frontmatter: schema_version: 1, work_item: W###, status: draft, engineering_revision and spec_revision SHA256 of the exact UTF-8 files. Calculate with Node crypto; never guess.
3
+ Reread the applicable global PRD rules, the FULL approved engineering.md, selected spec/tasks, affected code and tests. The PRD supplies product constraints; the spec supplies only this work item's bounded delivery scope. Write implementation-plan.md BEFORE application edits. Frontmatter: schema_version: 1, work_item: W###, status: draft, engineering_revision and spec_revision SHA256 of the exact UTF-8 files. Calculate with Node crypto; never guess.
4
4
 
5
5
  Use exact headings:
6
6
 
@@ -1,5 +1,5 @@
1
1
  # Review — verify delivery
2
2
 
3
- Reread FULL engineering.md, frozen scope, tasks and approved plan. Review acceptance, regressions, SRP, semantic naming, low coupling/high cohesion, vertical-slice locality and complexity ROI. Explain concrete defects, not stylistic alternatives. Run actual tests and npx --no-install flow validate; never write fake gate evidence.
3
+ Reread applicable global PRD rules, FULL engineering.md, frozen scope, tasks and approved plan. Review acceptance, regressions, SRP, semantic naming, low coupling/high cohesion, vertical-slice locality and complexity ROI. Explain concrete defects, not stylistic alternatives. Run actual tests and npx --no-install flow validate; never write fake gate evidence.
4
4
 
5
- Defects become new fix tasks. Material approach changes require revised plan approval; never rewrite completed task history. Complete only after all tasks, acceptance, gates, qualitative review and traceability pass. Populate spec outcome/implementation/validation; synchronize backlog/state/graph, validate and route again.
5
+ Defects become new fix tasks. Material approach changes require revised plan approval; never rewrite completed task history. After all tasks, acceptance, gates, qualitative review and traceability pass, run `npx --no-install flow work-item review-complete W### --domain domain`, replacing `domain` with the repository's conventional commit scope. It only finalizes this item; then run `flow route`, which alone selects the next item or finishes the project. Populate spec outcome/implementation/validation and validate again.
@@ -0,0 +1,7 @@
1
+ # Specification — deepen one eligible work-item
2
+
3
+ Read the PRD, full engineering contract and selected backlog outline. Do not deepen other work-items.
4
+
5
+ Create `spec.md` with `# Work Item Specification` and sections Problem, Scope, Non-goals, Requirements, observable Acceptance criteria, Contracts, Data and APIs, Edge cases, Risks, Decisions, and Gates. Resolve ambiguity that would make planning unsafe. Keep `spec_maturity` exclusively in backlog.
6
+
7
+ Validate, then run `flow work-item promote W###`. Only then create tasks and a plan. Contract, dependency or spec changes invalidate unexecuted plans; never rewrite completed history.
@@ -1,157 +0,0 @@
1
- import { parseDocument } from 'yaml';
2
-
3
- export class ArtifactValidationError extends Error {
4
- constructor(message) {
5
- super(message);
6
- this.name = 'ArtifactValidationError';
7
- }
8
- }
9
-
10
- const KINDS = new Set(['feature', 'technical', 'maintenance']);
11
- const STATES = new Set(['pending', 'in_progress', 'completed']);
12
- const WORK_ITEM_ID = /^W\d{3,}$/;
13
- const WORK_ITEM_FOLDER = /^W\d{3,}-[a-z0-9]+(?:-[a-z0-9]+)*$/;
14
-
15
- function fail(message) {
16
- throw new ArtifactValidationError(message);
17
- }
18
-
19
- function requireObject(value, label) {
20
- if (!value || typeof value !== 'object' || Array.isArray(value)) fail(`${label} must be a mapping.`);
21
- return value;
22
- }
23
-
24
- function requireString(value, label) {
25
- if (typeof value !== 'string' || !value.trim()) fail(`${label} must be a non-empty string.`);
26
- return value.trim();
27
- }
28
-
29
- export function parseBacklog(text, { source = 'backlog.yaml' } = {}) {
30
- const document = parseDocument(text, { prettyErrors: false, uniqueKeys: true });
31
- if (document.errors.length) fail(`${source} is invalid: ${document.errors[0].message}`);
32
- const backlog = requireObject(document.toJS(), source);
33
- if (backlog.schema_version !== 2) fail(`${source} schema_version must be 2.`);
34
- if (!Array.isArray(backlog.work_items)) fail(`${source} work_items must be a list.`);
35
-
36
- const ids = new Set();
37
- const items = backlog.work_items.map((raw, index) => {
38
- const label = `work_items[${index}]`;
39
- const item = requireObject(raw, label);
40
- const id = requireString(item.id, `${label}.id`);
41
- if (!WORK_ITEM_ID.test(id)) fail(`${label}.id must use W followed by a zero-padded numeric sequence, e.g. W015.`);
42
- if (ids.has(id)) fail(`${source} contains duplicate work-item ID '${id}'.`);
43
- ids.add(id);
44
-
45
- const folder = requireString(item.folder ?? item.path, `${label}.folder`);
46
- if (!WORK_ITEM_FOLDER.test(folder))
47
- fail(`${label}.folder must use W###-kebab-case, e.g. W015-learner-web-application.`);
48
- const numericId = id.slice(1);
49
- if (!folder.startsWith(`W${numericId}-`)) fail(`${label}.folder must preserve the numeric sequence from ${id}.`);
50
-
51
- if (!KINDS.has(item.kind)) fail(`${label}.kind must be feature, technical, or maintenance.`);
52
- const title = requireString(item.title, `${label}.title`);
53
- const state = item.state ?? item.status;
54
- if (!STATES.has(state)) fail(`${label}.state must be pending, in_progress, or completed.`);
55
- if (!Number.isInteger(item.priority) || item.priority < 1) fail(`${label}.priority must be a positive integer.`);
56
- if (!Array.isArray(item.depends_on ?? [])) fail(`${label}.depends_on must be a list.`);
57
- if (!Array.isArray(item.blockers ?? [])) fail(`${label}.blockers must be a list when present.`);
58
-
59
- const dependencies = [];
60
- const seenDependencies = new Set();
61
- for (const rawDependency of item.depends_on ?? []) {
62
- const dependency = requireString(rawDependency, `${label}.depends_on entry`);
63
- if (!WORK_ITEM_ID.test(dependency)) fail(`${label}.depends_on contains invalid work-item ID '${dependency}'.`);
64
- if (dependency === id) fail(`Work item '${id}' cannot depend on itself.`);
65
- if (seenDependencies.has(dependency)) fail(`Work item '${id}' lists dependency '${dependency}' more than once.`);
66
- seenDependencies.add(dependency);
67
- dependencies.push(dependency);
68
- }
69
-
70
- return {
71
- id,
72
- folder,
73
- kind: item.kind,
74
- title,
75
- state,
76
- priority: item.priority,
77
- depends_on: dependencies,
78
- blockers: (item.blockers ?? []).map((blocker) => {
79
- requireObject(blocker, `${id} blocker`);
80
- if (!/^[a-z][a-z0-9-]*$/.test(blocker.id ?? '')) fail(`${id} blocker.id must be stable kebab-case.`);
81
- if (!['external_action', 'consequential_decision'].includes(blocker.type))
82
- fail(`${id} blocker.type is invalid.`);
83
- requireString(blocker.description, `${id} blocker.description`);
84
- if (!['unresolved', 'resolved'].includes(blocker.status)) fail(`${id} blocker.status is invalid.`);
85
- return blocker;
86
- })
87
- };
88
- });
89
-
90
- const byId = new Map(items.map((item) => [item.id, item]));
91
- for (const item of items) {
92
- for (const dependency of item.depends_on) {
93
- if (!byId.has(dependency)) fail(`Work item '${item.id}' depends on unknown work item '${dependency}'.`);
94
- }
95
- }
96
- validateAcyclic(items);
97
- if (items.filter((item) => item.state === 'in_progress').length > 1)
98
- fail('Only one mutating work item may be in_progress.');
99
- return { schema_version: 2, work_items: items };
100
- }
101
-
102
- export function validateAcyclic(items) {
103
- const indegree = new Map(items.map((item) => [item.id, item.depends_on.length]));
104
- const dependents = new Map(items.map((item) => [item.id, []]));
105
- for (const item of items) for (const dependency of item.depends_on) dependents.get(dependency).push(item.id);
106
- const queue = items.filter((item) => indegree.get(item.id) === 0).map((item) => item.id);
107
- let visited = 0;
108
- while (queue.length) {
109
- const id = queue.shift();
110
- visited += 1;
111
- for (const dependent of dependents.get(id)) {
112
- const next = indegree.get(dependent) - 1;
113
- indegree.set(dependent, next);
114
- if (next === 0) queue.push(dependent);
115
- }
116
- }
117
- if (visited !== items.length) fail('backlog.yaml work-item dependencies contain a cycle.');
118
- }
119
-
120
- export function deriveExecutionStatus(item, byId) {
121
- if (item.state === 'completed') return { status: 'completed', reasons: [] };
122
- const incompleteDependencies = item.depends_on.filter((id) => byId.get(id)?.state !== 'completed');
123
- if (incompleteDependencies.length)
124
- return { status: 'blocked', reasons: incompleteDependencies.map((id) => ({ type: 'dependency', ref: id })) };
125
- return { status: item.state === 'in_progress' ? 'in_progress' : 'ready', reasons: [] };
126
- }
127
-
128
- export function topologicalOrder(items) {
129
- const byId = new Map(items.map((item) => [item.id, item]));
130
- const remaining = new Map(items.map((item) => [item.id, item.depends_on.length]));
131
- const dependents = new Map(items.map((item) => [item.id, []]));
132
- for (const item of items) for (const dependency of item.depends_on) dependents.get(dependency).push(item.id);
133
- const compare = (a, b) => a.localeCompare(b);
134
- for (const ids of dependents.values()) ids.sort(compare);
135
- const ready = items
136
- .filter((item) => item.depends_on.length === 0)
137
- .map((item) => item.id)
138
- .sort(compare);
139
- const levels = new Map(ready.map((id) => [id, 0]));
140
- const orderedIds = [];
141
- while (ready.length) {
142
- const id = ready.shift();
143
- orderedIds.push(id);
144
- for (const dependent of dependents.get(id)) {
145
- levels.set(dependent, Math.max(levels.get(dependent) ?? 0, (levels.get(id) ?? 0) + 1));
146
- const count = remaining.get(dependent) - 1;
147
- remaining.set(dependent, count);
148
- if (count === 0) {
149
- ready.push(dependent);
150
- ready.sort(compare);
151
- }
152
- }
153
- }
154
- return orderedIds
155
- .map((id) => byId.get(id))
156
- .sort((a, b) => levels.get(a.id) - levels.get(b.id) || compare(a.id, b.id));
157
- }
@@ -1,30 +0,0 @@
1
- import { parseDocument } from 'yaml';
2
- import { createHash } from 'node:crypto';
3
- export function documentRevision(text) {
4
- return createHash('sha256').update(text).digest('hex');
5
- }
6
- export function documentMetadata(text) {
7
- const match = text.match(/^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)/);
8
- if (!match) throw new Error('Missing YAML frontmatter.');
9
- const doc = parseDocument(match[1], { uniqueKeys: true });
10
- if (doc.errors.length) throw new Error(doc.errors[0].message);
11
- const value = doc.toJS();
12
- if (!value || typeof value !== 'object' || Array.isArray(value)) throw new Error('Frontmatter must be a mapping.');
13
- return value;
14
- }
15
- export function validateDocument(text, headings) {
16
- let metadata = {};
17
- const errors = [];
18
- try {
19
- metadata = documentMetadata(text);
20
- } catch (error) {
21
- errors.push(error.message);
22
- }
23
- if (metadata.schema_version !== 1) errors.push('schema_version must be 1.');
24
- if (!['draft', 'approved'].includes(metadata.status)) errors.push('status must be draft or approved.');
25
- if (metadata.status === 'approved' && (!metadata.approved_at || Number.isNaN(Date.parse(metadata.approved_at))))
26
- errors.push('approved_at must record the human approval timestamp.');
27
- const actual = new Set(text.split(/\r?\n/).filter((line) => /^#{1,2} /.test(line)));
28
- for (const heading of headings) if (!actual.has(heading)) errors.push(`Missing ${heading}.`);
29
- return { ...metadata, errors };
30
- }
@@ -1,26 +0,0 @@
1
- import { validateDocument } from './document.mjs';
2
- import { READABILITY_FIRST_PROFILE } from '../shared/profiles.mjs';
3
- export const ENGINEERING_HEADINGS = [
4
- '# Engineering',
5
- '## System shape',
6
- '## Modules and ownership',
7
- '## Dependency direction and boundaries',
8
- '## Vertical slices and code organization',
9
- '## Naming and readability conventions',
10
- '## Data ownership and persistence',
11
- '## Error handling',
12
- '## Testing and verification',
13
- '## Dependencies and external services',
14
- '## Security and operations',
15
- '## Deterministic gates',
16
- '## Deferred complexity',
17
- '## Exceptions'
18
- ];
19
- export function validateEngineeringDocument(text) {
20
- const result = validateDocument(text, ENGINEERING_HEADINGS);
21
- if (result.baseline?.profile !== READABILITY_FIRST_PROFILE.id)
22
- result.errors.push('baseline.profile must be flow/readability-first@1.');
23
- if (!['improve', 'preserve', 'not_applicable'].includes(result.baseline?.existing_code_policy))
24
- result.errors.push('baseline.existing_code_policy is invalid.');
25
- return result;
26
- }
@@ -1,28 +0,0 @@
1
- import { parseDocument } from 'yaml';
2
- import { ArtifactValidationError } from './backlog.mjs';
3
-
4
- const KINDS = new Set(['command', 'builtin']);
5
-
6
- export function parseGates(text, { source = 'gates.yaml' } = {}) {
7
- const document = parseDocument(text, { prettyErrors: false, uniqueKeys: true });
8
- if (document.errors.length) throw new ArtifactValidationError(`${source} is invalid: ${document.errors[0].message}`);
9
- const value = document.toJS();
10
- if (!value || typeof value !== 'object' || Array.isArray(value))
11
- throw new ArtifactValidationError(`${source} must be a mapping.`);
12
- if (value.schema_version !== 1) throw new ArtifactValidationError(`${source} schema_version must be 1.`);
13
- if (!Array.isArray(value.gates)) throw new ArtifactValidationError(`${source} gates must be a list.`);
14
- const ids = new Set();
15
- const gates = value.gates.map((gate, index) => {
16
- if (!gate || typeof gate !== 'object' || Array.isArray(gate))
17
- throw new ArtifactValidationError(`gates[${index}] must be a mapping.`);
18
- if (typeof gate.id !== 'string' || !/^[a-z][a-z0-9-]*$/.test(gate.id))
19
- throw new ArtifactValidationError(`gates[${index}].id must be lower kebab-case.`);
20
- if (ids.has(gate.id)) throw new ArtifactValidationError(`duplicate gate '${gate.id}'.`);
21
- ids.add(gate.id);
22
- if (!KINDS.has(gate.kind)) throw new ArtifactValidationError(`${gate.id}.kind must be command or builtin.`);
23
- if (gate.kind === 'command' && (!gate.command || typeof gate.command !== 'string'))
24
- throw new ArtifactValidationError(`${gate.id} command gate requires command.`);
25
- return { ...gate, blocking: gate.blocking !== false };
26
- });
27
- return { schema_version: 1, gates };
28
- }
@@ -1,27 +0,0 @@
1
- import { documentRevision, validateDocument } from './document.mjs';
2
- export { documentRevision } from './document.mjs';
3
- export const PLAN_HEADINGS = [
4
- '# Implementation Plan',
5
- '## Outcome',
6
- '## Current state',
7
- '## Proposed changes',
8
- '## Execution sequence',
9
- '## Task mapping',
10
- '## Data and control flow',
11
- '## Engineering compliance',
12
- '## Tests and validation',
13
- '## Risks and rollback',
14
- '## Deliberately excluded',
15
- '## Human decisions required'
16
- ];
17
- export function validateImplementationPlan(text, { workItem, engineeringText, specText, checkRevisions = true }) {
18
- const result = validateDocument(text, PLAN_HEADINGS);
19
- if (result.work_item !== workItem) result.errors.push('Plan belongs to a different work item.');
20
- for (const key of ['engineering_revision', 'spec_revision'])
21
- if (!/^[a-f0-9]{64}$/.test(result[key] ?? '')) result.errors.push(`${key} must be SHA256.`);
22
- if (checkRevisions && result.engineering_revision !== documentRevision(engineeringText))
23
- result.errors.push('Engineering revision is stale.');
24
- if (checkRevisions && result.spec_revision !== documentRevision(specText))
25
- result.errors.push('Spec revision is stale.');
26
- return result;
27
- }
@@ -1,12 +0,0 @@
1
- import { validateDocument } from './document.mjs';
2
- export function validatePrdDocument(text) {
3
- return validateDocument(text, [
4
- '# Product Requirements',
5
- '## Purpose',
6
- '## Users',
7
- '## Scope',
8
- '## Requirements',
9
- '## Constraints',
10
- '## Non-goals'
11
- ]);
12
- }
@@ -1,63 +0,0 @@
1
- import { parseDocument, stringify } from 'yaml';
2
- import { ArtifactValidationError } from './backlog.mjs';
3
-
4
- const PHASES = new Set([
5
- 'engineering_bootstrap',
6
- 'discovery',
7
- 'engineering',
8
- 'planning',
9
- 'backlog_planning',
10
- 'work_item_plan_approval',
11
- 'build',
12
- 'implementation',
13
- 'review',
14
- 'work_item_review',
15
- 'reconcile',
16
- 'migration_reconciliation',
17
- 'complete'
18
- ]);
19
- const STOP_REASONS = new Set([null, 'consequential_decision', 'external_action', 'unrecoverable_blocker', 'finished']);
20
-
21
- export function emptyState() {
22
- return {
23
- schema_version: 1,
24
- execution: { id: null, phase: 'discovery', step: 'define_project', workflow_hash: null },
25
- active: { work_item: null, task: null },
26
- stop_reason: null,
27
- migration: { status: 'not_required' }
28
- };
29
- }
30
-
31
- export function parseState(text, { source = 'state.yaml' } = {}) {
32
- const document = parseDocument(text, { prettyErrors: false, uniqueKeys: true });
33
- if (document.errors.length) throw new ArtifactValidationError(`${source} is invalid: ${document.errors[0].message}`);
34
- const value = document.toJS();
35
- if (!value || typeof value !== 'object' || Array.isArray(value))
36
- throw new ArtifactValidationError(`${source} must be a mapping.`);
37
- if (value.schema_version !== 1) throw new ArtifactValidationError(`${source} schema_version must be 1.`);
38
- const phase = value.execution?.phase;
39
- if (!PHASES.has(phase)) throw new ArtifactValidationError(`${source} execution.phase is invalid.`);
40
- if (!STOP_REASONS.has(value.stop_reason ?? null))
41
- throw new ArtifactValidationError(`${source} stop_reason is invalid.`);
42
- if (!['not_required', 'pending_reconciliation', 'completed'].includes(value.migration?.status ?? 'not_required'))
43
- throw new ArtifactValidationError(`${source} migration.status is invalid.`);
44
- return {
45
- schema_version: 1,
46
- execution: {
47
- id: value.execution?.id ?? null,
48
- phase,
49
- step: value.execution?.step ?? null,
50
- workflow_hash: value.execution?.workflow_hash ?? null
51
- },
52
- active: {
53
- work_item: value.active?.work_item ?? null,
54
- task: value.active?.task ?? null
55
- },
56
- stop_reason: value.stop_reason ?? null,
57
- migration: value.migration ?? { status: 'not_required' }
58
- };
59
- }
60
-
61
- export function stringifyState(state) {
62
- return stringify(state, { lineWidth: 0 });
63
- }
@@ -1,90 +0,0 @@
1
- import { parseDocument } from 'yaml';
2
- import { ArtifactValidationError } from './backlog.mjs';
3
-
4
- const STATES = new Set(['pending', 'in_progress', 'completed']);
5
- const TASK_ID = /^T\d{3,}$/;
6
-
7
- function fail(message) {
8
- throw new ArtifactValidationError(message);
9
- }
10
- function requireObject(value, label) {
11
- if (!value || typeof value !== 'object' || Array.isArray(value)) fail(`${label} must be a mapping.`);
12
- return value;
13
- }
14
- function requireString(value, label) {
15
- if (typeof value !== 'string' || !value.trim()) fail(`${label} must be a non-empty string.`);
16
- return value.trim();
17
- }
18
-
19
- export function parseTasks(text, { source = 'tasks.yaml', expectedWorkItem = null } = {}) {
20
- const document = parseDocument(text, { prettyErrors: false, uniqueKeys: true });
21
- if (document.errors.length) fail(`${source} is invalid: ${document.errors[0].message}`);
22
- const value = requireObject(document.toJS(), source);
23
- if (value.schema_version !== 1) fail(`${source} schema_version must be 1.`);
24
- const workItem = requireString(value.work_item, `${source} work_item`);
25
- if (expectedWorkItem && workItem !== expectedWorkItem)
26
- fail(`${source} belongs to ${workItem}, expected ${expectedWorkItem}.`);
27
- if (!Array.isArray(value.tasks)) fail(`${source} tasks must be a list.`);
28
- const ids = new Set();
29
- const tasks = value.tasks.map((raw, index) => {
30
- const label = `tasks[${index}]`;
31
- const task = requireObject(raw, label);
32
- const id = requireString(task.id, `${label}.id`);
33
- if (!TASK_ID.test(id)) fail(`${label}.id must use T followed by a zero-padded numeric sequence, e.g. T003.`);
34
- if (ids.has(id)) fail(`${source} contains duplicate task ID '${id}'.`);
35
- ids.add(id);
36
- const title = requireString(task.title, `${label}.title`);
37
- const state = task.state ?? task.status;
38
- if (!STATES.has(state)) fail(`${label}.state must be pending, in_progress, or completed.`);
39
- if (!Array.isArray(task.depends_on ?? [])) fail(`${label}.depends_on must be a list.`);
40
- const dependencies = [...new Set(task.depends_on ?? [])];
41
- if (dependencies.length !== (task.depends_on ?? []).length) fail(`${id} contains duplicate task dependencies.`);
42
- for (const dependency of dependencies) {
43
- if (!TASK_ID.test(dependency)) fail(`${id} depends on invalid task ID '${dependency}'.`);
44
- if (dependency === id) fail(`${id} cannot depend on itself.`);
45
- }
46
- const implementation = task.implementation ?? 'commit';
47
- if (!['commit', 'none', 'legacy'].includes(implementation))
48
- fail(`${id}.implementation must be commit, none or legacy.`);
49
- if (implementation === 'legacy' && state !== 'completed')
50
- fail(`${id}: legacy is reserved for completed migrated tasks.`);
51
- return {
52
- id,
53
- title,
54
- state,
55
- depends_on: dependencies,
56
- implementation,
57
- ...(task.legacy_commit ? { legacy_commit: task.legacy_commit } : {})
58
- };
59
- });
60
- const byId = new Map(tasks.map((task) => [task.id, task]));
61
- for (const task of tasks)
62
- for (const dependency of task.depends_on)
63
- if (!byId.has(dependency)) fail(`${task.id} depends on unknown task '${dependency}'.`);
64
- validateTaskDag(tasks);
65
- if (tasks.filter((task) => task.state === 'in_progress').length > 1)
66
- fail('Only one mutating task may be in_progress.');
67
- return { schema_version: 1, work_item: workItem, tasks };
68
- }
69
-
70
- function validateTaskDag(tasks) {
71
- const indegree = new Map(tasks.map((task) => [task.id, task.depends_on.length]));
72
- const dependents = new Map(tasks.map((task) => [task.id, []]));
73
- for (const task of tasks) for (const dependency of task.depends_on) dependents.get(dependency).push(task.id);
74
- const queue = tasks.filter((task) => task.depends_on.length === 0).map((task) => task.id);
75
- let visited = 0;
76
- while (queue.length) {
77
- const id = queue.shift();
78
- visited += 1;
79
- for (const dependent of dependents.get(id)) {
80
- const next = indegree.get(dependent) - 1;
81
- indegree.set(dependent, next);
82
- if (next === 0) queue.push(dependent);
83
- }
84
- }
85
- if (visited !== tasks.length) fail('tasks.yaml task dependencies contain a cycle.');
86
- }
87
-
88
- export function qualifiedTaskId(workItem, task) {
89
- return `${workItem}-${task}`;
90
- }