@popoverai/dotrequirements 0.26.0 → 0.26.2
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 +11 -7
- 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/codebase-to-spec/validate.js +3 -2
- package/dist/commands/acceptance-test.js +4 -2
- package/dist/commands/ai-setup.js +97 -59
- 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/commands/get.js +6 -2
- package/dist/commands/init.js +7 -5
- package/dist/commands/link-resolution.d.ts +3 -1
- package/dist/commands/link-resolution.js +4 -2
- package/dist/commands/pull.js +36 -3
- package/dist/commands/push.js +54 -16
- package/dist/commands/report.js +18 -3
- package/dist/commands/review-test.js +16 -8
- package/dist/commands/tests-for.js +13 -13
- package/dist/commands/validate.js +14 -14
- package/dist/harness/cache.d.ts +19 -3
- package/dist/harness/cache.js +38 -12
- package/dist/harness/finalize.js +33 -1
- package/dist/harness/index.js +16 -9
- package/dist/harness/requirementsLoader.js +12 -0
- package/dist/harness/tracking.d.ts +17 -2
- package/dist/harness/tracking.js +83 -9
- package/dist/mcp/handlers/authoring.js +13 -4
- package/dist/mcp/handlers/get.js +7 -3
- package/dist/mcp/handlers/push.js +59 -13
- package/dist/mcp/handlers/review.d.ts +1 -0
- package/dist/mcp/handlers/review.js +58 -15
- package/dist/mcp/handlers/test-mapping.js +47 -12
- package/dist/mcp/handlers/types.d.ts +14 -0
- package/dist/mcp/handlers/types.js +27 -0
- package/dist/mcp/index.js +4 -0
- package/dist/push/core.d.ts +50 -0
- package/dist/push/core.js +149 -11
- package/dist/push/index.d.ts +1 -1
- package/dist/push/index.js +1 -1
- package/dist/requirements/cloud-coverage.d.ts +12 -2
- package/dist/requirements/cloud-coverage.js +30 -3
- package/dist/requirements/grep.d.ts +7 -2
- package/dist/requirements/grep.js +75 -47
- package/dist/schema/builder.d.ts +1 -1
- package/dist/schema/builder.js +13 -0
- package/dist/schema/conversions.d.ts +7 -2
- package/dist/schema/conversions.js +13 -4
- package/dist/schema/parser-core.d.ts +28 -0
- package/dist/schema/parser-core.js +80 -9
- package/dist/schema/parser.d.ts +8 -26
- package/dist/schema/parser.js +23 -251
- package/dist/schema/resolver.js +18 -8
- package/dist/templates/skills/codebase-to-spec/SKILL.md +10 -4
- package/dist/utils/env.js +17 -1
- package/dist/utils/oauth-flow.js +8 -0
- package/dist/utils/project-settings.d.ts +4 -0
- package/dist/utils/project-settings.js +14 -1
- 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
|
@@ -87,8 +87,9 @@ function describeValidationError(err) {
|
|
|
87
87
|
const parts = [err.message];
|
|
88
88
|
let cursor = err.cause;
|
|
89
89
|
while (cursor instanceof Error) {
|
|
90
|
-
// Don't repeat the same message twice if the wrapper just rethrew
|
|
91
|
-
|
|
90
|
+
// Don't repeat the same message twice if the wrapper just rethrew
|
|
91
|
+
// (or already embedded the inner message in its own).
|
|
92
|
+
if (cursor.message && !parts[parts.length - 1].includes(cursor.message)) {
|
|
92
93
|
parts.push(cursor.message);
|
|
93
94
|
}
|
|
94
95
|
cursor = cursor.cause;
|
|
@@ -127,8 +127,10 @@ export async function acceptanceTestCommand(requirementKey, url, options) {
|
|
|
127
127
|
else {
|
|
128
128
|
displayResults(requirementTree, results);
|
|
129
129
|
}
|
|
130
|
-
// 11. Exit with appropriate code
|
|
131
|
-
|
|
130
|
+
// 11. Exit with appropriate code — computed over the requirement tree so
|
|
131
|
+
// a requirement with a missing result counts as not passing, agreeing
|
|
132
|
+
// with the displayed counts (ACCEPTANCE-1.0/.1/.2)
|
|
133
|
+
const failedCount = requirementTree.filter((_, i) => results[i]?.status !== "passed").length;
|
|
132
134
|
if (failedCount > 0) {
|
|
133
135
|
process.exit(1);
|
|
134
136
|
}
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import { execSync } from "node:child_process";
|
|
1
|
+
import { execFileSync, execSync } from "node:child_process";
|
|
2
2
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
3
3
|
import { homedir } from "node:os";
|
|
4
4
|
import { join } from "node:path";
|
|
5
5
|
import prompts from "prompts";
|
|
6
6
|
import { brand } from "../utils/brand.js";
|
|
7
7
|
import { appendOrUpdateSection, buildNoGitRepoMessage, findDotrequirementsSection, findGitRoot, getContextFileName, } from "../utils/context-file.js";
|
|
8
|
+
import { findProjectRoot, readProjectSettings, } from "../utils/project-settings.js";
|
|
8
9
|
import { loadTemplate } from "../utils/templates.js";
|
|
9
10
|
/**
|
|
10
11
|
* Load the context file section template
|
|
@@ -12,6 +13,56 @@ import { loadTemplate } from "../utils/templates.js";
|
|
|
12
13
|
function loadContextFileSection() {
|
|
13
14
|
return loadTemplate("context-file-section.md");
|
|
14
15
|
}
|
|
16
|
+
/**
|
|
17
|
+
* Read an assistant's existing MCP config file (AISETUP-2).
|
|
18
|
+
* Returns {} when the file is absent or empty. Returns null when the file
|
|
19
|
+
* exists but is unusable — unreadable (AISETUP-2.2), invalid JSON
|
|
20
|
+
* (AISETUP-2.1), or a non-object root (AISETUP-2.3). In every null case the
|
|
21
|
+
* path has already been shown to the user and the caller must stop without
|
|
22
|
+
* writing; rewriting would destroy every other configured server.
|
|
23
|
+
*/
|
|
24
|
+
function readExistingMcpConfig(configPath) {
|
|
25
|
+
const stopWithoutWriting = (reason) => {
|
|
26
|
+
console.error(`❌ ${reason}`);
|
|
27
|
+
console.log(" Nothing was changed. Fix (or remove) the file, then re-run ai-setup:");
|
|
28
|
+
console.log(` ${configPath}\n`);
|
|
29
|
+
return null;
|
|
30
|
+
};
|
|
31
|
+
if (!existsSync(configPath)) {
|
|
32
|
+
return {};
|
|
33
|
+
}
|
|
34
|
+
let existingContent;
|
|
35
|
+
try {
|
|
36
|
+
existingContent = readFileSync(configPath, "utf-8");
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
return stopWithoutWriting("Existing config file could not be read.");
|
|
40
|
+
}
|
|
41
|
+
if (!existingContent.trim()) {
|
|
42
|
+
return {};
|
|
43
|
+
}
|
|
44
|
+
let parsed;
|
|
45
|
+
try {
|
|
46
|
+
parsed = JSON.parse(existingContent);
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
return stopWithoutWriting("Existing config file contains invalid JSON.");
|
|
50
|
+
}
|
|
51
|
+
// AISETUP-2.3: a non-object root (e.g. a top-level array) would swallow the
|
|
52
|
+
// mcpServers assignment and serialize back without it — success would be
|
|
53
|
+
// reported while nothing was configured
|
|
54
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
55
|
+
return stopWithoutWriting("Existing config file is not a JSON object.");
|
|
56
|
+
}
|
|
57
|
+
// AISETUP-2.3: same trap one level down — an array-valued mcpServers takes
|
|
58
|
+
// the server entry as an expando property that JSON.stringify drops
|
|
59
|
+
const mcpServers = parsed.mcpServers;
|
|
60
|
+
if (mcpServers != null &&
|
|
61
|
+
(typeof mcpServers !== "object" || Array.isArray(mcpServers))) {
|
|
62
|
+
return stopWithoutWriting('Existing config file has an "mcpServers" field that is not a JSON object.');
|
|
63
|
+
}
|
|
64
|
+
return parsed;
|
|
65
|
+
}
|
|
15
66
|
/**
|
|
16
67
|
* Install workflow guidance to the appropriate context file for a platform.
|
|
17
68
|
* With `yes` (the non-interactive --assistant path, AISETUP-1.0), both
|
|
@@ -166,10 +217,18 @@ async function dispatchSetup(client, yes = false) {
|
|
|
166
217
|
}
|
|
167
218
|
async function setupClaudeCode(yes = false) {
|
|
168
219
|
console.log("\n📦 Configuring MCP for Claude Code...\n");
|
|
169
|
-
|
|
220
|
+
// Register via an args array so no shell quoting is involved: cmd.exe does
|
|
221
|
+
// not honor single quotes, so a shell-quoted JSON string breaks on Windows.
|
|
222
|
+
const mcpServerJson = JSON.stringify({
|
|
223
|
+
type: "stdio",
|
|
224
|
+
command: "npx",
|
|
225
|
+
args: ["-y", "@popoverai/dotrequirements", "mcp"],
|
|
226
|
+
});
|
|
227
|
+
// Shell-safe on every platform (no quoting needed anywhere)
|
|
228
|
+
const manualCommand = "claude mcp add dotrequirements -- npx -y @popoverai/dotrequirements mcp";
|
|
170
229
|
let mcpConfigured = false;
|
|
171
230
|
try {
|
|
172
|
-
|
|
231
|
+
execFileSync("claude", ["mcp", "add-json", "dotrequirements", mcpServerJson], { stdio: "pipe" });
|
|
173
232
|
console.log("✅ MCP configured successfully for Claude Code!");
|
|
174
233
|
mcpConfigured = true;
|
|
175
234
|
}
|
|
@@ -185,7 +244,7 @@ async function setupClaudeCode(yes = false) {
|
|
|
185
244
|
else {
|
|
186
245
|
console.error("❌ Failed to configure MCP automatically.");
|
|
187
246
|
console.log("\nYou can manually run:");
|
|
188
|
-
console.log(` ${
|
|
247
|
+
console.log(` ${manualCommand}`);
|
|
189
248
|
if (stderr) {
|
|
190
249
|
console.log(`\nError: ${stderr}`);
|
|
191
250
|
}
|
|
@@ -224,24 +283,17 @@ async function setupClaudeDesktop() {
|
|
|
224
283
|
REQUIREMENTS_DIR: process.cwd(),
|
|
225
284
|
},
|
|
226
285
|
};
|
|
286
|
+
// AISETUP-2.1–2.3: bail out before touching anything if the config is unusable
|
|
287
|
+
const config = readExistingMcpConfig(configPath);
|
|
288
|
+
if (config === null) {
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
227
291
|
try {
|
|
228
292
|
if (!existsSync(configDir)) {
|
|
229
293
|
console.log("Creating Claude Desktop config directory...");
|
|
230
294
|
mkdirSync(configDir, { recursive: true });
|
|
231
295
|
}
|
|
232
|
-
|
|
233
|
-
if (existsSync(configPath)) {
|
|
234
|
-
try {
|
|
235
|
-
const existingContent = readFileSync(configPath, "utf-8");
|
|
236
|
-
if (existingContent.trim()) {
|
|
237
|
-
config = JSON.parse(existingContent);
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
catch {
|
|
241
|
-
console.log("⚠️ Existing config file is invalid, creating fresh config...");
|
|
242
|
-
config = {};
|
|
243
|
-
}
|
|
244
|
-
}
|
|
296
|
+
// AISETUP-2.0: other configured servers survive; only ours is (re)set
|
|
245
297
|
if (!config.mcpServers) {
|
|
246
298
|
config.mcpServers = {};
|
|
247
299
|
}
|
|
@@ -271,30 +323,26 @@ async function setupClaudeDesktop() {
|
|
|
271
323
|
}
|
|
272
324
|
async function setupCursor(yes = false) {
|
|
273
325
|
console.log("\n📦 Configuring MCP for Cursor (project-specific)...\n");
|
|
274
|
-
|
|
275
|
-
|
|
326
|
+
// Cursor loads .cursor/mcp.json from the project (git) root — match the
|
|
327
|
+
// context-file placement, falling back to cwd outside a repository
|
|
328
|
+
const projectDir = (await findGitRoot()) ?? process.cwd();
|
|
329
|
+
const configDir = join(projectDir, ".cursor");
|
|
330
|
+
const configPath = join(configDir, "mcp.json");
|
|
276
331
|
const mcpServerConfig = {
|
|
277
332
|
command: "npx",
|
|
278
333
|
args: ["-y", "@popoverai/dotrequirements", "mcp"],
|
|
279
334
|
};
|
|
335
|
+
// AISETUP-2.1–2.3: bail out before touching anything if the config is unusable
|
|
336
|
+
const config = readExistingMcpConfig(configPath);
|
|
337
|
+
if (config === null) {
|
|
338
|
+
return;
|
|
339
|
+
}
|
|
280
340
|
try {
|
|
281
341
|
if (!existsSync(configDir)) {
|
|
282
342
|
console.log("Creating Cursor config directory...");
|
|
283
343
|
mkdirSync(configDir, { recursive: true });
|
|
284
344
|
}
|
|
285
|
-
|
|
286
|
-
if (existsSync(configPath)) {
|
|
287
|
-
try {
|
|
288
|
-
const existingContent = readFileSync(configPath, "utf-8");
|
|
289
|
-
if (existingContent.trim()) {
|
|
290
|
-
config = JSON.parse(existingContent);
|
|
291
|
-
}
|
|
292
|
-
}
|
|
293
|
-
catch {
|
|
294
|
-
console.log("⚠️ Existing config file is invalid, creating fresh config...");
|
|
295
|
-
config = {};
|
|
296
|
-
}
|
|
297
|
-
}
|
|
345
|
+
// AISETUP-2.0: other configured servers survive; only ours is (re)set
|
|
298
346
|
if (!config.mcpServers) {
|
|
299
347
|
config.mcpServers = {};
|
|
300
348
|
}
|
|
@@ -324,45 +372,33 @@ async function setupAntigravity(yes = false) {
|
|
|
324
372
|
console.log("\n📦 Configuring MCP for Google Antigravity...\n");
|
|
325
373
|
const configPath = join(homedir(), ".gemini", "antigravity", "mcp_config.json");
|
|
326
374
|
const configDir = join(homedir(), ".gemini", "antigravity");
|
|
327
|
-
//
|
|
328
|
-
|
|
375
|
+
// AISETUP-3.0: detect the project the way the CLI defines projects —
|
|
376
|
+
// .requirements/project-settings.json at the project root
|
|
329
377
|
let projectId = null;
|
|
330
378
|
let projectPath = null;
|
|
331
|
-
|
|
379
|
+
const projectRoot = findProjectRoot();
|
|
380
|
+
if (projectRoot) {
|
|
332
381
|
try {
|
|
333
|
-
const
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
if (trimmed.startsWith("DOTREQUIREMENTS_PROJECT_ID=")) {
|
|
338
|
-
projectId = trimmed.split("=")[1]?.trim().replace(/['"]/g, "");
|
|
339
|
-
projectPath = process.cwd();
|
|
340
|
-
break;
|
|
341
|
-
}
|
|
382
|
+
const settings = readProjectSettings(projectRoot);
|
|
383
|
+
if (settings) {
|
|
384
|
+
projectId = settings.projectId;
|
|
385
|
+
projectPath = projectRoot;
|
|
342
386
|
}
|
|
343
387
|
}
|
|
344
388
|
catch {
|
|
345
|
-
//
|
|
389
|
+
// Settings file unreadable/invalid — continue with global setup
|
|
346
390
|
}
|
|
347
391
|
}
|
|
392
|
+
// AISETUP-2.1–2.3: bail out before touching anything if the config is unusable
|
|
393
|
+
const config = readExistingMcpConfig(configPath);
|
|
394
|
+
if (config === null) {
|
|
395
|
+
return;
|
|
396
|
+
}
|
|
348
397
|
try {
|
|
349
398
|
if (!existsSync(configDir)) {
|
|
350
399
|
console.log("Creating Antigravity config directory...");
|
|
351
400
|
mkdirSync(configDir, { recursive: true });
|
|
352
401
|
}
|
|
353
|
-
let config = {};
|
|
354
|
-
if (existsSync(configPath)) {
|
|
355
|
-
try {
|
|
356
|
-
const existingContent = readFileSync(configPath, "utf-8");
|
|
357
|
-
if (existingContent.trim()) {
|
|
358
|
-
config = JSON.parse(existingContent);
|
|
359
|
-
}
|
|
360
|
-
}
|
|
361
|
-
catch {
|
|
362
|
-
console.log("⚠️ Existing config file is invalid, creating fresh config...");
|
|
363
|
-
config = {};
|
|
364
|
-
}
|
|
365
|
-
}
|
|
366
402
|
if (!config.mcpServers) {
|
|
367
403
|
config.mcpServers = {};
|
|
368
404
|
}
|
|
@@ -385,7 +421,9 @@ async function setupAntigravity(yes = false) {
|
|
|
385
421
|
console.log(` Path: ${projectPath}\n`);
|
|
386
422
|
}
|
|
387
423
|
else {
|
|
388
|
-
|
|
424
|
+
// AISETUP-3.1: outside any initialized project — skip the per-project
|
|
425
|
+
// step and say so
|
|
426
|
+
console.log("ℹ️ No project found (no .requirements/project-settings.json in this directory or its parents)");
|
|
389
427
|
console.log(" Setting up global MCP server only.\n");
|
|
390
428
|
}
|
|
391
429
|
writeFileSync(configPath, JSON.stringify(config, null, 2), "utf-8");
|
|
@@ -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
|
package/dist/commands/get.js
CHANGED
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
import { glob } from "glob";
|
|
2
|
-
import { formatRequirementTree,
|
|
2
|
+
import { formatRequirementTree, loadAllRequirements, } from "../requirements/index.js";
|
|
3
3
|
import { findFilesWithRequirement, findTestCodeForRequirement, } from "../requirements/testCodeExtractor.js";
|
|
4
4
|
export async function getCommand(id) {
|
|
5
5
|
const workspaceRoot = process.cwd();
|
|
6
6
|
const { flattened } = await loadAllRequirements(workspaceRoot);
|
|
7
|
-
|
|
7
|
+
// Match the node itself plus all of its descendants by id prefix (mirrors
|
|
8
|
+
// the #46 MCP get fix). The shared getRequirementTree only matches on
|
|
9
|
+
// rootId/id equality, which drops grandchildren (e.g. REQ-123.0.0) when a
|
|
10
|
+
// child id (REQ-123.0) is requested.
|
|
11
|
+
const tree = flattened.filter((r) => r.id === id || r.id.startsWith(`${id}.`));
|
|
8
12
|
// CLI-FIND-GET-1.2: missing ID → error + non-zero exit
|
|
9
13
|
if (tree.length === 0) {
|
|
10
14
|
throw new Error(`Requirement "${id}" not found`);
|
package/dist/commands/init.js
CHANGED
|
@@ -147,11 +147,10 @@ async function authenticatedFlow(cwd, options) {
|
|
|
147
147
|
}
|
|
148
148
|
if (projectChoice === "create") {
|
|
149
149
|
await createNewProjectFlow(cwd, client, selectedTeam, options);
|
|
150
|
+
return true;
|
|
150
151
|
}
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
}
|
|
154
|
-
return true;
|
|
152
|
+
// INIT-12: a cancelled project selection means init did not complete
|
|
153
|
+
return await connectExistingProjectFlow(cwd, client, selectedTeam);
|
|
155
154
|
}
|
|
156
155
|
/**
|
|
157
156
|
* INIT-18: Prompt user to select a team after OAuth
|
|
@@ -268,13 +267,15 @@ async function createNewProjectFlow(cwd, client, team, options) {
|
|
|
268
267
|
}
|
|
269
268
|
/**
|
|
270
269
|
* INIT-6, INIT-16: Connect to an existing project
|
|
270
|
+
* Returns true if connected successfully, false if the user cancelled
|
|
271
|
+
* (INIT-12: cancellation must not read as a successful init)
|
|
271
272
|
*/
|
|
272
273
|
async function connectExistingProjectFlow(cwd, client, team) {
|
|
273
274
|
// INIT-6.0, INIT-16.0: Select project from team
|
|
274
275
|
const selectedProject = await selectProject(client, team.teamId);
|
|
275
276
|
if (!selectedProject) {
|
|
276
277
|
console.log("Initialization cancelled.");
|
|
277
|
-
return;
|
|
278
|
+
return false;
|
|
278
279
|
}
|
|
279
280
|
// AUTHZ-3: Prompt for secret expiry (paid tier only, if creating new secret)
|
|
280
281
|
const expiryDays = await promptExpiryDays(team.tier);
|
|
@@ -306,6 +307,7 @@ async function connectExistingProjectFlow(cwd, client, team) {
|
|
|
306
307
|
console.log("\nNext steps:");
|
|
307
308
|
console.log(" 1. Review the requirements in .requirements/");
|
|
308
309
|
console.log(" 2. Add the test harness to your test setup (see README)");
|
|
310
|
+
return true;
|
|
309
311
|
}
|
|
310
312
|
/**
|
|
311
313
|
* MEMBERSHIP-18: Invite flow - join a team using an invite token
|
|
@@ -29,7 +29,9 @@ export interface LinkFlags {
|
|
|
29
29
|
json?: boolean;
|
|
30
30
|
}
|
|
31
31
|
/**
|
|
32
|
-
* LINK-12.3: any deviation flag implies non-interactive mode.
|
|
32
|
+
* LINK-12.3: any deviation flag implies non-interactive mode. The JSON
|
|
33
|
+
* output flag does too — machine-readable output only exists on the
|
|
34
|
+
* non-interactive path, so `link --json` alone must not run interactively.
|
|
33
35
|
*/
|
|
34
36
|
export declare function isNonInteractive(flags: LinkFlags): boolean;
|
|
35
37
|
export declare function hasCapacity(team: TeamOption): boolean;
|
|
@@ -6,10 +6,12 @@
|
|
|
6
6
|
* assistant needs to ask the user and retry with a deviation flag.
|
|
7
7
|
*/
|
|
8
8
|
/**
|
|
9
|
-
* LINK-12.3: any deviation flag implies non-interactive mode.
|
|
9
|
+
* LINK-12.3: any deviation flag implies non-interactive mode. The JSON
|
|
10
|
+
* output flag does too — machine-readable output only exists on the
|
|
11
|
+
* non-interactive path, so `link --json` alone must not run interactively.
|
|
10
12
|
*/
|
|
11
13
|
export function isNonInteractive(flags) {
|
|
12
|
-
return Boolean(flags.yes || flags.team || flags.connect || flags.create);
|
|
14
|
+
return Boolean(flags.yes || flags.team || flags.connect || flags.create || flags.json);
|
|
13
15
|
}
|
|
14
16
|
export function hasCapacity(team) {
|
|
15
17
|
return team.projectLimit === -1 || team.projectCount < team.projectLimit;
|