contract-driven-delivery 2.1.2 → 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +188 -0
- package/README.md +159 -23
- package/assets/CLAUDE.template.md +21 -9
- package/assets/CODEX.template.md +8 -9
- package/assets/agents/backend-engineer.md +3 -1
- package/assets/agents/frontend-engineer.md +3 -2
- package/assets/cdd/conformance.json +16 -0
- package/assets/cdd/tier-policy.json +35 -0
- package/assets/contracts/api/api-contract.md +26 -0
- package/assets/hooks/pre-tool-use-graph-first.sh +65 -0
- package/assets/skills/contract-driven-delivery/scripts/validate_api_conformance.py +543 -0
- package/assets/skills/contract-driven-delivery/scripts/validate_api_semantic.py +8 -1
- package/assets/skills/contract-driven-delivery/scripts/validate_contract_versions.py +4 -0
- package/dist/cli/index.js +2122 -494
- package/docs/adr/0001-contract-to-openapi-export.md +142 -0
- package/docs/adr/0002-schema-carrying-contract-format.md +277 -0
- package/docs/adr/0003-code-intelligence-indexing-strategy.md +110 -0
- package/docs/api-conformance.md +108 -0
- package/docs/openapi-export.md +157 -0
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -101,8 +101,9 @@ var init_logger = __esm({
|
|
|
101
101
|
// src/utils/mcp-hint.ts
|
|
102
102
|
function logRecommendedMcpSetup() {
|
|
103
103
|
log.info("Recommended for AI agents: enable the cdd-kit MCP server.");
|
|
104
|
-
log.dim("
|
|
105
|
-
log.dim(
|
|
104
|
+
log.dim(" Claude Code user-scope setup: claude mcp add --scope user cdd-kit -- cdd-kit mcp");
|
|
105
|
+
log.dim(" Verify in Claude Code: /mcp or `claude mcp list`");
|
|
106
|
+
log.dim(" Note: Claude Code CLI reads MCP servers from ~/.claude.json; ~/.claude/settings.json is not enough.");
|
|
106
107
|
log.dim(" Tools exposed: cdd_graph_context, cdd_graph_query, cdd_graph_impact, cdd_index_query, cdd_index_impact");
|
|
107
108
|
log.dim(" Use MCP graph tools before source reads; CLI fallback: cdd-kit graph/index.");
|
|
108
109
|
}
|
|
@@ -118,26 +119,26 @@ var code_map_hook_exports = {};
|
|
|
118
119
|
__export(code_map_hook_exports, {
|
|
119
120
|
installCodeMapHook: () => installCodeMapHook
|
|
120
121
|
});
|
|
121
|
-
import { existsSync as
|
|
122
|
-
import { join as
|
|
122
|
+
import { existsSync as existsSync4, readFileSync as readFileSync3, writeFileSync as writeFileSync2, chmodSync, mkdirSync as mkdirSync2 } from "fs";
|
|
123
|
+
import { join as join5 } from "path";
|
|
123
124
|
async function installCodeMapHook(cwd) {
|
|
124
|
-
const gitDir =
|
|
125
|
-
if (!
|
|
125
|
+
const gitDir = join5(cwd, ".git");
|
|
126
|
+
if (!existsSync4(gitDir)) {
|
|
126
127
|
log.warn("not a git repository (no .git/ found); skipping code-map hook installation");
|
|
127
128
|
return;
|
|
128
129
|
}
|
|
129
|
-
const hooksDir =
|
|
130
|
+
const hooksDir = join5(gitDir, "hooks");
|
|
130
131
|
mkdirSync2(hooksDir, { recursive: true });
|
|
131
|
-
const dest =
|
|
132
|
+
const dest = join5(hooksDir, "pre-commit");
|
|
132
133
|
let final;
|
|
133
|
-
if (!
|
|
134
|
+
if (!existsSync4(dest)) {
|
|
134
135
|
final = `#!/bin/sh
|
|
135
136
|
set -e
|
|
136
137
|
|
|
137
138
|
${CODE_MAP_BLOCK}
|
|
138
139
|
`;
|
|
139
140
|
} else {
|
|
140
|
-
const existing =
|
|
141
|
+
const existing = readFileSync3(dest, "utf8");
|
|
141
142
|
const startIdx = existing.indexOf(START_MARKER);
|
|
142
143
|
const endIdx = existing.indexOf(END_MARKER);
|
|
143
144
|
if (startIdx >= 0 && endIdx > startIdx) {
|
|
@@ -149,15 +150,15 @@ ${CODE_MAP_BLOCK}
|
|
|
149
150
|
final = trimmed + "\n\n" + CODE_MAP_BLOCK + "\n";
|
|
150
151
|
}
|
|
151
152
|
}
|
|
152
|
-
|
|
153
|
+
writeFileSync2(dest, final, "utf8");
|
|
153
154
|
try {
|
|
154
155
|
chmodSync(dest, 493);
|
|
155
156
|
} catch {
|
|
156
157
|
}
|
|
157
158
|
try {
|
|
158
|
-
const cddDir =
|
|
159
|
+
const cddDir = join5(cwd, ".cdd");
|
|
159
160
|
mkdirSync2(cddDir, { recursive: true });
|
|
160
|
-
|
|
161
|
+
writeFileSync2(join5(cddDir, ".hooks-installed"), `installed: ${(/* @__PURE__ */ new Date()).toISOString()}
|
|
161
162
|
`, "utf8");
|
|
162
163
|
} catch {
|
|
163
164
|
}
|
|
@@ -191,27 +192,217 @@ ${END_MARKER}`;
|
|
|
191
192
|
}
|
|
192
193
|
});
|
|
193
194
|
|
|
194
|
-
// src/
|
|
195
|
-
|
|
195
|
+
// src/commands/install-agent-hooks.ts
|
|
196
|
+
var install_agent_hooks_exports = {};
|
|
197
|
+
__export(install_agent_hooks_exports, {
|
|
198
|
+
installAgentHooks: () => installAgentHooks
|
|
199
|
+
});
|
|
200
|
+
import { existsSync as existsSync5, readFileSync as readFileSync4, writeFileSync as writeFileSync3, copyFileSync as copyFileSync2, chmodSync as chmodSync2, mkdirSync as mkdirSync3 } from "fs";
|
|
196
201
|
import { join as join6 } from "path";
|
|
202
|
+
async function installAgentHooks(opts = {}) {
|
|
203
|
+
const mode = opts.graphFirst ?? "advisory";
|
|
204
|
+
if (mode !== "advisory" && mode !== "strict") {
|
|
205
|
+
log.error(`invalid mode: ${mode}. Use 'advisory' or 'strict'.`);
|
|
206
|
+
process.exit(1);
|
|
207
|
+
}
|
|
208
|
+
const cwd = process.cwd();
|
|
209
|
+
const srcHook = join6(ASSET.hooks, HOOK_FILENAME);
|
|
210
|
+
if (!existsSync5(srcHook)) {
|
|
211
|
+
if (opts.fromInit) {
|
|
212
|
+
log.warn(`graph-first hook not armed: bundled hook missing (${srcHook}). Reinstall the package, then run \`cdd-kit install-agent-hooks\`.`);
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
log.error(`bundled hook not found: ${srcHook}. Reinstall the cdd-kit package.`);
|
|
216
|
+
process.exit(1);
|
|
217
|
+
}
|
|
218
|
+
const destHook = join6(cwd, HOOK_REL_PATH);
|
|
219
|
+
mkdirSync3(join6(cwd, ".claude", "hooks"), { recursive: true });
|
|
220
|
+
copyFileSync2(srcHook, destHook);
|
|
221
|
+
try {
|
|
222
|
+
chmodSync2(destHook, 493);
|
|
223
|
+
} catch {
|
|
224
|
+
}
|
|
225
|
+
const settingsPath = join6(cwd, SETTINGS_REL_PATH);
|
|
226
|
+
let settings = {};
|
|
227
|
+
if (existsSync5(settingsPath)) {
|
|
228
|
+
try {
|
|
229
|
+
settings = JSON.parse(readFileSync4(settingsPath, "utf8"));
|
|
230
|
+
} catch (err) {
|
|
231
|
+
if (opts.fromInit) {
|
|
232
|
+
log.warn(`graph-first hook not armed: ${SETTINGS_REL_PATH} is not valid JSON (${err.message}). Fix it, then run \`cdd-kit install-agent-hooks\`.`);
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
log.error(`${SETTINGS_REL_PATH} is not valid JSON: ${err.message}. Fix or remove it, then re-run.`);
|
|
236
|
+
process.exit(1);
|
|
237
|
+
}
|
|
238
|
+
if (typeof settings !== "object" || settings === null || Array.isArray(settings)) {
|
|
239
|
+
if (opts.fromInit) {
|
|
240
|
+
log.warn(`graph-first hook not armed: ${SETTINGS_REL_PATH} must be a JSON object. Fix it, then run \`cdd-kit install-agent-hooks\`.`);
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
log.error(`${SETTINGS_REL_PATH} must be a JSON object.`);
|
|
244
|
+
process.exit(1);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
settings.hooks = settings.hooks ?? {};
|
|
248
|
+
const preTool = Array.isArray(settings.hooks.PreToolUse) ? settings.hooks.PreToolUse : [];
|
|
249
|
+
const isOurHandler = (h) => typeof h?.command === "string" && h.command.includes(HOOK_MARKER);
|
|
250
|
+
const preserved = [];
|
|
251
|
+
for (const e of preTool) {
|
|
252
|
+
if (typeof e?.command === "string" && e.command.includes(HOOK_MARKER) && !Array.isArray(e?.hooks)) {
|
|
253
|
+
continue;
|
|
254
|
+
}
|
|
255
|
+
if (Array.isArray(e?.hooks) && e.hooks.some(isOurHandler)) {
|
|
256
|
+
const others = e.hooks.filter((h) => !isOurHandler(h));
|
|
257
|
+
if (others.length === 0)
|
|
258
|
+
continue;
|
|
259
|
+
preserved.push({ ...e, hooks: others });
|
|
260
|
+
continue;
|
|
261
|
+
}
|
|
262
|
+
preserved.push(e);
|
|
263
|
+
}
|
|
264
|
+
const invoke = `./${HOOK_REL_PATH}`;
|
|
265
|
+
const command = mode === "strict" ? `CDD_GRAPH_FIRST_STRICT=1 ${invoke}` : invoke;
|
|
266
|
+
preserved.push({ matcher: "Read", hooks: [{ type: "command", command }] });
|
|
267
|
+
settings.hooks.PreToolUse = preserved;
|
|
268
|
+
mkdirSync3(join6(cwd, ".claude"), { recursive: true });
|
|
269
|
+
writeFileSync3(settingsPath, JSON.stringify(settings, null, 2) + "\n", "utf8");
|
|
270
|
+
log.ok(`graph-first PreToolUse hook installed (${mode}) at ${SETTINGS_REL_PATH}`);
|
|
271
|
+
log.info(`hook script: ${HOOK_REL_PATH}`);
|
|
272
|
+
if (mode === "advisory") {
|
|
273
|
+
log.info("advisory mode: reminds agents to use `cdd-kit index query --with-source` before Read; does not block.");
|
|
274
|
+
log.info("re-run with `--graph-first strict` to hard-block source Reads when a code-map exists.");
|
|
275
|
+
} else {
|
|
276
|
+
log.info("strict mode: blocks source-file Read when .cdd/code-map.yml exists, steering to graph/index queries.");
|
|
277
|
+
log.info("re-run with `--graph-first advisory` to downgrade to reminders only.");
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
var HOOK_FILENAME, HOOK_REL_PATH, SETTINGS_REL_PATH, HOOK_MARKER;
|
|
281
|
+
var init_install_agent_hooks = __esm({
|
|
282
|
+
"src/commands/install-agent-hooks.ts"() {
|
|
283
|
+
"use strict";
|
|
284
|
+
init_paths();
|
|
285
|
+
init_logger();
|
|
286
|
+
HOOK_FILENAME = "pre-tool-use-graph-first.sh";
|
|
287
|
+
HOOK_REL_PATH = `.claude/hooks/${HOOK_FILENAME}`;
|
|
288
|
+
SETTINGS_REL_PATH = ".claude/settings.json";
|
|
289
|
+
HOOK_MARKER = "pre-tool-use-graph-first";
|
|
290
|
+
}
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
// src/commands/install-hooks.ts
|
|
294
|
+
var install_hooks_exports = {};
|
|
295
|
+
__export(install_hooks_exports, {
|
|
296
|
+
installHooks: () => installHooks
|
|
297
|
+
});
|
|
298
|
+
import { existsSync as existsSync6, readFileSync as readFileSync5, writeFileSync as writeFileSync4, chmodSync as chmodSync3, mkdirSync as mkdirSync4, statSync as statSync2 } from "fs";
|
|
299
|
+
import { join as join7, resolve } from "path";
|
|
300
|
+
import { spawnSync } from "child_process";
|
|
301
|
+
function resolveHooksDir(cwd, gitPath, fromInit) {
|
|
302
|
+
const res = spawnSync("git", ["rev-parse", "--git-path", "hooks"], { cwd, encoding: "utf8" });
|
|
303
|
+
if (res.status === 0 && res.stdout.trim()) {
|
|
304
|
+
return resolve(cwd, res.stdout.trim());
|
|
305
|
+
}
|
|
306
|
+
let gitIsDir = false;
|
|
307
|
+
try {
|
|
308
|
+
gitIsDir = statSync2(gitPath).isDirectory();
|
|
309
|
+
} catch {
|
|
310
|
+
}
|
|
311
|
+
if (gitIsDir)
|
|
312
|
+
return join7(gitPath, "hooks");
|
|
313
|
+
const why = "`.git` is a worktree/submodule pointer and git could not resolve the hooks path";
|
|
314
|
+
if (fromInit) {
|
|
315
|
+
log.warn(`pre-commit gate not armed: ${why}. Run \`cdd-kit install-hooks\` in the main checkout.`);
|
|
316
|
+
return null;
|
|
317
|
+
}
|
|
318
|
+
log.error(`cannot resolve hooks dir: ${why}. Run this in the main checkout.`);
|
|
319
|
+
process.exit(1);
|
|
320
|
+
}
|
|
321
|
+
async function installHooks(opts = {}) {
|
|
322
|
+
const cwd = process.cwd();
|
|
323
|
+
const gitPath = join7(cwd, ".git");
|
|
324
|
+
if (!existsSync6(gitPath)) {
|
|
325
|
+
if (opts.fromInit) {
|
|
326
|
+
log.warn("pre-commit gate not armed: not a git repository yet. Run `cdd-kit install-hooks` after `git init`.");
|
|
327
|
+
return;
|
|
328
|
+
}
|
|
329
|
+
log.error("not a git repository (no .git/ found in cwd)");
|
|
330
|
+
process.exit(1);
|
|
331
|
+
}
|
|
332
|
+
const hooksDir = resolveHooksDir(cwd, gitPath, opts.fromInit ?? false);
|
|
333
|
+
if (hooksDir === null)
|
|
334
|
+
return;
|
|
335
|
+
mkdirSync4(hooksDir, { recursive: true });
|
|
336
|
+
const dest = join7(hooksDir, "pre-commit");
|
|
337
|
+
const ourHook = readFileSync5(join7(ASSET.hooks, "pre-commit"), "utf8");
|
|
338
|
+
let final;
|
|
339
|
+
if (!existsSync6(dest)) {
|
|
340
|
+
final = ourHook;
|
|
341
|
+
} else {
|
|
342
|
+
const existing = readFileSync5(dest, "utf8");
|
|
343
|
+
const startIdx = existing.indexOf(START_MARKER2);
|
|
344
|
+
const endIdx = existing.indexOf(END_MARKER2);
|
|
345
|
+
if (startIdx >= 0 && endIdx > startIdx) {
|
|
346
|
+
const before = existing.slice(0, startIdx);
|
|
347
|
+
const after = existing.slice(endIdx + END_MARKER2.length);
|
|
348
|
+
const ourStart = ourHook.indexOf(START_MARKER2);
|
|
349
|
+
const ourEnd = ourHook.indexOf(END_MARKER2) + END_MARKER2.length;
|
|
350
|
+
const ourBlock = ourHook.slice(ourStart, ourEnd);
|
|
351
|
+
final = before + ourBlock + after;
|
|
352
|
+
} else {
|
|
353
|
+
const ourStart = ourHook.indexOf(START_MARKER2);
|
|
354
|
+
const ourEnd = ourHook.indexOf(END_MARKER2) + END_MARKER2.length;
|
|
355
|
+
const ourBlock = ourHook.slice(ourStart, ourEnd);
|
|
356
|
+
if (existing.startsWith("#!")) {
|
|
357
|
+
const firstNewline = existing.indexOf("\n");
|
|
358
|
+
const shebang = existing.slice(0, firstNewline + 1);
|
|
359
|
+
const rest = existing.slice(firstNewline + 1);
|
|
360
|
+
final = shebang + "\n" + ourBlock + "\n" + rest;
|
|
361
|
+
} else {
|
|
362
|
+
final = "#!/bin/sh\n" + ourBlock + "\n" + existing;
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
writeFileSync4(dest, final, "utf8");
|
|
367
|
+
try {
|
|
368
|
+
chmodSync3(dest, 493);
|
|
369
|
+
} catch {
|
|
370
|
+
}
|
|
371
|
+
log.ok(`pre-commit hook installed at ${dest}`);
|
|
372
|
+
log.info("cdd-kit gate will now run automatically before each commit affecting specs/changes/");
|
|
373
|
+
}
|
|
374
|
+
var START_MARKER2, END_MARKER2;
|
|
375
|
+
var init_install_hooks = __esm({
|
|
376
|
+
"src/commands/install-hooks.ts"() {
|
|
377
|
+
"use strict";
|
|
378
|
+
init_paths();
|
|
379
|
+
init_logger();
|
|
380
|
+
START_MARKER2 = "# cdd-kit-managed-block-start";
|
|
381
|
+
END_MARKER2 = "# cdd-kit-managed-block-end";
|
|
382
|
+
}
|
|
383
|
+
});
|
|
384
|
+
|
|
385
|
+
// src/utils/provider.ts
|
|
386
|
+
import { existsSync as existsSync8, readFileSync as readFileSync7 } from "fs";
|
|
387
|
+
import { join as join9 } from "path";
|
|
197
388
|
function validateProviderOption(provider) {
|
|
198
389
|
return provider === "auto" || provider === "claude" || provider === "codex" || provider === "both";
|
|
199
390
|
}
|
|
200
391
|
function inferProvider(cwd, requested = "auto") {
|
|
201
392
|
if (requested !== "auto")
|
|
202
393
|
return requested;
|
|
203
|
-
const modelPolicyPath =
|
|
204
|
-
if (
|
|
394
|
+
const modelPolicyPath = join9(cwd, ".cdd", "model-policy.json");
|
|
395
|
+
if (existsSync8(modelPolicyPath)) {
|
|
205
396
|
try {
|
|
206
|
-
const policy = JSON.parse(
|
|
397
|
+
const policy = JSON.parse(readFileSync7(modelPolicyPath, "utf8"));
|
|
207
398
|
if (policy.provider === "claude" || policy.provider === "codex" || policy.provider === "both") {
|
|
208
399
|
return policy.provider;
|
|
209
400
|
}
|
|
210
401
|
} catch {
|
|
211
402
|
}
|
|
212
403
|
}
|
|
213
|
-
const hasClaude =
|
|
214
|
-
const hasCodex =
|
|
404
|
+
const hasClaude = existsSync8(join9(cwd, "CLAUDE.md")) || existsSync8(join9(cwd, "AGENTS.md"));
|
|
405
|
+
const hasCodex = existsSync8(join9(cwd, "CODEX.md"));
|
|
215
406
|
if (hasClaude && hasCodex)
|
|
216
407
|
return "both";
|
|
217
408
|
if (hasCodex)
|
|
@@ -225,27 +416,27 @@ var init_provider = __esm({
|
|
|
225
416
|
});
|
|
226
417
|
|
|
227
418
|
// src/commands/update.ts
|
|
228
|
-
import { join as
|
|
229
|
-
import { existsSync as
|
|
419
|
+
import { join as join10 } from "path";
|
|
420
|
+
import { existsSync as existsSync9, mkdirSync as mkdirSync5, readdirSync as readdirSync3, copyFileSync as copyFileSync3, readFileSync as readFileSync8 } from "fs";
|
|
230
421
|
import { createHash } from "crypto";
|
|
231
422
|
import { homedir as homedir2 } from "os";
|
|
232
423
|
function fileHash(filePath) {
|
|
233
|
-
const buf =
|
|
424
|
+
const buf = readFileSync8(filePath);
|
|
234
425
|
return createHash("sha256").update(buf).digest("hex");
|
|
235
426
|
}
|
|
236
427
|
function diffDir(src, dest) {
|
|
237
428
|
const entries = [];
|
|
238
|
-
if (!
|
|
429
|
+
if (!existsSync9(src))
|
|
239
430
|
return entries;
|
|
240
431
|
function walk(currentSrc, currentDest) {
|
|
241
432
|
const items = readdirSync3(currentSrc, { withFileTypes: true });
|
|
242
433
|
for (const item of items) {
|
|
243
|
-
const srcPath =
|
|
244
|
-
const destPath =
|
|
434
|
+
const srcPath = join10(currentSrc, item.name);
|
|
435
|
+
const destPath = join10(currentDest, item.name);
|
|
245
436
|
if (item.isDirectory()) {
|
|
246
437
|
walk(srcPath, destPath);
|
|
247
438
|
} else {
|
|
248
|
-
if (!
|
|
439
|
+
if (!existsSync9(destPath)) {
|
|
249
440
|
entries.push({ src: srcPath, dest: destPath, action: "add" });
|
|
250
441
|
} else if (fileHash(srcPath) !== fileHash(destPath)) {
|
|
251
442
|
entries.push({ src: srcPath, dest: destPath, action: "overwrite" });
|
|
@@ -263,33 +454,33 @@ function applyDir(entries) {
|
|
|
263
454
|
for (const e of entries) {
|
|
264
455
|
if (e.action === "skip")
|
|
265
456
|
continue;
|
|
266
|
-
|
|
267
|
-
|
|
457
|
+
mkdirSync5(join10(e.dest, ".."), { recursive: true });
|
|
458
|
+
copyFileSync3(e.src, e.dest);
|
|
268
459
|
count += 1;
|
|
269
460
|
}
|
|
270
461
|
return count;
|
|
271
462
|
}
|
|
272
463
|
function backupDir(dir, backupDest) {
|
|
273
|
-
if (!
|
|
464
|
+
if (!existsSync9(dir))
|
|
274
465
|
return;
|
|
275
|
-
|
|
466
|
+
mkdirSync5(backupDest, { recursive: true });
|
|
276
467
|
function walk(src, dst) {
|
|
277
468
|
const items = readdirSync3(src, { withFileTypes: true });
|
|
278
469
|
for (const item of items) {
|
|
279
|
-
const s =
|
|
280
|
-
const d =
|
|
470
|
+
const s = join10(src, item.name);
|
|
471
|
+
const d = join10(dst, item.name);
|
|
281
472
|
if (item.isDirectory()) {
|
|
282
|
-
|
|
473
|
+
mkdirSync5(d, { recursive: true });
|
|
283
474
|
walk(s, d);
|
|
284
475
|
} else
|
|
285
|
-
|
|
476
|
+
copyFileSync3(s, d);
|
|
286
477
|
}
|
|
287
478
|
}
|
|
288
479
|
walk(dir, backupDest);
|
|
289
480
|
}
|
|
290
481
|
async function update(opts) {
|
|
291
482
|
if (opts.postinstall) {
|
|
292
|
-
if (!
|
|
483
|
+
if (!existsSync9(join10(SKILLS_HOME, "contract-driven-delivery"))) {
|
|
293
484
|
return;
|
|
294
485
|
}
|
|
295
486
|
opts.yes = true;
|
|
@@ -307,7 +498,7 @@ async function update(opts) {
|
|
|
307
498
|
const provider = inferProvider(cwd, requestedProvider);
|
|
308
499
|
const updateClaudeAssets = provider === "claude" || provider === "both";
|
|
309
500
|
const agentDiff = updateClaudeAssets ? diffDir(ASSET.agents, AGENTS_HOME) : [];
|
|
310
|
-
const skillDiff = updateClaudeAssets ? readdirSync3(ASSET.skills, { withFileTypes: true }).filter((d) => d.isDirectory()).flatMap((d) => diffDir(
|
|
501
|
+
const skillDiff = updateClaudeAssets ? readdirSync3(ASSET.skills, { withFileTypes: true }).filter((d) => d.isDirectory()).flatMap((d) => diffDir(join10(ASSET.skills, d.name), join10(SKILLS_HOME, d.name))) : [];
|
|
311
502
|
const toWrite = [...agentDiff, ...skillDiff].filter((e) => e.action !== "skip");
|
|
312
503
|
const toAdd = toWrite.filter((e) => e.action === "add");
|
|
313
504
|
const toOver = toWrite.filter((e) => e.action === "overwrite");
|
|
@@ -345,13 +536,13 @@ async function update(opts) {
|
|
|
345
536
|
return;
|
|
346
537
|
}
|
|
347
538
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
348
|
-
const backupRoot =
|
|
539
|
+
const backupRoot = join10(homedir2(), ".claude", ".cdd-kit-backup", timestamp);
|
|
349
540
|
if (!quiet) {
|
|
350
541
|
log.blank();
|
|
351
542
|
log.info(`Backing up to ${backupRoot} \u2026`);
|
|
352
543
|
}
|
|
353
|
-
backupDir(AGENTS_HOME,
|
|
354
|
-
backupDir(SKILLS_HOME,
|
|
544
|
+
backupDir(AGENTS_HOME, join10(backupRoot, "agents"));
|
|
545
|
+
backupDir(SKILLS_HOME, join10(backupRoot, "skills"));
|
|
355
546
|
if (!quiet)
|
|
356
547
|
log.ok(`Backup complete: ${backupRoot}`);
|
|
357
548
|
if (!quiet)
|
|
@@ -392,7 +583,7 @@ var init_update = __esm({
|
|
|
392
583
|
});
|
|
393
584
|
|
|
394
585
|
// src/utils/digest.ts
|
|
395
|
-
import { readFileSync as
|
|
586
|
+
import { readFileSync as readFileSync9 } from "fs";
|
|
396
587
|
import { createHash as createHash2 } from "crypto";
|
|
397
588
|
function normalizeContentForHash(buf) {
|
|
398
589
|
if (!buf.includes(13))
|
|
@@ -403,7 +594,7 @@ function normalizeContentForHash(buf) {
|
|
|
403
594
|
function sha256OfFileNormalized(path) {
|
|
404
595
|
let buf;
|
|
405
596
|
try {
|
|
406
|
-
buf =
|
|
597
|
+
buf = readFileSync9(path);
|
|
407
598
|
} catch {
|
|
408
599
|
return "";
|
|
409
600
|
}
|
|
@@ -420,9 +611,9 @@ var context_scan_exports = {};
|
|
|
420
611
|
__export(context_scan_exports, {
|
|
421
612
|
contextScan: () => contextScan
|
|
422
613
|
});
|
|
423
|
-
import { existsSync as
|
|
614
|
+
import { existsSync as existsSync10, mkdirSync as mkdirSync6, readFileSync as readFileSync10, readdirSync as readdirSync4, writeFileSync as writeFileSync6 } from "fs";
|
|
424
615
|
import { createHash as createHash3 } from "crypto";
|
|
425
|
-
import { basename, dirname as dirname3, join as
|
|
616
|
+
import { basename, dirname as dirname3, join as join11, relative as relative2 } from "path";
|
|
426
617
|
function inputsDigest(paths, cwd) {
|
|
427
618
|
const combined = paths.slice().sort().map((p) => {
|
|
428
619
|
const rel = relative2(cwd, p).replace(/\\/g, "/");
|
|
@@ -435,10 +626,10 @@ function stripGlobSuffix(pattern) {
|
|
|
435
626
|
}
|
|
436
627
|
function getForbiddenPaths(cwd) {
|
|
437
628
|
const forbidden = new Set(DEFAULT_FORBIDDEN);
|
|
438
|
-
const policyPath =
|
|
629
|
+
const policyPath = join11(cwd, ".cdd", "context-policy.json");
|
|
439
630
|
try {
|
|
440
|
-
if (
|
|
441
|
-
const policy = JSON.parse(
|
|
631
|
+
if (existsSync10(policyPath)) {
|
|
632
|
+
const policy = JSON.parse(readFileSync10(policyPath, "utf8"));
|
|
442
633
|
for (const pattern of policy.forbiddenPaths ?? []) {
|
|
443
634
|
forbidden.add(stripGlobSuffix(pattern));
|
|
444
635
|
}
|
|
@@ -467,7 +658,7 @@ function buildTree(dir, cwd, forbidden, stats, prefix = "", depth = 0) {
|
|
|
467
658
|
});
|
|
468
659
|
let output = "";
|
|
469
660
|
const visible = entries.filter((entry) => {
|
|
470
|
-
const relPath = relative2(cwd,
|
|
661
|
+
const relPath = relative2(cwd, join11(dir, entry.name));
|
|
471
662
|
return !isForbidden(relPath, forbidden);
|
|
472
663
|
});
|
|
473
664
|
const truncated = visible.length > PER_DIR_ENTRY_CAP;
|
|
@@ -475,7 +666,7 @@ function buildTree(dir, cwd, forbidden, stats, prefix = "", depth = 0) {
|
|
|
475
666
|
if (truncated)
|
|
476
667
|
stats.truncatedDirs += 1;
|
|
477
668
|
shown.forEach((entry, index2) => {
|
|
478
|
-
const fullPath =
|
|
669
|
+
const fullPath = join11(dir, entry.name);
|
|
479
670
|
const isLast = index2 === shown.length - 1 && !truncated;
|
|
480
671
|
const connector = isLast ? "\\-- " : "|-- ";
|
|
481
672
|
output += `${prefix}${connector}${entry.name}${entry.isDirectory() ? "/" : ""}
|
|
@@ -537,10 +728,10 @@ function parseContractMetadata(content) {
|
|
|
537
728
|
return { title: firstHeading(content), summary, metadata };
|
|
538
729
|
}
|
|
539
730
|
function findContractFiles(dir, found = []) {
|
|
540
|
-
if (!
|
|
731
|
+
if (!existsSync10(dir))
|
|
541
732
|
return found;
|
|
542
733
|
for (const entry of readdirSync4(dir, { withFileTypes: true })) {
|
|
543
|
-
const fullPath =
|
|
734
|
+
const fullPath = join11(dir, entry.name);
|
|
544
735
|
if (entry.isDirectory())
|
|
545
736
|
findContractFiles(fullPath, found);
|
|
546
737
|
else if (entry.isFile() && entry.name.endsWith(".md") && entry.name !== "INDEX.md" && entry.name !== "CHANGELOG.md")
|
|
@@ -550,14 +741,14 @@ function findContractFiles(dir, found = []) {
|
|
|
550
741
|
}
|
|
551
742
|
async function contextScan(opts = {}) {
|
|
552
743
|
const cwd = process.cwd();
|
|
553
|
-
const specsContextDir =
|
|
554
|
-
|
|
744
|
+
const specsContextDir = join11(cwd, "specs", "context");
|
|
745
|
+
mkdirSync6(specsContextDir, { recursive: true });
|
|
555
746
|
const forbidden = getForbiddenPaths(cwd);
|
|
556
747
|
const surface = opts.surface;
|
|
557
748
|
let scanRoot = cwd;
|
|
558
749
|
if (surface) {
|
|
559
|
-
const resolvedSurface =
|
|
560
|
-
if (!
|
|
750
|
+
const resolvedSurface = join11(cwd, surface);
|
|
751
|
+
if (!existsSync10(resolvedSurface)) {
|
|
561
752
|
log.error(`--surface path not found: ${surface}`);
|
|
562
753
|
process.exit(1);
|
|
563
754
|
}
|
|
@@ -569,10 +760,10 @@ async function contextScan(opts = {}) {
|
|
|
569
760
|
}
|
|
570
761
|
const treeStats = { dirs: 0, files: 0, omittedDirs: 0, truncatedDirs: 0 };
|
|
571
762
|
const tree = buildTree(scanRoot, cwd, forbidden, treeStats);
|
|
572
|
-
const policyPath =
|
|
573
|
-
const projectMapInputs = [policyPath].filter(
|
|
574
|
-
|
|
575
|
-
|
|
763
|
+
const policyPath = join11(cwd, ".cdd", "context-policy.json");
|
|
764
|
+
const projectMapInputs = [policyPath].filter(existsSync10);
|
|
765
|
+
writeFileSync6(
|
|
766
|
+
join11(specsContextDir, "project-map.md"),
|
|
576
767
|
[
|
|
577
768
|
"---",
|
|
578
769
|
"artifact: project-map",
|
|
@@ -605,14 +796,14 @@ async function contextScan(opts = {}) {
|
|
|
605
796
|
"utf8"
|
|
606
797
|
);
|
|
607
798
|
log.ok("Created specs/context/project-map.md");
|
|
608
|
-
const contractFiles = findContractFiles(
|
|
799
|
+
const contractFiles = findContractFiles(join11(cwd, "contracts")).sort((a, b) => relative2(cwd, a).localeCompare(relative2(cwd, b)));
|
|
609
800
|
const contractEntries = [];
|
|
610
801
|
const inventoryRows = [];
|
|
611
802
|
let missingSummary = 0;
|
|
612
803
|
for (const file of contractFiles) {
|
|
613
804
|
const relPath = relative2(cwd, file).replace(/\\/g, "/");
|
|
614
805
|
const dir = dirname3(relPath).replace(/\\/g, "/");
|
|
615
|
-
const { title, summary, metadata } = parseContractMetadata(
|
|
806
|
+
const { title, summary, metadata } = parseContractMetadata(readFileSync10(file, "utf8"));
|
|
616
807
|
const contractType = deriveContractType(relPath, metadata);
|
|
617
808
|
const owner = metadata.owner ?? "unknown";
|
|
618
809
|
const surface2 = metadata.surface ?? dir;
|
|
@@ -667,7 +858,7 @@ async function contextScan(opts = {}) {
|
|
|
667
858
|
"",
|
|
668
859
|
...contractEntries
|
|
669
860
|
].join("\n");
|
|
670
|
-
|
|
861
|
+
writeFileSync6(join11(specsContextDir, "contracts-index.md"), contractIndex, "utf8");
|
|
671
862
|
if (missingSummary > 0) {
|
|
672
863
|
log.warn(`Created specs/context/contracts-index.md with ${missingSummary} missing summary warning(s).`);
|
|
673
864
|
} else {
|
|
@@ -3694,7 +3885,7 @@ var require_compile = __commonJS({
|
|
|
3694
3885
|
const schOrFunc = root.refs[ref];
|
|
3695
3886
|
if (schOrFunc)
|
|
3696
3887
|
return schOrFunc;
|
|
3697
|
-
let _sch =
|
|
3888
|
+
let _sch = resolve6.call(this, root, ref);
|
|
3698
3889
|
if (_sch === void 0) {
|
|
3699
3890
|
const schema = (_a = root.localRefs) === null || _a === void 0 ? void 0 : _a[ref];
|
|
3700
3891
|
const { schemaId } = this.opts;
|
|
@@ -3721,7 +3912,7 @@ var require_compile = __commonJS({
|
|
|
3721
3912
|
function sameSchemaEnv(s1, s2) {
|
|
3722
3913
|
return s1.schema === s2.schema && s1.root === s2.root && s1.baseId === s2.baseId;
|
|
3723
3914
|
}
|
|
3724
|
-
function
|
|
3915
|
+
function resolve6(root, ref) {
|
|
3725
3916
|
let sch;
|
|
3726
3917
|
while (typeof (sch = this.refs[ref]) == "string")
|
|
3727
3918
|
ref = sch;
|
|
@@ -4297,7 +4488,7 @@ var require_fast_uri = __commonJS({
|
|
|
4297
4488
|
}
|
|
4298
4489
|
return uri;
|
|
4299
4490
|
}
|
|
4300
|
-
function
|
|
4491
|
+
function resolve6(baseURI, relativeURI, options) {
|
|
4301
4492
|
const schemelessOptions = options ? Object.assign({ scheme: "null" }, options) : { scheme: "null" };
|
|
4302
4493
|
const resolved = resolveComponent(parse3(baseURI, schemelessOptions), parse3(relativeURI, schemelessOptions), schemelessOptions, true);
|
|
4303
4494
|
schemelessOptions.skipEscape = true;
|
|
@@ -4525,7 +4716,7 @@ var require_fast_uri = __commonJS({
|
|
|
4525
4716
|
var fastUri = {
|
|
4526
4717
|
SCHEMES,
|
|
4527
4718
|
normalize,
|
|
4528
|
-
resolve:
|
|
4719
|
+
resolve: resolve6,
|
|
4529
4720
|
resolveComponent,
|
|
4530
4721
|
equal,
|
|
4531
4722
|
serialize,
|
|
@@ -7514,18 +7705,265 @@ var require_dist = __commonJS({
|
|
|
7514
7705
|
}
|
|
7515
7706
|
});
|
|
7516
7707
|
|
|
7708
|
+
// src/utils/tier-floor.ts
|
|
7709
|
+
import { existsSync as existsSync13, readFileSync as readFileSync12 } from "fs";
|
|
7710
|
+
import { join as join14 } from "path";
|
|
7711
|
+
function loadTierPolicy(cwd) {
|
|
7712
|
+
const path = join14(cwd, ".cdd", "tier-policy.json");
|
|
7713
|
+
if (!existsSync13(path))
|
|
7714
|
+
return DEFAULT_TIER_POLICY;
|
|
7715
|
+
try {
|
|
7716
|
+
const raw = JSON.parse(readFileSync12(path, "utf8"));
|
|
7717
|
+
if (typeof raw.enabled === "boolean" && raw.enabled === false) {
|
|
7718
|
+
return { enabled: false, rules: [] };
|
|
7719
|
+
}
|
|
7720
|
+
if (!Array.isArray(raw.rules))
|
|
7721
|
+
return DEFAULT_TIER_POLICY;
|
|
7722
|
+
const rules = [];
|
|
7723
|
+
for (const r of raw.rules) {
|
|
7724
|
+
if (r && typeof r === "object" && typeof r.maxTier === "number" && Array.isArray(r.patterns)) {
|
|
7725
|
+
const rule = r;
|
|
7726
|
+
rules.push({
|
|
7727
|
+
maxTier: rule.maxTier,
|
|
7728
|
+
label: typeof rule.label === "string" ? rule.label : `tier ${rule.maxTier} surface`,
|
|
7729
|
+
patterns: rule.patterns.filter((p) => typeof p === "string")
|
|
7730
|
+
});
|
|
7731
|
+
}
|
|
7732
|
+
}
|
|
7733
|
+
if (rules.length === 0)
|
|
7734
|
+
return DEFAULT_TIER_POLICY;
|
|
7735
|
+
return { enabled: raw.enabled !== false, rules };
|
|
7736
|
+
} catch {
|
|
7737
|
+
return DEFAULT_TIER_POLICY;
|
|
7738
|
+
}
|
|
7739
|
+
}
|
|
7740
|
+
function patternToRegExp(pattern) {
|
|
7741
|
+
try {
|
|
7742
|
+
return new RegExp(`(?<![a-z0-9])(?:${pattern})(?![a-z0-9])`, "i");
|
|
7743
|
+
} catch {
|
|
7744
|
+
return null;
|
|
7745
|
+
}
|
|
7746
|
+
}
|
|
7747
|
+
function computeTierFloor(text, policy = DEFAULT_TIER_POLICY, opts = {}) {
|
|
7748
|
+
if (!policy.enabled) {
|
|
7749
|
+
return { floorTier: null, label: null, matched: [] };
|
|
7750
|
+
}
|
|
7751
|
+
let floorTier = null;
|
|
7752
|
+
let label = null;
|
|
7753
|
+
const matched = /* @__PURE__ */ new Set();
|
|
7754
|
+
const scan = (rules, haystack) => {
|
|
7755
|
+
if (!haystack)
|
|
7756
|
+
return;
|
|
7757
|
+
for (const rule of rules) {
|
|
7758
|
+
let ruleHit = false;
|
|
7759
|
+
for (const pattern of rule.patterns) {
|
|
7760
|
+
const re = patternToRegExp(pattern);
|
|
7761
|
+
const hit = re ? re.exec(haystack) : null;
|
|
7762
|
+
if (hit) {
|
|
7763
|
+
ruleHit = true;
|
|
7764
|
+
matched.add(hit[0].toLowerCase().replace(/\s+/g, " ").trim());
|
|
7765
|
+
}
|
|
7766
|
+
}
|
|
7767
|
+
if (ruleHit && (floorTier === null || rule.maxTier < floorTier)) {
|
|
7768
|
+
floorTier = rule.maxTier;
|
|
7769
|
+
label = rule.label;
|
|
7770
|
+
}
|
|
7771
|
+
}
|
|
7772
|
+
};
|
|
7773
|
+
scan(policy.rules, text);
|
|
7774
|
+
const paths = opts.paths ?? [];
|
|
7775
|
+
if (paths.length > 0) {
|
|
7776
|
+
scan(policy.rules.filter((r) => r.maxTier === 0), paths.join("\n"));
|
|
7777
|
+
}
|
|
7778
|
+
return { floorTier, label, matched: [...matched].sort() };
|
|
7779
|
+
}
|
|
7780
|
+
var DEFAULT_TIER_POLICY;
|
|
7781
|
+
var init_tier_floor = __esm({
|
|
7782
|
+
"src/utils/tier-floor.ts"() {
|
|
7783
|
+
"use strict";
|
|
7784
|
+
DEFAULT_TIER_POLICY = {
|
|
7785
|
+
enabled: true,
|
|
7786
|
+
rules: [
|
|
7787
|
+
{
|
|
7788
|
+
maxTier: 0,
|
|
7789
|
+
label: "critical surface (auth / payments / data migration / concurrency / secrets)",
|
|
7790
|
+
patterns: [
|
|
7791
|
+
"auth",
|
|
7792
|
+
"authn",
|
|
7793
|
+
"authz",
|
|
7794
|
+
"authentication",
|
|
7795
|
+
"authorization",
|
|
7796
|
+
"authenticate",
|
|
7797
|
+
"authorize",
|
|
7798
|
+
"authenticated",
|
|
7799
|
+
"authorized",
|
|
7800
|
+
"login",
|
|
7801
|
+
"logout",
|
|
7802
|
+
"sign-?in",
|
|
7803
|
+
"sign-?up",
|
|
7804
|
+
"passwords?",
|
|
7805
|
+
"passwd",
|
|
7806
|
+
"credentials?",
|
|
7807
|
+
"secrets?",
|
|
7808
|
+
"api[- ]?keys?",
|
|
7809
|
+
// Qualified so security tokens trip the floor but design/CSS "tokens" do not.
|
|
7810
|
+
"(access|api|auth|session|bearer|refresh|csrf|id|reset)[- ]?tokens?",
|
|
7811
|
+
"jwt",
|
|
7812
|
+
"oauth",
|
|
7813
|
+
"oidc",
|
|
7814
|
+
"saml",
|
|
7815
|
+
"sessions?",
|
|
7816
|
+
"cookies?",
|
|
7817
|
+
"payments?",
|
|
7818
|
+
"billing",
|
|
7819
|
+
"invoices?",
|
|
7820
|
+
"charges?",
|
|
7821
|
+
"refunds?",
|
|
7822
|
+
"checkout",
|
|
7823
|
+
"stripe",
|
|
7824
|
+
"paypal",
|
|
7825
|
+
"migrations?",
|
|
7826
|
+
"migrate",
|
|
7827
|
+
"alter table",
|
|
7828
|
+
"drop table",
|
|
7829
|
+
"drop column",
|
|
7830
|
+
"schema change",
|
|
7831
|
+
"concurrency",
|
|
7832
|
+
"race condition",
|
|
7833
|
+
"mutex",
|
|
7834
|
+
"deadlock",
|
|
7835
|
+
"transaction isolation",
|
|
7836
|
+
"encrypt",
|
|
7837
|
+
"decrypt",
|
|
7838
|
+
"crypto",
|
|
7839
|
+
"hashing",
|
|
7840
|
+
"rbac",
|
|
7841
|
+
"permissions?",
|
|
7842
|
+
"access control",
|
|
7843
|
+
"privileges?",
|
|
7844
|
+
"pii",
|
|
7845
|
+
"gdpr",
|
|
7846
|
+
"hipaa",
|
|
7847
|
+
"rate limit",
|
|
7848
|
+
"csrf",
|
|
7849
|
+
"xss",
|
|
7850
|
+
"sql injection"
|
|
7851
|
+
]
|
|
7852
|
+
},
|
|
7853
|
+
{
|
|
7854
|
+
maxTier: 2,
|
|
7855
|
+
label: "behavioral surface (api / data shape / queue / cache / external integration)",
|
|
7856
|
+
patterns: [
|
|
7857
|
+
"endpoint",
|
|
7858
|
+
"route",
|
|
7859
|
+
"api contract",
|
|
7860
|
+
"request schema",
|
|
7861
|
+
"response schema",
|
|
7862
|
+
"pagination",
|
|
7863
|
+
"queue",
|
|
7864
|
+
"worker",
|
|
7865
|
+
"cron",
|
|
7866
|
+
"scheduler",
|
|
7867
|
+
"webhook",
|
|
7868
|
+
"cache",
|
|
7869
|
+
"redis",
|
|
7870
|
+
"database",
|
|
7871
|
+
"query",
|
|
7872
|
+
"index",
|
|
7873
|
+
"external service",
|
|
7874
|
+
"third[- ]?party",
|
|
7875
|
+
"integration",
|
|
7876
|
+
"data shape",
|
|
7877
|
+
"nullable",
|
|
7878
|
+
"breaking change"
|
|
7879
|
+
]
|
|
7880
|
+
}
|
|
7881
|
+
]
|
|
7882
|
+
};
|
|
7883
|
+
}
|
|
7884
|
+
});
|
|
7885
|
+
|
|
7886
|
+
// src/utils/git-paths.ts
|
|
7887
|
+
import { spawnSync as spawnSync3 } from "child_process";
|
|
7888
|
+
function unquote(raw) {
|
|
7889
|
+
return raw.trim().replace(/^"(.*)"$/, "$1");
|
|
7890
|
+
}
|
|
7891
|
+
function getTouchedPaths(cwd) {
|
|
7892
|
+
let res;
|
|
7893
|
+
try {
|
|
7894
|
+
res = spawnSync3("git", ["status", "--porcelain", "--untracked-files=all"], { cwd, encoding: "utf8" });
|
|
7895
|
+
} catch {
|
|
7896
|
+
return [];
|
|
7897
|
+
}
|
|
7898
|
+
if (res.status !== 0 || !res.stdout)
|
|
7899
|
+
return [];
|
|
7900
|
+
const paths = [];
|
|
7901
|
+
for (const line of res.stdout.split("\n")) {
|
|
7902
|
+
if (!line.trim())
|
|
7903
|
+
continue;
|
|
7904
|
+
const body = line.slice(3);
|
|
7905
|
+
const arrow = body.indexOf(" -> ");
|
|
7906
|
+
if (arrow >= 0) {
|
|
7907
|
+
for (const part of [body.slice(0, arrow), body.slice(arrow + 4)]) {
|
|
7908
|
+
const clean = unquote(part);
|
|
7909
|
+
if (clean)
|
|
7910
|
+
paths.push(clean);
|
|
7911
|
+
}
|
|
7912
|
+
} else {
|
|
7913
|
+
const clean = unquote(body);
|
|
7914
|
+
if (clean)
|
|
7915
|
+
paths.push(clean);
|
|
7916
|
+
}
|
|
7917
|
+
}
|
|
7918
|
+
return paths;
|
|
7919
|
+
}
|
|
7920
|
+
function getStagedPaths(cwd) {
|
|
7921
|
+
let res;
|
|
7922
|
+
try {
|
|
7923
|
+
res = spawnSync3("git", ["diff", "--cached", "--name-status", "--no-color"], { cwd, encoding: "utf8" });
|
|
7924
|
+
} catch {
|
|
7925
|
+
return [];
|
|
7926
|
+
}
|
|
7927
|
+
if (res.status !== 0 || !res.stdout)
|
|
7928
|
+
return [];
|
|
7929
|
+
const paths = [];
|
|
7930
|
+
for (const line of res.stdout.split("\n")) {
|
|
7931
|
+
if (!line.trim())
|
|
7932
|
+
continue;
|
|
7933
|
+
const parts = line.split(" ");
|
|
7934
|
+
const status = parts[0] ?? "";
|
|
7935
|
+
if (/^[RC]/.test(status) && parts.length >= 3) {
|
|
7936
|
+
for (const part of [parts[1], parts[2]]) {
|
|
7937
|
+
const clean = unquote(part ?? "");
|
|
7938
|
+
if (clean)
|
|
7939
|
+
paths.push(clean);
|
|
7940
|
+
}
|
|
7941
|
+
} else if (parts[1]) {
|
|
7942
|
+
const clean = unquote(parts[1]);
|
|
7943
|
+
if (clean)
|
|
7944
|
+
paths.push(clean);
|
|
7945
|
+
}
|
|
7946
|
+
}
|
|
7947
|
+
return paths;
|
|
7948
|
+
}
|
|
7949
|
+
var init_git_paths = __esm({
|
|
7950
|
+
"src/utils/git-paths.ts"() {
|
|
7951
|
+
"use strict";
|
|
7952
|
+
}
|
|
7953
|
+
});
|
|
7954
|
+
|
|
7517
7955
|
// src/utils/gitignore.ts
|
|
7518
|
-
import { existsSync as
|
|
7519
|
-
import { join as
|
|
7956
|
+
import { existsSync as existsSync16, readFileSync as readFileSync15, writeFileSync as writeFileSync9 } from "fs";
|
|
7957
|
+
import { join as join16 } from "path";
|
|
7520
7958
|
function ensureGitignoreEntry(cwd, entry, comment = "cdd-kit generated backups (do not commit)") {
|
|
7521
|
-
const path =
|
|
7959
|
+
const path = join16(cwd, ".gitignore");
|
|
7522
7960
|
const trimmed = entry.trim();
|
|
7523
7961
|
if (!trimmed)
|
|
7524
7962
|
return false;
|
|
7525
7963
|
const re = new RegExp(`^\\s*${trimmed.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\s*$`, "m");
|
|
7526
7964
|
let existing = "";
|
|
7527
|
-
if (
|
|
7528
|
-
existing =
|
|
7965
|
+
if (existsSync16(path))
|
|
7966
|
+
existing = readFileSync15(path, "utf8");
|
|
7529
7967
|
if (re.test(existing))
|
|
7530
7968
|
return false;
|
|
7531
7969
|
const sep = existing.length > 0 && !existing.endsWith("\n") ? "\n" : "";
|
|
@@ -7535,7 +7973,7 @@ ${trimmed}
|
|
|
7535
7973
|
# ${comment}
|
|
7536
7974
|
${trimmed}
|
|
7537
7975
|
`;
|
|
7538
|
-
|
|
7976
|
+
writeFileSync9(path, existing + block, "utf8");
|
|
7539
7977
|
return true;
|
|
7540
7978
|
}
|
|
7541
7979
|
var init_gitignore = __esm({
|
|
@@ -7549,15 +7987,15 @@ var migrate_exports = {};
|
|
|
7549
7987
|
__export(migrate_exports, {
|
|
7550
7988
|
migrate: () => migrate
|
|
7551
7989
|
});
|
|
7552
|
-
import { join as
|
|
7553
|
-
import { cpSync as cpSync2, existsSync as
|
|
7990
|
+
import { join as join17 } from "path";
|
|
7991
|
+
import { cpSync as cpSync2, existsSync as existsSync17, mkdirSync as mkdirSync8, readdirSync as readdirSync7, readFileSync as readFileSync16, renameSync, rmSync as rmSync2, writeFileSync as writeFileSync10 } from "fs";
|
|
7554
7992
|
import yaml2 from "js-yaml";
|
|
7555
7993
|
function backupChangeDir(cwd, changeId, sessionStamp) {
|
|
7556
|
-
const backupRoot =
|
|
7557
|
-
const backupDir2 =
|
|
7558
|
-
|
|
7559
|
-
const sourceDir =
|
|
7560
|
-
if (
|
|
7994
|
+
const backupRoot = join17(cwd, ".cdd", "migrate-backup", sessionStamp);
|
|
7995
|
+
const backupDir2 = join17(backupRoot, changeId);
|
|
7996
|
+
mkdirSync8(backupRoot, { recursive: true });
|
|
7997
|
+
const sourceDir = join17(cwd, "specs", "changes", changeId);
|
|
7998
|
+
if (existsSync17(sourceDir)) {
|
|
7561
7999
|
cpSync2(sourceDir, backupDir2, { recursive: true });
|
|
7562
8000
|
}
|
|
7563
8001
|
return backupDir2;
|
|
@@ -7690,16 +8128,16 @@ function parseLegacyTaskList(body) {
|
|
|
7690
8128
|
return rows;
|
|
7691
8129
|
}
|
|
7692
8130
|
function migrateTasksFile(changeId, changeDir, enableContextGovernance, detectedTier, changed, warnings, pendingWrites, pendingDeletes) {
|
|
7693
|
-
const newPath =
|
|
7694
|
-
const legacyPath =
|
|
7695
|
-
if (
|
|
8131
|
+
const newPath = join17(changeDir, "tasks.yml");
|
|
8132
|
+
const legacyPath = join17(changeDir, "tasks.md");
|
|
8133
|
+
if (existsSync17(newPath)) {
|
|
7696
8134
|
return;
|
|
7697
8135
|
}
|
|
7698
|
-
if (!
|
|
8136
|
+
if (!existsSync17(legacyPath)) {
|
|
7699
8137
|
warnings.push("tasks.md not found and tasks.yml missing \u2014 skipping tasks migration");
|
|
7700
8138
|
return;
|
|
7701
8139
|
}
|
|
7702
|
-
const raw =
|
|
8140
|
+
const raw = readFileSync16(legacyPath, "utf8");
|
|
7703
8141
|
const fm = parseLegacyFrontmatter(raw);
|
|
7704
8142
|
const bodyMatch = raw.match(/^---\r?\n[\s\S]*?\r?\n---\r?\n?([\s\S]*)$/);
|
|
7705
8143
|
const body = bodyMatch ? bodyMatch[1] : raw;
|
|
@@ -7783,17 +8221,17 @@ function parseLegacyAgentLog(content) {
|
|
|
7783
8221
|
return data;
|
|
7784
8222
|
}
|
|
7785
8223
|
function migrateAgentLogs(changeDir, changed, pendingWrites, pendingDeletes) {
|
|
7786
|
-
const agentLogDir =
|
|
7787
|
-
if (!
|
|
8224
|
+
const agentLogDir = join17(changeDir, "agent-log");
|
|
8225
|
+
if (!existsSync17(agentLogDir))
|
|
7788
8226
|
return;
|
|
7789
8227
|
const mdLogs = readdirSync7(agentLogDir).filter((f) => f.endsWith(".md"));
|
|
7790
8228
|
for (const f of mdLogs) {
|
|
7791
|
-
const fullPath =
|
|
8229
|
+
const fullPath = join17(agentLogDir, f);
|
|
7792
8230
|
const yamlName = f.replace(/\.md$/, ".yml");
|
|
7793
|
-
const yamlFull =
|
|
7794
|
-
if (
|
|
8231
|
+
const yamlFull = join17(agentLogDir, yamlName);
|
|
8232
|
+
if (existsSync17(yamlFull))
|
|
7795
8233
|
continue;
|
|
7796
|
-
const raw =
|
|
8234
|
+
const raw = readFileSync16(fullPath, "utf8");
|
|
7797
8235
|
const parsed = parseLegacyAgentLog(raw);
|
|
7798
8236
|
const yamlOut = yaml2.dump(parsed, { lineWidth: -1, noRefs: true });
|
|
7799
8237
|
pendingWrites.push({ path: yamlFull, content: yamlOut });
|
|
@@ -7802,15 +8240,15 @@ function migrateAgentLogs(changeDir, changed, pendingWrites, pendingDeletes) {
|
|
|
7802
8240
|
}
|
|
7803
8241
|
}
|
|
7804
8242
|
function ensureImplementationPlanScaffold(changeId, changeDir, changed, warnings, pendingWrites) {
|
|
7805
|
-
const planPath =
|
|
7806
|
-
if (
|
|
8243
|
+
const planPath = join17(changeDir, "implementation-plan.md");
|
|
8244
|
+
if (existsSync17(planPath))
|
|
7807
8245
|
return;
|
|
7808
|
-
const templatePath =
|
|
7809
|
-
if (!
|
|
8246
|
+
const templatePath = join17(ASSET.specsTemplates, "implementation-plan.md");
|
|
8247
|
+
if (!existsSync17(templatePath)) {
|
|
7810
8248
|
warnings.push("implementation-plan.md template not found; run cdd-kit upgrade --yes after updating cdd-kit");
|
|
7811
8249
|
return;
|
|
7812
8250
|
}
|
|
7813
|
-
const template =
|
|
8251
|
+
const template = readFileSync16(templatePath, "utf8").replace(/<change-id>/g, changeId).replace(/<id>/g, changeId);
|
|
7814
8252
|
pendingWrites.push({ path: planPath, content: template });
|
|
7815
8253
|
changed.push("implementation-plan.md: added scaffold");
|
|
7816
8254
|
warnings.push("implementation-plan.md scaffold added; fill it before implementation agents continue");
|
|
@@ -7821,9 +8259,9 @@ function migrateOne(changeId, changeDir, enableContextGovernance) {
|
|
|
7821
8259
|
const pending = [];
|
|
7822
8260
|
const deletes = [];
|
|
7823
8261
|
let detectedTier = null;
|
|
7824
|
-
const classifPath =
|
|
7825
|
-
if (
|
|
7826
|
-
const content =
|
|
8262
|
+
const classifPath = join17(changeDir, "change-classification.md");
|
|
8263
|
+
if (existsSync17(classifPath)) {
|
|
8264
|
+
const content = readFileSync16(classifPath, "utf8");
|
|
7827
8265
|
const hasNewTierFormat = /^## Tier\s*\n\s*-\s*\d\s*$/m.test(content);
|
|
7828
8266
|
const oldMatch = content.match(/\*\*Tier[:\*]+\s*(?:Tier\s*)?(\d)/i) ?? content.match(/^-?\s*Tier:\s*(?:Tier\s*)?(\d)/mi);
|
|
7829
8267
|
if (oldMatch)
|
|
@@ -7854,8 +8292,8 @@ function migrateOne(changeId, changeDir, enableContextGovernance) {
|
|
|
7854
8292
|
migrateTasksFile(changeId, changeDir, enableContextGovernance, detectedTier, changed, warnings, pending, deletes);
|
|
7855
8293
|
ensureImplementationPlanScaffold(changeId, changeDir, changed, warnings, pending);
|
|
7856
8294
|
migrateAgentLogs(changeDir, changed, pending, deletes);
|
|
7857
|
-
const manifestPath =
|
|
7858
|
-
if (!
|
|
8295
|
+
const manifestPath = join17(changeDir, "context-manifest.md");
|
|
8296
|
+
if (!existsSync17(manifestPath)) {
|
|
7859
8297
|
changed.push(enableContextGovernance ? "context-manifest.md: added context-governance v1 manifest scaffold" : "context-manifest.md: added legacy context manifest scaffold");
|
|
7860
8298
|
pending.push({
|
|
7861
8299
|
path: manifestPath,
|
|
@@ -7871,7 +8309,7 @@ function commitWritesAtomically(pending, deletes) {
|
|
|
7871
8309
|
try {
|
|
7872
8310
|
for (const write of pending) {
|
|
7873
8311
|
const tmp = `${write.path}.cdd-migrate.tmp`;
|
|
7874
|
-
|
|
8312
|
+
writeFileSync10(tmp, write.content, "utf8");
|
|
7875
8313
|
renames.push({ tmp, final: write.path });
|
|
7876
8314
|
}
|
|
7877
8315
|
} catch (err) {
|
|
@@ -7900,8 +8338,8 @@ async function migrate(changeId, opts = {}) {
|
|
|
7900
8338
|
const noBackup = opts.noBackup ?? false;
|
|
7901
8339
|
const idsToMigrate = [];
|
|
7902
8340
|
if (opts.all) {
|
|
7903
|
-
const changesDir =
|
|
7904
|
-
if (!
|
|
8341
|
+
const changesDir = join17(cwd, "specs", "changes");
|
|
8342
|
+
if (!existsSync17(changesDir)) {
|
|
7905
8343
|
log.info("No specs/changes/ directory found \u2014 nothing to migrate.");
|
|
7906
8344
|
return;
|
|
7907
8345
|
}
|
|
@@ -7909,8 +8347,8 @@ async function migrate(changeId, opts = {}) {
|
|
|
7909
8347
|
...readdirSync7(changesDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name)
|
|
7910
8348
|
);
|
|
7911
8349
|
} else if (changeId) {
|
|
7912
|
-
const specificDir =
|
|
7913
|
-
if (!
|
|
8350
|
+
const specificDir = join17(cwd, "specs", "changes", changeId);
|
|
8351
|
+
if (!existsSync17(specificDir)) {
|
|
7914
8352
|
log.error(`Change not found: specs/changes/${changeId}`);
|
|
7915
8353
|
process.exit(1);
|
|
7916
8354
|
}
|
|
@@ -7930,10 +8368,10 @@ async function migrate(changeId, opts = {}) {
|
|
|
7930
8368
|
const sessionStamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
7931
8369
|
let migratedCount = 0;
|
|
7932
8370
|
let upToDateCount = 0;
|
|
7933
|
-
const backupRoot =
|
|
8371
|
+
const backupRoot = join17(cwd, ".cdd", "migrate-backup", sessionStamp);
|
|
7934
8372
|
for (const id of idsToMigrate) {
|
|
7935
|
-
const changeDir =
|
|
7936
|
-
if (!
|
|
8373
|
+
const changeDir = join17(cwd, "specs", "changes", id);
|
|
8374
|
+
if (!existsSync17(changeDir)) {
|
|
7937
8375
|
log.warn(` ${id}: directory not found \u2014 skipping`);
|
|
7938
8376
|
continue;
|
|
7939
8377
|
}
|
|
@@ -7995,40 +8433,40 @@ var upgrade_exports = {};
|
|
|
7995
8433
|
__export(upgrade_exports, {
|
|
7996
8434
|
upgrade: () => upgrade
|
|
7997
8435
|
});
|
|
7998
|
-
import { existsSync as
|
|
7999
|
-
import { dirname as
|
|
8436
|
+
import { existsSync as existsSync18, mkdirSync as mkdirSync9, readdirSync as readdirSync8, copyFileSync as copyFileSync4, readFileSync as readFileSync17, writeFileSync as writeFileSync11 } from "fs";
|
|
8437
|
+
import { dirname as dirname5, join as join18, relative as relative4 } from "path";
|
|
8000
8438
|
function planMissingFiles(srcDir, destDir, label, planned) {
|
|
8001
|
-
if (!
|
|
8439
|
+
if (!existsSync18(srcDir))
|
|
8002
8440
|
return;
|
|
8003
8441
|
for (const entry of readdirSync8(srcDir, { withFileTypes: true })) {
|
|
8004
|
-
const src =
|
|
8005
|
-
const dest =
|
|
8442
|
+
const src = join18(srcDir, entry.name);
|
|
8443
|
+
const dest = join18(destDir, entry.name);
|
|
8006
8444
|
if (entry.isDirectory()) {
|
|
8007
|
-
planMissingFiles(src, dest,
|
|
8445
|
+
planMissingFiles(src, dest, join18(label, entry.name), planned);
|
|
8008
8446
|
continue;
|
|
8009
8447
|
}
|
|
8010
|
-
if (!
|
|
8011
|
-
planned.push({ src, dest, rel:
|
|
8448
|
+
if (!existsSync18(dest)) {
|
|
8449
|
+
planned.push({ src, dest, rel: join18(label, relative4(srcDir, src)) });
|
|
8012
8450
|
}
|
|
8013
8451
|
}
|
|
8014
8452
|
}
|
|
8015
8453
|
function planProviderGuidance(cwd, provider, planned) {
|
|
8016
8454
|
if (provider === "claude" || provider === "both") {
|
|
8017
|
-
if (!
|
|
8018
|
-
planned.push({ src: ASSET.claudeTemplate, dest:
|
|
8455
|
+
if (!existsSync18(join18(cwd, "CLAUDE.md"))) {
|
|
8456
|
+
planned.push({ src: ASSET.claudeTemplate, dest: join18(cwd, "CLAUDE.md"), rel: "CLAUDE.md" });
|
|
8019
8457
|
}
|
|
8020
|
-
if (!
|
|
8021
|
-
planned.push({ src: ASSET.agentsTemplate, dest:
|
|
8458
|
+
if (!existsSync18(join18(cwd, "AGENTS.md"))) {
|
|
8459
|
+
planned.push({ src: ASSET.agentsTemplate, dest: join18(cwd, "AGENTS.md"), rel: "AGENTS.md" });
|
|
8022
8460
|
}
|
|
8023
8461
|
}
|
|
8024
|
-
if ((provider === "codex" || provider === "both") && !
|
|
8025
|
-
planned.push({ src: ASSET.codexTemplate, dest:
|
|
8462
|
+
if ((provider === "codex" || provider === "both") && !existsSync18(join18(cwd, "CODEX.md"))) {
|
|
8463
|
+
planned.push({ src: ASSET.codexTemplate, dest: join18(cwd, "CODEX.md"), rel: "CODEX.md" });
|
|
8026
8464
|
}
|
|
8027
8465
|
}
|
|
8028
8466
|
function applyCopy(plan) {
|
|
8029
8467
|
for (const item of plan) {
|
|
8030
|
-
|
|
8031
|
-
|
|
8468
|
+
mkdirSync9(dirname5(item.dest), { recursive: true });
|
|
8469
|
+
copyFileSync4(item.src, item.dest);
|
|
8032
8470
|
}
|
|
8033
8471
|
}
|
|
8034
8472
|
async function upgrade(opts = {}) {
|
|
@@ -8040,12 +8478,12 @@ async function upgrade(opts = {}) {
|
|
|
8040
8478
|
}
|
|
8041
8479
|
const provider = inferProvider(cwd, requestedProvider);
|
|
8042
8480
|
const plan = [];
|
|
8043
|
-
planMissingFiles(ASSET.contracts,
|
|
8044
|
-
planMissingFiles(ASSET.specsTemplates,
|
|
8045
|
-
planMissingFiles(ASSET.testsTemplates,
|
|
8046
|
-
planMissingFiles(ASSET.ci,
|
|
8047
|
-
planMissingFiles(ASSET.githubWorkflows,
|
|
8048
|
-
planMissingFiles(ASSET.cddConfig,
|
|
8481
|
+
planMissingFiles(ASSET.contracts, join18(cwd, "contracts"), "contracts", plan);
|
|
8482
|
+
planMissingFiles(ASSET.specsTemplates, join18(cwd, "specs", "templates"), "specs/templates", plan);
|
|
8483
|
+
planMissingFiles(ASSET.testsTemplates, join18(cwd, "tests", "templates"), "tests/templates", plan);
|
|
8484
|
+
planMissingFiles(ASSET.ci, join18(cwd, "ci"), "ci", plan);
|
|
8485
|
+
planMissingFiles(ASSET.githubWorkflows, join18(cwd, ".github", "workflows"), ".github/workflows", plan);
|
|
8486
|
+
planMissingFiles(ASSET.cddConfig, join18(cwd, ".cdd"), ".cdd", plan);
|
|
8049
8487
|
planProviderGuidance(cwd, provider, plan);
|
|
8050
8488
|
log.blank();
|
|
8051
8489
|
log.info(`Upgrade provider: ${provider}`);
|
|
@@ -8082,11 +8520,11 @@ async function upgrade(opts = {}) {
|
|
|
8082
8520
|
return;
|
|
8083
8521
|
}
|
|
8084
8522
|
applyCopy(plan);
|
|
8085
|
-
const modelPolicyPath =
|
|
8086
|
-
if (
|
|
8523
|
+
const modelPolicyPath = join18(cwd, ".cdd", "model-policy.json");
|
|
8524
|
+
if (existsSync18(modelPolicyPath)) {
|
|
8087
8525
|
let existing = {};
|
|
8088
8526
|
try {
|
|
8089
|
-
existing = JSON.parse(
|
|
8527
|
+
existing = JSON.parse(readFileSync17(modelPolicyPath, "utf8"));
|
|
8090
8528
|
} catch {
|
|
8091
8529
|
}
|
|
8092
8530
|
const merged = {
|
|
@@ -8095,7 +8533,7 @@ async function upgrade(opts = {}) {
|
|
|
8095
8533
|
generated_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
8096
8534
|
roles: existing.roles && typeof existing.roles === "object" ? existing.roles : {}
|
|
8097
8535
|
};
|
|
8098
|
-
|
|
8536
|
+
writeFileSync11(modelPolicyPath, JSON.stringify(merged, null, 2) + "\n", "utf8");
|
|
8099
8537
|
}
|
|
8100
8538
|
log.blank();
|
|
8101
8539
|
log.ok(`Upgrade complete: ${plan.length} missing file(s) added.`);
|
|
@@ -8233,7 +8671,7 @@ function renderYaml(entries, opts) {
|
|
|
8233
8671
|
}
|
|
8234
8672
|
}
|
|
8235
8673
|
}
|
|
8236
|
-
const headerLineCount = opts.sourcesDigest ?
|
|
8674
|
+
const headerLineCount = 2 + (opts.sourcesDigest ? 1 : 0) + (opts.surfaceRoot ? 1 : 0);
|
|
8237
8675
|
const mapLines = bodyLines.length + headerLineCount;
|
|
8238
8676
|
const compression = totalSrc === 0 ? 0 : totalSrc / mapLines;
|
|
8239
8677
|
const fileCount = entries.length;
|
|
@@ -8244,6 +8682,9 @@ function renderYaml(entries, opts) {
|
|
|
8244
8682
|
if (opts.sourcesDigest) {
|
|
8245
8683
|
header.push(`# sources-digest: ${opts.sourcesDigest}`);
|
|
8246
8684
|
}
|
|
8685
|
+
if (opts.surfaceRoot) {
|
|
8686
|
+
header.push(`# surface-root: ${opts.surfaceRoot}`);
|
|
8687
|
+
}
|
|
8247
8688
|
return [...header, ...bodyLines].join("\n") + "\n";
|
|
8248
8689
|
}
|
|
8249
8690
|
var YAML_RESERVED;
|
|
@@ -8255,8 +8696,8 @@ var init_yaml_writer = __esm({
|
|
|
8255
8696
|
});
|
|
8256
8697
|
|
|
8257
8698
|
// src/code-map/config.ts
|
|
8258
|
-
import { existsSync as
|
|
8259
|
-
import { join as
|
|
8699
|
+
import { existsSync as existsSync19, readFileSync as readFileSync18 } from "fs";
|
|
8700
|
+
import { join as join19 } from "path";
|
|
8260
8701
|
import { load as yamlLoad } from "js-yaml";
|
|
8261
8702
|
function asStringArray(value, key, where) {
|
|
8262
8703
|
if (value === void 0)
|
|
@@ -8272,8 +8713,8 @@ function asStringArray(value, key, where) {
|
|
|
8272
8713
|
return value;
|
|
8273
8714
|
}
|
|
8274
8715
|
function loadCodeMapConfig(cwd) {
|
|
8275
|
-
const filePath =
|
|
8276
|
-
if (!
|
|
8716
|
+
const filePath = join19(cwd, CONFIG_REL_PATH);
|
|
8717
|
+
if (!existsSync19(filePath)) {
|
|
8277
8718
|
return {
|
|
8278
8719
|
include: [...BUILTIN_INCLUDE],
|
|
8279
8720
|
exclude: [...BUILTIN_EXCLUDE],
|
|
@@ -8282,7 +8723,7 @@ function loadCodeMapConfig(cwd) {
|
|
|
8282
8723
|
}
|
|
8283
8724
|
let text;
|
|
8284
8725
|
try {
|
|
8285
|
-
text =
|
|
8726
|
+
text = readFileSync18(filePath, "utf8");
|
|
8286
8727
|
} catch (err) {
|
|
8287
8728
|
throw new Error(`failed to read ${CONFIG_REL_PATH}: ${err.message}`);
|
|
8288
8729
|
}
|
|
@@ -8352,8 +8793,8 @@ var init_config = __esm({
|
|
|
8352
8793
|
});
|
|
8353
8794
|
|
|
8354
8795
|
// src/code-map/include-exclude.ts
|
|
8355
|
-
import { readdirSync as readdirSync9, statSync as
|
|
8356
|
-
import { join as
|
|
8796
|
+
import { readdirSync as readdirSync9, statSync as statSync3 } from "fs";
|
|
8797
|
+
import { join as join20 } from "path";
|
|
8357
8798
|
import picomatch from "picomatch";
|
|
8358
8799
|
function walkRepo(root, opts = {}) {
|
|
8359
8800
|
const includes = opts.include ?? [];
|
|
@@ -8372,13 +8813,13 @@ function walkRepo(root, opts = {}) {
|
|
|
8372
8813
|
}
|
|
8373
8814
|
for (const entry of entries) {
|
|
8374
8815
|
const relPath = relDir ? `${relDir}/${entry.name}` : entry.name;
|
|
8375
|
-
const absPath =
|
|
8816
|
+
const absPath = join20(dir, entry.name);
|
|
8376
8817
|
if (entry.isDirectory() || entry.isSymbolicLink()) {
|
|
8377
8818
|
const dirPattern = `${relPath}/**`;
|
|
8378
8819
|
if (isExcluded(relPath) || isExcluded(dirPattern))
|
|
8379
8820
|
continue;
|
|
8380
8821
|
try {
|
|
8381
|
-
const stat =
|
|
8822
|
+
const stat = statSync3(absPath);
|
|
8382
8823
|
if (stat.isDirectory()) {
|
|
8383
8824
|
walk(absPath, relPath);
|
|
8384
8825
|
}
|
|
@@ -8444,10 +8885,10 @@ var init_orchestrator = __esm({
|
|
|
8444
8885
|
});
|
|
8445
8886
|
|
|
8446
8887
|
// src/code-map/freshness.ts
|
|
8447
|
-
import { existsSync as
|
|
8448
|
-
import { join as
|
|
8888
|
+
import { existsSync as existsSync20, readFileSync as readFileSync19, statSync as statSync4 } from "fs";
|
|
8889
|
+
import { join as join21 } from "path";
|
|
8449
8890
|
function checkCodeMapFreshness(cwd, mapRel = ".cdd/code-map.yml", include, exclude) {
|
|
8450
|
-
const mapPath =
|
|
8891
|
+
const mapPath = join21(cwd, mapRel);
|
|
8451
8892
|
let cfg;
|
|
8452
8893
|
try {
|
|
8453
8894
|
cfg = loadCodeMapConfig(cwd);
|
|
@@ -8463,17 +8904,17 @@ function checkCodeMapFreshness(cwd, mapRel = ".cdd/code-map.yml", include, exclu
|
|
|
8463
8904
|
const includeFinal = [...cfg.include, ...include ?? []];
|
|
8464
8905
|
const excludeFinal = [...cfg.exclude, ...exclude ?? []];
|
|
8465
8906
|
const sourceFiles = walkRepo(cwd, { include: includeFinal, exclude: excludeFinal });
|
|
8466
|
-
if (!
|
|
8907
|
+
if (!existsSync20(mapPath)) {
|
|
8467
8908
|
if (sourceFiles.length === 0) {
|
|
8468
8909
|
return { status: "missing-greenfield", staleFiles: [], staleCount: 0, mapPath };
|
|
8469
8910
|
}
|
|
8470
8911
|
return { status: "missing-with-sources", staleFiles: [], staleCount: 0, mapPath };
|
|
8471
8912
|
}
|
|
8472
|
-
const mapMtime =
|
|
8913
|
+
const mapMtime = statSync4(mapPath).mtimeMs;
|
|
8473
8914
|
const staleAll = [];
|
|
8474
8915
|
for (const absPath of sourceFiles) {
|
|
8475
8916
|
try {
|
|
8476
|
-
const mtime =
|
|
8917
|
+
const mtime = statSync4(absPath).mtimeMs;
|
|
8477
8918
|
if (mtime > mapMtime) {
|
|
8478
8919
|
const rel = absPath.replace(/\\/g, "/").replace(cwd.replace(/\\/g, "/") + "/", "");
|
|
8479
8920
|
staleAll.push(rel);
|
|
@@ -8500,7 +8941,7 @@ function checkCodeMapFreshness(cwd, mapRel = ".cdd/code-map.yml", include, exclu
|
|
|
8500
8941
|
}
|
|
8501
8942
|
function readSourcesDigest(mapPath) {
|
|
8502
8943
|
try {
|
|
8503
|
-
const head =
|
|
8944
|
+
const head = readFileSync19(mapPath, "utf8").slice(0, 2048);
|
|
8504
8945
|
const m = head.match(/^# sources-digest:\s*([a-f0-9]+)/m);
|
|
8505
8946
|
return m ? m[1] : null;
|
|
8506
8947
|
} catch {
|
|
@@ -8517,7 +8958,7 @@ var init_freshness = __esm({
|
|
|
8517
8958
|
});
|
|
8518
8959
|
|
|
8519
8960
|
// src/code-map/index-reader.ts
|
|
8520
|
-
import { existsSync as
|
|
8961
|
+
import { existsSync as existsSync21, readFileSync as readFileSync20 } from "fs";
|
|
8521
8962
|
import yaml3 from "js-yaml";
|
|
8522
8963
|
async function ensureCodeMapFresh(mapPath, refresh2) {
|
|
8523
8964
|
if (!refresh2)
|
|
@@ -8555,13 +8996,13 @@ function sidecarPathFor(mapPath) {
|
|
|
8555
8996
|
}
|
|
8556
8997
|
function tryLoadSidecar(mapPath, mapText) {
|
|
8557
8998
|
const sidecarPath = sidecarPathFor(mapPath);
|
|
8558
|
-
if (!
|
|
8999
|
+
if (!existsSync21(sidecarPath))
|
|
8559
9000
|
return null;
|
|
8560
9001
|
const headerDigest = mapText.match(/^# sources-digest:\s*([a-f0-9]+)/m)?.[1];
|
|
8561
9002
|
if (!headerDigest)
|
|
8562
9003
|
return null;
|
|
8563
9004
|
try {
|
|
8564
|
-
const raw = JSON.parse(
|
|
9005
|
+
const raw = JSON.parse(readFileSync20(sidecarPath, "utf8"));
|
|
8565
9006
|
if (raw && raw.sourcesDigest === headerDigest && Array.isArray(raw.entries)) {
|
|
8566
9007
|
return raw.entries;
|
|
8567
9008
|
}
|
|
@@ -8570,10 +9011,10 @@ function tryLoadSidecar(mapPath, mapText) {
|
|
|
8570
9011
|
return null;
|
|
8571
9012
|
}
|
|
8572
9013
|
function loadCodeMapEntries(mapPath) {
|
|
8573
|
-
if (!
|
|
9014
|
+
if (!existsSync21(mapPath)) {
|
|
8574
9015
|
throw new Error(`${mapPath} is missing; run \`cdd-kit code-map\` first.`);
|
|
8575
9016
|
}
|
|
8576
|
-
const text =
|
|
9017
|
+
const text = readFileSync20(mapPath, "utf8");
|
|
8577
9018
|
const fromSidecar = tryLoadSidecar(mapPath, text);
|
|
8578
9019
|
if (fromSidecar)
|
|
8579
9020
|
return fromSidecar;
|
|
@@ -8945,7 +9386,7 @@ var init_builder = __esm({
|
|
|
8945
9386
|
});
|
|
8946
9387
|
|
|
8947
9388
|
// src/code-graph/reader.ts
|
|
8948
|
-
import { existsSync as
|
|
9389
|
+
import { existsSync as existsSync22, readFileSync as readFileSync21 } from "fs";
|
|
8949
9390
|
function graphPathFor(mapPath) {
|
|
8950
9391
|
const match = mapPath.match(/^(.*?)(?:code-map)(.*)\.ya?ml$/i);
|
|
8951
9392
|
if (match)
|
|
@@ -8953,10 +9394,10 @@ function graphPathFor(mapPath) {
|
|
|
8953
9394
|
return `${mapPath.replace(/\.ya?ml$/i, "")}.graph.json`;
|
|
8954
9395
|
}
|
|
8955
9396
|
function loadCodeGraph(graphPath) {
|
|
8956
|
-
if (!
|
|
9397
|
+
if (!existsSync22(graphPath)) {
|
|
8957
9398
|
throw new Error(`${graphPath} is missing; run \`cdd-kit code-map\` first.`);
|
|
8958
9399
|
}
|
|
8959
|
-
const raw = JSON.parse(
|
|
9400
|
+
const raw = JSON.parse(readFileSync21(graphPath, "utf8"));
|
|
8960
9401
|
if (!raw || raw.schema_version !== "1.0" || !Array.isArray(raw.nodes) || !Array.isArray(raw.edges)) {
|
|
8961
9402
|
throw new Error(`${graphPath} is not a cdd-kit code graph v1 index.`);
|
|
8962
9403
|
}
|
|
@@ -8974,9 +9415,9 @@ __export(worker_dispatch_exports, {
|
|
|
8974
9415
|
scanLangWithWorkers: () => scanLangWithWorkers
|
|
8975
9416
|
});
|
|
8976
9417
|
import { execFile } from "child_process";
|
|
8977
|
-
import { writeFileSync as
|
|
9418
|
+
import { writeFileSync as writeFileSync12, unlinkSync } from "fs";
|
|
8978
9419
|
import { randomBytes } from "crypto";
|
|
8979
|
-
import { join as
|
|
9420
|
+
import { join as join22 } from "path";
|
|
8980
9421
|
import { tmpdir } from "os";
|
|
8981
9422
|
function chunk(arr, parts) {
|
|
8982
9423
|
const size = Math.ceil(arr.length / parts);
|
|
@@ -8986,15 +9427,15 @@ function chunk(arr, parts) {
|
|
|
8986
9427
|
return out;
|
|
8987
9428
|
}
|
|
8988
9429
|
function scanChunkInChild(cliEntry, lang, files, repoRoot) {
|
|
8989
|
-
return new Promise((
|
|
9430
|
+
return new Promise((resolve6, reject) => {
|
|
8990
9431
|
if (!ALLOWED_LANGS.has(lang)) {
|
|
8991
9432
|
return reject(new Error(`refusing to spawn worker for unknown lang: ${lang}`));
|
|
8992
9433
|
}
|
|
8993
|
-
const listFile =
|
|
9434
|
+
const listFile = join22(
|
|
8994
9435
|
tmpdir(),
|
|
8995
9436
|
`cdd-cm-worker-${process.pid}-${randomBytes(12).toString("hex")}.txt`
|
|
8996
9437
|
);
|
|
8997
|
-
|
|
9438
|
+
writeFileSync12(listFile, files.join("\n") + "\n", { encoding: "utf8", mode: 384 });
|
|
8998
9439
|
execFile(
|
|
8999
9440
|
process.execPath,
|
|
9000
9441
|
[cliEntry, "__code-map-scan", "--lang", lang, "--batch-file", listFile, "--repo-root", repoRoot],
|
|
@@ -9008,7 +9449,7 @@ function scanChunkInChild(cliEntry, lang, files, repoRoot) {
|
|
|
9008
9449
|
return reject(err);
|
|
9009
9450
|
try {
|
|
9010
9451
|
const parsed = JSON.parse(stdout);
|
|
9011
|
-
|
|
9452
|
+
resolve6({ entries: parsed.entries ?? [], warnings: parsed.warnings ?? [] });
|
|
9012
9453
|
} catch (e) {
|
|
9013
9454
|
reject(e);
|
|
9014
9455
|
}
|
|
@@ -9066,15 +9507,15 @@ var python_exports = {};
|
|
|
9066
9507
|
__export(python_exports, {
|
|
9067
9508
|
pythonScanner: () => pythonScanner
|
|
9068
9509
|
});
|
|
9069
|
-
import { spawnSync as
|
|
9070
|
-
import { writeFileSync as
|
|
9510
|
+
import { spawnSync as spawnSync4 } from "child_process";
|
|
9511
|
+
import { writeFileSync as writeFileSync13, unlinkSync as unlinkSync2 } from "fs";
|
|
9071
9512
|
import { randomBytes as randomBytes2 } from "crypto";
|
|
9072
|
-
import { join as
|
|
9513
|
+
import { join as join23 } from "path";
|
|
9073
9514
|
import { tmpdir as tmpdir2 } from "os";
|
|
9074
9515
|
function detectPython2() {
|
|
9075
9516
|
for (const candidate of ["python3", "python"]) {
|
|
9076
9517
|
try {
|
|
9077
|
-
const result =
|
|
9518
|
+
const result = spawnSync4(candidate, ["--version"], {
|
|
9078
9519
|
encoding: "utf8",
|
|
9079
9520
|
timeout: 5e3
|
|
9080
9521
|
});
|
|
@@ -9143,13 +9584,13 @@ var init_python = __esm({
|
|
|
9143
9584
|
const entries = [];
|
|
9144
9585
|
const warnings = [];
|
|
9145
9586
|
const scriptPath = ASSET.codeMapPython;
|
|
9146
|
-
const listFile =
|
|
9147
|
-
|
|
9587
|
+
const listFile = join23(tmpdir2(), `cdd-codemap-${process.pid}-${randomBytes2(12).toString("hex")}.txt`);
|
|
9588
|
+
writeFileSync13(listFile, absolutePaths.join("\n") + "\n", { encoding: "utf8", mode: 384 });
|
|
9148
9589
|
let stdout = "";
|
|
9149
9590
|
let stderr = "";
|
|
9150
9591
|
let exitCode = 0;
|
|
9151
9592
|
try {
|
|
9152
|
-
const result =
|
|
9593
|
+
const result = spawnSync4(
|
|
9153
9594
|
interpreter,
|
|
9154
9595
|
[scriptPath, "--batch-file", listFile, "--repo-root", repoRoot],
|
|
9155
9596
|
{
|
|
@@ -9213,7 +9654,7 @@ var init_python = __esm({
|
|
|
9213
9654
|
}
|
|
9214
9655
|
const r = parsed;
|
|
9215
9656
|
entries.push({
|
|
9216
|
-
path: canonicalRelPath(
|
|
9657
|
+
path: canonicalRelPath(join23(repoRoot, r.path), repoRoot),
|
|
9217
9658
|
total_lines: r.total_lines,
|
|
9218
9659
|
imports: r.imports ?? [],
|
|
9219
9660
|
constants: r.constants ?? [],
|
|
@@ -9263,7 +9704,7 @@ __export(javascript_exports, {
|
|
|
9263
9704
|
parseJsSource: () => parseJsSource,
|
|
9264
9705
|
parseSourceWithPlugins: () => parseSourceWithPlugins
|
|
9265
9706
|
});
|
|
9266
|
-
import { readFileSync as
|
|
9707
|
+
import { readFileSync as readFileSync23 } from "fs";
|
|
9267
9708
|
import { parse } from "@babel/parser";
|
|
9268
9709
|
function parseSourceWithPlugins(source, plugins) {
|
|
9269
9710
|
return parse(source, {
|
|
@@ -9666,7 +10107,7 @@ var init_javascript = __esm({
|
|
|
9666
10107
|
async scan(absolutePath, repoRoot) {
|
|
9667
10108
|
let source;
|
|
9668
10109
|
try {
|
|
9669
|
-
source =
|
|
10110
|
+
source = readFileSync23(absolutePath, "utf8");
|
|
9670
10111
|
} catch (err) {
|
|
9671
10112
|
throw err;
|
|
9672
10113
|
}
|
|
@@ -9686,7 +10127,7 @@ var typescript_exports = {};
|
|
|
9686
10127
|
__export(typescript_exports, {
|
|
9687
10128
|
tsScanner: () => tsScanner
|
|
9688
10129
|
});
|
|
9689
|
-
import { readFileSync as
|
|
10130
|
+
import { readFileSync as readFileSync24 } from "fs";
|
|
9690
10131
|
var TypeScriptScanner, tsScanner;
|
|
9691
10132
|
var init_typescript = __esm({
|
|
9692
10133
|
"src/code-map/scanners/typescript.ts"() {
|
|
@@ -9698,7 +10139,7 @@ var init_typescript = __esm({
|
|
|
9698
10139
|
async scan(absolutePath, repoRoot) {
|
|
9699
10140
|
let source;
|
|
9700
10141
|
try {
|
|
9701
|
-
source =
|
|
10142
|
+
source = readFileSync24(absolutePath, "utf8");
|
|
9702
10143
|
} catch (err) {
|
|
9703
10144
|
throw err;
|
|
9704
10145
|
}
|
|
@@ -9736,7 +10177,7 @@ var vue_exports = {};
|
|
|
9736
10177
|
__export(vue_exports, {
|
|
9737
10178
|
vueScanner: () => vueScanner
|
|
9738
10179
|
});
|
|
9739
|
-
import { readFileSync as
|
|
10180
|
+
import { readFileSync as readFileSync25 } from "fs";
|
|
9740
10181
|
import { parse as parse2 } from "@vue/compiler-sfc";
|
|
9741
10182
|
var VueScanner, vueScanner;
|
|
9742
10183
|
var init_vue = __esm({
|
|
@@ -9749,7 +10190,7 @@ var init_vue = __esm({
|
|
|
9749
10190
|
async scan(absolutePath, repoRoot) {
|
|
9750
10191
|
let source;
|
|
9751
10192
|
try {
|
|
9752
|
-
source =
|
|
10193
|
+
source = readFileSync25(absolutePath, "utf8");
|
|
9753
10194
|
} catch (err) {
|
|
9754
10195
|
throw err;
|
|
9755
10196
|
}
|
|
@@ -9844,14 +10285,15 @@ var init_vue = __esm({
|
|
|
9844
10285
|
var code_map_exports = {};
|
|
9845
10286
|
__export(code_map_exports, {
|
|
9846
10287
|
codeMap: () => codeMap,
|
|
9847
|
-
computeSourcesDigest: () => computeSourcesDigest
|
|
10288
|
+
computeSourcesDigest: () => computeSourcesDigest,
|
|
10289
|
+
slugifySurface: () => slugifySurface
|
|
9848
10290
|
});
|
|
9849
|
-
import { existsSync as
|
|
9850
|
-
import { resolve, dirname as
|
|
10291
|
+
import { existsSync as existsSync23, mkdirSync as mkdirSync10, readFileSync as readFileSync26, writeFileSync as writeFileSync14 } from "fs";
|
|
10292
|
+
import { resolve as resolve3, dirname as dirname6, relative as relative6 } from "path";
|
|
9851
10293
|
import { createHash as createHash6 } from "crypto";
|
|
9852
10294
|
import { createRequire } from "module";
|
|
9853
10295
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
9854
|
-
import { join as
|
|
10296
|
+
import { join as join24 } from "path";
|
|
9855
10297
|
function computeSourcesDigest(absolutePaths, cwd) {
|
|
9856
10298
|
const lines = absolutePaths.slice().sort().map((p) => {
|
|
9857
10299
|
const rel = relative6(cwd, p).replace(/\\/g, "/");
|
|
@@ -9866,14 +10308,15 @@ function slugifySurface(surface) {
|
|
|
9866
10308
|
async function codeMap(opts) {
|
|
9867
10309
|
const start = Date.now();
|
|
9868
10310
|
if (opts.surface) {
|
|
9869
|
-
const resolvedSurface =
|
|
9870
|
-
if (!
|
|
10311
|
+
const resolvedSurface = resolve3(process.cwd(), opts.surface);
|
|
10312
|
+
if (!existsSync23(resolvedSurface)) {
|
|
9871
10313
|
log.error(`code-map --surface path not found: ${opts.surface}`);
|
|
9872
10314
|
return 1;
|
|
9873
10315
|
}
|
|
9874
10316
|
}
|
|
9875
10317
|
const scanPath = opts.surface ?? opts.path;
|
|
9876
|
-
const root =
|
|
10318
|
+
const root = resolve3(process.cwd(), scanPath);
|
|
10319
|
+
const surfaceRoot = opts.surface ? relative6(process.cwd(), root).replace(/\\/g, "/") || "." : void 0;
|
|
9877
10320
|
const out = opts.out ?? (opts.surface ? `.cdd/code-map.${slugifySurface(opts.surface)}.yml` : ".cdd/code-map.yml");
|
|
9878
10321
|
let cfg;
|
|
9879
10322
|
try {
|
|
@@ -9941,7 +10384,8 @@ async function codeMap(opts) {
|
|
|
9941
10384
|
const sourcesDigest = computeSourcesDigest(files, root);
|
|
9942
10385
|
const yamlBody = renderYaml(result.entries, {
|
|
9943
10386
|
generator: `cdd-kit ${_pkg.version}`,
|
|
9944
|
-
sourcesDigest
|
|
10387
|
+
sourcesDigest,
|
|
10388
|
+
surfaceRoot
|
|
9945
10389
|
});
|
|
9946
10390
|
const totalSrc = result.entries.reduce((s, e) => s + e.total_lines, 0);
|
|
9947
10391
|
const mapLines = yamlBody.split("\n").length;
|
|
@@ -9952,7 +10396,7 @@ async function codeMap(opts) {
|
|
|
9952
10396
|
log.warn(`${w.path}: ${w.message}`);
|
|
9953
10397
|
}
|
|
9954
10398
|
if (opts.check) {
|
|
9955
|
-
const existing =
|
|
10399
|
+
const existing = existsSync23(out) ? readFileSync26(out, "utf8") : "";
|
|
9956
10400
|
const normalize = (s) => s.replace(/\r\n/g, "\n").replace(/\r/g, "\n").replace(/^# generated: [^\n]+\n/m, "# generated: <normalized>\n");
|
|
9957
10401
|
if (normalize(existing) !== normalize(yamlBody)) {
|
|
9958
10402
|
if (!opts.silent)
|
|
@@ -9963,11 +10407,11 @@ async function codeMap(opts) {
|
|
|
9963
10407
|
log.ok(`code-map up to date: ${out}`);
|
|
9964
10408
|
return 0;
|
|
9965
10409
|
}
|
|
9966
|
-
|
|
9967
|
-
|
|
10410
|
+
mkdirSync10(dirname6(out), { recursive: true });
|
|
10411
|
+
writeFileSync14(out, yamlBody, "utf8");
|
|
9968
10412
|
try {
|
|
9969
10413
|
const sidecarPath = sidecarPathFor(out);
|
|
9970
|
-
|
|
10414
|
+
writeFileSync14(sidecarPath, JSON.stringify({ sourcesDigest, entries: result.entries }), "utf8");
|
|
9971
10415
|
const rel = relative6(process.cwd(), sidecarPath).replace(/\\/g, "/");
|
|
9972
10416
|
if (!rel.startsWith("..")) {
|
|
9973
10417
|
ensureGitignoreEntry(process.cwd(), rel, "cdd-kit local cache (do not commit)");
|
|
@@ -9977,7 +10421,7 @@ async function codeMap(opts) {
|
|
|
9977
10421
|
generator: `cdd-kit ${_pkg.version}`,
|
|
9978
10422
|
sourcesDigest
|
|
9979
10423
|
});
|
|
9980
|
-
|
|
10424
|
+
writeFileSync14(graphPath, JSON.stringify(graph2, null, 2) + "\n", "utf8");
|
|
9981
10425
|
const graphRel = relative6(process.cwd(), graphPath).replace(/\\/g, "/");
|
|
9982
10426
|
if (!graphRel.startsWith("..")) {
|
|
9983
10427
|
ensureGitignoreEntry(process.cwd(), graphRel, "cdd-kit local cache (do not commit)");
|
|
@@ -10002,8 +10446,8 @@ var init_code_map = __esm({
|
|
|
10002
10446
|
init_digest();
|
|
10003
10447
|
init_gitignore();
|
|
10004
10448
|
_require = createRequire(import.meta.url);
|
|
10005
|
-
_pkgPath =
|
|
10006
|
-
_pkg = JSON.parse(
|
|
10449
|
+
_pkgPath = join24(fileURLToPath2(import.meta.url), "..", "..", "..", "package.json");
|
|
10450
|
+
_pkg = JSON.parse(readFileSync26(_pkgPath, "utf8"));
|
|
10007
10451
|
}
|
|
10008
10452
|
});
|
|
10009
10453
|
|
|
@@ -10012,15 +10456,15 @@ var refresh_exports = {};
|
|
|
10012
10456
|
__export(refresh_exports, {
|
|
10013
10457
|
refresh: () => refresh
|
|
10014
10458
|
});
|
|
10015
|
-
import { existsSync as
|
|
10016
|
-
import { dirname as
|
|
10459
|
+
import { existsSync as existsSync24, mkdirSync as mkdirSync11, readdirSync as readdirSync10, copyFileSync as copyFileSync5, readFileSync as readFileSync27, writeFileSync as writeFileSync15 } from "fs";
|
|
10460
|
+
import { dirname as dirname7, join as join25, relative as relative7 } from "path";
|
|
10017
10461
|
import { createHash as createHash7 } from "crypto";
|
|
10018
10462
|
function fileHash2(filePath) {
|
|
10019
|
-
return createHash7("sha256").update(
|
|
10463
|
+
return createHash7("sha256").update(readFileSync27(filePath)).digest("hex");
|
|
10020
10464
|
}
|
|
10021
10465
|
function planForceRefresh(srcDir, destDir, sectionLabel) {
|
|
10022
10466
|
const plan = [];
|
|
10023
|
-
if (!
|
|
10467
|
+
if (!existsSync24(srcDir))
|
|
10024
10468
|
return plan;
|
|
10025
10469
|
function walk(curSrc, curDest) {
|
|
10026
10470
|
let items;
|
|
@@ -10030,16 +10474,16 @@ function planForceRefresh(srcDir, destDir, sectionLabel) {
|
|
|
10030
10474
|
return;
|
|
10031
10475
|
}
|
|
10032
10476
|
for (const item of items) {
|
|
10033
|
-
const sp =
|
|
10034
|
-
const dp =
|
|
10477
|
+
const sp = join25(curSrc, item.name);
|
|
10478
|
+
const dp = join25(curDest, item.name);
|
|
10035
10479
|
if (item.isDirectory()) {
|
|
10036
10480
|
walk(sp, dp);
|
|
10037
10481
|
continue;
|
|
10038
10482
|
}
|
|
10039
10483
|
if (!item.isFile())
|
|
10040
10484
|
continue;
|
|
10041
|
-
const rel =
|
|
10042
|
-
if (!
|
|
10485
|
+
const rel = join25(sectionLabel, relative7(srcDir, sp)).replace(/\\/g, "/");
|
|
10486
|
+
if (!existsSync24(dp)) {
|
|
10043
10487
|
plan.push({ src: sp, dest: dp, rel, action: "add" });
|
|
10044
10488
|
} else if (fileHash2(sp) !== fileHash2(dp)) {
|
|
10045
10489
|
plan.push({ src: sp, dest: dp, rel, action: "overwrite" });
|
|
@@ -10052,9 +10496,9 @@ function planForceRefresh(srcDir, destDir, sectionLabel) {
|
|
|
10052
10496
|
return plan;
|
|
10053
10497
|
}
|
|
10054
10498
|
function planSingleFile(src, dest, rel) {
|
|
10055
|
-
if (!
|
|
10499
|
+
if (!existsSync24(src))
|
|
10056
10500
|
return null;
|
|
10057
|
-
if (!
|
|
10501
|
+
if (!existsSync24(dest))
|
|
10058
10502
|
return { src, dest, rel, action: "add" };
|
|
10059
10503
|
if (fileHash2(src) !== fileHash2(dest))
|
|
10060
10504
|
return { src, dest, rel, action: "overwrite" };
|
|
@@ -10067,15 +10511,15 @@ function applyPlan(plan, backupRoot) {
|
|
|
10067
10511
|
if (item.action === "skip")
|
|
10068
10512
|
continue;
|
|
10069
10513
|
if (item.action === "overwrite") {
|
|
10070
|
-
const backupPath =
|
|
10071
|
-
|
|
10072
|
-
|
|
10514
|
+
const backupPath = join25(backupRoot, item.rel);
|
|
10515
|
+
mkdirSync11(dirname7(backupPath), { recursive: true });
|
|
10516
|
+
copyFileSync5(item.dest, backupPath);
|
|
10073
10517
|
overwritten += 1;
|
|
10074
10518
|
} else {
|
|
10075
10519
|
added += 1;
|
|
10076
10520
|
}
|
|
10077
|
-
|
|
10078
|
-
|
|
10521
|
+
mkdirSync11(dirname7(item.dest), { recursive: true });
|
|
10522
|
+
copyFileSync5(item.src, item.dest);
|
|
10079
10523
|
}
|
|
10080
10524
|
return { added, overwritten };
|
|
10081
10525
|
}
|
|
@@ -10096,14 +10540,14 @@ function parseAgentFrontmatter(content) {
|
|
|
10096
10540
|
return fm;
|
|
10097
10541
|
}
|
|
10098
10542
|
function resyncModelPolicy(cwd) {
|
|
10099
|
-
const policyPath =
|
|
10543
|
+
const policyPath = join25(cwd, ".cdd", "model-policy.json");
|
|
10100
10544
|
const result = { changed: false, diff: [], policyPath };
|
|
10101
|
-
if (!
|
|
10545
|
+
if (!existsSync24(AGENTS_HOME))
|
|
10102
10546
|
return result;
|
|
10103
10547
|
const desired = {};
|
|
10104
10548
|
const agentFiles = readdirSync10(AGENTS_HOME, { withFileTypes: true }).filter((d) => d.isFile() && d.name.endsWith(".md"));
|
|
10105
10549
|
for (const f of agentFiles) {
|
|
10106
|
-
const content =
|
|
10550
|
+
const content = readFileSync27(join25(AGENTS_HOME, f.name), "utf8");
|
|
10107
10551
|
const fm = parseAgentFrontmatter(content);
|
|
10108
10552
|
if (fm.name && fm.model)
|
|
10109
10553
|
desired[fm.name] = fm.model;
|
|
@@ -10111,9 +10555,9 @@ function resyncModelPolicy(cwd) {
|
|
|
10111
10555
|
if (Object.keys(desired).length === 0)
|
|
10112
10556
|
return result;
|
|
10113
10557
|
let existing = {};
|
|
10114
|
-
if (
|
|
10558
|
+
if (existsSync24(policyPath)) {
|
|
10115
10559
|
try {
|
|
10116
|
-
existing = JSON.parse(
|
|
10560
|
+
existing = JSON.parse(readFileSync27(policyPath, "utf8"));
|
|
10117
10561
|
} catch {
|
|
10118
10562
|
}
|
|
10119
10563
|
}
|
|
@@ -10133,8 +10577,8 @@ function resyncModelPolicy(cwd) {
|
|
|
10133
10577
|
merged["schema-version"] = "0.2.0";
|
|
10134
10578
|
if (!("provider" in merged))
|
|
10135
10579
|
merged["provider"] = "claude";
|
|
10136
|
-
|
|
10137
|
-
|
|
10580
|
+
mkdirSync11(dirname7(policyPath), { recursive: true });
|
|
10581
|
+
writeFileSync15(policyPath, JSON.stringify(merged, null, 2) + "\n", "utf8");
|
|
10138
10582
|
result.changed = true;
|
|
10139
10583
|
return result;
|
|
10140
10584
|
}
|
|
@@ -10142,21 +10586,21 @@ function planTemplateRefresh(cwd) {
|
|
|
10142
10586
|
const sections = [];
|
|
10143
10587
|
sections.push({
|
|
10144
10588
|
name: "specs/templates",
|
|
10145
|
-
plan: planForceRefresh(ASSET.specsTemplates,
|
|
10589
|
+
plan: planForceRefresh(ASSET.specsTemplates, join25(cwd, "specs", "templates"), "specs/templates")
|
|
10146
10590
|
});
|
|
10147
10591
|
sections.push({
|
|
10148
10592
|
name: "tests/templates",
|
|
10149
|
-
plan: planForceRefresh(ASSET.testsTemplates,
|
|
10593
|
+
plan: planForceRefresh(ASSET.testsTemplates, join25(cwd, "tests", "templates"), "tests/templates")
|
|
10150
10594
|
});
|
|
10151
|
-
const ciTemplatesAsset =
|
|
10152
|
-
if (
|
|
10595
|
+
const ciTemplatesAsset = join25(ASSET.ci, "..", "ci-templates");
|
|
10596
|
+
if (existsSync24(ciTemplatesAsset)) {
|
|
10153
10597
|
sections.push({
|
|
10154
10598
|
name: "ci-templates",
|
|
10155
|
-
plan: planForceRefresh(ciTemplatesAsset,
|
|
10599
|
+
plan: planForceRefresh(ciTemplatesAsset, join25(cwd, "ci-templates"), "ci-templates")
|
|
10156
10600
|
});
|
|
10157
10601
|
}
|
|
10158
|
-
const wfAsset =
|
|
10159
|
-
const wfDest =
|
|
10602
|
+
const wfAsset = join25(ASSET.githubWorkflows, "contract-driven-gates.yml");
|
|
10603
|
+
const wfDest = join25(cwd, ".github", "workflows", "contract-driven-gates.yml");
|
|
10160
10604
|
const wfPlan = planSingleFile(wfAsset, wfDest, ".github/workflows/contract-driven-gates.yml");
|
|
10161
10605
|
if (wfPlan)
|
|
10162
10606
|
sections.push({ name: ".github/workflows/contract-driven-gates.yml", plan: [wfPlan] });
|
|
@@ -10211,7 +10655,7 @@ async function refresh(opts) {
|
|
|
10211
10655
|
}
|
|
10212
10656
|
if (apply) {
|
|
10213
10657
|
const ts = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
10214
|
-
backupRoot =
|
|
10658
|
+
backupRoot = join25(cwd, ".cdd", ".refresh-backup", ts);
|
|
10215
10659
|
const result = applyPlan(total, backupRoot);
|
|
10216
10660
|
templateAdded = result.added;
|
|
10217
10661
|
templateOverwritten = result.overwritten;
|
|
@@ -10231,8 +10675,8 @@ async function refresh(opts) {
|
|
|
10231
10675
|
}
|
|
10232
10676
|
log.blank();
|
|
10233
10677
|
if (!opts.noHooks) {
|
|
10234
|
-
const markerPath =
|
|
10235
|
-
if (
|
|
10678
|
+
const markerPath = join25(cwd, HOOKS_MARKER_PATH);
|
|
10679
|
+
if (existsSync24(markerPath)) {
|
|
10236
10680
|
log.info("[4/6] re-install code-map pre-commit hook (marker found)");
|
|
10237
10681
|
if (apply) {
|
|
10238
10682
|
try {
|
|
@@ -10301,7 +10745,7 @@ async function refresh(opts) {
|
|
|
10301
10745
|
logRecommendedMcpSetup();
|
|
10302
10746
|
} else {
|
|
10303
10747
|
log.info("Dry-run finished. Re-run with `--yes` to apply.");
|
|
10304
|
-
log.info(
|
|
10748
|
+
log.info("After applying, register MCP with `claude mcp add --scope user cdd-kit -- cdd-kit mcp` so agents use graph/code-map tools directly.");
|
|
10305
10749
|
}
|
|
10306
10750
|
log.blank();
|
|
10307
10751
|
}
|
|
@@ -10328,8 +10772,8 @@ __export(lint_agents_exports, {
|
|
|
10328
10772
|
lintAgentContent: () => lintAgentContent,
|
|
10329
10773
|
lintAgents: () => lintAgents
|
|
10330
10774
|
});
|
|
10331
|
-
import { readdirSync as readdirSync11, readFileSync as
|
|
10332
|
-
import { join as
|
|
10775
|
+
import { readdirSync as readdirSync11, readFileSync as readFileSync28 } from "fs";
|
|
10776
|
+
import { join as join26 } from "path";
|
|
10333
10777
|
import { load as yamlLoad2 } from "js-yaml";
|
|
10334
10778
|
function extractRequiredArtifactsSection(content) {
|
|
10335
10779
|
const match = content.match(
|
|
@@ -10458,7 +10902,7 @@ function lintAgentContent(filename, rawContent, opts = {}) {
|
|
|
10458
10902
|
return violations;
|
|
10459
10903
|
}
|
|
10460
10904
|
function collectAgentViolations(cwd, opts = {}) {
|
|
10461
|
-
const agentsDir =
|
|
10905
|
+
const agentsDir = join26(cwd, ".claude", "agents");
|
|
10462
10906
|
let files;
|
|
10463
10907
|
try {
|
|
10464
10908
|
files = readdirSync11(agentsDir).filter((f) => f.endsWith(".md")).sort();
|
|
@@ -10469,7 +10913,7 @@ function collectAgentViolations(cwd, opts = {}) {
|
|
|
10469
10913
|
for (const filename of files) {
|
|
10470
10914
|
let content;
|
|
10471
10915
|
try {
|
|
10472
|
-
content =
|
|
10916
|
+
content = readFileSync28(join26(agentsDir, filename), "utf8");
|
|
10473
10917
|
} catch {
|
|
10474
10918
|
violations.push({
|
|
10475
10919
|
file: filename,
|
|
@@ -10488,7 +10932,7 @@ async function lintAgents(opts) {
|
|
|
10488
10932
|
const violations = collectAgentViolations(cwd, opts);
|
|
10489
10933
|
if (violations === null) {
|
|
10490
10934
|
log.error(
|
|
10491
|
-
`lint-agents: cannot read ${
|
|
10935
|
+
`lint-agents: cannot read ${join26(cwd, ".claude", "agents")} -- is this a cdd-kit project?`
|
|
10492
10936
|
);
|
|
10493
10937
|
return 1;
|
|
10494
10938
|
}
|
|
@@ -10513,22 +10957,132 @@ var init_lint_agents = __esm({
|
|
|
10513
10957
|
}
|
|
10514
10958
|
});
|
|
10515
10959
|
|
|
10960
|
+
// src/commands/chokepoints.ts
|
|
10961
|
+
import { existsSync as existsSync25, readFileSync as readFileSync29, readdirSync as readdirSync12 } from "fs";
|
|
10962
|
+
import { join as join27, resolve as resolve4 } from "path";
|
|
10963
|
+
import { spawnSync as spawnSync5 } from "child_process";
|
|
10964
|
+
function safeRead2(path) {
|
|
10965
|
+
try {
|
|
10966
|
+
return readFileSync29(path, "utf8");
|
|
10967
|
+
} catch {
|
|
10968
|
+
return "";
|
|
10969
|
+
}
|
|
10970
|
+
}
|
|
10971
|
+
function probeGraphFirst(cwd) {
|
|
10972
|
+
const settingsPath = join27(cwd, ".claude", "settings.json");
|
|
10973
|
+
let live = false;
|
|
10974
|
+
if (existsSync25(settingsPath)) {
|
|
10975
|
+
try {
|
|
10976
|
+
const settings = JSON.parse(safeRead2(settingsPath));
|
|
10977
|
+
const entries = settings.hooks?.PreToolUse;
|
|
10978
|
+
if (Array.isArray(entries)) {
|
|
10979
|
+
live = entries.some(
|
|
10980
|
+
(e) => Array.isArray(e?.hooks) && e.hooks.some((h) => typeof h?.command === "string" && h.command.includes(GRAPH_FIRST_MARKER))
|
|
10981
|
+
);
|
|
10982
|
+
}
|
|
10983
|
+
} catch {
|
|
10984
|
+
}
|
|
10985
|
+
}
|
|
10986
|
+
return {
|
|
10987
|
+
id: "graph-first-hook",
|
|
10988
|
+
name: "graph-first exploration hook",
|
|
10989
|
+
live,
|
|
10990
|
+
detail: live ? "PreToolUse hook steers agents to graph/index queries before Read" : "dormant \u2014 run `cdd-kit install-agent-hooks --graph-first advisory` to stop agents defaulting to Read"
|
|
10991
|
+
};
|
|
10992
|
+
}
|
|
10993
|
+
function resolveHooksDir2(cwd) {
|
|
10994
|
+
try {
|
|
10995
|
+
const res = spawnSync5("git", ["rev-parse", "--git-path", "hooks"], { cwd, encoding: "utf8" });
|
|
10996
|
+
if (res.status === 0 && res.stdout.trim())
|
|
10997
|
+
return resolve4(cwd, res.stdout.trim());
|
|
10998
|
+
} catch {
|
|
10999
|
+
}
|
|
11000
|
+
return join27(cwd, ".git", "hooks");
|
|
11001
|
+
}
|
|
11002
|
+
function probePreCommitGate(cwd) {
|
|
11003
|
+
const hookPath = join27(resolveHooksDir2(cwd), "pre-commit");
|
|
11004
|
+
const live = existsSync25(hookPath) && safeRead2(hookPath).includes(PRECOMMIT_MARKER);
|
|
11005
|
+
return {
|
|
11006
|
+
id: "pre-commit-gate",
|
|
11007
|
+
name: "pre-commit gate hook",
|
|
11008
|
+
live,
|
|
11009
|
+
detail: live ? "`cdd-kit gate` runs before each commit touching specs/contracts" : "dormant \u2014 run `cdd-kit install-hooks` to block commits that fail the gate"
|
|
11010
|
+
};
|
|
11011
|
+
}
|
|
11012
|
+
function probeOpenApiGate(cwd) {
|
|
11013
|
+
let where = "";
|
|
11014
|
+
const pkgPath = join27(cwd, "package.json");
|
|
11015
|
+
if (existsSync25(pkgPath)) {
|
|
11016
|
+
try {
|
|
11017
|
+
const pkg2 = JSON.parse(safeRead2(pkgPath));
|
|
11018
|
+
const scripts = pkg2.scripts ?? {};
|
|
11019
|
+
for (const [name, cmd] of Object.entries(scripts)) {
|
|
11020
|
+
if (typeof cmd === "string" && cmd.includes(OPENAPI_CHECK_MARKER)) {
|
|
11021
|
+
where = `package.json script \`${name}\``;
|
|
11022
|
+
break;
|
|
11023
|
+
}
|
|
11024
|
+
}
|
|
11025
|
+
} catch {
|
|
11026
|
+
}
|
|
11027
|
+
}
|
|
11028
|
+
if (!where) {
|
|
11029
|
+
const wfDir = join27(cwd, ".github", "workflows");
|
|
11030
|
+
if (existsSync25(wfDir)) {
|
|
11031
|
+
try {
|
|
11032
|
+
for (const entry of readdirSync12(wfDir)) {
|
|
11033
|
+
if (!/\.ya?ml$/.test(entry))
|
|
11034
|
+
continue;
|
|
11035
|
+
if (safeRead2(join27(wfDir, entry)).includes(OPENAPI_CHECK_MARKER)) {
|
|
11036
|
+
where = `CI workflow ${entry}`;
|
|
11037
|
+
break;
|
|
11038
|
+
}
|
|
11039
|
+
}
|
|
11040
|
+
} catch {
|
|
11041
|
+
}
|
|
11042
|
+
}
|
|
11043
|
+
}
|
|
11044
|
+
const live = where !== "";
|
|
11045
|
+
return {
|
|
11046
|
+
id: "openapi-sync-gate",
|
|
11047
|
+
name: "OpenAPI sync gate",
|
|
11048
|
+
live,
|
|
11049
|
+
detail: live ? `\`cdd-kit openapi export --check\` runs in ${where}` : "dormant \u2014 wire `cdd-kit openapi export --check --out <artifact>` into CI or a package.json script (or run `cdd-kit init`)"
|
|
11050
|
+
};
|
|
11051
|
+
}
|
|
11052
|
+
function detectChokepoints(cwd) {
|
|
11053
|
+
return [
|
|
11054
|
+
probeGraphFirst(cwd),
|
|
11055
|
+
probePreCommitGate(cwd),
|
|
11056
|
+
probeOpenApiGate(cwd)
|
|
11057
|
+
];
|
|
11058
|
+
}
|
|
11059
|
+
var GRAPH_FIRST_MARKER, PRECOMMIT_MARKER, OPENAPI_CHECK_MARKER;
|
|
11060
|
+
var init_chokepoints = __esm({
|
|
11061
|
+
"src/commands/chokepoints.ts"() {
|
|
11062
|
+
"use strict";
|
|
11063
|
+
GRAPH_FIRST_MARKER = "pre-tool-use-graph-first";
|
|
11064
|
+
PRECOMMIT_MARKER = "# cdd-kit-managed-block-start";
|
|
11065
|
+
OPENAPI_CHECK_MARKER = "openapi export --check";
|
|
11066
|
+
}
|
|
11067
|
+
});
|
|
11068
|
+
|
|
10516
11069
|
// src/commands/doctor.ts
|
|
10517
11070
|
var doctor_exports = {};
|
|
10518
11071
|
__export(doctor_exports, {
|
|
10519
11072
|
doctor: () => doctor
|
|
10520
11073
|
});
|
|
10521
|
-
import { existsSync as
|
|
11074
|
+
import { existsSync as existsSync26, readdirSync as readdirSync13, readFileSync as readFileSync30 } from "fs";
|
|
10522
11075
|
import { createHash as createHash8 } from "crypto";
|
|
10523
|
-
import {
|
|
11076
|
+
import { spawnSync as spawnSync6 } from "child_process";
|
|
11077
|
+
import { join as join28, relative as relative8 } from "path";
|
|
10524
11078
|
function fileExists(cwd, relPath) {
|
|
10525
|
-
return
|
|
11079
|
+
return existsSync26(join28(cwd, relPath));
|
|
10526
11080
|
}
|
|
10527
11081
|
function findFiles(dir, predicate, found = []) {
|
|
10528
|
-
if (!
|
|
11082
|
+
if (!existsSync26(dir))
|
|
10529
11083
|
return found;
|
|
10530
|
-
for (const entry of
|
|
10531
|
-
const fullPath =
|
|
11084
|
+
for (const entry of readdirSync13(dir, { withFileTypes: true })) {
|
|
11085
|
+
const fullPath = join28(dir, entry.name);
|
|
10532
11086
|
if (entry.isDirectory())
|
|
10533
11087
|
findFiles(fullPath, predicate, found);
|
|
10534
11088
|
else if (entry.isFile() && predicate(entry.name))
|
|
@@ -10544,9 +11098,9 @@ function inputDigest(paths, cwd) {
|
|
|
10544
11098
|
return createHash8("sha256").update(combined).digest("hex");
|
|
10545
11099
|
}
|
|
10546
11100
|
function readContextIndexMetadata(filePath) {
|
|
10547
|
-
if (!
|
|
11101
|
+
if (!existsSync26(filePath))
|
|
10548
11102
|
return {};
|
|
10549
|
-
const text =
|
|
11103
|
+
const text = readFileSync30(filePath, "utf8");
|
|
10550
11104
|
const out = {};
|
|
10551
11105
|
const digestMatch = text.match(/^inputs-digest:\s*([a-f0-9]+)/m);
|
|
10552
11106
|
if (digestMatch)
|
|
@@ -10558,14 +11112,14 @@ function readContextIndexMetadata(filePath) {
|
|
|
10558
11112
|
}
|
|
10559
11113
|
function checkContextFreshness(cwd) {
|
|
10560
11114
|
const findings = [];
|
|
10561
|
-
const projectMap =
|
|
10562
|
-
const contractsIndex =
|
|
10563
|
-
const contextPolicy =
|
|
11115
|
+
const projectMap = join28(cwd, "specs", "context", "project-map.md");
|
|
11116
|
+
const contractsIndex = join28(cwd, "specs", "context", "contracts-index.md");
|
|
11117
|
+
const contextPolicy = join28(cwd, ".cdd", "context-policy.json");
|
|
10564
11118
|
const contractFiles = findFiles(
|
|
10565
|
-
|
|
11119
|
+
join28(cwd, "contracts"),
|
|
10566
11120
|
(name) => name.endsWith(".md") && name !== "INDEX.md" && name !== "CHANGELOG.md"
|
|
10567
11121
|
);
|
|
10568
|
-
if (!
|
|
11122
|
+
if (!existsSync26(projectMap) || !existsSync26(contractsIndex)) {
|
|
10569
11123
|
findings.push({
|
|
10570
11124
|
level: "warning",
|
|
10571
11125
|
message: "specs/context indexes are missing; run cdd-kit context-scan before classification"
|
|
@@ -10574,7 +11128,7 @@ function checkContextFreshness(cwd) {
|
|
|
10574
11128
|
}
|
|
10575
11129
|
const projectMapMeta = readContextIndexMetadata(projectMap);
|
|
10576
11130
|
const contractsIndexMeta = readContextIndexMetadata(contractsIndex);
|
|
10577
|
-
const projectInputDigest = inputDigest([contextPolicy].filter(
|
|
11131
|
+
const projectInputDigest = inputDigest([contextPolicy].filter(existsSync26), cwd);
|
|
10578
11132
|
if (projectMapMeta.inputsDigest === void 0) {
|
|
10579
11133
|
findings.push({
|
|
10580
11134
|
level: "warning",
|
|
@@ -10611,7 +11165,7 @@ function checkContextFreshness(cwd) {
|
|
|
10611
11165
|
}
|
|
10612
11166
|
function readAgentModel(path) {
|
|
10613
11167
|
try {
|
|
10614
|
-
const text =
|
|
11168
|
+
const text = readFileSync30(path, "utf8");
|
|
10615
11169
|
const m = text.match(/^model:\s*(\S+)/m);
|
|
10616
11170
|
return m ? m[1] : null;
|
|
10617
11171
|
} catch {
|
|
@@ -10619,12 +11173,12 @@ function readAgentModel(path) {
|
|
|
10619
11173
|
}
|
|
10620
11174
|
}
|
|
10621
11175
|
function checkModelPolicyDrift(cwd) {
|
|
10622
|
-
const policyPath =
|
|
10623
|
-
if (!
|
|
11176
|
+
const policyPath = join28(cwd, ".cdd", "model-policy.json");
|
|
11177
|
+
if (!existsSync26(policyPath))
|
|
10624
11178
|
return [];
|
|
10625
11179
|
let policy;
|
|
10626
11180
|
try {
|
|
10627
|
-
policy = JSON.parse(
|
|
11181
|
+
policy = JSON.parse(readFileSync30(policyPath, "utf8"));
|
|
10628
11182
|
} catch {
|
|
10629
11183
|
return [{ level: "warning", message: ".cdd/model-policy.json is not valid JSON" }];
|
|
10630
11184
|
}
|
|
@@ -10636,18 +11190,18 @@ function checkModelPolicyDrift(cwd) {
|
|
|
10636
11190
|
}];
|
|
10637
11191
|
}
|
|
10638
11192
|
const candidateDirs = [
|
|
10639
|
-
|
|
10640
|
-
process.env.HOME ?
|
|
10641
|
-
process.env.USERPROFILE ?
|
|
10642
|
-
].filter((p) => p &&
|
|
11193
|
+
join28(cwd, ".claude", "agents"),
|
|
11194
|
+
process.env.HOME ? join28(process.env.HOME, ".claude", "agents") : "",
|
|
11195
|
+
process.env.USERPROFILE ? join28(process.env.USERPROFILE, ".claude", "agents") : ""
|
|
11196
|
+
].filter((p) => p && existsSync26(p));
|
|
10643
11197
|
if (candidateDirs.length === 0)
|
|
10644
11198
|
return [];
|
|
10645
11199
|
const findings = [];
|
|
10646
11200
|
for (const [role, expected] of Object.entries(roles)) {
|
|
10647
11201
|
let foundAny = false;
|
|
10648
11202
|
for (const dir of candidateDirs) {
|
|
10649
|
-
const path =
|
|
10650
|
-
if (!
|
|
11203
|
+
const path = join28(dir, `${role}.md`);
|
|
11204
|
+
if (!existsSync26(path))
|
|
10651
11205
|
continue;
|
|
10652
11206
|
foundAny = true;
|
|
10653
11207
|
const actual = readAgentModel(path);
|
|
@@ -10667,14 +11221,14 @@ function checkModelPolicyDrift(cwd) {
|
|
|
10667
11221
|
return findings;
|
|
10668
11222
|
}
|
|
10669
11223
|
function checkAgentLint(cwd) {
|
|
10670
|
-
const agentsDir =
|
|
10671
|
-
if (!
|
|
11224
|
+
const agentsDir = join28(cwd, ".claude", "agents");
|
|
11225
|
+
if (!existsSync26(agentsDir))
|
|
10672
11226
|
return [];
|
|
10673
11227
|
const violations = collectAgentViolations(cwd);
|
|
10674
11228
|
if (violations === null) {
|
|
10675
11229
|
return [{
|
|
10676
11230
|
level: "warning",
|
|
10677
|
-
message: `lint-agents: could not read ${
|
|
11231
|
+
message: `lint-agents: could not read ${join28(".claude", "agents")} -- agent prompts were not scanned`
|
|
10678
11232
|
}];
|
|
10679
11233
|
}
|
|
10680
11234
|
const findings = violations.map((v) => ({
|
|
@@ -10688,8 +11242,8 @@ function checkAgentLint(cwd) {
|
|
|
10688
11242
|
}
|
|
10689
11243
|
function checkCodeMap(cwd) {
|
|
10690
11244
|
const findings = [];
|
|
10691
|
-
const mapPath =
|
|
10692
|
-
if (!
|
|
11245
|
+
const mapPath = join28(cwd, ".cdd", "code-map.yml");
|
|
11246
|
+
if (!existsSync26(mapPath)) {
|
|
10693
11247
|
const probe2 = checkCodeMapFreshness(cwd);
|
|
10694
11248
|
if (probe2.status === "config-error") {
|
|
10695
11249
|
findings.push({ level: "warning", message: `.cdd/code-map-config.yml is invalid: ${probe2.configError}` });
|
|
@@ -10708,7 +11262,7 @@ function checkCodeMap(cwd) {
|
|
|
10708
11262
|
const more = probe.staleCount > 3 ? ` (+${probe.staleCount - 3} more)` : "";
|
|
10709
11263
|
findings.push({ level: "warning", message: `code-map stale: ${top}${more}; run \`cdd-kit code-map\`` });
|
|
10710
11264
|
}
|
|
10711
|
-
const text =
|
|
11265
|
+
const text = readFileSync30(mapPath, "utf8");
|
|
10712
11266
|
const m = text.match(/^# files: (\d+), src-lines: (\d+), map-lines: (\d+), compression: ([\d.]+)x/m);
|
|
10713
11267
|
if (m) {
|
|
10714
11268
|
findings.push({ level: "ok", message: `code-map: ${m[1]} files, ${m[4]}x compression` });
|
|
@@ -10717,6 +11271,68 @@ function checkCodeMap(cwd) {
|
|
|
10717
11271
|
}
|
|
10718
11272
|
return findings;
|
|
10719
11273
|
}
|
|
11274
|
+
function checkApiConformance(cwd) {
|
|
11275
|
+
const hasContract = existsSync26(join28(cwd, "contracts", "api", "api-contract.md"));
|
|
11276
|
+
if (!hasContract)
|
|
11277
|
+
return [];
|
|
11278
|
+
const configPath = join28(cwd, ".cdd", "conformance.json");
|
|
11279
|
+
if (!existsSync26(configPath)) {
|
|
11280
|
+
return [{
|
|
11281
|
+
level: "ok",
|
|
11282
|
+
message: 'API conformance: not configured (add .cdd/conformance.json with "enabled": true to catch frontend/backend drift against the contract)'
|
|
11283
|
+
}];
|
|
11284
|
+
}
|
|
11285
|
+
try {
|
|
11286
|
+
const cfg = JSON.parse(readFileSync30(configPath, "utf8"));
|
|
11287
|
+
return [{
|
|
11288
|
+
level: "ok",
|
|
11289
|
+
message: cfg.enabled ? "API conformance: enabled (cdd-kit validate --contracts checks code against the API contract)" : 'API conformance: present but disabled (set "enabled": true in .cdd/conformance.json to enforce code-vs-contract checks)'
|
|
11290
|
+
}];
|
|
11291
|
+
} catch {
|
|
11292
|
+
return [{ level: "warning", message: ".cdd/conformance.json is not valid JSON" }];
|
|
11293
|
+
}
|
|
11294
|
+
}
|
|
11295
|
+
function checkMcpRegistration(cwd, provider) {
|
|
11296
|
+
if (provider !== "claude" && provider !== "both")
|
|
11297
|
+
return [];
|
|
11298
|
+
if (!existsSync26(join28(cwd, ".cdd")))
|
|
11299
|
+
return [];
|
|
11300
|
+
const bin = process.env.CDD_CLAUDE_BIN || "claude";
|
|
11301
|
+
let result;
|
|
11302
|
+
try {
|
|
11303
|
+
result = bin.toLowerCase().endsWith(".js") ? spawnSync6(process.execPath, [bin, "mcp", "list"], { encoding: "utf8", timeout: 3e3 }) : spawnSync6(bin, ["mcp", "list"], { encoding: "utf8", timeout: 3e3 });
|
|
11304
|
+
} catch {
|
|
11305
|
+
result = { error: new Error("spawn failed") };
|
|
11306
|
+
}
|
|
11307
|
+
if (result.error || typeof result.status !== "number") {
|
|
11308
|
+
return [{
|
|
11309
|
+
level: "ok",
|
|
11310
|
+
message: "MCP: could not run `claude mcp list` (Claude Code CLI not found); skip if you do not use Claude Code"
|
|
11311
|
+
}];
|
|
11312
|
+
}
|
|
11313
|
+
if (result.status !== 0) {
|
|
11314
|
+
return [{
|
|
11315
|
+
level: "ok",
|
|
11316
|
+
message: `MCP: \`claude mcp list\` exited ${result.status}; cannot verify cdd-kit registration`
|
|
11317
|
+
}];
|
|
11318
|
+
}
|
|
11319
|
+
const output = `${result.stdout ?? ""}${result.stderr ?? ""}`;
|
|
11320
|
+
if (/\bcdd-kit\b/.test(output)) {
|
|
11321
|
+
return [{ level: "ok", message: "MCP: cdd-kit server is registered with Claude Code" }];
|
|
11322
|
+
}
|
|
11323
|
+
return [{
|
|
11324
|
+
level: "ok",
|
|
11325
|
+
message: "MCP: cdd-kit not registered \u2014 agents will fall back to Read instead of graph/index tools; run `claude mcp add --scope user cdd-kit -- cdd-kit mcp`"
|
|
11326
|
+
}];
|
|
11327
|
+
}
|
|
11328
|
+
function checkChokepoints(cwd) {
|
|
11329
|
+
if (!existsSync26(join28(cwd, ".cdd")))
|
|
11330
|
+
return [];
|
|
11331
|
+
return detectChokepoints(cwd).map((c) => ({
|
|
11332
|
+
level: "ok",
|
|
11333
|
+
message: c.live ? `chokepoint ${c.name}: live \u2014 ${c.detail}` : `chokepoint ${c.name}: ${c.detail}`
|
|
11334
|
+
}));
|
|
11335
|
+
}
|
|
10720
11336
|
async function buildDoctorReport(cwd, opts) {
|
|
10721
11337
|
const requestedProvider = opts.provider ?? "auto";
|
|
10722
11338
|
if (!validateProviderOption(requestedProvider)) {
|
|
@@ -10742,6 +11358,9 @@ async function buildDoctorReport(cwd, opts) {
|
|
|
10742
11358
|
findings.push(...checkModelPolicyDrift(cwd));
|
|
10743
11359
|
findings.push(...checkAgentLint(cwd));
|
|
10744
11360
|
findings.push(...checkCodeMap(cwd));
|
|
11361
|
+
findings.push(...checkApiConformance(cwd));
|
|
11362
|
+
findings.push(...checkMcpRegistration(cwd, provider));
|
|
11363
|
+
findings.push(...checkChokepoints(cwd));
|
|
10745
11364
|
const errors = findings.filter((finding) => finding.level === "error").length;
|
|
10746
11365
|
const warnings = findings.filter((finding) => finding.level === "warning").length;
|
|
10747
11366
|
return {
|
|
@@ -10784,11 +11403,11 @@ async function attemptAutoFixes(cwd, report) {
|
|
|
10784
11403
|
}
|
|
10785
11404
|
}
|
|
10786
11405
|
if (/model-policy\.json has no role bindings/i.test(finding.message)) {
|
|
10787
|
-
const policyPath =
|
|
11406
|
+
const policyPath = join28(cwd, ".cdd", "model-policy.json");
|
|
10788
11407
|
try {
|
|
10789
11408
|
let existing = {};
|
|
10790
11409
|
try {
|
|
10791
|
-
existing = JSON.parse(
|
|
11410
|
+
existing = JSON.parse(readFileSync30(policyPath, "utf8"));
|
|
10792
11411
|
} catch {
|
|
10793
11412
|
}
|
|
10794
11413
|
const merged = {
|
|
@@ -10813,8 +11432,8 @@ async function attemptAutoFixes(cwd, report) {
|
|
|
10813
11432
|
"repo-context-scanner": "haiku"
|
|
10814
11433
|
}
|
|
10815
11434
|
};
|
|
10816
|
-
const { writeFileSync:
|
|
10817
|
-
|
|
11435
|
+
const { writeFileSync: writeFileSync19 } = await import("fs");
|
|
11436
|
+
writeFileSync19(policyPath, JSON.stringify(merged, null, 2) + "\n", "utf8");
|
|
10818
11437
|
fixed.push(`populated .cdd/model-policy.json with default role bindings`);
|
|
10819
11438
|
continue;
|
|
10820
11439
|
} catch (err) {
|
|
@@ -10883,16 +11502,188 @@ var init_doctor = __esm({
|
|
|
10883
11502
|
init_provider();
|
|
10884
11503
|
init_freshness();
|
|
10885
11504
|
init_lint_agents();
|
|
11505
|
+
init_chokepoints();
|
|
10886
11506
|
init_digest();
|
|
10887
11507
|
}
|
|
10888
11508
|
});
|
|
10889
11509
|
|
|
10890
|
-
// src/commands/code-map-
|
|
10891
|
-
var
|
|
10892
|
-
__export(
|
|
10893
|
-
|
|
11510
|
+
// src/commands/code-map-watch.ts
|
|
11511
|
+
var code_map_watch_exports = {};
|
|
11512
|
+
__export(code_map_watch_exports, {
|
|
11513
|
+
codeMapWatch: () => codeMapWatch,
|
|
11514
|
+
createDebouncedRunner: () => createDebouncedRunner,
|
|
11515
|
+
generatedArtifactSet: () => generatedArtifactSet,
|
|
11516
|
+
makeWatchExcludeFilter: () => makeWatchExcludeFilter
|
|
10894
11517
|
});
|
|
10895
|
-
import {
|
|
11518
|
+
import { watch } from "fs";
|
|
11519
|
+
import { resolve as resolve5 } from "path";
|
|
11520
|
+
import picomatch2 from "picomatch";
|
|
11521
|
+
function createDebouncedRunner(run, debounceMs) {
|
|
11522
|
+
let timer = null;
|
|
11523
|
+
let running = false;
|
|
11524
|
+
let queued = false;
|
|
11525
|
+
async function fire() {
|
|
11526
|
+
if (running) {
|
|
11527
|
+
queued = true;
|
|
11528
|
+
return;
|
|
11529
|
+
}
|
|
11530
|
+
running = true;
|
|
11531
|
+
try {
|
|
11532
|
+
await run();
|
|
11533
|
+
} finally {
|
|
11534
|
+
running = false;
|
|
11535
|
+
if (queued) {
|
|
11536
|
+
queued = false;
|
|
11537
|
+
void fire();
|
|
11538
|
+
}
|
|
11539
|
+
}
|
|
11540
|
+
}
|
|
11541
|
+
function trigger() {
|
|
11542
|
+
if (timer)
|
|
11543
|
+
clearTimeout(timer);
|
|
11544
|
+
timer = setTimeout(() => {
|
|
11545
|
+
timer = null;
|
|
11546
|
+
void fire();
|
|
11547
|
+
}, debounceMs);
|
|
11548
|
+
}
|
|
11549
|
+
function dispose() {
|
|
11550
|
+
if (timer) {
|
|
11551
|
+
clearTimeout(timer);
|
|
11552
|
+
timer = null;
|
|
11553
|
+
}
|
|
11554
|
+
}
|
|
11555
|
+
return { trigger, dispose };
|
|
11556
|
+
}
|
|
11557
|
+
function generatedArtifactSet(outRel, cwd = process.cwd()) {
|
|
11558
|
+
return /* @__PURE__ */ new Set([
|
|
11559
|
+
resolve5(cwd, outRel),
|
|
11560
|
+
resolve5(cwd, sidecarPathFor(outRel)),
|
|
11561
|
+
resolve5(cwd, graphPathFor(outRel))
|
|
11562
|
+
]);
|
|
11563
|
+
}
|
|
11564
|
+
function makeWatchExcludeFilter(excludes, configRel = CONFIG_REL_PATH) {
|
|
11565
|
+
if (excludes.length === 0)
|
|
11566
|
+
return () => false;
|
|
11567
|
+
const match = picomatch2(excludes, { dot: true });
|
|
11568
|
+
return (rel) => rel !== configRel && (match(rel) || match(`${rel}/**`));
|
|
11569
|
+
}
|
|
11570
|
+
async function codeMapWatch(opts) {
|
|
11571
|
+
const debounceMs = opts.debounceMs ?? 500;
|
|
11572
|
+
const pollMs = opts.pollMs ?? 2e3;
|
|
11573
|
+
const scanPath = opts.surface ?? opts.path;
|
|
11574
|
+
const root = resolve5(process.cwd(), scanPath);
|
|
11575
|
+
const outRel = opts.out ?? (opts.surface ? `.cdd/code-map.${slugifySurface(opts.surface)}.yml` : ".cdd/code-map.yml");
|
|
11576
|
+
const generatedAbs = generatedArtifactSet(outRel);
|
|
11577
|
+
let isExcluded = () => false;
|
|
11578
|
+
try {
|
|
11579
|
+
const cfg = loadCodeMapConfig(root);
|
|
11580
|
+
isExcluded = makeWatchExcludeFilter([...cfg.exclude, ...opts.exclude ?? []]);
|
|
11581
|
+
} catch {
|
|
11582
|
+
}
|
|
11583
|
+
const rebuild = async () => {
|
|
11584
|
+
const exit = await codeMap({ ...opts, check: false, silent: true });
|
|
11585
|
+
if (exit === 0) {
|
|
11586
|
+
log.ok(`code-map refreshed (${(/* @__PURE__ */ new Date()).toLocaleTimeString()})`);
|
|
11587
|
+
} else {
|
|
11588
|
+
log.warn("code-map refresh reported a problem; map left unchanged where possible.");
|
|
11589
|
+
}
|
|
11590
|
+
return exit;
|
|
11591
|
+
};
|
|
11592
|
+
log.info(`code-map --watch: building initial map for ${scanPath}\u2026`);
|
|
11593
|
+
const initialExit = await rebuild();
|
|
11594
|
+
if (initialExit !== 0) {
|
|
11595
|
+
log.error("code-map --watch: initial build failed; not starting the watcher.");
|
|
11596
|
+
return initialExit;
|
|
11597
|
+
}
|
|
11598
|
+
const { trigger, dispose } = createDebouncedRunner(() => rebuild().then(() => void 0), debounceMs);
|
|
11599
|
+
let watcher = null;
|
|
11600
|
+
let pollTimer = null;
|
|
11601
|
+
let driftChecking = false;
|
|
11602
|
+
const triggerIfDrift = () => {
|
|
11603
|
+
if (driftChecking)
|
|
11604
|
+
return;
|
|
11605
|
+
driftChecking = true;
|
|
11606
|
+
void codeMap({ ...opts, check: true, silent: true }).then((drift) => {
|
|
11607
|
+
if (drift !== 0)
|
|
11608
|
+
trigger();
|
|
11609
|
+
}).finally(() => {
|
|
11610
|
+
driftChecking = false;
|
|
11611
|
+
});
|
|
11612
|
+
};
|
|
11613
|
+
const startPolling = (reason) => {
|
|
11614
|
+
if (pollTimer)
|
|
11615
|
+
return;
|
|
11616
|
+
if (reason)
|
|
11617
|
+
log.warn(reason);
|
|
11618
|
+
pollTimer = setInterval(triggerIfDrift, pollMs);
|
|
11619
|
+
log.ok(`polling ${scanPath} every ${pollMs}ms. Ctrl-C to stop.`);
|
|
11620
|
+
};
|
|
11621
|
+
const isSelfWrite = (filename) => {
|
|
11622
|
+
return generatedAbs.has(resolve5(root, filename));
|
|
11623
|
+
};
|
|
11624
|
+
try {
|
|
11625
|
+
watcher = watch(root, { recursive: true }, (_event, filename) => {
|
|
11626
|
+
if (filename) {
|
|
11627
|
+
const rel = filename.toString();
|
|
11628
|
+
if (isSelfWrite(rel))
|
|
11629
|
+
return;
|
|
11630
|
+
if (isExcluded(rel.replace(/\\/g, "/")))
|
|
11631
|
+
return;
|
|
11632
|
+
trigger();
|
|
11633
|
+
} else {
|
|
11634
|
+
triggerIfDrift();
|
|
11635
|
+
}
|
|
11636
|
+
});
|
|
11637
|
+
watcher.on("error", (err) => {
|
|
11638
|
+
log.warn(`watch error (${err.message}); closing watcher and falling back to polling.`);
|
|
11639
|
+
try {
|
|
11640
|
+
watcher?.close();
|
|
11641
|
+
} catch {
|
|
11642
|
+
}
|
|
11643
|
+
watcher = null;
|
|
11644
|
+
startPolling();
|
|
11645
|
+
});
|
|
11646
|
+
log.ok(`watching ${scanPath} (recursive, debounce ${debounceMs}ms). Ctrl-C to stop.`);
|
|
11647
|
+
} catch {
|
|
11648
|
+
startPolling("recursive fs.watch unavailable on this platform; falling back to freshness polling.");
|
|
11649
|
+
}
|
|
11650
|
+
return await new Promise((resolvePromise) => {
|
|
11651
|
+
const stop = () => {
|
|
11652
|
+
dispose();
|
|
11653
|
+
if (watcher)
|
|
11654
|
+
watcher.close();
|
|
11655
|
+
if (pollTimer)
|
|
11656
|
+
clearInterval(pollTimer);
|
|
11657
|
+
log.info("code-map --watch stopped.");
|
|
11658
|
+
resolvePromise(0);
|
|
11659
|
+
};
|
|
11660
|
+
const signal = opts.signal;
|
|
11661
|
+
if (signal) {
|
|
11662
|
+
if (signal.aborted) {
|
|
11663
|
+
stop();
|
|
11664
|
+
return;
|
|
11665
|
+
}
|
|
11666
|
+
signal.addEventListener("abort", stop, { once: true });
|
|
11667
|
+
}
|
|
11668
|
+
});
|
|
11669
|
+
}
|
|
11670
|
+
var init_code_map_watch = __esm({
|
|
11671
|
+
"src/commands/code-map-watch.ts"() {
|
|
11672
|
+
"use strict";
|
|
11673
|
+
init_logger();
|
|
11674
|
+
init_code_map();
|
|
11675
|
+
init_config();
|
|
11676
|
+
init_index_reader();
|
|
11677
|
+
init_reader();
|
|
11678
|
+
}
|
|
11679
|
+
});
|
|
11680
|
+
|
|
11681
|
+
// src/commands/code-map-scan-worker.ts
|
|
11682
|
+
var code_map_scan_worker_exports = {};
|
|
11683
|
+
__export(code_map_scan_worker_exports, {
|
|
11684
|
+
runScanWorker: () => runScanWorker
|
|
11685
|
+
});
|
|
11686
|
+
import { readFileSync as readFileSync31 } from "fs";
|
|
10896
11687
|
async function runScanWorker(opts) {
|
|
10897
11688
|
let scanner;
|
|
10898
11689
|
switch (opts.lang) {
|
|
@@ -10912,7 +11703,7 @@ async function runScanWorker(opts) {
|
|
|
10912
11703
|
}
|
|
10913
11704
|
let files;
|
|
10914
11705
|
try {
|
|
10915
|
-
files =
|
|
11706
|
+
files = readFileSync31(opts.batchFile, "utf8").split("\n").map((s) => s.trim()).filter(Boolean);
|
|
10916
11707
|
} catch (err) {
|
|
10917
11708
|
process.stderr.write(`__code-map-scan: cannot read --batch-file: ${err.message}
|
|
10918
11709
|
`);
|
|
@@ -10932,10 +11723,16 @@ var init_code_map_scan_worker = __esm({
|
|
|
10932
11723
|
// src/commands/index-query.ts
|
|
10933
11724
|
var index_query_exports = {};
|
|
10934
11725
|
__export(index_query_exports, {
|
|
11726
|
+
DEFAULT_SOURCE_BUDGET: () => DEFAULT_SOURCE_BUDGET,
|
|
10935
11727
|
indexQuery: () => indexQuery,
|
|
10936
|
-
queryEntries: () => queryEntries
|
|
11728
|
+
queryEntries: () => queryEntries,
|
|
11729
|
+
resolveSourceBudget: () => resolveSourceBudget,
|
|
11730
|
+
surfaceRootFor: () => surfaceRootFor
|
|
10937
11731
|
});
|
|
10938
|
-
import { existsSync as
|
|
11732
|
+
import { existsSync as existsSync27, readFileSync as readFileSync32 } from "fs";
|
|
11733
|
+
function resolveSourceBudget(budget) {
|
|
11734
|
+
return Number.isFinite(budget) && budget > 0 ? Math.floor(budget) : DEFAULT_SOURCE_BUDGET;
|
|
11735
|
+
}
|
|
10939
11736
|
async function indexQuery(term, opts) {
|
|
10940
11737
|
const mapPath = opts.map || ".cdd/code-map.yml";
|
|
10941
11738
|
const limit = Number.isFinite(opts.limit) && opts.limit > 0 ? Math.floor(opts.limit) : 10;
|
|
@@ -10945,7 +11742,7 @@ async function indexQuery(term, opts) {
|
|
|
10945
11742
|
return printFailure(freshness.error, opts.json);
|
|
10946
11743
|
}
|
|
10947
11744
|
refreshed = freshness.refreshed;
|
|
10948
|
-
if (!
|
|
11745
|
+
if (!existsSync27(mapPath)) {
|
|
10949
11746
|
return printFailure(`${mapPath} is missing; run \`cdd-kit code-map\` first.`, opts.json);
|
|
10950
11747
|
}
|
|
10951
11748
|
let entries;
|
|
@@ -10955,6 +11752,9 @@ async function indexQuery(term, opts) {
|
|
|
10955
11752
|
return printFailure(`${mapPath} is not readable YAML: ${err.message}`, opts.json);
|
|
10956
11753
|
}
|
|
10957
11754
|
const results = queryEntries(entries, term).slice(0, limit);
|
|
11755
|
+
if (opts.withSource) {
|
|
11756
|
+
attachSource(results, resolveSourceBudget(opts.sourceBudget), surfaceRootFor(mapPath));
|
|
11757
|
+
}
|
|
10958
11758
|
const payload = {
|
|
10959
11759
|
index: mapPath,
|
|
10960
11760
|
query: term,
|
|
@@ -11073,6 +11873,68 @@ function firstLine(lines) {
|
|
|
11073
11873
|
const m = lines?.match(/^\d+/);
|
|
11074
11874
|
return m ? Number(m[0]) : Number.MAX_SAFE_INTEGER;
|
|
11075
11875
|
}
|
|
11876
|
+
function surfaceRootFor(mapPath) {
|
|
11877
|
+
try {
|
|
11878
|
+
if (!existsSync27(mapPath))
|
|
11879
|
+
return void 0;
|
|
11880
|
+
const head = readFileSync32(mapPath, "utf8").slice(0, 4096);
|
|
11881
|
+
const m = head.match(/^# surface-root:\s*(.+)$/m);
|
|
11882
|
+
return m ? m[1].trim() : void 0;
|
|
11883
|
+
} catch {
|
|
11884
|
+
return void 0;
|
|
11885
|
+
}
|
|
11886
|
+
}
|
|
11887
|
+
function attachSource(results, budget, baseDir) {
|
|
11888
|
+
const fileCache = /* @__PURE__ */ new Map();
|
|
11889
|
+
let used = 0;
|
|
11890
|
+
const readLines = (path) => {
|
|
11891
|
+
if (fileCache.has(path))
|
|
11892
|
+
return fileCache.get(path) ?? null;
|
|
11893
|
+
let lines = null;
|
|
11894
|
+
try {
|
|
11895
|
+
const resolved = baseDir && baseDir !== "." ? `${baseDir.replace(/\/+$/, "")}/${path}` : path;
|
|
11896
|
+
lines = existsSync27(resolved) ? readFileSync32(resolved, "utf8").split(/\r?\n/) : null;
|
|
11897
|
+
} catch {
|
|
11898
|
+
lines = null;
|
|
11899
|
+
}
|
|
11900
|
+
fileCache.set(path, lines);
|
|
11901
|
+
return lines;
|
|
11902
|
+
};
|
|
11903
|
+
for (const result of results) {
|
|
11904
|
+
const fileLines = readLines(result.path);
|
|
11905
|
+
if (!fileLines)
|
|
11906
|
+
continue;
|
|
11907
|
+
for (const match of result.matches) {
|
|
11908
|
+
const range = resolveRange(match);
|
|
11909
|
+
if (!range)
|
|
11910
|
+
continue;
|
|
11911
|
+
const [start, end] = range;
|
|
11912
|
+
if (used >= budget) {
|
|
11913
|
+
match.source_truncated = true;
|
|
11914
|
+
continue;
|
|
11915
|
+
}
|
|
11916
|
+
const allowedEnd = Math.min(end, start + (budget - used) - 1);
|
|
11917
|
+
const slice = fileLines.slice(start - 1, allowedEnd);
|
|
11918
|
+
match.source = slice.join("\n");
|
|
11919
|
+
used += slice.length;
|
|
11920
|
+
if (allowedEnd < end)
|
|
11921
|
+
match.source_truncated = true;
|
|
11922
|
+
}
|
|
11923
|
+
}
|
|
11924
|
+
}
|
|
11925
|
+
function resolveRange(match) {
|
|
11926
|
+
if (match.lines) {
|
|
11927
|
+
const m = match.lines.match(/^(\d+)\s*-\s*(\d+)$/);
|
|
11928
|
+
if (m)
|
|
11929
|
+
return [Number(m[1]), Number(m[2])];
|
|
11930
|
+
const single = match.lines.match(/^(\d+)$/);
|
|
11931
|
+
if (single)
|
|
11932
|
+
return [Number(single[1]), Number(single[1])];
|
|
11933
|
+
}
|
|
11934
|
+
if (typeof match.line === "number")
|
|
11935
|
+
return [match.line, match.line];
|
|
11936
|
+
return null;
|
|
11937
|
+
}
|
|
11076
11938
|
function printText(payload) {
|
|
11077
11939
|
if (payload.results.length === 0) {
|
|
11078
11940
|
console.log(`No matches for "${payload.query}" in ${payload.index}.`);
|
|
@@ -11088,9 +11950,18 @@ function printText(payload) {
|
|
|
11088
11950
|
const loc = match.lines ? ` lines ${match.lines}` : match.line ? ` line ${match.line}` : "";
|
|
11089
11951
|
const detail = match.detail && match.detail !== match.name ? ` - ${match.detail}` : "";
|
|
11090
11952
|
console.log(` - ${match.kind}: ${match.name}${loc}${detail}`);
|
|
11953
|
+
if (match.source !== void 0) {
|
|
11954
|
+
for (const srcLine of match.source.split("\n")) {
|
|
11955
|
+
console.log(` | ${srcLine}`);
|
|
11956
|
+
}
|
|
11957
|
+
if (match.source_truncated)
|
|
11958
|
+
console.log(" | \u2026 (source budget reached; Read the rest directly)");
|
|
11959
|
+
} else if (match.source_truncated) {
|
|
11960
|
+
console.log(" (source budget reached; Read this range directly)");
|
|
11961
|
+
}
|
|
11091
11962
|
}
|
|
11092
11963
|
}
|
|
11093
|
-
console.log("Next: read only the listed file/ranges first.");
|
|
11964
|
+
console.log(payload.results.some((r) => r.matches.some((m) => m.source !== void 0)) ? "Source included above \u2014 no separate Read needed for these ranges." : "Next: read only the listed file/ranges first.");
|
|
11094
11965
|
}
|
|
11095
11966
|
function printFailure(message, json) {
|
|
11096
11967
|
if (json) {
|
|
@@ -11101,10 +11972,12 @@ function printFailure(message, json) {
|
|
|
11101
11972
|
}
|
|
11102
11973
|
return 1;
|
|
11103
11974
|
}
|
|
11975
|
+
var DEFAULT_SOURCE_BUDGET;
|
|
11104
11976
|
var init_index_query = __esm({
|
|
11105
11977
|
"src/commands/index-query.ts"() {
|
|
11106
11978
|
"use strict";
|
|
11107
11979
|
init_index_reader();
|
|
11980
|
+
DEFAULT_SOURCE_BUDGET = 400;
|
|
11108
11981
|
}
|
|
11109
11982
|
});
|
|
11110
11983
|
|
|
@@ -11113,7 +11986,7 @@ var index_impact_exports = {};
|
|
|
11113
11986
|
__export(index_impact_exports, {
|
|
11114
11987
|
indexImpact: () => indexImpact
|
|
11115
11988
|
});
|
|
11116
|
-
import { existsSync as
|
|
11989
|
+
import { existsSync as existsSync28 } from "fs";
|
|
11117
11990
|
import { posix as posix2 } from "path";
|
|
11118
11991
|
async function indexImpact(term, opts) {
|
|
11119
11992
|
const mapPath = opts.map || ".cdd/code-map.yml";
|
|
@@ -11122,7 +11995,7 @@ async function indexImpact(term, opts) {
|
|
|
11122
11995
|
if (freshness.error) {
|
|
11123
11996
|
return printFailure2(freshness.error, opts.json);
|
|
11124
11997
|
}
|
|
11125
|
-
if (!
|
|
11998
|
+
if (!existsSync28(mapPath)) {
|
|
11126
11999
|
return printFailure2(`${mapPath} is missing; run \`cdd-kit code-map\` first.`, opts.json);
|
|
11127
12000
|
}
|
|
11128
12001
|
let entries;
|
|
@@ -11476,22 +12349,22 @@ __export(graph_exports, {
|
|
|
11476
12349
|
graphStatus: () => graphStatus,
|
|
11477
12350
|
graphSync: () => graphSync
|
|
11478
12351
|
});
|
|
11479
|
-
import { existsSync as
|
|
11480
|
-
import { join as
|
|
11481
|
-
import { spawnSync as
|
|
12352
|
+
import { existsSync as existsSync29, readFileSync as readFileSync33 } from "fs";
|
|
12353
|
+
import { join as join29 } from "path";
|
|
12354
|
+
import { spawnSync as spawnSync7 } from "child_process";
|
|
11482
12355
|
function codeGraphCommand() {
|
|
11483
12356
|
return process.env.CDD_CODEGRAPH_BIN || "codegraph";
|
|
11484
12357
|
}
|
|
11485
12358
|
function runCodeGraph(args, cwd = process.cwd()) {
|
|
11486
12359
|
const command = codeGraphCommand();
|
|
11487
12360
|
if (command.toLowerCase().endsWith(".js")) {
|
|
11488
|
-
return
|
|
12361
|
+
return spawnSync7(process.execPath, [command, ...args], { cwd, encoding: "buffer" });
|
|
11489
12362
|
}
|
|
11490
|
-
return
|
|
12363
|
+
return spawnSync7(command, args, { cwd, encoding: "buffer" });
|
|
11491
12364
|
}
|
|
11492
12365
|
function probeCodeGraph(cwd = process.cwd()) {
|
|
11493
12366
|
const command = codeGraphCommand();
|
|
11494
|
-
const initialized =
|
|
12367
|
+
const initialized = existsSync29(join29(cwd, ".codegraph"));
|
|
11495
12368
|
const version = runCodeGraph(["--version"], cwd);
|
|
11496
12369
|
if (version.error) {
|
|
11497
12370
|
return {
|
|
@@ -11561,7 +12434,7 @@ async function ensureNativeGraph(mapPath, refresh2) {
|
|
|
11561
12434
|
if (freshness.error)
|
|
11562
12435
|
return { graphPath: graphPathFor(mapPath), refreshed: freshness.refreshed, error: freshness.error };
|
|
11563
12436
|
const graphPath = graphPathFor(mapPath);
|
|
11564
|
-
if (!
|
|
12437
|
+
if (!existsSync29(graphPath) && refresh2) {
|
|
11565
12438
|
const { codeMap: codeMap2 } = await Promise.resolve().then(() => (init_code_map(), code_map_exports));
|
|
11566
12439
|
const exit = await codeMap2({
|
|
11567
12440
|
path: ".",
|
|
@@ -11594,7 +12467,7 @@ async function graphStatus(opts = {}) {
|
|
|
11594
12467
|
const graphPath = graphPathFor(mapPath);
|
|
11595
12468
|
const freshness = checkCodeMapFreshness(cwd, mapPath);
|
|
11596
12469
|
let graphStats;
|
|
11597
|
-
if (
|
|
12470
|
+
if (existsSync29(graphPath)) {
|
|
11598
12471
|
try {
|
|
11599
12472
|
const graph2 = loadCodeGraph(graphPath);
|
|
11600
12473
|
graphStats = { graph: graphPath, nodes: graph2.nodes.length, edges: graph2.edges.length, unresolved: graph2.unresolved.length };
|
|
@@ -11663,8 +12536,10 @@ async function graphQuery(term, opts) {
|
|
|
11663
12536
|
try {
|
|
11664
12537
|
const graph2 = loadCodeGraph(ensured.graphPath);
|
|
11665
12538
|
const results = searchGraph(graph2, term, opts.limit);
|
|
12539
|
+
const sources = opts.withSource ? collectNodeSources(results.map((r) => r.node), resolveSourceBudget(opts.sourceBudget), surfaceRootFor(mapPath)) : /* @__PURE__ */ new Map();
|
|
11666
12540
|
if (opts.json) {
|
|
11667
|
-
|
|
12541
|
+
const withSrc = opts.withSource ? results.map((r) => ({ ...r, source: sources.get(r.node.id)?.source, source_truncated: sources.get(r.node.id)?.truncated })) : results;
|
|
12542
|
+
writeJson({ engine: "native", graph: ensured.graphPath, query: term, refreshed: ensured.refreshed, results: withSrc });
|
|
11668
12543
|
} else {
|
|
11669
12544
|
console.log(`graph: ${ensured.graphPath}${ensured.refreshed ? " (refreshed)" : ""}`);
|
|
11670
12545
|
console.log(`query: ${term}`);
|
|
@@ -11673,8 +12548,15 @@ async function graphQuery(term, opts) {
|
|
|
11673
12548
|
const n = result.node;
|
|
11674
12549
|
console.log(`- ${n.kind}: ${n.qualified_name} lines ${n.start_line}-${n.end_line}`);
|
|
11675
12550
|
console.log(` edges: ${result.edges.incoming} incoming, ${result.edges.outgoing} outgoing`);
|
|
12551
|
+
const src = sources.get(n.id);
|
|
12552
|
+
if (src) {
|
|
12553
|
+
for (const srcLine of src.source.split("\n"))
|
|
12554
|
+
console.log(` | ${srcLine}`);
|
|
12555
|
+
if (src.truncated)
|
|
12556
|
+
console.log(" | \u2026 (source budget reached; Read the rest directly)");
|
|
12557
|
+
}
|
|
11676
12558
|
}
|
|
11677
|
-
console.log("Next: run cdd-kit graph impact <node/file/symbol> before editing.");
|
|
12559
|
+
console.log(opts.withSource ? "Source included above \u2014 no separate Read needed for these ranges." : "Next: run cdd-kit graph impact <node/file/symbol> before editing.");
|
|
11678
12560
|
}
|
|
11679
12561
|
return results.length === 0 ? 1 : 0;
|
|
11680
12562
|
} catch (err) {
|
|
@@ -11685,9 +12567,44 @@ async function graphQuery(term, opts) {
|
|
|
11685
12567
|
map: opts.map || ".cdd/code-map.yml",
|
|
11686
12568
|
limit: opts.limit,
|
|
11687
12569
|
json: opts.json === true,
|
|
11688
|
-
refresh: opts.refresh !== false
|
|
12570
|
+
refresh: opts.refresh !== false,
|
|
12571
|
+
withSource: opts.withSource === true,
|
|
12572
|
+
sourceBudget: resolveSourceBudget(opts.sourceBudget)
|
|
11689
12573
|
});
|
|
11690
12574
|
}
|
|
12575
|
+
function collectNodeSources(nodes, budget, baseDir) {
|
|
12576
|
+
const out = /* @__PURE__ */ new Map();
|
|
12577
|
+
const fileCache = /* @__PURE__ */ new Map();
|
|
12578
|
+
let used = 0;
|
|
12579
|
+
const readLines = (path) => {
|
|
12580
|
+
if (fileCache.has(path))
|
|
12581
|
+
return fileCache.get(path) ?? null;
|
|
12582
|
+
let lines = null;
|
|
12583
|
+
try {
|
|
12584
|
+
const resolved = baseDir && baseDir !== "." ? `${baseDir.replace(/\/+$/, "")}/${path}` : path;
|
|
12585
|
+
lines = existsSync29(resolved) ? readFileSync33(resolved, "utf8").split(/\r?\n/) : null;
|
|
12586
|
+
} catch {
|
|
12587
|
+
lines = null;
|
|
12588
|
+
}
|
|
12589
|
+
fileCache.set(path, lines);
|
|
12590
|
+
return lines;
|
|
12591
|
+
};
|
|
12592
|
+
for (const node of nodes) {
|
|
12593
|
+
const fileLines = readLines(node.file_path);
|
|
12594
|
+
if (!fileLines || !node.start_line)
|
|
12595
|
+
continue;
|
|
12596
|
+
if (used >= budget) {
|
|
12597
|
+
out.set(node.id, { source: "", truncated: true });
|
|
12598
|
+
continue;
|
|
12599
|
+
}
|
|
12600
|
+
const end = node.end_line && node.end_line >= node.start_line ? node.end_line : node.start_line;
|
|
12601
|
+
const allowedEnd = Math.min(end, node.start_line + (budget - used) - 1);
|
|
12602
|
+
const slice = fileLines.slice(node.start_line - 1, allowedEnd);
|
|
12603
|
+
out.set(node.id, { source: slice.join("\n"), truncated: allowedEnd < end });
|
|
12604
|
+
used += slice.length;
|
|
12605
|
+
}
|
|
12606
|
+
return out;
|
|
12607
|
+
}
|
|
11691
12608
|
async function graphImpact2(term, opts) {
|
|
11692
12609
|
const selected = selectEngine(opts);
|
|
11693
12610
|
if ("error" in selected)
|
|
@@ -11783,7 +12700,7 @@ async function graphContext2(task, opts) {
|
|
|
11783
12700
|
const freshness = await ensureCodeMapFresh(mapPath, opts.refresh !== false);
|
|
11784
12701
|
if (freshness.error)
|
|
11785
12702
|
return printEngineError(freshness.error, opts.json, selected.probe);
|
|
11786
|
-
if (!
|
|
12703
|
+
if (!existsSync29(mapPath))
|
|
11787
12704
|
return printEngineError(`${mapPath} is missing; run \`cdd-kit code-map\` first.`, opts.json, selected.probe);
|
|
11788
12705
|
let entries;
|
|
11789
12706
|
try {
|
|
@@ -11834,12 +12751,70 @@ var init_graph = __esm({
|
|
|
11834
12751
|
}
|
|
11835
12752
|
});
|
|
11836
12753
|
|
|
12754
|
+
// src/commands/classify-check.ts
|
|
12755
|
+
var classify_check_exports = {};
|
|
12756
|
+
__export(classify_check_exports, {
|
|
12757
|
+
classifyCheck: () => classifyCheck
|
|
12758
|
+
});
|
|
12759
|
+
import { existsSync as existsSync30, readFileSync as readFileSync34 } from "fs";
|
|
12760
|
+
import { join as join30 } from "path";
|
|
12761
|
+
async function classifyCheck(changeId, opts = {}) {
|
|
12762
|
+
const cwd = process.cwd();
|
|
12763
|
+
const policy = loadTierPolicy(cwd);
|
|
12764
|
+
let intent = opts.text ?? "";
|
|
12765
|
+
let source = "inline --text";
|
|
12766
|
+
let paths = [];
|
|
12767
|
+
if (!intent && changeId) {
|
|
12768
|
+
const requestPath = join30(cwd, "specs", "changes", changeId, "change-request.md");
|
|
12769
|
+
if (existsSync30(requestPath))
|
|
12770
|
+
intent = readFileSync34(requestPath, "utf8");
|
|
12771
|
+
paths = getTouchedPaths(cwd);
|
|
12772
|
+
source = `specs/changes/${changeId}/change-request.md + touched paths`;
|
|
12773
|
+
}
|
|
12774
|
+
const floor = computeTierFloor(intent, policy, { paths });
|
|
12775
|
+
if (opts.json) {
|
|
12776
|
+
console.log(JSON.stringify({
|
|
12777
|
+
enabled: policy.enabled,
|
|
12778
|
+
source,
|
|
12779
|
+
floorTier: floor.floorTier,
|
|
12780
|
+
label: floor.label,
|
|
12781
|
+
matched: floor.matched
|
|
12782
|
+
}, null, 2));
|
|
12783
|
+
return 0;
|
|
12784
|
+
}
|
|
12785
|
+
if (!policy.enabled) {
|
|
12786
|
+
log.info("tier-floor policy is disabled (.cdd/tier-policy.json enabled:false) \u2014 no floor enforced.");
|
|
12787
|
+
return 0;
|
|
12788
|
+
}
|
|
12789
|
+
if (!intent && paths.length === 0) {
|
|
12790
|
+
log.warn('no intent text or touched paths found to scan (pass --text "<description>" or a change-id with change-request.md).');
|
|
12791
|
+
return 0;
|
|
12792
|
+
}
|
|
12793
|
+
if (floor.floorTier === null) {
|
|
12794
|
+
log.ok("no sensitive surface matched \u2014 classifier may assign any tier on merit.");
|
|
12795
|
+
return 0;
|
|
12796
|
+
}
|
|
12797
|
+
log.warn(`mechanical tier floor: ${floor.floorTier} (or stricter)`);
|
|
12798
|
+
log.info(` reason: ${floor.label}`);
|
|
12799
|
+
log.info(` matched: ${floor.matched.join(", ")}`);
|
|
12800
|
+
log.info(` \u2192 change-classification.md must declare tier ${floor.floorTier} or stricter; the gate enforces this.`);
|
|
12801
|
+
return 0;
|
|
12802
|
+
}
|
|
12803
|
+
var init_classify_check = __esm({
|
|
12804
|
+
"src/commands/classify-check.ts"() {
|
|
12805
|
+
"use strict";
|
|
12806
|
+
init_logger();
|
|
12807
|
+
init_tier_floor();
|
|
12808
|
+
init_git_paths();
|
|
12809
|
+
}
|
|
12810
|
+
});
|
|
12811
|
+
|
|
11837
12812
|
// src/mcp/server.ts
|
|
11838
12813
|
var server_exports = {};
|
|
11839
12814
|
__export(server_exports, {
|
|
11840
12815
|
runMcpServer: () => runMcpServer
|
|
11841
12816
|
});
|
|
11842
|
-
import { spawnSync as
|
|
12817
|
+
import { spawnSync as spawnSync8 } from "child_process";
|
|
11843
12818
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
11844
12819
|
import { createInterface } from "readline";
|
|
11845
12820
|
async function runMcpServer(opts) {
|
|
@@ -11930,6 +12905,7 @@ function callTool(name, args) {
|
|
|
11930
12905
|
"--limit",
|
|
11931
12906
|
String(optionalInt(args.limit, 10)),
|
|
11932
12907
|
"--json",
|
|
12908
|
+
...sourceArgs(args),
|
|
11933
12909
|
...refreshArgs(args)
|
|
11934
12910
|
]);
|
|
11935
12911
|
case "cdd_graph_context":
|
|
@@ -11972,6 +12948,7 @@ function callTool(name, args) {
|
|
|
11972
12948
|
"--limit",
|
|
11973
12949
|
String(optionalInt(args.limit, 10)),
|
|
11974
12950
|
"--json",
|
|
12951
|
+
...sourceArgs(args),
|
|
11975
12952
|
...refreshArgs(args)
|
|
11976
12953
|
]);
|
|
11977
12954
|
case "cdd_index_impact":
|
|
@@ -11995,7 +12972,7 @@ function callTool(name, args) {
|
|
|
11995
12972
|
}
|
|
11996
12973
|
function runCddJson(args) {
|
|
11997
12974
|
const cliPath = process.argv[1] || fileURLToPath3(import.meta.url);
|
|
11998
|
-
const result =
|
|
12975
|
+
const result = spawnSync8(process.execPath, [cliPath, ...args], {
|
|
11999
12976
|
cwd: process.cwd(),
|
|
12000
12977
|
env: process.env,
|
|
12001
12978
|
encoding: "utf8"
|
|
@@ -12017,6 +12994,11 @@ function runCddJson(args) {
|
|
|
12017
12994
|
function refreshArgs(args) {
|
|
12018
12995
|
return args.refresh === false ? ["--no-refresh"] : [];
|
|
12019
12996
|
}
|
|
12997
|
+
function sourceArgs(args) {
|
|
12998
|
+
if (args.withSource !== true)
|
|
12999
|
+
return [];
|
|
13000
|
+
return ["--with-source", "--source-budget", String(optionalInt(args.sourceBudget, 400))];
|
|
13001
|
+
}
|
|
12020
13002
|
function asObject(value) {
|
|
12021
13003
|
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
12022
13004
|
}
|
|
@@ -12062,7 +13044,7 @@ var init_server = __esm({
|
|
|
12062
13044
|
},
|
|
12063
13045
|
{
|
|
12064
13046
|
name: "cdd_graph_query",
|
|
12065
|
-
description: "Search native graph symbols/files and return candidate nodes with line ranges.",
|
|
13047
|
+
description: "Search native graph symbols/files and return candidate nodes with line ranges. Set withSource:true to also return the source slices inline so no separate file read is needed.",
|
|
12066
13048
|
inputSchema: {
|
|
12067
13049
|
type: "object",
|
|
12068
13050
|
properties: {
|
|
@@ -12070,6 +13052,8 @@ var init_server = __esm({
|
|
|
12070
13052
|
limit: { type: "integer", minimum: 1, maximum: 100, default: 10 },
|
|
12071
13053
|
map: { type: "string", description: "Code-map YAML path.", default: DEFAULT_MAP },
|
|
12072
13054
|
engine: { type: "string", enum: ["auto", "native", "codegraph", "codemap"], default: "auto" },
|
|
13055
|
+
withSource: { type: "boolean", description: "Include matched source slices inline (replaces a follow-up read).", default: false },
|
|
13056
|
+
sourceBudget: { type: "integer", minimum: 1, maximum: 5e3, default: 400, description: "Max total source lines to return when withSource is true." },
|
|
12073
13057
|
refresh: { type: "boolean", default: true }
|
|
12074
13058
|
},
|
|
12075
13059
|
required: ["query"],
|
|
@@ -12111,13 +13095,15 @@ var init_server = __esm({
|
|
|
12111
13095
|
},
|
|
12112
13096
|
{
|
|
12113
13097
|
name: "cdd_index_query",
|
|
12114
|
-
description: "Fallback code-map query for files, symbols, imports, and line ranges.",
|
|
13098
|
+
description: "Fallback code-map query for files, symbols, imports, and line ranges. Set withSource:true to also return the source slices inline so no separate file read is needed.",
|
|
12115
13099
|
inputSchema: {
|
|
12116
13100
|
type: "object",
|
|
12117
13101
|
properties: {
|
|
12118
13102
|
query: { type: "string", description: "Symbol, file path, import module, enum member, or substring." },
|
|
12119
13103
|
limit: { type: "integer", minimum: 1, maximum: 100, default: 10 },
|
|
12120
13104
|
map: { type: "string", description: "Code-map YAML path.", default: DEFAULT_MAP },
|
|
13105
|
+
withSource: { type: "boolean", description: "Include matched source slices inline (replaces a follow-up read).", default: false },
|
|
13106
|
+
sourceBudget: { type: "integer", minimum: 1, maximum: 5e3, default: 400, description: "Max total source lines to return when withSource is true." },
|
|
12121
13107
|
refresh: { type: "boolean", default: true }
|
|
12122
13108
|
},
|
|
12123
13109
|
required: ["query"],
|
|
@@ -12148,28 +13134,28 @@ var archive_exports = {};
|
|
|
12148
13134
|
__export(archive_exports, {
|
|
12149
13135
|
archive: () => archive
|
|
12150
13136
|
});
|
|
12151
|
-
import { join as
|
|
12152
|
-
import { existsSync as
|
|
13137
|
+
import { join as join31 } from "path";
|
|
13138
|
+
import { existsSync as existsSync31, mkdirSync as mkdirSync12, renameSync as renameSync2, readFileSync as readFileSync35, writeFileSync as writeFileSync16, appendFileSync, cpSync as cpSync3, rmSync as rmSync3 } from "fs";
|
|
12153
13139
|
import yaml4 from "js-yaml";
|
|
12154
13140
|
async function archive(changeId) {
|
|
12155
13141
|
const cwd = process.cwd();
|
|
12156
|
-
const changeDir =
|
|
13142
|
+
const changeDir = join31(cwd, "specs", "changes", changeId);
|
|
12157
13143
|
const archiveYear = (/* @__PURE__ */ new Date()).getFullYear().toString();
|
|
12158
|
-
const archiveBase =
|
|
12159
|
-
const archiveDir =
|
|
12160
|
-
const indexPath =
|
|
12161
|
-
if (!
|
|
13144
|
+
const archiveBase = join31(cwd, "specs", "archive", archiveYear);
|
|
13145
|
+
const archiveDir = join31(archiveBase, changeId);
|
|
13146
|
+
const indexPath = join31(cwd, "specs", "archive", "INDEX.md");
|
|
13147
|
+
if (!existsSync31(changeDir)) {
|
|
12162
13148
|
log.error(`Change not found: specs/changes/${changeId}`);
|
|
12163
13149
|
process.exit(1);
|
|
12164
13150
|
}
|
|
12165
|
-
if (
|
|
13151
|
+
if (existsSync31(archiveDir)) {
|
|
12166
13152
|
log.error(`Already archived: specs/archive/${archiveYear}/${changeId}`);
|
|
12167
13153
|
process.exit(1);
|
|
12168
13154
|
}
|
|
12169
|
-
const tasksPath =
|
|
12170
|
-
if (
|
|
13155
|
+
const tasksPath = join31(changeDir, "tasks.yml");
|
|
13156
|
+
if (existsSync31(tasksPath)) {
|
|
12171
13157
|
try {
|
|
12172
|
-
const raw =
|
|
13158
|
+
const raw = readFileSync35(tasksPath, "utf8");
|
|
12173
13159
|
const data = yaml4.load(raw);
|
|
12174
13160
|
if (data?.status === "gate-blocked") {
|
|
12175
13161
|
log.warn("tasks.yml has status: gate-blocked \u2014 archiving anyway (change was paused).");
|
|
@@ -12182,8 +13168,8 @@ async function archive(changeId) {
|
|
|
12182
13168
|
log.warn("tasks.yml could not be parsed \u2014 archiving anyway.");
|
|
12183
13169
|
}
|
|
12184
13170
|
}
|
|
12185
|
-
if (!
|
|
12186
|
-
|
|
13171
|
+
if (!existsSync31(archiveBase)) {
|
|
13172
|
+
mkdirSync12(archiveBase, { recursive: true });
|
|
12187
13173
|
}
|
|
12188
13174
|
try {
|
|
12189
13175
|
renameSync2(changeDir, archiveDir);
|
|
@@ -12199,8 +13185,8 @@ async function archive(changeId) {
|
|
|
12199
13185
|
const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
12200
13186
|
const indexLine = `| ${changeId} | ${archiveYear} | ${today} | specs/archive/${archiveYear}/${changeId}/ |
|
|
12201
13187
|
`;
|
|
12202
|
-
if (!
|
|
12203
|
-
|
|
13188
|
+
if (!existsSync31(indexPath)) {
|
|
13189
|
+
writeFileSync16(indexPath, `# Archive Index
|
|
12204
13190
|
|
|
12205
13191
|
| change-id | year | archived-date | path |
|
|
12206
13192
|
|---|---|---|---|
|
|
@@ -12224,37 +13210,37 @@ var abandon_exports = {};
|
|
|
12224
13210
|
__export(abandon_exports, {
|
|
12225
13211
|
abandon: () => abandon
|
|
12226
13212
|
});
|
|
12227
|
-
import { join as
|
|
12228
|
-
import { existsSync as
|
|
13213
|
+
import { join as join32 } from "path";
|
|
13214
|
+
import { existsSync as existsSync32, readFileSync as readFileSync36, writeFileSync as writeFileSync17, appendFileSync as appendFileSync2, mkdirSync as mkdirSync13 } from "fs";
|
|
12229
13215
|
import yaml5 from "js-yaml";
|
|
12230
13216
|
async function abandon(changeId, opts) {
|
|
12231
13217
|
const cwd = process.cwd();
|
|
12232
|
-
const changeDir =
|
|
12233
|
-
const tasksPath =
|
|
12234
|
-
if (!
|
|
13218
|
+
const changeDir = join32(cwd, "specs", "changes", changeId);
|
|
13219
|
+
const tasksPath = join32(changeDir, "tasks.yml");
|
|
13220
|
+
if (!existsSync32(changeDir)) {
|
|
12235
13221
|
log.error(`Change not found: specs/changes/${changeId}`);
|
|
12236
13222
|
process.exit(1);
|
|
12237
13223
|
}
|
|
12238
|
-
if (
|
|
12239
|
-
const raw =
|
|
13224
|
+
if (existsSync32(tasksPath)) {
|
|
13225
|
+
const raw = readFileSync36(tasksPath, "utf8");
|
|
12240
13226
|
const data = yaml5.load(raw) ?? {};
|
|
12241
13227
|
data["status"] = "abandoned";
|
|
12242
13228
|
if (!data["change-id"]) {
|
|
12243
13229
|
data["change-id"] = changeId;
|
|
12244
13230
|
}
|
|
12245
|
-
|
|
13231
|
+
writeFileSync17(tasksPath, yaml5.dump(data, { lineWidth: -1, noRefs: true }), "utf8");
|
|
12246
13232
|
}
|
|
12247
13233
|
const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
12248
|
-
const archiveDir =
|
|
12249
|
-
const indexPath =
|
|
13234
|
+
const archiveDir = join32(cwd, "specs", "archive");
|
|
13235
|
+
const indexPath = join32(archiveDir, "INDEX.md");
|
|
12250
13236
|
const reason = opts.reason ?? "no reason given";
|
|
12251
13237
|
const indexLine = `| ${changeId} | abandoned | ${today} | ${reason} |
|
|
12252
13238
|
`;
|
|
12253
|
-
if (!
|
|
12254
|
-
|
|
13239
|
+
if (!existsSync32(archiveDir)) {
|
|
13240
|
+
mkdirSync13(archiveDir, { recursive: true });
|
|
12255
13241
|
}
|
|
12256
|
-
if (!
|
|
12257
|
-
|
|
13242
|
+
if (!existsSync32(indexPath)) {
|
|
13243
|
+
writeFileSync17(indexPath, `# Archive Index
|
|
12258
13244
|
|
|
12259
13245
|
| change-id | status | date | notes |
|
|
12260
13246
|
|---|---|---|---|
|
|
@@ -12278,28 +13264,28 @@ var list_changes_exports = {};
|
|
|
12278
13264
|
__export(list_changes_exports, {
|
|
12279
13265
|
listChanges: () => listChanges
|
|
12280
13266
|
});
|
|
12281
|
-
import { join as
|
|
12282
|
-
import { existsSync as
|
|
13267
|
+
import { join as join33 } from "path";
|
|
13268
|
+
import { existsSync as existsSync33, readdirSync as readdirSync14, readFileSync as readFileSync37 } from "fs";
|
|
12283
13269
|
import yaml6 from "js-yaml";
|
|
12284
13270
|
async function listChanges() {
|
|
12285
13271
|
const cwd = process.cwd();
|
|
12286
|
-
const changesDir =
|
|
13272
|
+
const changesDir = join33(cwd, "specs", "changes");
|
|
12287
13273
|
log.blank();
|
|
12288
13274
|
const active = [];
|
|
12289
|
-
if (
|
|
12290
|
-
active.push(...
|
|
13275
|
+
if (existsSync33(changesDir)) {
|
|
13276
|
+
active.push(...readdirSync14(changesDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name));
|
|
12291
13277
|
}
|
|
12292
13278
|
if (active.length === 0) {
|
|
12293
13279
|
log.info("No active changes in specs/changes/");
|
|
12294
13280
|
} else {
|
|
12295
13281
|
log.info("Active changes:");
|
|
12296
13282
|
for (const id of active) {
|
|
12297
|
-
const tasksPath =
|
|
13283
|
+
const tasksPath = join33(changesDir, id, "tasks.yml");
|
|
12298
13284
|
let status = "in-progress";
|
|
12299
13285
|
let pending = 0;
|
|
12300
|
-
if (
|
|
13286
|
+
if (existsSync33(tasksPath)) {
|
|
12301
13287
|
try {
|
|
12302
|
-
const raw =
|
|
13288
|
+
const raw = readFileSync37(tasksPath, "utf8");
|
|
12303
13289
|
const data = yaml6.load(raw);
|
|
12304
13290
|
if (data?.status)
|
|
12305
13291
|
status = data.status;
|
|
@@ -12331,9 +13317,9 @@ __export(context_exports, {
|
|
|
12331
13317
|
rejectContextExpansion: () => rejectContextExpansion,
|
|
12332
13318
|
requestContextExpansion: () => requestContextExpansion
|
|
12333
13319
|
});
|
|
12334
|
-
import { existsSync as
|
|
12335
|
-
import { join as
|
|
12336
|
-
import
|
|
13320
|
+
import { existsSync as existsSync34, readFileSync as readFileSync38, writeFileSync as writeFileSync18 } from "fs";
|
|
13321
|
+
import { join as join34 } from "path";
|
|
13322
|
+
import picomatch3 from "picomatch";
|
|
12337
13323
|
function normalizePath(path) {
|
|
12338
13324
|
return path.replace(/\\/g, "/").replace(/^\.\//, "").trim();
|
|
12339
13325
|
}
|
|
@@ -12347,18 +13333,18 @@ function validateRepoRelativePath(path) {
|
|
|
12347
13333
|
return null;
|
|
12348
13334
|
}
|
|
12349
13335
|
function manifestPathFor(changeId) {
|
|
12350
|
-
return
|
|
13336
|
+
return join34(process.cwd(), "specs", "changes", changeId, "context-manifest.md");
|
|
12351
13337
|
}
|
|
12352
13338
|
function readManifest(changeId) {
|
|
12353
13339
|
const manifestPath = manifestPathFor(changeId);
|
|
12354
|
-
if (!
|
|
13340
|
+
if (!existsSync34(manifestPath)) {
|
|
12355
13341
|
log.error(`context manifest not found: specs/changes/${changeId}/context-manifest.md`);
|
|
12356
13342
|
process.exit(1);
|
|
12357
13343
|
}
|
|
12358
|
-
return
|
|
13344
|
+
return readFileSync38(manifestPath, "utf8");
|
|
12359
13345
|
}
|
|
12360
13346
|
function writeManifest(changeId, content) {
|
|
12361
|
-
|
|
13347
|
+
writeFileSync18(manifestPathFor(changeId), content.endsWith("\n") ? content : `${content}
|
|
12362
13348
|
`, "utf8");
|
|
12363
13349
|
}
|
|
12364
13350
|
function sectionBody(content, heading) {
|
|
@@ -12384,7 +13370,7 @@ function pathMatches(relPath, patterns, currentChangeId) {
|
|
|
12384
13370
|
return normalized.startsWith("specs/changes/");
|
|
12385
13371
|
}
|
|
12386
13372
|
if (/[*?[{]/.test(pattern)) {
|
|
12387
|
-
if (
|
|
13373
|
+
if (picomatch3.isMatch(normalized, pattern, { dot: true, nocase: false }))
|
|
12388
13374
|
return true;
|
|
12389
13375
|
if (pattern.endsWith("/**")) {
|
|
12390
13376
|
const base = pattern.slice(0, -3);
|
|
@@ -12397,11 +13383,11 @@ function pathMatches(relPath, patterns, currentChangeId) {
|
|
|
12397
13383
|
});
|
|
12398
13384
|
}
|
|
12399
13385
|
function loadContextPolicy() {
|
|
12400
|
-
const policyPath =
|
|
12401
|
-
if (!
|
|
13386
|
+
const policyPath = join34(process.cwd(), ".cdd", "context-policy.json");
|
|
13387
|
+
if (!existsSync34(policyPath))
|
|
12402
13388
|
return { forbiddenPaths: DEFAULT_FORBIDDEN_PATHS };
|
|
12403
13389
|
try {
|
|
12404
|
-
const custom = JSON.parse(
|
|
13390
|
+
const custom = JSON.parse(readFileSync38(policyPath, "utf8"));
|
|
12405
13391
|
return {
|
|
12406
13392
|
forbiddenPaths: Array.from(/* @__PURE__ */ new Set([
|
|
12407
13393
|
...DEFAULT_FORBIDDEN_PATHS,
|
|
@@ -12683,16 +13669,16 @@ var init_context = __esm({
|
|
|
12683
13669
|
});
|
|
12684
13670
|
|
|
12685
13671
|
// src/cli/index.ts
|
|
12686
|
-
import { readFileSync as
|
|
13672
|
+
import { readFileSync as readFileSync39 } from "fs";
|
|
12687
13673
|
import os from "os";
|
|
12688
13674
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
12689
|
-
import { dirname as
|
|
13675
|
+
import { dirname as dirname8, join as join35 } from "path";
|
|
12690
13676
|
import { Command } from "commander";
|
|
12691
13677
|
|
|
12692
13678
|
// src/commands/init.ts
|
|
12693
13679
|
init_paths();
|
|
12694
|
-
import { join as
|
|
12695
|
-
import { rmSync, readFileSync as
|
|
13680
|
+
import { join as join8 } from "path";
|
|
13681
|
+
import { rmSync, readFileSync as readFileSync6, writeFileSync as writeFileSync5, existsSync as existsSync7, readdirSync as readdirSync2 } from "fs";
|
|
12696
13682
|
|
|
12697
13683
|
// src/utils/copy.ts
|
|
12698
13684
|
init_logger();
|
|
@@ -12860,14 +13846,57 @@ function detectStack(repoRoot) {
|
|
|
12860
13846
|
};
|
|
12861
13847
|
}
|
|
12862
13848
|
|
|
13849
|
+
// src/commands/suggest-codegen.ts
|
|
13850
|
+
import { existsSync as existsSync3, readFileSync as readFileSync2, writeFileSync } from "fs";
|
|
13851
|
+
import { join as join4 } from "path";
|
|
13852
|
+
var ARTIFACT_PATH = "contracts/api/openapi.json";
|
|
13853
|
+
var CLIENT_OUT = "src/api/types.ts";
|
|
13854
|
+
var SCRIPT_GENERATE = "contract:client";
|
|
13855
|
+
var SCRIPT_CHECK = "contract:client:check";
|
|
13856
|
+
var GENERATE_CMD = `cdd-kit openapi export --out ${ARTIFACT_PATH} && npx --yes openapi-typescript ${ARTIFACT_PATH} -o ${CLIENT_OUT}`;
|
|
13857
|
+
var CHECK_CMD = `cdd-kit openapi export --check --out ${ARTIFACT_PATH}`;
|
|
13858
|
+
function suggestCodegenScript(cwd) {
|
|
13859
|
+
const pkgPath = join4(cwd, "package.json");
|
|
13860
|
+
if (!existsSync3(pkgPath)) {
|
|
13861
|
+
return { added: [], skipped: "no package.json (client codegen is consumer-specific; see docs/openapi-export.md)" };
|
|
13862
|
+
}
|
|
13863
|
+
let pkg2;
|
|
13864
|
+
let raw;
|
|
13865
|
+
try {
|
|
13866
|
+
raw = readFileSync2(pkgPath, "utf8");
|
|
13867
|
+
pkg2 = JSON.parse(raw);
|
|
13868
|
+
} catch {
|
|
13869
|
+
return { added: [], skipped: "package.json is not valid JSON; left untouched" };
|
|
13870
|
+
}
|
|
13871
|
+
if (typeof pkg2 !== "object" || pkg2 === null || Array.isArray(pkg2)) {
|
|
13872
|
+
return { added: [], skipped: "package.json is not a JSON object; left untouched" };
|
|
13873
|
+
}
|
|
13874
|
+
const scripts = typeof pkg2.scripts === "object" && pkg2.scripts !== null && !Array.isArray(pkg2.scripts) ? pkg2.scripts : {};
|
|
13875
|
+
const hasGenerate = SCRIPT_GENERATE in scripts;
|
|
13876
|
+
const hasCheck = SCRIPT_CHECK in scripts;
|
|
13877
|
+
if (hasGenerate || hasCheck) {
|
|
13878
|
+
const present = [hasGenerate ? SCRIPT_GENERATE : "", hasCheck ? SCRIPT_CHECK : ""].filter(Boolean).join(" + ");
|
|
13879
|
+
return { added: [], skipped: `${present} already present in package.json; leaving codegen wiring to you` };
|
|
13880
|
+
}
|
|
13881
|
+
scripts[SCRIPT_GENERATE] = GENERATE_CMD;
|
|
13882
|
+
scripts[SCRIPT_CHECK] = CHECK_CMD;
|
|
13883
|
+
pkg2.scripts = scripts;
|
|
13884
|
+
const trailing = raw.endsWith("\n") ? "\n" : "";
|
|
13885
|
+
writeFileSync(pkgPath, JSON.stringify(pkg2, null, 2) + trailing, "utf8");
|
|
13886
|
+
return {
|
|
13887
|
+
added: [SCRIPT_GENERATE, SCRIPT_CHECK],
|
|
13888
|
+
note: `requires openapi-typescript (\`npm i -D openapi-typescript\`); edit the output path (${CLIENT_OUT}) to fit your project, then wire \`npm run ${SCRIPT_CHECK}\` into CI`
|
|
13889
|
+
};
|
|
13890
|
+
}
|
|
13891
|
+
|
|
12863
13892
|
// src/commands/init.ts
|
|
12864
13893
|
init_mcp_hint();
|
|
12865
13894
|
function readCondaEnvName(cwd) {
|
|
12866
|
-
const envYml =
|
|
12867
|
-
if (!
|
|
13895
|
+
const envYml = join8(cwd, "environment.yml");
|
|
13896
|
+
if (!existsSync7(envYml))
|
|
12868
13897
|
return "base";
|
|
12869
13898
|
try {
|
|
12870
|
-
const content =
|
|
13899
|
+
const content = readFileSync6(envYml, "utf8");
|
|
12871
13900
|
const match = content.match(/^name:\s*(.+)$/m);
|
|
12872
13901
|
return match ? match[1].trim() : "base";
|
|
12873
13902
|
} catch {
|
|
@@ -12875,11 +13904,11 @@ function readCondaEnvName(cwd) {
|
|
|
12875
13904
|
}
|
|
12876
13905
|
}
|
|
12877
13906
|
function loadCiTemplate(stack) {
|
|
12878
|
-
const templatePath =
|
|
12879
|
-
if (!
|
|
13907
|
+
const templatePath = join8(ASSETS_DIR, "ci-templates", `${stack}.yml`);
|
|
13908
|
+
if (!existsSync7(templatePath))
|
|
12880
13909
|
return null;
|
|
12881
13910
|
try {
|
|
12882
|
-
return
|
|
13911
|
+
return readFileSync6(templatePath, "utf8");
|
|
12883
13912
|
} catch {
|
|
12884
13913
|
return null;
|
|
12885
13914
|
}
|
|
@@ -12915,6 +13944,7 @@ async function init(opts) {
|
|
|
12915
13944
|
}
|
|
12916
13945
|
const cwd = process.cwd();
|
|
12917
13946
|
const createdPaths = [];
|
|
13947
|
+
const restoreActions = [];
|
|
12918
13948
|
const installClaude = opts.provider === "claude" || opts.provider === "both";
|
|
12919
13949
|
const installCodex = opts.provider === "codex" || opts.provider === "both";
|
|
12920
13950
|
function track(paths) {
|
|
@@ -12930,6 +13960,13 @@ async function init(opts) {
|
|
|
12930
13960
|
log.warn(`could not remove: ${p}`);
|
|
12931
13961
|
}
|
|
12932
13962
|
}
|
|
13963
|
+
for (const restore of [...restoreActions].reverse()) {
|
|
13964
|
+
try {
|
|
13965
|
+
restore();
|
|
13966
|
+
} catch {
|
|
13967
|
+
log.warn("could not restore an in-place file edit");
|
|
13968
|
+
}
|
|
13969
|
+
}
|
|
12933
13970
|
}
|
|
12934
13971
|
log.blank();
|
|
12935
13972
|
log.info("Initialising contract-driven-delivery kit\u2026");
|
|
@@ -12943,9 +13980,9 @@ async function init(opts) {
|
|
|
12943
13980
|
const skillDirs = readdirSync2(ASSET.skills, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
|
|
12944
13981
|
let totalSkillFiles = 0;
|
|
12945
13982
|
for (const skillName of skillDirs) {
|
|
12946
|
-
const skillDest =
|
|
13983
|
+
const skillDest = join8(SKILLS_HOME, skillName);
|
|
12947
13984
|
log.info(`Installing skill \u2192 ${skillDest}`);
|
|
12948
|
-
const { count, created } = copyDirTracked(
|
|
13985
|
+
const { count, created } = copyDirTracked(join8(ASSET.skills, skillName), skillDest, { overwrite: true });
|
|
12949
13986
|
track(created);
|
|
12950
13987
|
totalSkillFiles += count;
|
|
12951
13988
|
}
|
|
@@ -12959,44 +13996,44 @@ async function init(opts) {
|
|
|
12959
13996
|
log.info(`Scaffolding project files in ${cwd}`);
|
|
12960
13997
|
const { count: contractsCount, created: contractsCreated } = copyDirTracked(
|
|
12961
13998
|
ASSET.contracts,
|
|
12962
|
-
|
|
13999
|
+
join8(cwd, "contracts"),
|
|
12963
14000
|
{ overwrite: opts.force, label: "contracts" }
|
|
12964
14001
|
);
|
|
12965
14002
|
track(contractsCreated);
|
|
12966
14003
|
log.ok(`contracts/ \u2014 ${contractsCount} file(s) written.`);
|
|
12967
14004
|
const { count: specsCount, created: specsCreated } = copyDirTracked(
|
|
12968
14005
|
ASSET.specsTemplates,
|
|
12969
|
-
|
|
14006
|
+
join8(cwd, "specs", "templates"),
|
|
12970
14007
|
{ overwrite: opts.force, label: "specs/templates" }
|
|
12971
14008
|
);
|
|
12972
14009
|
track(specsCreated);
|
|
12973
14010
|
log.ok(`specs/templates/ \u2014 ${specsCount} file(s) written.`);
|
|
12974
14011
|
const { count: testsCount, created: testsCreated } = copyDirTracked(
|
|
12975
14012
|
ASSET.testsTemplates,
|
|
12976
|
-
|
|
14013
|
+
join8(cwd, "tests", "templates"),
|
|
12977
14014
|
{ overwrite: opts.force, label: "tests/templates" }
|
|
12978
14015
|
);
|
|
12979
14016
|
track(testsCreated);
|
|
12980
14017
|
log.ok(`tests/templates/ \u2014 ${testsCount} file(s) written.`);
|
|
12981
14018
|
const { count: ciCount, created: ciCreated } = copyDirTracked(
|
|
12982
14019
|
ASSET.ci,
|
|
12983
|
-
|
|
14020
|
+
join8(cwd, "ci"),
|
|
12984
14021
|
{ overwrite: opts.force, label: "ci" }
|
|
12985
14022
|
);
|
|
12986
14023
|
track(ciCreated);
|
|
12987
14024
|
log.ok(`ci/ \u2014 ${ciCount} file(s) written.`);
|
|
12988
14025
|
const { count: cddConfigCount, created: cddConfigCreated } = copyDirTracked(
|
|
12989
14026
|
ASSET.cddConfig,
|
|
12990
|
-
|
|
14027
|
+
join8(cwd, ".cdd"),
|
|
12991
14028
|
{ overwrite: opts.force, label: ".cdd" }
|
|
12992
14029
|
);
|
|
12993
14030
|
track(cddConfigCreated);
|
|
12994
14031
|
log.ok(`.cdd/ - ${cddConfigCount} file(s) written.`);
|
|
12995
|
-
const modelPolicyPath =
|
|
12996
|
-
if (
|
|
14032
|
+
const modelPolicyPath = join8(cwd, ".cdd", "model-policy.json");
|
|
14033
|
+
if (existsSync7(modelPolicyPath)) {
|
|
12997
14034
|
let existing = {};
|
|
12998
14035
|
try {
|
|
12999
|
-
existing = JSON.parse(
|
|
14036
|
+
existing = JSON.parse(readFileSync6(modelPolicyPath, "utf8"));
|
|
13000
14037
|
} catch {
|
|
13001
14038
|
}
|
|
13002
14039
|
const merged = {
|
|
@@ -13005,11 +14042,11 @@ async function init(opts) {
|
|
|
13005
14042
|
generated_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
13006
14043
|
roles: existing.roles && typeof existing.roles === "object" && Object.keys(existing.roles).length > 0 ? existing.roles : {}
|
|
13007
14044
|
};
|
|
13008
|
-
|
|
14045
|
+
writeFileSync5(modelPolicyPath, JSON.stringify(merged, null, 2) + "\n", "utf8");
|
|
13009
14046
|
}
|
|
13010
14047
|
const { count: wfCount, created: wfCreated } = copyDirTracked(
|
|
13011
14048
|
ASSET.githubWorkflows,
|
|
13012
|
-
|
|
14049
|
+
join8(cwd, ".github", "workflows"),
|
|
13013
14050
|
{ overwrite: opts.force, label: ".github/workflows" }
|
|
13014
14051
|
);
|
|
13015
14052
|
track(wfCreated);
|
|
@@ -13033,11 +14070,11 @@ async function init(opts) {
|
|
|
13033
14070
|
} else {
|
|
13034
14071
|
log.warn("Could not detect stack \u2014 CI placeholder left in place.");
|
|
13035
14072
|
}
|
|
13036
|
-
const ciYmlDest =
|
|
13037
|
-
if (
|
|
14073
|
+
const ciYmlDest = join8(cwd, ".github", "workflows", "contract-driven-gates.yml");
|
|
14074
|
+
if (existsSync7(ciYmlDest)) {
|
|
13038
14075
|
const template = loadCiTemplate(detection.primary);
|
|
13039
14076
|
if (template) {
|
|
13040
|
-
const original =
|
|
14077
|
+
const original = readFileSync6(ciYmlDest, "utf8");
|
|
13041
14078
|
let patched = patchFastGateYml(original, template, detection.primary);
|
|
13042
14079
|
if (detection.primary === "conda" && patched.includes("{{conda-env-name}}")) {
|
|
13043
14080
|
const envName = readCondaEnvName(cwd);
|
|
@@ -13045,39 +14082,52 @@ async function init(opts) {
|
|
|
13045
14082
|
log.ok(`Conda environment name set to: ${envName}`);
|
|
13046
14083
|
}
|
|
13047
14084
|
if (patched !== original) {
|
|
13048
|
-
|
|
14085
|
+
writeFileSync5(ciYmlDest, patched, "utf8");
|
|
13049
14086
|
log.ok(`CI fast-gate patched for stack: ${detection.primary}`);
|
|
13050
14087
|
}
|
|
13051
14088
|
}
|
|
13052
14089
|
}
|
|
14090
|
+
const pkgJsonPath = join8(cwd, "package.json");
|
|
14091
|
+
const pkgJsonBefore = existsSync7(pkgJsonPath) ? readFileSync6(pkgJsonPath, "utf8") : null;
|
|
14092
|
+
const codegen = suggestCodegenScript(cwd);
|
|
14093
|
+
if (codegen.added.length > 0) {
|
|
14094
|
+
if (pkgJsonBefore !== null) {
|
|
14095
|
+
restoreActions.push(() => writeFileSync5(pkgJsonPath, pkgJsonBefore, "utf8"));
|
|
14096
|
+
}
|
|
14097
|
+
log.ok(`package.json: added ${codegen.added.join(" + ")} script(s) for contract\u2192client codegen`);
|
|
14098
|
+
if (codegen.note)
|
|
14099
|
+
log.info(codegen.note);
|
|
14100
|
+
} else if (codegen.skipped && existsSync7(pkgJsonPath)) {
|
|
14101
|
+
log.info(`contract\u2192client codegen: ${codegen.skipped}`);
|
|
14102
|
+
}
|
|
13053
14103
|
if (installClaude) {
|
|
13054
14104
|
const { written: claudeWritten, created: claudeCreated } = copyFileTracked(
|
|
13055
14105
|
ASSET.claudeTemplate,
|
|
13056
|
-
|
|
14106
|
+
join8(cwd, "CLAUDE.md"),
|
|
13057
14107
|
{ overwrite: false, label: "CLAUDE.md" }
|
|
13058
14108
|
);
|
|
13059
14109
|
if (claudeCreated)
|
|
13060
|
-
track([
|
|
14110
|
+
track([join8(cwd, "CLAUDE.md")]);
|
|
13061
14111
|
if (claudeWritten)
|
|
13062
14112
|
log.ok("CLAUDE.md created.");
|
|
13063
14113
|
const { written: agentsWritten, created: agentsCreated } = copyFileTracked(
|
|
13064
14114
|
ASSET.agentsTemplate,
|
|
13065
|
-
|
|
14115
|
+
join8(cwd, "AGENTS.md"),
|
|
13066
14116
|
{ overwrite: false, label: "AGENTS.md" }
|
|
13067
14117
|
);
|
|
13068
14118
|
if (agentsCreated)
|
|
13069
|
-
track([
|
|
14119
|
+
track([join8(cwd, "AGENTS.md")]);
|
|
13070
14120
|
if (agentsWritten)
|
|
13071
14121
|
log.ok("AGENTS.md created.");
|
|
13072
14122
|
}
|
|
13073
14123
|
if (installCodex) {
|
|
13074
14124
|
const { written: codexWritten, created: codexCreated } = copyFileTracked(
|
|
13075
14125
|
ASSET.codexTemplate,
|
|
13076
|
-
|
|
14126
|
+
join8(cwd, "CODEX.md"),
|
|
13077
14127
|
{ overwrite: false, label: "CODEX.md" }
|
|
13078
14128
|
);
|
|
13079
14129
|
if (codexCreated)
|
|
13080
|
-
track([
|
|
14130
|
+
track([join8(cwd, "CODEX.md")]);
|
|
13081
14131
|
if (codexWritten)
|
|
13082
14132
|
log.ok("CODEX.md created.");
|
|
13083
14133
|
}
|
|
@@ -13085,6 +14135,16 @@ async function init(opts) {
|
|
|
13085
14135
|
const { installCodeMapHook: installCodeMapHook2 } = await Promise.resolve().then(() => (init_code_map_hook(), code_map_hook_exports));
|
|
13086
14136
|
await installCodeMapHook2(cwd);
|
|
13087
14137
|
}
|
|
14138
|
+
if (!opts.globalOnly && opts.arm !== false) {
|
|
14139
|
+
log.info("Arming enforcement chokepoints\u2026");
|
|
14140
|
+
if (installClaude) {
|
|
14141
|
+
const { installAgentHooks: installAgentHooks2 } = await Promise.resolve().then(() => (init_install_agent_hooks(), install_agent_hooks_exports));
|
|
14142
|
+
await installAgentHooks2({ graphFirst: "advisory", fromInit: true });
|
|
14143
|
+
}
|
|
14144
|
+
const { installHooks: installHooks2 } = await Promise.resolve().then(() => (init_install_hooks(), install_hooks_exports));
|
|
14145
|
+
await installHooks2({ fromInit: true });
|
|
14146
|
+
log.info("Chokepoints armed. Run `cdd-kit doctor` to see live/dormant status; use `--no-arm` next time to skip.");
|
|
14147
|
+
}
|
|
13088
14148
|
log.blank();
|
|
13089
14149
|
}
|
|
13090
14150
|
} catch (err) {
|
|
@@ -13108,9 +14168,9 @@ init_update();
|
|
|
13108
14168
|
|
|
13109
14169
|
// src/commands/new-change.ts
|
|
13110
14170
|
init_paths();
|
|
13111
|
-
import { join as
|
|
14171
|
+
import { join as join12, relative as relative3 } from "path";
|
|
13112
14172
|
import { createHash as createHash4 } from "crypto";
|
|
13113
|
-
import { existsSync as
|
|
14173
|
+
import { existsSync as existsSync11, readFileSync as readFileSync11, readdirSync as readdirSync5, writeFileSync as writeFileSync7 } from "fs";
|
|
13114
14174
|
init_logger();
|
|
13115
14175
|
init_context_scan();
|
|
13116
14176
|
init_digest();
|
|
@@ -13122,10 +14182,10 @@ function inputsDigest2(paths, cwd) {
|
|
|
13122
14182
|
return createHash4("sha256").update(combined).digest("hex");
|
|
13123
14183
|
}
|
|
13124
14184
|
function findContractFiles2(dir, found = []) {
|
|
13125
|
-
if (!
|
|
14185
|
+
if (!existsSync11(dir))
|
|
13126
14186
|
return found;
|
|
13127
14187
|
for (const entry of readdirSync5(dir, { withFileTypes: true })) {
|
|
13128
|
-
const fullPath =
|
|
14188
|
+
const fullPath = join12(dir, entry.name);
|
|
13129
14189
|
if (entry.isDirectory())
|
|
13130
14190
|
findContractFiles2(fullPath, found);
|
|
13131
14191
|
else if (entry.isFile() && entry.name.endsWith(".md") && entry.name !== "INDEX.md" && entry.name !== "CHANGELOG.md") {
|
|
@@ -13135,22 +14195,22 @@ function findContractFiles2(dir, found = []) {
|
|
|
13135
14195
|
return found;
|
|
13136
14196
|
}
|
|
13137
14197
|
function readIndexDigest(filePath) {
|
|
13138
|
-
if (!
|
|
14198
|
+
if (!existsSync11(filePath))
|
|
13139
14199
|
return null;
|
|
13140
|
-
const m =
|
|
14200
|
+
const m = readFileSync11(filePath, "utf8").match(/^inputs-digest:\s*([a-f0-9]+)/m);
|
|
13141
14201
|
return m ? m[1] : null;
|
|
13142
14202
|
}
|
|
13143
14203
|
async function ensureFreshContextIndexes(cwd) {
|
|
13144
|
-
const projectMap =
|
|
13145
|
-
const contractsIndex =
|
|
13146
|
-
const policyPath =
|
|
13147
|
-
const policyInputs = [policyPath].filter(
|
|
13148
|
-
const contractFiles = findContractFiles2(
|
|
14204
|
+
const projectMap = join12(cwd, "specs", "context", "project-map.md");
|
|
14205
|
+
const contractsIndex = join12(cwd, "specs", "context", "contracts-index.md");
|
|
14206
|
+
const policyPath = join12(cwd, ".cdd", "context-policy.json");
|
|
14207
|
+
const policyInputs = [policyPath].filter(existsSync11);
|
|
14208
|
+
const contractFiles = findContractFiles2(join12(cwd, "contracts"));
|
|
13149
14209
|
const wantProjectDigest = inputsDigest2(policyInputs, cwd);
|
|
13150
14210
|
const wantContractsDigest = inputsDigest2(contractFiles, cwd);
|
|
13151
14211
|
const haveProjectDigest = readIndexDigest(projectMap);
|
|
13152
14212
|
const haveContractsDigest = readIndexDigest(contractsIndex);
|
|
13153
|
-
const needsScan = !
|
|
14213
|
+
const needsScan = !existsSync11(projectMap) || !existsSync11(contractsIndex) || haveProjectDigest !== wantProjectDigest || haveContractsDigest !== wantContractsDigest;
|
|
13154
14214
|
if (!needsScan)
|
|
13155
14215
|
return;
|
|
13156
14216
|
log.info("context indexes missing or stale \u2014 running cdd-kit context-scan\u2026");
|
|
@@ -13181,9 +14241,9 @@ function parseDependsOn(raw) {
|
|
|
13181
14241
|
return raw.split(",").map((item) => item.trim()).filter(Boolean);
|
|
13182
14242
|
}
|
|
13183
14243
|
function applyScaffoldMetadata(tasksPath, changeId, dependencies) {
|
|
13184
|
-
if (!
|
|
14244
|
+
if (!existsSync11(tasksPath))
|
|
13185
14245
|
return;
|
|
13186
|
-
let raw =
|
|
14246
|
+
let raw = readFileSync11(tasksPath, "utf8");
|
|
13187
14247
|
raw = raw.replace(/^change-id:\s*<change-id>\s*$/m, `change-id: ${changeId}`);
|
|
13188
14248
|
if (dependencies.length > 0) {
|
|
13189
14249
|
const dependsOn = [
|
|
@@ -13192,7 +14252,7 @@ function applyScaffoldMetadata(tasksPath, changeId, dependencies) {
|
|
|
13192
14252
|
].join("\n");
|
|
13193
14253
|
raw = raw.replace(/^depends-on:\s*\[\]\s*$/m, dependsOn);
|
|
13194
14254
|
}
|
|
13195
|
-
|
|
14255
|
+
writeFileSync7(tasksPath, raw, "utf8");
|
|
13196
14256
|
}
|
|
13197
14257
|
async function newChange(name, opts) {
|
|
13198
14258
|
if (!SAFE_NAME.test(name)) {
|
|
@@ -13207,8 +14267,8 @@ async function newChange(name, opts) {
|
|
|
13207
14267
|
}
|
|
13208
14268
|
}
|
|
13209
14269
|
const cwd = process.cwd();
|
|
13210
|
-
const changeDir =
|
|
13211
|
-
if (
|
|
14270
|
+
const changeDir = join12(cwd, "specs", "changes", name);
|
|
14271
|
+
if (existsSync11(changeDir)) {
|
|
13212
14272
|
if (opts.force) {
|
|
13213
14273
|
log.warn(`Forcing re-scaffold of existing change directory: ${changeDir}`);
|
|
13214
14274
|
log.warn("Existing files will NOT be deleted; only template files will be overwritten.");
|
|
@@ -13231,9 +14291,9 @@ async function newChange(name, opts) {
|
|
|
13231
14291
|
const templates = opts.all ? [...REQUIRED_TEMPLATES, ...listOptional()] : [...REQUIRED_TEMPLATES];
|
|
13232
14292
|
let written = 0;
|
|
13233
14293
|
for (const tmpl of templates) {
|
|
13234
|
-
const src =
|
|
13235
|
-
const dest =
|
|
13236
|
-
if (!
|
|
14294
|
+
const src = join12(ASSET.specsTemplates, tmpl);
|
|
14295
|
+
const dest = join12(changeDir, tmpl);
|
|
14296
|
+
if (!existsSync11(src)) {
|
|
13237
14297
|
log.warn(`Template not found, skipping: ${tmpl}`);
|
|
13238
14298
|
continue;
|
|
13239
14299
|
}
|
|
@@ -13241,7 +14301,7 @@ async function newChange(name, opts) {
|
|
|
13241
14301
|
log.dim(tmpl);
|
|
13242
14302
|
written += 1;
|
|
13243
14303
|
}
|
|
13244
|
-
const tasksPath =
|
|
14304
|
+
const tasksPath = join12(changeDir, "tasks.yml");
|
|
13245
14305
|
applyScaffoldMetadata(tasksPath, name, dependencies);
|
|
13246
14306
|
if (dependencies.length > 0) {
|
|
13247
14307
|
log.dim(`depends-on: ${dependencies.join(", ")}`);
|
|
@@ -13254,9 +14314,9 @@ async function newChange(name, opts) {
|
|
|
13254
14314
|
// src/commands/validate.ts
|
|
13255
14315
|
init_paths();
|
|
13256
14316
|
init_logger();
|
|
13257
|
-
import { join as
|
|
13258
|
-
import { existsSync as
|
|
13259
|
-
import { spawnSync } from "child_process";
|
|
14317
|
+
import { join as join13 } from "path";
|
|
14318
|
+
import { existsSync as existsSync12 } from "fs";
|
|
14319
|
+
import { spawnSync as spawnSync2 } from "child_process";
|
|
13260
14320
|
var VALIDATORS = [
|
|
13261
14321
|
{
|
|
13262
14322
|
flag: "contracts",
|
|
@@ -13264,6 +14324,7 @@ var VALIDATORS = [
|
|
|
13264
14324
|
label: "contracts",
|
|
13265
14325
|
chain: [
|
|
13266
14326
|
{ script: "validate_api_semantic.py", label: "API semantic" },
|
|
14327
|
+
{ script: "validate_api_conformance.py", label: "API conformance" },
|
|
13267
14328
|
{ script: "validate_env_semantic.py", label: "Env semantic" }
|
|
13268
14329
|
]
|
|
13269
14330
|
},
|
|
@@ -13274,7 +14335,7 @@ var VALIDATORS = [
|
|
|
13274
14335
|
];
|
|
13275
14336
|
function resolvePython() {
|
|
13276
14337
|
for (const cmd of ["python3", "python"]) {
|
|
13277
|
-
const r =
|
|
14338
|
+
const r = spawnSync2(cmd, ["--version"], { stdio: "ignore" });
|
|
13278
14339
|
if (r.status === 0)
|
|
13279
14340
|
return cmd;
|
|
13280
14341
|
}
|
|
@@ -13288,21 +14349,22 @@ async function validate(opts) {
|
|
|
13288
14349
|
log.error(e instanceof Error ? e.message : String(e));
|
|
13289
14350
|
process.exit(1);
|
|
13290
14351
|
}
|
|
13291
|
-
const scriptsDir =
|
|
14352
|
+
const scriptsDir = join13(ASSET.skill, "scripts");
|
|
13292
14353
|
const runAll = !opts.contracts && !opts.env && !opts.ci && !opts.spec && !opts.versions;
|
|
14354
|
+
const pyEnv = { ...process.env, PYTHONUTF8: "1", PYTHONIOENCODING: "utf-8" };
|
|
13293
14355
|
log.blank();
|
|
13294
14356
|
let failed = false;
|
|
13295
14357
|
for (const v of VALIDATORS) {
|
|
13296
14358
|
if (!runAll && !opts[v.flag])
|
|
13297
14359
|
continue;
|
|
13298
|
-
const scriptPath =
|
|
13299
|
-
if (!
|
|
14360
|
+
const scriptPath = join13(scriptsDir, v.script);
|
|
14361
|
+
if (!existsSync12(scriptPath)) {
|
|
13300
14362
|
log.warn(`${v.label}: script not found, skipping (${v.script})`);
|
|
13301
14363
|
log.blank();
|
|
13302
14364
|
continue;
|
|
13303
14365
|
}
|
|
13304
14366
|
log.info(`Validating ${v.label}\u2026`);
|
|
13305
|
-
const r =
|
|
14367
|
+
const r = spawnSync2(py, [scriptPath, ...v.args ?? []], { stdio: "inherit", cwd: process.cwd(), env: pyEnv });
|
|
13306
14368
|
if (r.status !== 0) {
|
|
13307
14369
|
log.error(`${v.label} validation failed.`);
|
|
13308
14370
|
failed = true;
|
|
@@ -13312,14 +14374,14 @@ async function validate(opts) {
|
|
|
13312
14374
|
log.blank();
|
|
13313
14375
|
if (v.chain) {
|
|
13314
14376
|
for (const chained of v.chain) {
|
|
13315
|
-
const chainedPath =
|
|
13316
|
-
if (!
|
|
14377
|
+
const chainedPath = join13(scriptsDir, chained.script);
|
|
14378
|
+
if (!existsSync12(chainedPath)) {
|
|
13317
14379
|
log.warn(`${chained.label}: script not found, skipping (${chained.script})`);
|
|
13318
14380
|
log.blank();
|
|
13319
14381
|
continue;
|
|
13320
14382
|
}
|
|
13321
14383
|
log.info(`Validating ${chained.label}\u2026`);
|
|
13322
|
-
const cr =
|
|
14384
|
+
const cr = spawnSync2(py, [chainedPath], { stdio: "inherit", cwd: process.cwd(), env: pyEnv });
|
|
13323
14385
|
if (cr.status !== 0) {
|
|
13324
14386
|
log.error(`${chained.label} validation failed.`);
|
|
13325
14387
|
failed = true;
|
|
@@ -13343,8 +14405,8 @@ async function validate(opts) {
|
|
|
13343
14405
|
var import_ajv = __toESM(require_ajv(), 1);
|
|
13344
14406
|
var import_ajv_formats = __toESM(require_dist(), 1);
|
|
13345
14407
|
init_logger();
|
|
13346
|
-
import { existsSync as
|
|
13347
|
-
import { join as
|
|
14408
|
+
import { existsSync as existsSync14, readFileSync as readFileSync13, readdirSync as readdirSync6 } from "fs";
|
|
14409
|
+
import { join as join15 } from "path";
|
|
13348
14410
|
import yaml from "js-yaml";
|
|
13349
14411
|
|
|
13350
14412
|
// src/schemas/tasks.schema.ts
|
|
@@ -13373,6 +14435,7 @@ var tasksSchema = {
|
|
|
13373
14435
|
items: { type: "string", pattern: "^[a-zA-Z0-9][a-zA-Z0-9_-]{0,63}$" },
|
|
13374
14436
|
default: []
|
|
13375
14437
|
},
|
|
14438
|
+
"tier-floor-override": { type: "string", minLength: 1 },
|
|
13376
14439
|
"token-budget": { type: ["string", "number"] },
|
|
13377
14440
|
created: { type: "string" },
|
|
13378
14441
|
completed: { type: "string" },
|
|
@@ -13395,6 +14458,8 @@ var tasksSchema = {
|
|
|
13395
14458
|
};
|
|
13396
14459
|
|
|
13397
14460
|
// src/commands/gate.ts
|
|
14461
|
+
init_tier_floor();
|
|
14462
|
+
init_git_paths();
|
|
13398
14463
|
var ajv = new import_ajv.default({ allErrors: true, allowUnionTypes: true });
|
|
13399
14464
|
(0, import_ajv_formats.default)(ajv);
|
|
13400
14465
|
var validateTasks = ajv.compile(tasksSchema);
|
|
@@ -13432,6 +14497,15 @@ function meaningfulChars(text) {
|
|
|
13432
14497
|
function stripHtmlComments(text) {
|
|
13433
14498
|
return text.replace(/<!--[\s\S]*?-->/g, "");
|
|
13434
14499
|
}
|
|
14500
|
+
var PLACEHOLDER_LITERALS = ["<id>", "<date>", "<change-id>"];
|
|
14501
|
+
var RE_META = /[.*+?^${}()|[\]\\]/g;
|
|
14502
|
+
function findPlaceholders(text) {
|
|
14503
|
+
const clean = stripHtmlComments(text);
|
|
14504
|
+
return PLACEHOLDER_LITERALS.filter((token) => {
|
|
14505
|
+
const re = new RegExp(`:[ \\t]*${token.replace(RE_META, "\\$&")}[ \\t]*\\r?$`, "m");
|
|
14506
|
+
return re.test(clean);
|
|
14507
|
+
}).sort();
|
|
14508
|
+
}
|
|
13435
14509
|
function countPendingExpansions(sectionBody2) {
|
|
13436
14510
|
if (!sectionBody2.trim())
|
|
13437
14511
|
return 0;
|
|
@@ -13453,7 +14527,7 @@ function countPendingContextRequests(content) {
|
|
|
13453
14527
|
}
|
|
13454
14528
|
function loadYamlFile(path) {
|
|
13455
14529
|
try {
|
|
13456
|
-
const raw =
|
|
14530
|
+
const raw = readFileSync13(path, "utf8");
|
|
13457
14531
|
return { data: yaml.load(raw, { schema: yaml.JSON_SCHEMA }), parseError: null };
|
|
13458
14532
|
} catch (err) {
|
|
13459
14533
|
return { data: null, parseError: err.message };
|
|
@@ -13484,8 +14558,8 @@ function ajvErrorsToMessages(errors, prefix, knownKeys) {
|
|
|
13484
14558
|
return out;
|
|
13485
14559
|
}
|
|
13486
14560
|
function isContextGovernedChange(changeDir) {
|
|
13487
|
-
const tasksPath =
|
|
13488
|
-
if (!
|
|
14561
|
+
const tasksPath = join15(changeDir, "tasks.yml");
|
|
14562
|
+
if (!existsSync14(tasksPath))
|
|
13489
14563
|
return false;
|
|
13490
14564
|
const { data } = loadYamlFile(tasksPath);
|
|
13491
14565
|
return data?.["context-governance"] === "v1";
|
|
@@ -13513,12 +14587,12 @@ function lintTasksFile(tasksPath, errors, warnings) {
|
|
|
13513
14587
|
return data;
|
|
13514
14588
|
}
|
|
13515
14589
|
function resolveTier(changeDir) {
|
|
13516
|
-
const classifPath =
|
|
13517
|
-
const classificationPresent =
|
|
13518
|
-
const classificationText = classificationPresent ?
|
|
14590
|
+
const classifPath = join15(changeDir, "change-classification.md");
|
|
14591
|
+
const classificationPresent = existsSync14(classifPath);
|
|
14592
|
+
const classificationText = classificationPresent ? readFileSync13(classifPath, "utf8") : "";
|
|
13519
14593
|
const classificationHasLooseMarker = classificationPresent && TIER_PATTERN.test(classificationText);
|
|
13520
|
-
const tasksPath =
|
|
13521
|
-
if (
|
|
14594
|
+
const tasksPath = join15(changeDir, "tasks.yml");
|
|
14595
|
+
if (existsSync14(tasksPath)) {
|
|
13522
14596
|
const { data } = loadYamlFile(tasksPath);
|
|
13523
14597
|
const t = data?.tier;
|
|
13524
14598
|
if (typeof t === "number" && t >= 0 && t <= 5) {
|
|
@@ -13564,7 +14638,7 @@ function enforceTierConsistency(changeDir, errors, warnings) {
|
|
|
13564
14638
|
return;
|
|
13565
14639
|
}
|
|
13566
14640
|
if (resolution.source === "tasks-frontmatter" && resolution.classificationPresent) {
|
|
13567
|
-
const text =
|
|
14641
|
+
const text = readFileSync13(join15(changeDir, "change-classification.md"), "utf8");
|
|
13568
14642
|
const structured = text.match(/^## Tier\s*\n\s*-\s*(\d)\s*$/m);
|
|
13569
14643
|
const bold = text.match(/\*\*Tier:\*\*\s*Tier\s*(\d)\b/i);
|
|
13570
14644
|
const classifTier = structured ? parseInt(structured[1], 10) : bold ? parseInt(bold[1], 10) : NaN;
|
|
@@ -13575,12 +14649,50 @@ function enforceTierConsistency(changeDir, errors, warnings) {
|
|
|
13575
14649
|
}
|
|
13576
14650
|
}
|
|
13577
14651
|
}
|
|
14652
|
+
function enforceTierFloor(changeDir, errors, warnings) {
|
|
14653
|
+
const cwd = process.cwd();
|
|
14654
|
+
const policy = loadTierPolicy(cwd);
|
|
14655
|
+
if (!policy.enabled)
|
|
14656
|
+
return;
|
|
14657
|
+
const requestPath = join15(changeDir, "change-request.md");
|
|
14658
|
+
const requestText = existsSync14(requestPath) ? readFileSync13(requestPath, "utf8") : "";
|
|
14659
|
+
const staged = getStagedPaths(cwd);
|
|
14660
|
+
const stagedChangeIds = new Set(
|
|
14661
|
+
staged.map((p) => /^specs\/changes\/([^/]+)\//.exec(p)?.[1]).filter((id) => id !== void 0)
|
|
14662
|
+
);
|
|
14663
|
+
const touched = stagedChangeIds.size > 1 ? [] : staged;
|
|
14664
|
+
const floor = computeTierFloor(requestText, policy, { paths: touched });
|
|
14665
|
+
if (floor.floorTier === null)
|
|
14666
|
+
return;
|
|
14667
|
+
const declared = resolveTier(changeDir).tier;
|
|
14668
|
+
if (declared === null) {
|
|
14669
|
+
warnings.push(
|
|
14670
|
+
`tier floor: ${floor.label} detected (matched: ${floor.matched.join(", ")}); classification must declare tier ${floor.floorTier} or stricter.`
|
|
14671
|
+
);
|
|
14672
|
+
return;
|
|
14673
|
+
}
|
|
14674
|
+
if (declared <= floor.floorTier)
|
|
14675
|
+
return;
|
|
14676
|
+
const overrideRaw = (() => {
|
|
14677
|
+
const { data } = loadYamlFile(join15(changeDir, "tasks.yml"));
|
|
14678
|
+
const o = data?.["tier-floor-override"];
|
|
14679
|
+
return typeof o === "string" ? o.trim() : "";
|
|
14680
|
+
})();
|
|
14681
|
+
const detail = `${floor.label} detected (matched: ${floor.matched.join(", ")}) requires tier ${floor.floorTier} or stricter, but classification declared tier ${declared}.`;
|
|
14682
|
+
if (overrideRaw) {
|
|
14683
|
+
warnings.push(`tier floor override: ${detail} Bypassed by tier-floor-override: "${overrideRaw}".`);
|
|
14684
|
+
} else {
|
|
14685
|
+
errors.push(
|
|
14686
|
+
`tier floor violation: ${detail} Re-classify to tier ${floor.floorTier} (or stricter), or record \`tier-floor-override: "<reason>"\` in tasks.yml frontmatter to bypass with an audit trail. Disable entirely in .cdd/tier-policy.json.`
|
|
14687
|
+
);
|
|
14688
|
+
}
|
|
14689
|
+
}
|
|
13578
14690
|
function isArchivedChange(cwd, changeId) {
|
|
13579
|
-
const archiveRoot =
|
|
13580
|
-
if (!
|
|
14691
|
+
const archiveRoot = join15(cwd, "specs", "archive");
|
|
14692
|
+
if (!existsSync14(archiveRoot))
|
|
13581
14693
|
return false;
|
|
13582
14694
|
const years = readdirSync6(archiveRoot, { withFileTypes: true }).filter((d) => d.isDirectory());
|
|
13583
|
-
return years.some((year) =>
|
|
14695
|
+
return years.some((year) => existsSync14(join15(archiveRoot, year.name, changeId)));
|
|
13584
14696
|
}
|
|
13585
14697
|
function detectDependencyCycle(cwd, startChangeId) {
|
|
13586
14698
|
const visited = /* @__PURE__ */ new Set();
|
|
@@ -13593,8 +14705,8 @@ function detectDependencyCycle(cwd, startChangeId) {
|
|
|
13593
14705
|
return null;
|
|
13594
14706
|
visited.add(id);
|
|
13595
14707
|
stack.push(id);
|
|
13596
|
-
const tasksPath =
|
|
13597
|
-
if (
|
|
14708
|
+
const tasksPath = join15(cwd, "specs", "changes", id, "tasks.yml");
|
|
14709
|
+
if (existsSync14(tasksPath)) {
|
|
13598
14710
|
const { data } = loadYamlFile(tasksPath);
|
|
13599
14711
|
const deps = data?.["depends-on"] ?? [];
|
|
13600
14712
|
for (const dep of deps) {
|
|
@@ -13609,8 +14721,8 @@ function detectDependencyCycle(cwd, startChangeId) {
|
|
|
13609
14721
|
return visit(startChangeId);
|
|
13610
14722
|
}
|
|
13611
14723
|
function validateDependencies(cwd, changeId, changeDir) {
|
|
13612
|
-
const tasksPath =
|
|
13613
|
-
if (!
|
|
14724
|
+
const tasksPath = join15(changeDir, "tasks.yml");
|
|
14725
|
+
if (!existsSync14(tasksPath))
|
|
13614
14726
|
return [];
|
|
13615
14727
|
const { data } = loadYamlFile(tasksPath);
|
|
13616
14728
|
const dependencies = data?.["depends-on"] ?? [];
|
|
@@ -13624,10 +14736,10 @@ function validateDependencies(cwd, changeId, changeDir) {
|
|
|
13624
14736
|
errors.push(`tasks.yml: change cannot depend on itself (${dep})`);
|
|
13625
14737
|
continue;
|
|
13626
14738
|
}
|
|
13627
|
-
const upstreamDir =
|
|
13628
|
-
if (
|
|
13629
|
-
const upstreamTasks =
|
|
13630
|
-
if (!
|
|
14739
|
+
const upstreamDir = join15(cwd, "specs", "changes", dep);
|
|
14740
|
+
if (existsSync14(upstreamDir)) {
|
|
14741
|
+
const upstreamTasks = join15(upstreamDir, "tasks.yml");
|
|
14742
|
+
if (!existsSync14(upstreamTasks)) {
|
|
13631
14743
|
errors.push(`dependency ${dep}: missing tasks.yml`);
|
|
13632
14744
|
continue;
|
|
13633
14745
|
}
|
|
@@ -13647,19 +14759,19 @@ function validateDependencies(cwd, changeId, changeDir) {
|
|
|
13647
14759
|
async function gate(changeId, opts = {}) {
|
|
13648
14760
|
const strict = opts.strict ?? false;
|
|
13649
14761
|
const cwd = process.cwd();
|
|
13650
|
-
const changeDir =
|
|
13651
|
-
if (!
|
|
14762
|
+
const changeDir = join15(cwd, "specs", "changes", changeId);
|
|
14763
|
+
if (!existsSync14(changeDir)) {
|
|
13652
14764
|
log.error(`change not found: ${changeId} (looked in ${changeDir})`);
|
|
13653
14765
|
process.exit(1);
|
|
13654
14766
|
}
|
|
13655
14767
|
const errors = [];
|
|
13656
14768
|
const warnings = [];
|
|
13657
14769
|
const isNewChange = isContextGovernedChange(changeDir);
|
|
13658
|
-
const manifestPath =
|
|
13659
|
-
const hasManifest =
|
|
14770
|
+
const manifestPath = join15(changeDir, "context-manifest.md");
|
|
14771
|
+
const hasManifest = existsSync14(manifestPath);
|
|
13660
14772
|
errors.push(...validateDependencies(cwd, changeId, changeDir));
|
|
13661
14773
|
if (hasManifest) {
|
|
13662
|
-
const pending = countPendingContextRequests(
|
|
14774
|
+
const pending = countPendingContextRequests(readFileSync13(manifestPath, "utf8"));
|
|
13663
14775
|
if (pending > 0) {
|
|
13664
14776
|
warnings.push(`context-manifest.md: has ${pending} pending context expansion request(s)`);
|
|
13665
14777
|
}
|
|
@@ -13675,7 +14787,7 @@ async function gate(changeId, opts = {}) {
|
|
|
13675
14787
|
}
|
|
13676
14788
|
continue;
|
|
13677
14789
|
}
|
|
13678
|
-
if (!
|
|
14790
|
+
if (!existsSync14(join15(changeDir, f))) {
|
|
13679
14791
|
errors.push(`missing required artifact: ${f}`);
|
|
13680
14792
|
}
|
|
13681
14793
|
}
|
|
@@ -13685,21 +14797,30 @@ async function gate(changeId, opts = {}) {
|
|
|
13685
14797
|
continue;
|
|
13686
14798
|
if (f === "tasks.yml")
|
|
13687
14799
|
continue;
|
|
13688
|
-
const content =
|
|
14800
|
+
const content = readFileSync13(join15(changeDir, f), "utf8");
|
|
13689
14801
|
const minChars = MIN_CHARS[f] ?? 100;
|
|
13690
14802
|
if (meaningfulChars(content) < minChars) {
|
|
13691
14803
|
errors.push(`${f}: appears to be a stub (< ${minChars} meaningful chars)`);
|
|
14804
|
+
continue;
|
|
14805
|
+
}
|
|
14806
|
+
if (f !== "context-manifest.md") {
|
|
14807
|
+
const placeholders = findPlaceholders(content);
|
|
14808
|
+
if (placeholders.length > 0) {
|
|
14809
|
+
errors.push(
|
|
14810
|
+
`${f}: still contains unfilled template placeholder(s) ${placeholders.join(", ")} \u2014 replace them with the change's real values before the gate can pass`
|
|
14811
|
+
);
|
|
14812
|
+
}
|
|
13692
14813
|
}
|
|
13693
14814
|
}
|
|
13694
|
-
const classifPath =
|
|
14815
|
+
const classifPath = join15(changeDir, "change-classification.md");
|
|
13695
14816
|
const tierResolution = resolveTier(changeDir);
|
|
13696
|
-
if (tierResolution.tier === null &&
|
|
14817
|
+
if (tierResolution.tier === null && existsSync14(classifPath) && !tierResolution.classificationHasLooseMarker) {
|
|
13697
14818
|
errors.push("change-classification.md: missing tier/risk marker (set tier in tasks.yml frontmatter, or include Tier 0-5 / low|medium|high|critical in change-classification.md)");
|
|
13698
14819
|
}
|
|
13699
14820
|
}
|
|
13700
|
-
const tasksPath =
|
|
14821
|
+
const tasksPath = join15(changeDir, "tasks.yml");
|
|
13701
14822
|
let tasksData = null;
|
|
13702
|
-
if (
|
|
14823
|
+
if (existsSync14(tasksPath)) {
|
|
13703
14824
|
tasksData = lintTasksFile(tasksPath, errors, warnings);
|
|
13704
14825
|
}
|
|
13705
14826
|
if (tasksData) {
|
|
@@ -13714,6 +14835,7 @@ async function gate(changeId, opts = {}) {
|
|
|
13714
14835
|
}
|
|
13715
14836
|
}
|
|
13716
14837
|
enforceTierConsistency(changeDir, errors, warnings);
|
|
14838
|
+
enforceTierFloor(changeDir, errors, warnings);
|
|
13717
14839
|
for (const w of warnings) {
|
|
13718
14840
|
log.warn(` ${w}`);
|
|
13719
14841
|
}
|
|
@@ -13737,75 +14859,540 @@ async function gate(changeId, opts = {}) {
|
|
|
13737
14859
|
log.ok(`gate passed for change: ${changeId}`);
|
|
13738
14860
|
}
|
|
13739
14861
|
|
|
13740
|
-
// src/
|
|
13741
|
-
|
|
14862
|
+
// src/cli/index.ts
|
|
14863
|
+
init_install_hooks();
|
|
14864
|
+
init_install_agent_hooks();
|
|
14865
|
+
|
|
14866
|
+
// src/commands/openapi-export.ts
|
|
13742
14867
|
init_logger();
|
|
13743
|
-
import { existsSync as
|
|
13744
|
-
import {
|
|
13745
|
-
var
|
|
13746
|
-
var
|
|
13747
|
-
|
|
13748
|
-
const
|
|
13749
|
-
|
|
13750
|
-
|
|
13751
|
-
|
|
13752
|
-
|
|
14868
|
+
import { existsSync as existsSync15, readFileSync as readFileSync14, writeFileSync as writeFileSync8, mkdirSync as mkdirSync7 } from "fs";
|
|
14869
|
+
import { dirname as dirname4, resolve as resolve2 } from "path";
|
|
14870
|
+
var DEFAULT_CONTRACT = "contracts/api/api-contract.md";
|
|
14871
|
+
var VALID_METHODS = /* @__PURE__ */ new Set(["get", "post", "put", "delete", "patch", "head", "options"]);
|
|
14872
|
+
function stripFrontmatter(text) {
|
|
14873
|
+
const fm = {};
|
|
14874
|
+
if (text.startsWith("---")) {
|
|
14875
|
+
const end = text.indexOf("\n---", 3);
|
|
14876
|
+
if (end !== -1) {
|
|
14877
|
+
const block = text.slice(3, end);
|
|
14878
|
+
for (const line of block.split("\n")) {
|
|
14879
|
+
const m = line.match(/^([A-Za-z0-9_-]+):\s*(.*)$/);
|
|
14880
|
+
if (m)
|
|
14881
|
+
fm[m[1].trim()] = m[2].trim();
|
|
14882
|
+
}
|
|
14883
|
+
return { body: text.slice(end + 4).replace(/^\n+/, ""), frontmatter: fm };
|
|
14884
|
+
}
|
|
13753
14885
|
}
|
|
13754
|
-
|
|
13755
|
-
|
|
13756
|
-
|
|
13757
|
-
|
|
13758
|
-
|
|
13759
|
-
|
|
13760
|
-
|
|
13761
|
-
|
|
13762
|
-
|
|
13763
|
-
|
|
13764
|
-
|
|
13765
|
-
|
|
13766
|
-
|
|
13767
|
-
|
|
13768
|
-
|
|
13769
|
-
|
|
13770
|
-
|
|
13771
|
-
|
|
13772
|
-
|
|
13773
|
-
|
|
13774
|
-
|
|
13775
|
-
|
|
13776
|
-
|
|
13777
|
-
|
|
13778
|
-
|
|
13779
|
-
|
|
13780
|
-
|
|
14886
|
+
return { body: text, frontmatter: fm };
|
|
14887
|
+
}
|
|
14888
|
+
function parseRow(line) {
|
|
14889
|
+
return line.trim().replace(/^\|/, "").replace(/\|$/, "").split("|").map((c) => c.trim());
|
|
14890
|
+
}
|
|
14891
|
+
function isSeparator(cells) {
|
|
14892
|
+
return cells.every((c) => c === "" || /^:?-+:?$/.test(c));
|
|
14893
|
+
}
|
|
14894
|
+
function parseEndpoints(body) {
|
|
14895
|
+
const rows = [];
|
|
14896
|
+
let inTable = false;
|
|
14897
|
+
let sepSeen = false;
|
|
14898
|
+
for (const raw of body.split("\n")) {
|
|
14899
|
+
const line = raw.trim();
|
|
14900
|
+
if (!line || !line.startsWith("|"))
|
|
14901
|
+
continue;
|
|
14902
|
+
const cells = parseRow(line);
|
|
14903
|
+
if (cells[0]?.toLowerCase() === "method") {
|
|
14904
|
+
inTable = true;
|
|
14905
|
+
sepSeen = false;
|
|
14906
|
+
continue;
|
|
14907
|
+
}
|
|
14908
|
+
if (!inTable)
|
|
14909
|
+
continue;
|
|
14910
|
+
if (!sepSeen && isSeparator(cells)) {
|
|
14911
|
+
sepSeen = true;
|
|
14912
|
+
continue;
|
|
14913
|
+
}
|
|
14914
|
+
if (cells.length < 2 || !cells.some(Boolean))
|
|
14915
|
+
continue;
|
|
14916
|
+
const method = (cells[0] ?? "").toLowerCase();
|
|
14917
|
+
const path = cells[1] ?? "";
|
|
14918
|
+
if (!VALID_METHODS.has(method) || !path.startsWith("/"))
|
|
14919
|
+
continue;
|
|
14920
|
+
rows.push({
|
|
14921
|
+
method,
|
|
14922
|
+
path,
|
|
14923
|
+
auth: (cells[2] ?? "").toLowerCase(),
|
|
14924
|
+
request: cells[3] ?? "",
|
|
14925
|
+
response: cells[4] ?? "",
|
|
14926
|
+
errors: cells[5] ?? ""
|
|
14927
|
+
});
|
|
14928
|
+
}
|
|
14929
|
+
return rows;
|
|
14930
|
+
}
|
|
14931
|
+
var SCHEMA_NAME_RE = /^[A-Za-z][A-Za-z0-9_]*$/;
|
|
14932
|
+
var PRIMITIVE_TYPES = /* @__PURE__ */ new Set(["string", "integer", "number", "boolean"]);
|
|
14933
|
+
function extractSchemasSection(body) {
|
|
14934
|
+
const lines = body.split("\n");
|
|
14935
|
+
const start = lines.findIndex((line) => /^##\s+Schemas\s*$/i.test(line.trim()));
|
|
14936
|
+
if (start === -1)
|
|
14937
|
+
return "";
|
|
14938
|
+
const out = [];
|
|
14939
|
+
for (let i = start + 1; i < lines.length; i += 1) {
|
|
14940
|
+
if (/^##\s+/.test(lines[i].trim()))
|
|
14941
|
+
break;
|
|
14942
|
+
out.push(lines[i]);
|
|
14943
|
+
}
|
|
14944
|
+
return out.join("\n");
|
|
14945
|
+
}
|
|
14946
|
+
function parseSchemaSections(body) {
|
|
14947
|
+
const schemasBlock = extractSchemasSection(body).replace(/<!--[\s\S]*?-->/g, "");
|
|
14948
|
+
if (!schemasBlock.trim())
|
|
14949
|
+
return { sections: [], errors: [] };
|
|
14950
|
+
const sections = [];
|
|
14951
|
+
const errors = [];
|
|
14952
|
+
const seen = /* @__PURE__ */ new Set();
|
|
14953
|
+
const headingRe = /^###\s+(.+?)\s*$/gm;
|
|
14954
|
+
const headings = Array.from(schemasBlock.matchAll(headingRe));
|
|
14955
|
+
for (let i = 0; i < headings.length; i += 1) {
|
|
14956
|
+
const heading = headings[i];
|
|
14957
|
+
const name = (heading[1] ?? "").trim();
|
|
14958
|
+
const contentStart = (heading.index ?? 0) + heading[0].length;
|
|
14959
|
+
const contentEnd = i + 1 < headings.length ? headings[i + 1].index ?? schemasBlock.length : schemasBlock.length;
|
|
14960
|
+
if (!SCHEMA_NAME_RE.test(name))
|
|
14961
|
+
continue;
|
|
14962
|
+
if (seen.has(name)) {
|
|
14963
|
+
errors.push(`Duplicate schema section: ${name}`);
|
|
14964
|
+
continue;
|
|
14965
|
+
}
|
|
14966
|
+
seen.add(name);
|
|
14967
|
+
sections.push({ name, content: schemasBlock.slice(contentStart, contentEnd) });
|
|
14968
|
+
}
|
|
14969
|
+
return { sections, errors };
|
|
14970
|
+
}
|
|
14971
|
+
function parseJsonSchemaBlocks(section) {
|
|
14972
|
+
const blocks = [];
|
|
14973
|
+
const errors = [];
|
|
14974
|
+
const blockRe = /```json-schema\s*\n([\s\S]*?)```/g;
|
|
14975
|
+
for (const match of section.content.matchAll(blockRe)) {
|
|
14976
|
+
try {
|
|
14977
|
+
const parsed = JSON.parse(match[1] ?? "");
|
|
14978
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
14979
|
+
errors.push(`Schema ${section.name}: json-schema block must be a JSON object`);
|
|
13781
14980
|
} else {
|
|
13782
|
-
|
|
14981
|
+
blocks.push(parsed);
|
|
13783
14982
|
}
|
|
14983
|
+
} catch (err) {
|
|
14984
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
14985
|
+
errors.push(`Schema ${section.name}: invalid json-schema block (${detail})`);
|
|
13784
14986
|
}
|
|
13785
14987
|
}
|
|
13786
|
-
|
|
13787
|
-
|
|
13788
|
-
|
|
13789
|
-
|
|
14988
|
+
return { blocks, errors };
|
|
14989
|
+
}
|
|
14990
|
+
function findForeignFenceTag(section) {
|
|
14991
|
+
const fenceRe = /```([^\n]*)(?:\n|$)[\s\S]*?```/g;
|
|
14992
|
+
for (const match of section.content.matchAll(fenceRe)) {
|
|
14993
|
+
const info = (match[1] ?? "").trim();
|
|
14994
|
+
if (info !== "json-schema")
|
|
14995
|
+
return info;
|
|
13790
14996
|
}
|
|
13791
|
-
|
|
13792
|
-
|
|
14997
|
+
return null;
|
|
14998
|
+
}
|
|
14999
|
+
function parseFieldTable(section) {
|
|
15000
|
+
const lines = section.content.split("\n");
|
|
15001
|
+
for (let i = 0; i < lines.length; i += 1) {
|
|
15002
|
+
const line = lines[i].trim();
|
|
15003
|
+
if (!line.startsWith("|"))
|
|
15004
|
+
continue;
|
|
15005
|
+
const header = parseRow(line).map((c) => c.toLowerCase());
|
|
15006
|
+
const fieldIdx = header.indexOf("field");
|
|
15007
|
+
const typeIdx = header.indexOf("type");
|
|
15008
|
+
const requiredIdx = header.indexOf("required");
|
|
15009
|
+
if (fieldIdx === -1 || typeIdx === -1 || requiredIdx === -1)
|
|
15010
|
+
continue;
|
|
15011
|
+
const separator = lines[i + 1]?.trim();
|
|
15012
|
+
if (!separator?.startsWith("|") || !isSeparator(parseRow(separator)))
|
|
15013
|
+
continue;
|
|
15014
|
+
const notesIdx = header.indexOf("notes");
|
|
15015
|
+
const formatIdx = header.indexOf("format");
|
|
15016
|
+
const rows = [];
|
|
15017
|
+
for (let j = i + 2; j < lines.length; j += 1) {
|
|
15018
|
+
const rowLine = lines[j].trim();
|
|
15019
|
+
if (!rowLine || !rowLine.startsWith("|"))
|
|
15020
|
+
break;
|
|
15021
|
+
const cells = parseRow(rowLine);
|
|
15022
|
+
if (isSeparator(cells) || cells.length < 2 || !cells.some(Boolean))
|
|
15023
|
+
continue;
|
|
15024
|
+
rows.push({
|
|
15025
|
+
field: cells[fieldIdx] ?? "",
|
|
15026
|
+
type: cells[typeIdx] ?? "",
|
|
15027
|
+
required: cells[requiredIdx] ?? "",
|
|
15028
|
+
notes: notesIdx === -1 ? "" : cells[notesIdx] ?? "",
|
|
15029
|
+
format: formatIdx === -1 ? "" : cells[formatIdx] ?? ""
|
|
15030
|
+
});
|
|
15031
|
+
}
|
|
15032
|
+
return { rows, found: true };
|
|
15033
|
+
}
|
|
15034
|
+
return { rows: [], found: false };
|
|
15035
|
+
}
|
|
15036
|
+
function compileType(typeValue, schemaNames, resolvableNames, context2) {
|
|
15037
|
+
const type = typeValue.trim();
|
|
15038
|
+
if (!type)
|
|
15039
|
+
throw new Error(`${context2}: empty type`);
|
|
15040
|
+
if (type.endsWith("[]")) {
|
|
15041
|
+
const inner = type.slice(0, -2).trim();
|
|
15042
|
+
if (!inner || inner.endsWith("[]"))
|
|
15043
|
+
throw new Error(`${context2}: unsupported array type "${type}"`);
|
|
15044
|
+
return { type: "array", items: compileType(inner, schemaNames, resolvableNames, context2) };
|
|
15045
|
+
}
|
|
15046
|
+
if (PRIMITIVE_TYPES.has(type))
|
|
15047
|
+
return { type };
|
|
15048
|
+
const enumMatch = type.match(/^enum\((.*)\)$/);
|
|
15049
|
+
if (enumMatch) {
|
|
15050
|
+
const values = (enumMatch[1] ?? "").split(",").map((v) => v.trim()).filter(Boolean);
|
|
15051
|
+
if (values.length === 0)
|
|
15052
|
+
throw new Error(`${context2}: enum must list at least one value`);
|
|
15053
|
+
return { type: "string", enum: values };
|
|
15054
|
+
}
|
|
15055
|
+
if (SCHEMA_NAME_RE.test(type) && schemaNames.has(type)) {
|
|
15056
|
+
if (!resolvableNames.has(type)) {
|
|
15057
|
+
throw new Error(`${context2}: referenced schema "${type}" has no field table or json-schema block`);
|
|
15058
|
+
}
|
|
15059
|
+
return { $ref: `#/components/schemas/${type}` };
|
|
15060
|
+
}
|
|
15061
|
+
throw new Error(`${context2}: unknown type "${type}"`);
|
|
15062
|
+
}
|
|
15063
|
+
function compileFieldTable(section, rows, schemaNames, resolvableNames) {
|
|
15064
|
+
const properties = {};
|
|
15065
|
+
const required = [];
|
|
15066
|
+
for (const row of rows) {
|
|
15067
|
+
const field = row.field.trim();
|
|
15068
|
+
if (!field)
|
|
15069
|
+
throw new Error(`Schema ${section.name}: field name is required`);
|
|
15070
|
+
const schema = compileType(row.type, schemaNames, resolvableNames, `Schema ${section.name}, field ${field}`);
|
|
15071
|
+
if (row.notes.trim())
|
|
15072
|
+
schema.description = row.notes.trim();
|
|
15073
|
+
if (row.format.trim())
|
|
15074
|
+
schema.format = row.format.trim();
|
|
15075
|
+
properties[field] = schema;
|
|
15076
|
+
if (row.required.trim().toLowerCase() === "yes")
|
|
15077
|
+
required.push(field);
|
|
15078
|
+
}
|
|
15079
|
+
const compiled = { type: "object", properties };
|
|
15080
|
+
if (required.length > 0)
|
|
15081
|
+
compiled.required = required;
|
|
15082
|
+
return compiled;
|
|
15083
|
+
}
|
|
15084
|
+
function parseContractSchemas(body) {
|
|
15085
|
+
const { sections, errors } = parseSchemaSections(body);
|
|
15086
|
+
if (sections.length === 0)
|
|
15087
|
+
return { schemas: {}, errors };
|
|
15088
|
+
const schemaNames = new Set(sections.map((s) => s.name));
|
|
15089
|
+
const metadata = /* @__PURE__ */ new Map();
|
|
15090
|
+
const resolvableNames = /* @__PURE__ */ new Set();
|
|
15091
|
+
for (const section of sections) {
|
|
15092
|
+
const raw = parseJsonSchemaBlocks(section);
|
|
15093
|
+
errors.push(...raw.errors);
|
|
15094
|
+
if (raw.blocks.length > 1)
|
|
15095
|
+
errors.push(`Schema ${section.name}: expected at most one json-schema block`);
|
|
15096
|
+
const fields = parseFieldTable(section);
|
|
15097
|
+
if (raw.blocks.length > 0 && fields.found) {
|
|
15098
|
+
errors.push(`Schema ${section.name}: choose either a field table or a json-schema block, not both`);
|
|
15099
|
+
}
|
|
15100
|
+
const foreign = findForeignFenceTag(section);
|
|
15101
|
+
if (foreign !== null) {
|
|
15102
|
+
const desc = foreign ? `a \`\`\`${foreign} code block` : "an untagged code block";
|
|
15103
|
+
errors.push(
|
|
15104
|
+
`Schema ${section.name}: found ${desc}, but a machine-typed schema body must use a \`\`\`json-schema fence. Change the fence to \`\`\`json-schema, use a "| field | type | required | ... |" table, or remove the fence to keep it a free-form (Tier C) prose contract.`
|
|
15105
|
+
);
|
|
15106
|
+
}
|
|
15107
|
+
if (raw.blocks.length === 1 || fields.found)
|
|
15108
|
+
resolvableNames.add(section.name);
|
|
15109
|
+
metadata.set(section.name, {
|
|
15110
|
+
section,
|
|
15111
|
+
rawBlocks: raw.blocks,
|
|
15112
|
+
fieldRows: fields.rows,
|
|
15113
|
+
hasFieldTable: fields.found
|
|
15114
|
+
});
|
|
15115
|
+
}
|
|
15116
|
+
const schemas = {};
|
|
15117
|
+
for (const [name, item] of metadata) {
|
|
15118
|
+
if (item.rawBlocks.length === 1 && !item.hasFieldTable) {
|
|
15119
|
+
schemas[name] = item.rawBlocks[0];
|
|
15120
|
+
continue;
|
|
15121
|
+
}
|
|
15122
|
+
if (!item.hasFieldTable)
|
|
15123
|
+
continue;
|
|
15124
|
+
try {
|
|
15125
|
+
schemas[name] = compileFieldTable(item.section, item.fieldRows, schemaNames, resolvableNames);
|
|
15126
|
+
} catch (err) {
|
|
15127
|
+
errors.push(err instanceof Error ? err.message : String(err));
|
|
15128
|
+
}
|
|
15129
|
+
}
|
|
15130
|
+
return { schemas, errors };
|
|
15131
|
+
}
|
|
15132
|
+
function resolveSchemaCell(cell, schemas) {
|
|
15133
|
+
const raw = cell.trim();
|
|
15134
|
+
if (!raw || raw === "-")
|
|
15135
|
+
return void 0;
|
|
15136
|
+
const isArray = raw.endsWith("[]");
|
|
15137
|
+
const name = isArray ? raw.slice(0, -2).trim() : raw;
|
|
15138
|
+
if (!SCHEMA_NAME_RE.test(name) || !schemas[name])
|
|
15139
|
+
return void 0;
|
|
15140
|
+
const ref = { $ref: `#/components/schemas/${name}` };
|
|
15141
|
+
return isArray ? { type: "array", items: ref } : ref;
|
|
15142
|
+
}
|
|
15143
|
+
function toOpenApiPath(path) {
|
|
15144
|
+
const params = [];
|
|
15145
|
+
const seen = /* @__PURE__ */ new Set();
|
|
15146
|
+
const addParam = (name) => {
|
|
15147
|
+
if (name && !seen.has(name)) {
|
|
15148
|
+
seen.add(name);
|
|
15149
|
+
params.push({ name, in: "path", required: true, schema: { type: "string" } });
|
|
15150
|
+
}
|
|
15151
|
+
};
|
|
15152
|
+
let oapi = path.replace(/:([A-Za-z_][\w]*)/g, (_m, n) => {
|
|
15153
|
+
addParam(n);
|
|
15154
|
+
return `{${n}}`;
|
|
15155
|
+
}).replace(/\{([^}/]+)\}/g, (_m, n) => {
|
|
15156
|
+
addParam(n.trim());
|
|
15157
|
+
return `{${n.trim()}}`;
|
|
15158
|
+
});
|
|
15159
|
+
oapi = oapi.split("?", 1)[0];
|
|
15160
|
+
return { path: oapi, params };
|
|
15161
|
+
}
|
|
15162
|
+
function authToSecurity(auth) {
|
|
15163
|
+
switch (auth) {
|
|
15164
|
+
case "required":
|
|
15165
|
+
case "admin":
|
|
15166
|
+
return { security: [{ bearerAuth: [] }], scheme: "bearerAuth" };
|
|
15167
|
+
case "optional":
|
|
15168
|
+
return { security: [{ bearerAuth: [] }, {}], scheme: "bearerAuth" };
|
|
15169
|
+
case "none":
|
|
15170
|
+
case "public":
|
|
15171
|
+
case "":
|
|
15172
|
+
return {};
|
|
15173
|
+
default:
|
|
15174
|
+
return { security: [{ bearerAuth: [] }], scheme: "bearerAuth" };
|
|
15175
|
+
}
|
|
15176
|
+
}
|
|
15177
|
+
function statusFromErrors(errors) {
|
|
15178
|
+
const codes = (errors.match(/\b[1-5]\d\d\b/g) ?? []).filter((v, i, a) => a.indexOf(v) === i);
|
|
15179
|
+
return codes;
|
|
15180
|
+
}
|
|
15181
|
+
function buildDoc(endpoints, frontmatter, styleBlock, schemas) {
|
|
15182
|
+
const paths = {};
|
|
15183
|
+
let anySecurity = false;
|
|
15184
|
+
for (const ep of endpoints) {
|
|
15185
|
+
const { path, params } = toOpenApiPath(ep.path);
|
|
15186
|
+
const successCode = ep.method === "post" ? "201" : "200";
|
|
15187
|
+
const responseSchema = resolveSchemaCell(ep.response, schemas);
|
|
15188
|
+
const responses = {
|
|
15189
|
+
[successCode]: { description: ep.response ? `Contract response: ${ep.response}` : "Success" }
|
|
15190
|
+
};
|
|
15191
|
+
if (responseSchema) {
|
|
15192
|
+
responses[successCode].content = {
|
|
15193
|
+
"application/json": { schema: responseSchema }
|
|
15194
|
+
};
|
|
15195
|
+
}
|
|
15196
|
+
for (const code of statusFromErrors(ep.errors)) {
|
|
15197
|
+
if (!responses[code])
|
|
15198
|
+
responses[code] = { description: "Error response (see contract error format)" };
|
|
15199
|
+
}
|
|
15200
|
+
const op = {
|
|
15201
|
+
summary: `${ep.method.toUpperCase()} ${ep.path}`,
|
|
15202
|
+
responses
|
|
15203
|
+
};
|
|
15204
|
+
if (params.length > 0)
|
|
15205
|
+
op.parameters = params;
|
|
15206
|
+
const { security, scheme } = authToSecurity(ep.auth);
|
|
15207
|
+
if (security) {
|
|
15208
|
+
op.security = security;
|
|
15209
|
+
if (scheme)
|
|
15210
|
+
anySecurity = true;
|
|
15211
|
+
}
|
|
15212
|
+
if (ep.request && ep.request !== "-" && ep.method !== "get") {
|
|
15213
|
+
const requestSchema = resolveSchemaCell(ep.request, schemas);
|
|
15214
|
+
if (requestSchema) {
|
|
15215
|
+
op.requestBody = {
|
|
15216
|
+
description: `Contract request: ${ep.request}`,
|
|
15217
|
+
content: {
|
|
15218
|
+
"application/json": { schema: requestSchema }
|
|
15219
|
+
}
|
|
15220
|
+
};
|
|
15221
|
+
} else {
|
|
15222
|
+
op.requestBody = {
|
|
15223
|
+
description: `Contract request: ${ep.request} (schema not machine-resolved; see contract)`,
|
|
15224
|
+
content: {},
|
|
15225
|
+
"x-cdd-unresolved": true
|
|
15226
|
+
};
|
|
15227
|
+
}
|
|
15228
|
+
}
|
|
15229
|
+
if (ep.response && !responseSchema)
|
|
15230
|
+
op["x-cdd-response-contract"] = ep.response;
|
|
15231
|
+
if (ep.errors)
|
|
15232
|
+
op["x-cdd-errors"] = ep.errors;
|
|
15233
|
+
paths[path] = paths[path] ?? {};
|
|
15234
|
+
paths[path][ep.method] = op;
|
|
15235
|
+
}
|
|
15236
|
+
const doc = {
|
|
15237
|
+
openapi: "3.1.0",
|
|
15238
|
+
info: {
|
|
15239
|
+
title: frontmatter.summary || frontmatter.surface || "API",
|
|
15240
|
+
version: frontmatter["schema-version"] || "0.0.0"
|
|
15241
|
+
},
|
|
15242
|
+
paths,
|
|
15243
|
+
"x-cdd-generated-from": DEFAULT_CONTRACT,
|
|
15244
|
+
"x-cdd-note": "Generated by `cdd-kit openapi export` from the markdown API contract (the source of truth). Partial by design: request/response bodies are free-form prose in the contract and are marked unresolved. Do not hand-edit; regenerate from the contract."
|
|
15245
|
+
};
|
|
15246
|
+
const styleText = styleBlock.trim();
|
|
15247
|
+
if (styleText)
|
|
15248
|
+
doc.info.description = styleText;
|
|
15249
|
+
if (anySecurity) {
|
|
15250
|
+
doc.components = doc.components ?? {};
|
|
15251
|
+
doc.components.securitySchemes = {
|
|
15252
|
+
bearerAuth: { type: "http", scheme: "bearer" }
|
|
15253
|
+
};
|
|
15254
|
+
}
|
|
15255
|
+
if (Object.keys(schemas).length > 0) {
|
|
15256
|
+
doc.components = doc.components ?? {};
|
|
15257
|
+
doc.components.schemas = schemas;
|
|
15258
|
+
}
|
|
15259
|
+
return doc;
|
|
15260
|
+
}
|
|
15261
|
+
function extractStyleBlock(body) {
|
|
15262
|
+
const m = body.match(/^##\s+API Style\s*\n([\s\S]*?)(?:\n##\s|\n#\s|$)/m);
|
|
15263
|
+
if (!m)
|
|
15264
|
+
return "";
|
|
15265
|
+
return m[1].split("\n").map((l) => l.trim()).filter((l) => l.startsWith("-")).join("\n");
|
|
15266
|
+
}
|
|
15267
|
+
function isNonEmptyComposite(v) {
|
|
15268
|
+
if (typeof v !== "object" || v === null)
|
|
15269
|
+
return false;
|
|
15270
|
+
return Array.isArray(v) ? v.length > 0 : Object.keys(v).length > 0;
|
|
15271
|
+
}
|
|
15272
|
+
function yamlScalar(value) {
|
|
15273
|
+
if (value === null || value === void 0)
|
|
15274
|
+
return "null";
|
|
15275
|
+
if (typeof value === "number" || typeof value === "boolean")
|
|
15276
|
+
return String(value);
|
|
15277
|
+
const s = String(value);
|
|
15278
|
+
if (s === "" || /[:#?{}\[\],&*!|>'"%@`]/.test(s) || /^[\s-]/.test(s) || /\s$/.test(s) || /^\d/.test(s)) {
|
|
15279
|
+
return JSON.stringify(s);
|
|
15280
|
+
}
|
|
15281
|
+
return s;
|
|
15282
|
+
}
|
|
15283
|
+
function toYaml(value, indent = 0) {
|
|
15284
|
+
const pad = " ".repeat(indent);
|
|
15285
|
+
if (!isNonEmptyComposite(value)) {
|
|
15286
|
+
if (Array.isArray(value))
|
|
15287
|
+
return "[]";
|
|
15288
|
+
if (typeof value === "object" && value !== null)
|
|
15289
|
+
return "{}";
|
|
15290
|
+
return yamlScalar(value);
|
|
15291
|
+
}
|
|
15292
|
+
if (Array.isArray(value)) {
|
|
15293
|
+
return value.map((item) => {
|
|
15294
|
+
if (isNonEmptyComposite(item)) {
|
|
15295
|
+
return `${pad}-
|
|
15296
|
+
${toYaml(item, indent + 1)}`;
|
|
15297
|
+
}
|
|
15298
|
+
return `${pad}- ${toYaml(item, indent)}`;
|
|
15299
|
+
}).join("\n");
|
|
15300
|
+
}
|
|
15301
|
+
return Object.entries(value).map(([k, v]) => {
|
|
15302
|
+
const key = /[:#\s]/.test(k) ? JSON.stringify(k) : k;
|
|
15303
|
+
if (isNonEmptyComposite(v)) {
|
|
15304
|
+
return `${pad}${key}:
|
|
15305
|
+
${toYaml(v, indent + 1)}`;
|
|
15306
|
+
}
|
|
15307
|
+
return `${pad}${key}: ${toYaml(v, indent)}`;
|
|
15308
|
+
}).join("\n");
|
|
15309
|
+
}
|
|
15310
|
+
async function openapiExport(opts = {}) {
|
|
15311
|
+
const contractPath = opts.contract || DEFAULT_CONTRACT;
|
|
15312
|
+
const format = opts.format || "json";
|
|
15313
|
+
if (!existsSync15(contractPath)) {
|
|
15314
|
+
log.error(`API contract not found: ${contractPath}`);
|
|
15315
|
+
return 1;
|
|
15316
|
+
}
|
|
15317
|
+
const raw = readFileSync14(contractPath, "utf8");
|
|
15318
|
+
const { body, frontmatter } = stripFrontmatter(raw);
|
|
15319
|
+
const endpoints = parseEndpoints(body);
|
|
15320
|
+
const contractSchemas = parseContractSchemas(body);
|
|
15321
|
+
if (contractSchemas.errors.length > 0) {
|
|
15322
|
+
for (const err of contractSchemas.errors)
|
|
15323
|
+
log.error(err);
|
|
15324
|
+
return 1;
|
|
15325
|
+
}
|
|
15326
|
+
if (endpoints.length === 0) {
|
|
15327
|
+
log.error(`No endpoint table rows found in ${contractPath}. Add rows to the "| method | path | ... |" table first.`);
|
|
15328
|
+
return 1;
|
|
15329
|
+
}
|
|
15330
|
+
const styleBlock = extractStyleBlock(body);
|
|
15331
|
+
const doc = buildDoc(endpoints, frontmatter, styleBlock, contractSchemas.schemas);
|
|
15332
|
+
const serialized = format === "yaml" ? `${toYaml(doc)}
|
|
15333
|
+
` : `${JSON.stringify(doc, null, 2)}
|
|
15334
|
+
`;
|
|
15335
|
+
if (opts.check) {
|
|
15336
|
+
if (!opts.out) {
|
|
15337
|
+
log.error("openapi export --check requires --out <path> (the committed artifact to verify against the contract)");
|
|
15338
|
+
return 1;
|
|
15339
|
+
}
|
|
15340
|
+
const contractFlag = contractPath === DEFAULT_CONTRACT ? "" : ` --contract ${contractPath}`;
|
|
15341
|
+
const yamlFlag = format === "yaml" ? " --yaml" : "";
|
|
15342
|
+
const regen = `cdd-kit openapi export${contractFlag} --out ${opts.out}${yamlFlag}`;
|
|
15343
|
+
if (!existsSync15(opts.out)) {
|
|
15344
|
+
log.error(`openapi export --check: ${opts.out} does not exist. Run \`${regen}\` and commit it.`);
|
|
15345
|
+
return 1;
|
|
15346
|
+
}
|
|
15347
|
+
const committed = readFileSync14(opts.out, "utf8");
|
|
15348
|
+
if (committed === serialized) {
|
|
15349
|
+
log.ok(`OpenAPI artifact ${opts.out} is in sync with ${contractPath} (${endpoints.length} endpoint(s))`);
|
|
15350
|
+
return 0;
|
|
15351
|
+
}
|
|
15352
|
+
log.error(`OpenAPI artifact ${opts.out} is OUT OF SYNC with ${contractPath}. The contract changed but the export was not regenerated.`);
|
|
15353
|
+
log.error(`Fix: \`${regen}\` and commit the result.`);
|
|
15354
|
+
return 1;
|
|
15355
|
+
}
|
|
15356
|
+
if (opts.out) {
|
|
15357
|
+
const outPath = resolve2(opts.out);
|
|
15358
|
+
mkdirSync7(dirname4(outPath), { recursive: true });
|
|
15359
|
+
writeFileSync8(outPath, serialized, "utf8");
|
|
15360
|
+
log.ok(`OpenAPI ${format.toUpperCase()} written to ${outPath} (${endpoints.length} endpoint(s))`);
|
|
15361
|
+
const unresolved = countUnresolved(doc);
|
|
15362
|
+
if (unresolved > 0) {
|
|
15363
|
+
log.info(`${unresolved} request body schema(s) left unresolved (free-form prose in the contract). Fill them in the consumer generator or enrich the contract.`);
|
|
15364
|
+
}
|
|
15365
|
+
} else {
|
|
15366
|
+
process.stdout.write(serialized);
|
|
15367
|
+
}
|
|
15368
|
+
return 0;
|
|
15369
|
+
}
|
|
15370
|
+
function countUnresolved(doc) {
|
|
15371
|
+
let n = 0;
|
|
15372
|
+
for (const methods of Object.values(doc.paths)) {
|
|
15373
|
+
for (const op of Object.values(methods)) {
|
|
15374
|
+
if (op.requestBody?.["x-cdd-unresolved"])
|
|
15375
|
+
n += 1;
|
|
15376
|
+
}
|
|
15377
|
+
}
|
|
15378
|
+
return n;
|
|
13793
15379
|
}
|
|
13794
15380
|
|
|
13795
15381
|
// src/cli/index.ts
|
|
13796
|
-
var __dirname2 =
|
|
13797
|
-
var pkg = JSON.parse(
|
|
15382
|
+
var __dirname2 = dirname8(fileURLToPath4(import.meta.url));
|
|
15383
|
+
var pkg = JSON.parse(readFileSync39(join35(__dirname2, "..", "..", "package.json"), "utf8"));
|
|
13798
15384
|
var program = new Command();
|
|
13799
15385
|
program.name("cdd-kit").description("Contract-Driven Delivery Kit CLI").version(pkg.version);
|
|
13800
15386
|
program.command("init").description(
|
|
13801
15387
|
"Install agents/skill into ~/.claude and scaffold project files in cwd"
|
|
13802
|
-
).option("--global-only", "Only install into ~/.claude, skip project files", false).option("--local-only", "Only scaffold project files, skip ~/.claude", false).option("--force", "Overwrite existing project files", false).option("--provider <provider>", "Provider adapter to scaffold: claude, codex, or both", "claude").option("--hooks", "
|
|
15388
|
+
).option("--global-only", "Only install into ~/.claude, skip project files", false).option("--local-only", "Only scaffold project files, skip ~/.claude", false).option("--force", "Overwrite existing project files", false).option("--provider <provider>", "Provider adapter to scaffold: claude, codex, or both", "claude").option("--hooks", "Also install the pre-commit hook that auto-regenerates .cdd/code-map.yml", false).option("--no-arm", "Skip arming enforcement chokepoints (graph-first hook + pre-commit gate)").action(
|
|
13803
15389
|
(opts) => init({
|
|
13804
15390
|
globalOnly: opts.globalOnly,
|
|
13805
15391
|
localOnly: opts.localOnly,
|
|
13806
15392
|
force: opts.force,
|
|
13807
15393
|
provider: opts.provider,
|
|
13808
|
-
hooks: opts.hooks
|
|
15394
|
+
hooks: opts.hooks,
|
|
15395
|
+
arm: opts.arm !== false
|
|
13809
15396
|
})
|
|
13810
15397
|
);
|
|
13811
15398
|
program.command("update").description("Update provider assets for the current project (does not overwrite project guidance files)").option("--yes", "Apply changes (default is dry-run)", false).option("--provider <provider>", "Provider adapter to update: auto, claude, codex, or both", "auto").option("--postinstall", "Internal: invoked by npm postinstall; no-op if cdd has not been init-ed", false).action((opts) => update({ yes: opts.yes, provider: opts.provider, postinstall: opts.postinstall }));
|
|
@@ -13866,7 +15453,26 @@ function resolveWorkers(value) {
|
|
|
13866
15453
|
return 1;
|
|
13867
15454
|
return Math.min(n, 16);
|
|
13868
15455
|
}
|
|
13869
|
-
program.command("code-map [path]").description("Scan source files and emit a structural index at .cdd/code-map.yml").option("--out <path>", "Output YAML path (default .cdd/code-map.yml; with --surface, .cdd/code-map.<surface>.yml)").option("--surface <subpath>", "Scope the scan to a monorepo subtree and name the map after it").option("--workers [n]", "Parallelize JS/TS/Vue scanning across N child processes (default: CPU count - 1)").option("--include <glob>", "Additional include glob (repeatable)", collectRepeatable, []).option("--exclude <glob>", "Additional exclude glob (repeatable)", collectRepeatable, []).option("--check", "Exit 1 if regenerating would change the file (no write)", false).option("--max-lines <n>", "Warn for files exceeding this line count (default 100000)", "100000").action(async (path, opts) => {
|
|
15456
|
+
program.command("code-map [path]").description("Scan source files and emit a structural index at .cdd/code-map.yml").option("--out <path>", "Output YAML path (default .cdd/code-map.yml; with --surface, .cdd/code-map.<surface>.yml)").option("--surface <subpath>", "Scope the scan to a monorepo subtree and name the map after it").option("--workers [n]", "Parallelize JS/TS/Vue scanning across N child processes (default: CPU count - 1)").option("--include <glob>", "Additional include glob (repeatable)", collectRepeatable, []).option("--exclude <glob>", "Additional exclude glob (repeatable)", collectRepeatable, []).option("--check", "Exit 1 if regenerating would change the file (no write)", false).option("--watch", "Keep the map fresh in the background, rebuilding on file changes (debounced)", false).option("--debounce <ms>", "With --watch: coalesce change bursts within this window (default 500)", "500").option("--max-lines <n>", "Warn for files exceeding this line count (default 100000)", "100000").action(async (path, opts) => {
|
|
15457
|
+
if (opts.watch) {
|
|
15458
|
+
const { codeMapWatch: codeMapWatch2 } = await Promise.resolve().then(() => (init_code_map_watch(), code_map_watch_exports));
|
|
15459
|
+
const controller = new AbortController();
|
|
15460
|
+
const onSignal = () => controller.abort();
|
|
15461
|
+
process.once("SIGINT", onSignal);
|
|
15462
|
+
process.once("SIGTERM", onSignal);
|
|
15463
|
+
const exit2 = await codeMapWatch2({
|
|
15464
|
+
path: path ?? ".",
|
|
15465
|
+
out: opts.out,
|
|
15466
|
+
surface: opts.surface,
|
|
15467
|
+
workers: resolveWorkers(opts.workers),
|
|
15468
|
+
include: opts.include,
|
|
15469
|
+
exclude: opts.exclude,
|
|
15470
|
+
maxLines: parseInt(opts.maxLines, 10),
|
|
15471
|
+
debounceMs: parseInt(opts.debounce ?? "500", 10),
|
|
15472
|
+
signal: controller.signal
|
|
15473
|
+
});
|
|
15474
|
+
process.exit(exit2);
|
|
15475
|
+
}
|
|
13870
15476
|
const { codeMap: codeMap2 } = await Promise.resolve().then(() => (init_code_map(), code_map_exports));
|
|
13871
15477
|
const exit = await codeMap2({
|
|
13872
15478
|
path: path ?? ".",
|
|
@@ -13886,13 +15492,15 @@ program.command("__code-map-scan", { hidden: true }).requiredOption("--lang <lan
|
|
|
13886
15492
|
process.exit(exit);
|
|
13887
15493
|
});
|
|
13888
15494
|
var index = program.command("index").description("Query machine-readable project indexes before opening source files");
|
|
13889
|
-
index.command("query <term>").description("Search .cdd/code-map.yml for files, symbols, imports, and line ranges").option("--map <path>", "Code-map YAML path", ".cdd/code-map.yml").option("--limit <n>", "Maximum result files to print", "10").option("--json", "Print machine-readable JSON", false).option("--no-refresh", "Do not auto-regenerate stale or missing code-map before querying").action(async (term, opts) => {
|
|
15495
|
+
index.command("query <term>").description("Search .cdd/code-map.yml for files, symbols, imports, and line ranges").option("--map <path>", "Code-map YAML path", ".cdd/code-map.yml").option("--limit <n>", "Maximum result files to print", "10").option("--json", "Print machine-readable JSON", false).option("--with-source", "Include the matched source slices inline so no separate Read is needed", false).option("--source-budget <n>", "Max total source lines to emit with --with-source", "400").option("--no-refresh", "Do not auto-regenerate stale or missing code-map before querying").action(async (term, opts) => {
|
|
13890
15496
|
const { indexQuery: indexQuery2 } = await Promise.resolve().then(() => (init_index_query(), index_query_exports));
|
|
13891
15497
|
const exit = await indexQuery2(term, {
|
|
13892
15498
|
map: opts.map,
|
|
13893
15499
|
limit: parseInt(opts.limit, 10),
|
|
13894
15500
|
json: opts.json === true,
|
|
13895
|
-
refresh: opts.refresh !== false
|
|
15501
|
+
refresh: opts.refresh !== false,
|
|
15502
|
+
withSource: opts.withSource === true,
|
|
15503
|
+
sourceBudget: parseInt(opts.sourceBudget ?? "400", 10)
|
|
13896
15504
|
});
|
|
13897
15505
|
process.exit(exit);
|
|
13898
15506
|
});
|
|
@@ -13917,14 +15525,16 @@ graph.command("sync [path]").description("Run CodeGraph incremental sync (requir
|
|
|
13917
15525
|
const exit = await graphSync2({ path, engine: opts.engine, json: opts.json === true });
|
|
13918
15526
|
process.exit(exit);
|
|
13919
15527
|
});
|
|
13920
|
-
graph.command("query <term>").description("Search native graph symbols, optionally delegating to CodeGraph or code-map").option("--engine <engine>", "Graph engine: auto, native, codegraph, or codemap", "auto").option("--map <path>", "Code-map YAML path for fallback", ".cdd/code-map.yml").option("--limit <n>", "Maximum results to print", "10").option("--json", "Print machine-readable JSON", false).option("--no-refresh", "Do not auto-regenerate stale or missing fallback code-map").action(async (term, opts) => {
|
|
15528
|
+
graph.command("query <term>").description("Search native graph symbols, optionally delegating to CodeGraph or code-map").option("--engine <engine>", "Graph engine: auto, native, codegraph, or codemap", "auto").option("--map <path>", "Code-map YAML path for fallback", ".cdd/code-map.yml").option("--limit <n>", "Maximum results to print", "10").option("--json", "Print machine-readable JSON", false).option("--with-source", "Include matched source slices inline so no separate Read is needed (native/codemap engines)", false).option("--source-budget <n>", "Max total source lines to emit with --with-source", "400").option("--no-refresh", "Do not auto-regenerate stale or missing fallback code-map").action(async (term, opts) => {
|
|
13921
15529
|
const { graphQuery: graphQuery2 } = await Promise.resolve().then(() => (init_graph(), graph_exports));
|
|
13922
15530
|
const exit = await graphQuery2(term, {
|
|
13923
15531
|
engine: opts.engine,
|
|
13924
15532
|
map: opts.map,
|
|
13925
15533
|
limit: parseInt(opts.limit, 10),
|
|
13926
15534
|
json: opts.json === true,
|
|
13927
|
-
refresh: opts.refresh !== false
|
|
15535
|
+
refresh: opts.refresh !== false,
|
|
15536
|
+
withSource: opts.withSource === true,
|
|
15537
|
+
sourceBudget: parseInt(opts.sourceBudget ?? "400", 10)
|
|
13928
15538
|
});
|
|
13929
15539
|
process.exit(exit);
|
|
13930
15540
|
});
|
|
@@ -13951,6 +15561,11 @@ graph.command("context <task>").description("Build task context with native grap
|
|
|
13951
15561
|
});
|
|
13952
15562
|
process.exit(exit);
|
|
13953
15563
|
});
|
|
15564
|
+
program.command("classify-check [change-id]").description("Show the mechanical risk-tier floor for a change before classification (advisory; gate enforces it)").option("--text <text>", "Scan this inline intent text instead of a change directory").option("--json", "Print machine-readable JSON", false).action(async (changeId, opts) => {
|
|
15565
|
+
const { classifyCheck: classifyCheck2 } = await Promise.resolve().then(() => (init_classify_check(), classify_check_exports));
|
|
15566
|
+
const exit = await classifyCheck2(changeId, { text: opts.text, json: opts.json });
|
|
15567
|
+
process.exit(exit);
|
|
15568
|
+
});
|
|
13954
15569
|
program.command("mcp").description("Run the cdd-kit MCP stdio server exposing graph and code-map tools").action(async () => {
|
|
13955
15570
|
const { runMcpServer: runMcpServer2 } = await Promise.resolve().then(() => (init_server(), server_exports));
|
|
13956
15571
|
await runMcpServer2({ version: pkg.version });
|
|
@@ -13982,6 +15597,19 @@ program.command("list").description("List active changes in specs/changes/").act
|
|
|
13982
15597
|
program.command("install-hooks").description("Install pre-commit hook that runs cdd-kit gate on staged changes").action(async () => {
|
|
13983
15598
|
await installHooks();
|
|
13984
15599
|
});
|
|
15600
|
+
program.command("install-agent-hooks").description("Install Claude Code agent hooks into .claude/settings.json (graph-first exploration)").option("--graph-first <mode>", "Install the graph-first PreToolUse hook: 'advisory' (default) or 'strict'", "advisory").action(async (opts) => {
|
|
15601
|
+
await installAgentHooks({ graphFirst: opts.graphFirst });
|
|
15602
|
+
});
|
|
15603
|
+
var openapi = program.command("openapi").description("Project the API contract into tooling artifacts (see docs/adr/0001-contract-to-openapi-export.md)");
|
|
15604
|
+
openapi.command("export").description("Export contracts/api/api-contract.md as a minimal OpenAPI 3.1 skeleton").option("--contract <path>", "API contract markdown path", "contracts/api/api-contract.md").option("--out <path>", "Write to a file instead of stdout").option("--yaml", "Emit YAML instead of JSON", false).option("--check", "Verify the artifact at --out is in sync with the contract (exits 1 on drift); does not write", false).action(async (opts) => {
|
|
15605
|
+
const exit = await openapiExport({
|
|
15606
|
+
contract: opts.contract,
|
|
15607
|
+
out: opts.out,
|
|
15608
|
+
format: opts.yaml ? "yaml" : "json",
|
|
15609
|
+
check: opts.check
|
|
15610
|
+
});
|
|
15611
|
+
process.exit(exit);
|
|
15612
|
+
});
|
|
13985
15613
|
program.command("detect-stack").description("Detect the project tech stack and print the result").action(() => {
|
|
13986
15614
|
const cwd = process.cwd();
|
|
13987
15615
|
const result = detectStack(cwd);
|