@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.
- package/README.md +7 -8
- package/dist/cli.js +8 -1
- package/dist/codebase-to-spec/dispatch.d.ts +60 -14
- package/dist/codebase-to-spec/dispatch.js +381 -15
- package/dist/codebase-to-spec/pack.d.ts +7 -0
- package/dist/codebase-to-spec/pack.js +29 -8
- package/dist/codebase-to-spec/present.d.ts +9 -0
- package/dist/codebase-to-spec/present.js +23 -2
- package/dist/codebase-to-spec/prompts/editor.d.ts +1 -1
- package/dist/codebase-to-spec/prompts/editor.js +1 -1
- package/dist/codebase-to-spec/prompts/specifier.d.ts +1 -1
- package/dist/codebase-to-spec/prompts/specifier.js +3 -2
- package/dist/codebase-to-spec/schemas.d.ts +153 -0
- package/dist/codebase-to-spec/schemas.js +111 -0
- package/dist/codebase-to-spec/skill-install.d.ts +42 -29
- package/dist/codebase-to-spec/skill-install.js +122 -112
- package/dist/codebase-to-spec/version-check.d.ts +31 -0
- package/dist/codebase-to-spec/version-check.js +56 -0
- package/dist/commands/ai-setup.d.ts +12 -1
- package/dist/commands/ai-setup.js +65 -33
- package/dist/commands/codebase-to-spec/dispatch-context.d.ts +2 -5
- package/dist/commands/codebase-to-spec/dispatch-context.js +2 -5
- package/dist/commands/codebase-to-spec/dispatch-editor.d.ts +0 -1
- package/dist/commands/codebase-to-spec/dispatch-editor.js +0 -1
- package/dist/commands/codebase-to-spec/dispatch-planner.d.ts +0 -1
- package/dist/commands/codebase-to-spec/dispatch-planner.js +0 -1
- package/dist/commands/codebase-to-spec/dispatch-spec.d.ts +3 -6
- package/dist/commands/codebase-to-spec/dispatch-spec.js +3 -6
- package/dist/commands/codebase-to-spec/index.js +3 -2
- package/dist/commands/codebase-to-spec/pack.d.ts +9 -0
- package/dist/commands/codebase-to-spec/pack.js +23 -3
- package/dist/commands/codebase-to-spec/skill-install.js +2 -9
- package/dist/commands/init.js +6 -1
- package/dist/commands/link-resolution.d.ts +79 -0
- package/dist/commands/link-resolution.js +141 -0
- package/dist/commands/link.d.ts +14 -4
- package/dist/commands/link.js +369 -16
- package/dist/commands/pull.js +19 -2
- package/dist/commands/push.js +36 -2
- package/dist/convex.d.ts +5 -3
- package/dist/convex.js +5 -3
- package/dist/harness/cache.d.ts +0 -14
- package/dist/harness/cache.js +1 -41
- package/dist/harness/finalize.js +2 -2
- package/dist/harness/prepare.js +1 -3
- package/dist/harness/requirementsLoader.d.ts +3 -3
- package/dist/harness/requirementsLoader.js +13 -8
- package/dist/mcp/handlers/authoring.d.ts +5 -5
- package/dist/mcp/handlers/authoring.js +9 -9
- package/dist/mcp/handlers/push.d.ts +2 -2
- package/dist/mcp/handlers/push.js +36 -3
- package/dist/mcp/handlers/review.d.ts +4 -4
- package/dist/mcp/handlers/review.js +4 -4
- package/dist/mcp/handlers/search.d.ts +1 -1
- package/dist/mcp/handlers/search.js +1 -1
- package/dist/mcp/index.js +29 -0
- package/dist/push/core.d.ts +18 -0
- package/dist/push/core.js +70 -3
- package/dist/push/index.d.ts +1 -1
- package/dist/push/index.js +1 -1
- package/dist/schema/parser-core.js +5 -1
- package/dist/schema/parser.js +5 -1
- package/dist/schema/run-marker.d.ts +38 -0
- package/dist/schema/run-marker.js +138 -0
- package/dist/schema/schemas.d.ts +12 -0
- package/dist/schema/schemas.js +1 -0
- package/dist/templates/agents/cts-worker.md +3 -3
- package/dist/templates/skills/codebase-to-spec/SKILL.md +56 -158
- package/dist/templates/workflows/specify-codebase.js +374 -0
- package/dist/utils/own-package.d.ts +10 -0
- package/dist/utils/own-package.js +13 -0
- package/dist/utils/project-selector.d.ts +5 -0
- package/dist/utils/project-selector.js +4 -0
- package/package.json +3 -3
- 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
|
|
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
|
|
250
|
-
dotreq cts
|
|
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
|
-
|
|
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
|
-
> **
|
|
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
|
|
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
|
|
2
|
+
* Dispatch-context composition for the codebase-to-spec workflow.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
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
|
-
* - `
|
|
10
|
-
* - `planner-
|
|
11
|
-
*
|
|
12
|
-
* outline.yaml
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
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-
|
|
20
|
-
* - CTSO-CONV-
|
|
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
|
|
2
|
+
* Dispatch-context composition for the codebase-to-spec workflow.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
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
|
-
* - `
|
|
10
|
-
* - `planner-
|
|
11
|
-
*
|
|
12
|
-
* outline.yaml
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
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-
|
|
20
|
-
* - CTSO-CONV-
|
|
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
|
/**
|