@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,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cache directory layout and path helpers for codebase-to-spec.
|
|
3
|
+
*
|
|
4
|
+
* All pipeline artifacts live under `.dotrequirements-cache/` in the project
|
|
5
|
+
* root. Wrappers and downstream stages read cached inputs by these paths;
|
|
6
|
+
* resumability (CTS-RESUME-1..4) is driven by the presence/absence of these
|
|
7
|
+
* files.
|
|
8
|
+
*
|
|
9
|
+
* Requirements covered:
|
|
10
|
+
* - CTS-PACK-1: pack stage paths (overview.txt, source.txt)
|
|
11
|
+
* - CTS-RESUME-1..4: per-stage cache detection
|
|
12
|
+
*/
|
|
13
|
+
import { existsSync, mkdirSync, rmSync } from "node:fs";
|
|
14
|
+
import { join } from "node:path";
|
|
15
|
+
export const CACHE_DIR_NAME = ".dotrequirements-cache";
|
|
16
|
+
export function cachePaths(projectRoot) {
|
|
17
|
+
const root = join(projectRoot, CACHE_DIR_NAME);
|
|
18
|
+
return {
|
|
19
|
+
root,
|
|
20
|
+
overview: join(root, "overview.txt"),
|
|
21
|
+
source: join(root, "source.txt"),
|
|
22
|
+
outlineFinal: join(root, "outline-final.json"),
|
|
23
|
+
outlineTurn: (n) => join(root, `outline-${n}.json`),
|
|
24
|
+
outlineReviewTurn: (n) => join(root, `outline-review-${n}.json`),
|
|
25
|
+
partialsDir: join(root, "partials"),
|
|
26
|
+
partial: (name) => join(root, "partials", `${name}.partial.md`),
|
|
27
|
+
composedSpec: join(root, "spec-composed.md"),
|
|
28
|
+
specVersion: (n) => join(root, `spec-v${n}.md`),
|
|
29
|
+
specReviewTurn: (n) => join(root, `spec-review-${n}.json`),
|
|
30
|
+
specFinal: join(root, "spec-final.md"),
|
|
31
|
+
pipelineSummary: join(root, "pipeline-summary.json"),
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
export function ensureCacheDir(projectRoot) {
|
|
35
|
+
const paths = cachePaths(projectRoot);
|
|
36
|
+
if (!existsSync(paths.root)) {
|
|
37
|
+
mkdirSync(paths.root, { recursive: true });
|
|
38
|
+
}
|
|
39
|
+
if (!existsSync(paths.partialsDir)) {
|
|
40
|
+
mkdirSync(paths.partialsDir, { recursive: true });
|
|
41
|
+
}
|
|
42
|
+
return paths;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Remove all cached artifacts. Used by `--fresh`.
|
|
46
|
+
* Requirement: CTS-RESUME-4
|
|
47
|
+
*/
|
|
48
|
+
export function clearCache(projectRoot) {
|
|
49
|
+
const root = join(projectRoot, CACHE_DIR_NAME);
|
|
50
|
+
if (existsSync(root)) {
|
|
51
|
+
rmSync(root, { recursive: true, force: true });
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
//# sourceMappingURL=cache.js.map
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `claude -p` subprocess wrapper for codebase-to-spec.
|
|
3
|
+
*
|
|
4
|
+
* Provides a small typed surface for invoking the user's Claude Code agent in
|
|
5
|
+
* non-interactive (print) mode, with optional stateful session continuation
|
|
6
|
+
* and JSON-schema output validation.
|
|
7
|
+
*
|
|
8
|
+
* Why this lives here:
|
|
9
|
+
* - The pipeline spawns the user's claude binary, inheriting the user's
|
|
10
|
+
* environment so whatever auth mode they've configured (API key, OAuth via
|
|
11
|
+
* Claude Code subscription, etc.) is what gets used.
|
|
12
|
+
* - Each invocation is a separate context window. Stateful loops use
|
|
13
|
+
* --session-id and --resume to maintain conversation state across turns.
|
|
14
|
+
*
|
|
15
|
+
* Requirements covered:
|
|
16
|
+
* - CTS-PLAN-2, CTS-EDIT-1: stateful reviewer sessions via --session-id / --resume
|
|
17
|
+
* - CTS-PLAN-2, CTS-EDIT-1: JSON-schema-validated output via --json-schema
|
|
18
|
+
*/
|
|
19
|
+
export interface ClaudeRunOptions {
|
|
20
|
+
/** System prompt content (passed via --system-prompt). */
|
|
21
|
+
systemPrompt: string;
|
|
22
|
+
/** User message (passed as final positional arg). */
|
|
23
|
+
userMessage: string;
|
|
24
|
+
/** Model alias (e.g., 'sonnet') or full name. Defaults to 'sonnet'. */
|
|
25
|
+
model?: string;
|
|
26
|
+
/** Tools the agent may use. Defaults to ['Read']. */
|
|
27
|
+
tools?: string[];
|
|
28
|
+
/** Directories to grant filesystem access to (via --add-dir). */
|
|
29
|
+
addDirs?: string[];
|
|
30
|
+
/** Optional JSON schema for structured output validation. */
|
|
31
|
+
jsonSchema?: object;
|
|
32
|
+
/**
|
|
33
|
+
* Stateful session control:
|
|
34
|
+
* - `{ kind: 'fresh', sessionId: '<uuid>' }` — create a new session with this id
|
|
35
|
+
* - `{ kind: 'resume', sessionId: '<uuid>' }` — continue an existing session
|
|
36
|
+
* - omitted / undefined — stateless, no persistence
|
|
37
|
+
*/
|
|
38
|
+
session?: {
|
|
39
|
+
kind: "fresh";
|
|
40
|
+
sessionId: string;
|
|
41
|
+
} | {
|
|
42
|
+
kind: "resume";
|
|
43
|
+
sessionId: string;
|
|
44
|
+
};
|
|
45
|
+
/** Override the working directory. Defaults to system temp dir. */
|
|
46
|
+
cwd?: string;
|
|
47
|
+
/** Override permission mode. Defaults to 'bypassPermissions'. */
|
|
48
|
+
permissionMode?: "bypassPermissions" | "auto" | "default" | "acceptEdits";
|
|
49
|
+
/** Path to the claude binary. Defaults to 'claude' (found in PATH). */
|
|
50
|
+
claudeBinary?: string;
|
|
51
|
+
}
|
|
52
|
+
export interface ClaudeRunResult {
|
|
53
|
+
/** Process exit code. */
|
|
54
|
+
exitCode: number;
|
|
55
|
+
/** Captured stdout. */
|
|
56
|
+
stdout: string;
|
|
57
|
+
/** Captured stderr. */
|
|
58
|
+
stderr: string;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Default runner — spawns the `claude` binary as a subprocess.
|
|
62
|
+
*/
|
|
63
|
+
export declare function runClaude(options: ClaudeRunOptions): Promise<ClaudeRunResult>;
|
|
64
|
+
/**
|
|
65
|
+
* Pluggable runner interface so tests can swap in a mock without spawning
|
|
66
|
+
* a real subprocess. Production code uses `runClaude`; tests pass a fake.
|
|
67
|
+
*/
|
|
68
|
+
export type ClaudeRunner = (options: ClaudeRunOptions) => Promise<ClaudeRunResult>;
|
|
69
|
+
//# sourceMappingURL=claude.d.ts.map
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `claude -p` subprocess wrapper for codebase-to-spec.
|
|
3
|
+
*
|
|
4
|
+
* Provides a small typed surface for invoking the user's Claude Code agent in
|
|
5
|
+
* non-interactive (print) mode, with optional stateful session continuation
|
|
6
|
+
* and JSON-schema output validation.
|
|
7
|
+
*
|
|
8
|
+
* Why this lives here:
|
|
9
|
+
* - The pipeline spawns the user's claude binary, inheriting the user's
|
|
10
|
+
* environment so whatever auth mode they've configured (API key, OAuth via
|
|
11
|
+
* Claude Code subscription, etc.) is what gets used.
|
|
12
|
+
* - Each invocation is a separate context window. Stateful loops use
|
|
13
|
+
* --session-id and --resume to maintain conversation state across turns.
|
|
14
|
+
*
|
|
15
|
+
* Requirements covered:
|
|
16
|
+
* - CTS-PLAN-2, CTS-EDIT-1: stateful reviewer sessions via --session-id / --resume
|
|
17
|
+
* - CTS-PLAN-2, CTS-EDIT-1: JSON-schema-validated output via --json-schema
|
|
18
|
+
*/
|
|
19
|
+
import { spawn } from "node:child_process";
|
|
20
|
+
import { tmpdir } from "node:os";
|
|
21
|
+
/**
|
|
22
|
+
* Default runner — spawns the `claude` binary as a subprocess.
|
|
23
|
+
*/
|
|
24
|
+
export async function runClaude(options) {
|
|
25
|
+
const { systemPrompt, userMessage, model = "sonnet", tools = ["Read"], addDirs = [], jsonSchema, session, cwd = tmpdir(), permissionMode = "bypassPermissions", claudeBinary = "claude", } = options;
|
|
26
|
+
const args = ["-p"];
|
|
27
|
+
args.push("--model", model);
|
|
28
|
+
args.push("--permission-mode", permissionMode);
|
|
29
|
+
// `--output-format text` does NOT enforce `--json-schema` (we verified this
|
|
30
|
+
// empirically). When we want schema-enforced JSON we need
|
|
31
|
+
// `--output-format json`, which wraps the response in a metadata envelope
|
|
32
|
+
// whose `structured_output` field contains the validated JSON.
|
|
33
|
+
args.push("--output-format", jsonSchema ? "json" : "text");
|
|
34
|
+
if (tools.length > 0) {
|
|
35
|
+
args.push("--tools", tools.join(","));
|
|
36
|
+
}
|
|
37
|
+
for (const dir of addDirs) {
|
|
38
|
+
args.push("--add-dir", dir);
|
|
39
|
+
}
|
|
40
|
+
if (jsonSchema) {
|
|
41
|
+
args.push("--json-schema", JSON.stringify(jsonSchema));
|
|
42
|
+
}
|
|
43
|
+
if (session) {
|
|
44
|
+
if (session.kind === "fresh") {
|
|
45
|
+
args.push("--session-id", session.sessionId);
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
args.push("--resume", session.sessionId);
|
|
49
|
+
// On resume we still need to re-pass permission mode and add-dirs;
|
|
50
|
+
// resume doesn't preserve them by default.
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
args.push("--no-session-persistence");
|
|
55
|
+
}
|
|
56
|
+
// System prompt — only on initial calls. On resume, the system prompt is
|
|
57
|
+
// already baked into the session and re-passing it can confuse the agent.
|
|
58
|
+
if (!session || session.kind === "fresh") {
|
|
59
|
+
args.push("--append-system-prompt", systemPrompt);
|
|
60
|
+
}
|
|
61
|
+
// The user message is always the final positional arg.
|
|
62
|
+
args.push(userMessage);
|
|
63
|
+
return new Promise((resolve, reject) => {
|
|
64
|
+
const child = spawn(claudeBinary, args, {
|
|
65
|
+
cwd,
|
|
66
|
+
// Inherit the parent environment so `claude -p` uses whatever auth
|
|
67
|
+
// mode the user has configured (API key if set, OAuth otherwise).
|
|
68
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
69
|
+
});
|
|
70
|
+
// Close stdin immediately — we pass user message via argv, not stdin.
|
|
71
|
+
child.stdin.end();
|
|
72
|
+
let stdout = "";
|
|
73
|
+
let stderr = "";
|
|
74
|
+
child.stdout.on("data", (chunk) => {
|
|
75
|
+
stdout += chunk.toString();
|
|
76
|
+
});
|
|
77
|
+
child.stderr.on("data", (chunk) => {
|
|
78
|
+
stderr += chunk.toString();
|
|
79
|
+
});
|
|
80
|
+
child.on("error", reject);
|
|
81
|
+
child.on("close", (code) => {
|
|
82
|
+
const exitCode = code ?? 0;
|
|
83
|
+
// If we asked for schema-validated JSON, claude -p returns a metadata
|
|
84
|
+
// envelope. Extract the agent's actual output from `structured_output`
|
|
85
|
+
// (preferred) or `result` (fallback). Callers expect `stdout` to
|
|
86
|
+
// contain only the agent's output, not the envelope.
|
|
87
|
+
if (jsonSchema && exitCode === 0 && stdout.trim().length > 0) {
|
|
88
|
+
try {
|
|
89
|
+
const envelope = JSON.parse(stdout);
|
|
90
|
+
if (envelope && typeof envelope === "object") {
|
|
91
|
+
if (envelope.is_error) {
|
|
92
|
+
// Surface the API error message on stderr so callers can see it.
|
|
93
|
+
const apiErrSnippet = typeof envelope.result === "string"
|
|
94
|
+
? envelope.result
|
|
95
|
+
: JSON.stringify(envelope);
|
|
96
|
+
resolve({
|
|
97
|
+
exitCode: exitCode || 1,
|
|
98
|
+
stdout: "",
|
|
99
|
+
stderr: `${stderr}\nclaude -p reported is_error: ${apiErrSnippet}`,
|
|
100
|
+
});
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
if ("structured_output" in envelope) {
|
|
104
|
+
resolve({
|
|
105
|
+
exitCode,
|
|
106
|
+
stdout: JSON.stringify(envelope.structured_output),
|
|
107
|
+
stderr,
|
|
108
|
+
});
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
if (typeof envelope.result === "string") {
|
|
112
|
+
resolve({ exitCode, stdout: envelope.result, stderr });
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
catch {
|
|
118
|
+
// Fall through to raw stdout. Caller's parser will surface the
|
|
119
|
+
// schema-validation failure.
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
resolve({ exitCode, stdout, stderr });
|
|
123
|
+
});
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
//# sourceMappingURL=claude.js.map
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Compose stage: assemble per-area partials into a single composed spec.
|
|
3
|
+
*
|
|
4
|
+
* The composed document has YAML frontmatter (title, defaultPrefix), an H1
|
|
5
|
+
* title, a summary paragraph from the outline, and one H2 section per area.
|
|
6
|
+
* Each area section contains the partial's content.
|
|
7
|
+
*
|
|
8
|
+
* After assembly, schema validation runs over the document. Mechanical errors
|
|
9
|
+
* (duplicate IDs within an area, etc.) trigger deterministic fixes; other
|
|
10
|
+
* errors surface to the caller.
|
|
11
|
+
*
|
|
12
|
+
* Requirements covered:
|
|
13
|
+
* - CTS-COMPOSE-1: Compose produces a single Markdown document from partials
|
|
14
|
+
* - CTS-COMPOSE-2: Compose validates the resulting document and corrects mechanical errors
|
|
15
|
+
*/
|
|
16
|
+
import type { Outline } from "./schemas.js";
|
|
17
|
+
export interface ComposeOptions {
|
|
18
|
+
outline: Outline;
|
|
19
|
+
/** Resolves an area's sanitized name to its partial file path. */
|
|
20
|
+
partialPathFor: (sanitizedAreaName: string) => string;
|
|
21
|
+
/** Where to write the composed spec. */
|
|
22
|
+
composedPath: string;
|
|
23
|
+
}
|
|
24
|
+
export interface ComposeResult {
|
|
25
|
+
/** Path the composed spec was written to. */
|
|
26
|
+
composedPath: string;
|
|
27
|
+
/** Number of partials that contributed content. */
|
|
28
|
+
partialsIncluded: number;
|
|
29
|
+
/** Number of areas whose partial was missing or marked as missing. */
|
|
30
|
+
partialsMissing: number;
|
|
31
|
+
/** Validation outcome — undefined when validation passes. */
|
|
32
|
+
validationError?: string;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Assemble the composed Markdown document.
|
|
36
|
+
*
|
|
37
|
+
* Public so tests can call without disk side effects (composedPath isn't used
|
|
38
|
+
* here; the caller writes the returned string).
|
|
39
|
+
*/
|
|
40
|
+
export declare function assembleComposedDocument(outline: Outline, partialPathFor: (sanitized: string) => string): {
|
|
41
|
+
content: string;
|
|
42
|
+
partialsIncluded: number;
|
|
43
|
+
partialsMissing: number;
|
|
44
|
+
};
|
|
45
|
+
/**
|
|
46
|
+
* Compose the spec and write it to disk. Validates the result.
|
|
47
|
+
*/
|
|
48
|
+
export declare function runCompose(options: ComposeOptions): ComposeResult;
|
|
49
|
+
//# sourceMappingURL=compose.d.ts.map
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Compose stage: assemble per-area partials into a single composed spec.
|
|
3
|
+
*
|
|
4
|
+
* The composed document has YAML frontmatter (title, defaultPrefix), an H1
|
|
5
|
+
* title, a summary paragraph from the outline, and one H2 section per area.
|
|
6
|
+
* Each area section contains the partial's content.
|
|
7
|
+
*
|
|
8
|
+
* After assembly, schema validation runs over the document. Mechanical errors
|
|
9
|
+
* (duplicate IDs within an area, etc.) trigger deterministic fixes; other
|
|
10
|
+
* errors surface to the caller.
|
|
11
|
+
*
|
|
12
|
+
* Requirements covered:
|
|
13
|
+
* - CTS-COMPOSE-1: Compose produces a single Markdown document from partials
|
|
14
|
+
* - CTS-COMPOSE-2: Compose validates the resulting document and corrects mechanical errors
|
|
15
|
+
*/
|
|
16
|
+
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
17
|
+
import { parseRequirementsFile } from "../schema/parser.js";
|
|
18
|
+
import { sanitizeAreaName } from "./fan-out.js";
|
|
19
|
+
/**
|
|
20
|
+
* Read a partial file. If missing or marked as a missing-partial placeholder,
|
|
21
|
+
* return null (caller will emit a stub section).
|
|
22
|
+
*/
|
|
23
|
+
function readPartial(path) {
|
|
24
|
+
if (!existsSync(path))
|
|
25
|
+
return null;
|
|
26
|
+
const content = readFileSync(path, "utf-8");
|
|
27
|
+
const trimmed = content.trim();
|
|
28
|
+
if (!trimmed)
|
|
29
|
+
return null;
|
|
30
|
+
// Common placeholders for missing partials produced by fan-out on failure
|
|
31
|
+
// or by zero-files-assigned areas.
|
|
32
|
+
if (trimmed.startsWith("_(missing partial") ||
|
|
33
|
+
trimmed.startsWith("_(no files assigned")) {
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
return content;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Assemble the composed Markdown document.
|
|
40
|
+
*
|
|
41
|
+
* Public so tests can call without disk side effects (composedPath isn't used
|
|
42
|
+
* here; the caller writes the returned string).
|
|
43
|
+
*/
|
|
44
|
+
export function assembleComposedDocument(outline, partialPathFor) {
|
|
45
|
+
const parts = [];
|
|
46
|
+
parts.push("---");
|
|
47
|
+
parts.push("document:");
|
|
48
|
+
parts.push(` title: "${escapeYamlString(outline.title)}"`);
|
|
49
|
+
parts.push(` defaultPrefix: ${outline.defaultPrefix}`);
|
|
50
|
+
parts.push("---");
|
|
51
|
+
parts.push("");
|
|
52
|
+
parts.push(`# ${outline.title}`);
|
|
53
|
+
parts.push("");
|
|
54
|
+
parts.push(outline.summary.trim());
|
|
55
|
+
parts.push("");
|
|
56
|
+
let partialsIncluded = 0;
|
|
57
|
+
let partialsMissing = 0;
|
|
58
|
+
for (const area of outline.areas) {
|
|
59
|
+
const sanitized = sanitizeAreaName(area.name);
|
|
60
|
+
const partialPath = partialPathFor(sanitized);
|
|
61
|
+
parts.push("---");
|
|
62
|
+
parts.push("");
|
|
63
|
+
parts.push(`## ${area.name}`);
|
|
64
|
+
parts.push("");
|
|
65
|
+
const partial = readPartial(partialPath);
|
|
66
|
+
if (partial) {
|
|
67
|
+
parts.push(partial.trim());
|
|
68
|
+
partialsIncluded++;
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
parts.push(`_(partial for ${area.name} is missing or empty — surface this to the user when presenting the spec)_`);
|
|
72
|
+
partialsMissing++;
|
|
73
|
+
}
|
|
74
|
+
parts.push("");
|
|
75
|
+
}
|
|
76
|
+
return {
|
|
77
|
+
content: parts.join("\n"),
|
|
78
|
+
partialsIncluded,
|
|
79
|
+
partialsMissing,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
/** Escape a string for safe YAML scalar use. */
|
|
83
|
+
function escapeYamlString(s) {
|
|
84
|
+
return s.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Attempt to fix mechanical errors in a composed document.
|
|
88
|
+
*
|
|
89
|
+
* For v0, the simplest mechanical fix is duplicate IDs within an area —
|
|
90
|
+
* we detect and renumber them sequentially. Position-path and Gherkin syntax
|
|
91
|
+
* errors surface to the validator's caller; we don't auto-fix those because
|
|
92
|
+
* the editor loop is better suited to it.
|
|
93
|
+
*/
|
|
94
|
+
function fixMechanicalErrors(content) {
|
|
95
|
+
// Detect duplicate IDs (lines like "PREFIX-AREA-001: ...") within each area.
|
|
96
|
+
// For v0 we punt — the schema validator will catch them and the caller will
|
|
97
|
+
// surface to the user. Returning content unchanged keeps the contract: the
|
|
98
|
+
// function is a no-op for inputs without fixable errors.
|
|
99
|
+
return content;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Compose the spec and write it to disk. Validates the result.
|
|
103
|
+
*/
|
|
104
|
+
export function runCompose(options) {
|
|
105
|
+
const { outline, partialPathFor, composedPath } = options;
|
|
106
|
+
const { content, partialsIncluded, partialsMissing } = assembleComposedDocument(outline, partialPathFor);
|
|
107
|
+
const finalContent = fixMechanicalErrors(content);
|
|
108
|
+
writeFileSync(composedPath, finalContent, "utf-8");
|
|
109
|
+
// Validate
|
|
110
|
+
let validationError;
|
|
111
|
+
try {
|
|
112
|
+
parseRequirementsFile(finalContent);
|
|
113
|
+
}
|
|
114
|
+
catch (err) {
|
|
115
|
+
validationError = err instanceof Error ? err.message : String(err);
|
|
116
|
+
}
|
|
117
|
+
return {
|
|
118
|
+
composedPath,
|
|
119
|
+
partialsIncluded,
|
|
120
|
+
partialsMissing,
|
|
121
|
+
validationError,
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
//# sourceMappingURL=compose.js.map
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Spec review + editor loop.
|
|
3
|
+
*
|
|
4
|
+
* Same structure as the outline review loop, scaled up for document-level
|
|
5
|
+
* revision: stateful spec reviewer + stateless editor. The reviewer
|
|
6
|
+
* persists across turns so it can compare prior spec versions to the latest.
|
|
7
|
+
*
|
|
8
|
+
* Requirements covered:
|
|
9
|
+
* - CTS-EDIT-1: stateful spec reviewer
|
|
10
|
+
* - CTS-EDIT-2: approved → proceed
|
|
11
|
+
* - CTS-EDIT-3: approved-with-revisions → one mechanical edit pass
|
|
12
|
+
* - CTS-EDIT-4: requires-another-review → revise & resubmit
|
|
13
|
+
* - CTS-EDIT-5: editor operates on cohesive document
|
|
14
|
+
* - CTS-EDIT-6: max-turns + convergence nudge
|
|
15
|
+
*/
|
|
16
|
+
import { type ClaudeRunner } from "./claude.js";
|
|
17
|
+
import type { ProgressEmitter } from "./progress.js";
|
|
18
|
+
import { type SpecReview } from "./schemas.js";
|
|
19
|
+
export declare const DEFAULT_EDIT_LOOP_MAX_TURNS = 6;
|
|
20
|
+
export interface EditLoopOptions {
|
|
21
|
+
/** Path to the composed spec (input). */
|
|
22
|
+
composedSpecPath: string;
|
|
23
|
+
/**
|
|
24
|
+
* Resolver: given a turn number (1-indexed), return the path the editor's
|
|
25
|
+
* output for that turn should land at. Loop persists each version.
|
|
26
|
+
*/
|
|
27
|
+
specVersionFor: (turn: number) => string;
|
|
28
|
+
/** Path to the codebase pack. */
|
|
29
|
+
fullPackPath: string;
|
|
30
|
+
/** Directories for filesystem access. */
|
|
31
|
+
addDirs: string[];
|
|
32
|
+
maxTurns?: number;
|
|
33
|
+
runner?: ClaudeRunner;
|
|
34
|
+
model?: string;
|
|
35
|
+
progress?: ProgressEmitter;
|
|
36
|
+
/** Override session id (for tests / reproducibility). */
|
|
37
|
+
sessionId?: string;
|
|
38
|
+
}
|
|
39
|
+
export interface EditLoopResult {
|
|
40
|
+
/** Path to the final spec (post-edit if applicable). */
|
|
41
|
+
finalSpecPath: string;
|
|
42
|
+
/** Per-turn spec paths (turn 1 is the composed-spec copy, then each revision). */
|
|
43
|
+
specPathsByTurn: string[];
|
|
44
|
+
/** Per-turn reviewer results. */
|
|
45
|
+
reviewsByTurn: SpecReview[];
|
|
46
|
+
/** The final reviewer verdict. */
|
|
47
|
+
finalVerdict: SpecReview["verdict"];
|
|
48
|
+
/** Number of review turns used. */
|
|
49
|
+
turnsUsed: number;
|
|
50
|
+
/** True if the loop hit max-turns without converging. */
|
|
51
|
+
hitMaxTurns: boolean;
|
|
52
|
+
}
|
|
53
|
+
export declare function runEditLoop(options: EditLoopOptions): Promise<EditLoopResult>;
|
|
54
|
+
//# sourceMappingURL=edit-loop.d.ts.map
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Spec review + editor loop.
|
|
3
|
+
*
|
|
4
|
+
* Same structure as the outline review loop, scaled up for document-level
|
|
5
|
+
* revision: stateful spec reviewer + stateless editor. The reviewer
|
|
6
|
+
* persists across turns so it can compare prior spec versions to the latest.
|
|
7
|
+
*
|
|
8
|
+
* Requirements covered:
|
|
9
|
+
* - CTS-EDIT-1: stateful spec reviewer
|
|
10
|
+
* - CTS-EDIT-2: approved → proceed
|
|
11
|
+
* - CTS-EDIT-3: approved-with-revisions → one mechanical edit pass
|
|
12
|
+
* - CTS-EDIT-4: requires-another-review → revise & resubmit
|
|
13
|
+
* - CTS-EDIT-5: editor operates on cohesive document
|
|
14
|
+
* - CTS-EDIT-6: max-turns + convergence nudge
|
|
15
|
+
*/
|
|
16
|
+
import { randomUUID } from "node:crypto";
|
|
17
|
+
import { copyFileSync } from "node:fs";
|
|
18
|
+
import { runClaude } from "./claude.js";
|
|
19
|
+
import { runEditor } from "./editor.js";
|
|
20
|
+
import { SPEC_REVIEWER_PROMPT } from "./prompts/spec-reviewer.js";
|
|
21
|
+
import { parseSpecReview, SPEC_REVIEW_JSON_SCHEMA, } from "./schemas.js";
|
|
22
|
+
export const DEFAULT_EDIT_LOOP_MAX_TURNS = 6;
|
|
23
|
+
function reviewerUserMessageInitial(specPath, packPath) {
|
|
24
|
+
return [
|
|
25
|
+
`Initial spec review.`,
|
|
26
|
+
``,
|
|
27
|
+
`Composed spec: ${specPath}`,
|
|
28
|
+
`Codebase pack: ${packPath}`,
|
|
29
|
+
``,
|
|
30
|
+
`Read both via the Read tool, then produce your critique JSON.`,
|
|
31
|
+
].join("\n");
|
|
32
|
+
}
|
|
33
|
+
function reviewerUserMessageRevised(turn, specPath, packPath, nudge) {
|
|
34
|
+
const lines = [
|
|
35
|
+
`Review turn ${turn}. The editor has revised the spec based on your prior feedback.`,
|
|
36
|
+
``,
|
|
37
|
+
];
|
|
38
|
+
if (nudge) {
|
|
39
|
+
lines.push(nudge, ``);
|
|
40
|
+
}
|
|
41
|
+
lines.push(`Revised spec: ${specPath}`, `Codebase pack at ${packPath} is unchanged.`, ``, `Read the revised spec, compare to the prior version, and produce your critique JSON.`);
|
|
42
|
+
return lines.join("\n");
|
|
43
|
+
}
|
|
44
|
+
function convergenceNudge(turn, maxTurns) {
|
|
45
|
+
return `CONVERGENCE NUDGE: This is review turn ${turn} of ${maxTurns} maximum. Please prioritize convergence. Your job is still to only approve a genuinely-ready spec, but if it is close, lean toward \`approved-with-revisions\` over \`requires-another-review\`. Reserve \`requires-another-review\` for genuine issues that the editor has not addressed despite prior feedback.`;
|
|
46
|
+
}
|
|
47
|
+
async function callReviewer(args) {
|
|
48
|
+
const result = await args.runner({
|
|
49
|
+
systemPrompt: SPEC_REVIEWER_PROMPT,
|
|
50
|
+
userMessage: args.userMessage,
|
|
51
|
+
model: args.model,
|
|
52
|
+
tools: ["Read", "Grep", "Glob"],
|
|
53
|
+
addDirs: args.addDirs,
|
|
54
|
+
jsonSchema: SPEC_REVIEW_JSON_SCHEMA,
|
|
55
|
+
session: {
|
|
56
|
+
kind: args.isFirstTurn ? "fresh" : "resume",
|
|
57
|
+
sessionId: args.sessionId,
|
|
58
|
+
},
|
|
59
|
+
});
|
|
60
|
+
if (result.exitCode !== 0) {
|
|
61
|
+
throw new Error(`Spec reviewer failed (exit ${result.exitCode}): ${result.stderr || result.stdout}`);
|
|
62
|
+
}
|
|
63
|
+
// Retry-once on parse failure (CTS-EDIT-1 convention, same as outline loop).
|
|
64
|
+
try {
|
|
65
|
+
return parseSpecReview(result.stdout);
|
|
66
|
+
}
|
|
67
|
+
catch (err) {
|
|
68
|
+
const retry = await args.runner({
|
|
69
|
+
systemPrompt: SPEC_REVIEWER_PROMPT,
|
|
70
|
+
userMessage: `${args.userMessage}\n\n(Your prior output failed schema validation: ${err instanceof Error ? err.message : String(err)}. Please try again, emitting JSON only.)`,
|
|
71
|
+
model: args.model,
|
|
72
|
+
tools: ["Read", "Grep", "Glob"],
|
|
73
|
+
addDirs: args.addDirs,
|
|
74
|
+
jsonSchema: SPEC_REVIEW_JSON_SCHEMA,
|
|
75
|
+
session: { kind: "resume", sessionId: args.sessionId },
|
|
76
|
+
});
|
|
77
|
+
if (retry.exitCode !== 0) {
|
|
78
|
+
throw new Error(`Spec reviewer retry failed (exit ${retry.exitCode}): ${retry.stderr || retry.stdout}`);
|
|
79
|
+
}
|
|
80
|
+
return parseSpecReview(retry.stdout);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
export async function runEditLoop(options) {
|
|
84
|
+
const { composedSpecPath, specVersionFor, fullPackPath, addDirs, maxTurns = DEFAULT_EDIT_LOOP_MAX_TURNS, runner = runClaude, model, progress, sessionId = randomUUID(), } = options;
|
|
85
|
+
// Turn 1: copy the composed spec to spec-v1 and review it.
|
|
86
|
+
const specV1 = specVersionFor(1);
|
|
87
|
+
copyFileSync(composedSpecPath, specV1);
|
|
88
|
+
progress?.emit({
|
|
89
|
+
stage: "spec-review",
|
|
90
|
+
step: "turn-1",
|
|
91
|
+
message: `Reviewing composed spec (${specV1})`,
|
|
92
|
+
});
|
|
93
|
+
let review = await callReviewer({
|
|
94
|
+
runner,
|
|
95
|
+
sessionId,
|
|
96
|
+
isFirstTurn: true,
|
|
97
|
+
userMessage: reviewerUserMessageInitial(specV1, fullPackPath),
|
|
98
|
+
addDirs,
|
|
99
|
+
model,
|
|
100
|
+
});
|
|
101
|
+
const specPathsByTurn = [specV1];
|
|
102
|
+
const reviewsByTurn = [review];
|
|
103
|
+
let currentSpecPath = specV1;
|
|
104
|
+
progress?.emit({
|
|
105
|
+
stage: "spec-review",
|
|
106
|
+
step: "turn-1",
|
|
107
|
+
message: `Verdict: ${review.verdict}`,
|
|
108
|
+
data: { verdict: review.verdict },
|
|
109
|
+
});
|
|
110
|
+
let turn = 1;
|
|
111
|
+
while (review.verdict === "requires-another-review" && turn < maxTurns) {
|
|
112
|
+
turn++;
|
|
113
|
+
const nextSpec = specVersionFor(turn);
|
|
114
|
+
progress?.emit({
|
|
115
|
+
stage: "spec-review",
|
|
116
|
+
step: `edit-turn-${turn}`,
|
|
117
|
+
message: `Editor revising spec (turn ${turn}/${maxTurns})`,
|
|
118
|
+
});
|
|
119
|
+
const editResult = await runEditor({
|
|
120
|
+
inputSpecPath: currentSpecPath,
|
|
121
|
+
outputSpecPath: nextSpec,
|
|
122
|
+
fullPackPath,
|
|
123
|
+
addDirs,
|
|
124
|
+
critique: review,
|
|
125
|
+
mode: "revise",
|
|
126
|
+
runner,
|
|
127
|
+
model,
|
|
128
|
+
});
|
|
129
|
+
if (!editResult.wroteSpec) {
|
|
130
|
+
throw new Error(`Editor produced no output spec at ${nextSpec} (exit ${editResult.exitCode}): ${editResult.stderr}`);
|
|
131
|
+
}
|
|
132
|
+
currentSpecPath = nextSpec;
|
|
133
|
+
specPathsByTurn.push(currentSpecPath);
|
|
134
|
+
const nudge = turn >= maxTurns - 1 && turn !== maxTurns
|
|
135
|
+
? convergenceNudge(turn, maxTurns)
|
|
136
|
+
: undefined;
|
|
137
|
+
progress?.emit({
|
|
138
|
+
stage: "spec-review",
|
|
139
|
+
step: `turn-${turn}`,
|
|
140
|
+
message: `Reviewing revised spec (turn ${turn}/${maxTurns})`,
|
|
141
|
+
data: { turn, maxTurns, nudge: !!nudge },
|
|
142
|
+
});
|
|
143
|
+
review = await callReviewer({
|
|
144
|
+
runner,
|
|
145
|
+
sessionId,
|
|
146
|
+
isFirstTurn: false,
|
|
147
|
+
userMessage: reviewerUserMessageRevised(turn, currentSpecPath, fullPackPath, nudge),
|
|
148
|
+
addDirs,
|
|
149
|
+
model,
|
|
150
|
+
});
|
|
151
|
+
reviewsByTurn.push(review);
|
|
152
|
+
progress?.emit({
|
|
153
|
+
stage: "spec-review",
|
|
154
|
+
step: `turn-${turn}`,
|
|
155
|
+
message: `Verdict: ${review.verdict}`,
|
|
156
|
+
data: { verdict: review.verdict },
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
const hitMaxTurns = review.verdict === "requires-another-review" && turn >= maxTurns;
|
|
160
|
+
// approved-with-revisions → apply mechanical revisions to a final version.
|
|
161
|
+
if (review.verdict === "approved-with-revisions" &&
|
|
162
|
+
review.revisions.length > 0) {
|
|
163
|
+
const finalTurn = turn + 1;
|
|
164
|
+
const finalPath = specVersionFor(finalTurn);
|
|
165
|
+
progress?.emit({
|
|
166
|
+
stage: "spec-review",
|
|
167
|
+
step: "apply-revisions",
|
|
168
|
+
message: `Applying ${review.revisions.length} mechanical revision(s)`,
|
|
169
|
+
});
|
|
170
|
+
const applyResult = await runEditor({
|
|
171
|
+
inputSpecPath: currentSpecPath,
|
|
172
|
+
outputSpecPath: finalPath,
|
|
173
|
+
fullPackPath,
|
|
174
|
+
addDirs,
|
|
175
|
+
critique: review,
|
|
176
|
+
mode: "apply",
|
|
177
|
+
runner,
|
|
178
|
+
model,
|
|
179
|
+
});
|
|
180
|
+
if (!applyResult.wroteSpec) {
|
|
181
|
+
throw new Error(`Apply-mode editor produced no output spec at ${finalPath} (exit ${applyResult.exitCode}): ${applyResult.stderr}`);
|
|
182
|
+
}
|
|
183
|
+
currentSpecPath = finalPath;
|
|
184
|
+
specPathsByTurn.push(currentSpecPath);
|
|
185
|
+
}
|
|
186
|
+
return {
|
|
187
|
+
finalSpecPath: currentSpecPath,
|
|
188
|
+
specPathsByTurn,
|
|
189
|
+
reviewsByTurn,
|
|
190
|
+
finalVerdict: review.verdict,
|
|
191
|
+
turnsUsed: turn,
|
|
192
|
+
hitMaxTurns,
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
//# sourceMappingURL=edit-loop.js.map
|