@popoverai/dotrequirements 0.23.0 → 0.24.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 +167 -20
- package/dist/cli.js +121 -60
- package/dist/codebase-to-spec/budget.d.ts +53 -0
- package/dist/codebase-to-spec/budget.js +80 -0
- package/dist/codebase-to-spec/cache.d.ts +49 -0
- package/dist/codebase-to-spec/cache.js +54 -0
- package/dist/codebase-to-spec/claude.d.ts +69 -0
- package/dist/codebase-to-spec/claude.js +126 -0
- package/dist/codebase-to-spec/compose.d.ts +49 -0
- package/dist/codebase-to-spec/compose.js +124 -0
- package/dist/codebase-to-spec/edit-loop.d.ts +54 -0
- package/dist/codebase-to-spec/edit-loop.js +195 -0
- package/dist/codebase-to-spec/editor.d.ts +54 -0
- package/dist/codebase-to-spec/editor.js +74 -0
- package/dist/codebase-to-spec/exit-codes.d.ts +40 -0
- package/dist/codebase-to-spec/exit-codes.js +58 -0
- package/dist/codebase-to-spec/fan-out.d.ts +63 -0
- package/dist/codebase-to-spec/fan-out.js +215 -0
- package/dist/codebase-to-spec/interactive.d.ts +30 -0
- package/dist/codebase-to-spec/interactive.js +48 -0
- package/dist/codebase-to-spec/outline-review-loop.d.ts +51 -0
- package/dist/codebase-to-spec/outline-review-loop.js +187 -0
- package/dist/codebase-to-spec/pack.d.ts +51 -0
- package/dist/codebase-to-spec/pack.js +127 -0
- package/dist/codebase-to-spec/planner.d.ts +41 -0
- package/dist/codebase-to-spec/planner.js +76 -0
- package/dist/codebase-to-spec/present.d.ts +94 -0
- package/dist/codebase-to-spec/present.js +288 -0
- package/dist/codebase-to-spec/progress.d.ts +33 -0
- package/dist/codebase-to-spec/progress.js +28 -0
- package/dist/codebase-to-spec/prompts/editor.d.ts +13 -0
- package/dist/codebase-to-spec/prompts/editor.js +57 -0
- package/dist/codebase-to-spec/prompts/outline-reviewer.d.ts +12 -0
- package/dist/codebase-to-spec/prompts/outline-reviewer.js +87 -0
- package/dist/codebase-to-spec/prompts/planner-apply.d.ts +12 -0
- package/dist/codebase-to-spec/prompts/planner-apply.js +32 -0
- package/dist/codebase-to-spec/prompts/planner-initial.d.ts +11 -0
- package/dist/codebase-to-spec/prompts/planner-initial.js +125 -0
- package/dist/codebase-to-spec/prompts/planner-revise.d.ts +14 -0
- package/dist/codebase-to-spec/prompts/planner-revise.js +60 -0
- package/dist/codebase-to-spec/prompts/spec-reviewer.d.ts +16 -0
- package/dist/codebase-to-spec/prompts/spec-reviewer.js +96 -0
- package/dist/codebase-to-spec/prompts/specifier.d.ts +12 -0
- package/dist/codebase-to-spec/prompts/specifier.js +100 -0
- package/dist/codebase-to-spec/prompts/style-check.d.ts +12 -0
- package/dist/codebase-to-spec/prompts/style-check.js +78 -0
- package/dist/codebase-to-spec/schemas.d.ts +257 -0
- package/dist/codebase-to-spec/schemas.js +183 -0
- package/dist/codebase-to-spec/skill-install.d.ts +57 -0
- package/dist/codebase-to-spec/skill-install.js +79 -0
- package/dist/codebase-to-spec/slice.d.ts +49 -0
- package/dist/codebase-to-spec/slice.js +111 -0
- package/dist/codebase-to-spec/specifier.d.ts +60 -0
- package/dist/codebase-to-spec/specifier.js +79 -0
- package/dist/codebase-to-spec/style-check.d.ts +29 -0
- package/dist/codebase-to-spec/style-check.js +33 -0
- package/dist/codebase-to-spec/summary.d.ts +51 -0
- package/dist/codebase-to-spec/summary.js +183 -0
- package/dist/codebase-to-spec/validate.d.ts +46 -0
- package/dist/codebase-to-spec/validate.js +130 -0
- package/dist/commands/acceptance-test.d.ts +6 -0
- package/dist/commands/acceptance-test.js +212 -0
- package/dist/commands/ai-setup.d.ts +5 -0
- package/dist/commands/ai-setup.js +441 -0
- package/dist/commands/browsertest.js +34 -27
- package/dist/commands/codebase-to-spec/compose.d.ts +14 -0
- package/dist/commands/codebase-to-spec/compose.js +57 -0
- package/dist/commands/codebase-to-spec/edit-loop.d.ts +16 -0
- package/dist/commands/codebase-to-spec/edit-loop.js +83 -0
- package/dist/commands/codebase-to-spec/fan-out.d.ts +19 -0
- package/dist/commands/codebase-to-spec/fan-out.js +77 -0
- package/dist/commands/codebase-to-spec/index.d.ts +9 -0
- package/dist/commands/codebase-to-spec/index.js +135 -0
- package/dist/commands/codebase-to-spec/pack.d.ts +22 -0
- package/dist/commands/codebase-to-spec/pack.js +76 -0
- package/dist/commands/codebase-to-spec/plan-loop.d.ts +26 -0
- package/dist/commands/codebase-to-spec/plan-loop.js +105 -0
- package/dist/commands/codebase-to-spec/present.d.ts +21 -0
- package/dist/commands/codebase-to-spec/present.js +92 -0
- package/dist/commands/codebase-to-spec/run.d.ts +20 -0
- package/dist/commands/codebase-to-spec/run.js +85 -0
- package/dist/commands/codebase-to-spec/skill-install.d.ts +20 -0
- package/dist/commands/codebase-to-spec/skill-install.js +51 -0
- package/dist/commands/codebase-to-spec/specify-area.d.ts +18 -0
- package/dist/commands/codebase-to-spec/specify-area.js +82 -0
- package/dist/commands/codebase-to-spec/style-check.d.ts +15 -0
- package/dist/commands/codebase-to-spec/style-check.js +42 -0
- package/dist/commands/codebase-to-spec/validate.d.ts +18 -0
- package/dist/commands/codebase-to-spec/validate.js +38 -0
- package/dist/commands/create-requirement-document.d.ts +2 -0
- package/dist/commands/create-requirement-document.js +41 -0
- package/dist/commands/finalize.js +7 -7
- package/dist/commands/get.d.ts +2 -0
- package/dist/commands/get.js +55 -0
- package/dist/commands/init.js +132 -117
- package/dist/commands/link.js +27 -27
- package/dist/commands/list.d.ts +6 -0
- package/dist/commands/list.js +43 -0
- package/dist/commands/mcp-setup.js +159 -149
- package/dist/commands/mcp.js +1 -1
- package/dist/commands/prepare.js +4 -4
- package/dist/commands/pull.js +116 -121
- package/dist/commands/push.js +106 -112
- package/dist/commands/report.d.ts +6 -2
- package/dist/commands/report.js +177 -122
- package/dist/commands/requirements-for.d.ts +2 -0
- package/dist/commands/requirements-for.js +29 -0
- package/dist/commands/review-test.d.ts +2 -0
- package/dist/commands/review-test.js +75 -0
- package/dist/commands/search.d.ts +6 -0
- package/dist/commands/search.js +39 -0
- package/dist/commands/style-check.d.ts +7 -0
- package/dist/commands/style-check.js +75 -0
- package/dist/commands/test.js +53 -59
- package/dist/commands/tests-for.d.ts +2 -0
- package/dist/commands/tests-for.js +80 -0
- package/dist/commands/validate.d.ts +6 -0
- package/dist/commands/validate.js +72 -0
- package/dist/config.js +1 -1
- package/dist/convex.d.ts +34 -22
- package/dist/convex.js +38 -22
- package/dist/harness/cache.d.ts +1 -5
- package/dist/harness/cache.js +49 -59
- package/dist/harness/convexReporting.d.ts +1 -1
- package/dist/harness/convexReporting.js +9 -7
- package/dist/harness/coverageCache.js +3 -3
- package/dist/harness/finalize.js +59 -46
- package/dist/harness/index.d.ts +6 -7
- package/dist/harness/index.js +9 -10
- package/dist/harness/prepare.js +6 -5
- package/dist/harness/requirementsLoader.d.ts +2 -2
- package/dist/harness/requirementsLoader.js +13 -35
- package/dist/harness/tracking.js +18 -18
- package/dist/harness/types.d.ts +1 -1
- package/dist/mcp/convexClient.d.ts +0 -39
- package/dist/mcp/convexClient.js +2 -107
- package/dist/mcp/grep.d.ts +1 -1
- package/dist/mcp/grep.js +87 -42
- package/dist/mcp/handlers/authoring.d.ts +1 -1
- package/dist/mcp/handlers/authoring.js +30 -234
- package/dist/mcp/handlers/coverage.d.ts +1 -1
- package/dist/mcp/handlers/coverage.js +13 -15
- package/dist/mcp/handlers/debug.d.ts +2 -3
- package/dist/mcp/handlers/debug.js +10 -10
- package/dist/mcp/handlers/get.d.ts +1 -1
- package/dist/mcp/handlers/get.js +11 -10
- package/dist/mcp/handlers/index.d.ts +20 -20
- package/dist/mcp/handlers/index.js +10 -10
- package/dist/mcp/handlers/list.d.ts +4 -33
- package/dist/mcp/handlers/list.js +16 -38
- package/dist/mcp/handlers/push.d.ts +1 -1
- package/dist/mcp/handlers/push.js +28 -18
- package/dist/mcp/handlers/report.d.ts +16 -0
- package/dist/mcp/handlers/report.js +134 -0
- package/dist/mcp/handlers/review.d.ts +1 -1
- package/dist/mcp/handlers/review.js +40 -59
- package/dist/mcp/handlers/search.d.ts +1 -1
- package/dist/mcp/handlers/search.js +7 -9
- package/dist/mcp/handlers/test-mapping.d.ts +1 -1
- package/dist/mcp/handlers/test-mapping.js +14 -14
- package/dist/mcp/handlers/types.d.ts +3 -3
- package/dist/mcp/handlers/types.js +2 -2
- package/dist/mcp/index.d.ts +1 -1
- package/dist/mcp/index.js +147 -167
- package/dist/mcp/requirements.d.ts +2 -2
- package/dist/mcp/requirements.js +30 -30
- package/dist/mcp/testCodeExtractor.js +24 -26
- package/dist/mcp/types.d.ts +1 -1
- package/dist/push/core.d.ts +2 -2
- package/dist/push/core.js +20 -20
- package/dist/push/index.d.ts +1 -1
- package/dist/push/index.js +2 -2
- package/dist/requirements/cloud-ai.d.ts +57 -0
- package/dist/requirements/cloud-ai.js +104 -0
- package/dist/requirements/cloud-coverage.d.ts +41 -0
- package/dist/requirements/cloud-coverage.js +60 -0
- package/dist/requirements/coverage.d.ts +45 -0
- package/dist/requirements/coverage.js +114 -0
- package/dist/requirements/grep.d.ts +33 -0
- package/dist/requirements/grep.js +306 -0
- package/dist/requirements/index.d.ts +73 -0
- package/dist/requirements/index.js +174 -0
- package/dist/requirements/style-guide.d.ts +67 -0
- package/dist/requirements/style-guide.js +299 -0
- package/dist/requirements/testCodeExtractor.d.ts +22 -0
- package/dist/requirements/testCodeExtractor.js +150 -0
- package/dist/schema/browser.d.ts +8 -8
- package/dist/schema/browser.js +13 -15
- package/dist/schema/builder.d.ts +1 -1
- package/dist/schema/builder.js +13 -44
- package/dist/schema/conversions.d.ts +2 -2
- package/dist/schema/conversions.js +11 -11
- package/dist/schema/index.d.ts +9 -9
- package/dist/schema/index.js +15 -15
- package/dist/schema/parser-core.d.ts +1 -1
- package/dist/schema/parser-core.js +23 -22
- package/dist/schema/parser.d.ts +3 -3
- package/dist/schema/parser.js +27 -31
- package/dist/schema/resolver.d.ts +1 -1
- package/dist/schema/resolver.js +9 -9
- package/dist/schema/scenario.d.ts +1 -1
- package/dist/schema/scenario.js +1 -1
- package/dist/schema/schemas.d.ts +3 -3
- package/dist/schema/schemas.js +41 -28
- package/dist/schema/test-schema.js +27 -27
- package/dist/templates/context-file-section.md +3 -2
- package/dist/templates/example-requirements.js +1 -1
- package/dist/templates/example-requirements.ts +3 -1
- package/dist/templates/requirements-readme.js +1 -1
- package/dist/templates/requirements-readme.ts +1 -1
- package/dist/templates/skills/codebase-to-spec/SKILL.md +118 -0
- package/dist/utils/brand.js +3 -3
- package/dist/utils/browser-launch.js +4 -4
- package/dist/utils/context-file.d.ts +1 -1
- package/dist/utils/context-file.js +26 -26
- package/dist/utils/env.js +7 -7
- package/dist/utils/gitignore.js +7 -7
- package/dist/utils/oauth-callback-server.d.ts +1 -1
- package/dist/utils/oauth-callback-server.js +27 -25
- package/dist/utils/oauth-flow.js +32 -29
- package/dist/utils/project-discovery.d.ts +3 -3
- package/dist/utils/project-discovery.js +18 -17
- package/dist/utils/project-name.js +8 -8
- package/dist/utils/project-selector.d.ts +1 -1
- package/dist/utils/project-selector.js +24 -21
- package/dist/utils/project-settings.d.ts +1 -1
- package/dist/utils/project-settings.js +24 -22
- package/dist/utils/templates.js +6 -6
- package/package.json +2 -1
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Outline review loop: planner → reviewer → revise → reviewer → ... → approved.
|
|
3
|
+
*
|
|
4
|
+
* The reviewer runs in a SINGLE stateful claude session across turns (so it
|
|
5
|
+
* can compare prior outlines to the latest one). The planner is stateless —
|
|
6
|
+
* each invocation is fresh.
|
|
7
|
+
*
|
|
8
|
+
* Requirements covered:
|
|
9
|
+
* - CTS-PLAN-2: stateful reviewer session
|
|
10
|
+
* - CTS-PLAN-3: approved → proceed
|
|
11
|
+
* - CTS-PLAN-4: approved-with-revisions → one revision pass, then proceed
|
|
12
|
+
* - CTS-PLAN-5: requires-another-review → revision loop
|
|
13
|
+
* - CTS-PLAN-6: max-turns + convergence nudge
|
|
14
|
+
*/
|
|
15
|
+
import { randomUUID } from "node:crypto";
|
|
16
|
+
import { runClaude } from "./claude.js";
|
|
17
|
+
import { runInitialPlanner, runRevisingPlanner, } from "./planner.js";
|
|
18
|
+
import { OUTLINE_REVIEWER_PROMPT } from "./prompts/outline-reviewer.js";
|
|
19
|
+
import { OUTLINE_REVIEW_JSON_SCHEMA, parseOutlineReview, } from "./schemas.js";
|
|
20
|
+
export const DEFAULT_OUTLINE_LOOP_MAX_TURNS = 3;
|
|
21
|
+
function reviewerUserMessageInitial(overviewPath, outline) {
|
|
22
|
+
return [
|
|
23
|
+
`Initial outline review.`,
|
|
24
|
+
``,
|
|
25
|
+
`The compressed packed codebase is at: ${overviewPath}`,
|
|
26
|
+
``,
|
|
27
|
+
`## Outline`,
|
|
28
|
+
`\`\`\`json`,
|
|
29
|
+
JSON.stringify(outline, null, 2),
|
|
30
|
+
`\`\`\``,
|
|
31
|
+
``,
|
|
32
|
+
`Read the codebase if you need to verify file paths or coverage gaps, then produce your critique JSON.`,
|
|
33
|
+
].join("\n");
|
|
34
|
+
}
|
|
35
|
+
function reviewerUserMessageRevised(turn, overviewPath, outline, nudge) {
|
|
36
|
+
const parts = [
|
|
37
|
+
`Review turn ${turn}. The planner has revised the outline based on your prior feedback.`,
|
|
38
|
+
``,
|
|
39
|
+
];
|
|
40
|
+
if (nudge) {
|
|
41
|
+
parts.push(nudge, ``);
|
|
42
|
+
}
|
|
43
|
+
parts.push(`The codebase pack at ${overviewPath} is unchanged.`, ``, `## Revised outline`, `\`\`\`json`, JSON.stringify(outline, null, 2), `\`\`\``, ``, `Compare this outline to the prior one. Report on what was fixed, what is still open, and what (if anything) was introduced as a new issue. Then produce your critique JSON.`);
|
|
44
|
+
return parts.join("\n");
|
|
45
|
+
}
|
|
46
|
+
function convergenceNudge(turn, maxTurns) {
|
|
47
|
+
return `CONVERGENCE NUDGE: This is review turn ${turn} of ${maxTurns} maximum. Please prioritize convergence. Your job is still to only approve a genuinely-ready outline, but if it is close, lean toward \`approved-with-revisions\` over \`requires-another-review\`. Reserve \`requires-another-review\` for genuine structural problems that the planner has not addressed despite prior feedback.`;
|
|
48
|
+
}
|
|
49
|
+
async function callReviewer(args) {
|
|
50
|
+
const result = await args.runner({
|
|
51
|
+
systemPrompt: OUTLINE_REVIEWER_PROMPT,
|
|
52
|
+
userMessage: args.userMessage,
|
|
53
|
+
model: args.model,
|
|
54
|
+
tools: ["Read", "Grep"],
|
|
55
|
+
addDirs: args.addDirs,
|
|
56
|
+
jsonSchema: OUTLINE_REVIEW_JSON_SCHEMA,
|
|
57
|
+
session: {
|
|
58
|
+
kind: args.isFirstTurn ? "fresh" : "resume",
|
|
59
|
+
sessionId: args.sessionId,
|
|
60
|
+
},
|
|
61
|
+
});
|
|
62
|
+
if (result.exitCode !== 0) {
|
|
63
|
+
throw new Error(`Outline reviewer failed (exit ${result.exitCode}): ${result.stderr || result.stdout}`);
|
|
64
|
+
}
|
|
65
|
+
// On parse failure, retry the same turn once before failing.
|
|
66
|
+
// CTS-PLAN-2 requirement: "on validation failure the CLI logs the parse error
|
|
67
|
+
// and retries the same turn once before failing the loop"
|
|
68
|
+
try {
|
|
69
|
+
return parseOutlineReview(result.stdout);
|
|
70
|
+
}
|
|
71
|
+
catch (err) {
|
|
72
|
+
// Retry once
|
|
73
|
+
const retry = await args.runner({
|
|
74
|
+
systemPrompt: OUTLINE_REVIEWER_PROMPT,
|
|
75
|
+
userMessage: `${args.userMessage}\n\n(Your prior output failed schema validation: ${err instanceof Error ? err.message : String(err)}. Please try again, emitting JSON only.)`,
|
|
76
|
+
model: args.model,
|
|
77
|
+
tools: ["Read", "Grep"],
|
|
78
|
+
addDirs: args.addDirs,
|
|
79
|
+
jsonSchema: OUTLINE_REVIEW_JSON_SCHEMA,
|
|
80
|
+
session: { kind: "resume", sessionId: args.sessionId },
|
|
81
|
+
});
|
|
82
|
+
if (retry.exitCode !== 0) {
|
|
83
|
+
throw new Error(`Outline reviewer retry failed (exit ${retry.exitCode}): ${retry.stderr || retry.stdout}`);
|
|
84
|
+
}
|
|
85
|
+
return parseOutlineReview(retry.stdout);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Run the outline review loop end-to-end.
|
|
90
|
+
*
|
|
91
|
+
* Returns the final outline (after applying mechanical revisions if the
|
|
92
|
+
* verdict was `approved-with-revisions`), the per-turn trace, and the final
|
|
93
|
+
* verdict.
|
|
94
|
+
*/
|
|
95
|
+
export async function runOutlineReviewLoop(options) {
|
|
96
|
+
const { overviewPath, addDirs, maxTurns = DEFAULT_OUTLINE_LOOP_MAX_TURNS, runner = runClaude, model, progress, sessionId = randomUUID(), } = options;
|
|
97
|
+
const plannerCtx = { overviewPath, addDirs, runner, model };
|
|
98
|
+
// Turn 1 — initial planner + initial reviewer
|
|
99
|
+
progress?.emit({
|
|
100
|
+
stage: "plan",
|
|
101
|
+
step: "planner-initial",
|
|
102
|
+
message: "Generating initial outline",
|
|
103
|
+
});
|
|
104
|
+
const outline1 = await runInitialPlanner(plannerCtx);
|
|
105
|
+
const outlinesByTurn = [outline1];
|
|
106
|
+
const reviewsByTurn = [];
|
|
107
|
+
progress?.emit({
|
|
108
|
+
stage: "outline-review",
|
|
109
|
+
step: "turn-1",
|
|
110
|
+
message: `Reviewing initial outline (${outline1.areas.length} areas)`,
|
|
111
|
+
});
|
|
112
|
+
let currentOutline = outline1;
|
|
113
|
+
let review = await callReviewer({
|
|
114
|
+
runner,
|
|
115
|
+
sessionId,
|
|
116
|
+
isFirstTurn: true,
|
|
117
|
+
userMessage: reviewerUserMessageInitial(overviewPath, outline1),
|
|
118
|
+
addDirs,
|
|
119
|
+
model,
|
|
120
|
+
});
|
|
121
|
+
reviewsByTurn.push(review);
|
|
122
|
+
progress?.emit({
|
|
123
|
+
stage: "outline-review",
|
|
124
|
+
step: "turn-1",
|
|
125
|
+
message: `Verdict: ${review.verdict}`,
|
|
126
|
+
data: { verdict: review.verdict },
|
|
127
|
+
});
|
|
128
|
+
let turn = 1;
|
|
129
|
+
while (review.verdict === "requires-another-review" && turn < maxTurns) {
|
|
130
|
+
turn++;
|
|
131
|
+
progress?.emit({
|
|
132
|
+
stage: "plan",
|
|
133
|
+
step: `revise-turn-${turn}`,
|
|
134
|
+
message: "Revising outline based on critique",
|
|
135
|
+
});
|
|
136
|
+
currentOutline = await runRevisingPlanner(plannerCtx, currentOutline, review);
|
|
137
|
+
outlinesByTurn.push(currentOutline);
|
|
138
|
+
const nudge = turn >= maxTurns - 1 && turn !== maxTurns
|
|
139
|
+
? convergenceNudge(turn, maxTurns)
|
|
140
|
+
: undefined;
|
|
141
|
+
progress?.emit({
|
|
142
|
+
stage: "outline-review",
|
|
143
|
+
step: `turn-${turn}`,
|
|
144
|
+
message: `Reviewing revised outline (turn ${turn}/${maxTurns})`,
|
|
145
|
+
data: { turn, maxTurns, nudge: !!nudge },
|
|
146
|
+
});
|
|
147
|
+
review = await callReviewer({
|
|
148
|
+
runner,
|
|
149
|
+
sessionId,
|
|
150
|
+
isFirstTurn: false,
|
|
151
|
+
userMessage: reviewerUserMessageRevised(turn, overviewPath, currentOutline, nudge),
|
|
152
|
+
addDirs,
|
|
153
|
+
model,
|
|
154
|
+
});
|
|
155
|
+
reviewsByTurn.push(review);
|
|
156
|
+
progress?.emit({
|
|
157
|
+
stage: "outline-review",
|
|
158
|
+
step: `turn-${turn}`,
|
|
159
|
+
message: `Verdict: ${review.verdict}`,
|
|
160
|
+
data: { verdict: review.verdict },
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
const hitMaxTurns = review.verdict === "requires-another-review" && turn >= maxTurns;
|
|
164
|
+
// If the verdict ended on approved-with-revisions, run the planner once
|
|
165
|
+
// more to apply the listed revisions. Same agent as the revise loop; the
|
|
166
|
+
// difference is that we proceed to fan-out instead of looping back to
|
|
167
|
+
// review.
|
|
168
|
+
if (review.verdict === "approved-with-revisions" &&
|
|
169
|
+
review.revisions.length > 0) {
|
|
170
|
+
progress?.emit({
|
|
171
|
+
stage: "plan",
|
|
172
|
+
step: "apply-revisions",
|
|
173
|
+
message: `Applying ${review.revisions.length} revision(s)`,
|
|
174
|
+
});
|
|
175
|
+
currentOutline = await runRevisingPlanner(plannerCtx, currentOutline, review);
|
|
176
|
+
outlinesByTurn.push(currentOutline);
|
|
177
|
+
}
|
|
178
|
+
return {
|
|
179
|
+
outline: currentOutline,
|
|
180
|
+
outlinesByTurn,
|
|
181
|
+
reviewsByTurn,
|
|
182
|
+
finalVerdict: review.verdict,
|
|
183
|
+
turnsUsed: turn,
|
|
184
|
+
hitMaxTurns,
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
//# sourceMappingURL=outline-review-loop.js.map
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pack stage: produce compressed and uncompressed views of a codebase.
|
|
3
|
+
*
|
|
4
|
+
* Wraps Repomix's programmatic API to write:
|
|
5
|
+
* - `.dotrequirements-cache/overview.txt` — compressed (tree-sitter signatures)
|
|
6
|
+
* - `.dotrequirements-cache/source.txt` — uncompressed (full source for slices)
|
|
7
|
+
*
|
|
8
|
+
* Requirements covered:
|
|
9
|
+
* - CTS-PACK-1: Pack stage produces compressed and uncompressed views
|
|
10
|
+
* - CTS-PACK-2: Engineer can extend the ignore list per project
|
|
11
|
+
*/
|
|
12
|
+
import type { CachePaths } from "./cache.js";
|
|
13
|
+
/**
|
|
14
|
+
* The curated default ignore list. These patterns are applied in addition to
|
|
15
|
+
* `.gitignore` and any project-level `.dotrequirements-ignore`.
|
|
16
|
+
*
|
|
17
|
+
* Sized to remove obvious non-behavioral noise: generated code, package
|
|
18
|
+
* manifests/lockfiles, vendored libraries, build artifacts, large binaries.
|
|
19
|
+
*/
|
|
20
|
+
export declare const DEFAULT_IGNORES: readonly string[];
|
|
21
|
+
/**
|
|
22
|
+
* Read project-level ignore patterns from `.dotrequirements-ignore` (and
|
|
23
|
+
* `.dotreqignore` as a shorthand), if either exists. Returns an empty array if
|
|
24
|
+
* no file is present.
|
|
25
|
+
*/
|
|
26
|
+
export declare function readProjectIgnores(projectRoot: string): string[];
|
|
27
|
+
export interface PackOptions {
|
|
28
|
+
projectRoot: string;
|
|
29
|
+
paths: CachePaths;
|
|
30
|
+
/** Optional scope path (subdir of projectRoot) to limit which files are packed. */
|
|
31
|
+
scope?: string;
|
|
32
|
+
/** Additional ignore patterns (beyond defaults and project-level). */
|
|
33
|
+
extraIgnores?: string[];
|
|
34
|
+
}
|
|
35
|
+
export interface PackResult {
|
|
36
|
+
overviewPath: string;
|
|
37
|
+
sourcePath: string;
|
|
38
|
+
/** Number of files included after ignores. */
|
|
39
|
+
fileCount: number;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Run Repomix twice over the same scope: once compressed, once uncompressed.
|
|
43
|
+
*
|
|
44
|
+
* We use Repomix's programmatic API rather than `npx repomix` so the CLI does
|
|
45
|
+
* not require a separate install and so we can control config precisely.
|
|
46
|
+
*
|
|
47
|
+
* NOTE: Repomix's TypeScript API is invoked via `runCli`. We invoke it as the
|
|
48
|
+
* library exposes it; the surface is small enough that this wraps cleanly.
|
|
49
|
+
*/
|
|
50
|
+
export declare function runPack(options: PackOptions): Promise<PackResult>;
|
|
51
|
+
//# sourceMappingURL=pack.d.ts.map
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pack stage: produce compressed and uncompressed views of a codebase.
|
|
3
|
+
*
|
|
4
|
+
* Wraps Repomix's programmatic API to write:
|
|
5
|
+
* - `.dotrequirements-cache/overview.txt` — compressed (tree-sitter signatures)
|
|
6
|
+
* - `.dotrequirements-cache/source.txt` — uncompressed (full source for slices)
|
|
7
|
+
*
|
|
8
|
+
* Requirements covered:
|
|
9
|
+
* - CTS-PACK-1: Pack stage produces compressed and uncompressed views
|
|
10
|
+
* - CTS-PACK-2: Engineer can extend the ignore list per project
|
|
11
|
+
*/
|
|
12
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
13
|
+
import { join } from "node:path";
|
|
14
|
+
/**
|
|
15
|
+
* The curated default ignore list. These patterns are applied in addition to
|
|
16
|
+
* `.gitignore` and any project-level `.dotrequirements-ignore`.
|
|
17
|
+
*
|
|
18
|
+
* Sized to remove obvious non-behavioral noise: generated code, package
|
|
19
|
+
* manifests/lockfiles, vendored libraries, build artifacts, large binaries.
|
|
20
|
+
*/
|
|
21
|
+
export const DEFAULT_IGNORES = [
|
|
22
|
+
// Vendored dependencies
|
|
23
|
+
"node_modules/**",
|
|
24
|
+
"vendor/**",
|
|
25
|
+
".pnp.*",
|
|
26
|
+
// Build outputs
|
|
27
|
+
"dist/**",
|
|
28
|
+
"build/**",
|
|
29
|
+
"out/**",
|
|
30
|
+
".next/**",
|
|
31
|
+
".nuxt/**",
|
|
32
|
+
".svelte-kit/**",
|
|
33
|
+
".turbo/**",
|
|
34
|
+
".parcel-cache/**",
|
|
35
|
+
// Generated code
|
|
36
|
+
"**/*.generated.*",
|
|
37
|
+
"**/_generated/**",
|
|
38
|
+
"**/__generated__/**",
|
|
39
|
+
"**/*.pb.ts",
|
|
40
|
+
"**/*.pb.go",
|
|
41
|
+
// Lockfiles
|
|
42
|
+
"package-lock.json",
|
|
43
|
+
"pnpm-lock.yaml",
|
|
44
|
+
"yarn.lock",
|
|
45
|
+
"bun.lockb",
|
|
46
|
+
"composer.lock",
|
|
47
|
+
"Gemfile.lock",
|
|
48
|
+
"poetry.lock",
|
|
49
|
+
"uv.lock",
|
|
50
|
+
"Cargo.lock",
|
|
51
|
+
// Coverage and test artifacts
|
|
52
|
+
"coverage/**",
|
|
53
|
+
".nyc_output/**",
|
|
54
|
+
".next-cache/**",
|
|
55
|
+
// IDE / editor
|
|
56
|
+
".idea/**",
|
|
57
|
+
".vscode/**",
|
|
58
|
+
// OS files
|
|
59
|
+
".DS_Store",
|
|
60
|
+
"Thumbs.db",
|
|
61
|
+
// Project caches we own
|
|
62
|
+
".dotrequirements-cache/**",
|
|
63
|
+
];
|
|
64
|
+
/**
|
|
65
|
+
* Read project-level ignore patterns from `.dotrequirements-ignore` (and
|
|
66
|
+
* `.dotreqignore` as a shorthand), if either exists. Returns an empty array if
|
|
67
|
+
* no file is present.
|
|
68
|
+
*/
|
|
69
|
+
export function readProjectIgnores(projectRoot) {
|
|
70
|
+
const candidates = [".dotrequirements-ignore", ".dotreqignore"];
|
|
71
|
+
for (const name of candidates) {
|
|
72
|
+
const p = join(projectRoot, name);
|
|
73
|
+
if (existsSync(p)) {
|
|
74
|
+
const content = readFileSync(p, "utf-8");
|
|
75
|
+
return content
|
|
76
|
+
.split(/\r?\n/)
|
|
77
|
+
.map((line) => line.trim())
|
|
78
|
+
.filter((line) => line.length > 0 && !line.startsWith("#"));
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return [];
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Run Repomix twice over the same scope: once compressed, once uncompressed.
|
|
85
|
+
*
|
|
86
|
+
* We use Repomix's programmatic API rather than `npx repomix` so the CLI does
|
|
87
|
+
* not require a separate install and so we can control config precisely.
|
|
88
|
+
*
|
|
89
|
+
* NOTE: Repomix's TypeScript API is invoked via `runCli`. We invoke it as the
|
|
90
|
+
* library exposes it; the surface is small enough that this wraps cleanly.
|
|
91
|
+
*/
|
|
92
|
+
export async function runPack(options) {
|
|
93
|
+
const { projectRoot, paths, scope, extraIgnores = [] } = options;
|
|
94
|
+
const ignores = [
|
|
95
|
+
...DEFAULT_IGNORES,
|
|
96
|
+
...readProjectIgnores(projectRoot),
|
|
97
|
+
...extraIgnores,
|
|
98
|
+
];
|
|
99
|
+
const target = scope ? join(projectRoot, scope) : projectRoot;
|
|
100
|
+
// Import dynamically — Repomix is heavy and we don't want to load it at CLI
|
|
101
|
+
// boot time for unrelated commands.
|
|
102
|
+
const repomix = await import("repomix");
|
|
103
|
+
const { runCli } = repomix;
|
|
104
|
+
// Run compressed (overview)
|
|
105
|
+
await runCli([target], projectRoot, {
|
|
106
|
+
output: paths.overview,
|
|
107
|
+
style: "plain",
|
|
108
|
+
compress: true,
|
|
109
|
+
ignore: ignores.join(","),
|
|
110
|
+
});
|
|
111
|
+
// Run uncompressed (source)
|
|
112
|
+
await runCli([target], projectRoot, {
|
|
113
|
+
output: paths.source,
|
|
114
|
+
style: "plain",
|
|
115
|
+
compress: false,
|
|
116
|
+
ignore: ignores.join(","),
|
|
117
|
+
});
|
|
118
|
+
// Count "File:" headers in the uncompressed pack to report file count
|
|
119
|
+
const sourceContent = readFileSync(paths.source, "utf-8");
|
|
120
|
+
const fileCount = (sourceContent.match(/^File: /gm) ?? []).length;
|
|
121
|
+
return {
|
|
122
|
+
overviewPath: paths.overview,
|
|
123
|
+
sourcePath: paths.source,
|
|
124
|
+
fileCount,
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
//# sourceMappingURL=pack.js.map
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Planner functions (initial and revise).
|
|
3
|
+
*
|
|
4
|
+
* Two modes correspond to two places the planner is invoked:
|
|
5
|
+
* - initial: first outline from the compressed pack (CTS-PLAN-1)
|
|
6
|
+
* - revise: address reviewer feedback — works for both
|
|
7
|
+
* `requires-another-review` (CTS-PLAN-5) and
|
|
8
|
+
* `approved-with-revisions` (CTS-PLAN-4). The orchestration
|
|
9
|
+
* decides whether to loop back to the reviewer or proceed to
|
|
10
|
+
* fan-out.
|
|
11
|
+
*
|
|
12
|
+
* Each mode is stateless — a fresh claude -p session per call.
|
|
13
|
+
*/
|
|
14
|
+
import { type ClaudeRunner } from "./claude.js";
|
|
15
|
+
import { type Outline, type OutlineReview } from "./schemas.js";
|
|
16
|
+
export interface PlannerContext {
|
|
17
|
+
/** Path to the compressed pack file (readable by the spawned agent). */
|
|
18
|
+
overviewPath: string;
|
|
19
|
+
/** Directories the agent should have read access to (e.g., the cache dir). */
|
|
20
|
+
addDirs: string[];
|
|
21
|
+
/** Optional claude runner override (for tests). */
|
|
22
|
+
runner?: ClaudeRunner;
|
|
23
|
+
/** Optional model override. */
|
|
24
|
+
model?: string;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Run the planner in initial mode and return a parsed outline.
|
|
28
|
+
* Requirement: CTS-PLAN-1
|
|
29
|
+
*/
|
|
30
|
+
export declare function runInitialPlanner(ctx: PlannerContext): Promise<Outline>;
|
|
31
|
+
/**
|
|
32
|
+
* Run the planner in revise mode — produce a new outline that addresses the
|
|
33
|
+
* reviewer's output. Handles both verdicts that trigger another planner pass:
|
|
34
|
+
* `requires-another-review` (categorized findings, judgment-based) and
|
|
35
|
+
* `approved-with-revisions` (explicit revisions list). The same prompt handles
|
|
36
|
+
* both shapes; the prompt switches behavior based on what's in the review.
|
|
37
|
+
*
|
|
38
|
+
* Requirements: CTS-PLAN-4, CTS-PLAN-5
|
|
39
|
+
*/
|
|
40
|
+
export declare function runRevisingPlanner(ctx: PlannerContext, priorOutline: Outline, review: OutlineReview): Promise<Outline>;
|
|
41
|
+
//# sourceMappingURL=planner.d.ts.map
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Planner functions (initial and revise).
|
|
3
|
+
*
|
|
4
|
+
* Two modes correspond to two places the planner is invoked:
|
|
5
|
+
* - initial: first outline from the compressed pack (CTS-PLAN-1)
|
|
6
|
+
* - revise: address reviewer feedback — works for both
|
|
7
|
+
* `requires-another-review` (CTS-PLAN-5) and
|
|
8
|
+
* `approved-with-revisions` (CTS-PLAN-4). The orchestration
|
|
9
|
+
* decides whether to loop back to the reviewer or proceed to
|
|
10
|
+
* fan-out.
|
|
11
|
+
*
|
|
12
|
+
* Each mode is stateless — a fresh claude -p session per call.
|
|
13
|
+
*/
|
|
14
|
+
import { runClaude } from "./claude.js";
|
|
15
|
+
import { PLANNER_INITIAL_PROMPT } from "./prompts/planner-initial.js";
|
|
16
|
+
import { PLANNER_REVISE_PROMPT } from "./prompts/planner-revise.js";
|
|
17
|
+
import { OUTLINE_JSON_SCHEMA, parseOutline, } from "./schemas.js";
|
|
18
|
+
/**
|
|
19
|
+
* Run the planner in initial mode and return a parsed outline.
|
|
20
|
+
* Requirement: CTS-PLAN-1
|
|
21
|
+
*/
|
|
22
|
+
export async function runInitialPlanner(ctx) {
|
|
23
|
+
const runner = ctx.runner ?? runClaude;
|
|
24
|
+
const result = await runner({
|
|
25
|
+
systemPrompt: PLANNER_INITIAL_PROMPT,
|
|
26
|
+
userMessage: `The compressed packed codebase is at: ${ctx.overviewPath}\n\nRead it and produce the outline JSON described in your system prompt.`,
|
|
27
|
+
model: ctx.model,
|
|
28
|
+
tools: ["Read"],
|
|
29
|
+
addDirs: ctx.addDirs,
|
|
30
|
+
jsonSchema: OUTLINE_JSON_SCHEMA,
|
|
31
|
+
});
|
|
32
|
+
if (result.exitCode !== 0) {
|
|
33
|
+
throw new Error(`Planner failed (exit ${result.exitCode}): ${result.stderr || result.stdout}`);
|
|
34
|
+
}
|
|
35
|
+
return parseOutline(result.stdout);
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Run the planner in revise mode — produce a new outline that addresses the
|
|
39
|
+
* reviewer's output. Handles both verdicts that trigger another planner pass:
|
|
40
|
+
* `requires-another-review` (categorized findings, judgment-based) and
|
|
41
|
+
* `approved-with-revisions` (explicit revisions list). The same prompt handles
|
|
42
|
+
* both shapes; the prompt switches behavior based on what's in the review.
|
|
43
|
+
*
|
|
44
|
+
* Requirements: CTS-PLAN-4, CTS-PLAN-5
|
|
45
|
+
*/
|
|
46
|
+
export async function runRevisingPlanner(ctx, priorOutline, review) {
|
|
47
|
+
const runner = ctx.runner ?? runClaude;
|
|
48
|
+
const userMessage = [
|
|
49
|
+
`The compressed packed codebase is at: ${ctx.overviewPath}`,
|
|
50
|
+
``,
|
|
51
|
+
`## Prior outline`,
|
|
52
|
+
`\`\`\`json`,
|
|
53
|
+
JSON.stringify(priorOutline, null, 2),
|
|
54
|
+
`\`\`\``,
|
|
55
|
+
``,
|
|
56
|
+
`## Reviewer output`,
|
|
57
|
+
`\`\`\`json`,
|
|
58
|
+
JSON.stringify(review, null, 2),
|
|
59
|
+
`\`\`\``,
|
|
60
|
+
``,
|
|
61
|
+
`Produce a revised outline JSON that addresses the reviewer's output. Output JSON only.`,
|
|
62
|
+
].join("\n");
|
|
63
|
+
const result = await runner({
|
|
64
|
+
systemPrompt: PLANNER_REVISE_PROMPT,
|
|
65
|
+
userMessage,
|
|
66
|
+
model: ctx.model,
|
|
67
|
+
tools: ["Read"],
|
|
68
|
+
addDirs: ctx.addDirs,
|
|
69
|
+
jsonSchema: OUTLINE_JSON_SCHEMA,
|
|
70
|
+
});
|
|
71
|
+
if (result.exitCode !== 0) {
|
|
72
|
+
throw new Error(`Revising planner failed (exit ${result.exitCode}): ${result.stderr || result.stdout}`);
|
|
73
|
+
}
|
|
74
|
+
return parseOutline(result.stdout);
|
|
75
|
+
}
|
|
76
|
+
//# sourceMappingURL=planner.js.map
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Present stage: write the final spec to .requirements/.
|
|
3
|
+
*
|
|
4
|
+
* - When the outline has 5+ areas, split into per-area files
|
|
5
|
+
* (`.requirements/<sanitized-area-name>.requirements.md`).
|
|
6
|
+
* - When the outline has fewer than 5 areas, write a single document at
|
|
7
|
+
* `.requirements/<sanitized-title>.requirements.md`.
|
|
8
|
+
* - Overwrite handling depends on interactive vs non-interactive mode.
|
|
9
|
+
*
|
|
10
|
+
* Requirements covered:
|
|
11
|
+
* - CTS-PRESENT-1: Final spec written to .requirements/, split when 5+ areas
|
|
12
|
+
* - CTS-PRESENT-2: Interactive overwrite prompt with three choices
|
|
13
|
+
* - CTS-PRESENT-3: Non-interactive overwrites require --overwrite or --skip-existing
|
|
14
|
+
*/
|
|
15
|
+
import type { Outline } from "./schemas.js";
|
|
16
|
+
export declare const AREA_SPLIT_THRESHOLD = 5;
|
|
17
|
+
export type OverwritePolicy = "prompt" | "overwrite" | "skip-existing" | "fail-fast";
|
|
18
|
+
export interface PresentOptions {
|
|
19
|
+
outline: Outline;
|
|
20
|
+
finalSpecPath: string;
|
|
21
|
+
/** Project root (.requirements/ will be created under this). */
|
|
22
|
+
projectRoot: string;
|
|
23
|
+
overwritePolicy: OverwritePolicy;
|
|
24
|
+
}
|
|
25
|
+
export interface PresentResult {
|
|
26
|
+
/** Paths written under .requirements/ (or those that would have been written, in fail cases). */
|
|
27
|
+
outputPaths: string[];
|
|
28
|
+
/** Per-file action taken. */
|
|
29
|
+
actions: PresentAction[];
|
|
30
|
+
/** True if every file landed successfully. */
|
|
31
|
+
allWritten: boolean;
|
|
32
|
+
/** Reason for failure, if `allWritten` is false in fail-fast mode. */
|
|
33
|
+
conflictPath?: string;
|
|
34
|
+
}
|
|
35
|
+
export interface PresentAction {
|
|
36
|
+
path: string;
|
|
37
|
+
action: "created" | "overwrote" | "skipped" | "merged" | "refused";
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Resolve the output path(s) the present stage would write to.
|
|
41
|
+
*/
|
|
42
|
+
export declare function planPresentPaths(outline: Outline, projectRoot: string): {
|
|
43
|
+
mode: "single" | "split";
|
|
44
|
+
paths: {
|
|
45
|
+
area?: string;
|
|
46
|
+
outPath: string;
|
|
47
|
+
}[];
|
|
48
|
+
};
|
|
49
|
+
/**
|
|
50
|
+
* Split a composed spec into per-area sections.
|
|
51
|
+
*
|
|
52
|
+
* The compose stage writes:
|
|
53
|
+
*
|
|
54
|
+
* ---
|
|
55
|
+
* frontmatter
|
|
56
|
+
* ---
|
|
57
|
+
*
|
|
58
|
+
* # Title
|
|
59
|
+
*
|
|
60
|
+
* Summary.
|
|
61
|
+
*
|
|
62
|
+
* ---
|
|
63
|
+
*
|
|
64
|
+
* ## Area One
|
|
65
|
+
* ...
|
|
66
|
+
*
|
|
67
|
+
* ---
|
|
68
|
+
*
|
|
69
|
+
* ## Area Two
|
|
70
|
+
* ...
|
|
71
|
+
*
|
|
72
|
+
* We split on `\n---\n\n## ` and pair each chunk with its area heading.
|
|
73
|
+
*/
|
|
74
|
+
export declare function splitComposedSpec(composed: string, outline: Outline): Map<string, string>;
|
|
75
|
+
/**
|
|
76
|
+
* Build a single-file requirements document from the composed spec.
|
|
77
|
+
* The composed spec IS this document (with the right frontmatter and structure).
|
|
78
|
+
* We just read it and return it.
|
|
79
|
+
*/
|
|
80
|
+
export declare function buildSingleFileDoc(composedSpec: string): string;
|
|
81
|
+
/**
|
|
82
|
+
* Build a per-area requirements document for split mode.
|
|
83
|
+
*/
|
|
84
|
+
export declare function buildAreaDoc(outline: Outline, areaName: string, areaBody: string): string;
|
|
85
|
+
/**
|
|
86
|
+
* Run the present stage.
|
|
87
|
+
*
|
|
88
|
+
* Returns a list of actions taken per path. In fail-fast mode, if any path
|
|
89
|
+
* would conflict, the function returns early with `conflictPath` set and
|
|
90
|
+
* `allWritten = false` — no files are written in this case (preserving the
|
|
91
|
+
* "no file overwritten without choice" guarantee from CTS-PRESENT-2.1).
|
|
92
|
+
*/
|
|
93
|
+
export declare function runPresent(options: PresentOptions): Promise<PresentResult>;
|
|
94
|
+
//# sourceMappingURL=present.d.ts.map
|