@popoverai/dotrequirements 0.24.3 → 0.25.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 (30) hide show
  1. package/README.md +7 -8
  2. package/dist/codebase-to-spec/dispatch.d.ts +60 -14
  3. package/dist/codebase-to-spec/dispatch.js +381 -15
  4. package/dist/codebase-to-spec/pack.d.ts +7 -0
  5. package/dist/codebase-to-spec/pack.js +29 -8
  6. package/dist/codebase-to-spec/prompts/editor.d.ts +1 -1
  7. package/dist/codebase-to-spec/prompts/editor.js +1 -1
  8. package/dist/codebase-to-spec/prompts/specifier.d.ts +1 -1
  9. package/dist/codebase-to-spec/prompts/specifier.js +3 -2
  10. package/dist/codebase-to-spec/schemas.d.ts +153 -0
  11. package/dist/codebase-to-spec/schemas.js +111 -0
  12. package/dist/codebase-to-spec/skill-install.d.ts +28 -25
  13. package/dist/codebase-to-spec/skill-install.js +31 -83
  14. package/dist/commands/codebase-to-spec/dispatch-context.d.ts +2 -5
  15. package/dist/commands/codebase-to-spec/dispatch-context.js +2 -5
  16. package/dist/commands/codebase-to-spec/dispatch-editor.d.ts +0 -1
  17. package/dist/commands/codebase-to-spec/dispatch-editor.js +0 -1
  18. package/dist/commands/codebase-to-spec/dispatch-planner.d.ts +0 -1
  19. package/dist/commands/codebase-to-spec/dispatch-planner.js +0 -1
  20. package/dist/commands/codebase-to-spec/dispatch-spec.d.ts +3 -6
  21. package/dist/commands/codebase-to-spec/dispatch-spec.js +3 -6
  22. package/dist/commands/codebase-to-spec/index.js +3 -2
  23. package/dist/commands/codebase-to-spec/pack.d.ts +5 -0
  24. package/dist/commands/codebase-to-spec/pack.js +6 -3
  25. package/dist/commands/codebase-to-spec/skill-install.js +2 -9
  26. package/dist/templates/agents/cts-worker.md +3 -3
  27. package/dist/templates/skills/codebase-to-spec/SKILL.md +44 -168
  28. package/dist/templates/workflows/specify-codebase.js +372 -0
  29. package/package.json +2 -2
  30. package/dist/templates/hooks/cts-worker-persona.sh +0 -76
@@ -1,209 +1,85 @@
1
1
  ---
2
2
  name: codebase-to-spec
3
- description: Generate dotrequirements behavioral specifications from a codebase via the conversational orchestrator. The user's CC session drives the pipeline directly — dispatching workers as subagents, reviewing per-area drafts, and iterating with editor passes. Use this when the user wants to capture what an existing codebase does as a set of testable behavioral requirements (legacy systems, third-party libraries, before refactoring).
3
+ description: Generate dotrequirements behavioral specifications from a codebase. You confirm scope and review the result; a background workflow autonomously plans the area outline, drafts each area's requirements, and converges them via independent review. Use when the user wants to capture what an existing codebase does as testable behavioral requirements (legacy systems, third-party libraries, before refactoring).
4
+ allowed-tools: Bash, Read, Workflow, AskUserQuestion
4
5
  ---
5
6
 
6
- # Codebase to Spec — Conversational Orchestrator
7
+ # Codebase to Spec
7
8
 
8
- You are the conversational orchestrator for codebase-to-spec. Unlike a thin CLI wrapper, **you drive the pipeline directly** — dispatching workers as `cts-worker` subagents (via the Task tool), reading their outputs, writing per-stage reviews into `.dotrequirements-cache/outline.yaml`, and looping until each stage converges.
9
-
10
- The CLI provides dispatch-instruction commands (`cts dispatch-*`) that you call to scaffold each step; you then dispatch the worker subagent yourself. The substrate is the single evolving `outline.yaml` file — it carries both the spec content (areas, customers, source files) and the review thread for each stage.
9
+ You turn an existing codebase into a dotrequirements behavioral spec. You own two human touchpoints — **confirming scope** at the start and **reviewing the result** at the end. Everything between — planning the behavioral-area outline, drafting each area's requirements, and the independent reviews that converge them — runs autonomously inside the **`specify-codebase` dynamic workflow**. You do not draft or review requirements yourself, and you do not dispatch workers yourself; the workflow does.
11
10
 
12
11
  ## When this skill is right
13
12
 
14
- Use when the user wants behavioral requirements *from* code that already exists. Typical phrasings:
15
- - "Generate requirements from this codebase"
16
- - "Capture the behavior of this library"
17
- - "I want a behavioral spec for the auth module"
18
- - "Document what this service does"
19
-
20
- Do **not** use for new feature design (use the `dotreq-requirements` skill instead), bug investigation, or code review.
21
-
22
- ## Workflow
23
-
24
- ### Step 1 — Confirm scope
25
-
26
- If the user passed a scope argument (path), use it. Otherwise ask one concise question:
13
+ Use when the user wants behavioral requirements *from* code that already exists:
14
+ - "Generate requirements from this codebase" / "capture what this library does" / "spec the auth module before I refactor it"
27
15
 
28
- > "Which part of the codebase should I spec? Give me a path (e.g. `src/auth`) or say 'the whole thing'."
29
-
30
- For larger codebases, encourage scoping to a single area. The CLI will surface a `BudgetExceeded` exit code if the compressed pack is too large.
31
-
32
- ### Step 2 — Pack
16
+ Do **not** use for new-feature design (use the `dotreq-requirements` skill), bug investigation, or code review.
33
17
 
34
- Run `dotrequirements cts pack --scope <PATH>` via Bash. This is deterministic — no LLM work. The CLI emits `[CTS] pack/done` and `[CTS] pack/budget-ok` lines.
18
+ ## Prerequisite: dynamic workflows
35
19
 
36
- ### Step 3 — Plan: dispatch the planner, review, iterate to approval
20
+ This skill runs its pipeline as a dynamic Workflow. If the `Workflow` tool is not available in this environment, **stop** and tell the user:
37
21
 
38
- **3a. Initial planner dispatch.** Run `dotrequirements cts dispatch-planner` via Bash. The output is JSON like `{ "dispatch_id": "planner-initial", "output_path": "..." }`. Dispatch the worker:
39
-
40
- ```
41
- Task(subagent_type="cts-worker", prompt="dispatch-id=planner-initial")
42
- ```
22
+ > "codebase-to-spec runs as a dynamic workflow, which this Claude Code version/configuration doesn't support. For CI or headless use, run `dotrequirements cts run --scope <path>` instead."
43
23
 
44
- The PreToolUse hook composes the full planner prompt; the worker writes `outline.yaml` and signals completion.
45
-
46
- **3b. Review the outline.** Read `.dotrequirements-cache/outline.yaml`. Assess:
47
- - **Customer naming**: Each area must have ≥1 customers with concrete, area-scoped descriptions (not "a developer" — "a Python data engineer building ETL pipelines"). Same role across areas is OK if described freshly per area's context.
48
- - **Area set**: Customer-vocabulary names, not architectural labels. Coverage of substantive files; non-behavioral files (tests, build infrastructure) excluded.
49
- - **Mechanical correctness**: distinct prefixes per area; paths matching the pack; ≥1 customer per area.
50
-
51
- **3c. Write your review into outline.yaml.** Edit the outline to add a top-level `review:` section. Two shapes:
52
-
53
- ```yaml
54
- # Approved
55
- review:
56
- result: approved
57
- thread:
58
- - result: approved
59
- ```
60
-
61
- ```yaml
62
- # Needs revision (revisions list is required, ≥1 entries)
63
- review:
64
- result: needs-revision
65
- thread:
66
- - result: needs-revision
67
- revisions:
68
- - "Split the FOO area into two areas, separating the producer behavior from the reviewer behavior. Each should have its own customer and source files."
69
- - "Tighten the customer description on BAR — it currently reads as generic 'a developer'; ground it in what the customer is doing at this specific moment in the system."
70
- ```
71
-
72
- Revisions must be specific, actionable directives the planner can apply mechanically. Avoid "this feels off" — say what to change.
73
-
74
- **3d. If needs-revision, dispatch the planner again.** Run `dotrequirements cts dispatch-planner --revise`. Dispatch:
75
-
76
- ```
77
- Task(subagent_type="cts-worker", prompt="dispatch-id=planner-revise-<N>")
78
- ```
24
+ Do not attempt a non-workflow fallback.
79
25
 
80
- (N comes from the CLI's payload — `planner-revise-2` for the 2nd round, `planner-revise-3` for the 3rd, etc.)
81
-
82
- The worker rewrites outline.yaml's content while preserving the existing `review` section. After it completes, return to step 3b and review the revised outline. Append another entry to the `review.thread` array based on what you find.
83
-
84
- **3e. Approval.** When you're satisfied, edit outline.yaml to set `review.result: approved` and append a `{ result: approved }` entry to the thread. Proceed to step 4.
85
-
86
- ### Step 4 — Fan out: dispatch all specifiers in parallel
87
-
88
- Run `dotrequirements cts dispatch-spec`. The output is a JSON array of N payloads, one per area:
89
-
90
- ```json
91
- [
92
- { "dispatch_id": "specifier-PLAN", "output_path": "...", "area_name": "...", "area_prefix": "PLAN" },
93
- ...
94
- ]
95
- ```
96
-
97
- Fire all N background Task dispatches in **one message** (parallel, not sequential):
98
-
99
- ```
100
- Task(subagent_type="cts-worker", prompt="dispatch-id=specifier-PLAN", run_in_background=true)
101
- Task(subagent_type="cts-worker", prompt="dispatch-id=specifier-OREV", run_in_background=true)
102
- ... etc
103
- ```
104
-
105
- Each worker:
106
- - Reads the area's source files via Read tool (from `.dotrequirements-cache/source.txt` or directly from the worktree)
107
- - Drafts the partial at `.dotrequirements-cache/partials/<sanitized-area>.partial.md`
108
- - Runs `cts validate` + `cts style-check` on its own draft via Bash (self-style-check loop, capped at 2 runs)
109
- - Signals completion via task-notification
26
+ ## Workflow
110
27
 
111
- ### Step 5 — Per-area review and editor loops
28
+ ### 1. Confirm scope
112
29
 
113
- As each specifier's task-notification arrives, review that area's partial:
30
+ If the user gave a scope (a path), use it. Otherwise ask one concise question:
114
31
 
115
- **5a. Read the partial.** Pull up the file the worker wrote.
32
+ > "Which part of the codebase should I spec? Give me a path (e.g. `src/auth`) or say 'the whole thing'."
116
33
 
117
- **5b. Review against per-area criteria.** The area's customer descriptions in outline.yaml ground your review. Look for:
118
- - **Customer-grounding**: requirements use named personas from the area's customers
119
- - **Behavioral framing**: requirements describe what the customer observes, not API contracts or implementation mechanics
120
- - **Independent testability**: no cross-references between requirements; each readable on its own
121
- - **Coverage**: behaviors visible in source files are captured; tests/recipes informed but didn't drift into the spec
122
- - **Schema cleanliness**: validate already ran (worker self-validated); just sanity-check
34
+ For large codebases, encourage scoping to a single area — the pack has a context budget.
123
35
 
124
- **5c. Write your review into outline.yaml's area section.** Same shape as the project-level review, on the area:
36
+ ### 2. Pack (deterministic)
125
37
 
126
- ```yaml
127
- areas:
128
- - name: ...
129
- prefix: PLAN
130
- review:
131
- result: approved
132
- thread:
133
- - result: approved
134
- ...
135
- ```
38
+ Run `dotrequirements cts pack --scope <PATH>` via Bash. Deterministic, no LLM. It emits `[CTS] pack/done` then `pack/budget-ok`, or exits **10 (BudgetExceeded)** if the compressed pack is too large. On BudgetExceeded, surface the limit and offer a narrower scope (back to step 1).
136
39
 
137
- Or needs-revision with a revisions list (≥1 entries).
40
+ ### 3. Run the workflow
138
41
 
139
- **5d. If needs-revision, dispatch the editor.** Run `dotrequirements cts dispatch-editor <area-prefix>`. Then:
42
+ Launch the bundled workflow:
140
43
 
141
44
  ```
142
- Task(subagent_type="cts-worker", prompt="dispatch-id=editor-<prefix>")
45
+ Workflow({ name: "specify-codebase" })
143
46
  ```
144
47
 
145
- The worker reads the current partial + the revisions list inline (in the composed prompt), applies each revision via Edit, runs validate + style-check, signals completion. Return to 5a and re-review.
146
-
147
- **5e. Convergence.** Continue per-area loops until every area's `review.result` is `approved`. Each area can converge independently; don't block on one area to start reviewing another.
148
-
149
- ### Step 6 — Cross-area review
150
-
151
- Once all per-area reviews are approved, run `cts compose-orchestrator` to assemble the composed spec. Then read it and check:
152
-
153
- - **Persona consistency**: same role mentioned across areas should use consistent naming
154
- - **Duplicated behaviors**: same behavior captured in two areas (one should own it)
155
- - **Cross-area gaps**: behaviors that fell between areas
156
- - **Framing drift**: areas in different voices
157
-
158
- If cross-area concerns surface, edit the affected areas' `review` to needs-revision with appropriate revisions, dispatch editors per 5d, then re-compose. Otherwise proceed to step 7.
159
-
160
- ### Step 7 — Present
161
-
162
- Run `dotrequirements cts present-orchestrator [--overwrite] [--skip-existing]` via Bash. Writes the final files under `.requirements/`. Per CTS-PRESENT-1, splits into per-area files when the outline has ≥5 areas; single file otherwise.
163
-
164
- In non-interactive mode without `--overwrite` / `--skip-existing`, the CLI errors on conflicts. Ask the user which they want when a conflict is detected.
48
+ You normally pass no `args` — `cli` defaults to `dotrequirements` on PATH, and the iteration caps default to `roundsCap: 5` / `outlineRoundsCap: 4`. Override only if needed, passing `args` as a real object (not a JSON string). *(Only in the dotrequirements dev repo, pass `args: { cli: "node <repo>/packages/cli/dist/cli.js" }`.)*
165
49
 
166
- ### Step 8 — Summarize and offer follow-ups
50
+ The workflow plans the outline (planner + an independent reviewer, looping to convergence), enumerates the areas, fans out one specifier per area, converges each area (specify → review → edit), then composes the partials into one spec and runs a document-level cross-area review/edit pass (dedup, terminology, seam gaps). It runs in the background — watch progress in `/workflows`. Do not narrate every step; only surface the gates below.
167
51
 
168
- Present the final summary in conversational form:
169
- - How many areas, how many requirements
170
- - Any unconverged areas (latest thread entry was max-turns-hit)
171
- - File paths written
52
+ ### 4. Read the result
172
53
 
173
- Offer useful follow-ups:
174
- - **Push to cloud** — `dotrequirements push` syncs to dotrequirements cloud. Only suggest if the user has cloud configured (check `.dotrequirements/config.json` or ask).
175
- - **Refine an area** — re-review one area; mark needs-revision; re-dispatch editor.
176
- - **Re-run on different scope** — start over with a different `--scope` path.
54
+ The workflow returns one of:
55
+ - **`status: "done"`** — with `areas` (per-area `{ area_prefix, area_name, partial_path, status, rounds }`), `converged_count`, `unconverged` (prefixes that hit the round cap), `failed` (prefixes whose specifier failed outright — no draft was produced), `total`, and the cross-area pass result `cross_area_rounds` / `cross_area_converged`. The workflow has already composed and reconciled the spec. Proceed to step 5.
56
+ - **`status: "outline-unconverged"`** — the outline reviewer didn't approve the decomposition within `outlineRoundsCap`. The latest outline is at `.dotrequirements-cache/outline.yaml`. Surface this; offer to re-run, narrow scope, or hand-edit the outline. Do **not** present.
57
+ - **`status: "enumerate-failed"`** — the approved outline's areas couldn't be enumerated (the agent died). Nothing was drafted. Surface this and offer to re-run. Do **not** present.
177
58
 
178
- ## Pausing for user input
59
+ ### 5. Present (deterministic)
179
60
 
180
- These moments naturally invite user judgment — surface them in conversation rather than auto-deciding:
181
- - **Scope confirmation** at the start
182
- - **Outline approval** before fan-out (the area decomposition is high-leverage)
183
- - **Cross-area concerns** that look like the user's call (e.g., "should X and Y be one area or two?")
184
- - **Residual concerns** at the end (anything the loop didn't fully address)
61
+ On `status: "done"`, the workflow has already composed the spec and run the cross-area pass. Write the final file(s):
62
+ - `dotrequirements cts present-orchestrator` — writes the composed spec to file(s) under `.requirements/`.
185
63
 
186
- Routine progress narration ("dispatching planner...", "fan-out complete...") should NOT interrupt the user. Only judgment-requiring moments surface as questions.
64
+ `present-orchestrator` errors on existing-file conflicts unless given `--overwrite` or `--skip-existing`. When a conflict is reported, ask the user which they want, then re-run with that flag.
187
65
 
188
- ## Failure handling
66
+ When `cross_area_converged` is `false`, the cross-area pass hit `crossAreaRoundsCap` without a clean approval — present the spec, but surface that in the summary so the user gives the composed spec an extra look.
189
67
 
190
- The CLI uses stable exit codes:
68
+ ### 6. Summarize and offer follow-ups
191
69
 
192
- - **Exit 10 (BudgetExceeded)** — codebase too large after compression. Surface, offer narrower `--scope`.
193
- - **Exit 11 (MaxTurnsHit)** — review loop hit its cap. Latest artifact is on disk; surface residual notes and ask user whether to ship, re-iterate, or hand-edit.
194
- - **Exit 12 (OverwriteRefused)** — present found existing files. Offer `--overwrite` or `--skip-existing`.
195
- - **Exit 2 (MissingInput)** — usually means a prior step wasn't run. Surface CLI message verbatim.
196
- - **Exit 3 (StageFailed)** — a stage errored. Read stderr; offer resume (re-run same command — the cache means earlier stages are skipped), restart fresh (`--fresh`), or investigate.
70
+ Present a readable summary: how many areas and requirements, the file path(s) written, and — importantly — any `unconverged` areas (they hit the round cap; their latest draft was kept and is worth a look) and any `failed` areas (no draft was produced; they are absent from the spec). Then offer, **without auto-running any of them**:
71
+ - **Push to cloud** — `dotrequirements push` (only if cloud is configured; it's a destructive sync).
72
+ - **Refine an area** — re-run on a narrower scope.
73
+ - **Re-run on a different scope.**
197
74
 
198
75
  ## What you must NOT do
199
76
 
200
- - Don't try to generate requirements yourself; always dispatch workers.
201
- - Don't paraphrase the CLI's progress lines in misleading ways. If the CLI emits "specifier failed", don't say "specifier completed."
202
- - Don't claim convergence when an area's thread ends with `needs-revision`. The loop hasn't closed.
203
- - Don't push to cloud without asking — `dotrequirements push` is a destructive sync.
204
- - Don't edit files inside `.dotrequirements-cache/` other than `outline.yaml` (which you DO edit, to write reviews). Other cache files are managed by the CLI and workers.
205
- - Don't dispatch a worker without the corresponding `cts dispatch-*` call first — the dispatch-id must match what the CLI scaffolded.
77
+ - Don't draft, review, or edit requirements yourself — the workflow's workers do that.
78
+ - Don't dispatch workers via the Task tool — the workflow owns all agent work.
79
+ - Don't claim convergence when `unconverged` or `failed` is non-empty — surface those areas honestly.
80
+ - Don't present when `status` is `outline-unconverged` or `enumerate-failed`.
81
+ - Don't push to cloud without asking.
206
82
 
207
83
  ## Host portability
208
84
 
209
- This skill relies on Claude Code's Task tool, PreToolUse hook, and Bash. It does not work on hosts without subagent dispatch (Cursor, Codex) — those should use the legacy `cts run` CLI directly. The skill is bundled with `dotrequirements ai-setup` for Claude Code; the `cts-worker` agent definition and persona-injection hook are installed at the same time.
85
+ This skill requires Claude Code with dynamic-workflow support (it launches the `specify-codebase` workflow). On hosts without it, or for CI/headless use, the legacy `dotrequirements cts run` CLI runs the same pipeline non-interactively.
@@ -0,0 +1,372 @@
1
+ export const meta = {
2
+ name: 'specify-codebase',
3
+ description:
4
+ 'Codebase-to-spec: autonomously plan a behavioral outline, fan out specifiers across its areas, and converge each area via independent review + editor passes — no mid-run human input',
5
+ whenToUse:
6
+ 'Invoked by the codebase-to-spec skill after `cts pack`. Not run directly.',
7
+ phases: [
8
+ { title: 'Outline' },
9
+ { title: 'Specify' },
10
+ { title: 'Converge' },
11
+ { title: 'Reconcile' },
12
+ ],
13
+ }
14
+
15
+ // Injected globals: agent, pipeline, parallel, phase, log, args.
16
+ //
17
+ // args:
18
+ // cli — resolved dotrequirements CLI invocation workers shell out to
19
+ // (e.g. "dotrequirements" or "node /abs/path/packages/cli/dist/cli.js")
20
+ // roundsCap — max review→edit rounds per area before giving up (default 5)
21
+ // outlineRoundsCap — max plan→review→revise rounds for the outline (default 4)
22
+ // crossAreaRoundsCap — max cross-area review→edit rounds on the composed spec (default 5)
23
+ //
24
+ // The caps are a runaway backstop, not a target: an area stops the moment its
25
+ // reviewer approves, and reports `unconverged` if it can't converge in N rounds.
26
+ // Defaults are set high enough to give careful review room on messy codebases.
27
+ //
28
+ // args may arrive as a JSON string depending on how the caller passes it; normalize.
29
+ const input = typeof args === 'string' ? JSON.parse(args) : args || {}
30
+ // `cli` defaults to the PATH binary for installed users; the dev repo passes a
31
+ // `node <repo>/dist/cli.js` invocation explicitly.
32
+ const {
33
+ cli = 'dotrequirements',
34
+ roundsCap = 5,
35
+ outlineRoundsCap = 4,
36
+ crossAreaRoundsCap = 5,
37
+ } = input
38
+
39
+ // --- Schemas (mirrors of the JSON Schemas in schemas.ts; a sync test guards drift) ---
40
+
41
+ const PARTIAL_RESULT_SCHEMA = {
42
+ type: 'object',
43
+ properties: {
44
+ area_prefix: { type: 'string' },
45
+ partial_path: { type: 'string' },
46
+ status: { type: 'string', enum: ['drafted', 'skipped-resume'] },
47
+ requirement_count: { type: 'integer', minimum: 0 },
48
+ },
49
+ required: ['area_prefix', 'partial_path', 'status'],
50
+ additionalProperties: false,
51
+ }
52
+
53
+ const REVIEW_VERDICT_SCHEMA = {
54
+ type: 'object',
55
+ properties: {
56
+ result: { type: 'string', enum: ['approved', 'needs-revision'] },
57
+ revisions: { type: 'array', items: { type: 'string' } },
58
+ },
59
+ required: ['result'],
60
+ additionalProperties: false,
61
+ }
62
+
63
+ // Shape `cts dispatch-spec` emits (one entry per approved-outline area).
64
+ const AREAS_SCHEMA = {
65
+ type: 'object',
66
+ properties: {
67
+ areas: {
68
+ type: 'array',
69
+ items: {
70
+ type: 'object',
71
+ properties: {
72
+ dispatch_id: { type: 'string' },
73
+ area_prefix: { type: 'string' },
74
+ area_name: { type: 'string' },
75
+ output_path: { type: 'string' },
76
+ },
77
+ required: ['dispatch_id', 'area_prefix', 'area_name', 'output_path'],
78
+ additionalProperties: false,
79
+ },
80
+ },
81
+ },
82
+ required: ['areas'],
83
+ additionalProperties: false,
84
+ }
85
+
86
+ // Thin self-compose wrapper: a worker fetches its full instructions from the CLI.
87
+ function follow(line) {
88
+ return [
89
+ `Run exactly this command: ${line}`,
90
+ `It prints JSON of the form {"prompt": "..."}. Read the "prompt" field and follow it EXACTLY.`,
91
+ ]
92
+ }
93
+
94
+ // ============================ Stage 0: outline ============================
95
+ // plan → review → revise → re-review, until the outline reviewer approves the
96
+ // area decomposition or the cap is hit. Sequential (one outline), so the
97
+ // reviewer's writes to the document-level review.thread never contend.
98
+ phase('Outline')
99
+
100
+ await agent(
101
+ [
102
+ `You are a codebase-to-spec planner. Produce the behavioral-area outline for the packed codebase.`,
103
+ ...follow(`${cli} cts dispatch-context planner-initial`),
104
+ `End with a one-line confirmation of the outline path you wrote.`,
105
+ ].join('\n'),
106
+ { agentType: 'cts-worker', label: 'plan outline', phase: 'Outline' },
107
+ )
108
+
109
+ let outlineApproved = false
110
+ for (let round = 1; round <= outlineRoundsCap; round++) {
111
+ const verdict = await agent(
112
+ [
113
+ `You are a codebase-to-spec outline reviewer (independent of the planner).`,
114
+ ...follow(`${cli} cts dispatch-context outline-reviewer`),
115
+ `It tells you to record your verdict in the outline's top-level review thread and return it.`,
116
+ `Return { "result": "approved" | "needs-revision", "revisions": [...] }.`,
117
+ ].join('\n'),
118
+ {
119
+ schema: REVIEW_VERDICT_SCHEMA,
120
+ agentType: 'cts-worker',
121
+ label: `review outline r${round}`,
122
+ phase: 'Outline',
123
+ },
124
+ )
125
+
126
+ // Fail-closed: only an explicit "approved" verdict advances. A null verdict
127
+ // (reviewer subagent died after retries) is treated as non-approval — fall
128
+ // through to revise/re-review so we never fan out an unreviewed outline.
129
+ if (verdict?.result === 'approved') {
130
+ outlineApproved = true
131
+ break
132
+ }
133
+ if (round === outlineRoundsCap) break
134
+
135
+ await agent(
136
+ [
137
+ `You are a codebase-to-spec planner applying the reviewer's outline revisions.`,
138
+ ...follow(`${cli} cts dispatch-context planner-revise-${round + 1}`),
139
+ `End with a one-line confirmation.`,
140
+ ].join('\n'),
141
+ { agentType: 'cts-worker', label: `revise outline r${round}`, phase: 'Outline' },
142
+ )
143
+ }
144
+
145
+ if (!outlineApproved) {
146
+ // No mid-run HITL: don't fan out a decomposition the reviewer wouldn't pass.
147
+ log(`outline did not converge within ${outlineRoundsCap} rounds`)
148
+ return {
149
+ status: 'outline-unconverged',
150
+ outline_rounds: outlineRoundsCap,
151
+ areas: [],
152
+ converged_count: 0,
153
+ unconverged: [],
154
+ failed: [],
155
+ total: 0,
156
+ }
157
+ }
158
+
159
+ // ===================== Stage 1+2: fan out + converge =====================
160
+ // Enumerate the approved outline's areas (an agent runs the CLI; the script
161
+ // can't), then run each area through specify → review/edit independently.
162
+ phase('Specify')
163
+
164
+ const enumerated = await agent(
165
+ [
166
+ `Run exactly this command: ${cli} cts dispatch-spec`,
167
+ `It prints a JSON array, one entry per area of the approved outline.`,
168
+ `Return { "areas": <that array, verbatim> }.`,
169
+ ].join('\n'),
170
+ { schema: AREAS_SCHEMA, agentType: 'cts-worker', label: 'enumerate areas', phase: 'Specify' },
171
+ )
172
+
173
+ // Fail-closed: a dead enumerate agent (null return) must surface as a failure,
174
+ // not read as an empty-but-successful decomposition that sails through fan-out
175
+ // and reconcile to a hollow `done`.
176
+ if (!enumerated) {
177
+ log('area enumeration failed — cannot fan out')
178
+ return {
179
+ status: 'enumerate-failed',
180
+ areas: [],
181
+ converged_count: 0,
182
+ unconverged: [],
183
+ failed: [],
184
+ total: 0,
185
+ }
186
+ }
187
+ const areas = enumerated.areas
188
+
189
+ async function specify(area) {
190
+ return agent(
191
+ [
192
+ `You are a codebase-to-spec specifier worker for the area "${area.area_name}" (prefix ${area.area_prefix}).`,
193
+ ``,
194
+ `Step 1 — Resume check. If a non-empty partial already exists at:`,
195
+ ` ${area.output_path}`,
196
+ ` then do NOT regenerate it. Return`,
197
+ ` { "area_prefix": "${area.area_prefix}", "partial_path": "${area.output_path}", "status": "skipped-resume" }.`,
198
+ ``,
199
+ `Step 2 — Otherwise, run exactly this command:`,
200
+ ` ${cli} cts dispatch-context specifier-${area.area_prefix}`,
201
+ ` Read the "prompt" field of its JSON output and follow it EXACTLY — which source files to`,
202
+ ` read, how to ground requirements in this area's customers, where to write the partial,`,
203
+ ` and how to validate + style-check it.`,
204
+ ``,
205
+ `Step 3 — Return`,
206
+ ` { "area_prefix": "${area.area_prefix}", "partial_path": "${area.output_path}",`,
207
+ ` "status": "drafted", "requirement_count": <number of requirements you wrote> }.`,
208
+ ].join('\n'),
209
+ {
210
+ schema: PARTIAL_RESULT_SCHEMA,
211
+ agentType: 'cts-worker',
212
+ label: `specify ${area.area_name}`,
213
+ phase: 'Specify',
214
+ },
215
+ )
216
+ }
217
+
218
+ async function reviewEditLoop(specifyResult, area) {
219
+ // Fail-closed (CTSO-INTEG-3.2): a dead specifier (null return) means there may
220
+ // be no partial to review — record the failure and continue with the other
221
+ // areas, instead of burning review/edit rounds on a draft that may not exist.
222
+ if (!specifyResult) {
223
+ log(`area ${area.area_prefix}: specifier failed — recording and moving on`)
224
+ return {
225
+ area_prefix: area.area_prefix,
226
+ area_name: area.area_name,
227
+ partial_path: area.output_path,
228
+ status: 'failed',
229
+ rounds: 0,
230
+ }
231
+ }
232
+
233
+ for (let round = 1; round <= roundsCap; round++) {
234
+ const verdict = await agent(
235
+ [
236
+ `You are a codebase-to-spec reviewer for the area "${area.area_name}" (prefix ${area.area_prefix}).`,
237
+ ...follow(`${cli} cts dispatch-context reviewer-${area.area_prefix}`),
238
+ `It tells you to review the partial, record your verdict in the outline, and return it.`,
239
+ `Return { "result": "approved" | "needs-revision", "revisions": [...] }.`,
240
+ ].join('\n'),
241
+ {
242
+ schema: REVIEW_VERDICT_SCHEMA,
243
+ agentType: 'cts-worker',
244
+ label: `review ${area.area_name} r${round}`,
245
+ phase: 'Converge',
246
+ },
247
+ )
248
+
249
+ if (verdict?.result === 'approved') {
250
+ return {
251
+ area_prefix: area.area_prefix,
252
+ area_name: area.area_name,
253
+ partial_path: area.output_path,
254
+ status: 'converged',
255
+ rounds: round - 1,
256
+ }
257
+ }
258
+
259
+ // Fail-closed: a null verdict (dead reviewer) is NOT approval — but it also
260
+ // recorded no fresh revisions in the outline, so dispatching an editor here
261
+ // would only re-apply the previous round's already-applied list (or have
262
+ // nothing to act on in round 1). Skip straight to the next review round.
263
+ if (!verdict) continue
264
+
265
+ // Mirror the outline and cross-area loops: the final round's verdict is
266
+ // final. Without this guard a last editor pass would run whose output is
267
+ // never re-reviewed before composition.
268
+ if (round === roundsCap) break
269
+
270
+ await agent(
271
+ [
272
+ `You are a codebase-to-spec editor for the area "${area.area_name}" (prefix ${area.area_prefix}).`,
273
+ ...follow(`${cli} cts dispatch-context editor-${area.area_prefix}`),
274
+ `It embeds the reviewer's revisions and tells you to apply them, then validate + style-check.`,
275
+ `Return { "area_prefix": "${area.area_prefix}", "partial_path": "${area.output_path}",`,
276
+ ` "status": "drafted", "requirement_count": <number of requirements now in the partial> }.`,
277
+ ].join('\n'),
278
+ {
279
+ schema: PARTIAL_RESULT_SCHEMA,
280
+ agentType: 'cts-worker',
281
+ label: `edit ${area.area_name} r${round}`,
282
+ phase: 'Converge',
283
+ },
284
+ )
285
+ }
286
+
287
+ log(`area ${area.area_prefix} hit roundsCap=${roundsCap} without approval`)
288
+ return {
289
+ area_prefix: area.area_prefix,
290
+ area_name: area.area_name,
291
+ partial_path: area.output_path,
292
+ status: 'unconverged',
293
+ rounds: roundsCap,
294
+ }
295
+ }
296
+
297
+ log(`Outline approved — fanning out ${areas.length} area(s)`)
298
+
299
+ const results = (await pipeline(areas, specify, reviewEditLoop)).filter(Boolean)
300
+
301
+ // ===================== Stage 3: compose + cross-area reconcile =====================
302
+ // The per-area partials have converged. Assemble them into one composed spec
303
+ // (deterministic), then run the document-level cross-area review/edit loop
304
+ // (CTSO-CONV-5) — duplication, terminology drift, and seam gaps that only a
305
+ // whole-document view catches — before the skill presents the result.
306
+ phase('Reconcile')
307
+
308
+ await agent(
309
+ [
310
+ `Run exactly this command: ${cli} cts compose-orchestrator`,
311
+ `It deterministically assembles the converged area partials into a single composed spec.`,
312
+ `End with a one-line confirmation of the composed-spec path it reports.`,
313
+ ].join('\n'),
314
+ { agentType: 'cts-worker', label: 'compose spec', phase: 'Reconcile' },
315
+ )
316
+
317
+ let crossAreaRounds = 0
318
+ let crossAreaConverged = false
319
+ for (let round = 1; round <= crossAreaRoundsCap; round++) {
320
+ crossAreaRounds = round
321
+ const verdict = await agent(
322
+ [
323
+ `You are a codebase-to-spec cross-area reviewer (document-level, independent).`,
324
+ ...follow(`${cli} cts dispatch-context cross-area-reviewer`),
325
+ `It tells you to review the composed spec for cross-area + document-level issues, record your verdict in the outline, and return it.`,
326
+ `Return { "result": "approved" | "needs-revision", "revisions": [...] }.`,
327
+ ].join('\n'),
328
+ {
329
+ schema: REVIEW_VERDICT_SCHEMA,
330
+ agentType: 'cts-worker',
331
+ label: `cross-area review r${round}`,
332
+ phase: 'Reconcile',
333
+ },
334
+ )
335
+
336
+ // Fail-closed: only an explicit "approved" verdict ends the loop. A null
337
+ // verdict (dead reviewer) is non-approval — fall through to edit/re-review.
338
+ if (verdict?.result === 'approved') {
339
+ crossAreaConverged = true
340
+ break
341
+ }
342
+ if (round === crossAreaRoundsCap) break
343
+
344
+ // The reviewer recorded its revisions in the outline's crossAreaReview thread;
345
+ // the compose editor self-composes from there and edits the composed spec.
346
+ await agent(
347
+ [
348
+ `You are a codebase-to-spec compose editor applying cross-area revisions to the composed spec.`,
349
+ ...follow(`${cli} cts dispatch-context compose-editor`),
350
+ `It embeds the cross-area reviewer's revisions and tells you to apply them, then validate.`,
351
+ `End with a one-line confirmation.`,
352
+ ].join('\n'),
353
+ { agentType: 'cts-worker', label: `compose edit r${round}`, phase: 'Reconcile' },
354
+ )
355
+ }
356
+
357
+ if (!crossAreaConverged) {
358
+ log(`cross-area review did not converge within ${crossAreaRoundsCap} rounds`)
359
+ }
360
+
361
+ return {
362
+ status: 'done',
363
+ areas: results,
364
+ converged_count: results.filter((r) => r.status === 'converged').length,
365
+ unconverged: results
366
+ .filter((r) => r.status === 'unconverged')
367
+ .map((r) => r.area_prefix),
368
+ failed: results.filter((r) => r.status === 'failed').map((r) => r.area_prefix),
369
+ total: areas.length,
370
+ cross_area_rounds: crossAreaRounds,
371
+ cross_area_converged: crossAreaConverged,
372
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@popoverai/dotrequirements",
3
- "version": "0.24.3",
3
+ "version": "0.25.0",
4
4
  "description": "Requirements tracking CLI, test harness, and MCP server",
5
5
  "type": "module",
6
6
  "bin": {
@@ -71,7 +71,7 @@
71
71
  "@types/node": "^20",
72
72
  "@types/uuid": "^11.0.0",
73
73
  "typescript": "^5",
74
- "vitest": "^4.0.0"
74
+ "vitest": "^4.1.8"
75
75
  },
76
76
  "files": [
77
77
  "dist",