@popoverai/dotrequirements 0.24.2 → 0.24.3
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 +0 -1
- package/dist/codebase-to-spec/cache.d.ts +6 -0
- package/dist/codebase-to-spec/cache.js +1 -0
- package/dist/codebase-to-spec/dispatch.d.ts +69 -0
- package/dist/codebase-to-spec/dispatch.js +484 -0
- package/dist/codebase-to-spec/schemas.d.ts +375 -0
- package/dist/codebase-to-spec/schemas.js +133 -0
- package/dist/codebase-to-spec/skill-install.d.ts +36 -12
- package/dist/codebase-to-spec/skill-install.js +127 -26
- package/dist/commands/codebase-to-spec/compose-orchestrator.d.ts +14 -0
- package/dist/commands/codebase-to-spec/compose-orchestrator.js +54 -0
- package/dist/commands/codebase-to-spec/dispatch-context.d.ts +12 -0
- package/dist/commands/codebase-to-spec/dispatch-context.js +22 -0
- package/dist/commands/codebase-to-spec/dispatch-editor.d.ts +16 -0
- package/dist/commands/codebase-to-spec/dispatch-editor.js +71 -0
- package/dist/commands/codebase-to-spec/dispatch-planner.d.ts +19 -0
- package/dist/commands/codebase-to-spec/dispatch-planner.js +90 -0
- package/dist/commands/codebase-to-spec/dispatch-spec.d.ts +16 -0
- package/dist/commands/codebase-to-spec/dispatch-spec.js +59 -0
- package/dist/commands/codebase-to-spec/index.js +56 -1
- package/dist/commands/codebase-to-spec/present-orchestrator.d.ts +20 -0
- package/dist/commands/codebase-to-spec/present-orchestrator.js +81 -0
- package/dist/commands/codebase-to-spec/skill-install.js +12 -1
- package/dist/templates/agents/cts-worker.md +9 -0
- package/dist/templates/hooks/cts-worker-persona.sh +76 -0
- package/dist/templates/skills/codebase-to-spec/SKILL.md +159 -68
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -249,7 +249,6 @@ Generate behavioral requirements from an existing codebase. Packs the codebase,
|
|
|
249
249
|
dotreq cts run --scope src # full pipeline end-to-end
|
|
250
250
|
dotreq cts run --scope src --fresh # discard cache and start over
|
|
251
251
|
dotreq cts run --scope src --ignore-requirements # run against a codebase that already has its own .requirements/
|
|
252
|
-
dotreq cts skill-install # install the conversational skill wrapper
|
|
253
252
|
```
|
|
254
253
|
|
|
255
254
|
`cts` shells out to `claude -p` and uses whatever auth mode you've configured for Claude Code. Each pipeline stage is also runnable on its own (`dotreq cts pack`, `plan-loop`, `fan-out`, `compose`, `edit-loop`, `present`) for partial re-runs and debugging.
|
|
@@ -38,6 +38,12 @@ export interface CachePaths {
|
|
|
38
38
|
specFinal: string;
|
|
39
39
|
/** Final pipeline summary JSON, surfaced by present and consumed by wrappers. */
|
|
40
40
|
pipelineSummary: string;
|
|
41
|
+
/**
|
|
42
|
+
* Conversational orchestrator outline — single YAML file that evolves
|
|
43
|
+
* across the run (Phase 2b refactor). Carries content + review.thread
|
|
44
|
+
* lifecycle state inline. Distinct from the legacy outline-N.json files.
|
|
45
|
+
*/
|
|
46
|
+
outline: string;
|
|
41
47
|
}
|
|
42
48
|
export declare function cachePaths(projectRoot: string): CachePaths;
|
|
43
49
|
export declare function ensureCacheDir(projectRoot: string): CachePaths;
|
|
@@ -29,6 +29,7 @@ export function cachePaths(projectRoot) {
|
|
|
29
29
|
specReviewTurn: (n) => join(root, `spec-review-${n}.json`),
|
|
30
30
|
specFinal: join(root, "spec-final.md"),
|
|
31
31
|
pipelineSummary: join(root, "pipeline-summary.json"),
|
|
32
|
+
outline: join(root, "outline.yaml"),
|
|
32
33
|
};
|
|
33
34
|
}
|
|
34
35
|
export function ensureCacheDir(projectRoot) {
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dispatch-context composition for the conversational orchestrator.
|
|
3
|
+
*
|
|
4
|
+
* The orchestrator's PreToolUse hook calls `dotrequirements cts dispatch-context
|
|
5
|
+
* <dispatch-id>` to fetch the composed prompt that should be injected into a
|
|
6
|
+
* cts-worker subagent's first turn (per CTSO-CLI-1).
|
|
7
|
+
*
|
|
8
|
+
* Recognized dispatch IDs:
|
|
9
|
+
* - `phase1-test` — mechanical plumbing test (Phase 1)
|
|
10
|
+
* - `planner-initial` — initial planner dispatch; writes outline.yaml
|
|
11
|
+
* - `planner-revise-<turn>` — revising planner dispatch; reads the prior
|
|
12
|
+
* outline.yaml (with its review.thread), applies the latest thread
|
|
13
|
+
* entry's revisions, writes a new outline.yaml. `<turn>` is the round
|
|
14
|
+
* number being PRODUCED (turn 2 = first revision).
|
|
15
|
+
*
|
|
16
|
+
* Future phases will add `specifier-<area>`, `editor-<area>`, etc.
|
|
17
|
+
*
|
|
18
|
+
* Requirements covered:
|
|
19
|
+
* - CTSO-CLI-1: CLI exposes commands that return dispatch instructions
|
|
20
|
+
* - CTSO-CONV-2: orchestrator iterates on the planner's outline until approved
|
|
21
|
+
*/
|
|
22
|
+
export interface DispatchContext {
|
|
23
|
+
/**
|
|
24
|
+
* The full composed prompt the worker subagent should receive as its first
|
|
25
|
+
* turn. Includes the persona body + paths + any prior-round context needed
|
|
26
|
+
* to ground the worker's behavior.
|
|
27
|
+
*/
|
|
28
|
+
prompt: string;
|
|
29
|
+
}
|
|
30
|
+
/** Phase 1 dispatch-id used to validate the mechanical plumbing end-to-end. */
|
|
31
|
+
export declare const PHASE1_TEST_DISPATCH_ID = "phase1-test";
|
|
32
|
+
/** Phase 1 token the test worker echoes back. */
|
|
33
|
+
export declare const PHASE1_TEST_TOKEN = "PHASE1-WORKER-OK token=CTS9X";
|
|
34
|
+
/** Phase 2 dispatch-id for the initial planner pass. */
|
|
35
|
+
export declare const PLANNER_INITIAL_DISPATCH_ID = "planner-initial";
|
|
36
|
+
/**
|
|
37
|
+
* Phase 2b dispatch-id prefix for planner revise dispatches. Suffix is the
|
|
38
|
+
* turn number being PRODUCED (e.g., `planner-revise-2` produces the
|
|
39
|
+
* second turn's outline, reading the prior outline + its latest review
|
|
40
|
+
* thread entry as inputs).
|
|
41
|
+
*/
|
|
42
|
+
export declare const PLANNER_REVISE_DISPATCH_ID_PREFIX = "planner-revise-";
|
|
43
|
+
/**
|
|
44
|
+
* Phase 3 dispatch-id prefix for specifier dispatches. Suffix is the area's
|
|
45
|
+
* prefix from the approved outline (e.g., `specifier-PLAN` runs the
|
|
46
|
+
* specifier for the PLAN area).
|
|
47
|
+
*/
|
|
48
|
+
export declare const SPECIFIER_DISPATCH_ID_PREFIX = "specifier-";
|
|
49
|
+
/**
|
|
50
|
+
* Phase 4 dispatch-id prefix for editor dispatches. Suffix is the area's
|
|
51
|
+
* prefix (e.g., `editor-PLAN` revises the PLAN area's partial based on
|
|
52
|
+
* the latest area.review.thread entry's revisions).
|
|
53
|
+
*/
|
|
54
|
+
export declare const EDITOR_DISPATCH_ID_PREFIX = "editor-";
|
|
55
|
+
export interface ComposeDispatchOptions {
|
|
56
|
+
/**
|
|
57
|
+
* Project root used to resolve cache paths. Defaults to `findProjectRoot()`
|
|
58
|
+
* starting from cwd. Tests pass a temp directory.
|
|
59
|
+
*/
|
|
60
|
+
projectRoot?: string;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Compose the dispatch context for a given dispatch-id.
|
|
64
|
+
*
|
|
65
|
+
* @returns the composed dispatch context, or `null` if the id is unknown
|
|
66
|
+
* or required inputs (cache files) are missing.
|
|
67
|
+
*/
|
|
68
|
+
export declare function composeDispatchContext(dispatchId: string, options?: ComposeDispatchOptions): DispatchContext | null;
|
|
69
|
+
//# sourceMappingURL=dispatch.d.ts.map
|
|
@@ -0,0 +1,484 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dispatch-context composition for the conversational orchestrator.
|
|
3
|
+
*
|
|
4
|
+
* The orchestrator's PreToolUse hook calls `dotrequirements cts dispatch-context
|
|
5
|
+
* <dispatch-id>` to fetch the composed prompt that should be injected into a
|
|
6
|
+
* cts-worker subagent's first turn (per CTSO-CLI-1).
|
|
7
|
+
*
|
|
8
|
+
* Recognized dispatch IDs:
|
|
9
|
+
* - `phase1-test` — mechanical plumbing test (Phase 1)
|
|
10
|
+
* - `planner-initial` — initial planner dispatch; writes outline.yaml
|
|
11
|
+
* - `planner-revise-<turn>` — revising planner dispatch; reads the prior
|
|
12
|
+
* outline.yaml (with its review.thread), applies the latest thread
|
|
13
|
+
* entry's revisions, writes a new outline.yaml. `<turn>` is the round
|
|
14
|
+
* number being PRODUCED (turn 2 = first revision).
|
|
15
|
+
*
|
|
16
|
+
* Future phases will add `specifier-<area>`, `editor-<area>`, etc.
|
|
17
|
+
*
|
|
18
|
+
* Requirements covered:
|
|
19
|
+
* - CTSO-CLI-1: CLI exposes commands that return dispatch instructions
|
|
20
|
+
* - CTSO-CONV-2: orchestrator iterates on the planner's outline until approved
|
|
21
|
+
*/
|
|
22
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
23
|
+
import { findProjectRoot } from "../utils/project-settings.js";
|
|
24
|
+
import { cachePaths } from "./cache.js";
|
|
25
|
+
import { sanitizeAreaName } from "./fan-out.js";
|
|
26
|
+
import { EDITOR_PROMPT } from "./prompts/editor.js";
|
|
27
|
+
import { PLANNER_INITIAL_PROMPT } from "./prompts/planner-initial.js";
|
|
28
|
+
import { PLANNER_REVISE_PROMPT } from "./prompts/planner-revise.js";
|
|
29
|
+
import { SPECIFIER_PROMPT } from "./prompts/specifier.js";
|
|
30
|
+
import { parseConversationalOutline, } from "./schemas.js";
|
|
31
|
+
/** Phase 1 dispatch-id used to validate the mechanical plumbing end-to-end. */
|
|
32
|
+
export const PHASE1_TEST_DISPATCH_ID = "phase1-test";
|
|
33
|
+
/** Phase 1 token the test worker echoes back. */
|
|
34
|
+
export const PHASE1_TEST_TOKEN = "PHASE1-WORKER-OK token=CTS9X";
|
|
35
|
+
/** Phase 2 dispatch-id for the initial planner pass. */
|
|
36
|
+
export const PLANNER_INITIAL_DISPATCH_ID = "planner-initial";
|
|
37
|
+
/**
|
|
38
|
+
* Phase 2b dispatch-id prefix for planner revise dispatches. Suffix is the
|
|
39
|
+
* turn number being PRODUCED (e.g., `planner-revise-2` produces the
|
|
40
|
+
* second turn's outline, reading the prior outline + its latest review
|
|
41
|
+
* thread entry as inputs).
|
|
42
|
+
*/
|
|
43
|
+
export const PLANNER_REVISE_DISPATCH_ID_PREFIX = "planner-revise-";
|
|
44
|
+
/**
|
|
45
|
+
* Phase 3 dispatch-id prefix for specifier dispatches. Suffix is the area's
|
|
46
|
+
* prefix from the approved outline (e.g., `specifier-PLAN` runs the
|
|
47
|
+
* specifier for the PLAN area).
|
|
48
|
+
*/
|
|
49
|
+
export const SPECIFIER_DISPATCH_ID_PREFIX = "specifier-";
|
|
50
|
+
/**
|
|
51
|
+
* Phase 4 dispatch-id prefix for editor dispatches. Suffix is the area's
|
|
52
|
+
* prefix (e.g., `editor-PLAN` revises the PLAN area's partial based on
|
|
53
|
+
* the latest area.review.thread entry's revisions).
|
|
54
|
+
*/
|
|
55
|
+
export const EDITOR_DISPATCH_ID_PREFIX = "editor-";
|
|
56
|
+
/**
|
|
57
|
+
* Compose the dispatch context for a given dispatch-id.
|
|
58
|
+
*
|
|
59
|
+
* @returns the composed dispatch context, or `null` if the id is unknown
|
|
60
|
+
* or required inputs (cache files) are missing.
|
|
61
|
+
*/
|
|
62
|
+
export function composeDispatchContext(dispatchId, options = {}) {
|
|
63
|
+
if (dispatchId === PHASE1_TEST_DISPATCH_ID) {
|
|
64
|
+
return composePhase1Test();
|
|
65
|
+
}
|
|
66
|
+
if (dispatchId === PLANNER_INITIAL_DISPATCH_ID) {
|
|
67
|
+
return composePlannerInitial(options);
|
|
68
|
+
}
|
|
69
|
+
if (dispatchId.startsWith(PLANNER_REVISE_DISPATCH_ID_PREFIX)) {
|
|
70
|
+
const turnText = dispatchId.slice(PLANNER_REVISE_DISPATCH_ID_PREFIX.length);
|
|
71
|
+
const turn = Number.parseInt(turnText, 10);
|
|
72
|
+
if (!Number.isFinite(turn) || turn < 2 || String(turn) !== turnText) {
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
return composePlannerRevise(turn, options);
|
|
76
|
+
}
|
|
77
|
+
if (dispatchId.startsWith(SPECIFIER_DISPATCH_ID_PREFIX)) {
|
|
78
|
+
const areaPrefix = dispatchId.slice(SPECIFIER_DISPATCH_ID_PREFIX.length);
|
|
79
|
+
if (!areaPrefix)
|
|
80
|
+
return null;
|
|
81
|
+
return composeSpecifier(areaPrefix, options);
|
|
82
|
+
}
|
|
83
|
+
if (dispatchId.startsWith(EDITOR_DISPATCH_ID_PREFIX)) {
|
|
84
|
+
const areaPrefix = dispatchId.slice(EDITOR_DISPATCH_ID_PREFIX.length);
|
|
85
|
+
if (!areaPrefix)
|
|
86
|
+
return null;
|
|
87
|
+
return composeEditor(areaPrefix, options);
|
|
88
|
+
}
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
function composePhase1Test() {
|
|
92
|
+
return {
|
|
93
|
+
prompt: [
|
|
94
|
+
"You are the Phase 1 mechanical-plumbing test worker for the",
|
|
95
|
+
"codebase-to-spec conversational orchestrator.",
|
|
96
|
+
"",
|
|
97
|
+
"Reply with exactly the following text and nothing else:",
|
|
98
|
+
"",
|
|
99
|
+
PHASE1_TEST_TOKEN,
|
|
100
|
+
].join("\n"),
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* YAML overlay applied on top of both planner prompts. Overrides their
|
|
105
|
+
* "output JSON" instructions and adds per-area customer guidance.
|
|
106
|
+
*
|
|
107
|
+
* Designed as a single shared block since the schema, customer guidance,
|
|
108
|
+
* and format override apply identically to initial + revise dispatches.
|
|
109
|
+
*/
|
|
110
|
+
const PLANNER_YAML_OVERLAY = `---
|
|
111
|
+
|
|
112
|
+
## Output format — read carefully, this OVERRIDES the system prompt above
|
|
113
|
+
|
|
114
|
+
The system prompt above instructs you to output a JSON object. **This dispatch overrides that.** Your output format is YAML, not JSON. Follow the YAML schema below.
|
|
115
|
+
|
|
116
|
+
The schema also includes per-area \`customers\` and renames \`files\` to \`source_files\`. Both are new fields not present in the system prompt's schema description.
|
|
117
|
+
|
|
118
|
+
## YAML output schema
|
|
119
|
+
|
|
120
|
+
Your output is a YAML document with this structure (every field below is required unless marked optional):
|
|
121
|
+
|
|
122
|
+
\`\`\`yaml
|
|
123
|
+
title: <string>
|
|
124
|
+
defaultPrefix: <uppercase identifier, distinct from any area prefix>
|
|
125
|
+
summary: |
|
|
126
|
+
<multi-line prose summarizing the system: what it is, who it's for>
|
|
127
|
+
review: # OPTIONAL — present only when revising; preserve from input verbatim
|
|
128
|
+
result: approved | needs-revision
|
|
129
|
+
thread:
|
|
130
|
+
- result: approved | needs-revision
|
|
131
|
+
revisions: # only when result is needs-revision; ≥1 entries
|
|
132
|
+
- <string>
|
|
133
|
+
areas:
|
|
134
|
+
- name: <string — read in customer vocabulary>
|
|
135
|
+
prefix: <uppercase identifier, unique within document>
|
|
136
|
+
description: |
|
|
137
|
+
<multi-line prose grounding this area in customer behavior>
|
|
138
|
+
source_files: [<file paths from the pack>]
|
|
139
|
+
customers:
|
|
140
|
+
- description: |
|
|
141
|
+
<multi-line prose: SPECIFIC, area-scoped customer description>
|
|
142
|
+
\`\`\`
|
|
143
|
+
|
|
144
|
+
## Per-area customer guidance (NEW field — read carefully)
|
|
145
|
+
|
|
146
|
+
For EACH area, identify the customer(s) who care about that area's behaviors. Write each customer description fresh, specific to who interacts with THAT area:
|
|
147
|
+
|
|
148
|
+
- Not "developer" but "a CTS operator at the planning checkpoint, deciding whether CTS understood their codebase well enough to fan-out specifiers"
|
|
149
|
+
- Not "user" but the concrete role and the specific situation they're in when this area's behaviors matter
|
|
150
|
+
- The same role appearing in multiple areas may have DIFFERENT needs at different moments; describe each occurrence freshly
|
|
151
|
+
- If you find yourself writing the same generic description across areas, that's a smell — either your customers aren't specific enough OR your areas aren't customer-distinct
|
|
152
|
+
|
|
153
|
+
There is no project-level customer list. Each area's customers are defined inline, area-scoped.
|
|
154
|
+
|
|
155
|
+
## Schema validation reminders
|
|
156
|
+
|
|
157
|
+
- \`defaultPrefix\` and each area's \`prefix\` must be uppercase alphanumeric/underscore (regex: \`/^[A-Z][A-Z0-9_]*$/\`).
|
|
158
|
+
- Area prefixes must be unique within the document AND distinct from \`defaultPrefix\`.
|
|
159
|
+
- \`source_files\` paths must match the \`File: <path>\` headers in the pack exactly.
|
|
160
|
+
- Each area must have ≥1 customer with a non-empty \`description\`.
|
|
161
|
+
`;
|
|
162
|
+
function composePlannerInitial(options) {
|
|
163
|
+
const projectRoot = options.projectRoot ?? findProjectRoot(process.cwd()) ?? process.cwd();
|
|
164
|
+
const paths = cachePaths(projectRoot);
|
|
165
|
+
if (!existsSync(paths.overview)) {
|
|
166
|
+
return null;
|
|
167
|
+
}
|
|
168
|
+
const prompt = [
|
|
169
|
+
PLANNER_INITIAL_PROMPT,
|
|
170
|
+
"",
|
|
171
|
+
PLANNER_YAML_OVERLAY,
|
|
172
|
+
"",
|
|
173
|
+
"---",
|
|
174
|
+
"",
|
|
175
|
+
"## Your task for this dispatch",
|
|
176
|
+
"",
|
|
177
|
+
`Read the compressed packed codebase at: ${paths.overview}`,
|
|
178
|
+
"",
|
|
179
|
+
`Produce the outline YAML conforming to the schema above and write it to: ${paths.outline}`,
|
|
180
|
+
"",
|
|
181
|
+
"Do NOT include a `review` field — that section is added by the conversational orchestrator after you finish. Only emit the content fields.",
|
|
182
|
+
"",
|
|
183
|
+
"Do not output YAML in your reply — only write it to the file. When the file is written, end your response with a brief one-line confirmation noting the file path.",
|
|
184
|
+
].join("\n");
|
|
185
|
+
return { prompt };
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Resolve the CLI invocation prefix worker subagents should use when running
|
|
189
|
+
* `cts validate` and `cts style-check` on their partials. Resolved at module
|
|
190
|
+
* load so every dispatch composed in this run uses the same prefix:
|
|
191
|
+
*
|
|
192
|
+
* 1. `DOTREQUIREMENTS_CLI` env var if set (same override the cts-worker
|
|
193
|
+
* hook script uses — keeps the two resolution paths consistent).
|
|
194
|
+
* 2. `node <process.argv[1]>` — points the worker at the exact CLI binary
|
|
195
|
+
* the user just invoked. The worker subagent runs Bash on the same
|
|
196
|
+
* machine in the same project, so reproducing the user's CLI is more
|
|
197
|
+
* reliable than hoping `dotrequirements` on PATH resolves to the same
|
|
198
|
+
* version.
|
|
199
|
+
*/
|
|
200
|
+
function resolveCliInvocation() {
|
|
201
|
+
if (process.env.DOTREQUIREMENTS_CLI)
|
|
202
|
+
return process.env.DOTREQUIREMENTS_CLI;
|
|
203
|
+
return `node ${process.argv[1]}`;
|
|
204
|
+
}
|
|
205
|
+
const LOCAL_CLI_INVOCATION = resolveCliInvocation();
|
|
206
|
+
/**
|
|
207
|
+
* Overlay for the specifier dispatch. Inserted between the legacy
|
|
208
|
+
* SPECIFIER_PROMPT and the per-dispatch task instructions. Reinforces:
|
|
209
|
+
* - Customer-grounding discipline (per-area customers from outline.yaml)
|
|
210
|
+
* - source.txt as the read source for area files
|
|
211
|
+
* - Validate + style-check workflow
|
|
212
|
+
*/
|
|
213
|
+
const SPECIFIER_OVERLAY = `---
|
|
214
|
+
|
|
215
|
+
## Updated context — read carefully
|
|
216
|
+
|
|
217
|
+
The system prompt above gives you the discipline for producing a behavioral spec. This dispatch supplies the specific area you're working on, the customer-grounding for that area (from the approved outline), and the validate/style-check commands you must run on your own draft.
|
|
218
|
+
|
|
219
|
+
## Customer-grounding (the area's customers from the approved outline)
|
|
220
|
+
|
|
221
|
+
The customers below are area-scoped — each describes who interacts with THIS area's behaviors and what they're trying to do at this point in the system. Ground your requirements in these specific customer needs. Use the personas the outline names; don't substitute generic "user" / "developer" framings.
|
|
222
|
+
|
|
223
|
+
## Output format
|
|
224
|
+
|
|
225
|
+
The PARTIAL FILE is your primary deliverable. Markdown with fenced \`dotrequirements\` blocks per the format rules in the system prompt above. Do NOT include YAML frontmatter, H1 title, summary paragraph, or area H2 — the composer adds those downstream.
|
|
226
|
+
|
|
227
|
+
## Workflow (legacy CTS-SPEC-3 — REQUIRED)
|
|
228
|
+
|
|
229
|
+
After writing your initial draft, you MUST run validate then style-check on your own partial via Bash. Apply MUST FIX and SHOULD FIX findings via Edit. Style-check runs at most twice. Specific Bash commands appear in the task section below.
|
|
230
|
+
`;
|
|
231
|
+
function composeSpecifier(areaPrefix, options) {
|
|
232
|
+
const projectRoot = options.projectRoot ?? findProjectRoot(process.cwd()) ?? process.cwd();
|
|
233
|
+
const paths = cachePaths(projectRoot);
|
|
234
|
+
if (!existsSync(paths.outline) || !existsSync(paths.source)) {
|
|
235
|
+
return null;
|
|
236
|
+
}
|
|
237
|
+
// Parse and validate the outline. The outline must be approved (or we
|
|
238
|
+
// wouldn't be fanning out specifiers).
|
|
239
|
+
let outline;
|
|
240
|
+
try {
|
|
241
|
+
outline = parseConversationalOutline(readFileSync(paths.outline, "utf-8"));
|
|
242
|
+
}
|
|
243
|
+
catch {
|
|
244
|
+
return null;
|
|
245
|
+
}
|
|
246
|
+
if (!outline.review || outline.review.result !== "approved") {
|
|
247
|
+
return null;
|
|
248
|
+
}
|
|
249
|
+
const area = outline.areas.find((a) => a.prefix === areaPrefix);
|
|
250
|
+
if (!area)
|
|
251
|
+
return null;
|
|
252
|
+
const partialPath = paths.partial(sanitizeAreaName(area.name));
|
|
253
|
+
const outlineYaml = readFileSync(paths.outline, "utf-8");
|
|
254
|
+
const customersBlock = area.customers
|
|
255
|
+
.map((c, i) => `${i + 1}. ${c.description.trim()}`)
|
|
256
|
+
.join("\n\n");
|
|
257
|
+
const prompt = [
|
|
258
|
+
SPECIFIER_PROMPT,
|
|
259
|
+
"",
|
|
260
|
+
SPECIFIER_OVERLAY,
|
|
261
|
+
"",
|
|
262
|
+
"---",
|
|
263
|
+
"",
|
|
264
|
+
"## Your area",
|
|
265
|
+
"",
|
|
266
|
+
`Name: ${area.name}`,
|
|
267
|
+
`Prefix: ${area.prefix}`,
|
|
268
|
+
`Document defaultPrefix: ${outline.defaultPrefix}`,
|
|
269
|
+
`Requirement ID form: ${outline.defaultPrefix}-${area.prefix}-<N> (sequential, 1-indexed, no zero-padding)`,
|
|
270
|
+
"",
|
|
271
|
+
"### Description (from the approved outline)",
|
|
272
|
+
"",
|
|
273
|
+
area.description.trim(),
|
|
274
|
+
"",
|
|
275
|
+
"### Customers (area-scoped — ground your requirements in these)",
|
|
276
|
+
"",
|
|
277
|
+
customersBlock,
|
|
278
|
+
"",
|
|
279
|
+
"### Source files in this area",
|
|
280
|
+
"",
|
|
281
|
+
...area.source_files.map((f) => `- ${f}`),
|
|
282
|
+
"",
|
|
283
|
+
`Read these files from the uncompressed pack at ${paths.source} (grep for \`File: <name>\` headers to find each one's content). You can also read them directly from the worktree if you know the paths. You may consult other files in the pack (e.g., tests, READMEs) for behaviors documented outside this area's primary files.`,
|
|
284
|
+
"",
|
|
285
|
+
"---",
|
|
286
|
+
"",
|
|
287
|
+
"## Full outline (for cross-area awareness — what is in scope vs. not)",
|
|
288
|
+
"",
|
|
289
|
+
"```yaml",
|
|
290
|
+
outlineYaml.trim(),
|
|
291
|
+
"```",
|
|
292
|
+
"",
|
|
293
|
+
"---",
|
|
294
|
+
"",
|
|
295
|
+
"## Your task for this dispatch",
|
|
296
|
+
"",
|
|
297
|
+
`Write your partial to: ${partialPath}`,
|
|
298
|
+
"",
|
|
299
|
+
"Then run validate (REQUIRED) and style-check (REQUIRED) on your own partial via Bash:",
|
|
300
|
+
"",
|
|
301
|
+
`- Validate: \`${LOCAL_CLI_INVOCATION} cts validate ${partialPath}\``,
|
|
302
|
+
`- Style-check: \`${LOCAL_CLI_INVOCATION} cts style-check ${partialPath}\``,
|
|
303
|
+
"",
|
|
304
|
+
"Follow the three-phase workflow (Draft → Validate → Style-check) from the system prompt. Apply MUST FIX and SHOULD FIX findings via Edit. Cap style-check at two runs.",
|
|
305
|
+
"",
|
|
306
|
+
'End with a short confirmation: "Done. Partial saved to <path>."',
|
|
307
|
+
].join("\n");
|
|
308
|
+
return { prompt };
|
|
309
|
+
}
|
|
310
|
+
const EDITOR_OVERLAY = `---
|
|
311
|
+
|
|
312
|
+
## Updated context — read carefully
|
|
313
|
+
|
|
314
|
+
The system prompt above describes editing a composed spec **document**. **This dispatch scopes you to ONE partial spec for ONE behavioral area**, not the composed document. The partial is the work-in-progress for one area; CA reviewed it and flagged revisions you must apply.
|
|
315
|
+
|
|
316
|
+
## Critique shape (different from the legacy reviewer's shape)
|
|
317
|
+
|
|
318
|
+
Our orchestrator uses a simpler critique shape. You receive a list of \`revisions\` — concrete directives to apply. There are NO categorized findings (no coverage_gaps / framing_errors / cross_area_issues / internal_mechanics_drift). Just revisions.
|
|
319
|
+
|
|
320
|
+
You are in **apply-mode-equivalent**: apply each revision in the list as written. Do not introduce changes beyond the listed revisions. Do not restructure unflagged content. If a revision is ambiguous, apply your best literal interpretation and note the ambiguity in your final stdout confirmation.
|
|
321
|
+
|
|
322
|
+
## Customer-grounding (the area's customers from outline)
|
|
323
|
+
|
|
324
|
+
The customers below are area-scoped. Maintain the customer-grounded framing of existing requirements; any new requirements you add must name the same personas the existing requirements use.
|
|
325
|
+
|
|
326
|
+
## Workflow (REQUIRED — same as specifier)
|
|
327
|
+
|
|
328
|
+
After applying revisions via Edit, you MUST run validate + style-check on the revised partial via Bash. Apply MUST FIX and SHOULD FIX findings via Edit. Style-check runs at most twice. Specific Bash commands appear in the task section below.
|
|
329
|
+
`;
|
|
330
|
+
function composeEditor(areaPrefix, options) {
|
|
331
|
+
const projectRoot = options.projectRoot ?? findProjectRoot(process.cwd()) ?? process.cwd();
|
|
332
|
+
const paths = cachePaths(projectRoot);
|
|
333
|
+
if (!existsSync(paths.outline)) {
|
|
334
|
+
return null;
|
|
335
|
+
}
|
|
336
|
+
let outline;
|
|
337
|
+
try {
|
|
338
|
+
outline = parseConversationalOutline(readFileSync(paths.outline, "utf-8"));
|
|
339
|
+
}
|
|
340
|
+
catch {
|
|
341
|
+
return null;
|
|
342
|
+
}
|
|
343
|
+
const area = outline.areas.find((a) => a.prefix === areaPrefix);
|
|
344
|
+
if (!area)
|
|
345
|
+
return null;
|
|
346
|
+
// Editor only runs when the area has been reviewed and the latest review
|
|
347
|
+
// entry asks for revisions.
|
|
348
|
+
if (!area.review || area.review.result !== "needs-revision") {
|
|
349
|
+
return null;
|
|
350
|
+
}
|
|
351
|
+
const latestEntry = area.review.thread[area.review.thread.length - 1];
|
|
352
|
+
if (!latestEntry || latestEntry.result !== "needs-revision") {
|
|
353
|
+
return null;
|
|
354
|
+
}
|
|
355
|
+
const partialPath = paths.partial(sanitizeAreaName(area.name));
|
|
356
|
+
if (!existsSync(partialPath)) {
|
|
357
|
+
// Can't edit a partial that doesn't exist yet.
|
|
358
|
+
return null;
|
|
359
|
+
}
|
|
360
|
+
const currentPartial = readFileSync(partialPath, "utf-8");
|
|
361
|
+
const customersBlock = area.customers
|
|
362
|
+
.map((c, i) => `${i + 1}. ${c.description.trim()}`)
|
|
363
|
+
.join("\n\n");
|
|
364
|
+
const revisionsBlock = latestEntry.revisions
|
|
365
|
+
.map((r, i) => `${i + 1}. ${r.trim()}`)
|
|
366
|
+
.join("\n\n");
|
|
367
|
+
const prompt = [
|
|
368
|
+
EDITOR_PROMPT,
|
|
369
|
+
"",
|
|
370
|
+
EDITOR_OVERLAY,
|
|
371
|
+
"",
|
|
372
|
+
"---",
|
|
373
|
+
"",
|
|
374
|
+
"## Your area",
|
|
375
|
+
"",
|
|
376
|
+
`Name: ${area.name}`,
|
|
377
|
+
`Prefix: ${area.prefix}`,
|
|
378
|
+
`Document defaultPrefix: ${outline.defaultPrefix}`,
|
|
379
|
+
`Requirement ID form: ${outline.defaultPrefix}-${area.prefix}-<N>`,
|
|
380
|
+
"",
|
|
381
|
+
"### Description (from the approved outline)",
|
|
382
|
+
"",
|
|
383
|
+
area.description.trim(),
|
|
384
|
+
"",
|
|
385
|
+
"### Customers (area-scoped — preserve their grounding in your edits)",
|
|
386
|
+
"",
|
|
387
|
+
customersBlock,
|
|
388
|
+
"",
|
|
389
|
+
"---",
|
|
390
|
+
"",
|
|
391
|
+
"## Current partial (read via Read tool to load it into your view; the content is also embedded here for reference)",
|
|
392
|
+
"",
|
|
393
|
+
`Path: ${partialPath}`,
|
|
394
|
+
"",
|
|
395
|
+
"```markdown",
|
|
396
|
+
currentPartial.trim(),
|
|
397
|
+
"```",
|
|
398
|
+
"",
|
|
399
|
+
"---",
|
|
400
|
+
"",
|
|
401
|
+
"## Reviewer's revisions (apply each as written)",
|
|
402
|
+
"",
|
|
403
|
+
revisionsBlock,
|
|
404
|
+
"",
|
|
405
|
+
"---",
|
|
406
|
+
"",
|
|
407
|
+
"## Your task for this dispatch",
|
|
408
|
+
"",
|
|
409
|
+
`Use the Edit tool to apply each revision to the partial at ${partialPath}. Do not introduce changes beyond the listed revisions.`,
|
|
410
|
+
"",
|
|
411
|
+
"Then run validate (REQUIRED) and style-check (REQUIRED) on the revised partial via Bash:",
|
|
412
|
+
"",
|
|
413
|
+
`- Validate: \`${LOCAL_CLI_INVOCATION} cts validate ${partialPath}\``,
|
|
414
|
+
`- Style-check: \`${LOCAL_CLI_INVOCATION} cts style-check ${partialPath}\``,
|
|
415
|
+
"",
|
|
416
|
+
"Apply MUST FIX and SHOULD FIX findings from style-check via Edit. Cap style-check at two runs.",
|
|
417
|
+
"",
|
|
418
|
+
'End with a brief stdout confirmation summarizing the kinds of changes you made (e.g., "Applied 3 revisions: added 2 requirements about edge cases, rephrased 1 framing-error finding.").',
|
|
419
|
+
].join("\n");
|
|
420
|
+
return { prompt };
|
|
421
|
+
}
|
|
422
|
+
function composePlannerRevise(turn, options) {
|
|
423
|
+
const projectRoot = options.projectRoot ?? findProjectRoot(process.cwd()) ?? process.cwd();
|
|
424
|
+
const paths = cachePaths(projectRoot);
|
|
425
|
+
if (!existsSync(paths.overview) || !existsSync(paths.outline)) {
|
|
426
|
+
return null;
|
|
427
|
+
}
|
|
428
|
+
// Read and validate the current outline. The latest thread entry must
|
|
429
|
+
// be `needs-revision` for a revise dispatch to make sense.
|
|
430
|
+
let outline;
|
|
431
|
+
try {
|
|
432
|
+
outline = parseConversationalOutline(readFileSync(paths.outline, "utf-8"));
|
|
433
|
+
}
|
|
434
|
+
catch {
|
|
435
|
+
return null;
|
|
436
|
+
}
|
|
437
|
+
if (!outline.review || outline.review.result !== "needs-revision") {
|
|
438
|
+
return null;
|
|
439
|
+
}
|
|
440
|
+
const latestEntry = outline.review.thread[outline.review.thread.length - 1];
|
|
441
|
+
if (!latestEntry || latestEntry.result !== "needs-revision") {
|
|
442
|
+
return null;
|
|
443
|
+
}
|
|
444
|
+
const priorOutlineYaml = readFileSync(paths.outline, "utf-8");
|
|
445
|
+
const revisionsYaml = latestEntry.revisions
|
|
446
|
+
.map((r, i) => `- # revision ${i + 1}\n ${r.split("\n").join("\n ")}`)
|
|
447
|
+
.join("\n");
|
|
448
|
+
const prompt = [
|
|
449
|
+
PLANNER_REVISE_PROMPT,
|
|
450
|
+
"",
|
|
451
|
+
PLANNER_YAML_OVERLAY,
|
|
452
|
+
"",
|
|
453
|
+
"---",
|
|
454
|
+
"",
|
|
455
|
+
"## Inputs for this revision",
|
|
456
|
+
"",
|
|
457
|
+
`### Compressed packed codebase`,
|
|
458
|
+
`Path: ${paths.overview}`,
|
|
459
|
+
"",
|
|
460
|
+
`### Current outline.yaml (turn ${turn - 1})`,
|
|
461
|
+
"```yaml",
|
|
462
|
+
priorOutlineYaml.trim(),
|
|
463
|
+
"```",
|
|
464
|
+
"",
|
|
465
|
+
`### Reviewer's revisions (the latest \`review.thread\` entry's revisions, extracted)`,
|
|
466
|
+
"```yaml",
|
|
467
|
+
revisionsYaml,
|
|
468
|
+
"```",
|
|
469
|
+
"",
|
|
470
|
+
"---",
|
|
471
|
+
"",
|
|
472
|
+
"## Your task for this dispatch",
|
|
473
|
+
"",
|
|
474
|
+
"Apply each revision in the list as written. Preserve areas that the revisions don't touch.",
|
|
475
|
+
"",
|
|
476
|
+
"**IMPORTANT: Preserve the `review` section from the current outline verbatim.** Copy `review.result` and the entire `review.thread` array into your output exactly as they appear in the current outline. The orchestrator manages the review section; you must not modify it. After you write the file, the orchestrator will append a new review entry.",
|
|
477
|
+
"",
|
|
478
|
+
`Write the revised outline YAML to: ${paths.outline}`,
|
|
479
|
+
"",
|
|
480
|
+
"Do not output YAML in your reply — only write it to the file. End with a brief one-line confirmation noting the file path.",
|
|
481
|
+
].join("\n");
|
|
482
|
+
return { prompt };
|
|
483
|
+
}
|
|
484
|
+
//# sourceMappingURL=dispatch.js.map
|