@popoverai/dotrequirements 0.24.1 → 0.24.3
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 +1 -1
- package/dist/codebase-to-spec/cache.d.ts +6 -0
- package/dist/codebase-to-spec/cache.js +1 -0
- package/dist/codebase-to-spec/claude.d.ts +1 -0
- package/dist/codebase-to-spec/claude.js +9 -0
- package/dist/codebase-to-spec/dispatch.d.ts +69 -0
- package/dist/codebase-to-spec/dispatch.js +484 -0
- package/dist/codebase-to-spec/pack.d.ts +16 -0
- package/dist/codebase-to-spec/pack.js +17 -3
- package/dist/codebase-to-spec/present.d.ts +8 -1
- package/dist/codebase-to-spec/present.js +7 -4
- package/dist/codebase-to-spec/progress.d.ts +6 -0
- package/dist/codebase-to-spec/progress.js +34 -0
- package/dist/codebase-to-spec/prompts/outline-reviewer.d.ts +1 -1
- package/dist/codebase-to-spec/prompts/outline-reviewer.js +3 -1
- package/dist/codebase-to-spec/prompts/planner-initial.d.ts +1 -1
- package/dist/codebase-to-spec/prompts/planner-initial.js +4 -0
- package/dist/codebase-to-spec/prompts/planner-revise.d.ts +1 -1
- package/dist/codebase-to-spec/prompts/planner-revise.js +2 -2
- package/dist/codebase-to-spec/prompts/spec-reviewer.d.ts +1 -1
- package/dist/codebase-to-spec/prompts/spec-reviewer.js +6 -1
- package/dist/codebase-to-spec/prompts/specifier.d.ts +1 -1
- package/dist/codebase-to-spec/prompts/specifier.js +6 -4
- package/dist/codebase-to-spec/prompts/style-check.d.ts +10 -2
- package/dist/codebase-to-spec/prompts/style-check.js +76 -46
- package/dist/codebase-to-spec/schemas.d.ts +460 -1
- package/dist/codebase-to-spec/schemas.js +158 -1
- package/dist/codebase-to-spec/skill-install.d.ts +36 -12
- package/dist/codebase-to-spec/skill-install.js +127 -26
- package/dist/codebase-to-spec/specifier.js +6 -0
- package/dist/commands/codebase-to-spec/compose-orchestrator.d.ts +14 -0
- package/dist/commands/codebase-to-spec/compose-orchestrator.js +54 -0
- package/dist/commands/codebase-to-spec/dispatch-context.d.ts +12 -0
- package/dist/commands/codebase-to-spec/dispatch-context.js +22 -0
- package/dist/commands/codebase-to-spec/dispatch-editor.d.ts +16 -0
- package/dist/commands/codebase-to-spec/dispatch-editor.js +71 -0
- package/dist/commands/codebase-to-spec/dispatch-planner.d.ts +19 -0
- package/dist/commands/codebase-to-spec/dispatch-planner.js +90 -0
- package/dist/commands/codebase-to-spec/dispatch-spec.d.ts +16 -0
- package/dist/commands/codebase-to-spec/dispatch-spec.js +59 -0
- package/dist/commands/codebase-to-spec/index.js +69 -1
- package/dist/commands/codebase-to-spec/pack.d.ts +6 -0
- package/dist/commands/codebase-to-spec/pack.js +1 -0
- package/dist/commands/codebase-to-spec/present-orchestrator.d.ts +20 -0
- package/dist/commands/codebase-to-spec/present-orchestrator.js +81 -0
- package/dist/commands/codebase-to-spec/present.d.ts +5 -0
- package/dist/commands/codebase-to-spec/present.js +6 -1
- package/dist/commands/codebase-to-spec/run.js +1 -0
- package/dist/commands/codebase-to-spec/skill-install.js +12 -1
- package/dist/templates/agents/cts-worker.md +9 -0
- package/dist/templates/hooks/cts-worker-persona.sh +76 -0
- package/dist/templates/skills/codebase-to-spec/SKILL.md +159 -68
- package/package.json +4 -5
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `dotrequirements codebase-to-spec dispatch-planner [--revise]` subcommand.
|
|
3
|
+
*
|
|
4
|
+
* Initial mode: returns a dispatch payload for the first planner pass.
|
|
5
|
+
*
|
|
6
|
+
* --revise mode: reads the current `outline.yaml`, validates that its
|
|
7
|
+
* `review.result` is "needs-revision", and returns a payload for the next
|
|
8
|
+
* revise dispatch. The worker overwrites outline.yaml's content while
|
|
9
|
+
* preserving the review section.
|
|
10
|
+
*
|
|
11
|
+
* Requirements covered:
|
|
12
|
+
* - CTSO-CLI-1: CLI exposes commands that return dispatch instructions
|
|
13
|
+
* - CTSO-CONV-2: orchestrator iterates on planner's outline until approved
|
|
14
|
+
*/
|
|
15
|
+
export interface DispatchPlannerOptions {
|
|
16
|
+
revise?: boolean;
|
|
17
|
+
}
|
|
18
|
+
export declare function dispatchPlannerCommand(options?: DispatchPlannerOptions): Promise<void>;
|
|
19
|
+
//# sourceMappingURL=dispatch-planner.d.ts.map
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `dotrequirements codebase-to-spec dispatch-planner [--revise]` subcommand.
|
|
3
|
+
*
|
|
4
|
+
* Initial mode: returns a dispatch payload for the first planner pass.
|
|
5
|
+
*
|
|
6
|
+
* --revise mode: reads the current `outline.yaml`, validates that its
|
|
7
|
+
* `review.result` is "needs-revision", and returns a payload for the next
|
|
8
|
+
* revise dispatch. The worker overwrites outline.yaml's content while
|
|
9
|
+
* preserving the review section.
|
|
10
|
+
*
|
|
11
|
+
* Requirements covered:
|
|
12
|
+
* - CTSO-CLI-1: CLI exposes commands that return dispatch instructions
|
|
13
|
+
* - CTSO-CONV-2: orchestrator iterates on planner's outline until approved
|
|
14
|
+
*/
|
|
15
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
16
|
+
import { cachePaths } from "../../codebase-to-spec/cache.js";
|
|
17
|
+
import { PLANNER_INITIAL_DISPATCH_ID, PLANNER_REVISE_DISPATCH_ID_PREFIX, } from "../../codebase-to-spec/dispatch.js";
|
|
18
|
+
import { ExitCode } from "../../codebase-to-spec/exit-codes.js";
|
|
19
|
+
import { parseConversationalOutline, } from "../../codebase-to-spec/schemas.js";
|
|
20
|
+
import { findProjectRoot } from "../../utils/project-settings.js";
|
|
21
|
+
export async function dispatchPlannerCommand(options = {}) {
|
|
22
|
+
const projectRoot = findProjectRoot(process.cwd()) ?? process.cwd();
|
|
23
|
+
const paths = cachePaths(projectRoot);
|
|
24
|
+
if (options.revise) {
|
|
25
|
+
return dispatchPlannerRevise(paths);
|
|
26
|
+
}
|
|
27
|
+
return dispatchPlannerInitial(paths);
|
|
28
|
+
}
|
|
29
|
+
function dispatchPlannerInitial(paths) {
|
|
30
|
+
if (!existsSync(paths.overview)) {
|
|
31
|
+
process.stderr.write(`Compressed pack not found at ${paths.overview}. Run \`dotrequirements cts pack\` first.\n`);
|
|
32
|
+
process.exitCode = ExitCode.MissingInput;
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
const payload = {
|
|
36
|
+
dispatch_id: PLANNER_INITIAL_DISPATCH_ID,
|
|
37
|
+
output_path: paths.outline,
|
|
38
|
+
};
|
|
39
|
+
process.stdout.write(`${JSON.stringify(payload)}\n`);
|
|
40
|
+
}
|
|
41
|
+
function dispatchPlannerRevise(paths) {
|
|
42
|
+
if (!existsSync(paths.outline)) {
|
|
43
|
+
process.stderr.write(`No outline.yaml found at ${paths.outline}. Dispatch the initial planner first.\n`);
|
|
44
|
+
process.exitCode = ExitCode.MissingInput;
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
// Parse and validate the current outline. Surface clear errors rather
|
|
48
|
+
// than letting a malformed file silently produce a bad revise dispatch.
|
|
49
|
+
let outline;
|
|
50
|
+
try {
|
|
51
|
+
outline = parseConversationalOutline(readFileSync(paths.outline, "utf-8"));
|
|
52
|
+
}
|
|
53
|
+
catch (err) {
|
|
54
|
+
process.stderr.write(`Outline at ${paths.outline} is invalid: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
55
|
+
process.exitCode = ExitCode.MissingInput;
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
// The orchestrator should only call --revise when the outline has been
|
|
59
|
+
// reviewed and the result is "needs-revision". Other states are user
|
|
60
|
+
// errors worth surfacing clearly.
|
|
61
|
+
if (!outline.review) {
|
|
62
|
+
process.stderr.write(`Outline at ${paths.outline} has no \`review\` section. Add CA's review (with result and thread) before dispatching revise.\n`);
|
|
63
|
+
process.exitCode = ExitCode.MissingInput;
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
if (outline.review.result === "approved") {
|
|
67
|
+
process.stderr.write(`Outline at ${paths.outline} has review.result "approved" — nothing to revise. Proceed to fan-out.\n`);
|
|
68
|
+
process.exitCode = ExitCode.MissingInput;
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
// Latest thread entry must be needs-revision for there to be revisions
|
|
72
|
+
// to act on. Schema's discriminated union enforces revisions ≥ 1 when
|
|
73
|
+
// the entry is needs-revision.
|
|
74
|
+
const latestEntry = outline.review.thread[outline.review.thread.length - 1];
|
|
75
|
+
if (!latestEntry || latestEntry.result !== "needs-revision") {
|
|
76
|
+
process.stderr.write(`Outline at ${paths.outline} has review.result "needs-revision" but the latest thread entry isn't a needs-revision entry. The review thread is malformed.\n`);
|
|
77
|
+
process.exitCode = ExitCode.MissingInput;
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
// Next turn = current thread length + 1 (the round being PRODUCED by
|
|
81
|
+
// this revise dispatch; CA's review of THAT output will append a new
|
|
82
|
+
// thread entry of length+1 after this dispatch completes).
|
|
83
|
+
const nextTurn = outline.review.thread.length + 1;
|
|
84
|
+
const payload = {
|
|
85
|
+
dispatch_id: `${PLANNER_REVISE_DISPATCH_ID_PREFIX}${nextTurn}`,
|
|
86
|
+
output_path: paths.outline,
|
|
87
|
+
};
|
|
88
|
+
process.stdout.write(`${JSON.stringify(payload)}\n`);
|
|
89
|
+
}
|
|
90
|
+
//# sourceMappingURL=dispatch-planner.js.map
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `dotrequirements codebase-to-spec dispatch-spec` subcommand.
|
|
3
|
+
*
|
|
4
|
+
* Returns an array of specifier dispatch payloads — one per area in the
|
|
5
|
+
* approved outline. The conversational orchestrator reads this, fires N
|
|
6
|
+
* background Task dispatches in parallel (one cts-worker per area), and
|
|
7
|
+
* collects the resulting partials.
|
|
8
|
+
*
|
|
9
|
+
* Errors clearly if `outline.yaml`'s `review.result` isn't `"approved"` —
|
|
10
|
+
* the orchestrator should approve the outline before fan-out.
|
|
11
|
+
*
|
|
12
|
+
* Requirements covered:
|
|
13
|
+
* - CTSO-CLI-1: CLI exposes commands that return dispatch instructions
|
|
14
|
+
*/
|
|
15
|
+
export declare function dispatchSpecCommand(): Promise<void>;
|
|
16
|
+
//# sourceMappingURL=dispatch-spec.d.ts.map
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `dotrequirements codebase-to-spec dispatch-spec` subcommand.
|
|
3
|
+
*
|
|
4
|
+
* Returns an array of specifier dispatch payloads — one per area in the
|
|
5
|
+
* approved outline. The conversational orchestrator reads this, fires N
|
|
6
|
+
* background Task dispatches in parallel (one cts-worker per area), and
|
|
7
|
+
* collects the resulting partials.
|
|
8
|
+
*
|
|
9
|
+
* Errors clearly if `outline.yaml`'s `review.result` isn't `"approved"` —
|
|
10
|
+
* the orchestrator should approve the outline before fan-out.
|
|
11
|
+
*
|
|
12
|
+
* Requirements covered:
|
|
13
|
+
* - CTSO-CLI-1: CLI exposes commands that return dispatch instructions
|
|
14
|
+
*/
|
|
15
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
16
|
+
import { cachePaths } from "../../codebase-to-spec/cache.js";
|
|
17
|
+
import { SPECIFIER_DISPATCH_ID_PREFIX } from "../../codebase-to-spec/dispatch.js";
|
|
18
|
+
import { ExitCode } from "../../codebase-to-spec/exit-codes.js";
|
|
19
|
+
import { sanitizeAreaName } from "../../codebase-to-spec/fan-out.js";
|
|
20
|
+
import { parseConversationalOutline, } from "../../codebase-to-spec/schemas.js";
|
|
21
|
+
import { findProjectRoot } from "../../utils/project-settings.js";
|
|
22
|
+
export async function dispatchSpecCommand() {
|
|
23
|
+
const projectRoot = findProjectRoot(process.cwd()) ?? process.cwd();
|
|
24
|
+
const paths = cachePaths(projectRoot);
|
|
25
|
+
if (!existsSync(paths.outline)) {
|
|
26
|
+
process.stderr.write(`No outline.yaml found at ${paths.outline}. Dispatch the planner first.\n`);
|
|
27
|
+
process.exitCode = ExitCode.MissingInput;
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
let outline;
|
|
31
|
+
try {
|
|
32
|
+
outline = parseConversationalOutline(readFileSync(paths.outline, "utf-8"));
|
|
33
|
+
}
|
|
34
|
+
catch (err) {
|
|
35
|
+
process.stderr.write(`Outline at ${paths.outline} is invalid: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
36
|
+
process.exitCode = ExitCode.MissingInput;
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
if (!outline.review) {
|
|
40
|
+
process.stderr.write(`Outline at ${paths.outline} has no \`review\` section. Add CA's review with result=approved before dispatching specifiers.\n`);
|
|
41
|
+
process.exitCode = ExitCode.MissingInput;
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
if (outline.review.result !== "approved") {
|
|
45
|
+
process.stderr.write(`Outline at ${paths.outline} has review.result "${outline.review.result}", not "approved". Approve the outline before dispatching specifiers.\n`);
|
|
46
|
+
process.exitCode = ExitCode.MissingInput;
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
// For each area, produce a dispatch payload. The skill body fires N
|
|
50
|
+
// background Task calls in parallel using these payloads.
|
|
51
|
+
const dispatches = outline.areas.map((area) => ({
|
|
52
|
+
dispatch_id: `${SPECIFIER_DISPATCH_ID_PREFIX}${area.prefix}`,
|
|
53
|
+
output_path: paths.partial(sanitizeAreaName(area.name)),
|
|
54
|
+
area_name: area.name,
|
|
55
|
+
area_prefix: area.prefix,
|
|
56
|
+
}));
|
|
57
|
+
process.stdout.write(`${JSON.stringify(dispatches)}\n`);
|
|
58
|
+
}
|
|
59
|
+
//# sourceMappingURL=dispatch-spec.js.map
|
|
@@ -4,28 +4,52 @@
|
|
|
4
4
|
* Subcommands route to focused handlers in this directory. Each subcommand can
|
|
5
5
|
* be invoked granularly (e.g., `dotrequirements cts pack`) per CTS-CLI-2.
|
|
6
6
|
*/
|
|
7
|
+
import { installCtsAbortHandlers } from "../../codebase-to-spec/progress.js";
|
|
7
8
|
import { composeCommand } from "./compose.js";
|
|
9
|
+
import { composeOrchestratorCommand } from "./compose-orchestrator.js";
|
|
10
|
+
import { dispatchContextCommand } from "./dispatch-context.js";
|
|
11
|
+
import { dispatchEditorCommand } from "./dispatch-editor.js";
|
|
12
|
+
import { dispatchPlannerCommand } from "./dispatch-planner.js";
|
|
13
|
+
import { dispatchSpecCommand } from "./dispatch-spec.js";
|
|
8
14
|
import { editLoopCommand } from "./edit-loop.js";
|
|
9
15
|
import { fanOutCommand } from "./fan-out.js";
|
|
10
16
|
import { packCommand } from "./pack.js";
|
|
11
17
|
import { planLoopCommand } from "./plan-loop.js";
|
|
12
18
|
import { presentCommand } from "./present.js";
|
|
19
|
+
import { presentOrchestratorCommand } from "./present-orchestrator.js";
|
|
13
20
|
import { runCommand } from "./run.js";
|
|
14
21
|
import { skillInstallCommand } from "./skill-install.js";
|
|
15
22
|
import { specifyAreaCommand } from "./specify-area.js";
|
|
16
23
|
import { styleCheckCommand } from "./style-check.js";
|
|
17
24
|
import { validateCommand } from "./validate.js";
|
|
25
|
+
/**
|
|
26
|
+
* Marker used to hide a command from `--help` output without removing it.
|
|
27
|
+
* Used for dark-ship: the conversational orchestrator's plumbing commands and
|
|
28
|
+
* the skill-install entry point ship in the npm package but aren't surfaced
|
|
29
|
+
* in help listings or documentation. They remain fully invocable (the
|
|
30
|
+
* orchestrator skill calls them by name) — they just don't pollute the
|
|
31
|
+
* user-facing CLI surface until we flip the default path in Phase 6.
|
|
32
|
+
*/
|
|
33
|
+
const HIDDEN = { hidden: true };
|
|
18
34
|
export function registerCodebaseToSpec(program) {
|
|
19
35
|
const cts = program
|
|
20
36
|
.command("codebase-to-spec")
|
|
21
37
|
.alias("cts")
|
|
22
38
|
.description("Generate dotrequirements behavioral specifications from a codebase");
|
|
39
|
+
// Install SIGTERM/SIGINT handlers for every cts subcommand so an interrupted
|
|
40
|
+
// pipeline emits a structured `pipeline/aborted` line before exiting
|
|
41
|
+
// (CTS-OBSERVE-1.5). preAction fires after argv parsing and before the
|
|
42
|
+
// subcommand action runs.
|
|
43
|
+
cts.hook("preAction", () => {
|
|
44
|
+
installCtsAbortHandlers();
|
|
45
|
+
});
|
|
23
46
|
cts
|
|
24
47
|
.command("pack")
|
|
25
48
|
.description("Pack the codebase (compressed and uncompressed views) into the cache")
|
|
26
49
|
.option("-s, --scope <path>", "Limit to files under this path (subdir of project root)")
|
|
27
50
|
.option("--fresh", "Clear the cache before running")
|
|
28
51
|
.option("--budget <tokens>", "Override the working-context budget (in tokens)", (v) => parseInt(v, 10))
|
|
52
|
+
.option("--ignore-requirements", "Exclude `.requirements/**` from the pack (use when testing cts against a codebase whose existing requirements should not influence the output)")
|
|
29
53
|
.action((opts) => packCommand({ ...opts, budgetTokens: opts.budget }));
|
|
30
54
|
cts
|
|
31
55
|
.command("plan-loop")
|
|
@@ -81,6 +105,7 @@ export function registerCodebaseToSpec(program) {
|
|
|
81
105
|
.option("--non-interactive", "Force non-interactive mode (override TTY detection)")
|
|
82
106
|
.option("--overwrite", "Replace existing .requirements/ files without prompting")
|
|
83
107
|
.option("--skip-existing", "Leave existing .requirements/ files untouched")
|
|
108
|
+
.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)")
|
|
84
109
|
.action((opts) => {
|
|
85
110
|
const forceMode = opts.interactive
|
|
86
111
|
? "interactive"
|
|
@@ -91,6 +116,7 @@ export function registerCodebaseToSpec(program) {
|
|
|
91
116
|
forceMode,
|
|
92
117
|
overwrite: opts.overwrite,
|
|
93
118
|
skipExisting: opts.skipExisting,
|
|
119
|
+
ignoreRequirements: opts.ignoreRequirements,
|
|
94
120
|
});
|
|
95
121
|
});
|
|
96
122
|
cts
|
|
@@ -105,6 +131,7 @@ export function registerCodebaseToSpec(program) {
|
|
|
105
131
|
.option("--non-interactive", "Force non-interactive mode for the present stage")
|
|
106
132
|
.option("--overwrite", "Replace existing .requirements/ files without prompting")
|
|
107
133
|
.option("--skip-existing", "Leave existing .requirements/ files untouched")
|
|
134
|
+
.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)")
|
|
108
135
|
.action((opts) => {
|
|
109
136
|
const forceMode = opts.interactive
|
|
110
137
|
? "interactive"
|
|
@@ -118,10 +145,51 @@ export function registerCodebaseToSpec(program) {
|
|
|
118
145
|
forceMode,
|
|
119
146
|
overwrite: opts.overwrite,
|
|
120
147
|
skipExisting: opts.skipExisting,
|
|
148
|
+
ignoreRequirements: opts.ignoreRequirements,
|
|
149
|
+
});
|
|
150
|
+
});
|
|
151
|
+
cts
|
|
152
|
+
.command("dispatch-context <dispatch-id>", HIDDEN)
|
|
153
|
+
.description("Return the composed prompt for a worker dispatch as JSON on stdout (called by the cts-worker PreToolUse hook)")
|
|
154
|
+
.action((dispatchId) => dispatchContextCommand(dispatchId));
|
|
155
|
+
cts
|
|
156
|
+
.command("dispatch-planner", HIDDEN)
|
|
157
|
+
.description("Return a dispatch payload for a planner pass (initial, or --revise to scaffold the next turn from the latest cached outline + critique)")
|
|
158
|
+
.option("--revise", "Scaffold a revise dispatch using the latest cached outline + sibling critique file")
|
|
159
|
+
.action((opts) => dispatchPlannerCommand({ revise: opts.revise }));
|
|
160
|
+
cts
|
|
161
|
+
.command("dispatch-spec", HIDDEN)
|
|
162
|
+
.description("Return an array of specifier dispatch payloads — one per area in the approved outline (used by the conversational orchestrator skill to fan out N specifiers in parallel)")
|
|
163
|
+
.action(() => dispatchSpecCommand());
|
|
164
|
+
cts
|
|
165
|
+
.command("dispatch-editor <area-prefix>", HIDDEN)
|
|
166
|
+
.description("Return an editor dispatch payload for one area whose review is needs-revision (used by the conversational orchestrator skill to drive per-area convergence)")
|
|
167
|
+
.action((areaPrefix) => dispatchEditorCommand(areaPrefix));
|
|
168
|
+
cts
|
|
169
|
+
.command("compose-orchestrator", HIDDEN)
|
|
170
|
+
.description("Compose-orchestrator variant: reads outline.yaml (approved) + partials/, writes composedSpec")
|
|
171
|
+
.action(() => composeOrchestratorCommand());
|
|
172
|
+
cts
|
|
173
|
+
.command("present-orchestrator", HIDDEN)
|
|
174
|
+
.description("Present-orchestrator variant: reads outline.yaml + composedSpec, writes final files to .requirements/")
|
|
175
|
+
.option("--interactive", "Force interactive mode (override TTY detection)")
|
|
176
|
+
.option("--non-interactive", "Force non-interactive mode (override TTY detection)")
|
|
177
|
+
.option("--overwrite", "Replace existing .requirements/ files without prompting")
|
|
178
|
+
.option("--skip-existing", "Leave existing .requirements/ files untouched")
|
|
179
|
+
.action((opts) => {
|
|
180
|
+
const forceMode = opts.interactive
|
|
181
|
+
? "interactive"
|
|
182
|
+
: opts.nonInteractive
|
|
183
|
+
? "non-interactive"
|
|
184
|
+
: undefined;
|
|
185
|
+
return presentOrchestratorCommand({
|
|
186
|
+
forceMode,
|
|
187
|
+
overwrite: opts.overwrite,
|
|
188
|
+
skipExisting: opts.skipExisting,
|
|
121
189
|
});
|
|
122
190
|
});
|
|
123
191
|
cts
|
|
124
|
-
.command("skill-install")
|
|
192
|
+
.command("skill-install", HIDDEN)
|
|
125
193
|
.description("Install the codebase-to-spec skill (SKILL.md) into the host's skills directory")
|
|
126
194
|
.option("--global", "Install to ~/.claude/skills/ instead of the project")
|
|
127
195
|
.option("--target-dir <path>", "Install into a custom skills directory (for non-Claude-Code hosts)")
|
|
@@ -13,6 +13,12 @@ export interface PackOptions {
|
|
|
13
13
|
scope?: string;
|
|
14
14
|
fresh?: boolean;
|
|
15
15
|
budgetTokens?: number;
|
|
16
|
+
/**
|
|
17
|
+
* When true, exclude `.requirements/**` from the pack so an existing
|
|
18
|
+
* requirements directory does not influence the generated output.
|
|
19
|
+
* See CTS-PRESENT-5.
|
|
20
|
+
*/
|
|
21
|
+
ignoreRequirements?: boolean;
|
|
16
22
|
/** Override progress emitter, mainly for tests. */
|
|
17
23
|
progress?: import("../../codebase-to-spec/progress.js").ProgressEmitter;
|
|
18
24
|
/** Override the project root, mainly for tests. */
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `dotrequirements codebase-to-spec present-orchestrator` subcommand.
|
|
3
|
+
*
|
|
4
|
+
* Conversational-orchestrator variant of `present`. Reads outline.yaml +
|
|
5
|
+
* composedSpec (produced by compose-orchestrator), writes the final spec
|
|
6
|
+
* to `.requirements/`. The orchestrator doesn't have a separate
|
|
7
|
+
* document-level edit-loop, so this reads from composedSpec directly
|
|
8
|
+
* rather than specFinal.
|
|
9
|
+
*
|
|
10
|
+
* Requirements covered:
|
|
11
|
+
* - CTS-PRESENT-1..4 (reuses the legacy present logic)
|
|
12
|
+
* - CTSO-INTEG-1: orchestrator uses existing deterministic stages unchanged
|
|
13
|
+
*/
|
|
14
|
+
export interface PresentOrchestratorOptions {
|
|
15
|
+
forceMode?: "interactive" | "non-interactive";
|
|
16
|
+
overwrite?: boolean;
|
|
17
|
+
skipExisting?: boolean;
|
|
18
|
+
}
|
|
19
|
+
export declare function presentOrchestratorCommand(options?: PresentOrchestratorOptions): Promise<void>;
|
|
20
|
+
//# sourceMappingURL=present-orchestrator.d.ts.map
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `dotrequirements codebase-to-spec present-orchestrator` subcommand.
|
|
3
|
+
*
|
|
4
|
+
* Conversational-orchestrator variant of `present`. Reads outline.yaml +
|
|
5
|
+
* composedSpec (produced by compose-orchestrator), writes the final spec
|
|
6
|
+
* to `.requirements/`. The orchestrator doesn't have a separate
|
|
7
|
+
* document-level edit-loop, so this reads from composedSpec directly
|
|
8
|
+
* rather than specFinal.
|
|
9
|
+
*
|
|
10
|
+
* Requirements covered:
|
|
11
|
+
* - CTS-PRESENT-1..4 (reuses the legacy present logic)
|
|
12
|
+
* - CTSO-INTEG-1: orchestrator uses existing deterministic stages unchanged
|
|
13
|
+
*/
|
|
14
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
15
|
+
import { cachePaths } from "../../codebase-to-spec/cache.js";
|
|
16
|
+
import { ExitCode } from "../../codebase-to-spec/exit-codes.js";
|
|
17
|
+
import { detectMode } from "../../codebase-to-spec/interactive.js";
|
|
18
|
+
import { runPresent, } from "../../codebase-to-spec/present.js";
|
|
19
|
+
import { conversationalOutlineToLegacy, parseConversationalOutline, } from "../../codebase-to-spec/schemas.js";
|
|
20
|
+
import { findProjectRoot } from "../../utils/project-settings.js";
|
|
21
|
+
function resolvePolicy(isInteractive, options) {
|
|
22
|
+
if (options.overwrite && options.skipExisting) {
|
|
23
|
+
return { error: "--overwrite and --skip-existing are mutually exclusive." };
|
|
24
|
+
}
|
|
25
|
+
if (options.overwrite)
|
|
26
|
+
return "overwrite";
|
|
27
|
+
if (options.skipExisting)
|
|
28
|
+
return "skip-existing";
|
|
29
|
+
if (isInteractive)
|
|
30
|
+
return "prompt";
|
|
31
|
+
return "fail-fast";
|
|
32
|
+
}
|
|
33
|
+
export async function presentOrchestratorCommand(options = {}) {
|
|
34
|
+
const projectRoot = findProjectRoot(process.cwd()) ?? process.cwd();
|
|
35
|
+
const paths = cachePaths(projectRoot);
|
|
36
|
+
if (!existsSync(paths.outline)) {
|
|
37
|
+
process.stderr.write(`No outline.yaml found at ${paths.outline}. Run the orchestrator's planner first.\n`);
|
|
38
|
+
process.exitCode = ExitCode.MissingInput;
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
if (!existsSync(paths.composedSpec)) {
|
|
42
|
+
process.stderr.write(`No composed spec at ${paths.composedSpec}. Run \`cts compose-orchestrator\` first.\n`);
|
|
43
|
+
process.exitCode = ExitCode.MissingInput;
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
let outline;
|
|
47
|
+
try {
|
|
48
|
+
outline = parseConversationalOutline(readFileSync(paths.outline, "utf-8"));
|
|
49
|
+
}
|
|
50
|
+
catch (err) {
|
|
51
|
+
process.stderr.write(`Outline at ${paths.outline} is invalid: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
52
|
+
process.exitCode = ExitCode.MissingInput;
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
const { isInteractive } = detectMode(options.forceMode);
|
|
56
|
+
const policy = resolvePolicy(isInteractive, options);
|
|
57
|
+
if (typeof policy === "object") {
|
|
58
|
+
process.stderr.write(`${policy.error}\n`);
|
|
59
|
+
process.exitCode = ExitCode.InvalidFlags;
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
const legacyOutline = conversationalOutlineToLegacy(outline);
|
|
63
|
+
const result = await runPresent({
|
|
64
|
+
outline: legacyOutline,
|
|
65
|
+
finalSpecPath: paths.composedSpec,
|
|
66
|
+
projectRoot,
|
|
67
|
+
overwritePolicy: policy,
|
|
68
|
+
});
|
|
69
|
+
if (!result.allWritten) {
|
|
70
|
+
if (result.conflictPath) {
|
|
71
|
+
process.stderr.write(`Refusing to overwrite ${result.conflictPath}. Pass --overwrite or --skip-existing.\n`);
|
|
72
|
+
}
|
|
73
|
+
process.exitCode = ExitCode.OverwriteRefused;
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
for (const action of result.actions) {
|
|
77
|
+
process.stdout.write(` ${action.action}: ${action.path}\n`);
|
|
78
|
+
}
|
|
79
|
+
process.stdout.write(`Wrote ${result.actions.filter((a) => a.action === "created" || a.action === "overwrote").length} file(s) to .requirements/\n`);
|
|
80
|
+
}
|
|
81
|
+
//# sourceMappingURL=present-orchestrator.js.map
|
|
@@ -15,6 +15,11 @@ export interface PresentCmdOptions {
|
|
|
15
15
|
overwrite?: boolean;
|
|
16
16
|
/** Skip writing files that would conflict (non-interactive). */
|
|
17
17
|
skipExisting?: boolean;
|
|
18
|
+
/**
|
|
19
|
+
* When true, write output under `.requirements/cts/` instead of
|
|
20
|
+
* `.requirements/`. See CTS-PRESENT-5.
|
|
21
|
+
*/
|
|
22
|
+
ignoreRequirements?: boolean;
|
|
18
23
|
projectRoot?: string;
|
|
19
24
|
}
|
|
20
25
|
export declare function presentCommand(rawOptions?: PresentCmdOptions): Promise<void>;
|
|
@@ -51,16 +51,21 @@ export async function presentCommand(rawOptions = {}) {
|
|
|
51
51
|
process.exitCode = ExitCode.InvalidFlags;
|
|
52
52
|
return;
|
|
53
53
|
}
|
|
54
|
+
const outputSubdir = rawOptions.ignoreRequirements ? "cts" : undefined;
|
|
55
|
+
const outputDirLabel = outputSubdir
|
|
56
|
+
? `.requirements/${outputSubdir}/`
|
|
57
|
+
: ".requirements/";
|
|
54
58
|
progress.emit({
|
|
55
59
|
stage: "present",
|
|
56
60
|
step: "start",
|
|
57
|
-
message: `Writing final spec to
|
|
61
|
+
message: `Writing final spec to ${outputDirLabel} (mode: ${isInteractive ? "interactive" : "non-interactive"}, policy: ${policy})`,
|
|
58
62
|
});
|
|
59
63
|
const result = await runPresent({
|
|
60
64
|
outline,
|
|
61
65
|
finalSpecPath: paths.specFinal,
|
|
62
66
|
projectRoot,
|
|
63
67
|
overwritePolicy: policy,
|
|
68
|
+
outputSubdir,
|
|
64
69
|
});
|
|
65
70
|
// Per-file outcome announcements.
|
|
66
71
|
for (const action of result.actions) {
|
|
@@ -77,6 +77,7 @@ export async function runCommand(options = {}) {
|
|
|
77
77
|
forceMode: options.forceMode,
|
|
78
78
|
overwrite: options.overwrite,
|
|
79
79
|
skipExisting: options.skipExisting,
|
|
80
|
+
ignoreRequirements: options.ignoreRequirements,
|
|
80
81
|
projectRoot: options.projectRoot,
|
|
81
82
|
});
|
|
82
83
|
// Present sets a non-zero exit code on overwrite refusal or validation
|
|
@@ -41,7 +41,18 @@ export async function skillInstallCommand(options = {}) {
|
|
|
41
41
|
else {
|
|
42
42
|
process.stdout.write(`SKILL.md already up to date at ${result.installedPath}\n`);
|
|
43
43
|
}
|
|
44
|
-
|
|
44
|
+
if (result.companions) {
|
|
45
|
+
process.stdout.write(`Installed cts-worker agent at ${result.companions.agentPath}\n`);
|
|
46
|
+
process.stdout.write(`Installed persona-injection hook at ${result.companions.hookScriptPath}\n`);
|
|
47
|
+
if (result.companions.hookRegistered) {
|
|
48
|
+
process.stdout.write(`Registered PreToolUse hook in ${result.companions.settingsPath}\n`);
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
process.stdout.write(`PreToolUse hook already registered in ${result.companions.settingsPath}\n`);
|
|
52
|
+
}
|
|
53
|
+
process.stdout.write("\n⚠️ The hook script uses `dotrequirements` from PATH. If you're working in a local dev clone, set DOTREQUIREMENTS_CLI to override (see the hook script for details).\n");
|
|
54
|
+
}
|
|
55
|
+
process.stdout.write("\nSkill ready. In Claude Code, invoke it with `/codebase-to-spec` (or via natural language).\n");
|
|
45
56
|
}
|
|
46
57
|
catch (err) {
|
|
47
58
|
process.stderr.write(`${err instanceof Error ? err.message : String(err)}\n`);
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: cts-worker
|
|
3
|
+
description: Generic worker subagent for the codebase-to-spec conversational orchestrator. Used for planner, specifier, editor, and reviewer dispatches. The per-dispatch persona is injected by the cts-worker-persona PreToolUse hook at dispatch time.
|
|
4
|
+
tools: Bash, Read, Edit, Write, Glob, Grep
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
You are a codebase-to-spec worker. Your dispatched prompt is composed externally and supplied to you at dispatch time. It will specify your role for this dispatch (planner, specifier, editor, or reviewer), the artifacts you're working with, and the output convention you must follow.
|
|
8
|
+
|
|
9
|
+
Follow the dispatched prompt exactly. Do not improvise behavior beyond what it asks. When your work is done, submit via the convention specified in the dispatch (typically a `dotrequirements cts submit` call).
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# PreToolUse hook for Task(subagent_type=cts-worker) dispatches.
|
|
3
|
+
# Composes the worker's first-turn prompt by calling
|
|
4
|
+
# `dotrequirements cts dispatch-context <dispatch-id>` and emits the
|
|
5
|
+
# result via `updatedInput.prompt`.
|
|
6
|
+
#
|
|
7
|
+
# The orchestrator's convention: the original Task prompt MUST include
|
|
8
|
+
# the verbatim token `dispatch-id=<id>` so the hook can extract it.
|
|
9
|
+
#
|
|
10
|
+
# Bundled with the codebase-to-spec skill (CTSO-CLI-1).
|
|
11
|
+
#
|
|
12
|
+
# CLI resolution order:
|
|
13
|
+
# 1. DOTREQUIREMENTS_CLI env var (explicit override)
|
|
14
|
+
# 2. If running inside the dotrequirements monorepo itself
|
|
15
|
+
# ($CLAUDE_PROJECT_DIR/packages/cli/dist/cli.js exists), use that
|
|
16
|
+
# local build. Lets dotrequirements developers use the orchestrator
|
|
17
|
+
# against any worktree without a global install.
|
|
18
|
+
# 3. `dotrequirements` from PATH (the canonical end-user setup).
|
|
19
|
+
|
|
20
|
+
INPUT=$(cat)
|
|
21
|
+
SUBAGENT_TYPE=$(echo "$INPUT" | jq -r '.tool_input.subagent_type // ""')
|
|
22
|
+
|
|
23
|
+
# Self-filter: only act on cts-worker dispatches.
|
|
24
|
+
if [[ "$SUBAGENT_TYPE" != "cts-worker" ]]; then
|
|
25
|
+
exit 0
|
|
26
|
+
fi
|
|
27
|
+
|
|
28
|
+
ORIGINAL_PROMPT=$(echo "$INPUT" | jq -r '.tool_input.prompt // ""')
|
|
29
|
+
|
|
30
|
+
# Extract dispatch-id token from the original prompt.
|
|
31
|
+
DISPATCH_ID=$(echo "$ORIGINAL_PROMPT" | grep -oE 'dispatch-id=[a-zA-Z0-9_-]+' | head -1 | sed 's/dispatch-id=//')
|
|
32
|
+
|
|
33
|
+
if [[ -z "$DISPATCH_ID" ]]; then
|
|
34
|
+
# No dispatch-id in the prompt — let it through unmodified.
|
|
35
|
+
exit 0
|
|
36
|
+
fi
|
|
37
|
+
|
|
38
|
+
# Resolve the CLI invocation (see header for order).
|
|
39
|
+
if [[ -n "${DOTREQUIREMENTS_CLI:-}" ]]; then
|
|
40
|
+
CLI="$DOTREQUIREMENTS_CLI"
|
|
41
|
+
elif [[ -n "${CLAUDE_PROJECT_DIR:-}" && -f "$CLAUDE_PROJECT_DIR/packages/cli/dist/cli.js" ]]; then
|
|
42
|
+
CLI="node $CLAUDE_PROJECT_DIR/packages/cli/dist/cli.js"
|
|
43
|
+
else
|
|
44
|
+
CLI="dotrequirements"
|
|
45
|
+
fi
|
|
46
|
+
|
|
47
|
+
# Compose the dispatch context. CLI returns JSON like {"prompt": "..."}.
|
|
48
|
+
COMPOSED=$($CLI cts dispatch-context "$DISPATCH_ID" 2>/dev/null)
|
|
49
|
+
COMPOSE_STATUS=$?
|
|
50
|
+
|
|
51
|
+
if [[ $COMPOSE_STATUS -ne 0 || -z "$COMPOSED" ]]; then
|
|
52
|
+
# CLI call failed — let the dispatch through unmodified so the
|
|
53
|
+
# worker's failure is legible.
|
|
54
|
+
exit 0
|
|
55
|
+
fi
|
|
56
|
+
|
|
57
|
+
PROMPT=$(echo "$COMPOSED" | jq -r '.prompt // ""')
|
|
58
|
+
|
|
59
|
+
if [[ -z "$PROMPT" ]]; then
|
|
60
|
+
exit 0
|
|
61
|
+
fi
|
|
62
|
+
|
|
63
|
+
# Emit updatedInput so the worker subagent sees the composed prompt
|
|
64
|
+
# as its first-turn input. (Field name is `updatedInput`, not
|
|
65
|
+
# `modifiedInput` — verified by spike work; see working doc.)
|
|
66
|
+
jq -nc --arg p "$PROMPT" '{
|
|
67
|
+
hookSpecificOutput: {
|
|
68
|
+
hookEventName: "PreToolUse",
|
|
69
|
+
permissionDecision: "allow",
|
|
70
|
+
updatedInput: {
|
|
71
|
+
prompt: $p
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}'
|
|
75
|
+
|
|
76
|
+
exit 0
|