contract-driven-delivery 2.0.7 → 2.0.9
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 +113 -0
- package/dist/cli/index.js +2328 -1903
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -139,6 +139,13 @@ ${CODE_MAP_BLOCK}
|
|
|
139
139
|
chmodSync(dest, 493);
|
|
140
140
|
} catch {
|
|
141
141
|
}
|
|
142
|
+
try {
|
|
143
|
+
const cddDir = join4(cwd, ".cdd");
|
|
144
|
+
mkdirSync2(cddDir, { recursive: true });
|
|
145
|
+
writeFileSync(join4(cddDir, ".hooks-installed"), `installed: ${(/* @__PURE__ */ new Date()).toISOString()}
|
|
146
|
+
`, "utf8");
|
|
147
|
+
} catch {
|
|
148
|
+
}
|
|
142
149
|
log.ok(`code-map pre-commit hook installed at ${dest}`);
|
|
143
150
|
}
|
|
144
151
|
var START_MARKER, END_MARKER, CODE_MAP_BLOCK;
|
|
@@ -151,7 +158,8 @@ var init_code_map_hook = __esm({
|
|
|
151
158
|
CODE_MAP_BLOCK = `${START_MARKER}
|
|
152
159
|
# Auto-regenerates .cdd/code-map.yml when source files are staged.
|
|
153
160
|
# Generated by: cdd-kit init --hooks (re-run to update).
|
|
154
|
-
|
|
161
|
+
# Extension list mirrors src/code-map/config.ts BUILTIN_INCLUDE.
|
|
162
|
+
staged_src=$(git diff --cached --name-only --diff-filter=ACM | grep -E '\\.(py|js|jsx|mjs|cjs|ts|tsx|vue)$' || true)
|
|
155
163
|
if [ -n "$staged_src" ]; then
|
|
156
164
|
if cdd-kit code-map --check >/dev/null 2>&1; then
|
|
157
165
|
: # already up to date
|
|
@@ -201,6 +209,173 @@ var init_provider = __esm({
|
|
|
201
209
|
}
|
|
202
210
|
});
|
|
203
211
|
|
|
212
|
+
// src/commands/update.ts
|
|
213
|
+
import { join as join7 } from "path";
|
|
214
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync3, readdirSync as readdirSync3, copyFileSync as copyFileSync2, readFileSync as readFileSync5 } from "fs";
|
|
215
|
+
import { createHash } from "crypto";
|
|
216
|
+
import { homedir as homedir2 } from "os";
|
|
217
|
+
function fileHash(filePath) {
|
|
218
|
+
const buf = readFileSync5(filePath);
|
|
219
|
+
return createHash("sha256").update(buf).digest("hex");
|
|
220
|
+
}
|
|
221
|
+
function diffDir(src, dest) {
|
|
222
|
+
const entries = [];
|
|
223
|
+
if (!existsSync6(src))
|
|
224
|
+
return entries;
|
|
225
|
+
function walk(currentSrc, currentDest) {
|
|
226
|
+
const items = readdirSync3(currentSrc, { withFileTypes: true });
|
|
227
|
+
for (const item of items) {
|
|
228
|
+
const srcPath = join7(currentSrc, item.name);
|
|
229
|
+
const destPath = join7(currentDest, item.name);
|
|
230
|
+
if (item.isDirectory()) {
|
|
231
|
+
walk(srcPath, destPath);
|
|
232
|
+
} else {
|
|
233
|
+
if (!existsSync6(destPath)) {
|
|
234
|
+
entries.push({ src: srcPath, dest: destPath, action: "add" });
|
|
235
|
+
} else if (fileHash(srcPath) !== fileHash(destPath)) {
|
|
236
|
+
entries.push({ src: srcPath, dest: destPath, action: "overwrite" });
|
|
237
|
+
} else {
|
|
238
|
+
entries.push({ src: srcPath, dest: destPath, action: "skip" });
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
walk(src, dest);
|
|
244
|
+
return entries;
|
|
245
|
+
}
|
|
246
|
+
function applyDir(entries) {
|
|
247
|
+
let count = 0;
|
|
248
|
+
for (const e of entries) {
|
|
249
|
+
if (e.action === "skip")
|
|
250
|
+
continue;
|
|
251
|
+
mkdirSync3(join7(e.dest, ".."), { recursive: true });
|
|
252
|
+
copyFileSync2(e.src, e.dest);
|
|
253
|
+
count += 1;
|
|
254
|
+
}
|
|
255
|
+
return count;
|
|
256
|
+
}
|
|
257
|
+
function backupDir(dir, backupDest) {
|
|
258
|
+
if (!existsSync6(dir))
|
|
259
|
+
return;
|
|
260
|
+
mkdirSync3(backupDest, { recursive: true });
|
|
261
|
+
function walk(src, dst) {
|
|
262
|
+
const items = readdirSync3(src, { withFileTypes: true });
|
|
263
|
+
for (const item of items) {
|
|
264
|
+
const s = join7(src, item.name);
|
|
265
|
+
const d = join7(dst, item.name);
|
|
266
|
+
if (item.isDirectory()) {
|
|
267
|
+
mkdirSync3(d, { recursive: true });
|
|
268
|
+
walk(s, d);
|
|
269
|
+
} else
|
|
270
|
+
copyFileSync2(s, d);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
walk(dir, backupDest);
|
|
274
|
+
}
|
|
275
|
+
async function update(opts) {
|
|
276
|
+
if (opts.postinstall) {
|
|
277
|
+
if (!existsSync6(join7(SKILLS_HOME, "contract-driven-delivery"))) {
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
opts.yes = true;
|
|
281
|
+
opts.provider = "claude";
|
|
282
|
+
}
|
|
283
|
+
const quiet = !!opts.postinstall;
|
|
284
|
+
if (!quiet)
|
|
285
|
+
log.blank();
|
|
286
|
+
const cwd = process.cwd();
|
|
287
|
+
const requestedProvider = opts.provider ?? "auto";
|
|
288
|
+
if (!validateProviderOption(requestedProvider)) {
|
|
289
|
+
log.error(`Invalid provider: ${requestedProvider}. Use auto, claude, codex, or both.`);
|
|
290
|
+
process.exit(1);
|
|
291
|
+
}
|
|
292
|
+
const provider = inferProvider(cwd, requestedProvider);
|
|
293
|
+
const updateClaudeAssets = provider === "claude" || provider === "both";
|
|
294
|
+
const agentDiff = updateClaudeAssets ? diffDir(ASSET.agents, AGENTS_HOME) : [];
|
|
295
|
+
const skillDiff = updateClaudeAssets ? readdirSync3(ASSET.skills, { withFileTypes: true }).filter((d) => d.isDirectory()).flatMap((d) => diffDir(join7(ASSET.skills, d.name), join7(SKILLS_HOME, d.name))) : [];
|
|
296
|
+
const toWrite = [...agentDiff, ...skillDiff].filter((e) => e.action !== "skip");
|
|
297
|
+
const toAdd = toWrite.filter((e) => e.action === "add");
|
|
298
|
+
const toOver = toWrite.filter((e) => e.action === "overwrite");
|
|
299
|
+
const toSkip = [...agentDiff, ...skillDiff].filter((e) => e.action === "skip");
|
|
300
|
+
if (!quiet) {
|
|
301
|
+
log.info(`Provider: ${provider}`);
|
|
302
|
+
if (updateClaudeAssets) {
|
|
303
|
+
log.info(`Dry-run diff \u2014 agents: ${AGENTS_HOME}`);
|
|
304
|
+
log.info(`Dry-run diff \u2014 skills: ${SKILLS_HOME}`);
|
|
305
|
+
} else {
|
|
306
|
+
log.info("Codex provider has no global cdd-kit assets to update.");
|
|
307
|
+
log.info("Project files are preserved; run cdd-kit init --local-only --provider codex to add missing local guidance.");
|
|
308
|
+
}
|
|
309
|
+
log.blank();
|
|
310
|
+
if (toAdd.length)
|
|
311
|
+
log.info(` + ${toAdd.length} file(s) would be added`);
|
|
312
|
+
if (toOver.length)
|
|
313
|
+
log.warn(` ~ ${toOver.length} file(s) would be overwritten (user edits lost without backup)`);
|
|
314
|
+
if (toSkip.length)
|
|
315
|
+
log.dim(` ${toSkip.length} file(s) unchanged (skipped)`);
|
|
316
|
+
}
|
|
317
|
+
if (toWrite.length === 0) {
|
|
318
|
+
if (!quiet) {
|
|
319
|
+
log.blank();
|
|
320
|
+
log.ok("Already up to date \u2014 nothing to write.");
|
|
321
|
+
log.blank();
|
|
322
|
+
}
|
|
323
|
+
return;
|
|
324
|
+
}
|
|
325
|
+
if (!quiet && !opts.yes) {
|
|
326
|
+
log.blank();
|
|
327
|
+
log.info("Run with --yes to apply changes. Example:");
|
|
328
|
+
log.dim(" cdd-kit update --yes");
|
|
329
|
+
log.blank();
|
|
330
|
+
return;
|
|
331
|
+
}
|
|
332
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
333
|
+
const backupRoot = join7(homedir2(), ".claude", ".cdd-kit-backup", timestamp);
|
|
334
|
+
if (!quiet) {
|
|
335
|
+
log.blank();
|
|
336
|
+
log.info(`Backing up to ${backupRoot} \u2026`);
|
|
337
|
+
}
|
|
338
|
+
backupDir(AGENTS_HOME, join7(backupRoot, "agents"));
|
|
339
|
+
backupDir(SKILLS_HOME, join7(backupRoot, "skills"));
|
|
340
|
+
if (!quiet)
|
|
341
|
+
log.ok(`Backup complete: ${backupRoot}`);
|
|
342
|
+
if (!quiet)
|
|
343
|
+
log.blank();
|
|
344
|
+
let totalSynced = 0;
|
|
345
|
+
if (updateClaudeAssets) {
|
|
346
|
+
if (!quiet)
|
|
347
|
+
log.info(`Updating agents \u2192 ${AGENTS_HOME}`);
|
|
348
|
+
const agentCount = applyDir(agentDiff);
|
|
349
|
+
if (!quiet)
|
|
350
|
+
log.ok(`${agentCount} agent file(s) updated.`);
|
|
351
|
+
totalSynced += agentCount;
|
|
352
|
+
if (!quiet)
|
|
353
|
+
log.info(`Updating skills \u2192 ${SKILLS_HOME}`);
|
|
354
|
+
const skillCount = applyDir(skillDiff);
|
|
355
|
+
if (!quiet)
|
|
356
|
+
log.ok(`${skillCount} skill file(s) updated.`);
|
|
357
|
+
totalSynced += skillCount;
|
|
358
|
+
}
|
|
359
|
+
if (quiet) {
|
|
360
|
+
if (totalSynced > 0)
|
|
361
|
+
log.ok(`cdd-kit: synced ${totalSynced} file(s) to ~/.claude/`);
|
|
362
|
+
} else {
|
|
363
|
+
log.blank();
|
|
364
|
+
log.info("Project files (contracts/, specs/, tests/, ci/) were not changed.");
|
|
365
|
+
log.ok("Update complete.");
|
|
366
|
+
log.info(`Backup saved to: ${backupRoot}`);
|
|
367
|
+
log.blank();
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
var init_update = __esm({
|
|
371
|
+
"src/commands/update.ts"() {
|
|
372
|
+
"use strict";
|
|
373
|
+
init_paths();
|
|
374
|
+
init_logger();
|
|
375
|
+
init_provider();
|
|
376
|
+
}
|
|
377
|
+
});
|
|
378
|
+
|
|
204
379
|
// src/commands/context-scan.ts
|
|
205
380
|
var context_scan_exports = {};
|
|
206
381
|
__export(context_scan_exports, {
|
|
@@ -240,7 +415,14 @@ function getForbiddenPaths(cwd) {
|
|
|
240
415
|
}
|
|
241
416
|
function isForbidden(relPath, forbidden) {
|
|
242
417
|
const normalized = relPath.replace(/\\/g, "/");
|
|
243
|
-
|
|
418
|
+
if (forbidden.some((pattern) => normalized === pattern || normalized.startsWith(`${pattern}/`))) {
|
|
419
|
+
return true;
|
|
420
|
+
}
|
|
421
|
+
for (const segment of normalized.split("/")) {
|
|
422
|
+
if (FORBIDDEN_DIRECTORY_NAMES.has(segment))
|
|
423
|
+
return true;
|
|
424
|
+
}
|
|
425
|
+
return false;
|
|
244
426
|
}
|
|
245
427
|
function buildTree(dir, cwd, forbidden, stats, prefix = "", depth = 0) {
|
|
246
428
|
const entries = readdirSync4(dir, { withFileTypes: true }).sort((a, b) => {
|
|
@@ -457,7 +639,7 @@ async function contextScan(opts = {}) {
|
|
|
457
639
|
log.ok("Created specs/context/contracts-index.md");
|
|
458
640
|
}
|
|
459
641
|
}
|
|
460
|
-
var DEFAULT_FORBIDDEN, PER_DIR_ENTRY_CAP;
|
|
642
|
+
var DEFAULT_FORBIDDEN, FORBIDDEN_DIRECTORY_NAMES, PER_DIR_ENTRY_CAP;
|
|
461
643
|
var init_context_scan = __esm({
|
|
462
644
|
"src/commands/context-scan.ts"() {
|
|
463
645
|
"use strict";
|
|
@@ -470,8 +652,43 @@ var init_context_scan = __esm({
|
|
|
470
652
|
"build",
|
|
471
653
|
"assets",
|
|
472
654
|
"specs/archive",
|
|
473
|
-
"specs/changes"
|
|
655
|
+
"specs/changes",
|
|
656
|
+
// cdd-kit runtime artifacts. Without these the user's local backups land
|
|
657
|
+
// in `specs/context/project-map.md`, polluting it and breaking the
|
|
658
|
+
// inputs-digest match for any fresh clone.
|
|
659
|
+
".cdd/.refresh-backup",
|
|
660
|
+
".cdd/migrate-backup",
|
|
661
|
+
".cdd/runtime"
|
|
474
662
|
];
|
|
663
|
+
FORBIDDEN_DIRECTORY_NAMES = /* @__PURE__ */ new Set([
|
|
664
|
+
// Node — also caught by top-level prefix, but a nested
|
|
665
|
+
// `frontend/node_modules` requires basename matching
|
|
666
|
+
"node_modules",
|
|
667
|
+
// Python
|
|
668
|
+
"__pycache__",
|
|
669
|
+
".pytest_cache",
|
|
670
|
+
".mypy_cache",
|
|
671
|
+
".ruff_cache",
|
|
672
|
+
".tox",
|
|
673
|
+
// JS/TS frameworks
|
|
674
|
+
".next",
|
|
675
|
+
".nuxt",
|
|
676
|
+
".svelte-kit",
|
|
677
|
+
".parcel-cache",
|
|
678
|
+
".turbo",
|
|
679
|
+
".nyc_output",
|
|
680
|
+
// Generic build / coverage caches
|
|
681
|
+
"coverage",
|
|
682
|
+
"htmlcov",
|
|
683
|
+
".cache",
|
|
684
|
+
// Virtualenvs
|
|
685
|
+
"venv",
|
|
686
|
+
".venv",
|
|
687
|
+
// IDE / OS noise
|
|
688
|
+
".idea",
|
|
689
|
+
".vscode",
|
|
690
|
+
".DS_Store"
|
|
691
|
+
]);
|
|
475
692
|
PER_DIR_ENTRY_CAP = 50;
|
|
476
693
|
}
|
|
477
694
|
});
|
|
@@ -4046,49 +4263,49 @@ var require_fast_uri = __commonJS({
|
|
|
4046
4263
|
schemelessOptions.skipEscape = true;
|
|
4047
4264
|
return serialize(resolved, schemelessOptions);
|
|
4048
4265
|
}
|
|
4049
|
-
function resolveComponent(base,
|
|
4266
|
+
function resolveComponent(base, relative6, options, skipNormalization) {
|
|
4050
4267
|
const target = {};
|
|
4051
4268
|
if (!skipNormalization) {
|
|
4052
4269
|
base = parse3(serialize(base, options), options);
|
|
4053
|
-
|
|
4270
|
+
relative6 = parse3(serialize(relative6, options), options);
|
|
4054
4271
|
}
|
|
4055
4272
|
options = options || {};
|
|
4056
|
-
if (!options.tolerant &&
|
|
4057
|
-
target.scheme =
|
|
4058
|
-
target.userinfo =
|
|
4059
|
-
target.host =
|
|
4060
|
-
target.port =
|
|
4061
|
-
target.path = removeDotSegments(
|
|
4062
|
-
target.query =
|
|
4273
|
+
if (!options.tolerant && relative6.scheme) {
|
|
4274
|
+
target.scheme = relative6.scheme;
|
|
4275
|
+
target.userinfo = relative6.userinfo;
|
|
4276
|
+
target.host = relative6.host;
|
|
4277
|
+
target.port = relative6.port;
|
|
4278
|
+
target.path = removeDotSegments(relative6.path || "");
|
|
4279
|
+
target.query = relative6.query;
|
|
4063
4280
|
} else {
|
|
4064
|
-
if (
|
|
4065
|
-
target.userinfo =
|
|
4066
|
-
target.host =
|
|
4067
|
-
target.port =
|
|
4068
|
-
target.path = removeDotSegments(
|
|
4069
|
-
target.query =
|
|
4281
|
+
if (relative6.userinfo !== void 0 || relative6.host !== void 0 || relative6.port !== void 0) {
|
|
4282
|
+
target.userinfo = relative6.userinfo;
|
|
4283
|
+
target.host = relative6.host;
|
|
4284
|
+
target.port = relative6.port;
|
|
4285
|
+
target.path = removeDotSegments(relative6.path || "");
|
|
4286
|
+
target.query = relative6.query;
|
|
4070
4287
|
} else {
|
|
4071
|
-
if (!
|
|
4288
|
+
if (!relative6.path) {
|
|
4072
4289
|
target.path = base.path;
|
|
4073
|
-
if (
|
|
4074
|
-
target.query =
|
|
4290
|
+
if (relative6.query !== void 0) {
|
|
4291
|
+
target.query = relative6.query;
|
|
4075
4292
|
} else {
|
|
4076
4293
|
target.query = base.query;
|
|
4077
4294
|
}
|
|
4078
4295
|
} else {
|
|
4079
|
-
if (
|
|
4080
|
-
target.path = removeDotSegments(
|
|
4296
|
+
if (relative6.path[0] === "/") {
|
|
4297
|
+
target.path = removeDotSegments(relative6.path);
|
|
4081
4298
|
} else {
|
|
4082
4299
|
if ((base.userinfo !== void 0 || base.host !== void 0 || base.port !== void 0) && !base.path) {
|
|
4083
|
-
target.path = "/" +
|
|
4300
|
+
target.path = "/" + relative6.path;
|
|
4084
4301
|
} else if (!base.path) {
|
|
4085
|
-
target.path =
|
|
4302
|
+
target.path = relative6.path;
|
|
4086
4303
|
} else {
|
|
4087
|
-
target.path = base.path.slice(0, base.path.lastIndexOf("/") + 1) +
|
|
4304
|
+
target.path = base.path.slice(0, base.path.lastIndexOf("/") + 1) + relative6.path;
|
|
4088
4305
|
}
|
|
4089
4306
|
target.path = removeDotSegments(target.path);
|
|
4090
4307
|
}
|
|
4091
|
-
target.query =
|
|
4308
|
+
target.query = relative6.query;
|
|
4092
4309
|
}
|
|
4093
4310
|
target.userinfo = base.userinfo;
|
|
4094
4311
|
target.host = base.host;
|
|
@@ -4096,7 +4313,7 @@ var require_fast_uri = __commonJS({
|
|
|
4096
4313
|
}
|
|
4097
4314
|
target.scheme = base.scheme;
|
|
4098
4315
|
}
|
|
4099
|
-
target.fragment =
|
|
4316
|
+
target.fragment = relative6.fragment;
|
|
4100
4317
|
return target;
|
|
4101
4318
|
}
|
|
4102
4319
|
function equal(uriA, uriB, options) {
|
|
@@ -7468,1877 +7685,2232 @@ var init_freshness = __esm({
|
|
|
7468
7685
|
}
|
|
7469
7686
|
});
|
|
7470
7687
|
|
|
7471
|
-
// src/
|
|
7472
|
-
|
|
7473
|
-
|
|
7474
|
-
|
|
7475
|
-
|
|
7476
|
-
|
|
7477
|
-
}
|
|
7478
|
-
|
|
7479
|
-
|
|
7480
|
-
|
|
7481
|
-
|
|
7482
|
-
|
|
7483
|
-
|
|
7484
|
-
|
|
7485
|
-
|
|
7486
|
-
return "[]";
|
|
7487
|
-
return `[${items.map(quoteScalar).join(", ")}]`;
|
|
7488
|
-
}
|
|
7489
|
-
function truncateDecorator(d, max = 80) {
|
|
7490
|
-
const cleaned = d.replace(/\r?\n/g, " ");
|
|
7491
|
-
return cleaned.length <= max ? cleaned : cleaned.slice(0, max) + "...";
|
|
7492
|
-
}
|
|
7493
|
-
function renderClass(c) {
|
|
7494
|
-
const lines = [];
|
|
7495
|
-
lines.push(` - name: ${c.name}`);
|
|
7496
|
-
lines.push(` lines: ${c.lines[0]}-${c.lines[1]}`);
|
|
7497
|
-
if (c.methods.length > 0) {
|
|
7498
|
-
lines.push(" methods:");
|
|
7499
|
-
for (const m of c.methods) {
|
|
7500
|
-
const asyncPrefix = m.async ? "async " : "";
|
|
7501
|
-
lines.push(` - { name: ${asyncPrefix}${m.name}, lines: ${m.lines[0]}-${m.lines[1]} }`);
|
|
7502
|
-
}
|
|
7688
|
+
// src/commands/migrate.ts
|
|
7689
|
+
var migrate_exports = {};
|
|
7690
|
+
__export(migrate_exports, {
|
|
7691
|
+
migrate: () => migrate
|
|
7692
|
+
});
|
|
7693
|
+
import { join as join16 } from "path";
|
|
7694
|
+
import { cpSync as cpSync2, existsSync as existsSync14, mkdirSync as mkdirSync6, readdirSync as readdirSync8, readFileSync as readFileSync11, renameSync, rmSync as rmSync2, writeFileSync as writeFileSync6 } from "fs";
|
|
7695
|
+
import yaml3 from "js-yaml";
|
|
7696
|
+
function backupChangeDir(cwd, changeId, sessionStamp) {
|
|
7697
|
+
const backupRoot = join16(cwd, ".cdd", "migrate-backup", sessionStamp);
|
|
7698
|
+
const backupDir2 = join16(backupRoot, changeId);
|
|
7699
|
+
mkdirSync6(backupRoot, { recursive: true });
|
|
7700
|
+
const sourceDir = join16(cwd, "specs", "changes", changeId);
|
|
7701
|
+
if (existsSync14(sourceDir)) {
|
|
7702
|
+
cpSync2(sourceDir, backupDir2, { recursive: true });
|
|
7503
7703
|
}
|
|
7504
|
-
return
|
|
7704
|
+
return backupDir2;
|
|
7505
7705
|
}
|
|
7506
|
-
function
|
|
7507
|
-
|
|
7508
|
-
|
|
7509
|
-
|
|
7510
|
-
|
|
7511
|
-
|
|
7512
|
-
|
|
7513
|
-
|
|
7706
|
+
function buildLegacyContextManifest(changeId) {
|
|
7707
|
+
return [
|
|
7708
|
+
"# Context Manifest",
|
|
7709
|
+
"",
|
|
7710
|
+
"Generated by `cdd-kit migrate` for an existing change.",
|
|
7711
|
+
"Legacy manifest. Forbidden paths come from `.cdd/context-policy.json`.",
|
|
7712
|
+
"",
|
|
7713
|
+
"## Affected Surfaces",
|
|
7714
|
+
"- legacy-unknown",
|
|
7715
|
+
"",
|
|
7716
|
+
"## Allowed Paths",
|
|
7717
|
+
`- specs/changes/${changeId}/`,
|
|
7718
|
+
"",
|
|
7719
|
+
"## Required Contracts",
|
|
7720
|
+
"- legacy-unknown",
|
|
7721
|
+
"",
|
|
7722
|
+
"## Required Tests",
|
|
7723
|
+
"- legacy-unknown",
|
|
7724
|
+
"",
|
|
7725
|
+
"## Agent Work Packets",
|
|
7726
|
+
"",
|
|
7727
|
+
"## Context Expansion Requests",
|
|
7728
|
+
"-",
|
|
7729
|
+
"",
|
|
7730
|
+
"## Approved Expansions",
|
|
7731
|
+
"-",
|
|
7732
|
+
""
|
|
7733
|
+
].join("\n");
|
|
7514
7734
|
}
|
|
7515
|
-
function
|
|
7516
|
-
|
|
7517
|
-
|
|
7735
|
+
function buildContextGovernedManifest(changeId) {
|
|
7736
|
+
return [
|
|
7737
|
+
"# Context Manifest",
|
|
7738
|
+
"",
|
|
7739
|
+
"Generated by `cdd-kit migrate --enable-context-governance` for an existing change.",
|
|
7740
|
+
"Review and narrow the allowed paths before assigning implementation work.",
|
|
7741
|
+
"Forbidden paths come from `.cdd/context-policy.json`.",
|
|
7742
|
+
"",
|
|
7743
|
+
"## Affected Surfaces",
|
|
7744
|
+
"- legacy-unknown",
|
|
7745
|
+
"",
|
|
7746
|
+
"## Allowed Paths",
|
|
7747
|
+
`- specs/changes/${changeId}/`,
|
|
7748
|
+
"- specs/context/project-map.md",
|
|
7749
|
+
"- specs/context/contracts-index.md",
|
|
7750
|
+
"",
|
|
7751
|
+
"## Required Contracts",
|
|
7752
|
+
"- legacy-unknown",
|
|
7753
|
+
"",
|
|
7754
|
+
"## Required Tests",
|
|
7755
|
+
"- legacy-unknown",
|
|
7756
|
+
"",
|
|
7757
|
+
"## Agent Work Packets",
|
|
7758
|
+
"",
|
|
7759
|
+
"### change-classifier",
|
|
7760
|
+
"- allowed:",
|
|
7761
|
+
` - specs/changes/${changeId}/`,
|
|
7762
|
+
" - specs/context/project-map.md",
|
|
7763
|
+
" - specs/context/contracts-index.md",
|
|
7764
|
+
"",
|
|
7765
|
+
"## Context Expansion Requests",
|
|
7766
|
+
"",
|
|
7767
|
+
"<!--",
|
|
7768
|
+
"Agents must request context expansion instead of reading outside their work packet.",
|
|
7769
|
+
"Use this format only for real requests:",
|
|
7770
|
+
"",
|
|
7771
|
+
"- request-id: CER-001",
|
|
7772
|
+
" requested_paths:",
|
|
7773
|
+
" - src/example.ts",
|
|
7774
|
+
" reason: why this file is required",
|
|
7775
|
+
" status: pending",
|
|
7776
|
+
"-->",
|
|
7777
|
+
"",
|
|
7778
|
+
"## Approved Expansions",
|
|
7779
|
+
"-",
|
|
7780
|
+
""
|
|
7781
|
+
].join("\n");
|
|
7518
7782
|
}
|
|
7519
|
-
function
|
|
7520
|
-
const
|
|
7521
|
-
|
|
7522
|
-
|
|
7523
|
-
|
|
7524
|
-
|
|
7525
|
-
|
|
7783
|
+
function parseLegacyFrontmatter(content) {
|
|
7784
|
+
const m = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
|
|
7785
|
+
if (!m)
|
|
7786
|
+
return {};
|
|
7787
|
+
const out = {};
|
|
7788
|
+
for (const line of m[1].split(/\r?\n/)) {
|
|
7789
|
+
const colon = line.indexOf(":");
|
|
7790
|
+
if (colon === -1)
|
|
7791
|
+
continue;
|
|
7792
|
+
const key = line.slice(0, colon).trim();
|
|
7793
|
+
if (!key)
|
|
7794
|
+
continue;
|
|
7795
|
+
out[key] = line.slice(colon + 1).trim();
|
|
7526
7796
|
}
|
|
7527
|
-
return
|
|
7797
|
+
return out;
|
|
7528
7798
|
}
|
|
7529
|
-
function
|
|
7530
|
-
|
|
7531
|
-
|
|
7532
|
-
const
|
|
7533
|
-
|
|
7534
|
-
|
|
7535
|
-
|
|
7536
|
-
|
|
7537
|
-
const pathKey = quotePath(e.path);
|
|
7538
|
-
bodyLines.push(`${pathKey}: # ${e.total_lines} lines`);
|
|
7539
|
-
if (e.imports.length > 0) {
|
|
7540
|
-
bodyLines.push(" imports:");
|
|
7541
|
-
for (const imp of e.imports) {
|
|
7542
|
-
const mod = quoteScalar(imp.module);
|
|
7543
|
-
bodyLines.push(` - { module: ${mod}, items: ${renderItems(imp.items)}, line: ${imp.line} }`);
|
|
7544
|
-
}
|
|
7545
|
-
}
|
|
7546
|
-
if (e.constants.length > 0) {
|
|
7547
|
-
bodyLines.push(" constants:");
|
|
7548
|
-
for (const c of e.constants) {
|
|
7549
|
-
bodyLines.push(` - { name: ${c.name}, line: ${c.line} }`);
|
|
7550
|
-
}
|
|
7551
|
-
}
|
|
7552
|
-
if (e.classes.length > 0) {
|
|
7553
|
-
bodyLines.push(" classes:");
|
|
7554
|
-
for (const c of e.classes) {
|
|
7555
|
-
bodyLines.push(...renderClass(c));
|
|
7556
|
-
}
|
|
7557
|
-
}
|
|
7558
|
-
if (e.functions.length > 0) {
|
|
7559
|
-
bodyLines.push(" functions:");
|
|
7560
|
-
for (const f of e.functions) {
|
|
7561
|
-
bodyLines.push(renderFunction(f));
|
|
7562
|
-
}
|
|
7563
|
-
}
|
|
7564
|
-
if (e.interfaces && e.interfaces.length > 0) {
|
|
7565
|
-
bodyLines.push(" interfaces:");
|
|
7566
|
-
for (const t of e.interfaces) {
|
|
7567
|
-
bodyLines.push(renderTypeDef(t));
|
|
7568
|
-
}
|
|
7569
|
-
}
|
|
7570
|
-
if (e.types && e.types.length > 0) {
|
|
7571
|
-
bodyLines.push(" types:");
|
|
7572
|
-
for (const t of e.types) {
|
|
7573
|
-
bodyLines.push(renderTypeDef(t));
|
|
7574
|
-
}
|
|
7575
|
-
}
|
|
7576
|
-
if (e.enums && e.enums.length > 0) {
|
|
7577
|
-
bodyLines.push(" enums:");
|
|
7578
|
-
for (const en of e.enums) {
|
|
7579
|
-
bodyLines.push(...renderEnum(en));
|
|
7580
|
-
}
|
|
7581
|
-
}
|
|
7582
|
-
}
|
|
7583
|
-
const mapLines = bodyLines.length + 2;
|
|
7584
|
-
const compression = totalSrc === 0 ? 0 : totalSrc / mapLines;
|
|
7585
|
-
const fileCount = entries.length;
|
|
7586
|
-
const header = [
|
|
7587
|
-
`# generated: ${now} by ${opts.generator}`,
|
|
7588
|
-
`# files: ${fileCount}, src-lines: ${totalSrc}, map-lines: ${mapLines}, compression: ${compression.toFixed(1)}x`
|
|
7589
|
-
];
|
|
7590
|
-
return [...header, ...bodyLines].join("\n") + "\n";
|
|
7799
|
+
function parseListField(raw) {
|
|
7800
|
+
if (!raw)
|
|
7801
|
+
return [];
|
|
7802
|
+
const trimmed = raw.trim();
|
|
7803
|
+
if (!trimmed || trimmed === "[]")
|
|
7804
|
+
return [];
|
|
7805
|
+
const inner = trimmed.startsWith("[") && trimmed.endsWith("]") ? trimmed.slice(1, -1) : trimmed;
|
|
7806
|
+
return inner.split(",").map((item) => item.trim().replace(/^["']|["']$/g, "")).filter(Boolean);
|
|
7591
7807
|
}
|
|
7592
|
-
|
|
7593
|
-
|
|
7594
|
-
|
|
7595
|
-
|
|
7596
|
-
|
|
7597
|
-
|
|
7598
|
-
|
|
7599
|
-
|
|
7600
|
-
|
|
7601
|
-
async function scanInProcess(scanner, absolutePaths, repoRoot) {
|
|
7602
|
-
const entries = [];
|
|
7603
|
-
const warnings = [];
|
|
7604
|
-
for (const absPath of absolutePaths) {
|
|
7605
|
-
try {
|
|
7606
|
-
const entry = await scanner.scan(absPath, repoRoot);
|
|
7607
|
-
if (entry === null) {
|
|
7608
|
-
const rel = absPath.replace(/\\/g, "/").replace(repoRoot.replace(/\\/g, "/") + "/", "");
|
|
7609
|
-
warnings.push({ path: rel, message: "parse error (scanner returned null)" });
|
|
7610
|
-
} else {
|
|
7611
|
-
entries.push(entry);
|
|
7612
|
-
}
|
|
7613
|
-
} catch (err) {
|
|
7614
|
-
const rel = absPath.replace(/\\/g, "/").replace(repoRoot.replace(/\\/g, "/") + "/", "");
|
|
7615
|
-
warnings.push({ path: rel, message: `IO error: ${err.message}` });
|
|
7808
|
+
function parseLegacyTaskList(body) {
|
|
7809
|
+
const lines = body.split(/\r?\n/);
|
|
7810
|
+
const rows = [];
|
|
7811
|
+
let currentSection;
|
|
7812
|
+
for (const raw of lines) {
|
|
7813
|
+
const headerMatch = raw.match(/^##\s+\d+\.\s+(.*)\s*$/);
|
|
7814
|
+
if (headerMatch) {
|
|
7815
|
+
currentSection = headerMatch[1].trim();
|
|
7816
|
+
continue;
|
|
7616
7817
|
}
|
|
7818
|
+
const itemMatch = raw.match(/^\s*-\s*\[([ xX\-])\]\s+(\d+(?:\.\d+)*)\s+(.*)\s*$/);
|
|
7819
|
+
if (!itemMatch)
|
|
7820
|
+
continue;
|
|
7821
|
+
const mark = itemMatch[1];
|
|
7822
|
+
const id = itemMatch[2];
|
|
7823
|
+
const title = itemMatch[3].trim();
|
|
7824
|
+
let status = "pending";
|
|
7825
|
+
if (mark === "x" || mark === "X")
|
|
7826
|
+
status = "done";
|
|
7827
|
+
else if (mark === "-")
|
|
7828
|
+
status = "skipped";
|
|
7829
|
+
rows.push({ id, title, status, section: currentSection });
|
|
7617
7830
|
}
|
|
7618
|
-
return
|
|
7831
|
+
return rows;
|
|
7619
7832
|
}
|
|
7620
|
-
function
|
|
7621
|
-
const
|
|
7622
|
-
|
|
7623
|
-
|
|
7624
|
-
|
|
7625
|
-
if (!out[ext])
|
|
7626
|
-
out[ext] = [];
|
|
7627
|
-
out[ext].push(f);
|
|
7833
|
+
function migrateTasksFile(changeId, changeDir, enableContextGovernance, detectedTier, changed, warnings, pendingWrites, pendingDeletes) {
|
|
7834
|
+
const newPath = join16(changeDir, "tasks.yml");
|
|
7835
|
+
const legacyPath = join16(changeDir, "tasks.md");
|
|
7836
|
+
if (existsSync14(newPath)) {
|
|
7837
|
+
return;
|
|
7628
7838
|
}
|
|
7629
|
-
|
|
7630
|
-
|
|
7631
|
-
|
|
7632
|
-
"src/code-map/orchestrator.ts"() {
|
|
7633
|
-
"use strict";
|
|
7634
|
-
init_include_exclude();
|
|
7839
|
+
if (!existsSync14(legacyPath)) {
|
|
7840
|
+
warnings.push("tasks.md not found and tasks.yml missing \u2014 skipping tasks migration");
|
|
7841
|
+
return;
|
|
7635
7842
|
}
|
|
7636
|
-
|
|
7637
|
-
|
|
7638
|
-
|
|
7639
|
-
|
|
7640
|
-
|
|
7641
|
-
const
|
|
7642
|
-
|
|
7643
|
-
|
|
7644
|
-
|
|
7645
|
-
|
|
7646
|
-
}
|
|
7647
|
-
|
|
7648
|
-
|
|
7649
|
-
|
|
7650
|
-
}
|
|
7651
|
-
var init_common = __esm({
|
|
7652
|
-
"src/code-map/scanners/common.ts"() {
|
|
7653
|
-
"use strict";
|
|
7843
|
+
const raw = readFileSync11(legacyPath, "utf8");
|
|
7844
|
+
const fm = parseLegacyFrontmatter(raw);
|
|
7845
|
+
const bodyMatch = raw.match(/^---\r?\n[\s\S]*?\r?\n---\r?\n?([\s\S]*)$/);
|
|
7846
|
+
const body = bodyMatch ? bodyMatch[1] : raw;
|
|
7847
|
+
const tasksRows = parseLegacyTaskList(body);
|
|
7848
|
+
const data = {};
|
|
7849
|
+
data["change-id"] = fm["change-id"] || changeId;
|
|
7850
|
+
data["status"] = fm["status"] || "in-progress";
|
|
7851
|
+
if (fm["tier"] && /^\d+$/.test(fm["tier"])) {
|
|
7852
|
+
data["tier"] = parseInt(fm["tier"], 10);
|
|
7853
|
+
} else if (detectedTier) {
|
|
7854
|
+
data["tier"] = parseInt(detectedTier, 10);
|
|
7855
|
+
} else {
|
|
7856
|
+
data["tier"] = null;
|
|
7654
7857
|
}
|
|
7655
|
-
|
|
7656
|
-
|
|
7657
|
-
// src/code-map/scanners/python.ts
|
|
7658
|
-
var python_exports = {};
|
|
7659
|
-
__export(python_exports, {
|
|
7660
|
-
pythonScanner: () => pythonScanner
|
|
7661
|
-
});
|
|
7662
|
-
import { spawnSync as spawnSync2 } from "child_process";
|
|
7663
|
-
import { writeFileSync as writeFileSync6, unlinkSync } from "fs";
|
|
7664
|
-
import { join as join16 } from "path";
|
|
7665
|
-
import { tmpdir } from "os";
|
|
7666
|
-
function detectPython2() {
|
|
7667
|
-
for (const candidate of ["python3", "python"]) {
|
|
7668
|
-
try {
|
|
7669
|
-
const result = spawnSync2(candidate, ["--version"], {
|
|
7670
|
-
encoding: "utf8",
|
|
7671
|
-
timeout: 5e3
|
|
7672
|
-
});
|
|
7673
|
-
if (result.status === 0)
|
|
7674
|
-
return candidate;
|
|
7675
|
-
} catch {
|
|
7676
|
-
}
|
|
7858
|
+
if (enableContextGovernance || fm["context-governance"] === "v1") {
|
|
7859
|
+
data["context-governance"] = "v1";
|
|
7677
7860
|
}
|
|
7678
|
-
|
|
7861
|
+
const archive2 = parseListField(fm["archive-tasks"]);
|
|
7862
|
+
data["archive-tasks"] = archive2.length > 0 ? archive2 : ["7.1", "7.2"];
|
|
7863
|
+
const deps = parseListField(fm["depends-on"]);
|
|
7864
|
+
data["depends-on"] = deps;
|
|
7865
|
+
data["tasks"] = tasksRows.map((r) => {
|
|
7866
|
+
const out = { id: r.id, title: r.title, status: r.status };
|
|
7867
|
+
if (r.section)
|
|
7868
|
+
out["section"] = r.section;
|
|
7869
|
+
return out;
|
|
7870
|
+
});
|
|
7871
|
+
const yamlOut = yaml3.dump(data, { lineWidth: -1, noRefs: true });
|
|
7872
|
+
pendingWrites.push({ path: newPath, content: yamlOut });
|
|
7873
|
+
pendingDeletes.push({ path: legacyPath });
|
|
7874
|
+
changed.push(`tasks.md -> tasks.yml (${tasksRows.length} task(s) migrated)`);
|
|
7679
7875
|
}
|
|
7680
|
-
|
|
7681
|
-
|
|
7682
|
-
|
|
7683
|
-
|
|
7684
|
-
|
|
7685
|
-
|
|
7686
|
-
|
|
7687
|
-
|
|
7688
|
-
|
|
7689
|
-
|
|
7690
|
-
|
|
7691
|
-
|
|
7692
|
-
|
|
7876
|
+
function parseLegacyAgentLog(content) {
|
|
7877
|
+
const lines = content.split(/\r?\n/);
|
|
7878
|
+
const data = {};
|
|
7879
|
+
let i = 0;
|
|
7880
|
+
const topFieldRe = /^[ ]{0,1}-\s*([\w-]+):\s*(.*)$/;
|
|
7881
|
+
while (i < lines.length) {
|
|
7882
|
+
const line = lines[i];
|
|
7883
|
+
const fieldMatch = line.match(topFieldRe);
|
|
7884
|
+
if (!fieldMatch) {
|
|
7885
|
+
i++;
|
|
7886
|
+
continue;
|
|
7887
|
+
}
|
|
7888
|
+
const key = fieldMatch[1];
|
|
7889
|
+
const inline = fieldMatch[2].trim();
|
|
7890
|
+
if (key === "files-read" || key === "artifacts") {
|
|
7891
|
+
const items = [];
|
|
7892
|
+
let j = i + 1;
|
|
7893
|
+
while (j < lines.length) {
|
|
7894
|
+
const sub = lines[j];
|
|
7895
|
+
if (topFieldRe.test(sub))
|
|
7896
|
+
break;
|
|
7897
|
+
if (/^#/.test(sub))
|
|
7898
|
+
break;
|
|
7899
|
+
const itemMatch = sub.match(/^\s{2,}-\s+(.+?)\s*$/);
|
|
7900
|
+
if (itemMatch) {
|
|
7901
|
+
items.push(itemMatch[1].trim());
|
|
7693
7902
|
}
|
|
7694
|
-
|
|
7695
|
-
}
|
|
7696
|
-
async scan(absolutePath, repoRoot) {
|
|
7697
|
-
const result = await this.scanBatch([absolutePath], repoRoot);
|
|
7698
|
-
return result.entries[0] ?? null;
|
|
7903
|
+
j++;
|
|
7699
7904
|
}
|
|
7700
|
-
|
|
7701
|
-
|
|
7702
|
-
|
|
7703
|
-
|
|
7704
|
-
|
|
7705
|
-
|
|
7706
|
-
|
|
7707
|
-
path: "",
|
|
7708
|
-
message: `python interpreter not found on PATH; skipping ${count} .py file${count === 1 ? "" : "s"}`
|
|
7709
|
-
});
|
|
7710
|
-
return { entries, warnings };
|
|
7711
|
-
}
|
|
7712
|
-
const scriptPath = ASSET.codeMapPython;
|
|
7713
|
-
const rand = Math.random().toString(36).slice(2);
|
|
7714
|
-
const listFile = join16(tmpdir(), `cdd-codemap-${process.pid}-${rand}.txt`);
|
|
7715
|
-
writeFileSync6(listFile, absolutePaths.join("\n") + "\n", "utf8");
|
|
7716
|
-
let stdout = "";
|
|
7717
|
-
let stderr = "";
|
|
7718
|
-
let exitCode = 0;
|
|
7719
|
-
try {
|
|
7720
|
-
const result = spawnSync2(
|
|
7721
|
-
interpreter,
|
|
7722
|
-
[scriptPath, "--batch-file", listFile, "--repo-root", repoRoot],
|
|
7723
|
-
{
|
|
7724
|
-
encoding: "utf8",
|
|
7725
|
-
timeout: TIMEOUT_MS,
|
|
7726
|
-
maxBuffer: 50 * 1024 * 1024
|
|
7727
|
-
// 50MB
|
|
7728
|
-
}
|
|
7729
|
-
);
|
|
7730
|
-
stdout = result.stdout ?? "";
|
|
7731
|
-
stderr = result.stderr ?? "";
|
|
7732
|
-
exitCode = result.status ?? -1;
|
|
7733
|
-
if (result.error) {
|
|
7734
|
-
const errMsg = result.error.message ?? String(result.error);
|
|
7735
|
-
if (errMsg.includes("ENOENT")) {
|
|
7736
|
-
warnings.push({
|
|
7737
|
-
path: "",
|
|
7738
|
-
message: `python interpreter not found (ENOENT); skipping ${absolutePaths.length} .py file(s)`
|
|
7739
|
-
});
|
|
7740
|
-
return { entries, warnings };
|
|
7741
|
-
}
|
|
7742
|
-
if (errMsg.includes("ETIMEDOUT") || errMsg.includes("timeout")) {
|
|
7743
|
-
warnings.push({
|
|
7744
|
-
path: "",
|
|
7745
|
-
message: `python scanner timed out after ${TIMEOUT_MS}ms; skipping .py files`
|
|
7746
|
-
});
|
|
7747
|
-
return { entries, warnings };
|
|
7748
|
-
}
|
|
7749
|
-
warnings.push({
|
|
7750
|
-
path: "",
|
|
7751
|
-
message: `python scanner error: ${errMsg}; skipping .py files`
|
|
7752
|
-
});
|
|
7753
|
-
return { entries, warnings };
|
|
7754
|
-
}
|
|
7755
|
-
} finally {
|
|
7756
|
-
try {
|
|
7757
|
-
unlinkSync(listFile);
|
|
7758
|
-
} catch {
|
|
7759
|
-
}
|
|
7760
|
-
}
|
|
7761
|
-
if (exitCode === 3) {
|
|
7762
|
-
warnings.push({
|
|
7763
|
-
path: "",
|
|
7764
|
-
message: "python interpreter is < 3.9 (need ast.unparse); skipping .py files"
|
|
7765
|
-
});
|
|
7766
|
-
return { entries, warnings };
|
|
7767
|
-
}
|
|
7768
|
-
for (const line of stdout.split("\n")) {
|
|
7769
|
-
const trimmed = line.trim();
|
|
7770
|
-
if (!trimmed)
|
|
7771
|
-
continue;
|
|
7772
|
-
let parsed;
|
|
7773
|
-
try {
|
|
7774
|
-
parsed = JSON.parse(trimmed);
|
|
7775
|
-
} catch {
|
|
7776
|
-
continue;
|
|
7777
|
-
}
|
|
7778
|
-
if (!parsed.ok) {
|
|
7779
|
-
warnings.push({ path: parsed.path, message: parsed.error });
|
|
7780
|
-
continue;
|
|
7905
|
+
if (key === "files-read") {
|
|
7906
|
+
data["files-read"] = items;
|
|
7907
|
+
} else {
|
|
7908
|
+
data["artifacts"] = items.map((s) => {
|
|
7909
|
+
const idx = s.indexOf(":");
|
|
7910
|
+
if (idx === -1) {
|
|
7911
|
+
return { type: "note", pointer: s };
|
|
7781
7912
|
}
|
|
7782
|
-
const
|
|
7783
|
-
|
|
7784
|
-
|
|
7785
|
-
|
|
7786
|
-
imports: r.imports ?? [],
|
|
7787
|
-
constants: r.constants ?? [],
|
|
7788
|
-
classes: (r.classes ?? []).map((c) => ({
|
|
7789
|
-
name: c.name,
|
|
7790
|
-
lines: [c.lines[0], c.lines[1]],
|
|
7791
|
-
methods: (c.methods ?? []).map((m) => ({
|
|
7792
|
-
name: m.name,
|
|
7793
|
-
lines: [m.lines[0], m.lines[1]],
|
|
7794
|
-
async: m.async
|
|
7795
|
-
}))
|
|
7796
|
-
})),
|
|
7797
|
-
functions: (r.functions ?? []).map((f) => ({
|
|
7798
|
-
name: f.name,
|
|
7799
|
-
lines: [f.lines[0], f.lines[1]],
|
|
7800
|
-
decorators: f.decorators ?? [],
|
|
7801
|
-
async: f.async
|
|
7802
|
-
}))
|
|
7803
|
-
});
|
|
7804
|
-
}
|
|
7805
|
-
if (exitCode === 2) {
|
|
7806
|
-
warnings.push({
|
|
7807
|
-
path: "",
|
|
7808
|
-
message: `python scanner exited with code 2 (fatal); partial results only. stderr: ${stderr.slice(0, 200)}`
|
|
7809
|
-
});
|
|
7810
|
-
}
|
|
7811
|
-
return { entries, warnings };
|
|
7913
|
+
const type = s.slice(0, idx).trim();
|
|
7914
|
+
const pointer = s.slice(idx + 1).trim();
|
|
7915
|
+
return { type, pointer };
|
|
7916
|
+
});
|
|
7812
7917
|
}
|
|
7813
|
-
|
|
7814
|
-
|
|
7815
|
-
}
|
|
7816
|
-
});
|
|
7817
|
-
|
|
7818
|
-
// src/code-map/scanners/javascript.ts
|
|
7819
|
-
var javascript_exports = {};
|
|
7820
|
-
__export(javascript_exports, {
|
|
7821
|
-
COMMON_PLUGINS: () => COMMON_PLUGINS,
|
|
7822
|
-
countSourceLines: () => countSourceLines,
|
|
7823
|
-
jsScanner: () => jsScanner,
|
|
7824
|
-
parseAndExtract: () => parseAndExtract,
|
|
7825
|
-
parseJsSource: () => parseJsSource,
|
|
7826
|
-
parseSourceWithPlugins: () => parseSourceWithPlugins
|
|
7827
|
-
});
|
|
7828
|
-
import { readFileSync as readFileSync12 } from "fs";
|
|
7829
|
-
import { parse } from "@babel/parser";
|
|
7830
|
-
function parseSourceWithPlugins(source, plugins) {
|
|
7831
|
-
return parse(source, {
|
|
7832
|
-
sourceType: "unambiguous",
|
|
7833
|
-
allowReturnOutsideFunction: true,
|
|
7834
|
-
allowAwaitOutsideFunction: true,
|
|
7835
|
-
errorRecovery: true,
|
|
7836
|
-
plugins
|
|
7837
|
-
});
|
|
7838
|
-
}
|
|
7839
|
-
function isCallExpression(node, callee) {
|
|
7840
|
-
if (!node || node.type !== "CallExpression")
|
|
7841
|
-
return false;
|
|
7842
|
-
const c = node.callee;
|
|
7843
|
-
return c.type === "Identifier" && c.name === callee;
|
|
7844
|
-
}
|
|
7845
|
-
function extractRequireModule(node) {
|
|
7846
|
-
if (!node || node.type !== "CallExpression")
|
|
7847
|
-
return null;
|
|
7848
|
-
const c = node.callee;
|
|
7849
|
-
if (c.type !== "Identifier" || c.name !== "require")
|
|
7850
|
-
return null;
|
|
7851
|
-
const arg = node.arguments[0];
|
|
7852
|
-
if (!arg || arg.type !== "StringLiteral")
|
|
7853
|
-
return null;
|
|
7854
|
-
return arg.value;
|
|
7855
|
-
}
|
|
7856
|
-
function getLineRange(node) {
|
|
7857
|
-
const start = node.loc?.start.line ?? 1;
|
|
7858
|
-
const end = node.loc?.end.line ?? start;
|
|
7859
|
-
return [start, end];
|
|
7860
|
-
}
|
|
7861
|
-
function processImportDeclaration(node) {
|
|
7862
|
-
const items = [];
|
|
7863
|
-
for (const spec of node.specifiers) {
|
|
7864
|
-
if (spec.type === "ImportDefaultSpecifier") {
|
|
7865
|
-
items.push(`default:${spec.local.name}`);
|
|
7866
|
-
} else if (spec.type === "ImportNamespaceSpecifier") {
|
|
7867
|
-
items.push(`*:${spec.local.name}`);
|
|
7868
|
-
} else if (spec.type === "ImportSpecifier") {
|
|
7869
|
-
const imported = spec.imported;
|
|
7870
|
-
items.push(imported.type === "Identifier" ? imported.name : spec.local.name);
|
|
7918
|
+
i = j;
|
|
7919
|
+
continue;
|
|
7871
7920
|
}
|
|
7921
|
+
data[key] = inline;
|
|
7922
|
+
i++;
|
|
7872
7923
|
}
|
|
7873
|
-
return
|
|
7874
|
-
module: node.source.value,
|
|
7875
|
-
items,
|
|
7876
|
-
line: node.loc?.start.line ?? 1
|
|
7877
|
-
};
|
|
7924
|
+
return data;
|
|
7878
7925
|
}
|
|
7879
|
-
function
|
|
7880
|
-
const
|
|
7881
|
-
if (!
|
|
7882
|
-
return
|
|
7883
|
-
|
|
7884
|
-
|
|
7885
|
-
|
|
7886
|
-
|
|
7887
|
-
|
|
7888
|
-
|
|
7926
|
+
function migrateAgentLogs(changeDir, changed, pendingWrites, pendingDeletes) {
|
|
7927
|
+
const agentLogDir = join16(changeDir, "agent-log");
|
|
7928
|
+
if (!existsSync14(agentLogDir))
|
|
7929
|
+
return;
|
|
7930
|
+
const mdLogs = readdirSync8(agentLogDir).filter((f) => f.endsWith(".md"));
|
|
7931
|
+
for (const f of mdLogs) {
|
|
7932
|
+
const fullPath = join16(agentLogDir, f);
|
|
7933
|
+
const yamlName = f.replace(/\.md$/, ".yml");
|
|
7934
|
+
const yamlFull = join16(agentLogDir, yamlName);
|
|
7935
|
+
if (existsSync14(yamlFull))
|
|
7936
|
+
continue;
|
|
7937
|
+
const raw = readFileSync11(fullPath, "utf8");
|
|
7938
|
+
const parsed = parseLegacyAgentLog(raw);
|
|
7939
|
+
const yamlOut = yaml3.dump(parsed, { lineWidth: -1, noRefs: true });
|
|
7940
|
+
pendingWrites.push({ path: yamlFull, content: yamlOut });
|
|
7941
|
+
pendingDeletes.push({ path: fullPath });
|
|
7942
|
+
changed.push(`agent-log/${f} -> agent-log/${yamlName}`);
|
|
7943
|
+
}
|
|
7889
7944
|
}
|
|
7890
|
-
function
|
|
7891
|
-
const
|
|
7892
|
-
|
|
7893
|
-
|
|
7894
|
-
const
|
|
7895
|
-
|
|
7896
|
-
|
|
7897
|
-
|
|
7898
|
-
|
|
7899
|
-
|
|
7900
|
-
|
|
7901
|
-
|
|
7902
|
-
|
|
7945
|
+
function migrateOne(changeId, changeDir, enableContextGovernance) {
|
|
7946
|
+
const changed = [];
|
|
7947
|
+
const warnings = [];
|
|
7948
|
+
const pending = [];
|
|
7949
|
+
const deletes = [];
|
|
7950
|
+
let detectedTier = null;
|
|
7951
|
+
const classifPath = join16(changeDir, "change-classification.md");
|
|
7952
|
+
if (existsSync14(classifPath)) {
|
|
7953
|
+
const content = readFileSync11(classifPath, "utf8");
|
|
7954
|
+
const hasNewTierFormat = /^## Tier\s*\n\s*-\s*\d\s*$/m.test(content);
|
|
7955
|
+
const oldMatch = content.match(/\*\*Tier[:\*]+\s*(?:Tier\s*)?(\d)/i) ?? content.match(/^-?\s*Tier:\s*(?:Tier\s*)?(\d)/mi);
|
|
7956
|
+
if (oldMatch)
|
|
7957
|
+
detectedTier = oldMatch[1];
|
|
7958
|
+
if (!hasNewTierFormat) {
|
|
7959
|
+
if (detectedTier) {
|
|
7960
|
+
const addition = `
|
|
7961
|
+
## Tier
|
|
7962
|
+
- ${detectedTier}
|
|
7963
|
+
`;
|
|
7964
|
+
if (!content.includes("\n## Tier\n")) {
|
|
7965
|
+
changed.push(
|
|
7966
|
+
`change-classification.md: appended "## Tier\\n- ${detectedTier}" (converted from old format)`
|
|
7967
|
+
);
|
|
7968
|
+
pending.push({ path: classifPath, content: content + addition });
|
|
7969
|
+
}
|
|
7903
7970
|
} else {
|
|
7904
|
-
|
|
7971
|
+
warnings.push(
|
|
7972
|
+
"change-classification.md: could not detect tier (no **Tier:** N or ## Tier N found). Set `tier: <0-5>` in tasks.yml frontmatter to enable tier-based gate checks."
|
|
7973
|
+
);
|
|
7905
7974
|
}
|
|
7906
|
-
|
|
7907
|
-
|
|
7908
|
-
|
|
7909
|
-
|
|
7910
|
-
});
|
|
7975
|
+
} else {
|
|
7976
|
+
const structured = content.match(/^## Tier\s*\n\s*-\s*(\d)\s*$/m);
|
|
7977
|
+
if (structured)
|
|
7978
|
+
detectedTier = structured[1];
|
|
7911
7979
|
}
|
|
7912
7980
|
}
|
|
7913
|
-
|
|
7914
|
-
|
|
7915
|
-
|
|
7916
|
-
|
|
7917
|
-
|
|
7981
|
+
migrateTasksFile(changeId, changeDir, enableContextGovernance, detectedTier, changed, warnings, pending, deletes);
|
|
7982
|
+
migrateAgentLogs(changeDir, changed, pending, deletes);
|
|
7983
|
+
const manifestPath = join16(changeDir, "context-manifest.md");
|
|
7984
|
+
if (!existsSync14(manifestPath)) {
|
|
7985
|
+
changed.push(enableContextGovernance ? "context-manifest.md: added context-governance v1 manifest scaffold" : "context-manifest.md: added legacy context manifest scaffold");
|
|
7986
|
+
pending.push({
|
|
7987
|
+
path: manifestPath,
|
|
7988
|
+
content: enableContextGovernance ? buildContextGovernedManifest(changeId) : buildLegacyContextManifest(changeId)
|
|
7989
|
+
});
|
|
7990
|
+
} else if (enableContextGovernance) {
|
|
7991
|
+
warnings.push("context-manifest.md already exists \u2014 review allowed paths before relying on context-governance: v1");
|
|
7992
|
+
}
|
|
7993
|
+
return { result: { changed, warnings }, pending, deletes };
|
|
7918
7994
|
}
|
|
7919
|
-
function
|
|
7920
|
-
|
|
7921
|
-
|
|
7922
|
-
|
|
7923
|
-
|
|
7924
|
-
|
|
7925
|
-
|
|
7926
|
-
|
|
7927
|
-
|
|
7928
|
-
|
|
7995
|
+
function commitWritesAtomically(pending, deletes) {
|
|
7996
|
+
const renames = [];
|
|
7997
|
+
try {
|
|
7998
|
+
for (const write of pending) {
|
|
7999
|
+
const tmp = `${write.path}.cdd-migrate.tmp`;
|
|
8000
|
+
writeFileSync6(tmp, write.content, "utf8");
|
|
8001
|
+
renames.push({ tmp, final: write.path });
|
|
8002
|
+
}
|
|
8003
|
+
} catch (err) {
|
|
8004
|
+
for (const r of renames) {
|
|
8005
|
+
try {
|
|
8006
|
+
rmSync2(r.tmp, { force: true });
|
|
8007
|
+
} catch {
|
|
7929
8008
|
}
|
|
8009
|
+
}
|
|
8010
|
+
throw err;
|
|
8011
|
+
}
|
|
8012
|
+
for (const r of renames) {
|
|
8013
|
+
renameSync(r.tmp, r.final);
|
|
8014
|
+
}
|
|
8015
|
+
for (const d of deletes) {
|
|
8016
|
+
try {
|
|
8017
|
+
rmSync2(d.path, { force: true });
|
|
8018
|
+
} catch {
|
|
8019
|
+
}
|
|
8020
|
+
}
|
|
8021
|
+
}
|
|
8022
|
+
async function migrate(changeId, opts = {}) {
|
|
8023
|
+
const cwd = process.cwd();
|
|
8024
|
+
const dryRun = opts.dryRun ?? false;
|
|
8025
|
+
const enableContextGovernance = opts.enableContextGovernance ?? false;
|
|
8026
|
+
const noBackup = opts.noBackup ?? false;
|
|
8027
|
+
const idsToMigrate = [];
|
|
8028
|
+
if (opts.all) {
|
|
8029
|
+
const changesDir = join16(cwd, "specs", "changes");
|
|
8030
|
+
if (!existsSync14(changesDir)) {
|
|
8031
|
+
log.info("No specs/changes/ directory found \u2014 nothing to migrate.");
|
|
8032
|
+
return;
|
|
8033
|
+
}
|
|
8034
|
+
idsToMigrate.push(
|
|
8035
|
+
...readdirSync8(changesDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name)
|
|
8036
|
+
);
|
|
8037
|
+
} else if (changeId) {
|
|
8038
|
+
const specificDir = join16(cwd, "specs", "changes", changeId);
|
|
8039
|
+
if (!existsSync14(specificDir)) {
|
|
8040
|
+
log.error(`Change not found: specs/changes/${changeId}`);
|
|
8041
|
+
process.exit(1);
|
|
8042
|
+
}
|
|
8043
|
+
idsToMigrate.push(changeId);
|
|
8044
|
+
} else {
|
|
8045
|
+
log.error("Usage: cdd-kit migrate <change-id> | cdd-kit migrate --all [--dry-run] [--no-backup]");
|
|
8046
|
+
process.exit(1);
|
|
8047
|
+
}
|
|
8048
|
+
if (idsToMigrate.length === 0) {
|
|
8049
|
+
log.info("No changes found to migrate.");
|
|
8050
|
+
return;
|
|
8051
|
+
}
|
|
8052
|
+
if (dryRun) {
|
|
8053
|
+
log.info("Dry run \u2014 no files will be written.");
|
|
8054
|
+
log.blank();
|
|
8055
|
+
}
|
|
8056
|
+
const sessionStamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
8057
|
+
let migratedCount = 0;
|
|
8058
|
+
let upToDateCount = 0;
|
|
8059
|
+
const backupRoot = join16(cwd, ".cdd", "migrate-backup", sessionStamp);
|
|
8060
|
+
for (const id of idsToMigrate) {
|
|
8061
|
+
const changeDir = join16(cwd, "specs", "changes", id);
|
|
8062
|
+
if (!existsSync14(changeDir)) {
|
|
8063
|
+
log.warn(` ${id}: directory not found \u2014 skipping`);
|
|
7930
8064
|
continue;
|
|
7931
8065
|
}
|
|
7932
|
-
|
|
7933
|
-
|
|
8066
|
+
const { result, pending, deletes } = migrateOne(id, changeDir, enableContextGovernance);
|
|
8067
|
+
const { changed, warnings } = result;
|
|
8068
|
+
if (changed.length === 0) {
|
|
8069
|
+
log.info(` ${id}: already up to date`);
|
|
8070
|
+
upToDateCount++;
|
|
8071
|
+
for (const w of warnings)
|
|
8072
|
+
log.warn(` ${id}: ${w}`);
|
|
7934
8073
|
continue;
|
|
7935
8074
|
}
|
|
7936
|
-
if (
|
|
7937
|
-
|
|
7938
|
-
|
|
7939
|
-
|
|
7940
|
-
|
|
7941
|
-
|
|
7942
|
-
|
|
7943
|
-
|
|
7944
|
-
|
|
7945
|
-
|
|
7946
|
-
|
|
7947
|
-
|
|
7948
|
-
|
|
7949
|
-
|
|
8075
|
+
if (!dryRun) {
|
|
8076
|
+
try {
|
|
8077
|
+
if (!noBackup)
|
|
8078
|
+
backupChangeDir(cwd, id, sessionStamp);
|
|
8079
|
+
commitWritesAtomically(pending, deletes);
|
|
8080
|
+
} catch (err) {
|
|
8081
|
+
log.error(` ${id}: migration failed \u2014 ${err.message}`);
|
|
8082
|
+
if (!noBackup) {
|
|
8083
|
+
log.error(` ${id}: restore from .cdd/migrate-backup/${sessionStamp}/${id}/`);
|
|
8084
|
+
}
|
|
8085
|
+
process.exit(1);
|
|
8086
|
+
}
|
|
8087
|
+
}
|
|
8088
|
+
log.ok(` ${id}: migrated`);
|
|
8089
|
+
for (const c of changed)
|
|
8090
|
+
log.info(` + ${c}`);
|
|
8091
|
+
migratedCount++;
|
|
8092
|
+
for (const w of warnings)
|
|
8093
|
+
log.warn(` ${id}: ${w}`);
|
|
8094
|
+
}
|
|
8095
|
+
log.blank();
|
|
8096
|
+
if (dryRun) {
|
|
8097
|
+
log.info(`Dry run complete: ${migratedCount} change(s) would be updated, ${upToDateCount} already up to date.`);
|
|
8098
|
+
} else {
|
|
8099
|
+
log.ok(`Migration complete: ${migratedCount} updated, ${upToDateCount} already up to date.`);
|
|
8100
|
+
if (migratedCount > 0 && !noBackup) {
|
|
8101
|
+
log.info(`Backup: ${backupRoot}`);
|
|
8102
|
+
if (ensureGitignoreEntry(cwd, ".cdd/migrate-backup/")) {
|
|
8103
|
+
log.info("Added `.cdd/migrate-backup/` to .gitignore");
|
|
8104
|
+
}
|
|
8105
|
+
log.info('Next: git add specs/changes/ && git commit -m "chore: migrate changes to YAML format"');
|
|
8106
|
+
log.info("When stable, remove backup: rm -rf .cdd/migrate-backup/");
|
|
7950
8107
|
}
|
|
7951
8108
|
}
|
|
7952
8109
|
}
|
|
7953
|
-
function
|
|
7954
|
-
|
|
7955
|
-
|
|
7956
|
-
if (!
|
|
7957
|
-
return
|
|
7958
|
-
|
|
7959
|
-
|
|
7960
|
-
|
|
7961
|
-
|
|
7962
|
-
|
|
7963
|
-
|
|
7964
|
-
|
|
7965
|
-
|
|
7966
|
-
|
|
7967
|
-
|
|
7968
|
-
|
|
7969
|
-
|
|
7970
|
-
|
|
7971
|
-
|
|
7972
|
-
|
|
7973
|
-
};
|
|
8110
|
+
function ensureGitignoreEntry(cwd, entry) {
|
|
8111
|
+
const path = join16(cwd, ".gitignore");
|
|
8112
|
+
const trimmed = entry.trim();
|
|
8113
|
+
if (!trimmed)
|
|
8114
|
+
return false;
|
|
8115
|
+
const re = new RegExp(`^\\s*${trimmed.replace(/[.*+?^${}()|[\\]\\\\]/g, "\\$&")}\\s*$`, "m");
|
|
8116
|
+
let existing = "";
|
|
8117
|
+
if (existsSync14(path))
|
|
8118
|
+
existing = readFileSync11(path, "utf8");
|
|
8119
|
+
if (re.test(existing))
|
|
8120
|
+
return false;
|
|
8121
|
+
const sep = existing.length > 0 && !existing.endsWith("\n") ? "\n" : "";
|
|
8122
|
+
const block = existing.length === 0 ? `# cdd-kit generated backups (do not commit)
|
|
8123
|
+
${trimmed}
|
|
8124
|
+
` : `${sep}
|
|
8125
|
+
# cdd-kit generated backups (do not commit)
|
|
8126
|
+
${trimmed}
|
|
8127
|
+
`;
|
|
8128
|
+
writeFileSync6(path, existing + block, "utf8");
|
|
8129
|
+
return true;
|
|
7974
8130
|
}
|
|
7975
|
-
|
|
7976
|
-
|
|
7977
|
-
|
|
7978
|
-
|
|
7979
|
-
|
|
7980
|
-
|
|
7981
|
-
|
|
7982
|
-
|
|
8131
|
+
var init_migrate = __esm({
|
|
8132
|
+
"src/commands/migrate.ts"() {
|
|
8133
|
+
"use strict";
|
|
8134
|
+
init_logger();
|
|
8135
|
+
}
|
|
8136
|
+
});
|
|
8137
|
+
|
|
8138
|
+
// src/commands/upgrade.ts
|
|
8139
|
+
var upgrade_exports = {};
|
|
8140
|
+
__export(upgrade_exports, {
|
|
8141
|
+
upgrade: () => upgrade
|
|
8142
|
+
});
|
|
8143
|
+
import { existsSync as existsSync15, mkdirSync as mkdirSync7, readdirSync as readdirSync9, copyFileSync as copyFileSync3, readFileSync as readFileSync12, writeFileSync as writeFileSync7 } from "fs";
|
|
8144
|
+
import { dirname as dirname4, join as join17, relative as relative3 } from "path";
|
|
8145
|
+
function planMissingFiles(srcDir, destDir, label, planned) {
|
|
8146
|
+
if (!existsSync15(srcDir))
|
|
8147
|
+
return;
|
|
8148
|
+
for (const entry of readdirSync9(srcDir, { withFileTypes: true })) {
|
|
8149
|
+
const src = join17(srcDir, entry.name);
|
|
8150
|
+
const dest = join17(destDir, entry.name);
|
|
8151
|
+
if (entry.isDirectory()) {
|
|
8152
|
+
planMissingFiles(src, dest, join17(label, entry.name), planned);
|
|
7983
8153
|
continue;
|
|
7984
|
-
|
|
7985
|
-
|
|
7986
|
-
|
|
7987
|
-
|
|
8154
|
+
}
|
|
8155
|
+
if (!existsSync15(dest)) {
|
|
8156
|
+
planned.push({ src, dest, rel: join17(label, relative3(srcDir, src)) });
|
|
8157
|
+
}
|
|
7988
8158
|
}
|
|
7989
|
-
return {
|
|
7990
|
-
name: node.id.name,
|
|
7991
|
-
lines: getLineRange(node),
|
|
7992
|
-
exported,
|
|
7993
|
-
members
|
|
7994
|
-
};
|
|
7995
8159
|
}
|
|
7996
|
-
function
|
|
7997
|
-
if (
|
|
7998
|
-
|
|
7999
|
-
|
|
8160
|
+
function planProviderGuidance(cwd, provider, planned) {
|
|
8161
|
+
if (provider === "claude" || provider === "both") {
|
|
8162
|
+
if (!existsSync15(join17(cwd, "CLAUDE.md"))) {
|
|
8163
|
+
planned.push({ src: ASSET.claudeTemplate, dest: join17(cwd, "CLAUDE.md"), rel: "CLAUDE.md" });
|
|
8164
|
+
}
|
|
8165
|
+
if (!existsSync15(join17(cwd, "AGENTS.md"))) {
|
|
8166
|
+
planned.push({ src: ASSET.agentsTemplate, dest: join17(cwd, "AGENTS.md"), rel: "AGENTS.md" });
|
|
8167
|
+
}
|
|
8000
8168
|
}
|
|
8001
|
-
if (
|
|
8002
|
-
|
|
8003
|
-
|
|
8004
|
-
|
|
8005
|
-
|
|
8006
|
-
|
|
8007
|
-
|
|
8008
|
-
|
|
8009
|
-
|
|
8010
|
-
|
|
8169
|
+
if ((provider === "codex" || provider === "both") && !existsSync15(join17(cwd, "CODEX.md"))) {
|
|
8170
|
+
planned.push({ src: ASSET.codexTemplate, dest: join17(cwd, "CODEX.md"), rel: "CODEX.md" });
|
|
8171
|
+
}
|
|
8172
|
+
}
|
|
8173
|
+
function applyCopy(plan) {
|
|
8174
|
+
for (const item of plan) {
|
|
8175
|
+
mkdirSync7(dirname4(item.dest), { recursive: true });
|
|
8176
|
+
copyFileSync3(item.src, item.dest);
|
|
8177
|
+
}
|
|
8178
|
+
}
|
|
8179
|
+
async function upgrade(opts = {}) {
|
|
8180
|
+
const cwd = process.cwd();
|
|
8181
|
+
const requestedProvider = opts.provider ?? "auto";
|
|
8182
|
+
if (!validateProviderOption(requestedProvider)) {
|
|
8183
|
+
log.error(`Invalid provider: ${requestedProvider}. Use auto, claude, codex, or both.`);
|
|
8184
|
+
process.exit(1);
|
|
8185
|
+
}
|
|
8186
|
+
const provider = inferProvider(cwd, requestedProvider);
|
|
8187
|
+
const plan = [];
|
|
8188
|
+
planMissingFiles(ASSET.contracts, join17(cwd, "contracts"), "contracts", plan);
|
|
8189
|
+
planMissingFiles(ASSET.specsTemplates, join17(cwd, "specs", "templates"), "specs/templates", plan);
|
|
8190
|
+
planMissingFiles(ASSET.testsTemplates, join17(cwd, "tests", "templates"), "tests/templates", plan);
|
|
8191
|
+
planMissingFiles(ASSET.ci, join17(cwd, "ci"), "ci", plan);
|
|
8192
|
+
planMissingFiles(ASSET.githubWorkflows, join17(cwd, ".github", "workflows"), ".github/workflows", plan);
|
|
8193
|
+
planMissingFiles(ASSET.cddConfig, join17(cwd, ".cdd"), ".cdd", plan);
|
|
8194
|
+
planProviderGuidance(cwd, provider, plan);
|
|
8195
|
+
log.blank();
|
|
8196
|
+
log.info(`Upgrade provider: ${provider}`);
|
|
8197
|
+
if (plan.length === 0) {
|
|
8198
|
+
log.ok("No missing cdd-kit project files found.");
|
|
8199
|
+
if (opts.migrateChanges) {
|
|
8200
|
+
log.blank();
|
|
8201
|
+
log.info("Running change migration flow...");
|
|
8202
|
+
await migrate(void 0, {
|
|
8203
|
+
all: true,
|
|
8204
|
+
dryRun: !opts.yes,
|
|
8205
|
+
enableContextGovernance: opts.enableContextGovernance
|
|
8206
|
+
});
|
|
8011
8207
|
}
|
|
8208
|
+
log.blank();
|
|
8012
8209
|
return;
|
|
8013
8210
|
}
|
|
8014
|
-
|
|
8015
|
-
|
|
8211
|
+
log.info(`${plan.length} missing file(s) detected:`);
|
|
8212
|
+
for (const item of plan)
|
|
8213
|
+
log.dim(` + ${item.rel.replace(/\\/g, "/")}`);
|
|
8214
|
+
if (!opts.yes) {
|
|
8215
|
+
log.blank();
|
|
8216
|
+
log.info("Dry run only. Re-run with --yes to write missing files.");
|
|
8217
|
+
if (opts.migrateChanges) {
|
|
8218
|
+
log.blank();
|
|
8219
|
+
log.info("Previewing existing change migration because --migrate-changes was requested.");
|
|
8220
|
+
await migrate(void 0, {
|
|
8221
|
+
all: true,
|
|
8222
|
+
dryRun: true,
|
|
8223
|
+
enableContextGovernance: opts.enableContextGovernance
|
|
8224
|
+
});
|
|
8225
|
+
}
|
|
8226
|
+
log.blank();
|
|
8016
8227
|
return;
|
|
8017
8228
|
}
|
|
8018
|
-
|
|
8019
|
-
|
|
8020
|
-
|
|
8021
|
-
|
|
8022
|
-
|
|
8229
|
+
applyCopy(plan);
|
|
8230
|
+
const modelPolicyPath = join17(cwd, ".cdd", "model-policy.json");
|
|
8231
|
+
if (existsSync15(modelPolicyPath)) {
|
|
8232
|
+
let existing = {};
|
|
8233
|
+
try {
|
|
8234
|
+
existing = JSON.parse(readFileSync12(modelPolicyPath, "utf8"));
|
|
8235
|
+
} catch {
|
|
8236
|
+
}
|
|
8237
|
+
const merged = {
|
|
8238
|
+
...existing,
|
|
8239
|
+
provider,
|
|
8240
|
+
generated_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
8241
|
+
roles: existing.roles && typeof existing.roles === "object" ? existing.roles : {}
|
|
8242
|
+
};
|
|
8243
|
+
writeFileSync7(modelPolicyPath, JSON.stringify(merged, null, 2) + "\n", "utf8");
|
|
8023
8244
|
}
|
|
8024
|
-
|
|
8025
|
-
|
|
8026
|
-
|
|
8027
|
-
|
|
8028
|
-
|
|
8245
|
+
log.blank();
|
|
8246
|
+
log.ok(`Upgrade complete: ${plan.length} missing file(s) added.`);
|
|
8247
|
+
log.info("Existing project guidance and contracts were preserved.");
|
|
8248
|
+
if (opts.migrateChanges) {
|
|
8249
|
+
log.blank();
|
|
8250
|
+
log.info("Running change migration flow...");
|
|
8251
|
+
await migrate(void 0, {
|
|
8252
|
+
all: true,
|
|
8253
|
+
dryRun: false,
|
|
8254
|
+
enableContextGovernance: opts.enableContextGovernance
|
|
8255
|
+
});
|
|
8029
8256
|
}
|
|
8030
|
-
|
|
8031
|
-
|
|
8032
|
-
|
|
8257
|
+
log.blank();
|
|
8258
|
+
}
|
|
8259
|
+
var init_upgrade = __esm({
|
|
8260
|
+
"src/commands/upgrade.ts"() {
|
|
8261
|
+
"use strict";
|
|
8262
|
+
init_paths();
|
|
8263
|
+
init_logger();
|
|
8264
|
+
init_provider();
|
|
8265
|
+
init_migrate();
|
|
8033
8266
|
}
|
|
8034
|
-
|
|
8035
|
-
|
|
8036
|
-
|
|
8037
|
-
|
|
8038
|
-
|
|
8039
|
-
|
|
8040
|
-
|
|
8267
|
+
});
|
|
8268
|
+
|
|
8269
|
+
// src/code-map/yaml-writer.ts
|
|
8270
|
+
function quoteScalar(s) {
|
|
8271
|
+
if (s.startsWith(".") || YAML_RESERVED.test(s) || s.includes(" ") || s === "" || s === "null" || s === "true" || s === "false") {
|
|
8272
|
+
return `'${s.replace(/'/g, "''")}'`;
|
|
8273
|
+
}
|
|
8274
|
+
return s;
|
|
8275
|
+
}
|
|
8276
|
+
function quotePath(p) {
|
|
8277
|
+
if (YAML_RESERVED.test(p) || p.includes(" ")) {
|
|
8278
|
+
return `'${p.replace(/'/g, "''")}'`;
|
|
8279
|
+
}
|
|
8280
|
+
return p;
|
|
8281
|
+
}
|
|
8282
|
+
function renderItems(items) {
|
|
8283
|
+
if (items.length === 0)
|
|
8284
|
+
return "[]";
|
|
8285
|
+
return `[${items.map(quoteScalar).join(", ")}]`;
|
|
8286
|
+
}
|
|
8287
|
+
function truncateDecorator(d, max = 80) {
|
|
8288
|
+
const cleaned = d.replace(/\r?\n/g, " ");
|
|
8289
|
+
return cleaned.length <= max ? cleaned : cleaned.slice(0, max) + "...";
|
|
8290
|
+
}
|
|
8291
|
+
function renderClass(c) {
|
|
8292
|
+
const lines = [];
|
|
8293
|
+
lines.push(` - name: ${c.name}`);
|
|
8294
|
+
lines.push(` lines: ${c.lines[0]}-${c.lines[1]}`);
|
|
8295
|
+
if (c.methods.length > 0) {
|
|
8296
|
+
lines.push(" methods:");
|
|
8297
|
+
for (const m of c.methods) {
|
|
8298
|
+
const asyncPrefix = m.async ? "async " : "";
|
|
8299
|
+
lines.push(` - { name: ${asyncPrefix}${m.name}, lines: ${m.lines[0]}-${m.lines[1]} }`);
|
|
8041
8300
|
}
|
|
8042
|
-
|
|
8043
|
-
|
|
8044
|
-
|
|
8045
|
-
|
|
8046
|
-
|
|
8301
|
+
}
|
|
8302
|
+
return lines;
|
|
8303
|
+
}
|
|
8304
|
+
function renderFunction(f) {
|
|
8305
|
+
const asyncPrefix = f.async ? "async " : "";
|
|
8306
|
+
let comment = "";
|
|
8307
|
+
if (f.decorators.length > 0) {
|
|
8308
|
+
const truncated = truncateDecorator(f.decorators[0]);
|
|
8309
|
+
comment = ` # @${truncated}`;
|
|
8310
|
+
}
|
|
8311
|
+
return ` - { name: ${asyncPrefix}${f.name}, lines: ${f.lines[0]}-${f.lines[1]} }${comment}`;
|
|
8312
|
+
}
|
|
8313
|
+
function renderTypeDef(t) {
|
|
8314
|
+
const exportedSuffix = t.exported ? "" : " # local";
|
|
8315
|
+
return ` - { name: ${t.name}, lines: ${t.lines[0]}-${t.lines[1]} }${exportedSuffix}`;
|
|
8316
|
+
}
|
|
8317
|
+
function renderEnum(e) {
|
|
8318
|
+
const lines = [];
|
|
8319
|
+
const exportedSuffix = e.exported ? "" : " # local";
|
|
8320
|
+
lines.push(` - name: ${e.name}`);
|
|
8321
|
+
lines.push(` lines: ${e.lines[0]}-${e.lines[1]}${exportedSuffix}`);
|
|
8322
|
+
if (e.members.length > 0) {
|
|
8323
|
+
lines.push(` members: [${e.members.join(", ")}]`);
|
|
8324
|
+
}
|
|
8325
|
+
return lines;
|
|
8326
|
+
}
|
|
8327
|
+
function renderYaml(entries, opts) {
|
|
8328
|
+
const now = (/* @__PURE__ */ new Date()).toISOString().replace(/\.\d+Z$/, "Z");
|
|
8329
|
+
const totalSrc = entries.reduce((s, e) => s + e.total_lines, 0);
|
|
8330
|
+
const bodyLines = [];
|
|
8331
|
+
for (let i = 0; i < entries.length; i++) {
|
|
8332
|
+
const e = entries[i];
|
|
8333
|
+
if (i > 0)
|
|
8334
|
+
bodyLines.push("");
|
|
8335
|
+
const pathKey = quotePath(e.path);
|
|
8336
|
+
bodyLines.push(`${pathKey}: # ${e.total_lines} lines`);
|
|
8337
|
+
if (e.imports.length > 0) {
|
|
8338
|
+
bodyLines.push(" imports:");
|
|
8339
|
+
for (const imp of e.imports) {
|
|
8340
|
+
const mod = quoteScalar(imp.module);
|
|
8341
|
+
bodyLines.push(` - { module: ${mod}, items: ${renderItems(imp.items)}, line: ${imp.line} }`);
|
|
8342
|
+
}
|
|
8047
8343
|
}
|
|
8048
|
-
if (
|
|
8049
|
-
|
|
8050
|
-
|
|
8051
|
-
|
|
8052
|
-
|
|
8344
|
+
if (e.constants.length > 0) {
|
|
8345
|
+
bodyLines.push(" constants:");
|
|
8346
|
+
for (const c of e.constants) {
|
|
8347
|
+
bodyLines.push(` - { name: ${c.name}, line: ${c.line} }`);
|
|
8348
|
+
}
|
|
8349
|
+
}
|
|
8350
|
+
if (e.classes.length > 0) {
|
|
8351
|
+
bodyLines.push(" classes:");
|
|
8352
|
+
for (const c of e.classes) {
|
|
8353
|
+
bodyLines.push(...renderClass(c));
|
|
8354
|
+
}
|
|
8355
|
+
}
|
|
8356
|
+
if (e.functions.length > 0) {
|
|
8357
|
+
bodyLines.push(" functions:");
|
|
8358
|
+
for (const f of e.functions) {
|
|
8359
|
+
bodyLines.push(renderFunction(f));
|
|
8360
|
+
}
|
|
8361
|
+
}
|
|
8362
|
+
if (e.interfaces && e.interfaces.length > 0) {
|
|
8363
|
+
bodyLines.push(" interfaces:");
|
|
8364
|
+
for (const t of e.interfaces) {
|
|
8365
|
+
bodyLines.push(renderTypeDef(t));
|
|
8366
|
+
}
|
|
8367
|
+
}
|
|
8368
|
+
if (e.types && e.types.length > 0) {
|
|
8369
|
+
bodyLines.push(" types:");
|
|
8370
|
+
for (const t of e.types) {
|
|
8371
|
+
bodyLines.push(renderTypeDef(t));
|
|
8372
|
+
}
|
|
8373
|
+
}
|
|
8374
|
+
if (e.enums && e.enums.length > 0) {
|
|
8375
|
+
bodyLines.push(" enums:");
|
|
8376
|
+
for (const en of e.enums) {
|
|
8377
|
+
bodyLines.push(...renderEnum(en));
|
|
8378
|
+
}
|
|
8053
8379
|
}
|
|
8054
8380
|
}
|
|
8055
|
-
|
|
8056
|
-
|
|
8057
|
-
|
|
8058
|
-
|
|
8059
|
-
|
|
8060
|
-
|
|
8381
|
+
const mapLines = bodyLines.length + 2;
|
|
8382
|
+
const compression = totalSrc === 0 ? 0 : totalSrc / mapLines;
|
|
8383
|
+
const fileCount = entries.length;
|
|
8384
|
+
const header = [
|
|
8385
|
+
`# generated: ${now} by ${opts.generator}`,
|
|
8386
|
+
`# files: ${fileCount}, src-lines: ${totalSrc}, map-lines: ${mapLines}, compression: ${compression.toFixed(1)}x`
|
|
8387
|
+
];
|
|
8388
|
+
return [...header, ...bodyLines].join("\n") + "\n";
|
|
8389
|
+
}
|
|
8390
|
+
var YAML_RESERVED;
|
|
8391
|
+
var init_yaml_writer = __esm({
|
|
8392
|
+
"src/code-map/yaml-writer.ts"() {
|
|
8393
|
+
"use strict";
|
|
8394
|
+
YAML_RESERVED = /[:[\]{},&*!|>'"%@`#]/;
|
|
8395
|
+
}
|
|
8396
|
+
});
|
|
8397
|
+
|
|
8398
|
+
// src/code-map/orchestrator.ts
|
|
8399
|
+
async function scanInProcess(scanner, absolutePaths, repoRoot) {
|
|
8400
|
+
const entries = [];
|
|
8401
|
+
const warnings = [];
|
|
8402
|
+
for (const absPath of absolutePaths) {
|
|
8403
|
+
try {
|
|
8404
|
+
const entry = await scanner.scan(absPath, repoRoot);
|
|
8405
|
+
if (entry === null) {
|
|
8406
|
+
const rel = absPath.replace(/\\/g, "/").replace(repoRoot.replace(/\\/g, "/") + "/", "");
|
|
8407
|
+
warnings.push({ path: rel, message: "parse error (scanner returned null)" });
|
|
8408
|
+
} else {
|
|
8409
|
+
entries.push(entry);
|
|
8061
8410
|
}
|
|
8411
|
+
} catch (err) {
|
|
8412
|
+
const rel = absPath.replace(/\\/g, "/").replace(repoRoot.replace(/\\/g, "/") + "/", "");
|
|
8413
|
+
warnings.push({ path: rel, message: `IO error: ${err.message}` });
|
|
8062
8414
|
}
|
|
8063
8415
|
}
|
|
8416
|
+
return { entries, warnings };
|
|
8064
8417
|
}
|
|
8065
|
-
function
|
|
8066
|
-
|
|
8067
|
-
|
|
8068
|
-
|
|
8069
|
-
|
|
8070
|
-
|
|
8418
|
+
function bucketByExtension(files) {
|
|
8419
|
+
const out = {};
|
|
8420
|
+
for (const f of files) {
|
|
8421
|
+
const dot = f.lastIndexOf(".");
|
|
8422
|
+
const ext = dot >= 0 ? f.slice(dot).toLowerCase() : "";
|
|
8423
|
+
if (!out[ext])
|
|
8424
|
+
out[ext] = [];
|
|
8425
|
+
out[ext].push(f);
|
|
8071
8426
|
}
|
|
8072
|
-
|
|
8073
|
-
|
|
8074
|
-
|
|
8075
|
-
|
|
8076
|
-
|
|
8077
|
-
|
|
8078
|
-
types: [],
|
|
8079
|
-
enums: []
|
|
8080
|
-
};
|
|
8081
|
-
for (const stmt of ast.program.body) {
|
|
8082
|
-
processStatement(stmt, buckets, !!opts.extractTsTypes);
|
|
8427
|
+
return out;
|
|
8428
|
+
}
|
|
8429
|
+
var init_orchestrator = __esm({
|
|
8430
|
+
"src/code-map/orchestrator.ts"() {
|
|
8431
|
+
"use strict";
|
|
8432
|
+
init_include_exclude();
|
|
8083
8433
|
}
|
|
8084
|
-
|
|
8434
|
+
});
|
|
8435
|
+
|
|
8436
|
+
// src/code-map/scanners/common.ts
|
|
8437
|
+
import { relative as relative4 } from "path";
|
|
8438
|
+
function canonicalRelPath(absolutePath, repoRoot) {
|
|
8439
|
+
const rel = relative4(repoRoot, absolutePath);
|
|
8440
|
+
return rel.replace(/\\/g, "/").normalize("NFC");
|
|
8085
8441
|
}
|
|
8086
|
-
function
|
|
8087
|
-
|
|
8088
|
-
return 0;
|
|
8089
|
-
return source.split(/\r?\n/).length - (source.endsWith("\n") || source.endsWith("\r\n") ? 1 : 0);
|
|
8442
|
+
function isAllCapsConst(name) {
|
|
8443
|
+
return name.length >= 2 && /^[A-Z][A-Z0-9_]*$/.test(name);
|
|
8090
8444
|
}
|
|
8091
|
-
function
|
|
8092
|
-
const
|
|
8093
|
-
|
|
8094
|
-
if (!r)
|
|
8095
|
-
return null;
|
|
8096
|
-
return {
|
|
8097
|
-
path: relPath,
|
|
8098
|
-
total_lines,
|
|
8099
|
-
imports: r.imports,
|
|
8100
|
-
constants: r.constants,
|
|
8101
|
-
classes: r.classes,
|
|
8102
|
-
functions: r.functions
|
|
8103
|
-
};
|
|
8445
|
+
function isBinary(content) {
|
|
8446
|
+
const head = content.slice(0, 4096);
|
|
8447
|
+
return head.includes("\0");
|
|
8104
8448
|
}
|
|
8105
|
-
var
|
|
8106
|
-
|
|
8107
|
-
"src/code-map/scanners/javascript.ts"() {
|
|
8449
|
+
var init_common = __esm({
|
|
8450
|
+
"src/code-map/scanners/common.ts"() {
|
|
8108
8451
|
"use strict";
|
|
8109
|
-
init_common();
|
|
8110
|
-
COMMON_PLUGINS = [
|
|
8111
|
-
"classProperties",
|
|
8112
|
-
"classPrivateProperties",
|
|
8113
|
-
"classPrivateMethods",
|
|
8114
|
-
"decorators-legacy",
|
|
8115
|
-
"topLevelAwait",
|
|
8116
|
-
"dynamicImport",
|
|
8117
|
-
"optionalChaining",
|
|
8118
|
-
"nullishCoalescingOperator",
|
|
8119
|
-
"objectRestSpread",
|
|
8120
|
-
"asyncGenerators",
|
|
8121
|
-
"numericSeparator",
|
|
8122
|
-
"logicalAssignment"
|
|
8123
|
-
];
|
|
8124
|
-
JS_PLUGINS = ["jsx", ...COMMON_PLUGINS];
|
|
8125
|
-
JavaScriptScanner = class {
|
|
8126
|
-
extensions = [".js"];
|
|
8127
|
-
async scan(absolutePath, repoRoot) {
|
|
8128
|
-
let source;
|
|
8129
|
-
try {
|
|
8130
|
-
source = readFileSync12(absolutePath, "utf8");
|
|
8131
|
-
} catch (err) {
|
|
8132
|
-
throw err;
|
|
8133
|
-
}
|
|
8134
|
-
if (isBinary(source)) {
|
|
8135
|
-
return null;
|
|
8136
|
-
}
|
|
8137
|
-
const relPath = canonicalRelPath(absolutePath, repoRoot);
|
|
8138
|
-
return parseJsSource(source, relPath);
|
|
8139
|
-
}
|
|
8140
|
-
};
|
|
8141
|
-
jsScanner = new JavaScriptScanner();
|
|
8142
8452
|
}
|
|
8143
8453
|
});
|
|
8144
8454
|
|
|
8145
|
-
// src/code-map/scanners/
|
|
8146
|
-
var
|
|
8147
|
-
__export(
|
|
8148
|
-
|
|
8455
|
+
// src/code-map/scanners/python.ts
|
|
8456
|
+
var python_exports = {};
|
|
8457
|
+
__export(python_exports, {
|
|
8458
|
+
pythonScanner: () => pythonScanner
|
|
8149
8459
|
});
|
|
8150
|
-
import {
|
|
8151
|
-
|
|
8152
|
-
|
|
8153
|
-
|
|
8460
|
+
import { spawnSync as spawnSync2 } from "child_process";
|
|
8461
|
+
import { writeFileSync as writeFileSync8, unlinkSync } from "fs";
|
|
8462
|
+
import { join as join18 } from "path";
|
|
8463
|
+
import { tmpdir } from "os";
|
|
8464
|
+
function detectPython2() {
|
|
8465
|
+
for (const candidate of ["python3", "python"]) {
|
|
8466
|
+
try {
|
|
8467
|
+
const result = spawnSync2(candidate, ["--version"], {
|
|
8468
|
+
encoding: "utf8",
|
|
8469
|
+
timeout: 5e3
|
|
8470
|
+
});
|
|
8471
|
+
if (result.status === 0)
|
|
8472
|
+
return candidate;
|
|
8473
|
+
} catch {
|
|
8474
|
+
}
|
|
8475
|
+
}
|
|
8476
|
+
return null;
|
|
8477
|
+
}
|
|
8478
|
+
var TIMEOUT_MS, PythonScanner, pythonScanner;
|
|
8479
|
+
var init_python = __esm({
|
|
8480
|
+
"src/code-map/scanners/python.ts"() {
|
|
8154
8481
|
"use strict";
|
|
8482
|
+
init_paths();
|
|
8155
8483
|
init_common();
|
|
8156
|
-
|
|
8157
|
-
|
|
8158
|
-
extensions = [".
|
|
8159
|
-
|
|
8160
|
-
|
|
8161
|
-
|
|
8162
|
-
|
|
8163
|
-
} catch (err) {
|
|
8164
|
-
throw err;
|
|
8165
|
-
}
|
|
8166
|
-
if (isBinary(source)) {
|
|
8167
|
-
return null;
|
|
8484
|
+
TIMEOUT_MS = parseInt(process.env["CDD_CODE_MAP_TIMEOUT_MS"] ?? "30000", 10);
|
|
8485
|
+
PythonScanner = class {
|
|
8486
|
+
extensions = [".py"];
|
|
8487
|
+
_interpreter = void 0;
|
|
8488
|
+
getInterpreter() {
|
|
8489
|
+
if (this._interpreter === void 0) {
|
|
8490
|
+
this._interpreter = detectPython2();
|
|
8168
8491
|
}
|
|
8169
|
-
|
|
8170
|
-
const plugins = isTsx ? ["typescript", "jsx", ...COMMON_PLUGINS] : ["typescript", ...COMMON_PLUGINS];
|
|
8171
|
-
const r = parseAndExtract(source, { plugins, extractTsTypes: true });
|
|
8172
|
-
if (!r)
|
|
8173
|
-
return null;
|
|
8174
|
-
const relPath = canonicalRelPath(absolutePath, repoRoot);
|
|
8175
|
-
const total_lines = countSourceLines(source);
|
|
8176
|
-
return {
|
|
8177
|
-
path: relPath,
|
|
8178
|
-
total_lines,
|
|
8179
|
-
imports: r.imports,
|
|
8180
|
-
constants: r.constants,
|
|
8181
|
-
classes: r.classes,
|
|
8182
|
-
functions: r.functions,
|
|
8183
|
-
interfaces: r.interfaces,
|
|
8184
|
-
types: r.types,
|
|
8185
|
-
enums: r.enums
|
|
8186
|
-
};
|
|
8492
|
+
return this._interpreter;
|
|
8187
8493
|
}
|
|
8188
|
-
};
|
|
8189
|
-
tsScanner = new TypeScriptScanner();
|
|
8190
|
-
}
|
|
8191
|
-
});
|
|
8192
|
-
|
|
8193
|
-
// src/code-map/scanners/vue.ts
|
|
8194
|
-
var vue_exports = {};
|
|
8195
|
-
__export(vue_exports, {
|
|
8196
|
-
vueScanner: () => vueScanner
|
|
8197
|
-
});
|
|
8198
|
-
import { readFileSync as readFileSync14 } from "fs";
|
|
8199
|
-
import { parse as parse2 } from "@vue/compiler-sfc";
|
|
8200
|
-
var VueScanner, vueScanner;
|
|
8201
|
-
var init_vue = __esm({
|
|
8202
|
-
"src/code-map/scanners/vue.ts"() {
|
|
8203
|
-
"use strict";
|
|
8204
|
-
init_common();
|
|
8205
|
-
init_javascript();
|
|
8206
|
-
VueScanner = class {
|
|
8207
|
-
extensions = [".vue"];
|
|
8208
8494
|
async scan(absolutePath, repoRoot) {
|
|
8209
|
-
|
|
8210
|
-
|
|
8211
|
-
|
|
8212
|
-
|
|
8213
|
-
|
|
8214
|
-
|
|
8215
|
-
if (isBinary(source)) {
|
|
8216
|
-
return null;
|
|
8217
|
-
}
|
|
8218
|
-
const relPath = canonicalRelPath(absolutePath, repoRoot);
|
|
8219
|
-
const total_lines = source.split(/\r?\n/).length - (source.endsWith("\n") || source.endsWith("\r\n") ? 1 : 0);
|
|
8220
|
-
const { descriptor } = parse2(source, { filename: absolutePath });
|
|
8221
|
-
const allImports = [];
|
|
8222
|
-
const allConstants = [];
|
|
8223
|
-
const allFunctions = [];
|
|
8224
|
-
const allClasses = [];
|
|
8495
|
+
const result = await this.scanBatch([absolutePath], repoRoot);
|
|
8496
|
+
return result.entries[0] ?? null;
|
|
8497
|
+
}
|
|
8498
|
+
async scanBatch(absolutePaths, repoRoot) {
|
|
8499
|
+
const interpreter = this.getInterpreter();
|
|
8500
|
+
const entries = [];
|
|
8225
8501
|
const warnings = [];
|
|
8226
|
-
|
|
8227
|
-
|
|
8228
|
-
|
|
8229
|
-
path:
|
|
8230
|
-
|
|
8231
|
-
|
|
8232
|
-
|
|
8233
|
-
classes: [],
|
|
8234
|
-
functions: []
|
|
8235
|
-
};
|
|
8502
|
+
if (!interpreter) {
|
|
8503
|
+
const count = absolutePaths.length;
|
|
8504
|
+
warnings.push({
|
|
8505
|
+
path: "",
|
|
8506
|
+
message: `python interpreter not found on PATH; skipping ${count} .py file${count === 1 ? "" : "s"}`
|
|
8507
|
+
});
|
|
8508
|
+
return { entries, warnings };
|
|
8236
8509
|
}
|
|
8237
|
-
|
|
8238
|
-
|
|
8239
|
-
|
|
8240
|
-
|
|
8510
|
+
const scriptPath = ASSET.codeMapPython;
|
|
8511
|
+
const rand = Math.random().toString(36).slice(2);
|
|
8512
|
+
const listFile = join18(tmpdir(), `cdd-codemap-${process.pid}-${rand}.txt`);
|
|
8513
|
+
writeFileSync8(listFile, absolutePaths.join("\n") + "\n", "utf8");
|
|
8514
|
+
let stdout = "";
|
|
8515
|
+
let stderr = "";
|
|
8516
|
+
let exitCode = 0;
|
|
8517
|
+
try {
|
|
8518
|
+
const result = spawnSync2(
|
|
8519
|
+
interpreter,
|
|
8520
|
+
[scriptPath, "--batch-file", listFile, "--repo-root", repoRoot],
|
|
8521
|
+
{
|
|
8522
|
+
encoding: "utf8",
|
|
8523
|
+
timeout: TIMEOUT_MS,
|
|
8524
|
+
maxBuffer: 50 * 1024 * 1024
|
|
8525
|
+
// 50MB
|
|
8526
|
+
}
|
|
8527
|
+
);
|
|
8528
|
+
stdout = result.stdout ?? "";
|
|
8529
|
+
stderr = result.stderr ?? "";
|
|
8530
|
+
exitCode = result.status ?? -1;
|
|
8531
|
+
if (result.error) {
|
|
8532
|
+
const errMsg = result.error.message ?? String(result.error);
|
|
8533
|
+
if (errMsg.includes("ENOENT")) {
|
|
8534
|
+
warnings.push({
|
|
8535
|
+
path: "",
|
|
8536
|
+
message: `python interpreter not found (ENOENT); skipping ${absolutePaths.length} .py file(s)`
|
|
8537
|
+
});
|
|
8538
|
+
return { entries, warnings };
|
|
8539
|
+
}
|
|
8540
|
+
if (errMsg.includes("ETIMEDOUT") || errMsg.includes("timeout")) {
|
|
8541
|
+
warnings.push({
|
|
8542
|
+
path: "",
|
|
8543
|
+
message: `python scanner timed out after ${TIMEOUT_MS}ms; skipping .py files`
|
|
8544
|
+
});
|
|
8545
|
+
return { entries, warnings };
|
|
8546
|
+
}
|
|
8241
8547
|
warnings.push({
|
|
8242
|
-
path:
|
|
8243
|
-
message: `
|
|
8548
|
+
path: "",
|
|
8549
|
+
message: `python scanner error: ${errMsg}; skipping .py files`
|
|
8244
8550
|
});
|
|
8245
|
-
|
|
8246
|
-
}
|
|
8247
|
-
const blockContent = block.content;
|
|
8248
|
-
const lineOffset = block.loc.start.line;
|
|
8249
|
-
const scriptEntry = parseJsSource(blockContent, relPath);
|
|
8250
|
-
if (!scriptEntry) {
|
|
8251
|
-
warnings.push({ path: relPath, message: "parse error in script block" });
|
|
8252
|
-
continue;
|
|
8551
|
+
return { entries, warnings };
|
|
8253
8552
|
}
|
|
8254
|
-
|
|
8255
|
-
|
|
8256
|
-
|
|
8553
|
+
} finally {
|
|
8554
|
+
try {
|
|
8555
|
+
unlinkSync(listFile);
|
|
8556
|
+
} catch {
|
|
8257
8557
|
}
|
|
8258
|
-
|
|
8259
|
-
|
|
8558
|
+
}
|
|
8559
|
+
if (exitCode === 3) {
|
|
8560
|
+
warnings.push({
|
|
8561
|
+
path: "",
|
|
8562
|
+
message: "python interpreter is < 3.9 (need ast.unparse); skipping .py files"
|
|
8563
|
+
});
|
|
8564
|
+
return { entries, warnings };
|
|
8565
|
+
}
|
|
8566
|
+
for (const line of stdout.split("\n")) {
|
|
8567
|
+
const trimmed = line.trim();
|
|
8568
|
+
if (!trimmed)
|
|
8569
|
+
continue;
|
|
8570
|
+
let parsed;
|
|
8571
|
+
try {
|
|
8572
|
+
parsed = JSON.parse(trimmed);
|
|
8573
|
+
} catch {
|
|
8574
|
+
continue;
|
|
8260
8575
|
}
|
|
8261
|
-
|
|
8262
|
-
|
|
8263
|
-
|
|
8264
|
-
lines: [fn.lines[0] + offset, fn.lines[1] + offset]
|
|
8265
|
-
});
|
|
8576
|
+
if (!parsed.ok) {
|
|
8577
|
+
warnings.push({ path: parsed.path, message: parsed.error });
|
|
8578
|
+
continue;
|
|
8266
8579
|
}
|
|
8267
|
-
|
|
8268
|
-
|
|
8269
|
-
|
|
8270
|
-
|
|
8271
|
-
|
|
8272
|
-
|
|
8273
|
-
|
|
8580
|
+
const r = parsed;
|
|
8581
|
+
entries.push({
|
|
8582
|
+
path: canonicalRelPath(join18(repoRoot, r.path), repoRoot),
|
|
8583
|
+
total_lines: r.total_lines,
|
|
8584
|
+
imports: r.imports ?? [],
|
|
8585
|
+
constants: r.constants ?? [],
|
|
8586
|
+
classes: (r.classes ?? []).map((c) => ({
|
|
8587
|
+
name: c.name,
|
|
8588
|
+
lines: [c.lines[0], c.lines[1]],
|
|
8589
|
+
methods: (c.methods ?? []).map((m) => ({
|
|
8590
|
+
name: m.name,
|
|
8591
|
+
lines: [m.lines[0], m.lines[1]],
|
|
8592
|
+
async: m.async
|
|
8274
8593
|
}))
|
|
8275
|
-
})
|
|
8276
|
-
|
|
8594
|
+
})),
|
|
8595
|
+
functions: (r.functions ?? []).map((f) => ({
|
|
8596
|
+
name: f.name,
|
|
8597
|
+
lines: [f.lines[0], f.lines[1]],
|
|
8598
|
+
decorators: f.decorators ?? [],
|
|
8599
|
+
async: f.async
|
|
8600
|
+
}))
|
|
8601
|
+
});
|
|
8277
8602
|
}
|
|
8278
|
-
|
|
8279
|
-
|
|
8280
|
-
|
|
8281
|
-
|
|
8282
|
-
|
|
8283
|
-
|
|
8284
|
-
|
|
8285
|
-
};
|
|
8603
|
+
if (exitCode === 2) {
|
|
8604
|
+
warnings.push({
|
|
8605
|
+
path: "",
|
|
8606
|
+
message: `python scanner exited with code 2 (fatal); partial results only. stderr: ${stderr.slice(0, 200)}`
|
|
8607
|
+
});
|
|
8608
|
+
}
|
|
8609
|
+
return { entries, warnings };
|
|
8286
8610
|
}
|
|
8287
8611
|
};
|
|
8288
|
-
|
|
8289
|
-
}
|
|
8290
|
-
});
|
|
8291
|
-
|
|
8292
|
-
// src/commands/code-map.ts
|
|
8293
|
-
var code_map_exports = {};
|
|
8294
|
-
__export(code_map_exports, {
|
|
8295
|
-
codeMap: () => codeMap
|
|
8296
|
-
});
|
|
8297
|
-
import { existsSync as existsSync14, mkdirSync as mkdirSync6, readFileSync as readFileSync15, writeFileSync as writeFileSync7 } from "fs";
|
|
8298
|
-
import { resolve, dirname as dirname4 } from "path";
|
|
8299
|
-
import { createRequire } from "module";
|
|
8300
|
-
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
8301
|
-
import { join as join17 } from "path";
|
|
8302
|
-
async function codeMap(opts) {
|
|
8303
|
-
const root = resolve(process.cwd(), opts.path);
|
|
8304
|
-
const start = Date.now();
|
|
8305
|
-
let cfg;
|
|
8306
|
-
try {
|
|
8307
|
-
cfg = loadCodeMapConfig(root);
|
|
8308
|
-
} catch (err) {
|
|
8309
|
-
log.error(`code-map: ${err.message}`);
|
|
8310
|
-
return 1;
|
|
8311
|
-
}
|
|
8312
|
-
const include = [...cfg.include, ...opts.include];
|
|
8313
|
-
const exclude = [...cfg.exclude, ...opts.exclude];
|
|
8314
|
-
const files = walkRepo(root, { include, exclude });
|
|
8315
|
-
const buckets = bucketByExtension(files);
|
|
8316
|
-
const result = { entries: [], warnings: [] };
|
|
8317
|
-
const tasks = [];
|
|
8318
|
-
if (buckets[".py"]?.length) {
|
|
8319
|
-
const { pythonScanner: pythonScanner2 } = await Promise.resolve().then(() => (init_python(), python_exports));
|
|
8320
|
-
if (pythonScanner2.scanBatch) {
|
|
8321
|
-
tasks.push(pythonScanner2.scanBatch(buckets[".py"], root));
|
|
8322
|
-
}
|
|
8323
|
-
}
|
|
8324
|
-
const jsFiles = [
|
|
8325
|
-
...buckets[".js"] ?? [],
|
|
8326
|
-
...buckets[".jsx"] ?? [],
|
|
8327
|
-
...buckets[".mjs"] ?? [],
|
|
8328
|
-
...buckets[".cjs"] ?? []
|
|
8329
|
-
];
|
|
8330
|
-
if (jsFiles.length) {
|
|
8331
|
-
const { jsScanner: jsScanner2 } = await Promise.resolve().then(() => (init_javascript(), javascript_exports));
|
|
8332
|
-
tasks.push(scanInProcess(jsScanner2, jsFiles, root));
|
|
8333
|
-
}
|
|
8334
|
-
const tsFiles = [
|
|
8335
|
-
...buckets[".ts"] ?? [],
|
|
8336
|
-
...buckets[".tsx"] ?? []
|
|
8337
|
-
];
|
|
8338
|
-
if (tsFiles.length) {
|
|
8339
|
-
const { tsScanner: tsScanner2 } = await Promise.resolve().then(() => (init_typescript(), typescript_exports));
|
|
8340
|
-
tasks.push(scanInProcess(tsScanner2, tsFiles, root));
|
|
8341
|
-
}
|
|
8342
|
-
if (buckets[".vue"]?.length) {
|
|
8343
|
-
const { vueScanner: vueScanner2 } = await Promise.resolve().then(() => (init_vue(), vue_exports));
|
|
8344
|
-
tasks.push(scanInProcess(vueScanner2, buckets[".vue"], root));
|
|
8345
|
-
}
|
|
8346
|
-
for (const r of await Promise.all(tasks)) {
|
|
8347
|
-
result.entries.push(...r.entries);
|
|
8348
|
-
result.warnings.push(...r.warnings);
|
|
8349
|
-
}
|
|
8350
|
-
result.entries.sort((a, b) => a.path < b.path ? -1 : a.path > b.path ? 1 : 0);
|
|
8351
|
-
for (const e of result.entries) {
|
|
8352
|
-
if (e.total_lines > opts.maxLines) {
|
|
8353
|
-
result.warnings.push({
|
|
8354
|
-
path: e.path,
|
|
8355
|
-
message: `file exceeds --max-lines (${e.total_lines} > ${opts.maxLines})`
|
|
8356
|
-
});
|
|
8357
|
-
}
|
|
8358
|
-
}
|
|
8359
|
-
const yamlBody = renderYaml(result.entries, { generator: `cdd-kit ${_pkg.version}` });
|
|
8360
|
-
const totalSrc = result.entries.reduce((s, e) => s + e.total_lines, 0);
|
|
8361
|
-
const mapLines = yamlBody.split("\n").length;
|
|
8362
|
-
const compression = totalSrc === 0 ? 0 : totalSrc / mapLines;
|
|
8363
|
-
const summaryLine = `scanned ${result.entries.length} files, ${totalSrc} src lines -> ${opts.out} (${mapLines} lines, compression ${compression.toFixed(1)}x)`;
|
|
8364
|
-
for (const w of result.warnings) {
|
|
8365
|
-
log.warn(`${w.path}: ${w.message}`);
|
|
8366
|
-
}
|
|
8367
|
-
if (opts.check) {
|
|
8368
|
-
const existing = existsSync14(opts.out) ? readFileSync15(opts.out, "utf8") : "";
|
|
8369
|
-
const normalize = (s) => s.replace(/^# generated: [^\n]+\n/m, "# generated: <normalized>\n");
|
|
8370
|
-
if (normalize(existing) !== normalize(yamlBody)) {
|
|
8371
|
-
log.error(`code-map out of date: ${opts.out} would change. Run \`cdd-kit code-map\` to regenerate.`);
|
|
8372
|
-
return 1;
|
|
8373
|
-
}
|
|
8374
|
-
log.ok(`code-map up to date: ${opts.out}`);
|
|
8375
|
-
return 0;
|
|
8376
|
-
}
|
|
8377
|
-
mkdirSync6(dirname4(opts.out), { recursive: true });
|
|
8378
|
-
writeFileSync7(opts.out, yamlBody, "utf8");
|
|
8379
|
-
log.ok(`${summaryLine} (${Date.now() - start}ms)`);
|
|
8380
|
-
return 0;
|
|
8381
|
-
}
|
|
8382
|
-
var _require, _pkgPath, _pkg;
|
|
8383
|
-
var init_code_map = __esm({
|
|
8384
|
-
"src/commands/code-map.ts"() {
|
|
8385
|
-
"use strict";
|
|
8386
|
-
init_logger();
|
|
8387
|
-
init_yaml_writer();
|
|
8388
|
-
init_orchestrator();
|
|
8389
|
-
init_config();
|
|
8390
|
-
_require = createRequire(import.meta.url);
|
|
8391
|
-
_pkgPath = join17(fileURLToPath2(import.meta.url), "..", "..", "..", "package.json");
|
|
8392
|
-
_pkg = JSON.parse(readFileSync15(_pkgPath, "utf8"));
|
|
8612
|
+
pythonScanner = new PythonScanner();
|
|
8393
8613
|
}
|
|
8394
8614
|
});
|
|
8395
8615
|
|
|
8396
|
-
// src/
|
|
8397
|
-
var
|
|
8398
|
-
__export(
|
|
8399
|
-
|
|
8616
|
+
// src/code-map/scanners/javascript.ts
|
|
8617
|
+
var javascript_exports = {};
|
|
8618
|
+
__export(javascript_exports, {
|
|
8619
|
+
COMMON_PLUGINS: () => COMMON_PLUGINS,
|
|
8620
|
+
countSourceLines: () => countSourceLines,
|
|
8621
|
+
jsScanner: () => jsScanner,
|
|
8622
|
+
parseAndExtract: () => parseAndExtract,
|
|
8623
|
+
parseJsSource: () => parseJsSource,
|
|
8624
|
+
parseSourceWithPlugins: () => parseSourceWithPlugins
|
|
8400
8625
|
});
|
|
8401
|
-
import {
|
|
8402
|
-
import {
|
|
8403
|
-
|
|
8404
|
-
|
|
8405
|
-
|
|
8406
|
-
|
|
8407
|
-
|
|
8408
|
-
|
|
8409
|
-
|
|
8410
|
-
|
|
8411
|
-
const fullPath = join18(dir, entry.name);
|
|
8412
|
-
if (entry.isDirectory())
|
|
8413
|
-
findFiles(fullPath, predicate, found);
|
|
8414
|
-
else if (entry.isFile() && predicate(entry.name))
|
|
8415
|
-
found.push(fullPath);
|
|
8416
|
-
}
|
|
8417
|
-
return found;
|
|
8626
|
+
import { readFileSync as readFileSync14 } from "fs";
|
|
8627
|
+
import { parse } from "@babel/parser";
|
|
8628
|
+
function parseSourceWithPlugins(source, plugins) {
|
|
8629
|
+
return parse(source, {
|
|
8630
|
+
sourceType: "unambiguous",
|
|
8631
|
+
allowReturnOutsideFunction: true,
|
|
8632
|
+
allowAwaitOutsideFunction: true,
|
|
8633
|
+
errorRecovery: true,
|
|
8634
|
+
plugins
|
|
8635
|
+
});
|
|
8418
8636
|
}
|
|
8419
|
-
function
|
|
8420
|
-
|
|
8421
|
-
return
|
|
8422
|
-
|
|
8423
|
-
|
|
8424
|
-
}
|
|
8637
|
+
function isCallExpression(node, callee) {
|
|
8638
|
+
if (!node || node.type !== "CallExpression")
|
|
8639
|
+
return false;
|
|
8640
|
+
const c = node.callee;
|
|
8641
|
+
return c.type === "Identifier" && c.name === callee;
|
|
8425
8642
|
}
|
|
8426
|
-
function
|
|
8427
|
-
|
|
8428
|
-
|
|
8643
|
+
function extractRequireModule(node) {
|
|
8644
|
+
if (!node || node.type !== "CallExpression")
|
|
8645
|
+
return null;
|
|
8646
|
+
const c = node.callee;
|
|
8647
|
+
if (c.type !== "Identifier" || c.name !== "require")
|
|
8648
|
+
return null;
|
|
8649
|
+
const arg = node.arguments[0];
|
|
8650
|
+
if (!arg || arg.type !== "StringLiteral")
|
|
8651
|
+
return null;
|
|
8652
|
+
return arg.value;
|
|
8429
8653
|
}
|
|
8430
|
-
function
|
|
8431
|
-
|
|
8432
|
-
|
|
8433
|
-
|
|
8434
|
-
const out = {};
|
|
8435
|
-
const digestMatch = text.match(/^inputs-digest:\s*([a-f0-9]+)/m);
|
|
8436
|
-
if (digestMatch)
|
|
8437
|
-
out.inputsDigest = digestMatch[1];
|
|
8438
|
-
const missingMatch = text.match(/^missing-summary-count:\s*(\d+)/m);
|
|
8439
|
-
if (missingMatch)
|
|
8440
|
-
out.missingSummary = Number(missingMatch[1]);
|
|
8441
|
-
return out;
|
|
8654
|
+
function getLineRange(node) {
|
|
8655
|
+
const start = node.loc?.start.line ?? 1;
|
|
8656
|
+
const end = node.loc?.end.line ?? start;
|
|
8657
|
+
return [start, end];
|
|
8442
8658
|
}
|
|
8443
|
-
function
|
|
8444
|
-
const
|
|
8445
|
-
|
|
8446
|
-
|
|
8447
|
-
|
|
8448
|
-
|
|
8449
|
-
|
|
8450
|
-
|
|
8451
|
-
|
|
8452
|
-
|
|
8453
|
-
|
|
8454
|
-
level: "warning",
|
|
8455
|
-
message: "specs/context indexes are missing; run cdd-kit context-scan before classification"
|
|
8456
|
-
});
|
|
8457
|
-
return findings;
|
|
8458
|
-
}
|
|
8459
|
-
const projectMapMeta = readContextIndexMetadata(projectMap);
|
|
8460
|
-
const contractsIndexMeta = readContextIndexMetadata(contractsIndex);
|
|
8461
|
-
const projectInputDigest = inputDigest([contextPolicy].filter(existsSync15));
|
|
8462
|
-
if (projectMapMeta.inputsDigest === void 0) {
|
|
8463
|
-
findings.push({
|
|
8464
|
-
level: "warning",
|
|
8465
|
-
message: "specs/context/project-map.md was generated by an older cdd-kit (no inputs-digest); re-run cdd-kit context-scan"
|
|
8466
|
-
});
|
|
8467
|
-
} else if (projectInputDigest && projectMapMeta.inputsDigest !== projectInputDigest) {
|
|
8468
|
-
findings.push({
|
|
8469
|
-
level: "warning",
|
|
8470
|
-
message: "specs/context/project-map.md inputs changed (.cdd/context-policy.json); re-run cdd-kit context-scan"
|
|
8471
|
-
});
|
|
8472
|
-
}
|
|
8473
|
-
const contractsInputDigest = inputDigest(contractFiles);
|
|
8474
|
-
if (contractsIndexMeta.inputsDigest === void 0) {
|
|
8475
|
-
findings.push({
|
|
8476
|
-
level: "warning",
|
|
8477
|
-
message: "specs/context/contracts-index.md was generated by an older cdd-kit (no inputs-digest); re-run cdd-kit context-scan"
|
|
8478
|
-
});
|
|
8479
|
-
} else if (contractsInputDigest && contractsIndexMeta.inputsDigest !== contractsInputDigest) {
|
|
8480
|
-
findings.push({
|
|
8481
|
-
level: "warning",
|
|
8482
|
-
message: "specs/context/contracts-index.md inputs changed (contracts/*); re-run cdd-kit context-scan"
|
|
8483
|
-
});
|
|
8484
|
-
}
|
|
8485
|
-
if (contractsIndexMeta.missingSummary !== void 0 && contractsIndexMeta.missingSummary > 0) {
|
|
8486
|
-
findings.push({
|
|
8487
|
-
level: "warning",
|
|
8488
|
-
message: `contracts-index reports ${contractsIndexMeta.missingSummary} contract(s) without deterministic summary metadata`
|
|
8489
|
-
});
|
|
8490
|
-
}
|
|
8491
|
-
if (findings.length === 0) {
|
|
8492
|
-
findings.push({ level: "ok", message: "context indexes are present and fresh" });
|
|
8659
|
+
function processImportDeclaration(node) {
|
|
8660
|
+
const items = [];
|
|
8661
|
+
for (const spec of node.specifiers) {
|
|
8662
|
+
if (spec.type === "ImportDefaultSpecifier") {
|
|
8663
|
+
items.push(`default:${spec.local.name}`);
|
|
8664
|
+
} else if (spec.type === "ImportNamespaceSpecifier") {
|
|
8665
|
+
items.push(`*:${spec.local.name}`);
|
|
8666
|
+
} else if (spec.type === "ImportSpecifier") {
|
|
8667
|
+
const imported = spec.imported;
|
|
8668
|
+
items.push(imported.type === "Identifier" ? imported.name : spec.local.name);
|
|
8669
|
+
}
|
|
8493
8670
|
}
|
|
8494
|
-
return
|
|
8671
|
+
return {
|
|
8672
|
+
module: node.source.value,
|
|
8673
|
+
items,
|
|
8674
|
+
line: node.loc?.start.line ?? 1
|
|
8675
|
+
};
|
|
8495
8676
|
}
|
|
8496
|
-
function
|
|
8497
|
-
|
|
8498
|
-
|
|
8499
|
-
const m = text.match(/^model:\s*(\S+)/m);
|
|
8500
|
-
return m ? m[1] : null;
|
|
8501
|
-
} catch {
|
|
8677
|
+
function processFunctionDeclaration(node, nameOverride) {
|
|
8678
|
+
const name = nameOverride ?? node.id?.name;
|
|
8679
|
+
if (!name)
|
|
8502
8680
|
return null;
|
|
8503
|
-
|
|
8681
|
+
return {
|
|
8682
|
+
name,
|
|
8683
|
+
lines: getLineRange(node),
|
|
8684
|
+
decorators: [],
|
|
8685
|
+
async: node.async
|
|
8686
|
+
};
|
|
8504
8687
|
}
|
|
8505
|
-
function
|
|
8506
|
-
const
|
|
8507
|
-
if (!
|
|
8508
|
-
return
|
|
8509
|
-
|
|
8510
|
-
|
|
8511
|
-
|
|
8512
|
-
|
|
8513
|
-
|
|
8514
|
-
|
|
8515
|
-
|
|
8516
|
-
|
|
8517
|
-
|
|
8518
|
-
|
|
8519
|
-
|
|
8520
|
-
}];
|
|
8521
|
-
}
|
|
8522
|
-
const candidateDirs = [
|
|
8523
|
-
join18(cwd, ".claude", "agents"),
|
|
8524
|
-
process.env.HOME ? join18(process.env.HOME, ".claude", "agents") : "",
|
|
8525
|
-
process.env.USERPROFILE ? join18(process.env.USERPROFILE, ".claude", "agents") : ""
|
|
8526
|
-
].filter((p) => p && existsSync15(p));
|
|
8527
|
-
if (candidateDirs.length === 0)
|
|
8528
|
-
return [];
|
|
8529
|
-
const findings = [];
|
|
8530
|
-
for (const [role, expected] of Object.entries(roles)) {
|
|
8531
|
-
let foundAny = false;
|
|
8532
|
-
for (const dir of candidateDirs) {
|
|
8533
|
-
const path = join18(dir, `${role}.md`);
|
|
8534
|
-
if (!existsSync15(path))
|
|
8535
|
-
continue;
|
|
8536
|
-
foundAny = true;
|
|
8537
|
-
const actual = readAgentModel(path);
|
|
8538
|
-
if (actual && actual !== expected) {
|
|
8539
|
-
findings.push({
|
|
8540
|
-
level: "warning",
|
|
8541
|
-
message: `model-policy drift: ${role} expected ${expected}, agent prompt uses ${actual} (${path})`
|
|
8542
|
-
});
|
|
8688
|
+
function processClassDeclaration(node, nameOverride) {
|
|
8689
|
+
const name = nameOverride ?? node.id?.name;
|
|
8690
|
+
if (!name)
|
|
8691
|
+
return null;
|
|
8692
|
+
const methods = [];
|
|
8693
|
+
for (const member of node.body.body) {
|
|
8694
|
+
if (member.type === "ClassMethod" || member.type === "ClassPrivateMethod") {
|
|
8695
|
+
const m = member;
|
|
8696
|
+
let methodName;
|
|
8697
|
+
if (m.key.type === "Identifier") {
|
|
8698
|
+
methodName = m.key.name;
|
|
8699
|
+
} else if (m.key.type === "PrivateName") {
|
|
8700
|
+
methodName = `#${m.key.id.name}`;
|
|
8701
|
+
} else {
|
|
8702
|
+
methodName = "<computed>";
|
|
8543
8703
|
}
|
|
8704
|
+
methods.push({
|
|
8705
|
+
name: methodName,
|
|
8706
|
+
lines: getLineRange(m),
|
|
8707
|
+
async: m.async
|
|
8708
|
+
});
|
|
8544
8709
|
}
|
|
8545
|
-
if (!foundAny) {
|
|
8546
|
-
}
|
|
8547
|
-
}
|
|
8548
|
-
if (findings.length === 0) {
|
|
8549
|
-
findings.push({ level: "ok", message: "model-policy roles match installed agent prompts" });
|
|
8550
8710
|
}
|
|
8551
|
-
return
|
|
8711
|
+
return {
|
|
8712
|
+
name,
|
|
8713
|
+
lines: getLineRange(node),
|
|
8714
|
+
methods
|
|
8715
|
+
};
|
|
8552
8716
|
}
|
|
8553
|
-
|
|
8554
|
-
const
|
|
8555
|
-
|
|
8556
|
-
|
|
8557
|
-
|
|
8558
|
-
const
|
|
8559
|
-
|
|
8560
|
-
|
|
8561
|
-
|
|
8562
|
-
|
|
8563
|
-
for (const filename of files) {
|
|
8564
|
-
const content = rfs(join18(agentsDir, filename), "utf8");
|
|
8565
|
-
const artifactsSection = content.match(
|
|
8566
|
-
/### Required artifacts for this agent\s*\n[\s\S]*?(?=\n#{2,3} |\n---|\s*$)/
|
|
8567
|
-
)?.[0];
|
|
8568
|
-
if (!artifactsSection || !/```ya?ml\s*\nartifacts:/.test(artifactsSection)) {
|
|
8569
|
-
findings.push({
|
|
8570
|
-
level: "warning",
|
|
8571
|
-
message: `lint-agents: ${filename}: missing artifacts YAML block in Required artifacts section`
|
|
8572
|
-
});
|
|
8573
|
-
}
|
|
8574
|
-
const readScopeCount = (content.match(/^## Read scope\s*$/gm) ?? []).length;
|
|
8575
|
-
if (readScopeCount > 1) {
|
|
8576
|
-
findings.push({
|
|
8577
|
-
level: "warning",
|
|
8578
|
-
message: `lint-agents: ${filename}: duplicate ## Read scope headings (${readScopeCount})`
|
|
8579
|
-
});
|
|
8717
|
+
function processVariableDeclaration(node, imports, constants, functions) {
|
|
8718
|
+
for (const decl of node.declarations) {
|
|
8719
|
+
if (!decl.id || decl.id.type !== "Identifier")
|
|
8720
|
+
continue;
|
|
8721
|
+
const varName = decl.id.name;
|
|
8722
|
+
const init2 = decl.init;
|
|
8723
|
+
if (init2 && isCallExpression(init2, "require")) {
|
|
8724
|
+
const mod = extractRequireModule(init2);
|
|
8725
|
+
if (mod) {
|
|
8726
|
+
imports.push({ module: mod, items: [`default:${varName}`], line: node.loc?.start.line ?? 1 });
|
|
8580
8727
|
}
|
|
8728
|
+
continue;
|
|
8581
8729
|
}
|
|
8582
|
-
if (
|
|
8583
|
-
|
|
8730
|
+
if (isAllCapsConst(varName) && init2 !== null && init2 !== void 0) {
|
|
8731
|
+
constants.push({ name: varName, line: node.loc?.start.line ?? 1 });
|
|
8732
|
+
continue;
|
|
8584
8733
|
}
|
|
8585
|
-
|
|
8586
|
-
|
|
8587
|
-
|
|
8734
|
+
if (init2 && (init2.type === "ArrowFunctionExpression" || init2.type === "FunctionExpression")) {
|
|
8735
|
+
functions.push({
|
|
8736
|
+
name: varName,
|
|
8737
|
+
lines: getLineRange(node),
|
|
8738
|
+
decorators: [],
|
|
8739
|
+
async: init2.async
|
|
8740
|
+
});
|
|
8741
|
+
} else if (init2 && init2.type === "CallExpression" && /^[A-Z]/.test(varName)) {
|
|
8742
|
+
functions.push({
|
|
8743
|
+
name: varName,
|
|
8744
|
+
lines: getLineRange(node),
|
|
8745
|
+
decorators: [],
|
|
8746
|
+
async: false
|
|
8747
|
+
});
|
|
8748
|
+
}
|
|
8749
|
+
}
|
|
8750
|
+
}
|
|
8751
|
+
function processTsInterfaceDeclaration(node, exported) {
|
|
8752
|
+
if (!node || node.type !== "TSInterfaceDeclaration")
|
|
8753
|
+
return null;
|
|
8754
|
+
if (!node.id || node.id.type !== "Identifier")
|
|
8755
|
+
return null;
|
|
8756
|
+
return {
|
|
8757
|
+
name: node.id.name,
|
|
8758
|
+
lines: getLineRange(node),
|
|
8759
|
+
exported
|
|
8760
|
+
};
|
|
8761
|
+
}
|
|
8762
|
+
function processTsTypeAliasDeclaration(node, exported) {
|
|
8763
|
+
if (!node || node.type !== "TSTypeAliasDeclaration")
|
|
8764
|
+
return null;
|
|
8765
|
+
if (!node.id || node.id.type !== "Identifier")
|
|
8766
|
+
return null;
|
|
8767
|
+
return {
|
|
8768
|
+
name: node.id.name,
|
|
8769
|
+
lines: getLineRange(node),
|
|
8770
|
+
exported
|
|
8771
|
+
};
|
|
8772
|
+
}
|
|
8773
|
+
function processTsEnumDeclaration(node, exported) {
|
|
8774
|
+
if (!node || node.type !== "TSEnumDeclaration")
|
|
8775
|
+
return null;
|
|
8776
|
+
if (!node.id || node.id.type !== "Identifier")
|
|
8777
|
+
return null;
|
|
8778
|
+
const members = [];
|
|
8779
|
+
for (const m of node.members ?? []) {
|
|
8780
|
+
if (!m || !m.id)
|
|
8781
|
+
continue;
|
|
8782
|
+
if (m.id.type === "Identifier")
|
|
8783
|
+
members.push(m.id.name);
|
|
8784
|
+
else if (m.id.type === "StringLiteral")
|
|
8785
|
+
members.push(m.id.value);
|
|
8588
8786
|
}
|
|
8787
|
+
return {
|
|
8788
|
+
name: node.id.name,
|
|
8789
|
+
lines: getLineRange(node),
|
|
8790
|
+
exported,
|
|
8791
|
+
members
|
|
8792
|
+
};
|
|
8589
8793
|
}
|
|
8590
|
-
function
|
|
8591
|
-
|
|
8592
|
-
|
|
8593
|
-
|
|
8594
|
-
|
|
8595
|
-
|
|
8596
|
-
|
|
8597
|
-
|
|
8598
|
-
|
|
8794
|
+
function processStatement(stmt, buckets, extractTsTypes, exportedFromWrapper = false) {
|
|
8795
|
+
if (stmt.type === "ExportNamedDeclaration" && stmt.declaration) {
|
|
8796
|
+
processStatement(stmt.declaration, buckets, extractTsTypes, true);
|
|
8797
|
+
return;
|
|
8798
|
+
}
|
|
8799
|
+
if (stmt.type === "ExportDefaultDeclaration") {
|
|
8800
|
+
const decl = stmt.declaration;
|
|
8801
|
+
if (decl.type === "FunctionDeclaration") {
|
|
8802
|
+
const fe = processFunctionDeclaration(decl, decl.id?.name ?? "default");
|
|
8803
|
+
if (fe)
|
|
8804
|
+
buckets.functions.push(fe);
|
|
8805
|
+
} else if (decl.type === "ClassDeclaration") {
|
|
8806
|
+
const ce = processClassDeclaration(decl, decl.id?.name ?? "default");
|
|
8807
|
+
if (ce)
|
|
8808
|
+
buckets.classes.push(ce);
|
|
8599
8809
|
}
|
|
8600
|
-
return
|
|
8810
|
+
return;
|
|
8601
8811
|
}
|
|
8602
|
-
|
|
8603
|
-
|
|
8604
|
-
|
|
8605
|
-
return findings;
|
|
8812
|
+
if (stmt.type === "ImportDeclaration") {
|
|
8813
|
+
buckets.imports.push(processImportDeclaration(stmt));
|
|
8814
|
+
return;
|
|
8606
8815
|
}
|
|
8607
|
-
if (
|
|
8608
|
-
const
|
|
8609
|
-
|
|
8610
|
-
|
|
8816
|
+
if (stmt.type === "FunctionDeclaration") {
|
|
8817
|
+
const fe = processFunctionDeclaration(stmt);
|
|
8818
|
+
if (fe)
|
|
8819
|
+
buckets.functions.push(fe);
|
|
8820
|
+
return;
|
|
8611
8821
|
}
|
|
8612
|
-
|
|
8613
|
-
|
|
8614
|
-
|
|
8615
|
-
|
|
8616
|
-
|
|
8617
|
-
findings.push({ level: "warning", message: ".cdd/code-map.yml has no metadata header (regenerate with cdd-kit 2.0.5+)" });
|
|
8822
|
+
if (stmt.type === "ClassDeclaration") {
|
|
8823
|
+
const ce = processClassDeclaration(stmt);
|
|
8824
|
+
if (ce)
|
|
8825
|
+
buckets.classes.push(ce);
|
|
8826
|
+
return;
|
|
8618
8827
|
}
|
|
8619
|
-
|
|
8620
|
-
|
|
8621
|
-
|
|
8622
|
-
const requestedProvider = opts.provider ?? "auto";
|
|
8623
|
-
if (!validateProviderOption(requestedProvider)) {
|
|
8624
|
-
log.error(`Invalid provider: ${requestedProvider}. Use auto, claude, codex, or both.`);
|
|
8625
|
-
process.exit(1);
|
|
8828
|
+
if (stmt.type === "VariableDeclaration") {
|
|
8829
|
+
processVariableDeclaration(stmt, buckets.imports, buckets.constants, buckets.functions);
|
|
8830
|
+
return;
|
|
8626
8831
|
}
|
|
8627
|
-
|
|
8628
|
-
|
|
8629
|
-
|
|
8630
|
-
|
|
8631
|
-
|
|
8832
|
+
if (extractTsTypes) {
|
|
8833
|
+
const anyStmt = stmt;
|
|
8834
|
+
if (anyStmt.type === "TSInterfaceDeclaration") {
|
|
8835
|
+
const e = processTsInterfaceDeclaration(anyStmt, exportedFromWrapper);
|
|
8836
|
+
if (e)
|
|
8837
|
+
buckets.interfaces.push(e);
|
|
8838
|
+
return;
|
|
8839
|
+
}
|
|
8840
|
+
if (anyStmt.type === "TSTypeAliasDeclaration") {
|
|
8841
|
+
const e = processTsTypeAliasDeclaration(anyStmt, exportedFromWrapper);
|
|
8842
|
+
if (e)
|
|
8843
|
+
buckets.types.push(e);
|
|
8844
|
+
return;
|
|
8845
|
+
}
|
|
8846
|
+
if (anyStmt.type === "TSEnumDeclaration") {
|
|
8847
|
+
const e = processTsEnumDeclaration(anyStmt, exportedFromWrapper);
|
|
8848
|
+
if (e)
|
|
8849
|
+
buckets.enums.push(e);
|
|
8850
|
+
return;
|
|
8851
|
+
}
|
|
8632
8852
|
}
|
|
8633
|
-
if (
|
|
8634
|
-
|
|
8853
|
+
if (stmt.type === "ExpressionStatement") {
|
|
8854
|
+
const expr = stmt.expression;
|
|
8855
|
+
if (isCallExpression(expr, "require")) {
|
|
8856
|
+
const mod = extractRequireModule(expr);
|
|
8857
|
+
if (mod) {
|
|
8858
|
+
buckets.imports.push({ module: mod, items: [], line: stmt.loc?.start.line ?? 1 });
|
|
8859
|
+
}
|
|
8860
|
+
}
|
|
8635
8861
|
}
|
|
8636
|
-
|
|
8637
|
-
|
|
8862
|
+
}
|
|
8863
|
+
function parseAndExtract(source, opts) {
|
|
8864
|
+
let ast;
|
|
8865
|
+
try {
|
|
8866
|
+
ast = parseSourceWithPlugins(source, opts.plugins);
|
|
8867
|
+
} catch {
|
|
8868
|
+
return null;
|
|
8638
8869
|
}
|
|
8639
|
-
|
|
8640
|
-
|
|
8870
|
+
const buckets = {
|
|
8871
|
+
imports: [],
|
|
8872
|
+
constants: [],
|
|
8873
|
+
functions: [],
|
|
8874
|
+
classes: [],
|
|
8875
|
+
interfaces: [],
|
|
8876
|
+
types: [],
|
|
8877
|
+
enums: []
|
|
8878
|
+
};
|
|
8879
|
+
for (const stmt of ast.program.body) {
|
|
8880
|
+
processStatement(stmt, buckets, !!opts.extractTsTypes);
|
|
8641
8881
|
}
|
|
8642
|
-
|
|
8643
|
-
|
|
8644
|
-
|
|
8645
|
-
|
|
8646
|
-
|
|
8647
|
-
|
|
8882
|
+
return buckets;
|
|
8883
|
+
}
|
|
8884
|
+
function countSourceLines(source) {
|
|
8885
|
+
if (source === "")
|
|
8886
|
+
return 0;
|
|
8887
|
+
return source.split(/\r?\n/).length - (source.endsWith("\n") || source.endsWith("\r\n") ? 1 : 0);
|
|
8888
|
+
}
|
|
8889
|
+
function parseJsSource(source, relPath) {
|
|
8890
|
+
const total_lines = countSourceLines(source);
|
|
8891
|
+
const r = parseAndExtract(source, { plugins: JS_PLUGINS, extractTsTypes: false });
|
|
8892
|
+
if (!r)
|
|
8893
|
+
return null;
|
|
8648
8894
|
return {
|
|
8649
|
-
|
|
8650
|
-
|
|
8651
|
-
|
|
8652
|
-
|
|
8653
|
-
|
|
8654
|
-
|
|
8895
|
+
path: relPath,
|
|
8896
|
+
total_lines,
|
|
8897
|
+
imports: r.imports,
|
|
8898
|
+
constants: r.constants,
|
|
8899
|
+
classes: r.classes,
|
|
8900
|
+
functions: r.functions
|
|
8655
8901
|
};
|
|
8656
8902
|
}
|
|
8657
|
-
|
|
8658
|
-
|
|
8659
|
-
|
|
8660
|
-
|
|
8661
|
-
|
|
8662
|
-
|
|
8663
|
-
|
|
8664
|
-
|
|
8665
|
-
|
|
8666
|
-
|
|
8667
|
-
|
|
8668
|
-
|
|
8669
|
-
|
|
8670
|
-
|
|
8671
|
-
|
|
8672
|
-
|
|
8673
|
-
|
|
8903
|
+
var COMMON_PLUGINS, JS_PLUGINS, JavaScriptScanner, jsScanner;
|
|
8904
|
+
var init_javascript = __esm({
|
|
8905
|
+
"src/code-map/scanners/javascript.ts"() {
|
|
8906
|
+
"use strict";
|
|
8907
|
+
init_common();
|
|
8908
|
+
COMMON_PLUGINS = [
|
|
8909
|
+
"classProperties",
|
|
8910
|
+
"classPrivateProperties",
|
|
8911
|
+
"classPrivateMethods",
|
|
8912
|
+
"decorators-legacy",
|
|
8913
|
+
"topLevelAwait",
|
|
8914
|
+
"dynamicImport",
|
|
8915
|
+
"optionalChaining",
|
|
8916
|
+
"nullishCoalescingOperator",
|
|
8917
|
+
"objectRestSpread",
|
|
8918
|
+
"asyncGenerators",
|
|
8919
|
+
"numericSeparator",
|
|
8920
|
+
"logicalAssignment"
|
|
8921
|
+
];
|
|
8922
|
+
JS_PLUGINS = ["jsx", ...COMMON_PLUGINS];
|
|
8923
|
+
JavaScriptScanner = class {
|
|
8924
|
+
extensions = [".js"];
|
|
8925
|
+
async scan(absolutePath, repoRoot) {
|
|
8926
|
+
let source;
|
|
8927
|
+
try {
|
|
8928
|
+
source = readFileSync14(absolutePath, "utf8");
|
|
8929
|
+
} catch (err) {
|
|
8930
|
+
throw err;
|
|
8931
|
+
}
|
|
8932
|
+
if (isBinary(source)) {
|
|
8933
|
+
return null;
|
|
8934
|
+
}
|
|
8935
|
+
const relPath = canonicalRelPath(absolutePath, repoRoot);
|
|
8936
|
+
return parseJsSource(source, relPath);
|
|
8674
8937
|
}
|
|
8675
|
-
}
|
|
8676
|
-
|
|
8677
|
-
|
|
8678
|
-
|
|
8679
|
-
|
|
8680
|
-
|
|
8681
|
-
|
|
8682
|
-
|
|
8683
|
-
|
|
8684
|
-
|
|
8938
|
+
};
|
|
8939
|
+
jsScanner = new JavaScriptScanner();
|
|
8940
|
+
}
|
|
8941
|
+
});
|
|
8942
|
+
|
|
8943
|
+
// src/code-map/scanners/typescript.ts
|
|
8944
|
+
var typescript_exports = {};
|
|
8945
|
+
__export(typescript_exports, {
|
|
8946
|
+
tsScanner: () => tsScanner
|
|
8947
|
+
});
|
|
8948
|
+
import { readFileSync as readFileSync15 } from "fs";
|
|
8949
|
+
var TypeScriptScanner, tsScanner;
|
|
8950
|
+
var init_typescript = __esm({
|
|
8951
|
+
"src/code-map/scanners/typescript.ts"() {
|
|
8952
|
+
"use strict";
|
|
8953
|
+
init_common();
|
|
8954
|
+
init_javascript();
|
|
8955
|
+
TypeScriptScanner = class {
|
|
8956
|
+
extensions = [".ts", ".tsx"];
|
|
8957
|
+
async scan(absolutePath, repoRoot) {
|
|
8958
|
+
let source;
|
|
8959
|
+
try {
|
|
8960
|
+
source = readFileSync15(absolutePath, "utf8");
|
|
8961
|
+
} catch (err) {
|
|
8962
|
+
throw err;
|
|
8963
|
+
}
|
|
8964
|
+
if (isBinary(source)) {
|
|
8965
|
+
return null;
|
|
8966
|
+
}
|
|
8967
|
+
const isTsx = absolutePath.toLowerCase().endsWith(".tsx");
|
|
8968
|
+
const plugins = isTsx ? ["typescript", "jsx", ...COMMON_PLUGINS] : ["typescript", ...COMMON_PLUGINS];
|
|
8969
|
+
const r = parseAndExtract(source, { plugins, extractTsTypes: true });
|
|
8970
|
+
if (!r)
|
|
8971
|
+
return null;
|
|
8972
|
+
const relPath = canonicalRelPath(absolutePath, repoRoot);
|
|
8973
|
+
const total_lines = countSourceLines(source);
|
|
8974
|
+
return {
|
|
8975
|
+
path: relPath,
|
|
8976
|
+
total_lines,
|
|
8977
|
+
imports: r.imports,
|
|
8978
|
+
constants: r.constants,
|
|
8979
|
+
classes: r.classes,
|
|
8980
|
+
functions: r.functions,
|
|
8981
|
+
interfaces: r.interfaces,
|
|
8982
|
+
types: r.types,
|
|
8983
|
+
enums: r.enums
|
|
8984
|
+
};
|
|
8685
8985
|
}
|
|
8686
|
-
}
|
|
8687
|
-
|
|
8688
|
-
|
|
8689
|
-
|
|
8690
|
-
|
|
8986
|
+
};
|
|
8987
|
+
tsScanner = new TypeScriptScanner();
|
|
8988
|
+
}
|
|
8989
|
+
});
|
|
8990
|
+
|
|
8991
|
+
// src/code-map/scanners/vue.ts
|
|
8992
|
+
var vue_exports = {};
|
|
8993
|
+
__export(vue_exports, {
|
|
8994
|
+
vueScanner: () => vueScanner
|
|
8995
|
+
});
|
|
8996
|
+
import { readFileSync as readFileSync16 } from "fs";
|
|
8997
|
+
import { parse as parse2 } from "@vue/compiler-sfc";
|
|
8998
|
+
var VueScanner, vueScanner;
|
|
8999
|
+
var init_vue = __esm({
|
|
9000
|
+
"src/code-map/scanners/vue.ts"() {
|
|
9001
|
+
"use strict";
|
|
9002
|
+
init_common();
|
|
9003
|
+
init_javascript();
|
|
9004
|
+
VueScanner = class {
|
|
9005
|
+
extensions = [".vue"];
|
|
9006
|
+
async scan(absolutePath, repoRoot) {
|
|
9007
|
+
let source;
|
|
8691
9008
|
try {
|
|
8692
|
-
|
|
8693
|
-
} catch {
|
|
9009
|
+
source = readFileSync16(absolutePath, "utf8");
|
|
9010
|
+
} catch (err) {
|
|
9011
|
+
throw err;
|
|
8694
9012
|
}
|
|
8695
|
-
|
|
8696
|
-
|
|
8697
|
-
|
|
8698
|
-
|
|
8699
|
-
|
|
8700
|
-
|
|
8701
|
-
|
|
8702
|
-
|
|
8703
|
-
|
|
8704
|
-
|
|
8705
|
-
|
|
8706
|
-
|
|
8707
|
-
|
|
8708
|
-
|
|
8709
|
-
|
|
8710
|
-
|
|
8711
|
-
|
|
8712
|
-
|
|
8713
|
-
|
|
9013
|
+
if (isBinary(source)) {
|
|
9014
|
+
return null;
|
|
9015
|
+
}
|
|
9016
|
+
const relPath = canonicalRelPath(absolutePath, repoRoot);
|
|
9017
|
+
const total_lines = source.split(/\r?\n/).length - (source.endsWith("\n") || source.endsWith("\r\n") ? 1 : 0);
|
|
9018
|
+
const { descriptor } = parse2(source, { filename: absolutePath });
|
|
9019
|
+
const allImports = [];
|
|
9020
|
+
const allConstants = [];
|
|
9021
|
+
const allFunctions = [];
|
|
9022
|
+
const allClasses = [];
|
|
9023
|
+
const warnings = [];
|
|
9024
|
+
const scriptBlocks = [descriptor.script, descriptor.scriptSetup].filter(Boolean);
|
|
9025
|
+
if (scriptBlocks.length === 0) {
|
|
9026
|
+
return {
|
|
9027
|
+
path: relPath,
|
|
9028
|
+
total_lines,
|
|
9029
|
+
imports: [],
|
|
9030
|
+
constants: [],
|
|
9031
|
+
classes: [],
|
|
9032
|
+
functions: []
|
|
9033
|
+
};
|
|
9034
|
+
}
|
|
9035
|
+
for (const block of scriptBlocks) {
|
|
9036
|
+
if (!block)
|
|
9037
|
+
continue;
|
|
9038
|
+
if (block.lang === "ts" || block.lang === "tsx") {
|
|
9039
|
+
warnings.push({
|
|
9040
|
+
path: relPath,
|
|
9041
|
+
message: `skipping <script lang=${block.lang}> block (TypeScript not supported in v1)`
|
|
9042
|
+
});
|
|
9043
|
+
continue;
|
|
9044
|
+
}
|
|
9045
|
+
const blockContent = block.content;
|
|
9046
|
+
const lineOffset = block.loc.start.line;
|
|
9047
|
+
const scriptEntry = parseJsSource(blockContent, relPath);
|
|
9048
|
+
if (!scriptEntry) {
|
|
9049
|
+
warnings.push({ path: relPath, message: "parse error in script block" });
|
|
9050
|
+
continue;
|
|
9051
|
+
}
|
|
9052
|
+
const offset = lineOffset - 1;
|
|
9053
|
+
for (const imp of scriptEntry.imports) {
|
|
9054
|
+
allImports.push({ ...imp, line: imp.line + offset });
|
|
9055
|
+
}
|
|
9056
|
+
for (const c of scriptEntry.constants) {
|
|
9057
|
+
allConstants.push({ ...c, line: c.line + offset });
|
|
9058
|
+
}
|
|
9059
|
+
for (const fn of scriptEntry.functions) {
|
|
9060
|
+
allFunctions.push({
|
|
9061
|
+
...fn,
|
|
9062
|
+
lines: [fn.lines[0] + offset, fn.lines[1] + offset]
|
|
9063
|
+
});
|
|
9064
|
+
}
|
|
9065
|
+
for (const cls of scriptEntry.classes) {
|
|
9066
|
+
allClasses.push({
|
|
9067
|
+
...cls,
|
|
9068
|
+
lines: [cls.lines[0] + offset, cls.lines[1] + offset],
|
|
9069
|
+
methods: cls.methods.map((m) => ({
|
|
9070
|
+
...m,
|
|
9071
|
+
lines: [m.lines[0] + offset, m.lines[1] + offset]
|
|
9072
|
+
}))
|
|
9073
|
+
});
|
|
8714
9074
|
}
|
|
9075
|
+
}
|
|
9076
|
+
return {
|
|
9077
|
+
path: relPath,
|
|
9078
|
+
total_lines,
|
|
9079
|
+
imports: allImports,
|
|
9080
|
+
constants: allConstants,
|
|
9081
|
+
classes: allClasses,
|
|
9082
|
+
functions: allFunctions
|
|
8715
9083
|
};
|
|
8716
|
-
const { writeFileSync: writeFileSync13 } = await import("fs");
|
|
8717
|
-
writeFileSync13(policyPath, JSON.stringify(merged, null, 2) + "\n", "utf8");
|
|
8718
|
-
fixed.push(`populated .cdd/model-policy.json with default role bindings`);
|
|
8719
|
-
continue;
|
|
8720
|
-
} catch (err) {
|
|
8721
|
-
remaining.push({ level: "warning", message: `${finding.message} (auto-fix failed: ${err.message})` });
|
|
8722
|
-
continue;
|
|
8723
9084
|
}
|
|
8724
|
-
}
|
|
8725
|
-
|
|
8726
|
-
remaining.push({
|
|
8727
|
-
level: "warning",
|
|
8728
|
-
message: `${finding.message} (run \`cdd-kit upgrade --yes\` manually \u2014 too invasive for --fix)`
|
|
8729
|
-
});
|
|
8730
|
-
continue;
|
|
8731
|
-
}
|
|
8732
|
-
remaining.push(finding);
|
|
9085
|
+
};
|
|
9086
|
+
vueScanner = new VueScanner();
|
|
8733
9087
|
}
|
|
8734
|
-
|
|
8735
|
-
|
|
8736
|
-
|
|
8737
|
-
|
|
8738
|
-
|
|
8739
|
-
|
|
8740
|
-
|
|
8741
|
-
|
|
8742
|
-
|
|
8743
|
-
|
|
8744
|
-
|
|
8745
|
-
|
|
8746
|
-
|
|
8747
|
-
|
|
8748
|
-
|
|
9088
|
+
});
|
|
9089
|
+
|
|
9090
|
+
// src/commands/code-map.ts
|
|
9091
|
+
var code_map_exports = {};
|
|
9092
|
+
__export(code_map_exports, {
|
|
9093
|
+
codeMap: () => codeMap
|
|
9094
|
+
});
|
|
9095
|
+
import { existsSync as existsSync16, mkdirSync as mkdirSync8, readFileSync as readFileSync17, writeFileSync as writeFileSync9 } from "fs";
|
|
9096
|
+
import { resolve, dirname as dirname5 } from "path";
|
|
9097
|
+
import { createRequire } from "module";
|
|
9098
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
9099
|
+
import { join as join19 } from "path";
|
|
9100
|
+
async function codeMap(opts) {
|
|
9101
|
+
const root = resolve(process.cwd(), opts.path);
|
|
9102
|
+
const start = Date.now();
|
|
9103
|
+
let cfg;
|
|
9104
|
+
try {
|
|
9105
|
+
cfg = loadCodeMapConfig(root);
|
|
9106
|
+
} catch (err) {
|
|
9107
|
+
log.error(`code-map: ${err.message}`);
|
|
9108
|
+
return 1;
|
|
9109
|
+
}
|
|
9110
|
+
const include = [...cfg.include, ...opts.include];
|
|
9111
|
+
const exclude = [...cfg.exclude, ...opts.exclude];
|
|
9112
|
+
const files = walkRepo(root, { include, exclude });
|
|
9113
|
+
const buckets = bucketByExtension(files);
|
|
9114
|
+
const result = { entries: [], warnings: [] };
|
|
9115
|
+
const tasks = [];
|
|
9116
|
+
if (buckets[".py"]?.length) {
|
|
9117
|
+
const { pythonScanner: pythonScanner2 } = await Promise.resolve().then(() => (init_python(), python_exports));
|
|
9118
|
+
if (pythonScanner2.scanBatch) {
|
|
9119
|
+
tasks.push(pythonScanner2.scanBatch(buckets[".py"], root));
|
|
8749
9120
|
}
|
|
8750
9121
|
}
|
|
8751
|
-
|
|
8752
|
-
|
|
8753
|
-
|
|
8754
|
-
|
|
8755
|
-
|
|
9122
|
+
const jsFiles = [
|
|
9123
|
+
...buckets[".js"] ?? [],
|
|
9124
|
+
...buckets[".jsx"] ?? [],
|
|
9125
|
+
...buckets[".mjs"] ?? [],
|
|
9126
|
+
...buckets[".cjs"] ?? []
|
|
9127
|
+
];
|
|
9128
|
+
if (jsFiles.length) {
|
|
9129
|
+
const { jsScanner: jsScanner2 } = await Promise.resolve().then(() => (init_javascript(), javascript_exports));
|
|
9130
|
+
tasks.push(scanInProcess(jsScanner2, jsFiles, root));
|
|
8756
9131
|
}
|
|
8757
|
-
|
|
8758
|
-
|
|
8759
|
-
|
|
8760
|
-
|
|
8761
|
-
|
|
8762
|
-
|
|
8763
|
-
|
|
8764
|
-
else
|
|
8765
|
-
log.error(finding.message);
|
|
9132
|
+
const tsFiles = [
|
|
9133
|
+
...buckets[".ts"] ?? [],
|
|
9134
|
+
...buckets[".tsx"] ?? []
|
|
9135
|
+
];
|
|
9136
|
+
if (tsFiles.length) {
|
|
9137
|
+
const { tsScanner: tsScanner2 } = await Promise.resolve().then(() => (init_typescript(), typescript_exports));
|
|
9138
|
+
tasks.push(scanInProcess(tsScanner2, tsFiles, root));
|
|
8766
9139
|
}
|
|
8767
|
-
|
|
8768
|
-
|
|
8769
|
-
|
|
8770
|
-
|
|
9140
|
+
if (buckets[".vue"]?.length) {
|
|
9141
|
+
const { vueScanner: vueScanner2 } = await Promise.resolve().then(() => (init_vue(), vue_exports));
|
|
9142
|
+
tasks.push(scanInProcess(vueScanner2, buckets[".vue"], root));
|
|
9143
|
+
}
|
|
9144
|
+
for (const r of await Promise.all(tasks)) {
|
|
9145
|
+
result.entries.push(...r.entries);
|
|
9146
|
+
result.warnings.push(...r.warnings);
|
|
9147
|
+
}
|
|
9148
|
+
result.entries.sort((a, b) => a.path < b.path ? -1 : a.path > b.path ? 1 : 0);
|
|
9149
|
+
for (const e of result.entries) {
|
|
9150
|
+
if (e.total_lines > opts.maxLines) {
|
|
9151
|
+
result.warnings.push({
|
|
9152
|
+
path: e.path,
|
|
9153
|
+
message: `file exceeds --max-lines (${e.total_lines} > ${opts.maxLines})`
|
|
9154
|
+
});
|
|
9155
|
+
}
|
|
9156
|
+
}
|
|
9157
|
+
const yamlBody = renderYaml(result.entries, { generator: `cdd-kit ${_pkg.version}` });
|
|
9158
|
+
const totalSrc = result.entries.reduce((s, e) => s + e.total_lines, 0);
|
|
9159
|
+
const mapLines = yamlBody.split("\n").length;
|
|
9160
|
+
const compression = totalSrc === 0 ? 0 : totalSrc / mapLines;
|
|
9161
|
+
const summaryLine = `scanned ${result.entries.length} files, ${totalSrc} src lines -> ${opts.out} (${mapLines} lines, compression ${compression.toFixed(1)}x)`;
|
|
9162
|
+
for (const w of result.warnings) {
|
|
9163
|
+
log.warn(`${w.path}: ${w.message}`);
|
|
8771
9164
|
}
|
|
8772
|
-
if (
|
|
8773
|
-
|
|
8774
|
-
|
|
8775
|
-
|
|
9165
|
+
if (opts.check) {
|
|
9166
|
+
const existing = existsSync16(opts.out) ? readFileSync17(opts.out, "utf8") : "";
|
|
9167
|
+
const normalize = (s) => s.replace(/^# generated: [^\n]+\n/m, "# generated: <normalized>\n");
|
|
9168
|
+
if (normalize(existing) !== normalize(yamlBody)) {
|
|
9169
|
+
log.error(`code-map out of date: ${opts.out} would change. Run \`cdd-kit code-map\` to regenerate.`);
|
|
9170
|
+
return 1;
|
|
9171
|
+
}
|
|
9172
|
+
log.ok(`code-map up to date: ${opts.out}`);
|
|
9173
|
+
return 0;
|
|
8776
9174
|
}
|
|
8777
|
-
|
|
9175
|
+
mkdirSync8(dirname5(opts.out), { recursive: true });
|
|
9176
|
+
writeFileSync9(opts.out, yamlBody, "utf8");
|
|
9177
|
+
log.ok(`${summaryLine} (${Date.now() - start}ms)`);
|
|
9178
|
+
return 0;
|
|
8778
9179
|
}
|
|
8779
|
-
var
|
|
8780
|
-
|
|
9180
|
+
var _require, _pkgPath, _pkg;
|
|
9181
|
+
var init_code_map = __esm({
|
|
9182
|
+
"src/commands/code-map.ts"() {
|
|
8781
9183
|
"use strict";
|
|
8782
9184
|
init_logger();
|
|
8783
|
-
|
|
8784
|
-
|
|
9185
|
+
init_yaml_writer();
|
|
9186
|
+
init_orchestrator();
|
|
9187
|
+
init_config();
|
|
9188
|
+
_require = createRequire(import.meta.url);
|
|
9189
|
+
_pkgPath = join19(fileURLToPath2(import.meta.url), "..", "..", "..", "package.json");
|
|
9190
|
+
_pkg = JSON.parse(readFileSync17(_pkgPath, "utf8"));
|
|
8785
9191
|
}
|
|
8786
9192
|
});
|
|
8787
9193
|
|
|
8788
|
-
// src/commands/
|
|
8789
|
-
var
|
|
8790
|
-
__export(
|
|
8791
|
-
|
|
9194
|
+
// src/commands/refresh.ts
|
|
9195
|
+
var refresh_exports = {};
|
|
9196
|
+
__export(refresh_exports, {
|
|
9197
|
+
refresh: () => refresh
|
|
8792
9198
|
});
|
|
8793
|
-
import {
|
|
8794
|
-
import {
|
|
8795
|
-
import
|
|
8796
|
-
function
|
|
8797
|
-
|
|
8798
|
-
|
|
8799
|
-
|
|
8800
|
-
const
|
|
8801
|
-
if (
|
|
8802
|
-
|
|
9199
|
+
import { existsSync as existsSync17, mkdirSync as mkdirSync9, readdirSync as readdirSync10, copyFileSync as copyFileSync4, readFileSync as readFileSync18, writeFileSync as writeFileSync10 } from "fs";
|
|
9200
|
+
import { dirname as dirname6, join as join20, relative as relative5 } from "path";
|
|
9201
|
+
import { createHash as createHash4 } from "crypto";
|
|
9202
|
+
function fileHash2(filePath) {
|
|
9203
|
+
return createHash4("sha256").update(readFileSync18(filePath)).digest("hex");
|
|
9204
|
+
}
|
|
9205
|
+
function planForceRefresh(srcDir, destDir, sectionLabel) {
|
|
9206
|
+
const plan = [];
|
|
9207
|
+
if (!existsSync17(srcDir))
|
|
9208
|
+
return plan;
|
|
9209
|
+
function walk(curSrc, curDest) {
|
|
9210
|
+
let items;
|
|
9211
|
+
try {
|
|
9212
|
+
items = readdirSync10(curSrc, { withFileTypes: true });
|
|
9213
|
+
} catch {
|
|
9214
|
+
return;
|
|
9215
|
+
}
|
|
9216
|
+
for (const item of items) {
|
|
9217
|
+
const sp = join20(curSrc, item.name);
|
|
9218
|
+
const dp = join20(curDest, item.name);
|
|
9219
|
+
if (item.isDirectory()) {
|
|
9220
|
+
walk(sp, dp);
|
|
9221
|
+
continue;
|
|
9222
|
+
}
|
|
9223
|
+
if (!item.isFile())
|
|
9224
|
+
continue;
|
|
9225
|
+
const rel = join20(sectionLabel, relative5(srcDir, sp)).replace(/\\/g, "/");
|
|
9226
|
+
if (!existsSync17(dp)) {
|
|
9227
|
+
plan.push({ src: sp, dest: dp, rel, action: "add" });
|
|
9228
|
+
} else if (fileHash2(sp) !== fileHash2(dp)) {
|
|
9229
|
+
plan.push({ src: sp, dest: dp, rel, action: "overwrite" });
|
|
9230
|
+
} else {
|
|
9231
|
+
plan.push({ src: sp, dest: dp, rel, action: "skip" });
|
|
9232
|
+
}
|
|
9233
|
+
}
|
|
8803
9234
|
}
|
|
8804
|
-
|
|
9235
|
+
walk(srcDir, destDir);
|
|
9236
|
+
return plan;
|
|
8805
9237
|
}
|
|
8806
|
-
function
|
|
8807
|
-
|
|
8808
|
-
|
|
8809
|
-
|
|
8810
|
-
|
|
8811
|
-
|
|
8812
|
-
""
|
|
8813
|
-
|
|
8814
|
-
|
|
8815
|
-
|
|
8816
|
-
|
|
8817
|
-
|
|
8818
|
-
|
|
8819
|
-
|
|
8820
|
-
|
|
8821
|
-
|
|
8822
|
-
|
|
8823
|
-
|
|
8824
|
-
|
|
8825
|
-
|
|
8826
|
-
|
|
8827
|
-
|
|
8828
|
-
|
|
8829
|
-
|
|
8830
|
-
|
|
8831
|
-
|
|
8832
|
-
|
|
8833
|
-
|
|
9238
|
+
function planSingleFile(src, dest, rel) {
|
|
9239
|
+
if (!existsSync17(src))
|
|
9240
|
+
return null;
|
|
9241
|
+
if (!existsSync17(dest))
|
|
9242
|
+
return { src, dest, rel, action: "add" };
|
|
9243
|
+
if (fileHash2(src) !== fileHash2(dest))
|
|
9244
|
+
return { src, dest, rel, action: "overwrite" };
|
|
9245
|
+
return { src, dest, rel, action: "skip" };
|
|
9246
|
+
}
|
|
9247
|
+
function ensureGitignoreEntry2(cwd, entry) {
|
|
9248
|
+
const path = join20(cwd, ".gitignore");
|
|
9249
|
+
const trimmed = entry.trim();
|
|
9250
|
+
if (!trimmed)
|
|
9251
|
+
return false;
|
|
9252
|
+
const re = new RegExp(`^\\s*${trimmed.replace(/[.*+?^${}()|[\\]\\\\]/g, "\\$&")}\\s*$`, "m");
|
|
9253
|
+
let existing = "";
|
|
9254
|
+
if (existsSync17(path))
|
|
9255
|
+
existing = readFileSync18(path, "utf8");
|
|
9256
|
+
if (re.test(existing))
|
|
9257
|
+
return false;
|
|
9258
|
+
const sep = existing.length > 0 && !existing.endsWith("\n") ? "\n" : "";
|
|
9259
|
+
const block = existing.length === 0 ? `# cdd-kit generated backups (do not commit)
|
|
9260
|
+
${trimmed}
|
|
9261
|
+
` : `${sep}
|
|
9262
|
+
# cdd-kit generated backups (do not commit)
|
|
9263
|
+
${trimmed}
|
|
9264
|
+
`;
|
|
9265
|
+
writeFileSync10(path, existing + block, "utf8");
|
|
9266
|
+
return true;
|
|
8834
9267
|
}
|
|
8835
|
-
function
|
|
8836
|
-
|
|
8837
|
-
|
|
8838
|
-
|
|
8839
|
-
|
|
8840
|
-
|
|
8841
|
-
|
|
8842
|
-
|
|
8843
|
-
|
|
8844
|
-
|
|
8845
|
-
|
|
8846
|
-
|
|
8847
|
-
|
|
8848
|
-
|
|
8849
|
-
|
|
8850
|
-
|
|
8851
|
-
|
|
8852
|
-
|
|
8853
|
-
"",
|
|
8854
|
-
"## Required Tests",
|
|
8855
|
-
"- legacy-unknown",
|
|
8856
|
-
"",
|
|
8857
|
-
"## Agent Work Packets",
|
|
8858
|
-
"",
|
|
8859
|
-
"### change-classifier",
|
|
8860
|
-
"- allowed:",
|
|
8861
|
-
` - specs/changes/${changeId}/`,
|
|
8862
|
-
" - specs/context/project-map.md",
|
|
8863
|
-
" - specs/context/contracts-index.md",
|
|
8864
|
-
"",
|
|
8865
|
-
"## Context Expansion Requests",
|
|
8866
|
-
"",
|
|
8867
|
-
"<!--",
|
|
8868
|
-
"Agents must request context expansion instead of reading outside their work packet.",
|
|
8869
|
-
"Use this format only for real requests:",
|
|
8870
|
-
"",
|
|
8871
|
-
"- request-id: CER-001",
|
|
8872
|
-
" requested_paths:",
|
|
8873
|
-
" - src/example.ts",
|
|
8874
|
-
" reason: why this file is required",
|
|
8875
|
-
" status: pending",
|
|
8876
|
-
"-->",
|
|
8877
|
-
"",
|
|
8878
|
-
"## Approved Expansions",
|
|
8879
|
-
"-",
|
|
8880
|
-
""
|
|
8881
|
-
].join("\n");
|
|
9268
|
+
function applyPlan(plan, backupRoot) {
|
|
9269
|
+
let added = 0;
|
|
9270
|
+
let overwritten = 0;
|
|
9271
|
+
for (const item of plan) {
|
|
9272
|
+
if (item.action === "skip")
|
|
9273
|
+
continue;
|
|
9274
|
+
if (item.action === "overwrite") {
|
|
9275
|
+
const backupPath = join20(backupRoot, item.rel);
|
|
9276
|
+
mkdirSync9(dirname6(backupPath), { recursive: true });
|
|
9277
|
+
copyFileSync4(item.dest, backupPath);
|
|
9278
|
+
overwritten += 1;
|
|
9279
|
+
} else {
|
|
9280
|
+
added += 1;
|
|
9281
|
+
}
|
|
9282
|
+
mkdirSync9(dirname6(item.dest), { recursive: true });
|
|
9283
|
+
copyFileSync4(item.src, item.dest);
|
|
9284
|
+
}
|
|
9285
|
+
return { added, overwritten };
|
|
8882
9286
|
}
|
|
8883
|
-
function
|
|
9287
|
+
function parseAgentFrontmatter(content) {
|
|
8884
9288
|
const m = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
|
|
8885
9289
|
if (!m)
|
|
8886
9290
|
return {};
|
|
8887
|
-
const
|
|
9291
|
+
const fm = {};
|
|
8888
9292
|
for (const line of m[1].split(/\r?\n/)) {
|
|
8889
|
-
const
|
|
8890
|
-
if (
|
|
8891
|
-
continue;
|
|
8892
|
-
const key = line.slice(0, colon).trim();
|
|
8893
|
-
if (!key)
|
|
8894
|
-
continue;
|
|
8895
|
-
out[key] = line.slice(colon + 1).trim();
|
|
8896
|
-
}
|
|
8897
|
-
return out;
|
|
8898
|
-
}
|
|
8899
|
-
function parseListField(raw) {
|
|
8900
|
-
if (!raw)
|
|
8901
|
-
return [];
|
|
8902
|
-
const trimmed = raw.trim();
|
|
8903
|
-
if (!trimmed || trimmed === "[]")
|
|
8904
|
-
return [];
|
|
8905
|
-
const inner = trimmed.startsWith("[") && trimmed.endsWith("]") ? trimmed.slice(1, -1) : trimmed;
|
|
8906
|
-
return inner.split(",").map((item) => item.trim().replace(/^["']|["']$/g, "")).filter(Boolean);
|
|
8907
|
-
}
|
|
8908
|
-
function parseLegacyTaskList(body) {
|
|
8909
|
-
const lines = body.split(/\r?\n/);
|
|
8910
|
-
const rows = [];
|
|
8911
|
-
let currentSection;
|
|
8912
|
-
for (const raw of lines) {
|
|
8913
|
-
const headerMatch = raw.match(/^##\s+\d+\.\s+(.*)\s*$/);
|
|
8914
|
-
if (headerMatch) {
|
|
8915
|
-
currentSection = headerMatch[1].trim();
|
|
9293
|
+
const km = line.match(/^([a-zA-Z_-]+):\s*(.+?)\s*$/);
|
|
9294
|
+
if (!km)
|
|
8916
9295
|
continue;
|
|
9296
|
+
if (km[1] === "name")
|
|
9297
|
+
fm.name = km[2].trim();
|
|
9298
|
+
if (km[1] === "model")
|
|
9299
|
+
fm.model = km[2].trim();
|
|
9300
|
+
}
|
|
9301
|
+
return fm;
|
|
9302
|
+
}
|
|
9303
|
+
function resyncModelPolicy(cwd) {
|
|
9304
|
+
const policyPath = join20(cwd, ".cdd", "model-policy.json");
|
|
9305
|
+
const result = { changed: false, diff: [], policyPath };
|
|
9306
|
+
if (!existsSync17(AGENTS_HOME))
|
|
9307
|
+
return result;
|
|
9308
|
+
const desired = {};
|
|
9309
|
+
const agentFiles = readdirSync10(AGENTS_HOME, { withFileTypes: true }).filter((d) => d.isFile() && d.name.endsWith(".md"));
|
|
9310
|
+
for (const f of agentFiles) {
|
|
9311
|
+
const content = readFileSync18(join20(AGENTS_HOME, f.name), "utf8");
|
|
9312
|
+
const fm = parseAgentFrontmatter(content);
|
|
9313
|
+
if (fm.name && fm.model)
|
|
9314
|
+
desired[fm.name] = fm.model;
|
|
9315
|
+
}
|
|
9316
|
+
if (Object.keys(desired).length === 0)
|
|
9317
|
+
return result;
|
|
9318
|
+
let existing = {};
|
|
9319
|
+
if (existsSync17(policyPath)) {
|
|
9320
|
+
try {
|
|
9321
|
+
existing = JSON.parse(readFileSync18(policyPath, "utf8"));
|
|
9322
|
+
} catch {
|
|
8917
9323
|
}
|
|
8918
|
-
const itemMatch = raw.match(/^\s*-\s*\[([ xX\-])\]\s+(\d+(?:\.\d+)*)\s+(.*)\s*$/);
|
|
8919
|
-
if (!itemMatch)
|
|
8920
|
-
continue;
|
|
8921
|
-
const mark = itemMatch[1];
|
|
8922
|
-
const id = itemMatch[2];
|
|
8923
|
-
const title = itemMatch[3].trim();
|
|
8924
|
-
let status = "pending";
|
|
8925
|
-
if (mark === "x" || mark === "X")
|
|
8926
|
-
status = "done";
|
|
8927
|
-
else if (mark === "-")
|
|
8928
|
-
status = "skipped";
|
|
8929
|
-
rows.push({ id, title, status, section: currentSection });
|
|
8930
9324
|
}
|
|
8931
|
-
|
|
8932
|
-
|
|
8933
|
-
|
|
8934
|
-
|
|
8935
|
-
|
|
8936
|
-
if (existsSync16(newPath)) {
|
|
8937
|
-
return;
|
|
9325
|
+
const existingRoles = existing.roles && typeof existing.roles === "object" ? existing.roles : {};
|
|
9326
|
+
for (const [agent, model] of Object.entries(desired)) {
|
|
9327
|
+
if (existingRoles[agent] !== model) {
|
|
9328
|
+
result.diff.push({ agent, from: existingRoles[agent] ?? null, to: model });
|
|
9329
|
+
}
|
|
8938
9330
|
}
|
|
8939
|
-
if (
|
|
8940
|
-
|
|
8941
|
-
|
|
9331
|
+
if (result.diff.length === 0)
|
|
9332
|
+
return result;
|
|
9333
|
+
const merged = {
|
|
9334
|
+
...existing,
|
|
9335
|
+
roles: desired
|
|
9336
|
+
};
|
|
9337
|
+
if (!("schema-version" in merged))
|
|
9338
|
+
merged["schema-version"] = "0.2.0";
|
|
9339
|
+
if (!("provider" in merged))
|
|
9340
|
+
merged["provider"] = "claude";
|
|
9341
|
+
mkdirSync9(dirname6(policyPath), { recursive: true });
|
|
9342
|
+
writeFileSync10(policyPath, JSON.stringify(merged, null, 2) + "\n", "utf8");
|
|
9343
|
+
result.changed = true;
|
|
9344
|
+
return result;
|
|
9345
|
+
}
|
|
9346
|
+
function planTemplateRefresh(cwd) {
|
|
9347
|
+
const sections = [];
|
|
9348
|
+
sections.push({
|
|
9349
|
+
name: "specs/templates",
|
|
9350
|
+
plan: planForceRefresh(ASSET.specsTemplates, join20(cwd, "specs", "templates"), "specs/templates")
|
|
9351
|
+
});
|
|
9352
|
+
sections.push({
|
|
9353
|
+
name: "tests/templates",
|
|
9354
|
+
plan: planForceRefresh(ASSET.testsTemplates, join20(cwd, "tests", "templates"), "tests/templates")
|
|
9355
|
+
});
|
|
9356
|
+
const ciTemplatesAsset = join20(ASSET.ci, "..", "ci-templates");
|
|
9357
|
+
if (existsSync17(ciTemplatesAsset)) {
|
|
9358
|
+
sections.push({
|
|
9359
|
+
name: "ci-templates",
|
|
9360
|
+
plan: planForceRefresh(ciTemplatesAsset, join20(cwd, "ci-templates"), "ci-templates")
|
|
9361
|
+
});
|
|
8942
9362
|
}
|
|
8943
|
-
const
|
|
8944
|
-
const
|
|
8945
|
-
const
|
|
8946
|
-
|
|
8947
|
-
|
|
8948
|
-
const
|
|
8949
|
-
|
|
8950
|
-
|
|
8951
|
-
|
|
8952
|
-
|
|
8953
|
-
|
|
8954
|
-
|
|
9363
|
+
const wfAsset = join20(ASSET.githubWorkflows, "contract-driven-gates.yml");
|
|
9364
|
+
const wfDest = join20(cwd, ".github", "workflows", "contract-driven-gates.yml");
|
|
9365
|
+
const wfPlan = planSingleFile(wfAsset, wfDest, ".github/workflows/contract-driven-gates.yml");
|
|
9366
|
+
if (wfPlan)
|
|
9367
|
+
sections.push({ name: ".github/workflows/contract-driven-gates.yml", plan: [wfPlan] });
|
|
9368
|
+
const total = sections.flatMap((s) => s.plan);
|
|
9369
|
+
return { sections, total };
|
|
9370
|
+
}
|
|
9371
|
+
function summarizePlan(items) {
|
|
9372
|
+
return {
|
|
9373
|
+
add: items.filter((i) => i.action === "add").length,
|
|
9374
|
+
overwrite: items.filter((i) => i.action === "overwrite").length,
|
|
9375
|
+
skip: items.filter((i) => i.action === "skip").length
|
|
9376
|
+
};
|
|
9377
|
+
}
|
|
9378
|
+
async function refresh(opts) {
|
|
9379
|
+
const cwd = process.cwd();
|
|
9380
|
+
const apply = !!opts.yes;
|
|
9381
|
+
log.blank();
|
|
9382
|
+
log.info(apply ? "cdd-kit refresh \u2014 applying changes" : "cdd-kit refresh \u2014 dry-run preview (re-run with --yes to apply)");
|
|
9383
|
+
log.blank();
|
|
9384
|
+
if (!opts.noUpdate) {
|
|
9385
|
+
log.info("[1/6] ~/.claude/agents and ~/.claude/skills (via cdd-kit update)");
|
|
9386
|
+
await update({ yes: apply, provider: opts.provider ?? "auto" });
|
|
8955
9387
|
} else {
|
|
8956
|
-
|
|
9388
|
+
log.dim("[1/6] skipped (--no-update)");
|
|
8957
9389
|
}
|
|
8958
|
-
|
|
8959
|
-
|
|
9390
|
+
log.blank();
|
|
9391
|
+
if (!opts.noUpgrade) {
|
|
9392
|
+
log.info("[2/6] add missing project files (via cdd-kit upgrade)");
|
|
9393
|
+
await upgrade({ yes: apply, provider: opts.provider ?? "auto" });
|
|
9394
|
+
} else {
|
|
9395
|
+
log.dim("[2/6] skipped (--no-upgrade)");
|
|
8960
9396
|
}
|
|
8961
|
-
|
|
8962
|
-
|
|
8963
|
-
|
|
8964
|
-
|
|
8965
|
-
|
|
8966
|
-
|
|
8967
|
-
|
|
8968
|
-
|
|
8969
|
-
|
|
8970
|
-
|
|
8971
|
-
|
|
8972
|
-
|
|
8973
|
-
|
|
8974
|
-
|
|
8975
|
-
|
|
8976
|
-
|
|
8977
|
-
|
|
8978
|
-
|
|
8979
|
-
|
|
8980
|
-
|
|
8981
|
-
|
|
8982
|
-
|
|
8983
|
-
|
|
8984
|
-
|
|
8985
|
-
|
|
8986
|
-
|
|
8987
|
-
|
|
8988
|
-
|
|
8989
|
-
|
|
8990
|
-
|
|
8991
|
-
|
|
8992
|
-
|
|
8993
|
-
while (j < lines.length) {
|
|
8994
|
-
const sub = lines[j];
|
|
8995
|
-
if (topFieldRe.test(sub))
|
|
8996
|
-
break;
|
|
8997
|
-
if (/^#/.test(sub))
|
|
8998
|
-
break;
|
|
8999
|
-
const itemMatch = sub.match(/^\s{2,}-\s+(.+?)\s*$/);
|
|
9000
|
-
if (itemMatch) {
|
|
9001
|
-
items.push(itemMatch[1].trim());
|
|
9397
|
+
log.blank();
|
|
9398
|
+
let templateAdded = 0;
|
|
9399
|
+
let templateOverwritten = 0;
|
|
9400
|
+
let backupRoot = null;
|
|
9401
|
+
if (!opts.noTemplates) {
|
|
9402
|
+
log.info("[3/6] force-refresh kit-shipped templates");
|
|
9403
|
+
const { sections, total } = planTemplateRefresh(cwd);
|
|
9404
|
+
const summary = summarizePlan(total);
|
|
9405
|
+
if (summary.add === 0 && summary.overwrite === 0) {
|
|
9406
|
+
log.ok(" templates already up to date");
|
|
9407
|
+
} else {
|
|
9408
|
+
for (const s of sections) {
|
|
9409
|
+
const ss = summarizePlan(s.plan);
|
|
9410
|
+
if (ss.add === 0 && ss.overwrite === 0)
|
|
9411
|
+
continue;
|
|
9412
|
+
log.info(` ${s.name}: +${ss.add} ~${ss.overwrite} =${ss.skip}`);
|
|
9413
|
+
for (const item of s.plan.filter((i) => i.action !== "skip")) {
|
|
9414
|
+
log.dim(` ${item.action === "add" ? "+" : "~"} ${item.rel}`);
|
|
9415
|
+
}
|
|
9416
|
+
}
|
|
9417
|
+
if (apply) {
|
|
9418
|
+
const ts = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
9419
|
+
backupRoot = join20(cwd, ".cdd", ".refresh-backup", ts);
|
|
9420
|
+
const result = applyPlan(total, backupRoot);
|
|
9421
|
+
templateAdded = result.added;
|
|
9422
|
+
templateOverwritten = result.overwritten;
|
|
9423
|
+
log.ok(` applied: +${templateAdded} added, ~${templateOverwritten} overwritten`);
|
|
9424
|
+
if (templateOverwritten > 0) {
|
|
9425
|
+
log.info(` backup saved to: ${relative5(cwd, backupRoot).replace(/\\/g, "/")}`);
|
|
9426
|
+
if (ensureGitignoreEntry2(cwd, ".cdd/.refresh-backup/")) {
|
|
9427
|
+
log.info(" added `.cdd/.refresh-backup/` to .gitignore");
|
|
9428
|
+
}
|
|
9002
9429
|
}
|
|
9003
|
-
j++;
|
|
9004
|
-
}
|
|
9005
|
-
if (key === "files-read") {
|
|
9006
|
-
data["files-read"] = items;
|
|
9007
9430
|
} else {
|
|
9008
|
-
|
|
9009
|
-
const idx = s.indexOf(":");
|
|
9010
|
-
if (idx === -1) {
|
|
9011
|
-
return { type: "note", pointer: s };
|
|
9012
|
-
}
|
|
9013
|
-
const type = s.slice(0, idx).trim();
|
|
9014
|
-
const pointer = s.slice(idx + 1).trim();
|
|
9015
|
-
return { type, pointer };
|
|
9016
|
-
});
|
|
9431
|
+
log.dim(" (dry-run \u2014 no changes written)");
|
|
9017
9432
|
}
|
|
9018
|
-
i = j;
|
|
9019
|
-
continue;
|
|
9020
9433
|
}
|
|
9021
|
-
|
|
9022
|
-
|
|
9023
|
-
}
|
|
9024
|
-
return data;
|
|
9025
|
-
}
|
|
9026
|
-
function migrateAgentLogs(changeDir, changed, pendingWrites, pendingDeletes) {
|
|
9027
|
-
const agentLogDir = join19(changeDir, "agent-log");
|
|
9028
|
-
if (!existsSync16(agentLogDir))
|
|
9029
|
-
return;
|
|
9030
|
-
const mdLogs = readdirSync9(agentLogDir).filter((f) => f.endsWith(".md"));
|
|
9031
|
-
for (const f of mdLogs) {
|
|
9032
|
-
const fullPath = join19(agentLogDir, f);
|
|
9033
|
-
const yamlName = f.replace(/\.md$/, ".yml");
|
|
9034
|
-
const yamlFull = join19(agentLogDir, yamlName);
|
|
9035
|
-
if (existsSync16(yamlFull))
|
|
9036
|
-
continue;
|
|
9037
|
-
const raw = readFileSync17(fullPath, "utf8");
|
|
9038
|
-
const parsed = parseLegacyAgentLog(raw);
|
|
9039
|
-
const yamlOut = yaml3.dump(parsed, { lineWidth: -1, noRefs: true });
|
|
9040
|
-
pendingWrites.push({ path: yamlFull, content: yamlOut });
|
|
9041
|
-
pendingDeletes.push({ path: fullPath });
|
|
9042
|
-
changed.push(`agent-log/${f} -> agent-log/${yamlName}`);
|
|
9434
|
+
} else {
|
|
9435
|
+
log.dim("[3/6] skipped (--no-templates)");
|
|
9043
9436
|
}
|
|
9044
|
-
|
|
9045
|
-
|
|
9046
|
-
|
|
9047
|
-
|
|
9048
|
-
|
|
9049
|
-
|
|
9050
|
-
|
|
9051
|
-
|
|
9052
|
-
|
|
9053
|
-
|
|
9054
|
-
const hasNewTierFormat = /^## Tier\s*\n\s*-\s*\d\s*$/m.test(content);
|
|
9055
|
-
const oldMatch = content.match(/\*\*Tier[:\*]+\s*(?:Tier\s*)?(\d)/i) ?? content.match(/^-?\s*Tier:\s*(?:Tier\s*)?(\d)/mi);
|
|
9056
|
-
if (oldMatch)
|
|
9057
|
-
detectedTier = oldMatch[1];
|
|
9058
|
-
if (!hasNewTierFormat) {
|
|
9059
|
-
if (detectedTier) {
|
|
9060
|
-
const addition = `
|
|
9061
|
-
## Tier
|
|
9062
|
-
- ${detectedTier}
|
|
9063
|
-
`;
|
|
9064
|
-
if (!content.includes("\n## Tier\n")) {
|
|
9065
|
-
changed.push(
|
|
9066
|
-
`change-classification.md: appended "## Tier\\n- ${detectedTier}" (converted from old format)`
|
|
9067
|
-
);
|
|
9068
|
-
pending.push({ path: classifPath, content: content + addition });
|
|
9437
|
+
log.blank();
|
|
9438
|
+
if (!opts.noHooks) {
|
|
9439
|
+
const markerPath = join20(cwd, HOOKS_MARKER_PATH);
|
|
9440
|
+
if (existsSync17(markerPath)) {
|
|
9441
|
+
log.info("[4/6] re-install code-map pre-commit hook (marker found)");
|
|
9442
|
+
if (apply) {
|
|
9443
|
+
try {
|
|
9444
|
+
await installCodeMapHook(cwd);
|
|
9445
|
+
} catch (err) {
|
|
9446
|
+
log.warn(` hook re-install skipped: ${err.message}`);
|
|
9069
9447
|
}
|
|
9070
9448
|
} else {
|
|
9071
|
-
|
|
9072
|
-
"change-classification.md: could not detect tier (no **Tier:** N or ## Tier N found). Set `tier: <0-5>` in tasks.yml frontmatter to enable tier-based gate checks."
|
|
9073
|
-
);
|
|
9449
|
+
log.dim(" (dry-run \u2014 would re-install)");
|
|
9074
9450
|
}
|
|
9075
9451
|
} else {
|
|
9076
|
-
|
|
9077
|
-
if (structured)
|
|
9078
|
-
detectedTier = structured[1];
|
|
9452
|
+
log.dim("[4/6] no .cdd/.hooks-installed marker \u2014 skipping (run `cdd-kit init --hooks` to install)");
|
|
9079
9453
|
}
|
|
9454
|
+
} else {
|
|
9455
|
+
log.dim("[4/6] skipped (--no-hooks)");
|
|
9080
9456
|
}
|
|
9081
|
-
|
|
9082
|
-
|
|
9083
|
-
|
|
9084
|
-
|
|
9085
|
-
|
|
9086
|
-
|
|
9087
|
-
|
|
9088
|
-
|
|
9089
|
-
|
|
9090
|
-
|
|
9091
|
-
|
|
9092
|
-
}
|
|
9093
|
-
return { result: { changed, warnings }, pending, deletes };
|
|
9094
|
-
}
|
|
9095
|
-
function commitWritesAtomically(pending, deletes) {
|
|
9096
|
-
const renames = [];
|
|
9097
|
-
try {
|
|
9098
|
-
for (const write of pending) {
|
|
9099
|
-
const tmp = `${write.path}.cdd-migrate.tmp`;
|
|
9100
|
-
writeFileSync8(tmp, write.content, "utf8");
|
|
9101
|
-
renames.push({ tmp, final: write.path });
|
|
9457
|
+
log.blank();
|
|
9458
|
+
log.info("[5/6] resync .cdd/model-policy.json roles from agent frontmatter");
|
|
9459
|
+
if (apply) {
|
|
9460
|
+
const r = resyncModelPolicy(cwd);
|
|
9461
|
+
if (r.diff.length === 0) {
|
|
9462
|
+
log.ok(" roles already match agent prompts");
|
|
9463
|
+
} else {
|
|
9464
|
+
for (const d of r.diff) {
|
|
9465
|
+
log.info(` ${d.agent}: ${d.from ?? "(absent)"} \u2192 ${d.to}`);
|
|
9466
|
+
}
|
|
9467
|
+
log.ok(` ${r.diff.length} role binding(s) updated`);
|
|
9102
9468
|
}
|
|
9103
|
-
}
|
|
9104
|
-
|
|
9469
|
+
} else {
|
|
9470
|
+
const fakeApply = () => {
|
|
9471
|
+
};
|
|
9472
|
+
fakeApply();
|
|
9473
|
+
log.dim(" (dry-run \u2014 drift will be reported only when applied)");
|
|
9474
|
+
}
|
|
9475
|
+
log.blank();
|
|
9476
|
+
if (!opts.noCodeMap) {
|
|
9477
|
+
log.info("[6/6] regenerate .cdd/code-map.yml");
|
|
9478
|
+
if (apply) {
|
|
9105
9479
|
try {
|
|
9106
|
-
|
|
9107
|
-
|
|
9480
|
+
await codeMap({
|
|
9481
|
+
path: ".",
|
|
9482
|
+
out: ".cdd/code-map.yml",
|
|
9483
|
+
include: [],
|
|
9484
|
+
exclude: [],
|
|
9485
|
+
check: false,
|
|
9486
|
+
maxLines: 600
|
|
9487
|
+
});
|
|
9488
|
+
} catch (err) {
|
|
9489
|
+
log.warn(` code-map skipped: ${err.message}`);
|
|
9108
9490
|
}
|
|
9491
|
+
} else {
|
|
9492
|
+
log.dim(" (dry-run \u2014 would regenerate)");
|
|
9109
9493
|
}
|
|
9110
|
-
|
|
9494
|
+
} else {
|
|
9495
|
+
log.dim("[6/6] skipped (--no-code-map)");
|
|
9111
9496
|
}
|
|
9112
|
-
|
|
9113
|
-
|
|
9497
|
+
log.blank();
|
|
9498
|
+
if (apply) {
|
|
9499
|
+
log.ok("refresh complete.");
|
|
9500
|
+
if (backupRoot) {
|
|
9501
|
+
log.info(`Backup of overwritten templates: ${relative5(cwd, backupRoot).replace(/\\/g, "/")}`);
|
|
9502
|
+
}
|
|
9503
|
+
log.info("Next: review changes with `git diff`, then commit:");
|
|
9504
|
+
log.dim(" git add .cdd/code-map.yml .cdd/model-policy.json specs/templates tests/templates ci-templates .github/workflows");
|
|
9505
|
+
log.dim(' git commit -m "chore: cdd-kit refresh"');
|
|
9506
|
+
} else {
|
|
9507
|
+
log.info("Dry-run finished. Re-run with `--yes` to apply.");
|
|
9114
9508
|
}
|
|
9115
|
-
|
|
9116
|
-
|
|
9117
|
-
|
|
9118
|
-
|
|
9119
|
-
|
|
9509
|
+
log.blank();
|
|
9510
|
+
}
|
|
9511
|
+
var HOOKS_MARKER_PATH;
|
|
9512
|
+
var init_refresh = __esm({
|
|
9513
|
+
"src/commands/refresh.ts"() {
|
|
9514
|
+
"use strict";
|
|
9515
|
+
init_paths();
|
|
9516
|
+
init_logger();
|
|
9517
|
+
init_update();
|
|
9518
|
+
init_upgrade();
|
|
9519
|
+
init_code_map();
|
|
9520
|
+
init_code_map_hook();
|
|
9521
|
+
HOOKS_MARKER_PATH = ".cdd/.hooks-installed";
|
|
9120
9522
|
}
|
|
9523
|
+
});
|
|
9524
|
+
|
|
9525
|
+
// src/commands/doctor.ts
|
|
9526
|
+
var doctor_exports = {};
|
|
9527
|
+
__export(doctor_exports, {
|
|
9528
|
+
doctor: () => doctor
|
|
9529
|
+
});
|
|
9530
|
+
import { existsSync as existsSync18, readdirSync as readdirSync11, readFileSync as readFileSync19 } from "fs";
|
|
9531
|
+
import { createHash as createHash5 } from "crypto";
|
|
9532
|
+
import { join as join21 } from "path";
|
|
9533
|
+
function fileExists(cwd, relPath) {
|
|
9534
|
+
return existsSync18(join21(cwd, relPath));
|
|
9121
9535
|
}
|
|
9122
|
-
|
|
9123
|
-
|
|
9124
|
-
|
|
9125
|
-
const
|
|
9126
|
-
|
|
9127
|
-
|
|
9128
|
-
|
|
9129
|
-
|
|
9130
|
-
|
|
9131
|
-
log.info("No specs/changes/ directory found \u2014 nothing to migrate.");
|
|
9132
|
-
return;
|
|
9133
|
-
}
|
|
9134
|
-
idsToMigrate.push(
|
|
9135
|
-
...readdirSync9(changesDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name)
|
|
9136
|
-
);
|
|
9137
|
-
} else if (changeId) {
|
|
9138
|
-
const specificDir = join19(cwd, "specs", "changes", changeId);
|
|
9139
|
-
if (!existsSync16(specificDir)) {
|
|
9140
|
-
log.error(`Change not found: specs/changes/${changeId}`);
|
|
9141
|
-
process.exit(1);
|
|
9142
|
-
}
|
|
9143
|
-
idsToMigrate.push(changeId);
|
|
9144
|
-
} else {
|
|
9145
|
-
log.error("Usage: cdd-kit migrate <change-id> | cdd-kit migrate --all [--dry-run] [--no-backup]");
|
|
9146
|
-
process.exit(1);
|
|
9536
|
+
function findFiles(dir, predicate, found = []) {
|
|
9537
|
+
if (!existsSync18(dir))
|
|
9538
|
+
return found;
|
|
9539
|
+
for (const entry of readdirSync11(dir, { withFileTypes: true })) {
|
|
9540
|
+
const fullPath = join21(dir, entry.name);
|
|
9541
|
+
if (entry.isDirectory())
|
|
9542
|
+
findFiles(fullPath, predicate, found);
|
|
9543
|
+
else if (entry.isFile() && predicate(entry.name))
|
|
9544
|
+
found.push(fullPath);
|
|
9147
9545
|
}
|
|
9148
|
-
|
|
9149
|
-
|
|
9150
|
-
|
|
9546
|
+
return found;
|
|
9547
|
+
}
|
|
9548
|
+
function sha256OfFile3(path) {
|
|
9549
|
+
try {
|
|
9550
|
+
return createHash5("sha256").update(readFileSync19(path)).digest("hex");
|
|
9551
|
+
} catch {
|
|
9552
|
+
return "";
|
|
9151
9553
|
}
|
|
9152
|
-
|
|
9153
|
-
|
|
9154
|
-
|
|
9554
|
+
}
|
|
9555
|
+
function inputDigest(paths) {
|
|
9556
|
+
const combined = paths.slice().sort().map((p) => `${p}:${sha256OfFile3(p)}`).join("\n");
|
|
9557
|
+
return createHash5("sha256").update(combined).digest("hex");
|
|
9558
|
+
}
|
|
9559
|
+
function readContextIndexMetadata(filePath) {
|
|
9560
|
+
if (!existsSync18(filePath))
|
|
9561
|
+
return {};
|
|
9562
|
+
const text = readFileSync19(filePath, "utf8");
|
|
9563
|
+
const out = {};
|
|
9564
|
+
const digestMatch = text.match(/^inputs-digest:\s*([a-f0-9]+)/m);
|
|
9565
|
+
if (digestMatch)
|
|
9566
|
+
out.inputsDigest = digestMatch[1];
|
|
9567
|
+
const missingMatch = text.match(/^missing-summary-count:\s*(\d+)/m);
|
|
9568
|
+
if (missingMatch)
|
|
9569
|
+
out.missingSummary = Number(missingMatch[1]);
|
|
9570
|
+
return out;
|
|
9571
|
+
}
|
|
9572
|
+
function checkContextFreshness(cwd) {
|
|
9573
|
+
const findings = [];
|
|
9574
|
+
const projectMap = join21(cwd, "specs", "context", "project-map.md");
|
|
9575
|
+
const contractsIndex = join21(cwd, "specs", "context", "contracts-index.md");
|
|
9576
|
+
const contextPolicy = join21(cwd, ".cdd", "context-policy.json");
|
|
9577
|
+
const contractFiles = findFiles(
|
|
9578
|
+
join21(cwd, "contracts"),
|
|
9579
|
+
(name) => name.endsWith(".md") && name !== "INDEX.md" && name !== "CHANGELOG.md"
|
|
9580
|
+
);
|
|
9581
|
+
if (!existsSync18(projectMap) || !existsSync18(contractsIndex)) {
|
|
9582
|
+
findings.push({
|
|
9583
|
+
level: "warning",
|
|
9584
|
+
message: "specs/context indexes are missing; run cdd-kit context-scan before classification"
|
|
9585
|
+
});
|
|
9586
|
+
return findings;
|
|
9155
9587
|
}
|
|
9156
|
-
const
|
|
9157
|
-
|
|
9158
|
-
|
|
9159
|
-
|
|
9160
|
-
|
|
9161
|
-
|
|
9162
|
-
|
|
9163
|
-
|
|
9164
|
-
|
|
9165
|
-
|
|
9166
|
-
|
|
9167
|
-
|
|
9168
|
-
|
|
9169
|
-
|
|
9170
|
-
|
|
9171
|
-
|
|
9172
|
-
|
|
9173
|
-
|
|
9174
|
-
|
|
9175
|
-
|
|
9176
|
-
|
|
9177
|
-
|
|
9178
|
-
|
|
9179
|
-
|
|
9180
|
-
|
|
9181
|
-
|
|
9182
|
-
|
|
9183
|
-
|
|
9184
|
-
|
|
9185
|
-
|
|
9588
|
+
const projectMapMeta = readContextIndexMetadata(projectMap);
|
|
9589
|
+
const contractsIndexMeta = readContextIndexMetadata(contractsIndex);
|
|
9590
|
+
const projectInputDigest = inputDigest([contextPolicy].filter(existsSync18));
|
|
9591
|
+
if (projectMapMeta.inputsDigest === void 0) {
|
|
9592
|
+
findings.push({
|
|
9593
|
+
level: "warning",
|
|
9594
|
+
message: "specs/context/project-map.md was generated by an older cdd-kit (no inputs-digest); re-run cdd-kit context-scan"
|
|
9595
|
+
});
|
|
9596
|
+
} else if (projectInputDigest && projectMapMeta.inputsDigest !== projectInputDigest) {
|
|
9597
|
+
findings.push({
|
|
9598
|
+
level: "warning",
|
|
9599
|
+
message: "specs/context/project-map.md inputs changed (.cdd/context-policy.json); re-run cdd-kit context-scan"
|
|
9600
|
+
});
|
|
9601
|
+
}
|
|
9602
|
+
const contractsInputDigest = inputDigest(contractFiles);
|
|
9603
|
+
if (contractsIndexMeta.inputsDigest === void 0) {
|
|
9604
|
+
findings.push({
|
|
9605
|
+
level: "warning",
|
|
9606
|
+
message: "specs/context/contracts-index.md was generated by an older cdd-kit (no inputs-digest); re-run cdd-kit context-scan"
|
|
9607
|
+
});
|
|
9608
|
+
} else if (contractsInputDigest && contractsIndexMeta.inputsDigest !== contractsInputDigest) {
|
|
9609
|
+
findings.push({
|
|
9610
|
+
level: "warning",
|
|
9611
|
+
message: "specs/context/contracts-index.md inputs changed (contracts/*); re-run cdd-kit context-scan"
|
|
9612
|
+
});
|
|
9613
|
+
}
|
|
9614
|
+
if (contractsIndexMeta.missingSummary !== void 0 && contractsIndexMeta.missingSummary > 0) {
|
|
9615
|
+
findings.push({
|
|
9616
|
+
level: "warning",
|
|
9617
|
+
message: `contracts-index reports ${contractsIndexMeta.missingSummary} contract(s) without deterministic summary metadata`
|
|
9618
|
+
});
|
|
9619
|
+
}
|
|
9620
|
+
if (findings.length === 0) {
|
|
9621
|
+
findings.push({ level: "ok", message: "context indexes are present and fresh" });
|
|
9622
|
+
}
|
|
9623
|
+
return findings;
|
|
9624
|
+
}
|
|
9625
|
+
function readAgentModel(path) {
|
|
9626
|
+
try {
|
|
9627
|
+
const text = readFileSync19(path, "utf8");
|
|
9628
|
+
const m = text.match(/^model:\s*(\S+)/m);
|
|
9629
|
+
return m ? m[1] : null;
|
|
9630
|
+
} catch {
|
|
9631
|
+
return null;
|
|
9632
|
+
}
|
|
9633
|
+
}
|
|
9634
|
+
function checkModelPolicyDrift(cwd) {
|
|
9635
|
+
const policyPath = join21(cwd, ".cdd", "model-policy.json");
|
|
9636
|
+
if (!existsSync18(policyPath))
|
|
9637
|
+
return [];
|
|
9638
|
+
let policy;
|
|
9639
|
+
try {
|
|
9640
|
+
policy = JSON.parse(readFileSync19(policyPath, "utf8"));
|
|
9641
|
+
} catch {
|
|
9642
|
+
return [{ level: "warning", message: ".cdd/model-policy.json is not valid JSON" }];
|
|
9643
|
+
}
|
|
9644
|
+
const roles = policy.roles ?? {};
|
|
9645
|
+
if (Object.keys(roles).length === 0) {
|
|
9646
|
+
return [{
|
|
9647
|
+
level: "warning",
|
|
9648
|
+
message: ".cdd/model-policy.json has no role bindings; run cdd-kit upgrade to install defaults"
|
|
9649
|
+
}];
|
|
9650
|
+
}
|
|
9651
|
+
const candidateDirs = [
|
|
9652
|
+
join21(cwd, ".claude", "agents"),
|
|
9653
|
+
process.env.HOME ? join21(process.env.HOME, ".claude", "agents") : "",
|
|
9654
|
+
process.env.USERPROFILE ? join21(process.env.USERPROFILE, ".claude", "agents") : ""
|
|
9655
|
+
].filter((p) => p && existsSync18(p));
|
|
9656
|
+
if (candidateDirs.length === 0)
|
|
9657
|
+
return [];
|
|
9658
|
+
const findings = [];
|
|
9659
|
+
for (const [role, expected] of Object.entries(roles)) {
|
|
9660
|
+
let foundAny = false;
|
|
9661
|
+
for (const dir of candidateDirs) {
|
|
9662
|
+
const path = join21(dir, `${role}.md`);
|
|
9663
|
+
if (!existsSync18(path))
|
|
9664
|
+
continue;
|
|
9665
|
+
foundAny = true;
|
|
9666
|
+
const actual = readAgentModel(path);
|
|
9667
|
+
if (actual && actual !== expected) {
|
|
9668
|
+
findings.push({
|
|
9669
|
+
level: "warning",
|
|
9670
|
+
message: `model-policy drift: ${role} expected ${expected}, agent prompt uses ${actual} (${path})`
|
|
9671
|
+
});
|
|
9186
9672
|
}
|
|
9187
9673
|
}
|
|
9188
|
-
|
|
9189
|
-
for (const c of changed)
|
|
9190
|
-
log.info(` + ${c}`);
|
|
9191
|
-
migratedCount++;
|
|
9192
|
-
for (const w of warnings)
|
|
9193
|
-
log.warn(` ${id}: ${w}`);
|
|
9194
|
-
}
|
|
9195
|
-
log.blank();
|
|
9196
|
-
if (dryRun) {
|
|
9197
|
-
log.info(`Dry run complete: ${migratedCount} change(s) would be updated, ${upToDateCount} already up to date.`);
|
|
9198
|
-
} else {
|
|
9199
|
-
log.ok(`Migration complete: ${migratedCount} updated, ${upToDateCount} already up to date.`);
|
|
9200
|
-
if (migratedCount > 0 && !noBackup) {
|
|
9201
|
-
log.info(`Backup: ${backupRoot}`);
|
|
9202
|
-
log.info('Next: git add specs/changes/ && git commit -m "chore: migrate changes to YAML format"');
|
|
9203
|
-
log.info("When stable, remove backup: rm -rf .cdd/migrate-backup/");
|
|
9674
|
+
if (!foundAny) {
|
|
9204
9675
|
}
|
|
9205
9676
|
}
|
|
9206
|
-
|
|
9207
|
-
|
|
9208
|
-
"src/commands/migrate.ts"() {
|
|
9209
|
-
"use strict";
|
|
9210
|
-
init_logger();
|
|
9677
|
+
if (findings.length === 0) {
|
|
9678
|
+
findings.push({ level: "ok", message: "model-policy roles match installed agent prompts" });
|
|
9211
9679
|
}
|
|
9212
|
-
|
|
9213
|
-
|
|
9214
|
-
|
|
9215
|
-
|
|
9216
|
-
|
|
9217
|
-
|
|
9218
|
-
|
|
9219
|
-
|
|
9220
|
-
|
|
9221
|
-
|
|
9222
|
-
|
|
9223
|
-
|
|
9224
|
-
|
|
9225
|
-
|
|
9226
|
-
|
|
9227
|
-
|
|
9228
|
-
|
|
9229
|
-
|
|
9680
|
+
return findings;
|
|
9681
|
+
}
|
|
9682
|
+
async function checkAgentLint(cwd) {
|
|
9683
|
+
const agentsDir = join21(cwd, ".claude", "agents");
|
|
9684
|
+
if (!existsSync18(agentsDir))
|
|
9685
|
+
return [];
|
|
9686
|
+
try {
|
|
9687
|
+
const { readdirSync: rds, readFileSync: rfs } = await import("fs");
|
|
9688
|
+
const files = rds(agentsDir).filter((f) => f.endsWith(".md"));
|
|
9689
|
+
if (files.length === 0)
|
|
9690
|
+
return [];
|
|
9691
|
+
const findings = [];
|
|
9692
|
+
for (const filename of files) {
|
|
9693
|
+
const content = rfs(join21(agentsDir, filename), "utf8");
|
|
9694
|
+
const artifactsSection = content.match(
|
|
9695
|
+
/### Required artifacts for this agent\s*\n[\s\S]*?(?=\n#{2,3} |\n---|\s*$)/
|
|
9696
|
+
)?.[0];
|
|
9697
|
+
if (!artifactsSection || !/```ya?ml\s*\nartifacts:/.test(artifactsSection)) {
|
|
9698
|
+
findings.push({
|
|
9699
|
+
level: "warning",
|
|
9700
|
+
message: `lint-agents: ${filename}: missing artifacts YAML block in Required artifacts section`
|
|
9701
|
+
});
|
|
9702
|
+
}
|
|
9703
|
+
const readScopeCount = (content.match(/^## Read scope\s*$/gm) ?? []).length;
|
|
9704
|
+
if (readScopeCount > 1) {
|
|
9705
|
+
findings.push({
|
|
9706
|
+
level: "warning",
|
|
9707
|
+
message: `lint-agents: ${filename}: duplicate ## Read scope headings (${readScopeCount})`
|
|
9708
|
+
});
|
|
9709
|
+
}
|
|
9230
9710
|
}
|
|
9231
|
-
if (
|
|
9232
|
-
|
|
9711
|
+
if (findings.length === 0) {
|
|
9712
|
+
findings.push({ level: "ok", message: "lint-agents: all agent prompts pass shape checks" });
|
|
9233
9713
|
}
|
|
9714
|
+
return findings;
|
|
9715
|
+
} catch {
|
|
9716
|
+
return [];
|
|
9234
9717
|
}
|
|
9235
9718
|
}
|
|
9236
|
-
function
|
|
9237
|
-
|
|
9238
|
-
|
|
9239
|
-
|
|
9240
|
-
|
|
9241
|
-
if (
|
|
9242
|
-
|
|
9719
|
+
function checkCodeMap(cwd) {
|
|
9720
|
+
const findings = [];
|
|
9721
|
+
const mapPath = join21(cwd, ".cdd", "code-map.yml");
|
|
9722
|
+
if (!existsSync18(mapPath)) {
|
|
9723
|
+
const probe2 = checkCodeMapFreshness(cwd);
|
|
9724
|
+
if (probe2.status === "config-error") {
|
|
9725
|
+
findings.push({ level: "warning", message: `.cdd/code-map-config.yml is invalid: ${probe2.configError}` });
|
|
9726
|
+
} else if (probe2.status === "missing-with-sources") {
|
|
9727
|
+
findings.push({ level: "warning", message: ".cdd/code-map.yml is missing; run `cdd-kit code-map`" });
|
|
9243
9728
|
}
|
|
9729
|
+
return findings;
|
|
9730
|
+
}
|
|
9731
|
+
const probe = checkCodeMapFreshness(cwd);
|
|
9732
|
+
if (probe.status === "config-error") {
|
|
9733
|
+
findings.push({ level: "warning", message: `.cdd/code-map-config.yml is invalid: ${probe.configError}` });
|
|
9734
|
+
return findings;
|
|
9244
9735
|
}
|
|
9245
|
-
if (
|
|
9246
|
-
|
|
9736
|
+
if (probe.status === "stale") {
|
|
9737
|
+
const top = probe.staleFiles.slice(0, 3).join(", ");
|
|
9738
|
+
const more = probe.staleCount > 3 ? ` (+${probe.staleCount - 3} more)` : "";
|
|
9739
|
+
findings.push({ level: "warning", message: `code-map stale: ${top}${more}; run \`cdd-kit code-map\`` });
|
|
9247
9740
|
}
|
|
9248
|
-
|
|
9249
|
-
|
|
9250
|
-
|
|
9251
|
-
|
|
9252
|
-
|
|
9741
|
+
const text = readFileSync19(mapPath, "utf8");
|
|
9742
|
+
const m = text.match(/^# files: (\d+), src-lines: (\d+), map-lines: (\d+), compression: ([\d.]+)x/m);
|
|
9743
|
+
if (m) {
|
|
9744
|
+
findings.push({ level: "ok", message: `code-map: ${m[1]} files, ${m[4]}x compression` });
|
|
9745
|
+
} else {
|
|
9746
|
+
findings.push({ level: "warning", message: ".cdd/code-map.yml has no metadata header (regenerate with cdd-kit 2.0.5+)" });
|
|
9253
9747
|
}
|
|
9748
|
+
return findings;
|
|
9254
9749
|
}
|
|
9255
|
-
async function
|
|
9256
|
-
const cwd = process.cwd();
|
|
9750
|
+
async function buildDoctorReport(cwd, opts) {
|
|
9257
9751
|
const requestedProvider = opts.provider ?? "auto";
|
|
9258
9752
|
if (!validateProviderOption(requestedProvider)) {
|
|
9259
9753
|
log.error(`Invalid provider: ${requestedProvider}. Use auto, claude, codex, or both.`);
|
|
9260
9754
|
process.exit(1);
|
|
9261
9755
|
}
|
|
9756
|
+
const strict = opts.strict ?? false;
|
|
9262
9757
|
const provider = inferProvider(cwd, requestedProvider);
|
|
9263
|
-
const
|
|
9264
|
-
|
|
9265
|
-
|
|
9266
|
-
planMissingFiles(ASSET.testsTemplates, join20(cwd, "tests", "templates"), "tests/templates", plan);
|
|
9267
|
-
planMissingFiles(ASSET.ci, join20(cwd, "ci"), "ci", plan);
|
|
9268
|
-
planMissingFiles(ASSET.githubWorkflows, join20(cwd, ".github", "workflows"), ".github/workflows", plan);
|
|
9269
|
-
planMissingFiles(ASSET.cddConfig, join20(cwd, ".cdd"), ".cdd", plan);
|
|
9270
|
-
planProviderGuidance(cwd, provider, plan);
|
|
9271
|
-
log.blank();
|
|
9272
|
-
log.info(`Upgrade provider: ${provider}`);
|
|
9273
|
-
if (plan.length === 0) {
|
|
9274
|
-
log.ok("No missing cdd-kit project files found.");
|
|
9275
|
-
if (opts.migrateChanges) {
|
|
9276
|
-
log.blank();
|
|
9277
|
-
log.info("Running change migration flow...");
|
|
9278
|
-
await migrate(void 0, {
|
|
9279
|
-
all: true,
|
|
9280
|
-
dryRun: !opts.yes,
|
|
9281
|
-
enableContextGovernance: opts.enableContextGovernance
|
|
9282
|
-
});
|
|
9283
|
-
}
|
|
9284
|
-
log.blank();
|
|
9285
|
-
return;
|
|
9758
|
+
const findings = [];
|
|
9759
|
+
for (const relPath of ["contracts", "specs/templates", ".cdd/context-policy.json", ".cdd/model-policy.json"]) {
|
|
9760
|
+
findings.push(fileExists(cwd, relPath) ? { level: "ok", message: `${relPath} exists` } : { level: "warning", message: `${relPath} is missing; run cdd-kit upgrade --yes` });
|
|
9286
9761
|
}
|
|
9287
|
-
|
|
9288
|
-
|
|
9289
|
-
|
|
9290
|
-
if (!
|
|
9291
|
-
|
|
9292
|
-
|
|
9293
|
-
|
|
9294
|
-
|
|
9295
|
-
|
|
9296
|
-
|
|
9297
|
-
|
|
9298
|
-
|
|
9299
|
-
|
|
9762
|
+
if ((provider === "claude" || provider === "both") && !fileExists(cwd, "CLAUDE.md")) {
|
|
9763
|
+
findings.push({ level: "warning", message: "CLAUDE.md is missing for Claude provider; run cdd-kit upgrade --provider claude --yes" });
|
|
9764
|
+
}
|
|
9765
|
+
if ((provider === "claude" || provider === "both") && !fileExists(cwd, "AGENTS.md")) {
|
|
9766
|
+
findings.push({ level: "warning", message: "AGENTS.md is missing for Claude provider; run cdd-kit upgrade --provider claude --yes" });
|
|
9767
|
+
}
|
|
9768
|
+
if ((provider === "codex" || provider === "both") && !fileExists(cwd, "CODEX.md")) {
|
|
9769
|
+
findings.push({ level: "warning", message: "CODEX.md is missing for Codex provider; run cdd-kit upgrade --provider codex --yes" });
|
|
9770
|
+
}
|
|
9771
|
+
findings.push(...checkContextFreshness(cwd));
|
|
9772
|
+
findings.push(...checkModelPolicyDrift(cwd));
|
|
9773
|
+
findings.push(...await checkAgentLint(cwd));
|
|
9774
|
+
findings.push(...checkCodeMap(cwd));
|
|
9775
|
+
const errors = findings.filter((finding) => finding.level === "error").length;
|
|
9776
|
+
const warnings = findings.filter((finding) => finding.level === "warning").length;
|
|
9777
|
+
return {
|
|
9778
|
+
provider,
|
|
9779
|
+
strict,
|
|
9780
|
+
findings,
|
|
9781
|
+
errors,
|
|
9782
|
+
warnings,
|
|
9783
|
+
ok: errors === 0 && (!strict || warnings === 0)
|
|
9784
|
+
};
|
|
9785
|
+
}
|
|
9786
|
+
async function attemptAutoFixes(cwd, report) {
|
|
9787
|
+
const fixed = [];
|
|
9788
|
+
const remaining = [];
|
|
9789
|
+
for (const finding of report.findings) {
|
|
9790
|
+
if (finding.level !== "warning") {
|
|
9791
|
+
remaining.push(finding);
|
|
9792
|
+
continue;
|
|
9793
|
+
}
|
|
9794
|
+
if (/specs\/context indexes are missing|inputs changed|older cdd-kit|older than/i.test(finding.message)) {
|
|
9795
|
+
try {
|
|
9796
|
+
const { contextScan: contextScan2 } = await Promise.resolve().then(() => (init_context_scan(), context_scan_exports));
|
|
9797
|
+
await contextScan2();
|
|
9798
|
+
fixed.push(`ran context-scan to refresh specs/context/`);
|
|
9799
|
+
continue;
|
|
9800
|
+
} catch (err) {
|
|
9801
|
+
remaining.push({ level: "warning", message: `${finding.message} (auto-fix failed: ${err.message})` });
|
|
9802
|
+
continue;
|
|
9803
|
+
}
|
|
9804
|
+
}
|
|
9805
|
+
if (/code-map stale|code-map\.yml is missing/i.test(finding.message)) {
|
|
9806
|
+
try {
|
|
9807
|
+
const { codeMap: codeMap2 } = await Promise.resolve().then(() => (init_code_map(), code_map_exports));
|
|
9808
|
+
await codeMap2({ path: ".", out: ".cdd/code-map.yml", include: [], exclude: [], check: false, maxLines: 1e5 });
|
|
9809
|
+
fixed.push("regenerated .cdd/code-map.yml");
|
|
9810
|
+
continue;
|
|
9811
|
+
} catch (err) {
|
|
9812
|
+
remaining.push({ level: "warning", message: `${finding.message} (auto-fix failed: ${err.message})` });
|
|
9813
|
+
continue;
|
|
9814
|
+
}
|
|
9815
|
+
}
|
|
9816
|
+
if (/model-policy\.json has no role bindings/i.test(finding.message)) {
|
|
9817
|
+
const policyPath = join21(cwd, ".cdd", "model-policy.json");
|
|
9818
|
+
try {
|
|
9819
|
+
let existing = {};
|
|
9820
|
+
try {
|
|
9821
|
+
existing = JSON.parse(readFileSync19(policyPath, "utf8"));
|
|
9822
|
+
} catch {
|
|
9823
|
+
}
|
|
9824
|
+
const merged = {
|
|
9825
|
+
...existing,
|
|
9826
|
+
roles: {
|
|
9827
|
+
"change-classifier": "claude-opus-4-7",
|
|
9828
|
+
"spec-architect": "claude-opus-4-7",
|
|
9829
|
+
"qa-reviewer": "claude-opus-4-7",
|
|
9830
|
+
"contract-reviewer": "claude-sonnet-4-6",
|
|
9831
|
+
"test-strategist": "claude-sonnet-4-6",
|
|
9832
|
+
"backend-engineer": "claude-sonnet-4-6",
|
|
9833
|
+
"frontend-engineer": "claude-sonnet-4-6",
|
|
9834
|
+
"ci-cd-gatekeeper": "claude-sonnet-4-6",
|
|
9835
|
+
"e2e-resilience-engineer": "claude-sonnet-4-6",
|
|
9836
|
+
"monkey-test-engineer": "claude-sonnet-4-6",
|
|
9837
|
+
"stress-soak-engineer": "claude-sonnet-4-6",
|
|
9838
|
+
"ui-ux-reviewer": "claude-sonnet-4-6",
|
|
9839
|
+
"visual-reviewer": "claude-haiku-4-5-20251001",
|
|
9840
|
+
"dependency-security-reviewer": "claude-sonnet-4-6",
|
|
9841
|
+
"spec-drift-auditor": "claude-opus-4-7",
|
|
9842
|
+
"repo-context-scanner": "claude-haiku-4-5-20251001"
|
|
9843
|
+
}
|
|
9844
|
+
};
|
|
9845
|
+
const { writeFileSync: writeFileSync14 } = await import("fs");
|
|
9846
|
+
writeFileSync14(policyPath, JSON.stringify(merged, null, 2) + "\n", "utf8");
|
|
9847
|
+
fixed.push(`populated .cdd/model-policy.json with default role bindings`);
|
|
9848
|
+
continue;
|
|
9849
|
+
} catch (err) {
|
|
9850
|
+
remaining.push({ level: "warning", message: `${finding.message} (auto-fix failed: ${err.message})` });
|
|
9851
|
+
continue;
|
|
9852
|
+
}
|
|
9853
|
+
}
|
|
9854
|
+
if (/\.cdd\/.*is missing|run cdd-kit upgrade/i.test(finding.message)) {
|
|
9855
|
+
remaining.push({
|
|
9856
|
+
level: "warning",
|
|
9857
|
+
message: `${finding.message} (run \`cdd-kit upgrade --yes\` manually \u2014 too invasive for --fix)`
|
|
9300
9858
|
});
|
|
9859
|
+
continue;
|
|
9301
9860
|
}
|
|
9861
|
+
remaining.push(finding);
|
|
9862
|
+
}
|
|
9863
|
+
return { fixed, remaining };
|
|
9864
|
+
}
|
|
9865
|
+
async function doctor(opts = {}) {
|
|
9866
|
+
const cwd = process.cwd();
|
|
9867
|
+
let report = await buildDoctorReport(cwd, opts);
|
|
9868
|
+
if (opts.fix && !opts.json) {
|
|
9302
9869
|
log.blank();
|
|
9870
|
+
log.info("Doctor --fix: attempting safe auto-resolutions\u2026");
|
|
9871
|
+
const { fixed, remaining } = await attemptAutoFixes(cwd, report);
|
|
9872
|
+
for (const f of fixed)
|
|
9873
|
+
log.ok(`fixed: ${f}`);
|
|
9874
|
+
if (fixed.length > 0) {
|
|
9875
|
+
report = await buildDoctorReport(cwd, opts);
|
|
9876
|
+
} else {
|
|
9877
|
+
log.info("no auto-fixable findings");
|
|
9878
|
+
}
|
|
9879
|
+
}
|
|
9880
|
+
if (opts.json) {
|
|
9881
|
+
console.log(JSON.stringify(report, null, 2));
|
|
9882
|
+
if (!report.ok)
|
|
9883
|
+
process.exit(1);
|
|
9303
9884
|
return;
|
|
9304
9885
|
}
|
|
9305
|
-
|
|
9306
|
-
|
|
9307
|
-
|
|
9308
|
-
|
|
9309
|
-
|
|
9310
|
-
|
|
9311
|
-
|
|
9312
|
-
|
|
9313
|
-
|
|
9314
|
-
...existing,
|
|
9315
|
-
provider,
|
|
9316
|
-
generated_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
9317
|
-
roles: existing.roles && typeof existing.roles === "object" ? existing.roles : {}
|
|
9318
|
-
};
|
|
9319
|
-
writeFileSync9(modelPolicyPath, JSON.stringify(merged, null, 2) + "\n", "utf8");
|
|
9886
|
+
log.blank();
|
|
9887
|
+
log.info(`Doctor provider: ${report.provider}`);
|
|
9888
|
+
for (const finding of report.findings) {
|
|
9889
|
+
if (finding.level === "ok")
|
|
9890
|
+
log.ok(finding.message);
|
|
9891
|
+
else if (finding.level === "warning")
|
|
9892
|
+
log.warn(finding.message);
|
|
9893
|
+
else
|
|
9894
|
+
log.error(finding.message);
|
|
9320
9895
|
}
|
|
9321
9896
|
log.blank();
|
|
9322
|
-
|
|
9323
|
-
|
|
9324
|
-
|
|
9325
|
-
|
|
9326
|
-
|
|
9327
|
-
|
|
9328
|
-
|
|
9329
|
-
|
|
9330
|
-
enableContextGovernance: opts.enableContextGovernance
|
|
9331
|
-
});
|
|
9897
|
+
if (!report.ok) {
|
|
9898
|
+
log.error(report.strict && report.errors === 0 ? `doctor failed in strict mode with ${report.warnings} warning(s)` : `doctor failed with ${report.errors} error(s)`);
|
|
9899
|
+
process.exit(1);
|
|
9900
|
+
}
|
|
9901
|
+
if (report.warnings > 0) {
|
|
9902
|
+
log.warn(`doctor completed with ${report.warnings} warning(s)`);
|
|
9903
|
+
} else {
|
|
9904
|
+
log.ok("doctor passed");
|
|
9332
9905
|
}
|
|
9333
9906
|
log.blank();
|
|
9334
9907
|
}
|
|
9335
|
-
var
|
|
9336
|
-
"src/commands/
|
|
9908
|
+
var init_doctor = __esm({
|
|
9909
|
+
"src/commands/doctor.ts"() {
|
|
9337
9910
|
"use strict";
|
|
9338
|
-
init_paths();
|
|
9339
9911
|
init_logger();
|
|
9340
9912
|
init_provider();
|
|
9341
|
-
|
|
9913
|
+
init_freshness();
|
|
9342
9914
|
}
|
|
9343
9915
|
});
|
|
9344
9916
|
|
|
@@ -9347,8 +9919,8 @@ var lint_agents_exports = {};
|
|
|
9347
9919
|
__export(lint_agents_exports, {
|
|
9348
9920
|
lintAgents: () => lintAgents
|
|
9349
9921
|
});
|
|
9350
|
-
import { readdirSync as
|
|
9351
|
-
import { join as
|
|
9922
|
+
import { readdirSync as readdirSync12, readFileSync as readFileSync20 } from "fs";
|
|
9923
|
+
import { join as join22 } from "path";
|
|
9352
9924
|
import { load as yamlLoad2 } from "js-yaml";
|
|
9353
9925
|
function extractRequiredArtifactsSection(content) {
|
|
9354
9926
|
const match = content.match(
|
|
@@ -9389,20 +9961,20 @@ function hasFlatBacktickKeysWithoutFence(section) {
|
|
|
9389
9961
|
}
|
|
9390
9962
|
async function lintAgents(opts) {
|
|
9391
9963
|
const cwd = process.cwd();
|
|
9392
|
-
const agentsDir =
|
|
9964
|
+
const agentsDir = join22(cwd, ".claude", "agents");
|
|
9393
9965
|
let files;
|
|
9394
9966
|
try {
|
|
9395
|
-
files =
|
|
9967
|
+
files = readdirSync12(agentsDir).filter((f) => f.endsWith(".md")).sort();
|
|
9396
9968
|
} catch {
|
|
9397
9969
|
log.error(`lint-agents: cannot read ${agentsDir} \u2014 is this a cdd-kit project?`);
|
|
9398
9970
|
return 1;
|
|
9399
9971
|
}
|
|
9400
9972
|
const violations = [];
|
|
9401
9973
|
for (const filename of files) {
|
|
9402
|
-
const filePath =
|
|
9974
|
+
const filePath = join22(agentsDir, filename);
|
|
9403
9975
|
let content;
|
|
9404
9976
|
try {
|
|
9405
|
-
content =
|
|
9977
|
+
content = readFileSync20(filePath, "utf8");
|
|
9406
9978
|
} catch {
|
|
9407
9979
|
violations.push({
|
|
9408
9980
|
file: filename,
|
|
@@ -9514,28 +10086,28 @@ var archive_exports = {};
|
|
|
9514
10086
|
__export(archive_exports, {
|
|
9515
10087
|
archive: () => archive
|
|
9516
10088
|
});
|
|
9517
|
-
import { join as
|
|
9518
|
-
import { existsSync as
|
|
10089
|
+
import { join as join23 } from "path";
|
|
10090
|
+
import { existsSync as existsSync19, mkdirSync as mkdirSync10, renameSync as renameSync2, readFileSync as readFileSync21, writeFileSync as writeFileSync11, appendFileSync, cpSync as cpSync3, rmSync as rmSync3 } from "fs";
|
|
9519
10091
|
import yaml4 from "js-yaml";
|
|
9520
10092
|
async function archive(changeId) {
|
|
9521
10093
|
const cwd = process.cwd();
|
|
9522
|
-
const changeDir =
|
|
10094
|
+
const changeDir = join23(cwd, "specs", "changes", changeId);
|
|
9523
10095
|
const archiveYear = (/* @__PURE__ */ new Date()).getFullYear().toString();
|
|
9524
|
-
const archiveBase =
|
|
9525
|
-
const archiveDir =
|
|
9526
|
-
const indexPath =
|
|
9527
|
-
if (!
|
|
10096
|
+
const archiveBase = join23(cwd, "specs", "archive", archiveYear);
|
|
10097
|
+
const archiveDir = join23(archiveBase, changeId);
|
|
10098
|
+
const indexPath = join23(cwd, "specs", "archive", "INDEX.md");
|
|
10099
|
+
if (!existsSync19(changeDir)) {
|
|
9528
10100
|
log.error(`Change not found: specs/changes/${changeId}`);
|
|
9529
10101
|
process.exit(1);
|
|
9530
10102
|
}
|
|
9531
|
-
if (
|
|
10103
|
+
if (existsSync19(archiveDir)) {
|
|
9532
10104
|
log.error(`Already archived: specs/archive/${archiveYear}/${changeId}`);
|
|
9533
10105
|
process.exit(1);
|
|
9534
10106
|
}
|
|
9535
|
-
const tasksPath =
|
|
9536
|
-
if (
|
|
10107
|
+
const tasksPath = join23(changeDir, "tasks.yml");
|
|
10108
|
+
if (existsSync19(tasksPath)) {
|
|
9537
10109
|
try {
|
|
9538
|
-
const raw =
|
|
10110
|
+
const raw = readFileSync21(tasksPath, "utf8");
|
|
9539
10111
|
const data = yaml4.load(raw);
|
|
9540
10112
|
if (data?.status === "gate-blocked") {
|
|
9541
10113
|
log.warn("tasks.yml has status: gate-blocked \u2014 archiving anyway (change was paused).");
|
|
@@ -9548,8 +10120,8 @@ async function archive(changeId) {
|
|
|
9548
10120
|
log.warn("tasks.yml could not be parsed \u2014 archiving anyway.");
|
|
9549
10121
|
}
|
|
9550
10122
|
}
|
|
9551
|
-
if (!
|
|
9552
|
-
|
|
10123
|
+
if (!existsSync19(archiveBase)) {
|
|
10124
|
+
mkdirSync10(archiveBase, { recursive: true });
|
|
9553
10125
|
}
|
|
9554
10126
|
try {
|
|
9555
10127
|
renameSync2(changeDir, archiveDir);
|
|
@@ -9565,8 +10137,8 @@ async function archive(changeId) {
|
|
|
9565
10137
|
const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
9566
10138
|
const indexLine = `| ${changeId} | ${archiveYear} | ${today} | specs/archive/${archiveYear}/${changeId}/ |
|
|
9567
10139
|
`;
|
|
9568
|
-
if (!
|
|
9569
|
-
|
|
10140
|
+
if (!existsSync19(indexPath)) {
|
|
10141
|
+
writeFileSync11(indexPath, `# Archive Index
|
|
9570
10142
|
|
|
9571
10143
|
| change-id | year | archived-date | path |
|
|
9572
10144
|
|---|---|---|---|
|
|
@@ -9590,37 +10162,37 @@ var abandon_exports = {};
|
|
|
9590
10162
|
__export(abandon_exports, {
|
|
9591
10163
|
abandon: () => abandon
|
|
9592
10164
|
});
|
|
9593
|
-
import { join as
|
|
9594
|
-
import { existsSync as
|
|
10165
|
+
import { join as join24 } from "path";
|
|
10166
|
+
import { existsSync as existsSync20, readFileSync as readFileSync22, writeFileSync as writeFileSync12, appendFileSync as appendFileSync2, mkdirSync as mkdirSync11 } from "fs";
|
|
9595
10167
|
import yaml5 from "js-yaml";
|
|
9596
10168
|
async function abandon(changeId, opts) {
|
|
9597
10169
|
const cwd = process.cwd();
|
|
9598
|
-
const changeDir =
|
|
9599
|
-
const tasksPath =
|
|
9600
|
-
if (!
|
|
10170
|
+
const changeDir = join24(cwd, "specs", "changes", changeId);
|
|
10171
|
+
const tasksPath = join24(changeDir, "tasks.yml");
|
|
10172
|
+
if (!existsSync20(changeDir)) {
|
|
9601
10173
|
log.error(`Change not found: specs/changes/${changeId}`);
|
|
9602
10174
|
process.exit(1);
|
|
9603
10175
|
}
|
|
9604
|
-
if (
|
|
9605
|
-
const raw =
|
|
10176
|
+
if (existsSync20(tasksPath)) {
|
|
10177
|
+
const raw = readFileSync22(tasksPath, "utf8");
|
|
9606
10178
|
const data = yaml5.load(raw) ?? {};
|
|
9607
10179
|
data["status"] = "abandoned";
|
|
9608
10180
|
if (!data["change-id"]) {
|
|
9609
10181
|
data["change-id"] = changeId;
|
|
9610
10182
|
}
|
|
9611
|
-
|
|
10183
|
+
writeFileSync12(tasksPath, yaml5.dump(data, { lineWidth: -1, noRefs: true }), "utf8");
|
|
9612
10184
|
}
|
|
9613
10185
|
const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
9614
|
-
const archiveDir =
|
|
9615
|
-
const indexPath =
|
|
10186
|
+
const archiveDir = join24(cwd, "specs", "archive");
|
|
10187
|
+
const indexPath = join24(archiveDir, "INDEX.md");
|
|
9616
10188
|
const reason = opts.reason ?? "no reason given";
|
|
9617
10189
|
const indexLine = `| ${changeId} | abandoned | ${today} | ${reason} |
|
|
9618
10190
|
`;
|
|
9619
|
-
if (!
|
|
9620
|
-
|
|
10191
|
+
if (!existsSync20(archiveDir)) {
|
|
10192
|
+
mkdirSync11(archiveDir, { recursive: true });
|
|
9621
10193
|
}
|
|
9622
|
-
if (!
|
|
9623
|
-
|
|
10194
|
+
if (!existsSync20(indexPath)) {
|
|
10195
|
+
writeFileSync12(indexPath, `# Archive Index
|
|
9624
10196
|
|
|
9625
10197
|
| change-id | status | date | notes |
|
|
9626
10198
|
|---|---|---|---|
|
|
@@ -9644,28 +10216,28 @@ var list_changes_exports = {};
|
|
|
9644
10216
|
__export(list_changes_exports, {
|
|
9645
10217
|
listChanges: () => listChanges
|
|
9646
10218
|
});
|
|
9647
|
-
import { join as
|
|
9648
|
-
import { existsSync as
|
|
10219
|
+
import { join as join25 } from "path";
|
|
10220
|
+
import { existsSync as existsSync21, readdirSync as readdirSync13, readFileSync as readFileSync23 } from "fs";
|
|
9649
10221
|
import yaml6 from "js-yaml";
|
|
9650
10222
|
async function listChanges() {
|
|
9651
10223
|
const cwd = process.cwd();
|
|
9652
|
-
const changesDir =
|
|
10224
|
+
const changesDir = join25(cwd, "specs", "changes");
|
|
9653
10225
|
log.blank();
|
|
9654
10226
|
const active = [];
|
|
9655
|
-
if (
|
|
9656
|
-
active.push(...
|
|
10227
|
+
if (existsSync21(changesDir)) {
|
|
10228
|
+
active.push(...readdirSync13(changesDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name));
|
|
9657
10229
|
}
|
|
9658
10230
|
if (active.length === 0) {
|
|
9659
10231
|
log.info("No active changes in specs/changes/");
|
|
9660
10232
|
} else {
|
|
9661
10233
|
log.info("Active changes:");
|
|
9662
10234
|
for (const id of active) {
|
|
9663
|
-
const tasksPath =
|
|
10235
|
+
const tasksPath = join25(changesDir, id, "tasks.yml");
|
|
9664
10236
|
let status = "in-progress";
|
|
9665
10237
|
let pending = 0;
|
|
9666
|
-
if (
|
|
10238
|
+
if (existsSync21(tasksPath)) {
|
|
9667
10239
|
try {
|
|
9668
|
-
const raw =
|
|
10240
|
+
const raw = readFileSync23(tasksPath, "utf8");
|
|
9669
10241
|
const data = yaml6.load(raw);
|
|
9670
10242
|
if (data?.status)
|
|
9671
10243
|
status = data.status;
|
|
@@ -9696,8 +10268,8 @@ __export(context_exports, {
|
|
|
9696
10268
|
rejectContextExpansion: () => rejectContextExpansion,
|
|
9697
10269
|
requestContextExpansion: () => requestContextExpansion
|
|
9698
10270
|
});
|
|
9699
|
-
import { existsSync as
|
|
9700
|
-
import { join as
|
|
10271
|
+
import { existsSync as existsSync22, readFileSync as readFileSync24, writeFileSync as writeFileSync13 } from "fs";
|
|
10272
|
+
import { join as join26 } from "path";
|
|
9701
10273
|
function normalizePath(path) {
|
|
9702
10274
|
return path.replace(/\\/g, "/").replace(/^\.\//, "").trim();
|
|
9703
10275
|
}
|
|
@@ -9711,18 +10283,18 @@ function validateRepoRelativePath(path) {
|
|
|
9711
10283
|
return null;
|
|
9712
10284
|
}
|
|
9713
10285
|
function manifestPathFor(changeId) {
|
|
9714
|
-
return
|
|
10286
|
+
return join26(process.cwd(), "specs", "changes", changeId, "context-manifest.md");
|
|
9715
10287
|
}
|
|
9716
10288
|
function readManifest(changeId) {
|
|
9717
10289
|
const manifestPath = manifestPathFor(changeId);
|
|
9718
|
-
if (!
|
|
10290
|
+
if (!existsSync22(manifestPath)) {
|
|
9719
10291
|
log.error(`context manifest not found: specs/changes/${changeId}/context-manifest.md`);
|
|
9720
10292
|
process.exit(1);
|
|
9721
10293
|
}
|
|
9722
|
-
return
|
|
10294
|
+
return readFileSync24(manifestPath, "utf8");
|
|
9723
10295
|
}
|
|
9724
10296
|
function writeManifest(changeId, content) {
|
|
9725
|
-
|
|
10297
|
+
writeFileSync13(manifestPathFor(changeId), content.endsWith("\n") ? content : `${content}
|
|
9726
10298
|
`, "utf8");
|
|
9727
10299
|
}
|
|
9728
10300
|
function sectionBody(content, heading) {
|
|
@@ -9947,9 +10519,9 @@ var init_context = __esm({
|
|
|
9947
10519
|
});
|
|
9948
10520
|
|
|
9949
10521
|
// src/cli/index.ts
|
|
9950
|
-
import { readFileSync as
|
|
10522
|
+
import { readFileSync as readFileSync25 } from "fs";
|
|
9951
10523
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
9952
|
-
import { dirname as
|
|
10524
|
+
import { dirname as dirname7, join as join27 } from "path";
|
|
9953
10525
|
import { Command } from "commander";
|
|
9954
10526
|
|
|
9955
10527
|
// src/commands/init.ts
|
|
@@ -10364,167 +10936,8 @@ async function init(opts) {
|
|
|
10364
10936
|
log.blank();
|
|
10365
10937
|
}
|
|
10366
10938
|
|
|
10367
|
-
// src/
|
|
10368
|
-
|
|
10369
|
-
init_logger();
|
|
10370
|
-
init_provider();
|
|
10371
|
-
import { join as join7 } from "path";
|
|
10372
|
-
import { existsSync as existsSync6, mkdirSync as mkdirSync3, readdirSync as readdirSync3, copyFileSync as copyFileSync2, readFileSync as readFileSync5 } from "fs";
|
|
10373
|
-
import { createHash } from "crypto";
|
|
10374
|
-
import { homedir as homedir2 } from "os";
|
|
10375
|
-
function fileHash(filePath) {
|
|
10376
|
-
const buf = readFileSync5(filePath);
|
|
10377
|
-
return createHash("sha256").update(buf).digest("hex");
|
|
10378
|
-
}
|
|
10379
|
-
function diffDir(src, dest) {
|
|
10380
|
-
const entries = [];
|
|
10381
|
-
if (!existsSync6(src))
|
|
10382
|
-
return entries;
|
|
10383
|
-
function walk(currentSrc, currentDest) {
|
|
10384
|
-
const items = readdirSync3(currentSrc, { withFileTypes: true });
|
|
10385
|
-
for (const item of items) {
|
|
10386
|
-
const srcPath = join7(currentSrc, item.name);
|
|
10387
|
-
const destPath = join7(currentDest, item.name);
|
|
10388
|
-
if (item.isDirectory()) {
|
|
10389
|
-
walk(srcPath, destPath);
|
|
10390
|
-
} else {
|
|
10391
|
-
if (!existsSync6(destPath)) {
|
|
10392
|
-
entries.push({ src: srcPath, dest: destPath, action: "add" });
|
|
10393
|
-
} else if (fileHash(srcPath) !== fileHash(destPath)) {
|
|
10394
|
-
entries.push({ src: srcPath, dest: destPath, action: "overwrite" });
|
|
10395
|
-
} else {
|
|
10396
|
-
entries.push({ src: srcPath, dest: destPath, action: "skip" });
|
|
10397
|
-
}
|
|
10398
|
-
}
|
|
10399
|
-
}
|
|
10400
|
-
}
|
|
10401
|
-
walk(src, dest);
|
|
10402
|
-
return entries;
|
|
10403
|
-
}
|
|
10404
|
-
function applyDir(entries) {
|
|
10405
|
-
let count = 0;
|
|
10406
|
-
for (const e of entries) {
|
|
10407
|
-
if (e.action === "skip")
|
|
10408
|
-
continue;
|
|
10409
|
-
mkdirSync3(join7(e.dest, ".."), { recursive: true });
|
|
10410
|
-
copyFileSync2(e.src, e.dest);
|
|
10411
|
-
count += 1;
|
|
10412
|
-
}
|
|
10413
|
-
return count;
|
|
10414
|
-
}
|
|
10415
|
-
function backupDir(dir, backupDest) {
|
|
10416
|
-
if (!existsSync6(dir))
|
|
10417
|
-
return;
|
|
10418
|
-
mkdirSync3(backupDest, { recursive: true });
|
|
10419
|
-
function walk(src, dst) {
|
|
10420
|
-
const items = readdirSync3(src, { withFileTypes: true });
|
|
10421
|
-
for (const item of items) {
|
|
10422
|
-
const s = join7(src, item.name);
|
|
10423
|
-
const d = join7(dst, item.name);
|
|
10424
|
-
if (item.isDirectory()) {
|
|
10425
|
-
mkdirSync3(d, { recursive: true });
|
|
10426
|
-
walk(s, d);
|
|
10427
|
-
} else
|
|
10428
|
-
copyFileSync2(s, d);
|
|
10429
|
-
}
|
|
10430
|
-
}
|
|
10431
|
-
walk(dir, backupDest);
|
|
10432
|
-
}
|
|
10433
|
-
async function update(opts) {
|
|
10434
|
-
if (opts.postinstall) {
|
|
10435
|
-
if (!existsSync6(join7(SKILLS_HOME, "contract-driven-delivery"))) {
|
|
10436
|
-
return;
|
|
10437
|
-
}
|
|
10438
|
-
opts.yes = true;
|
|
10439
|
-
opts.provider = "claude";
|
|
10440
|
-
}
|
|
10441
|
-
const quiet = !!opts.postinstall;
|
|
10442
|
-
if (!quiet)
|
|
10443
|
-
log.blank();
|
|
10444
|
-
const cwd = process.cwd();
|
|
10445
|
-
const requestedProvider = opts.provider ?? "auto";
|
|
10446
|
-
if (!validateProviderOption(requestedProvider)) {
|
|
10447
|
-
log.error(`Invalid provider: ${requestedProvider}. Use auto, claude, codex, or both.`);
|
|
10448
|
-
process.exit(1);
|
|
10449
|
-
}
|
|
10450
|
-
const provider = inferProvider(cwd, requestedProvider);
|
|
10451
|
-
const updateClaudeAssets = provider === "claude" || provider === "both";
|
|
10452
|
-
const agentDiff = updateClaudeAssets ? diffDir(ASSET.agents, AGENTS_HOME) : [];
|
|
10453
|
-
const skillDiff = updateClaudeAssets ? readdirSync3(ASSET.skills, { withFileTypes: true }).filter((d) => d.isDirectory()).flatMap((d) => diffDir(join7(ASSET.skills, d.name), join7(SKILLS_HOME, d.name))) : [];
|
|
10454
|
-
const toWrite = [...agentDiff, ...skillDiff].filter((e) => e.action !== "skip");
|
|
10455
|
-
const toAdd = toWrite.filter((e) => e.action === "add");
|
|
10456
|
-
const toOver = toWrite.filter((e) => e.action === "overwrite");
|
|
10457
|
-
const toSkip = [...agentDiff, ...skillDiff].filter((e) => e.action === "skip");
|
|
10458
|
-
if (!quiet) {
|
|
10459
|
-
log.info(`Provider: ${provider}`);
|
|
10460
|
-
if (updateClaudeAssets) {
|
|
10461
|
-
log.info(`Dry-run diff \u2014 agents: ${AGENTS_HOME}`);
|
|
10462
|
-
log.info(`Dry-run diff \u2014 skills: ${SKILLS_HOME}`);
|
|
10463
|
-
} else {
|
|
10464
|
-
log.info("Codex provider has no global cdd-kit assets to update.");
|
|
10465
|
-
log.info("Project files are preserved; run cdd-kit init --local-only --provider codex to add missing local guidance.");
|
|
10466
|
-
}
|
|
10467
|
-
log.blank();
|
|
10468
|
-
if (toAdd.length)
|
|
10469
|
-
log.info(` + ${toAdd.length} file(s) would be added`);
|
|
10470
|
-
if (toOver.length)
|
|
10471
|
-
log.warn(` ~ ${toOver.length} file(s) would be overwritten (user edits lost without backup)`);
|
|
10472
|
-
if (toSkip.length)
|
|
10473
|
-
log.dim(` ${toSkip.length} file(s) unchanged (skipped)`);
|
|
10474
|
-
}
|
|
10475
|
-
if (toWrite.length === 0) {
|
|
10476
|
-
if (!quiet) {
|
|
10477
|
-
log.blank();
|
|
10478
|
-
log.ok("Already up to date \u2014 nothing to write.");
|
|
10479
|
-
log.blank();
|
|
10480
|
-
}
|
|
10481
|
-
return;
|
|
10482
|
-
}
|
|
10483
|
-
if (!quiet && !opts.yes) {
|
|
10484
|
-
log.blank();
|
|
10485
|
-
log.info("Run with --yes to apply changes. Example:");
|
|
10486
|
-
log.dim(" cdd-kit update --yes");
|
|
10487
|
-
log.blank();
|
|
10488
|
-
return;
|
|
10489
|
-
}
|
|
10490
|
-
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
10491
|
-
const backupRoot = join7(homedir2(), ".claude", ".cdd-kit-backup", timestamp);
|
|
10492
|
-
if (!quiet) {
|
|
10493
|
-
log.blank();
|
|
10494
|
-
log.info(`Backing up to ${backupRoot} \u2026`);
|
|
10495
|
-
}
|
|
10496
|
-
backupDir(AGENTS_HOME, join7(backupRoot, "agents"));
|
|
10497
|
-
backupDir(SKILLS_HOME, join7(backupRoot, "skills"));
|
|
10498
|
-
if (!quiet)
|
|
10499
|
-
log.ok(`Backup complete: ${backupRoot}`);
|
|
10500
|
-
if (!quiet)
|
|
10501
|
-
log.blank();
|
|
10502
|
-
let totalSynced = 0;
|
|
10503
|
-
if (updateClaudeAssets) {
|
|
10504
|
-
if (!quiet)
|
|
10505
|
-
log.info(`Updating agents \u2192 ${AGENTS_HOME}`);
|
|
10506
|
-
const agentCount = applyDir(agentDiff);
|
|
10507
|
-
if (!quiet)
|
|
10508
|
-
log.ok(`${agentCount} agent file(s) updated.`);
|
|
10509
|
-
totalSynced += agentCount;
|
|
10510
|
-
if (!quiet)
|
|
10511
|
-
log.info(`Updating skills \u2192 ${SKILLS_HOME}`);
|
|
10512
|
-
const skillCount = applyDir(skillDiff);
|
|
10513
|
-
if (!quiet)
|
|
10514
|
-
log.ok(`${skillCount} skill file(s) updated.`);
|
|
10515
|
-
totalSynced += skillCount;
|
|
10516
|
-
}
|
|
10517
|
-
if (quiet) {
|
|
10518
|
-
if (totalSynced > 0)
|
|
10519
|
-
log.ok(`cdd-kit: synced ${totalSynced} file(s) to ~/.claude/`);
|
|
10520
|
-
} else {
|
|
10521
|
-
log.blank();
|
|
10522
|
-
log.info("Project files (contracts/, specs/, tests/, ci/) were not changed.");
|
|
10523
|
-
log.ok("Update complete.");
|
|
10524
|
-
log.info(`Backup saved to: ${backupRoot}`);
|
|
10525
|
-
log.blank();
|
|
10526
|
-
}
|
|
10527
|
-
}
|
|
10939
|
+
// src/cli/index.ts
|
|
10940
|
+
init_update();
|
|
10528
10941
|
|
|
10529
10942
|
// src/commands/new-change.ts
|
|
10530
10943
|
init_paths();
|
|
@@ -11513,8 +11926,8 @@ async function installHooks() {
|
|
|
11513
11926
|
}
|
|
11514
11927
|
|
|
11515
11928
|
// src/cli/index.ts
|
|
11516
|
-
var __dirname2 =
|
|
11517
|
-
var pkg = JSON.parse(
|
|
11929
|
+
var __dirname2 = dirname7(fileURLToPath3(import.meta.url));
|
|
11930
|
+
var pkg = JSON.parse(readFileSync25(join27(__dirname2, "..", "..", "package.json"), "utf8"));
|
|
11518
11931
|
var program = new Command();
|
|
11519
11932
|
program.name("cdd-kit").description("Contract-Driven Delivery Kit CLI").version(pkg.version);
|
|
11520
11933
|
program.command("init").description(
|
|
@@ -11529,6 +11942,18 @@ program.command("init").description(
|
|
|
11529
11942
|
})
|
|
11530
11943
|
);
|
|
11531
11944
|
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 }));
|
|
11945
|
+
program.command("refresh").description("Complete upgrade: refresh agents, skills, templates, hooks, model-policy, and code-map in one command").option("--yes", "Apply changes (default is dry-run)", false).option("--no-templates", "Skip force-refresh of specs/templates, tests/templates, ci-templates, .github/workflows").option("--no-hooks", "Skip pre-commit hook re-installation").option("--no-code-map", "Skip code-map regeneration").option("--no-update", "Skip ~/.claude update step").option("--no-upgrade", "Skip project add-missing step").option("--provider <provider>", "Provider adapter: auto, claude, codex, or both", "auto").action(async (opts) => {
|
|
11946
|
+
const { refresh: refresh2 } = await Promise.resolve().then(() => (init_refresh(), refresh_exports));
|
|
11947
|
+
await refresh2({
|
|
11948
|
+
yes: opts.yes,
|
|
11949
|
+
noTemplates: opts.templates === false,
|
|
11950
|
+
noHooks: opts.hooks === false,
|
|
11951
|
+
noCodeMap: opts.codeMap === false,
|
|
11952
|
+
noUpdate: opts.update === false,
|
|
11953
|
+
noUpgrade: opts.upgrade === false,
|
|
11954
|
+
provider: opts.provider
|
|
11955
|
+
});
|
|
11956
|
+
});
|
|
11532
11957
|
program.command("doctor").description("Inspect cdd-kit repo health, provider guidance, and context index freshness").option("--strict", "Treat warnings as errors", false).option("--json", "Print a machine-readable health report", false).option("--provider <provider>", "Provider adapter to inspect: auto, claude, codex, or both", "auto").option("--fix", "Auto-resolve safe warnings (stale context indexes, missing role bindings)", false).action(async (opts) => {
|
|
11533
11958
|
const { doctor: doctor2 } = await Promise.resolve().then(() => (init_doctor(), doctor_exports));
|
|
11534
11959
|
await doctor2({ strict: opts.strict, json: opts.json, provider: opts.provider, fix: opts.fix });
|