@popoverai/dotrequirements 0.24.2 → 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 +0 -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/dispatch.d.ts +69 -0
- package/dist/codebase-to-spec/dispatch.js +484 -0
- package/dist/codebase-to-spec/schemas.d.ts +375 -0
- package/dist/codebase-to-spec/schemas.js +133 -0
- package/dist/codebase-to-spec/skill-install.d.ts +36 -12
- package/dist/codebase-to-spec/skill-install.js +127 -26
- 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 +56 -1
- 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/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 +1 -1
|
@@ -1,21 +1,23 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Skill installation logic for the codebase-to-spec skill.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
4
|
+
* Ships as a bundle: SKILL.md + cts-worker agent definition + persona-
|
|
5
|
+
* injection hook script + hook registration in `.claude/settings.local.json`.
|
|
6
|
+
* The skill body (the conversational orchestrator) DEPENDS on the companion
|
|
7
|
+
* files — installing just the skill without them would leave Task dispatches
|
|
8
|
+
* unable to resolve their persona. For `project` scope, all four pieces are
|
|
9
|
+
* installed in one shot. For `global` and `custom` scopes, only the skill
|
|
10
|
+
* body is installed; the companions are project-scoped by CC convention.
|
|
8
11
|
*
|
|
9
12
|
* Host portability (CTS-SKILL-5): the install logic supports any host that
|
|
10
13
|
* follows the Agent Skills format. The default target is Claude Code's
|
|
11
14
|
* convention; `--target-dir` lets users place the skill anywhere.
|
|
12
15
|
*
|
|
13
16
|
* Requirements covered:
|
|
14
|
-
* - CTS-SKILL-1
|
|
15
|
-
* -
|
|
16
|
-
* Agent Skills format support
|
|
17
|
+
* - CTS-SKILL-1, CTS-SKILL-5
|
|
18
|
+
* - CTSO-CLI-1 (bundles the agent + hook that PreToolUse-injects persona bodies)
|
|
17
19
|
*/
|
|
18
|
-
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
20
|
+
import { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync, } from "node:fs";
|
|
19
21
|
import { homedir } from "node:os";
|
|
20
22
|
import { dirname, join, resolve } from "node:path";
|
|
21
23
|
import { loadTemplate } from "../utils/templates.js";
|
|
@@ -49,31 +51,130 @@ export function loadSkillTemplate() {
|
|
|
49
51
|
return loadTemplate("skills/codebase-to-spec/SKILL.md");
|
|
50
52
|
}
|
|
51
53
|
/**
|
|
52
|
-
*
|
|
53
|
-
|
|
54
|
+
* Load the bundled cts-worker agent definition template.
|
|
55
|
+
*/
|
|
56
|
+
export function loadAgentTemplate() {
|
|
57
|
+
return loadTemplate("agents/cts-worker.md");
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Load the bundled persona-injection hook script template.
|
|
61
|
+
*/
|
|
62
|
+
export function loadHookTemplate() {
|
|
63
|
+
return loadTemplate("hooks/cts-worker-persona.sh");
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Install (or refuse to overwrite) one template file at a target path.
|
|
67
|
+
* Returns true if a write happened.
|
|
68
|
+
*/
|
|
69
|
+
function installTemplateFile(templateContent, targetPath, overwrite, description) {
|
|
70
|
+
const exists = existsSync(targetPath);
|
|
71
|
+
if (exists && !overwrite) {
|
|
72
|
+
const onDisk = readFileSync(targetPath, "utf-8");
|
|
73
|
+
if (onDisk === templateContent) {
|
|
74
|
+
return { installed: false, overwrote: false };
|
|
75
|
+
}
|
|
76
|
+
throw new Error(`${description} already exists at ${targetPath} and differs from the bundled template. Pass --overwrite to replace it, or delete the file first.`);
|
|
77
|
+
}
|
|
78
|
+
mkdirSync(dirname(targetPath), { recursive: true });
|
|
79
|
+
writeFileSync(targetPath, templateContent, "utf-8");
|
|
80
|
+
return { installed: true, overwrote: exists };
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Register the cts-worker-persona PreToolUse hook in the project's
|
|
84
|
+
* `.claude/settings.local.json`, preserving any existing keys.
|
|
85
|
+
*
|
|
86
|
+
* Returns true if the hook registration was added or updated.
|
|
87
|
+
*/
|
|
88
|
+
function registerHookInSettings(projectRoot, hookScriptPath) {
|
|
89
|
+
const settingsPath = join(projectRoot, ".claude", "settings.local.json");
|
|
90
|
+
const hookEntry = {
|
|
91
|
+
matcher: "Task",
|
|
92
|
+
hooks: [{ type: "command", command: hookScriptPath }],
|
|
93
|
+
};
|
|
94
|
+
// Read existing settings if present.
|
|
95
|
+
let settings = {};
|
|
96
|
+
if (existsSync(settingsPath)) {
|
|
97
|
+
try {
|
|
98
|
+
const content = readFileSync(settingsPath, "utf-8");
|
|
99
|
+
if (content.trim()) {
|
|
100
|
+
settings = JSON.parse(content);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
catch {
|
|
104
|
+
// Invalid JSON — refuse to clobber. Tell caller to fix manually.
|
|
105
|
+
throw new Error(`Existing ${settingsPath} contains invalid JSON. Fix it or delete it before installing the orchestrator hook.`);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
// Initialize the hooks tree if missing.
|
|
109
|
+
// biome-ignore lint/suspicious/noExplicitAny: settings.local.json is user-owned; we narrow ad-hoc.
|
|
110
|
+
const hooks = (settings.hooks ?? {});
|
|
111
|
+
const preToolUse = (hooks.PreToolUse ?? []);
|
|
112
|
+
// Check if an entry already references our hook script (idempotent).
|
|
113
|
+
const alreadyRegistered = preToolUse.some((entry) => {
|
|
114
|
+
if (entry.matcher !== "Task")
|
|
115
|
+
return false;
|
|
116
|
+
const innerHooks = entry.hooks;
|
|
117
|
+
if (!Array.isArray(innerHooks))
|
|
118
|
+
return false;
|
|
119
|
+
return innerHooks.some((h) => h.command === hookScriptPath);
|
|
120
|
+
});
|
|
121
|
+
if (alreadyRegistered) {
|
|
122
|
+
return { settingsPath, registered: false };
|
|
123
|
+
}
|
|
124
|
+
preToolUse.push(hookEntry);
|
|
125
|
+
hooks.PreToolUse = preToolUse;
|
|
126
|
+
settings.hooks = hooks;
|
|
127
|
+
mkdirSync(dirname(settingsPath), { recursive: true });
|
|
128
|
+
writeFileSync(settingsPath, `${JSON.stringify(settings, null, 2)}\n`, "utf-8");
|
|
129
|
+
return { settingsPath, registered: true };
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Install (or refuse to overwrite) the codebase-to-spec skill bundle.
|
|
133
|
+
*
|
|
134
|
+
* For `project` scope: installs skill body, cts-worker agent, persona hook
|
|
135
|
+
* script, and registers the hook in settings.local.json.
|
|
136
|
+
*
|
|
137
|
+
* For `global` and `custom` scopes: installs only the skill body.
|
|
54
138
|
*/
|
|
55
139
|
export function installSkill(options = {}) {
|
|
56
140
|
const skillDir = resolveSkillDir(options);
|
|
57
141
|
const skillPath = join(skillDir, "SKILL.md");
|
|
58
|
-
const
|
|
59
|
-
const
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
throw new Error(`SKILL.md already exists at ${skillPath} and differs from the bundled template. Pass --overwrite to replace it, or delete the file first.`);
|
|
142
|
+
const skillTemplate = loadSkillTemplate();
|
|
143
|
+
const skillResult = installTemplateFile(skillTemplate, skillPath, options.overwrite === true, "SKILL.md");
|
|
144
|
+
// Skill body only for global/custom scopes — CC reads agents and hooks
|
|
145
|
+
// per-project, so the companions don't make sense outside a project.
|
|
146
|
+
const scope = options.scope ?? "project";
|
|
147
|
+
if (scope !== "project") {
|
|
148
|
+
return {
|
|
149
|
+
installedPath: skillPath,
|
|
150
|
+
installed: skillResult.installed,
|
|
151
|
+
overwrote: skillResult.overwrote,
|
|
152
|
+
};
|
|
70
153
|
}
|
|
71
|
-
|
|
72
|
-
|
|
154
|
+
const projectRoot = options.projectRoot ?? process.cwd();
|
|
155
|
+
const agentPath = join(projectRoot, ".claude", "agents", "cts-worker.md");
|
|
156
|
+
const hookScriptPath = join(projectRoot, ".claude", "hooks", "cts-worker-persona.sh");
|
|
157
|
+
// Install the cts-worker agent definition.
|
|
158
|
+
installTemplateFile(loadAgentTemplate(), agentPath, options.overwrite === true, "cts-worker agent definition");
|
|
159
|
+
// Install the persona-injection hook script (executable bit set separately).
|
|
160
|
+
installTemplateFile(loadHookTemplate(), hookScriptPath, options.overwrite === true, "cts-worker-persona hook script");
|
|
161
|
+
// chmod the hook script to be executable so Claude Code's PreToolUse
|
|
162
|
+
// hook can invoke it. (Imported statically — the CLI package is ESM, so
|
|
163
|
+
// `require("node:fs")` here would throw ReferenceError and silently
|
|
164
|
+
// leave the script at 0644, breaking the hook on every install.)
|
|
165
|
+
chmodSync(hookScriptPath, 0o755);
|
|
166
|
+
// Register the hook in settings.local.json.
|
|
167
|
+
const { settingsPath, registered } = registerHookInSettings(projectRoot, hookScriptPath);
|
|
73
168
|
return {
|
|
74
169
|
installedPath: skillPath,
|
|
75
|
-
installed:
|
|
76
|
-
overwrote:
|
|
170
|
+
installed: skillResult.installed,
|
|
171
|
+
overwrote: skillResult.overwrote,
|
|
172
|
+
companions: {
|
|
173
|
+
agentPath,
|
|
174
|
+
hookScriptPath,
|
|
175
|
+
settingsPath,
|
|
176
|
+
hookRegistered: registered,
|
|
177
|
+
},
|
|
77
178
|
};
|
|
78
179
|
}
|
|
79
180
|
//# sourceMappingURL=skill-install.js.map
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `dotrequirements codebase-to-spec compose-orchestrator` subcommand.
|
|
3
|
+
*
|
|
4
|
+
* Conversational-orchestrator variant of `compose`. Reads outline.yaml
|
|
5
|
+
* (must be approved) + per-area partials, writes the composed spec to
|
|
6
|
+
* .dotrequirements-cache/spec-composed.md. Wraps the legacy `runCompose`
|
|
7
|
+
* by adapting outline.yaml to the legacy Outline shape.
|
|
8
|
+
*
|
|
9
|
+
* Requirements covered:
|
|
10
|
+
* - CTS-COMPOSE-1, CTS-COMPOSE-2 (reuses the legacy compose logic)
|
|
11
|
+
* - CTSO-INTEG-1: orchestrator uses existing deterministic stages unchanged
|
|
12
|
+
*/
|
|
13
|
+
export declare function composeOrchestratorCommand(): Promise<void>;
|
|
14
|
+
//# sourceMappingURL=compose-orchestrator.d.ts.map
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `dotrequirements codebase-to-spec compose-orchestrator` subcommand.
|
|
3
|
+
*
|
|
4
|
+
* Conversational-orchestrator variant of `compose`. Reads outline.yaml
|
|
5
|
+
* (must be approved) + per-area partials, writes the composed spec to
|
|
6
|
+
* .dotrequirements-cache/spec-composed.md. Wraps the legacy `runCompose`
|
|
7
|
+
* by adapting outline.yaml to the legacy Outline shape.
|
|
8
|
+
*
|
|
9
|
+
* Requirements covered:
|
|
10
|
+
* - CTS-COMPOSE-1, CTS-COMPOSE-2 (reuses the legacy compose logic)
|
|
11
|
+
* - CTSO-INTEG-1: orchestrator uses existing deterministic stages unchanged
|
|
12
|
+
*/
|
|
13
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
14
|
+
import { cachePaths } from "../../codebase-to-spec/cache.js";
|
|
15
|
+
import { runCompose } from "../../codebase-to-spec/compose.js";
|
|
16
|
+
import { ExitCode } from "../../codebase-to-spec/exit-codes.js";
|
|
17
|
+
import { conversationalOutlineToLegacy, parseConversationalOutline, } from "../../codebase-to-spec/schemas.js";
|
|
18
|
+
import { findProjectRoot } from "../../utils/project-settings.js";
|
|
19
|
+
export async function composeOrchestratorCommand() {
|
|
20
|
+
const projectRoot = findProjectRoot(process.cwd()) ?? process.cwd();
|
|
21
|
+
const paths = cachePaths(projectRoot);
|
|
22
|
+
if (!existsSync(paths.outline)) {
|
|
23
|
+
process.stderr.write(`No outline.yaml found at ${paths.outline}. Run the orchestrator's planner first.\n`);
|
|
24
|
+
process.exitCode = ExitCode.MissingInput;
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
let outline;
|
|
28
|
+
try {
|
|
29
|
+
outline = parseConversationalOutline(readFileSync(paths.outline, "utf-8"));
|
|
30
|
+
}
|
|
31
|
+
catch (err) {
|
|
32
|
+
process.stderr.write(`Outline at ${paths.outline} is invalid: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
33
|
+
process.exitCode = ExitCode.MissingInput;
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
if (!outline.review || outline.review.result !== "approved") {
|
|
37
|
+
process.stderr.write(`Outline at ${paths.outline} is not approved (review.result is "${outline.review?.result ?? "absent"}"). Approve the outline before composing.\n`);
|
|
38
|
+
process.exitCode = ExitCode.MissingInput;
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
const legacyOutline = conversationalOutlineToLegacy(outline);
|
|
42
|
+
const result = runCompose({
|
|
43
|
+
outline: legacyOutline,
|
|
44
|
+
partialPathFor: (sanitized) => paths.partial(sanitized),
|
|
45
|
+
composedPath: paths.composedSpec,
|
|
46
|
+
});
|
|
47
|
+
process.stdout.write(`Composed spec → ${result.composedPath} (${result.partialsIncluded} partials included, ${result.partialsMissing} missing)\n`);
|
|
48
|
+
if (result.validationError) {
|
|
49
|
+
process.stderr.write(`Composed spec failed validation:\n${result.validationError}\n`);
|
|
50
|
+
process.exitCode = ExitCode.StageFailed;
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
//# sourceMappingURL=compose-orchestrator.js.map
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `dotrequirements codebase-to-spec dispatch-context <dispatch-id>` subcommand.
|
|
3
|
+
*
|
|
4
|
+
* Returns the composed prompt for a worker dispatch as a JSON object on stdout.
|
|
5
|
+
* Called by the cts-worker PreToolUse hook to compose the subagent's first-turn
|
|
6
|
+
* prompt via `modifiedInput`.
|
|
7
|
+
*
|
|
8
|
+
* Requirements covered:
|
|
9
|
+
* - CTSO-CLI-1: CLI exposes commands that return dispatch instructions
|
|
10
|
+
*/
|
|
11
|
+
export declare function dispatchContextCommand(dispatchId: string): Promise<void>;
|
|
12
|
+
//# sourceMappingURL=dispatch-context.d.ts.map
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `dotrequirements codebase-to-spec dispatch-context <dispatch-id>` subcommand.
|
|
3
|
+
*
|
|
4
|
+
* Returns the composed prompt for a worker dispatch as a JSON object on stdout.
|
|
5
|
+
* Called by the cts-worker PreToolUse hook to compose the subagent's first-turn
|
|
6
|
+
* prompt via `modifiedInput`.
|
|
7
|
+
*
|
|
8
|
+
* Requirements covered:
|
|
9
|
+
* - CTSO-CLI-1: CLI exposes commands that return dispatch instructions
|
|
10
|
+
*/
|
|
11
|
+
import { composeDispatchContext } from "../../codebase-to-spec/dispatch.js";
|
|
12
|
+
import { ExitCode } from "../../codebase-to-spec/exit-codes.js";
|
|
13
|
+
export async function dispatchContextCommand(dispatchId) {
|
|
14
|
+
const ctx = composeDispatchContext(dispatchId);
|
|
15
|
+
if (!ctx) {
|
|
16
|
+
process.stderr.write(`Unknown dispatch-id: ${dispatchId}\n`);
|
|
17
|
+
process.exitCode = ExitCode.MissingInput;
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
process.stdout.write(`${JSON.stringify(ctx)}\n`);
|
|
21
|
+
}
|
|
22
|
+
//# sourceMappingURL=dispatch-context.js.map
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `dotrequirements codebase-to-spec dispatch-editor <area-prefix>` subcommand.
|
|
3
|
+
*
|
|
4
|
+
* Returns a dispatch payload for an editor pass on a specific area's partial.
|
|
5
|
+
* Validates that the area's `review.result` is `"needs-revision"` and that
|
|
6
|
+
* the partial file exists before scaffolding the dispatch.
|
|
7
|
+
*
|
|
8
|
+
* The worker overwrites the existing partial via Edit; the area.review.thread
|
|
9
|
+
* in outline.yaml stays where it is (the orchestrator manages it).
|
|
10
|
+
*
|
|
11
|
+
* Requirements covered:
|
|
12
|
+
* - CTSO-CLI-1: CLI exposes commands that return dispatch instructions
|
|
13
|
+
* - CTSO-CONV-3: orchestrator dispatches an editor when revisions are needed
|
|
14
|
+
*/
|
|
15
|
+
export declare function dispatchEditorCommand(areaPrefix: string): Promise<void>;
|
|
16
|
+
//# sourceMappingURL=dispatch-editor.d.ts.map
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `dotrequirements codebase-to-spec dispatch-editor <area-prefix>` subcommand.
|
|
3
|
+
*
|
|
4
|
+
* Returns a dispatch payload for an editor pass on a specific area's partial.
|
|
5
|
+
* Validates that the area's `review.result` is `"needs-revision"` and that
|
|
6
|
+
* the partial file exists before scaffolding the dispatch.
|
|
7
|
+
*
|
|
8
|
+
* The worker overwrites the existing partial via Edit; the area.review.thread
|
|
9
|
+
* in outline.yaml stays where it is (the orchestrator manages it).
|
|
10
|
+
*
|
|
11
|
+
* Requirements covered:
|
|
12
|
+
* - CTSO-CLI-1: CLI exposes commands that return dispatch instructions
|
|
13
|
+
* - CTSO-CONV-3: orchestrator dispatches an editor when revisions are needed
|
|
14
|
+
*/
|
|
15
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
16
|
+
import { cachePaths } from "../../codebase-to-spec/cache.js";
|
|
17
|
+
import { EDITOR_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 dispatchEditorCommand(areaPrefix) {
|
|
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
|
+
const area = outline.areas.find((a) => a.prefix === areaPrefix);
|
|
40
|
+
if (!area) {
|
|
41
|
+
process.stderr.write(`No area with prefix "${areaPrefix}" in outline.yaml. Available prefixes: ${outline.areas
|
|
42
|
+
.map((a) => a.prefix)
|
|
43
|
+
.join(", ")}\n`);
|
|
44
|
+
process.exitCode = ExitCode.MissingInput;
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
if (!area.review) {
|
|
48
|
+
process.stderr.write(`Area "${areaPrefix}" has no review yet. Write a per-area review with result=needs-revision before dispatching the editor.\n`);
|
|
49
|
+
process.exitCode = ExitCode.MissingInput;
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
if (area.review.result === "approved") {
|
|
53
|
+
process.stderr.write(`Area "${areaPrefix}" has review.result "approved" — nothing to revise. The partial is good as-is.\n`);
|
|
54
|
+
process.exitCode = ExitCode.MissingInput;
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
const partialPath = paths.partial(sanitizeAreaName(area.name));
|
|
58
|
+
if (!existsSync(partialPath)) {
|
|
59
|
+
process.stderr.write(`Partial not found at ${partialPath}. Run the specifier for area "${areaPrefix}" before dispatching the editor.\n`);
|
|
60
|
+
process.exitCode = ExitCode.MissingInput;
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
const payload = {
|
|
64
|
+
dispatch_id: `${EDITOR_DISPATCH_ID_PREFIX}${areaPrefix}`,
|
|
65
|
+
output_path: partialPath,
|
|
66
|
+
area_name: area.name,
|
|
67
|
+
area_prefix: area.prefix,
|
|
68
|
+
};
|
|
69
|
+
process.stdout.write(`${JSON.stringify(payload)}\n`);
|
|
70
|
+
}
|
|
71
|
+
//# sourceMappingURL=dispatch-editor.js.map
|
|
@@ -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
|
|
@@ -6,16 +6,31 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import { installCtsAbortHandlers } from "../../codebase-to-spec/progress.js";
|
|
8
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";
|
|
9
14
|
import { editLoopCommand } from "./edit-loop.js";
|
|
10
15
|
import { fanOutCommand } from "./fan-out.js";
|
|
11
16
|
import { packCommand } from "./pack.js";
|
|
12
17
|
import { planLoopCommand } from "./plan-loop.js";
|
|
13
18
|
import { presentCommand } from "./present.js";
|
|
19
|
+
import { presentOrchestratorCommand } from "./present-orchestrator.js";
|
|
14
20
|
import { runCommand } from "./run.js";
|
|
15
21
|
import { skillInstallCommand } from "./skill-install.js";
|
|
16
22
|
import { specifyAreaCommand } from "./specify-area.js";
|
|
17
23
|
import { styleCheckCommand } from "./style-check.js";
|
|
18
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 };
|
|
19
34
|
export function registerCodebaseToSpec(program) {
|
|
20
35
|
const cts = program
|
|
21
36
|
.command("codebase-to-spec")
|
|
@@ -134,7 +149,47 @@ export function registerCodebaseToSpec(program) {
|
|
|
134
149
|
});
|
|
135
150
|
});
|
|
136
151
|
cts
|
|
137
|
-
.command("
|
|
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,
|
|
189
|
+
});
|
|
190
|
+
});
|
|
191
|
+
cts
|
|
192
|
+
.command("skill-install", HIDDEN)
|
|
138
193
|
.description("Install the codebase-to-spec skill (SKILL.md) into the host's skills directory")
|
|
139
194
|
.option("--global", "Install to ~/.claude/skills/ instead of the project")
|
|
140
195
|
.option("--target-dir <path>", "Install into a custom skills directory (for non-Claude-Code hosts)")
|