@popoverai/dotrequirements 0.26.0 → 0.26.1
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/dist/codebase-to-spec/area-name.d.ts +13 -0
- package/dist/codebase-to-spec/area-name.js +18 -0
- package/dist/codebase-to-spec/cache.d.ts +31 -0
- package/dist/codebase-to-spec/cache.js +29 -1
- package/dist/codebase-to-spec/compose.d.ts +7 -4
- package/dist/codebase-to-spec/compose.js +10 -21
- package/dist/codebase-to-spec/dispatch.js +1 -1
- package/dist/codebase-to-spec/present.js +7 -2
- package/dist/codebase-to-spec/prompts/planner-initial.d.ts +3 -2
- package/dist/codebase-to-spec/prompts/planner-initial.js +3 -2
- package/dist/codebase-to-spec/renumber.d.ts +52 -0
- package/dist/codebase-to-spec/renumber.js +105 -0
- package/dist/codebase-to-spec/schemas.d.ts +35 -240
- package/dist/codebase-to-spec/schemas.js +5 -173
- package/dist/commands/codebase-to-spec/dispatch-editor.js +1 -1
- package/dist/commands/codebase-to-spec/dispatch-spec.js +1 -1
- package/dist/commands/codebase-to-spec/index.js +7 -103
- package/dist/commands/codebase-to-spec/pack.js +8 -1
- package/dist/commands/codebase-to-spec/present-orchestrator.d.ts +3 -1
- package/dist/commands/codebase-to-spec/present-orchestrator.js +11 -4
- package/dist/templates/skills/codebase-to-spec/SKILL.md +10 -4
- package/package.json +1 -1
- package/dist/codebase-to-spec/edit-loop.d.ts +0 -54
- package/dist/codebase-to-spec/edit-loop.js +0 -195
- package/dist/codebase-to-spec/editor.d.ts +0 -54
- package/dist/codebase-to-spec/editor.js +0 -74
- package/dist/codebase-to-spec/fan-out.d.ts +0 -63
- package/dist/codebase-to-spec/fan-out.js +0 -215
- package/dist/codebase-to-spec/outline-review-loop.d.ts +0 -51
- package/dist/codebase-to-spec/outline-review-loop.js +0 -187
- package/dist/codebase-to-spec/planner.d.ts +0 -41
- package/dist/codebase-to-spec/planner.js +0 -76
- package/dist/codebase-to-spec/prompts/outline-reviewer.d.ts +0 -12
- package/dist/codebase-to-spec/prompts/outline-reviewer.js +0 -89
- package/dist/codebase-to-spec/slice.d.ts +0 -49
- package/dist/codebase-to-spec/slice.js +0 -111
- package/dist/codebase-to-spec/specifier.d.ts +0 -60
- package/dist/codebase-to-spec/specifier.js +0 -85
- package/dist/codebase-to-spec/summary.d.ts +0 -51
- package/dist/codebase-to-spec/summary.js +0 -183
- package/dist/commands/codebase-to-spec/compose.d.ts +0 -14
- package/dist/commands/codebase-to-spec/compose.js +0 -57
- package/dist/commands/codebase-to-spec/edit-loop.d.ts +0 -16
- package/dist/commands/codebase-to-spec/edit-loop.js +0 -83
- package/dist/commands/codebase-to-spec/fan-out.d.ts +0 -19
- package/dist/commands/codebase-to-spec/fan-out.js +0 -77
- package/dist/commands/codebase-to-spec/plan-loop.d.ts +0 -26
- package/dist/commands/codebase-to-spec/plan-loop.js +0 -105
- package/dist/commands/codebase-to-spec/present.d.ts +0 -26
- package/dist/commands/codebase-to-spec/present.js +0 -97
- package/dist/commands/codebase-to-spec/run.d.ts +0 -20
- package/dist/commands/codebase-to-spec/run.js +0 -86
- package/dist/commands/codebase-to-spec/specify-area.d.ts +0 -18
- package/dist/commands/codebase-to-spec/specify-area.js +0 -82
|
@@ -12,10 +12,10 @@
|
|
|
12
12
|
* - CTSO-CONV-3: orchestrator dispatches an editor when revisions are needed
|
|
13
13
|
*/
|
|
14
14
|
import { existsSync, readFileSync } from "node:fs";
|
|
15
|
+
import { sanitizeAreaName } from "../../codebase-to-spec/area-name.js";
|
|
15
16
|
import { cachePaths } from "../../codebase-to-spec/cache.js";
|
|
16
17
|
import { EDITOR_DISPATCH_ID_PREFIX } from "../../codebase-to-spec/dispatch.js";
|
|
17
18
|
import { ExitCode } from "../../codebase-to-spec/exit-codes.js";
|
|
18
|
-
import { sanitizeAreaName } from "../../codebase-to-spec/fan-out.js";
|
|
19
19
|
import { parseConversationalOutline, } from "../../codebase-to-spec/schemas.js";
|
|
20
20
|
import { findProjectRoot } from "../../utils/project-settings.js";
|
|
21
21
|
export async function dispatchEditorCommand(areaPrefix) {
|
|
@@ -10,10 +10,10 @@
|
|
|
10
10
|
* the orchestrator should approve the outline before fan-out.
|
|
11
11
|
*/
|
|
12
12
|
import { existsSync, readFileSync } from "node:fs";
|
|
13
|
+
import { sanitizeAreaName } from "../../codebase-to-spec/area-name.js";
|
|
13
14
|
import { cachePaths } from "../../codebase-to-spec/cache.js";
|
|
14
15
|
import { SPECIFIER_DISPATCH_ID_PREFIX } from "../../codebase-to-spec/dispatch.js";
|
|
15
16
|
import { ExitCode } from "../../codebase-to-spec/exit-codes.js";
|
|
16
|
-
import { sanitizeAreaName } from "../../codebase-to-spec/fan-out.js";
|
|
17
17
|
import { parseConversationalOutline, } from "../../codebase-to-spec/schemas.js";
|
|
18
18
|
import { findProjectRoot } from "../../utils/project-settings.js";
|
|
19
19
|
export async function dispatchSpecCommand() {
|
|
@@ -5,30 +5,23 @@
|
|
|
5
5
|
* be invoked granularly (e.g., `dotrequirements cts pack`) per CTS-CLI-2.
|
|
6
6
|
*/
|
|
7
7
|
import { installCtsAbortHandlers } from "../../codebase-to-spec/progress.js";
|
|
8
|
-
import { composeCommand } from "./compose.js";
|
|
9
8
|
import { composeOrchestratorCommand } from "./compose-orchestrator.js";
|
|
10
9
|
import { dispatchContextCommand } from "./dispatch-context.js";
|
|
11
10
|
import { dispatchEditorCommand } from "./dispatch-editor.js";
|
|
12
11
|
import { dispatchPlannerCommand } from "./dispatch-planner.js";
|
|
13
12
|
import { dispatchSpecCommand } from "./dispatch-spec.js";
|
|
14
|
-
import { editLoopCommand } from "./edit-loop.js";
|
|
15
|
-
import { fanOutCommand } from "./fan-out.js";
|
|
16
13
|
import { packCommand } from "./pack.js";
|
|
17
|
-
import { planLoopCommand } from "./plan-loop.js";
|
|
18
|
-
import { presentCommand } from "./present.js";
|
|
19
14
|
import { presentOrchestratorCommand } from "./present-orchestrator.js";
|
|
20
|
-
import { runCommand } from "./run.js";
|
|
21
15
|
import { skillInstallCommand } from "./skill-install.js";
|
|
22
|
-
import { specifyAreaCommand } from "./specify-area.js";
|
|
23
16
|
import { styleCheckCommand } from "./style-check.js";
|
|
24
17
|
import { validateCommand } from "./validate.js";
|
|
25
18
|
/**
|
|
26
19
|
* Marker used to hide a command from `--help` output without removing it.
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
* in
|
|
30
|
-
*
|
|
31
|
-
*
|
|
20
|
+
* The conversational orchestrator's plumbing commands (the `dispatch-*` payload
|
|
21
|
+
* builders and the `-orchestrator` compose/present variants) and the
|
|
22
|
+
* skill-install entry point ship in the npm package but aren't surfaced in help
|
|
23
|
+
* listings — they're invoked by name by the orchestrator skill and workflow,
|
|
24
|
+
* not by users, so they stay out of the user-facing CLI surface.
|
|
32
25
|
*/
|
|
33
26
|
const HIDDEN = { hidden: true };
|
|
34
27
|
export function registerCodebaseToSpec(program) {
|
|
@@ -37,9 +30,8 @@ export function registerCodebaseToSpec(program) {
|
|
|
37
30
|
.alias("cts")
|
|
38
31
|
.description("Generate dotrequirements behavioral specifications from a codebase");
|
|
39
32
|
// Install SIGTERM/SIGINT handlers for every cts subcommand so an interrupted
|
|
40
|
-
//
|
|
41
|
-
//
|
|
42
|
-
// subcommand action runs.
|
|
33
|
+
// stage emits a structured `pipeline/aborted` line before exiting. preAction
|
|
34
|
+
// fires after argv parsing and before the subcommand action runs.
|
|
43
35
|
cts.hook("preAction", () => {
|
|
44
36
|
installCtsAbortHandlers();
|
|
45
37
|
});
|
|
@@ -52,31 +44,6 @@ export function registerCodebaseToSpec(program) {
|
|
|
52
44
|
.option("--budget <tokens>", "Override the working-context budget (in tokens)", (v) => parseInt(v, 10))
|
|
53
45
|
.option("--ignore-requirements", "Exclude `.requirements/**` from the pack (use when testing cts against a codebase whose existing requirements should not influence the output)")
|
|
54
46
|
.action((opts) => packCommand({ ...opts, budgetTokens: opts.budget }));
|
|
55
|
-
cts
|
|
56
|
-
.command("plan-loop")
|
|
57
|
-
.description("Run the planner + outline review loop on the cached pack")
|
|
58
|
-
.option("--max-turns <n>", "Hard cap on review turns", (v) => parseInt(v, 10))
|
|
59
|
-
.option("--model <name>", "Override the model used by the planner and reviewer")
|
|
60
|
-
.action((opts) => planLoopCommand({
|
|
61
|
-
maxTurns: opts.maxTurns,
|
|
62
|
-
model: opts.model,
|
|
63
|
-
}));
|
|
64
|
-
cts
|
|
65
|
-
.command("specify-area <name>")
|
|
66
|
-
.description("Run the specifier for one named area from the cached outline")
|
|
67
|
-
.option("--model <name>", "Override the model used by the specifier")
|
|
68
|
-
.action((name, opts) => specifyAreaCommand(name, {
|
|
69
|
-
model: opts.model,
|
|
70
|
-
}));
|
|
71
|
-
cts
|
|
72
|
-
.command("fan-out")
|
|
73
|
-
.description("Run all specifiers in parallel for areas in the cached outline")
|
|
74
|
-
.option("-c, --concurrency <n>", "Max concurrent specifiers", (v) => parseInt(v, 10))
|
|
75
|
-
.option("--model <name>", "Override the model used by specifiers")
|
|
76
|
-
.action((opts) => fanOutCommand({
|
|
77
|
-
concurrency: opts.concurrency,
|
|
78
|
-
model: opts.model,
|
|
79
|
-
}));
|
|
80
47
|
cts
|
|
81
48
|
.command("validate <partial-path>")
|
|
82
49
|
.description("Validate the schema/syntax of a partial-spec file (deterministic, offline)")
|
|
@@ -86,69 +53,6 @@ export function registerCodebaseToSpec(program) {
|
|
|
86
53
|
.description("Run a local style-check on a partial-spec file (severity-categorized feedback)")
|
|
87
54
|
.option("--model <name>", "Override the model used for the check")
|
|
88
55
|
.action((partialPath, opts) => styleCheckCommand(partialPath, { model: opts.model }));
|
|
89
|
-
cts
|
|
90
|
-
.command("compose")
|
|
91
|
-
.description("Assemble per-area partials into a single composed spec")
|
|
92
|
-
.action(() => composeCommand());
|
|
93
|
-
cts
|
|
94
|
-
.command("edit-loop")
|
|
95
|
-
.description("Run the spec reviewer + editor loop on the composed spec")
|
|
96
|
-
.option("--max-turns <n>", "Hard cap on edit-loop turns", (v) => parseInt(v, 10))
|
|
97
|
-
.option("--model <name>", "Override the model used by the reviewer and editor")
|
|
98
|
-
.action((opts) => editLoopCommand({
|
|
99
|
-
maxTurns: opts.maxTurns,
|
|
100
|
-
model: opts.model,
|
|
101
|
-
}));
|
|
102
|
-
cts
|
|
103
|
-
.command("present")
|
|
104
|
-
.description("Write the final spec to .requirements/ and emit a pipeline summary")
|
|
105
|
-
.option("--interactive", "Force interactive mode (override TTY detection)")
|
|
106
|
-
.option("--non-interactive", "Force non-interactive mode (override TTY detection)")
|
|
107
|
-
.option("--overwrite", "Replace existing .requirements/ files without prompting")
|
|
108
|
-
.option("--skip-existing", "Leave existing .requirements/ files untouched")
|
|
109
|
-
.option("--ignore-requirements", "Write output to `.requirements/cts/` instead of `.requirements/` (use together with `cts pack --ignore-requirements` when isolating cts from an existing requirements directory)")
|
|
110
|
-
.action((opts) => {
|
|
111
|
-
const forceMode = opts.interactive
|
|
112
|
-
? "interactive"
|
|
113
|
-
: opts.nonInteractive
|
|
114
|
-
? "non-interactive"
|
|
115
|
-
: undefined;
|
|
116
|
-
return presentCommand({
|
|
117
|
-
forceMode,
|
|
118
|
-
overwrite: opts.overwrite,
|
|
119
|
-
skipExisting: opts.skipExisting,
|
|
120
|
-
ignoreRequirements: opts.ignoreRequirements,
|
|
121
|
-
});
|
|
122
|
-
});
|
|
123
|
-
cts
|
|
124
|
-
.command("run")
|
|
125
|
-
.description("Run the full pipeline (pack → plan → specify → compose → edit → present)")
|
|
126
|
-
.option("-s, --scope <path>", "Limit to files under this path (subdir of project root)")
|
|
127
|
-
.option("--fresh", "Clear the cache before running")
|
|
128
|
-
.option("--budget <tokens>", "Override the working-context budget (in tokens)", (v) => parseInt(v, 10))
|
|
129
|
-
.option("--max-turns <n>", "Hard cap on outline review turns", (v) => parseInt(v, 10))
|
|
130
|
-
.option("--model <name>", "Override the model used by agent calls")
|
|
131
|
-
.option("--interactive", "Force interactive mode for the present stage")
|
|
132
|
-
.option("--non-interactive", "Force non-interactive mode for the present stage")
|
|
133
|
-
.option("--overwrite", "Replace existing .requirements/ files without prompting")
|
|
134
|
-
.option("--skip-existing", "Leave existing .requirements/ files untouched")
|
|
135
|
-
.option("--ignore-requirements", "Exclude `.requirements/**` from the pack and write output to `.requirements/cts/` instead of `.requirements/` (use when testing cts against a codebase whose existing requirements should not influence the output)")
|
|
136
|
-
.action((opts) => {
|
|
137
|
-
const forceMode = opts.interactive
|
|
138
|
-
? "interactive"
|
|
139
|
-
: opts.nonInteractive
|
|
140
|
-
? "non-interactive"
|
|
141
|
-
: undefined;
|
|
142
|
-
return runCommand({
|
|
143
|
-
...opts,
|
|
144
|
-
budgetTokens: opts.budget,
|
|
145
|
-
maxTurns: opts.maxTurns,
|
|
146
|
-
forceMode,
|
|
147
|
-
overwrite: opts.overwrite,
|
|
148
|
-
skipExisting: opts.skipExisting,
|
|
149
|
-
ignoreRequirements: opts.ignoreRequirements,
|
|
150
|
-
});
|
|
151
|
-
});
|
|
152
56
|
cts
|
|
153
57
|
.command("dispatch-context <dispatch-id>", HIDDEN)
|
|
154
58
|
.description("Return the composed prompt for a worker dispatch as JSON on stdout (run by cts-worker subagents to fetch their dispatch prompt)")
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
*/
|
|
13
13
|
import { resolve } from "node:path";
|
|
14
14
|
import { checkBudget, countTokensInFileExact, DEFAULT_BUDGET_TOKENS, formatBudgetExceededMessage, } from "../../codebase-to-spec/budget.js";
|
|
15
|
-
import { clearCache, ensureCacheDir } from "../../codebase-to-spec/cache.js";
|
|
15
|
+
import { clearCache, ensureCacheDir, writeRunConfig, } from "../../codebase-to-spec/cache.js";
|
|
16
16
|
import { ExitCode } from "../../codebase-to-spec/exit-codes.js";
|
|
17
17
|
import { runPack } from "../../codebase-to-spec/pack.js";
|
|
18
18
|
import { StdoutProgress } from "../../codebase-to-spec/progress.js";
|
|
@@ -35,6 +35,13 @@ export async function packCommand(rawOptions = {}) {
|
|
|
35
35
|
clearCache(projectRoot);
|
|
36
36
|
}
|
|
37
37
|
const paths = ensureCacheDir(projectRoot);
|
|
38
|
+
// Record the isolation decision once, here, so later stages (present) honor
|
|
39
|
+
// it without the caller — or the orchestrating agent — having to carry it
|
|
40
|
+
// across the run. Written on every pack so a re-pack without the flag clears
|
|
41
|
+
// a stale isolated marker. See CTS-PRESENT-5.2.
|
|
42
|
+
writeRunConfig(projectRoot, {
|
|
43
|
+
isolated: rawOptions.ignoreRequirements === true,
|
|
44
|
+
});
|
|
38
45
|
const scope = rawOptions.scope
|
|
39
46
|
? resolve(projectRoot, rawOptions.scope)
|
|
40
47
|
: undefined;
|
|
@@ -8,13 +8,15 @@
|
|
|
8
8
|
* rather than specFinal.
|
|
9
9
|
*
|
|
10
10
|
* Requirements covered:
|
|
11
|
-
* - CTS-PRESENT-1..
|
|
11
|
+
* - CTS-PRESENT-1..3 (shared present logic in present.ts)
|
|
12
12
|
* - CTSO-INTEG-1: orchestrator uses existing deterministic stages unchanged
|
|
13
13
|
*/
|
|
14
14
|
export interface PresentOrchestratorOptions {
|
|
15
15
|
forceMode?: "interactive" | "non-interactive";
|
|
16
16
|
overwrite?: boolean;
|
|
17
17
|
skipExisting?: boolean;
|
|
18
|
+
/** Override the project root, mainly for tests. */
|
|
19
|
+
projectRoot?: string;
|
|
18
20
|
}
|
|
19
21
|
export declare function presentOrchestratorCommand(options?: PresentOrchestratorOptions): Promise<void>;
|
|
20
22
|
//# sourceMappingURL=present-orchestrator.d.ts.map
|
|
@@ -8,11 +8,11 @@
|
|
|
8
8
|
* rather than specFinal.
|
|
9
9
|
*
|
|
10
10
|
* Requirements covered:
|
|
11
|
-
* - CTS-PRESENT-1..
|
|
11
|
+
* - CTS-PRESENT-1..3 (shared present logic in present.ts)
|
|
12
12
|
* - CTSO-INTEG-1: orchestrator uses existing deterministic stages unchanged
|
|
13
13
|
*/
|
|
14
14
|
import { existsSync, readFileSync } from "node:fs";
|
|
15
|
-
import { cachePaths } from "../../codebase-to-spec/cache.js";
|
|
15
|
+
import { cachePaths, readRunConfig } from "../../codebase-to-spec/cache.js";
|
|
16
16
|
import { ExitCode } from "../../codebase-to-spec/exit-codes.js";
|
|
17
17
|
import { detectMode } from "../../codebase-to-spec/interactive.js";
|
|
18
18
|
import { runPresent, } from "../../codebase-to-spec/present.js";
|
|
@@ -31,7 +31,7 @@ function resolvePolicy(isInteractive, options) {
|
|
|
31
31
|
return "fail-fast";
|
|
32
32
|
}
|
|
33
33
|
export async function presentOrchestratorCommand(options = {}) {
|
|
34
|
-
const projectRoot = findProjectRoot(process.cwd()) ?? process.cwd();
|
|
34
|
+
const projectRoot = options.projectRoot ?? findProjectRoot(process.cwd()) ?? process.cwd();
|
|
35
35
|
const paths = cachePaths(projectRoot);
|
|
36
36
|
if (!existsSync(paths.outline)) {
|
|
37
37
|
process.stderr.write(`No outline.yaml found at ${paths.outline}. Run the orchestrator's planner first.\n`);
|
|
@@ -60,11 +60,17 @@ export async function presentOrchestratorCommand(options = {}) {
|
|
|
60
60
|
return;
|
|
61
61
|
}
|
|
62
62
|
const legacyOutline = conversationalOutlineToLegacy(outline);
|
|
63
|
+
// Honor the isolation decision recorded at pack time (CTS-PRESENT-5.2). When
|
|
64
|
+
// the run is isolated, write under `.requirements/cts/` so the existing
|
|
65
|
+
// requirements directory is never overwritten — the choice rides the cache,
|
|
66
|
+
// not the agent's memory, so it holds even across a resumed or compacted run.
|
|
67
|
+
const { isolated } = readRunConfig(projectRoot);
|
|
63
68
|
const result = await runPresent({
|
|
64
69
|
outline: legacyOutline,
|
|
65
70
|
finalSpecPath: paths.composedSpec,
|
|
66
71
|
projectRoot,
|
|
67
72
|
overwritePolicy: policy,
|
|
73
|
+
outputSubdir: isolated ? "cts" : undefined,
|
|
68
74
|
});
|
|
69
75
|
if (!result.allWritten) {
|
|
70
76
|
if (result.conflictPath) {
|
|
@@ -76,6 +82,7 @@ export async function presentOrchestratorCommand(options = {}) {
|
|
|
76
82
|
for (const action of result.actions) {
|
|
77
83
|
process.stdout.write(` ${action.action}: ${action.path}\n`);
|
|
78
84
|
}
|
|
79
|
-
|
|
85
|
+
const outputDirLabel = isolated ? ".requirements/cts/" : ".requirements/";
|
|
86
|
+
process.stdout.write(`Wrote ${result.actions.filter((a) => a.action === "created" || a.action === "overwrote").length} file(s) to ${outputDirLabel}\n`);
|
|
80
87
|
}
|
|
81
88
|
//# sourceMappingURL=present-orchestrator.js.map
|
|
@@ -19,7 +19,7 @@ Do **not** use for new-feature design (use the `dotreq-requirements` skill), bug
|
|
|
19
19
|
|
|
20
20
|
This skill runs its pipeline as a dynamic Workflow. If the `Workflow` tool is not available in this environment, **stop** and tell the user:
|
|
21
21
|
|
|
22
|
-
> "codebase-to-spec runs as a dynamic workflow, which this Claude Code version/configuration doesn't support.
|
|
22
|
+
> "codebase-to-spec runs as a dynamic workflow, which this Claude Code version/configuration doesn't support. Update to a build with dynamic-workflow support to run it."
|
|
23
23
|
|
|
24
24
|
Do not attempt a non-workflow fallback.
|
|
25
25
|
|
|
@@ -33,9 +33,15 @@ If the user gave a scope (a path), use it. Otherwise ask one concise question:
|
|
|
33
33
|
|
|
34
34
|
For large codebases, encourage scoping to a single area — the pack has a context budget.
|
|
35
35
|
|
|
36
|
+
**Isolation check.** Before packing, check whether the target project already has a dotrequirements spec (a `.requirements/` directory). If it does, the user is likely re-running CTS on an already-specced codebase — often a dogfood or a quality eval — and the present stage would otherwise write over that spec. Offer an isolated run:
|
|
37
|
+
|
|
38
|
+
> "This project already has a `.requirements/` spec. Want me to isolate this run — ignore the existing spec as input, and write my output to `.requirements/cts/` so I never touch what's there? Or proceed normally and treat the existing spec as part of the codebase?"
|
|
39
|
+
|
|
40
|
+
On accept, run pack with `--ignore-requirements` in step 2; the decision is recorded with the run, so present honors it automatically — you don't pass it again. On decline, or when there's no `.requirements/`, pack normally. (CTSO-CONV-9.)
|
|
41
|
+
|
|
36
42
|
### 2. Pack (deterministic)
|
|
37
43
|
|
|
38
|
-
Run `{{DOTREQ_CLI}} cts pack --scope <PATH>` via Bash. Deterministic, no LLM. It emits `[CTS] pack/done` then `pack/budget-ok`, or exits **10 (BudgetExceeded)** if the compressed pack is too large. On BudgetExceeded, surface the limit and offer a narrower scope (back to step 1).
|
|
44
|
+
Run `{{DOTREQ_CLI}} cts pack --scope <PATH>` via Bash — add `--ignore-requirements` if the user accepted isolation in step 1. Deterministic, no LLM. It emits `[CTS] pack/done` then `pack/budget-ok`, or exits **10 (BudgetExceeded)** if the compressed pack is too large. On BudgetExceeded, surface the limit and offer a narrower scope (back to step 1).
|
|
39
45
|
|
|
40
46
|
**Stale install gate.** If pack also emits a `pack/stale-version` line, this install of codebase-to-spec is out of date. Before launching the workflow, tell the user (naming both versions from the line) and offer to refresh:
|
|
41
47
|
|
|
@@ -66,7 +72,7 @@ The workflow returns one of:
|
|
|
66
72
|
### 5. Present (deterministic)
|
|
67
73
|
|
|
68
74
|
On `status: "done"`, the workflow has already composed the spec and run the cross-area pass. Write the final file(s):
|
|
69
|
-
- `{{DOTREQ_CLI}} cts present-orchestrator` — writes the composed spec to file(s) under `.requirements/`.
|
|
75
|
+
- `{{DOTREQ_CLI}} cts present-orchestrator` — writes the composed spec to file(s) under `.requirements/`. For an isolated run (step 1), it writes under `.requirements/cts/` instead — it reads the decision recorded at pack time, so you pass no flag here.
|
|
70
76
|
|
|
71
77
|
`present-orchestrator` errors on existing-file conflicts unless given `--overwrite` or `--skip-existing`. When a conflict is reported, ask the user which they want, then re-run with that flag.
|
|
72
78
|
|
|
@@ -104,4 +110,4 @@ If the user declines the team-review offer, note that it stands for later and do
|
|
|
104
110
|
|
|
105
111
|
## Host portability
|
|
106
112
|
|
|
107
|
-
This skill requires Claude Code with dynamic-workflow support (it launches the `specify-codebase` workflow).
|
|
113
|
+
This skill requires Claude Code with dynamic-workflow support (it launches the `specify-codebase` workflow). There is no non-workflow fallback; on hosts without dynamic-workflow support, codebase-to-spec is unavailable.
|
package/package.json
CHANGED
|
@@ -1,54 +0,0 @@
|
|
|
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
|
|
@@ -1,195 +0,0 @@
|
|
|
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
|
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Editor worker: revise the composed spec in place based on a reviewer's
|
|
3
|
-
* critique.
|
|
4
|
-
*
|
|
5
|
-
* Two modes:
|
|
6
|
-
* - apply: apply mechanical revisions verbatim (when verdict is `approved-with-revisions`)
|
|
7
|
-
* - revise: address the critique using the editor's own judgment (when verdict is `requires-another-review`)
|
|
8
|
-
*
|
|
9
|
-
* Requirements covered:
|
|
10
|
-
* - CTS-EDIT-3: approved-with-revisions triggers apply mode
|
|
11
|
-
* - CTS-EDIT-4: requires-another-review triggers revise mode
|
|
12
|
-
* - CTS-EDIT-5: editor operates on the cohesive document, reads codebase only on demand
|
|
13
|
-
*/
|
|
14
|
-
import { type ClaudeRunner } from "./claude.js";
|
|
15
|
-
import type { SpecReview } from "./schemas.js";
|
|
16
|
-
export type EditorMode = "apply" | "revise";
|
|
17
|
-
export interface EditorContext {
|
|
18
|
-
/**
|
|
19
|
-
* Input spec path. The editor reads from here.
|
|
20
|
-
* If `outputSpecPath` differs, the input is copied to the output path first
|
|
21
|
-
* (so the editor's in-place edits land on the output).
|
|
22
|
-
*/
|
|
23
|
-
inputSpecPath: string;
|
|
24
|
-
/** Output spec path (may equal inputSpecPath for true in-place editing). */
|
|
25
|
-
outputSpecPath: string;
|
|
26
|
-
/** Path to the codebase pack the editor may consult on demand. */
|
|
27
|
-
fullPackPath: string;
|
|
28
|
-
/** Directories to grant filesystem access to. */
|
|
29
|
-
addDirs: string[];
|
|
30
|
-
/** Reviewer's critique. */
|
|
31
|
-
critique: SpecReview;
|
|
32
|
-
/** Apply or revise. */
|
|
33
|
-
mode: EditorMode;
|
|
34
|
-
runner?: ClaudeRunner;
|
|
35
|
-
model?: string;
|
|
36
|
-
}
|
|
37
|
-
export interface EditorResult {
|
|
38
|
-
/** True if the output file exists and is non-empty after the editor ran. */
|
|
39
|
-
wroteSpec: boolean;
|
|
40
|
-
/** Size of the output in bytes (0 if missing). */
|
|
41
|
-
outputBytes: number;
|
|
42
|
-
/** Process exit code. */
|
|
43
|
-
exitCode: number;
|
|
44
|
-
/** Captured stdout (one-line confirmation). */
|
|
45
|
-
stdout: string;
|
|
46
|
-
/** Captured stderr. */
|
|
47
|
-
stderr: string;
|
|
48
|
-
}
|
|
49
|
-
/**
|
|
50
|
-
* Run the editor. If outputSpecPath differs from inputSpecPath, copy first
|
|
51
|
-
* so the in-place edits land on the output.
|
|
52
|
-
*/
|
|
53
|
-
export declare function runEditor(ctx: EditorContext): Promise<EditorResult>;
|
|
54
|
-
//# sourceMappingURL=editor.d.ts.map
|