@popoverai/dotrequirements 0.24.3 → 0.26.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. package/README.md +7 -8
  2. package/dist/cli.js +8 -1
  3. package/dist/codebase-to-spec/dispatch.d.ts +60 -14
  4. package/dist/codebase-to-spec/dispatch.js +381 -15
  5. package/dist/codebase-to-spec/pack.d.ts +7 -0
  6. package/dist/codebase-to-spec/pack.js +29 -8
  7. package/dist/codebase-to-spec/present.d.ts +9 -0
  8. package/dist/codebase-to-spec/present.js +23 -2
  9. package/dist/codebase-to-spec/prompts/editor.d.ts +1 -1
  10. package/dist/codebase-to-spec/prompts/editor.js +1 -1
  11. package/dist/codebase-to-spec/prompts/specifier.d.ts +1 -1
  12. package/dist/codebase-to-spec/prompts/specifier.js +3 -2
  13. package/dist/codebase-to-spec/schemas.d.ts +153 -0
  14. package/dist/codebase-to-spec/schemas.js +111 -0
  15. package/dist/codebase-to-spec/skill-install.d.ts +42 -29
  16. package/dist/codebase-to-spec/skill-install.js +122 -112
  17. package/dist/codebase-to-spec/version-check.d.ts +31 -0
  18. package/dist/codebase-to-spec/version-check.js +56 -0
  19. package/dist/commands/ai-setup.d.ts +12 -1
  20. package/dist/commands/ai-setup.js +65 -33
  21. package/dist/commands/codebase-to-spec/dispatch-context.d.ts +2 -5
  22. package/dist/commands/codebase-to-spec/dispatch-context.js +2 -5
  23. package/dist/commands/codebase-to-spec/dispatch-editor.d.ts +0 -1
  24. package/dist/commands/codebase-to-spec/dispatch-editor.js +0 -1
  25. package/dist/commands/codebase-to-spec/dispatch-planner.d.ts +0 -1
  26. package/dist/commands/codebase-to-spec/dispatch-planner.js +0 -1
  27. package/dist/commands/codebase-to-spec/dispatch-spec.d.ts +3 -6
  28. package/dist/commands/codebase-to-spec/dispatch-spec.js +3 -6
  29. package/dist/commands/codebase-to-spec/index.js +3 -2
  30. package/dist/commands/codebase-to-spec/pack.d.ts +9 -0
  31. package/dist/commands/codebase-to-spec/pack.js +23 -3
  32. package/dist/commands/codebase-to-spec/skill-install.js +2 -9
  33. package/dist/commands/init.js +6 -1
  34. package/dist/commands/link-resolution.d.ts +79 -0
  35. package/dist/commands/link-resolution.js +141 -0
  36. package/dist/commands/link.d.ts +14 -4
  37. package/dist/commands/link.js +369 -16
  38. package/dist/commands/pull.js +19 -2
  39. package/dist/commands/push.js +36 -2
  40. package/dist/convex.d.ts +5 -3
  41. package/dist/convex.js +5 -3
  42. package/dist/harness/cache.d.ts +0 -14
  43. package/dist/harness/cache.js +1 -41
  44. package/dist/harness/finalize.js +2 -2
  45. package/dist/harness/prepare.js +1 -3
  46. package/dist/harness/requirementsLoader.d.ts +3 -3
  47. package/dist/harness/requirementsLoader.js +13 -8
  48. package/dist/mcp/handlers/authoring.d.ts +5 -5
  49. package/dist/mcp/handlers/authoring.js +9 -9
  50. package/dist/mcp/handlers/push.d.ts +2 -2
  51. package/dist/mcp/handlers/push.js +36 -3
  52. package/dist/mcp/handlers/review.d.ts +4 -4
  53. package/dist/mcp/handlers/review.js +4 -4
  54. package/dist/mcp/handlers/search.d.ts +1 -1
  55. package/dist/mcp/handlers/search.js +1 -1
  56. package/dist/mcp/index.js +29 -0
  57. package/dist/push/core.d.ts +18 -0
  58. package/dist/push/core.js +70 -3
  59. package/dist/push/index.d.ts +1 -1
  60. package/dist/push/index.js +1 -1
  61. package/dist/schema/parser-core.js +5 -1
  62. package/dist/schema/parser.js +5 -1
  63. package/dist/schema/run-marker.d.ts +38 -0
  64. package/dist/schema/run-marker.js +138 -0
  65. package/dist/schema/schemas.d.ts +12 -0
  66. package/dist/schema/schemas.js +1 -0
  67. package/dist/templates/agents/cts-worker.md +3 -3
  68. package/dist/templates/skills/codebase-to-spec/SKILL.md +56 -158
  69. package/dist/templates/workflows/specify-codebase.js +374 -0
  70. package/dist/utils/own-package.d.ts +10 -0
  71. package/dist/utils/own-package.js +13 -0
  72. package/dist/utils/project-selector.d.ts +5 -0
  73. package/dist/utils/project-selector.js +4 -0
  74. package/package.json +3 -3
  75. package/dist/templates/hooks/cts-worker-persona.sh +0 -76
package/README.md CHANGED
@@ -243,19 +243,18 @@ Optional settings: `vercelBypassSecret`, `browserbaseApiKey`, `browserbaseProjec
243
243
 
244
244
  ### `dotreq cts` *(Alpha)*
245
245
 
246
- Generate behavioral requirements from an existing codebase. Packs the codebase, plans a behavioral outline, fans out per-area specifier agents to draft per-area requirements, runs a dual review loop, and writes the result to `.requirements/`.
246
+ Generate behavioral requirements from an existing codebase — it plans a behavioral outline, drafts the requirements for each area, converges each through independent review, then reconciles the whole spec in a final cross-area pass, and writes the result to `.requirements/`.
247
+
248
+ It runs as a Claude Code skill backed by a dynamic workflow. Install it, then invoke it from Claude Code:
247
249
 
248
250
  ```bash
249
- dotreq cts run --scope src # full pipeline end-to-end
250
- dotreq cts run --scope src --fresh # discard cache and start over
251
- dotreq cts run --scope src --ignore-requirements # run against a codebase that already has its own .requirements/
251
+ dotreq cts skill-install # install into this project's .claude/
252
+ dotreq cts skill-install --global # …or into ~/.claude/ for every project
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.
255
-
256
- > **Alpha:** output quality is prompt-sensitive and varies by codebase. See the [Codebase to Spec docs](https://docs.dotrequirements.io/tools/cli/codebase-to-spec) for prerequisites, options, exit codes, and known rough edges. Feedback to support@dotrequirements.io welcome.
255
+ Then, in Claude Code, run `/codebase-to-spec` (or just ask — e.g. "spec the auth module"). You confirm the scope; the workflow autonomously plans the areas, drafts and reviews each one, reconciles them in a cross-area pass, and writes the result to `.requirements/`. Requires a Claude Code version with dynamic-workflow support.
257
256
 
258
- > **Heads up:** as of June 15, 2026, `claude -p` bills against your Claude subscription's API credit instead of the subscription seat (per Anthropic's May 13, 2026 announcement). `cts` runs will draw from that credit.
257
+ > **Alpha:** output quality is prompt-sensitive and varies by codebase. See the [Codebase to Spec docs](https://docs.dotrequirements.io/tools/cli/codebase-to-spec) for prerequisites, scoping, and known rough edges. Feedback to support@dotrequirements.io welcome.
259
258
 
260
259
  ### `dotreq ai-setup`
261
260
 
package/dist/cli.js CHANGED
@@ -64,7 +64,13 @@ program
64
64
  .action(wrapCommand(initCommand));
65
65
  program
66
66
  .command("link")
67
- .description("Link local environment to an existing project")
67
+ .description("Link local environment to a cloud project (creating one when needed)")
68
+ .option("-y, --yes", "Non-interactive: resolve every decision from defaults or flags; never prompt")
69
+ .option("--json", "Machine-readable output (one JSON object on stdout)")
70
+ .option("--team <nameOrId>", "Use this team (implies --yes)")
71
+ .option("--connect <slug>", "Connect to this existing project (implies --yes)")
72
+ .option("--create", "Create a new project (implies --yes)")
73
+ .option("-n, --name <name>", "Project name for --create or rename")
68
74
  .action(wrapCommand(linkCommand));
69
75
  program
70
76
  .command("pull")
@@ -125,6 +131,7 @@ program
125
131
  program
126
132
  .command("ai-setup")
127
133
  .description("Configure MCP server for your AI assistant (Claude Code, Claude Desktop, etc.)")
134
+ .option("-a, --assistant <id>", "Configure for this assistant without prompting (e.g. claude-code)")
128
135
  .action(wrapCommand(aiSetupCommand));
129
136
  program
130
137
  .command("search <query>")
@@ -1,23 +1,31 @@
1
1
  /**
2
- * Dispatch-context composition for the conversational orchestrator.
2
+ * Dispatch-context composition for the codebase-to-spec workflow.
3
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).
4
+ * A cts-worker runs `dotrequirements cts dispatch-context <dispatch-id>` to
5
+ * fetch the full composed prompt for its role (self-composition — there is no
6
+ * PreToolUse hook). Each returned prompt is self-contained: persona body +
7
+ * paths + any prior-round context the worker needs.
7
8
  *
8
9
  * 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.
10
+ * - `planner-initial` — initial planner; writes outline.yaml
11
+ * - `planner-revise-<turn>` — revising planner; reads the prior outline.yaml
12
+ * (with its review.thread), applies the latest thread entry's revisions, and
13
+ * writes a new outline.yaml. `<turn>` is the round being PRODUCED (2 = first
14
+ * revision).
15
+ * - `outline-reviewer` — independent reviewer of the area decomposition; appends
16
+ * its verdict to the document-level review.thread.
17
+ * - `specifier-<area>` — drafts one area's partial.
18
+ * - `reviewer-<area>` — independent per-area reviewer; appends its verdict to
19
+ * that area's review.thread.
20
+ * - `editor-<area>` — applies the per-area reviewer's revisions to the partial.
21
+ * - `cross-area-reviewer` — document-level reviewer of the composed spec
22
+ * (cross-area issues + coverage); returns its verdict for the reconcile loop.
23
+ * - `compose-editor` — applies cross-area revisions to the composed spec.
24
+ * - `phase1-test` — mechanical plumbing self-test.
17
25
  *
18
26
  * 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
27
+ * - CTSO-CONV-2: the outline converges via an independent reviewer loop
28
+ * - CTSO-CONV-5: cross-area review reconciles the composed spec
21
29
  */
22
30
  export interface DispatchContext {
23
31
  /**
@@ -52,6 +60,44 @@ export declare const SPECIFIER_DISPATCH_ID_PREFIX = "specifier-";
52
60
  * the latest area.review.thread entry's revisions).
53
61
  */
54
62
  export declare const EDITOR_DISPATCH_ID_PREFIX = "editor-";
63
+ /**
64
+ * Dispatch-id prefix for per-area reviewer dispatches. Suffix is the area's
65
+ * prefix (e.g., `reviewer-PLAN` reviews the PLAN area's partial). The reviewer
66
+ * is a distinct worker from the specifier/editor that produced the partial
67
+ * (independent second pair of eyes — CTSO-CONV-3.1). It appends its verdict to
68
+ * that area's `review.thread` in outline.yaml (read by the editor dispatch) and
69
+ * also returns the verdict so the workflow's convergence loop can branch on it.
70
+ */
71
+ export declare const REVIEWER_DISPATCH_ID_PREFIX = "reviewer-";
72
+ /**
73
+ * Dispatch-id for the outline reviewer — the independent reviewer for the
74
+ * planner's area decomposition (project level, one per run). Distinct from the
75
+ * planner that produced the outline (CTSO-CONV-2.1). It appends its verdict to
76
+ * the project-level `outline.review.thread` and returns the verdict so the
77
+ * workflow's outline loop can branch on it. The outline loop is sequential, so
78
+ * unlike the per-area reviewers this writer never contends on the file.
79
+ */
80
+ export declare const OUTLINE_REVIEWER_DISPATCH_ID = "outline-reviewer";
81
+ /**
82
+ * Dispatch-id for the cross-area reviewer — the document-level reviewer that
83
+ * runs AFTER per-area work converges and the partials are composed into a single
84
+ * spec. It reviews the composed spec for cross-area issues (duplication,
85
+ * terminology/persona drift, awkward cross-cutting splits, depth imbalance) plus
86
+ * document-level coverage and framing. It appends its verdict to the outline's
87
+ * top-level `crossAreaReview.thread` (auditable, like the per-area reviewers)
88
+ * and also returns it so the workflow's reconcile loop can branch on it
89
+ * (CTSO-CONV-5). Reuses the legacy SPEC_REVIEWER_PROMPT persona. One per round,
90
+ * sequential — no file contention.
91
+ */
92
+ export declare const CROSS_AREA_REVIEWER_DISPATCH_ID = "cross-area-reviewer";
93
+ /**
94
+ * Dispatch-id for the compose-level editor — applies the cross-area reviewer's
95
+ * revisions to the whole composed spec (not one area). Reuses the legacy
96
+ * EDITOR_PROMPT persona at its native document scope, reading the revisions to
97
+ * apply from the outline's top-level `crossAreaReview.thread` — the same
98
+ * auditable channel the per-area editor uses for `area.review.thread`.
99
+ */
100
+ export declare const COMPOSE_EDITOR_DISPATCH_ID = "compose-editor";
55
101
  export interface ComposeDispatchOptions {
56
102
  /**
57
103
  * Project root used to resolve cache paths. Defaults to `findProjectRoot()`
@@ -1,23 +1,31 @@
1
1
  /**
2
- * Dispatch-context composition for the conversational orchestrator.
2
+ * Dispatch-context composition for the codebase-to-spec workflow.
3
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).
4
+ * A cts-worker runs `dotrequirements cts dispatch-context <dispatch-id>` to
5
+ * fetch the full composed prompt for its role (self-composition — there is no
6
+ * PreToolUse hook). Each returned prompt is self-contained: persona body +
7
+ * paths + any prior-round context the worker needs.
7
8
  *
8
9
  * 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.
10
+ * - `planner-initial` — initial planner; writes outline.yaml
11
+ * - `planner-revise-<turn>` — revising planner; reads the prior outline.yaml
12
+ * (with its review.thread), applies the latest thread entry's revisions, and
13
+ * writes a new outline.yaml. `<turn>` is the round being PRODUCED (2 = first
14
+ * revision).
15
+ * - `outline-reviewer` — independent reviewer of the area decomposition; appends
16
+ * its verdict to the document-level review.thread.
17
+ * - `specifier-<area>` — drafts one area's partial.
18
+ * - `reviewer-<area>` — independent per-area reviewer; appends its verdict to
19
+ * that area's review.thread.
20
+ * - `editor-<area>` — applies the per-area reviewer's revisions to the partial.
21
+ * - `cross-area-reviewer` — document-level reviewer of the composed spec
22
+ * (cross-area issues + coverage); returns its verdict for the reconcile loop.
23
+ * - `compose-editor` — applies cross-area revisions to the composed spec.
24
+ * - `phase1-test` — mechanical plumbing self-test.
17
25
  *
18
26
  * 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
27
+ * - CTSO-CONV-2: the outline converges via an independent reviewer loop
28
+ * - CTSO-CONV-5: cross-area review reconciles the composed spec
21
29
  */
22
30
  import { existsSync, readFileSync } from "node:fs";
23
31
  import { findProjectRoot } from "../utils/project-settings.js";
@@ -26,6 +34,7 @@ import { sanitizeAreaName } from "./fan-out.js";
26
34
  import { EDITOR_PROMPT } from "./prompts/editor.js";
27
35
  import { PLANNER_INITIAL_PROMPT } from "./prompts/planner-initial.js";
28
36
  import { PLANNER_REVISE_PROMPT } from "./prompts/planner-revise.js";
37
+ import { SPEC_REVIEWER_PROMPT } from "./prompts/spec-reviewer.js";
29
38
  import { SPECIFIER_PROMPT } from "./prompts/specifier.js";
30
39
  import { parseConversationalOutline, } from "./schemas.js";
31
40
  /** Phase 1 dispatch-id used to validate the mechanical plumbing end-to-end. */
@@ -53,6 +62,44 @@ export const SPECIFIER_DISPATCH_ID_PREFIX = "specifier-";
53
62
  * the latest area.review.thread entry's revisions).
54
63
  */
55
64
  export const EDITOR_DISPATCH_ID_PREFIX = "editor-";
65
+ /**
66
+ * Dispatch-id prefix for per-area reviewer dispatches. Suffix is the area's
67
+ * prefix (e.g., `reviewer-PLAN` reviews the PLAN area's partial). The reviewer
68
+ * is a distinct worker from the specifier/editor that produced the partial
69
+ * (independent second pair of eyes — CTSO-CONV-3.1). It appends its verdict to
70
+ * that area's `review.thread` in outline.yaml (read by the editor dispatch) and
71
+ * also returns the verdict so the workflow's convergence loop can branch on it.
72
+ */
73
+ export const REVIEWER_DISPATCH_ID_PREFIX = "reviewer-";
74
+ /**
75
+ * Dispatch-id for the outline reviewer — the independent reviewer for the
76
+ * planner's area decomposition (project level, one per run). Distinct from the
77
+ * planner that produced the outline (CTSO-CONV-2.1). It appends its verdict to
78
+ * the project-level `outline.review.thread` and returns the verdict so the
79
+ * workflow's outline loop can branch on it. The outline loop is sequential, so
80
+ * unlike the per-area reviewers this writer never contends on the file.
81
+ */
82
+ export const OUTLINE_REVIEWER_DISPATCH_ID = "outline-reviewer";
83
+ /**
84
+ * Dispatch-id for the cross-area reviewer — the document-level reviewer that
85
+ * runs AFTER per-area work converges and the partials are composed into a single
86
+ * spec. It reviews the composed spec for cross-area issues (duplication,
87
+ * terminology/persona drift, awkward cross-cutting splits, depth imbalance) plus
88
+ * document-level coverage and framing. It appends its verdict to the outline's
89
+ * top-level `crossAreaReview.thread` (auditable, like the per-area reviewers)
90
+ * and also returns it so the workflow's reconcile loop can branch on it
91
+ * (CTSO-CONV-5). Reuses the legacy SPEC_REVIEWER_PROMPT persona. One per round,
92
+ * sequential — no file contention.
93
+ */
94
+ export const CROSS_AREA_REVIEWER_DISPATCH_ID = "cross-area-reviewer";
95
+ /**
96
+ * Dispatch-id for the compose-level editor — applies the cross-area reviewer's
97
+ * revisions to the whole composed spec (not one area). Reuses the legacy
98
+ * EDITOR_PROMPT persona at its native document scope, reading the revisions to
99
+ * apply from the outline's top-level `crossAreaReview.thread` — the same
100
+ * auditable channel the per-area editor uses for `area.review.thread`.
101
+ */
102
+ export const COMPOSE_EDITOR_DISPATCH_ID = "compose-editor";
56
103
  /**
57
104
  * Compose the dispatch context for a given dispatch-id.
58
105
  *
@@ -80,6 +127,24 @@ export function composeDispatchContext(dispatchId, options = {}) {
80
127
  return null;
81
128
  return composeSpecifier(areaPrefix, options);
82
129
  }
130
+ if (dispatchId === OUTLINE_REVIEWER_DISPATCH_ID) {
131
+ return composeOutlineReviewer(options);
132
+ }
133
+ if (dispatchId === CROSS_AREA_REVIEWER_DISPATCH_ID) {
134
+ return composeCrossAreaReviewer(options);
135
+ }
136
+ if (dispatchId === COMPOSE_EDITOR_DISPATCH_ID) {
137
+ return composeComposeEditor(options);
138
+ }
139
+ if (dispatchId.startsWith(REVIEWER_DISPATCH_ID_PREFIX)) {
140
+ const areaPrefix = dispatchId.slice(REVIEWER_DISPATCH_ID_PREFIX.length);
141
+ if (!areaPrefix)
142
+ return null;
143
+ return composeReviewer(areaPrefix, options);
144
+ }
145
+ // NOTE: the editor branch must come after the reviewer branch only if their
146
+ // prefixes could collide; they don't ("editor-" vs "reviewer-"), so order is
147
+ // irrelevant here.
83
148
  if (dispatchId.startsWith(EDITOR_DISPATCH_ID_PREFIX)) {
84
149
  const areaPrefix = dispatchId.slice(EDITOR_DISPATCH_ID_PREFIX.length);
85
150
  if (!areaPrefix)
@@ -344,7 +409,7 @@ function composeEditor(areaPrefix, options) {
344
409
  if (!area)
345
410
  return null;
346
411
  // Editor only runs when the area has been reviewed and the latest review
347
- // entry asks for revisions.
412
+ // thread entry (written by the reviewer into outline.yaml) asks for revisions.
348
413
  if (!area.review || area.review.result !== "needs-revision") {
349
414
  return null;
350
415
  }
@@ -419,6 +484,307 @@ function composeEditor(areaPrefix, options) {
419
484
  ].join("\n");
420
485
  return { prompt };
421
486
  }
487
+ const REVIEWER_PROMPT = `You are a codebase-to-spec **reviewer**. A specifier (or editor) worker has drafted the behavioral spec for ONE area; you are a fresh, independent pair of eyes reviewing that draft before it is accepted. You did not write it — judge it on its merits.
488
+
489
+ ## What you are reviewing
490
+
491
+ A "partial" — a Markdown file of fenced \`dotrequirements\` blocks capturing the behavior of one area, grounded in that area's customers. The schema has already been validated by the author; judge substance, not syntax.
492
+
493
+ ## Review criteria (priority order)
494
+
495
+ 1. **Customer-grounding** — Each requirement is framed around the area's named customers (the personas below), describing what that customer observes. Generic "the user" / "the developer" framing, or framing around the code's internals instead of the customer, is a problem.
496
+ 2. **Behavioral framing** — Requirements describe observable behavior, not API signatures, function names, data shapes, or implementation mechanics.
497
+ 3. **Independent testability** — Each requirement reads on its own. Cross-references between requirements ("as in REQ-X"), or criteria bundling several independent actions, hurt testability.
498
+ 4. **Coverage** — Behaviors visible in the area's source files are captured; flag notable missing behaviors.
499
+ 5. **No internal-mechanics leakage** — Internal vocabulary, private helpers, or pipeline jargon a customer would never observe should not appear.
500
+
501
+ ## Your verdict
502
+
503
+ - **approved** — the draft meets the criteria well enough to accept. No revisions.
504
+ - **needs-revision** — one or more criteria are not met. Provide **concrete, actionable** revision directives — each says *what to change*, specific enough that an editor can apply it mechanically. Not "this feels off" but "Reframe CTSPROMPT-STYLE-2 around what Riley observes from the checker, not the checker's internal categories."
505
+
506
+ Be a real reviewer: approve genuinely good drafts (don't invent problems), but don't rubber-stamp drafts with real customer-grounding, framing, or testability issues.`;
507
+ function composeReviewer(areaPrefix, options) {
508
+ const projectRoot = options.projectRoot ?? findProjectRoot(process.cwd()) ?? process.cwd();
509
+ const paths = cachePaths(projectRoot);
510
+ if (!existsSync(paths.outline)) {
511
+ return null;
512
+ }
513
+ let outline;
514
+ try {
515
+ outline = parseConversationalOutline(readFileSync(paths.outline, "utf-8"));
516
+ }
517
+ catch {
518
+ return null;
519
+ }
520
+ // Same gate as composeSpecifier: area reviews only run against areas of an
521
+ // approved outline, so an unapproved decomposition never gets rubber-stamped
522
+ // area by area.
523
+ if (!outline.review || outline.review.result !== "approved") {
524
+ return null;
525
+ }
526
+ const area = outline.areas.find((a) => a.prefix === areaPrefix);
527
+ if (!area)
528
+ return null;
529
+ const partialPath = paths.partial(sanitizeAreaName(area.name));
530
+ if (!existsSync(partialPath)) {
531
+ // Nothing to review until the specifier has produced a partial.
532
+ return null;
533
+ }
534
+ const currentPartial = readFileSync(partialPath, "utf-8");
535
+ const customersBlock = area.customers
536
+ .map((c, i) => `${i + 1}. ${c.description.trim()}`)
537
+ .join("\n\n");
538
+ const prompt = [
539
+ REVIEWER_PROMPT,
540
+ "",
541
+ "---",
542
+ "",
543
+ "## Your area",
544
+ "",
545
+ `Name: ${area.name}`,
546
+ `Prefix: ${area.prefix}`,
547
+ "",
548
+ "### Description (from the approved outline)",
549
+ "",
550
+ area.description.trim(),
551
+ "",
552
+ "### Customers (area-scoped — requirements must be grounded in these)",
553
+ "",
554
+ customersBlock,
555
+ "",
556
+ "---",
557
+ "",
558
+ "## The draft partial to review",
559
+ "",
560
+ `Path: ${partialPath}`,
561
+ "",
562
+ "```markdown",
563
+ currentPartial.trim(),
564
+ "```",
565
+ "",
566
+ "---",
567
+ "",
568
+ "## Your task for this dispatch",
569
+ "",
570
+ "1. Review the draft against the criteria above and decide your verdict.",
571
+ `2. Record your verdict in the outline at: ${paths.outline}`,
572
+ ` Find the area whose \`prefix:\` is \`${areaPrefix}\` and append one entry to its`,
573
+ " `review.thread` (create the area's `review:` block with a single-entry thread if it",
574
+ " has none yet), and set that area's `review.result` to match. Entry shapes:",
575
+ " - `{ result: approved }`",
576
+ ' - `{ result: needs-revision, revisions: ["<directive>", "..."] }`',
577
+ " Use the **Edit** tool and preserve every other part of the file exactly. Do NOT",
578
+ " rewrite the whole file. If the Edit fails because the file changed since you read it",
579
+ " (another area's reviewer wrote concurrently), re-read the outline and re-apply your",
580
+ " edit to the current content.",
581
+ "3. Return the same verdict as your structured output:",
582
+ ' `{ "result": "approved" | "needs-revision", "revisions": [...] }`.',
583
+ "",
584
+ "Your outline edit and your structured return MUST carry the same verdict.",
585
+ ].join("\n");
586
+ return { prompt };
587
+ }
588
+ const OUTLINE_REVIEWER_PROMPT = `You are a codebase-to-spec **outline reviewer**. A planner produced an outline that carves a packed codebase into behavioral areas, each with its own area-scoped customers. You are an independent reviewer (not the planner) judging whether this decomposition is good enough to fan out specifiers against.
589
+
590
+ ## Criteria
591
+
592
+ 1. **Area granularity** — each area is a coherent behavioral surface: not so broad it smears several distinct behaviors together, not so fine it is a trivial sliver.
593
+ 2. **Area boundaries** — areas do not overlap; a given behavior belongs to exactly one area.
594
+ 3. **Coverage** — the substantive behavioral files in the pack are each assigned to some area. Non-behavioral files (build config, pure test scaffolding) may be omitted.
595
+ 4. **Customer-vocabulary names** — area names read in the customer's language (what the software does for someone), not architectural or file-layout labels. Each area declares at least one area-scoped customer with a concrete description.
596
+ 5. **Prefixes** — each area has a distinct uppercase prefix.
597
+
598
+ ## Verdict
599
+
600
+ - **approved** — the decomposition is good enough to fan out.
601
+ - **needs-revision** — give concrete, actionable directives the planner can apply (e.g. "split the FOO area into producer and reviewer behaviors, each its own area"; "rename BAR to customer vocabulary"; "assign baz.ts to an area"). Not "this feels off."
602
+
603
+ Be a real reviewer: approve good decompositions, but flag real granularity / boundary / coverage / naming problems.`;
604
+ function composeOutlineReviewer(options) {
605
+ const projectRoot = options.projectRoot ?? findProjectRoot(process.cwd()) ?? process.cwd();
606
+ const paths = cachePaths(projectRoot);
607
+ if (!existsSync(paths.outline)) {
608
+ return null;
609
+ }
610
+ // Parse to confirm it is a well-formed outline before reviewing.
611
+ try {
612
+ parseConversationalOutline(readFileSync(paths.outline, "utf-8"));
613
+ }
614
+ catch {
615
+ return null;
616
+ }
617
+ const outlineYaml = readFileSync(paths.outline, "utf-8");
618
+ const prompt = [
619
+ OUTLINE_REVIEWER_PROMPT,
620
+ "",
621
+ "---",
622
+ "",
623
+ "## The outline to review",
624
+ "",
625
+ `Path: ${paths.outline}`,
626
+ "",
627
+ "```yaml",
628
+ outlineYaml.trim(),
629
+ "```",
630
+ "",
631
+ "---",
632
+ "",
633
+ "## Your task for this dispatch",
634
+ "",
635
+ "1. Review the outline against the criteria above and decide your verdict.",
636
+ `2. Record your verdict in the outline at: ${paths.outline}`,
637
+ " Append one entry to the document-level (top-level) `review.thread` — a sibling of",
638
+ " `title` / `areas`, NOT an area's `review` — creating the top-level `review:` block",
639
+ " with a single-entry thread if it has none yet, and set the top-level `review.result`",
640
+ " to match. Entry shapes:",
641
+ " - `{ result: approved }`",
642
+ ' - `{ result: needs-revision, revisions: ["<directive>", "..."] }`',
643
+ " Use the **Edit** tool and preserve every other part of the file exactly. If the Edit",
644
+ " fails because the file changed since you read it, re-read and re-apply.",
645
+ "3. Return the same verdict as your structured output:",
646
+ ' `{ "result": "approved" | "needs-revision", "revisions": [...] }`.',
647
+ "",
648
+ "Your outline edit and your structured return MUST carry the same verdict.",
649
+ ].join("\n");
650
+ return { prompt };
651
+ }
652
+ const CROSS_AREA_REVIEWER_OVERLAY = `---
653
+
654
+ ## Orchestrator adaptations — read carefully
655
+
656
+ You are reviewing the **composed spec** after per-area specifiers converged and their partials were assembled into one document. Focus on the document-level / cross-area pass — what only a whole-document view can catch: duplication across areas, inconsistent terminology or personas, awkward cross-cutting splits, depth imbalance, and document-level coverage gaps or framing errors that span areas. Per-area requirement quality has already been reviewed; do not re-litigate within-area style.
657
+
658
+ Run the **persona roster check** (cheap to fix here, impossible to see per-area): each distinct customer carries exactly ONE name across the whole document, and one name never denotes different customers in different areas. Flag confusably similar names.
659
+
660
+ ## Verdict shape (simpler than the legacy reviewer's)
661
+
662
+ Ignore the legacy categorized-findings object and three-state verdict described above. Return ONLY this shape:
663
+
664
+ - \`{ "result": "approved", "revisions": [] }\` — the composed spec is coherent and ready as-is. Reserve for genuinely good specs.
665
+ - \`{ "result": "needs-revision", "revisions": ["<directive>", "..."] }\` — fold every change you want (whether you'd have called it approved-with-revisions or requires-another-review) into a flat list of concrete, actionable directives an editor can apply mechanically. Cite requirement IDs and area names. Not "this feels off."`;
666
+ function composeCrossAreaReviewer(options) {
667
+ const projectRoot = options.projectRoot ?? findProjectRoot(process.cwd()) ?? process.cwd();
668
+ const paths = cachePaths(projectRoot);
669
+ if (!existsSync(paths.outline) || !existsSync(paths.composedSpec)) {
670
+ // Need both the outline (to record the verdict) and the composed spec (to
671
+ // review). The latter only exists once the partials have been composed.
672
+ return null;
673
+ }
674
+ const composed = readFileSync(paths.composedSpec, "utf-8");
675
+ const prompt = [
676
+ SPEC_REVIEWER_PROMPT,
677
+ "",
678
+ CROSS_AREA_REVIEWER_OVERLAY,
679
+ "",
680
+ "---",
681
+ "",
682
+ "## The composed spec to review",
683
+ "",
684
+ `Path: ${paths.composedSpec}`,
685
+ "",
686
+ "```markdown",
687
+ composed.trim(),
688
+ "```",
689
+ "",
690
+ "---",
691
+ "",
692
+ `For codebase grounding, the uncompressed pack is at ${paths.source} (grep for \`File: <name>\` headers). Consult it to spot-check coverage and framing against the actual code; you do not need to read all of it.`,
693
+ "",
694
+ "---",
695
+ "",
696
+ "## Your task for this dispatch",
697
+ "",
698
+ "1. Review the composed spec against the criteria above and decide your verdict.",
699
+ `2. Record your verdict in the outline at: ${paths.outline}`,
700
+ " Append one entry to the document-level (top-level) `crossAreaReview.thread` — a",
701
+ " sibling of `title` / `summary` / `areas` / `review`. This is SEPARATE from the",
702
+ " top-level `review` (which records outline approval); do NOT touch `review`. Create",
703
+ " the top-level `crossAreaReview:` block with a single-entry thread if it has none",
704
+ " yet, and set its `crossAreaReview.result` to match. Entry shapes:",
705
+ " - `{ result: approved }`",
706
+ ' - `{ result: needs-revision, revisions: ["<directive>", "..."] }`',
707
+ " Use the **Edit** tool and preserve every other part of the file exactly. If the Edit",
708
+ " fails because the file changed since you read it, re-read and re-apply.",
709
+ "3. Return the same verdict as your structured output:",
710
+ ' `{ "result": "approved" | "needs-revision", "revisions": [...] }`.',
711
+ "",
712
+ "Your outline edit and your structured return MUST carry the same verdict.",
713
+ ].join("\n");
714
+ return { prompt };
715
+ }
716
+ const COMPOSE_EDITOR_OVERLAY = `---
717
+
718
+ ## Critique shape (different from the legacy reviewer's shape)
719
+
720
+ You receive a flat list of \`revisions\` — concrete directives to apply to the composed spec. There are NO categorized findings (no coverage_gaps / framing_errors / cross_area_issues / internal_mechanics_drift). Just the revisions listed below.
721
+
722
+ Apply each revision as written; do not introduce changes beyond the listed revisions, and do not restructure unflagged content. If a revision is ambiguous, apply your best literal interpretation and note it in your final stdout confirmation. Keep the document coherent — this IS the composed document (not a single area's partial).`;
723
+ function composeComposeEditor(options) {
724
+ const projectRoot = options.projectRoot ?? findProjectRoot(process.cwd()) ?? process.cwd();
725
+ const paths = cachePaths(projectRoot);
726
+ if (!existsSync(paths.outline) || !existsSync(paths.composedSpec)) {
727
+ // Need the outline (for the cross-area revisions) and the composed spec
728
+ // (to edit). Either missing → nothing to do.
729
+ return null;
730
+ }
731
+ let outline;
732
+ try {
733
+ outline = parseConversationalOutline(readFileSync(paths.outline, "utf-8"));
734
+ }
735
+ catch {
736
+ return null;
737
+ }
738
+ // Editor only runs when the cross-area reviewer recorded a needs-revision
739
+ // verdict in the top-level crossAreaReview thread (mirrors the per-area
740
+ // editor's gate on area.review).
741
+ if (!outline.crossAreaReview ||
742
+ outline.crossAreaReview.result !== "needs-revision") {
743
+ return null;
744
+ }
745
+ const latestEntry = outline.crossAreaReview.thread[outline.crossAreaReview.thread.length - 1];
746
+ if (!latestEntry || latestEntry.result !== "needs-revision") {
747
+ return null;
748
+ }
749
+ const composed = readFileSync(paths.composedSpec, "utf-8");
750
+ const revisionsBlock = latestEntry.revisions
751
+ .map((r, i) => `${i + 1}. ${r.trim()}`)
752
+ .join("\n\n");
753
+ const prompt = [
754
+ EDITOR_PROMPT,
755
+ "",
756
+ COMPOSE_EDITOR_OVERLAY,
757
+ "",
758
+ "---",
759
+ "",
760
+ "## The composed spec to edit",
761
+ "",
762
+ `Path: ${paths.composedSpec}`,
763
+ "",
764
+ "```markdown",
765
+ composed.trim(),
766
+ "```",
767
+ "",
768
+ "---",
769
+ "",
770
+ "## Cross-area revisions (apply each as written)",
771
+ "",
772
+ revisionsBlock,
773
+ "",
774
+ "---",
775
+ "",
776
+ "## Your task for this dispatch",
777
+ "",
778
+ `Use the Edit tool to apply each revision above to the composed spec at ${paths.composedSpec}. Do not introduce changes beyond the listed revisions.`,
779
+ "",
780
+ `Then run validate (REQUIRED) on the composed spec via Bash: \`${LOCAL_CLI_INVOCATION} cts validate ${paths.composedSpec}\``,
781
+ "",
782
+ "Fix any schema errors validate reports. Do NOT run style-check here: the per-area partials were already style-checked before composition, and cross-area edits are structural (dedup, terminology, persona consistency), so re-style-checking the whole assembled document is unnecessary.",
783
+ "",
784
+ "End with a brief stdout confirmation summarizing the kinds of changes you made.",
785
+ ].join("\n");
786
+ return { prompt };
787
+ }
422
788
  function composePlannerRevise(turn, options) {
423
789
  const projectRoot = options.projectRoot ?? findProjectRoot(process.cwd()) ?? process.cwd();
424
790
  const paths = cachePaths(projectRoot);
@@ -30,6 +30,13 @@ export interface PackOptions {
30
30
  paths: CachePaths;
31
31
  /** Optional scope path (subdir of projectRoot) to limit which files are packed. */
32
32
  scope?: string;
33
+ /**
34
+ * When set, pack a remote repository (a GitHub URL or `owner/repo` shorthand)
35
+ * instead of a local path. Repomix clones it to a temp dir, packs, and cleans
36
+ * up. With `remote`, `scope` is interpreted as a repo-relative subdirectory and
37
+ * mapped to a repomix `include` glob (`<scope>/**`).
38
+ */
39
+ remote?: string;
33
40
  /** Additional ignore patterns (beyond defaults and project-level). */
34
41
  extraIgnores?: string[];
35
42
  /**