@popoverai/dotrequirements 0.24.1 → 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.
Files changed (53) hide show
  1. package/README.md +1 -1
  2. package/dist/codebase-to-spec/cache.d.ts +6 -0
  3. package/dist/codebase-to-spec/cache.js +1 -0
  4. package/dist/codebase-to-spec/claude.d.ts +1 -0
  5. package/dist/codebase-to-spec/claude.js +9 -0
  6. package/dist/codebase-to-spec/dispatch.d.ts +69 -0
  7. package/dist/codebase-to-spec/dispatch.js +484 -0
  8. package/dist/codebase-to-spec/pack.d.ts +16 -0
  9. package/dist/codebase-to-spec/pack.js +17 -3
  10. package/dist/codebase-to-spec/present.d.ts +8 -1
  11. package/dist/codebase-to-spec/present.js +7 -4
  12. package/dist/codebase-to-spec/progress.d.ts +6 -0
  13. package/dist/codebase-to-spec/progress.js +34 -0
  14. package/dist/codebase-to-spec/prompts/outline-reviewer.d.ts +1 -1
  15. package/dist/codebase-to-spec/prompts/outline-reviewer.js +3 -1
  16. package/dist/codebase-to-spec/prompts/planner-initial.d.ts +1 -1
  17. package/dist/codebase-to-spec/prompts/planner-initial.js +4 -0
  18. package/dist/codebase-to-spec/prompts/planner-revise.d.ts +1 -1
  19. package/dist/codebase-to-spec/prompts/planner-revise.js +2 -2
  20. package/dist/codebase-to-spec/prompts/spec-reviewer.d.ts +1 -1
  21. package/dist/codebase-to-spec/prompts/spec-reviewer.js +6 -1
  22. package/dist/codebase-to-spec/prompts/specifier.d.ts +1 -1
  23. package/dist/codebase-to-spec/prompts/specifier.js +6 -4
  24. package/dist/codebase-to-spec/prompts/style-check.d.ts +10 -2
  25. package/dist/codebase-to-spec/prompts/style-check.js +76 -46
  26. package/dist/codebase-to-spec/schemas.d.ts +460 -1
  27. package/dist/codebase-to-spec/schemas.js +158 -1
  28. package/dist/codebase-to-spec/skill-install.d.ts +36 -12
  29. package/dist/codebase-to-spec/skill-install.js +127 -26
  30. package/dist/codebase-to-spec/specifier.js +6 -0
  31. package/dist/commands/codebase-to-spec/compose-orchestrator.d.ts +14 -0
  32. package/dist/commands/codebase-to-spec/compose-orchestrator.js +54 -0
  33. package/dist/commands/codebase-to-spec/dispatch-context.d.ts +12 -0
  34. package/dist/commands/codebase-to-spec/dispatch-context.js +22 -0
  35. package/dist/commands/codebase-to-spec/dispatch-editor.d.ts +16 -0
  36. package/dist/commands/codebase-to-spec/dispatch-editor.js +71 -0
  37. package/dist/commands/codebase-to-spec/dispatch-planner.d.ts +19 -0
  38. package/dist/commands/codebase-to-spec/dispatch-planner.js +90 -0
  39. package/dist/commands/codebase-to-spec/dispatch-spec.d.ts +16 -0
  40. package/dist/commands/codebase-to-spec/dispatch-spec.js +59 -0
  41. package/dist/commands/codebase-to-spec/index.js +69 -1
  42. package/dist/commands/codebase-to-spec/pack.d.ts +6 -0
  43. package/dist/commands/codebase-to-spec/pack.js +1 -0
  44. package/dist/commands/codebase-to-spec/present-orchestrator.d.ts +20 -0
  45. package/dist/commands/codebase-to-spec/present-orchestrator.js +81 -0
  46. package/dist/commands/codebase-to-spec/present.d.ts +5 -0
  47. package/dist/commands/codebase-to-spec/present.js +6 -1
  48. package/dist/commands/codebase-to-spec/run.js +1 -0
  49. package/dist/commands/codebase-to-spec/skill-install.js +12 -1
  50. package/dist/templates/agents/cts-worker.md +9 -0
  51. package/dist/templates/hooks/cts-worker-persona.sh +76 -0
  52. package/dist/templates/skills/codebase-to-spec/SKILL.md +159 -68
  53. package/package.json +4 -5
package/README.md CHANGED
@@ -248,7 +248,7 @@ Generate behavioral requirements from an existing codebase. Packs the codebase,
248
248
  ```bash
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
- dotreq cts skill-install # install the conversational skill wrapper
251
+ dotreq cts run --scope src --ignore-requirements # run against a codebase that already has its own .requirements/
252
252
  ```
253
253
 
254
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) {
@@ -15,6 +15,7 @@
15
15
  * Requirements covered:
16
16
  * - CTS-PLAN-2, CTS-EDIT-1: stateful reviewer sessions via --session-id / --resume
17
17
  * - CTS-PLAN-2, CTS-EDIT-1: JSON-schema-validated output via --json-schema
18
+ * - CTS-OBSERVE-1.4: subprocess PID emitted to stderr after spawn
18
19
  */
19
20
  export interface ClaudeRunOptions {
20
21
  /** System prompt content (passed via --system-prompt). */
@@ -15,9 +15,11 @@
15
15
  * Requirements covered:
16
16
  * - CTS-PLAN-2, CTS-EDIT-1: stateful reviewer sessions via --session-id / --resume
17
17
  * - CTS-PLAN-2, CTS-EDIT-1: JSON-schema-validated output via --json-schema
18
+ * - CTS-OBSERVE-1.4: subprocess PID emitted to stderr after spawn
18
19
  */
19
20
  import { spawn } from "node:child_process";
20
21
  import { tmpdir } from "node:os";
22
+ import { stderr as processStderr } from "node:process";
21
23
  /**
22
24
  * Default runner — spawns the `claude` binary as a subprocess.
23
25
  */
@@ -67,6 +69,13 @@ export async function runClaude(options) {
67
69
  // mode the user has configured (API key if set, OAuth otherwise).
68
70
  stdio: ["pipe", "pipe", "pipe"],
69
71
  });
72
+ // Diagnostic: announce the spawned PID to stderr so engineers watching a
73
+ // long-running pipeline can verify the subprocess is alive while we await
74
+ // its response (CTS-OBSERVE-1.4). Stderr keeps the structured progress
75
+ // stream on stdout clean for the skill and CI parsers.
76
+ if (child.pid !== undefined) {
77
+ processStderr.write(`[cts.diag] claude -p spawned (pid=${child.pid})\n`);
78
+ }
70
79
  // Close stdin immediately — we pass user message via argv, not stdin.
71
80
  child.stdin.end();
72
81
  let stdout = "";
@@ -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
@@ -8,6 +8,7 @@
8
8
  * Requirements covered:
9
9
  * - CTS-PACK-1: Pack stage produces compressed and uncompressed views
10
10
  * - CTS-PACK-2: Engineer can extend the ignore list per project
11
+ * - CTS-PRESENT-5.0: --ignore-requirements adds .requirements/** to pack ignores
11
12
  */
12
13
  import type { CachePaths } from "./cache.js";
13
14
  /**
@@ -31,6 +32,12 @@ export interface PackOptions {
31
32
  scope?: string;
32
33
  /** Additional ignore patterns (beyond defaults and project-level). */
33
34
  extraIgnores?: string[];
35
+ /**
36
+ * When true, add `.requirements/**` to the ignore list so an existing
37
+ * requirements directory does not influence the generated output.
38
+ * See CTS-PRESENT-5.
39
+ */
40
+ ignoreRequirements?: boolean;
34
41
  }
35
42
  export interface PackResult {
36
43
  overviewPath: string;
@@ -47,5 +54,14 @@ export interface PackResult {
47
54
  * NOTE: Repomix's TypeScript API is invoked via `runCli`. We invoke it as the
48
55
  * library exposes it; the surface is small enough that this wraps cleanly.
49
56
  */
57
+ /**
58
+ * Build the full ignore list for a pack invocation, combining defaults,
59
+ * project-level patterns, caller-supplied extras, and (when set) the
60
+ * `.requirements/**` exclusion that powers `--ignore-requirements`.
61
+ */
62
+ export declare function buildIgnoreList(projectRoot: string, options?: {
63
+ extraIgnores?: string[];
64
+ ignoreRequirements?: boolean;
65
+ }): string[];
50
66
  export declare function runPack(options: PackOptions): Promise<PackResult>;
51
67
  //# sourceMappingURL=pack.d.ts.map