@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.
- package/README.md +7 -8
- package/dist/codebase-to-spec/dispatch.d.ts +60 -14
- package/dist/codebase-to-spec/dispatch.js +381 -15
- package/dist/codebase-to-spec/pack.d.ts +7 -0
- package/dist/codebase-to-spec/pack.js +29 -8
- package/dist/codebase-to-spec/prompts/editor.d.ts +1 -1
- package/dist/codebase-to-spec/prompts/editor.js +1 -1
- package/dist/codebase-to-spec/prompts/specifier.d.ts +1 -1
- package/dist/codebase-to-spec/prompts/specifier.js +3 -2
- package/dist/codebase-to-spec/schemas.d.ts +153 -0
- package/dist/codebase-to-spec/schemas.js +111 -0
- package/dist/codebase-to-spec/skill-install.d.ts +28 -25
- package/dist/codebase-to-spec/skill-install.js +31 -83
- package/dist/commands/codebase-to-spec/dispatch-context.d.ts +2 -5
- package/dist/commands/codebase-to-spec/dispatch-context.js +2 -5
- package/dist/commands/codebase-to-spec/dispatch-editor.d.ts +0 -1
- package/dist/commands/codebase-to-spec/dispatch-editor.js +0 -1
- package/dist/commands/codebase-to-spec/dispatch-planner.d.ts +0 -1
- package/dist/commands/codebase-to-spec/dispatch-planner.js +0 -1
- package/dist/commands/codebase-to-spec/dispatch-spec.d.ts +3 -6
- package/dist/commands/codebase-to-spec/dispatch-spec.js +3 -6
- package/dist/commands/codebase-to-spec/index.js +3 -2
- package/dist/commands/codebase-to-spec/pack.d.ts +5 -0
- package/dist/commands/codebase-to-spec/pack.js +6 -3
- package/dist/commands/codebase-to-spec/skill-install.js +2 -9
- package/dist/templates/agents/cts-worker.md +3 -3
- package/dist/templates/skills/codebase-to-spec/SKILL.md +44 -168
- package/dist/templates/workflows/specify-codebase.js +372 -0
- package/package.json +2 -2
- 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
|
|
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
|
|
7
|
+
# Codebase to Spec
|
|
7
8
|
|
|
8
|
-
You
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
18
|
+
## Prerequisite: dynamic workflows
|
|
35
19
|
|
|
36
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
###
|
|
28
|
+
### 1. Confirm scope
|
|
112
29
|
|
|
113
|
-
|
|
30
|
+
If the user gave a scope (a path), use it. Otherwise ask one concise question:
|
|
114
31
|
|
|
115
|
-
|
|
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
|
-
|
|
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
|
-
|
|
36
|
+
### 2. Pack (deterministic)
|
|
125
37
|
|
|
126
|
-
|
|
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
|
-
|
|
40
|
+
### 3. Run the workflow
|
|
138
41
|
|
|
139
|
-
|
|
42
|
+
Launch the bundled workflow:
|
|
140
43
|
|
|
141
44
|
```
|
|
142
|
-
|
|
45
|
+
Workflow({ name: "specify-codebase" })
|
|
143
46
|
```
|
|
144
47
|
|
|
145
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
174
|
-
-
|
|
175
|
-
-
|
|
176
|
-
-
|
|
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
|
-
|
|
59
|
+
### 5. Present (deterministic)
|
|
179
60
|
|
|
180
|
-
|
|
181
|
-
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
68
|
+
### 6. Summarize and offer follow-ups
|
|
191
69
|
|
|
192
|
-
|
|
193
|
-
- **
|
|
194
|
-
- **
|
|
195
|
-
- **
|
|
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
|
|
201
|
-
- Don't
|
|
202
|
-
- Don't claim convergence when
|
|
203
|
-
- Don't
|
|
204
|
-
- Don't
|
|
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
|
|
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.
|
|
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.
|
|
74
|
+
"vitest": "^4.1.8"
|
|
75
75
|
},
|
|
76
76
|
"files": [
|
|
77
77
|
"dist",
|