contract-driven-delivery 2.0.7 → 2.0.8
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 +61 -0
- package/dist/cli/index.js +2255 -1920
- 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, {
|
|
@@ -4046,49 +4221,49 @@ var require_fast_uri = __commonJS({
|
|
|
4046
4221
|
schemelessOptions.skipEscape = true;
|
|
4047
4222
|
return serialize(resolved, schemelessOptions);
|
|
4048
4223
|
}
|
|
4049
|
-
function resolveComponent(base,
|
|
4224
|
+
function resolveComponent(base, relative6, options, skipNormalization) {
|
|
4050
4225
|
const target = {};
|
|
4051
4226
|
if (!skipNormalization) {
|
|
4052
4227
|
base = parse3(serialize(base, options), options);
|
|
4053
|
-
|
|
4228
|
+
relative6 = parse3(serialize(relative6, options), options);
|
|
4054
4229
|
}
|
|
4055
4230
|
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 =
|
|
4231
|
+
if (!options.tolerant && relative6.scheme) {
|
|
4232
|
+
target.scheme = relative6.scheme;
|
|
4233
|
+
target.userinfo = relative6.userinfo;
|
|
4234
|
+
target.host = relative6.host;
|
|
4235
|
+
target.port = relative6.port;
|
|
4236
|
+
target.path = removeDotSegments(relative6.path || "");
|
|
4237
|
+
target.query = relative6.query;
|
|
4063
4238
|
} else {
|
|
4064
|
-
if (
|
|
4065
|
-
target.userinfo =
|
|
4066
|
-
target.host =
|
|
4067
|
-
target.port =
|
|
4068
|
-
target.path = removeDotSegments(
|
|
4069
|
-
target.query =
|
|
4239
|
+
if (relative6.userinfo !== void 0 || relative6.host !== void 0 || relative6.port !== void 0) {
|
|
4240
|
+
target.userinfo = relative6.userinfo;
|
|
4241
|
+
target.host = relative6.host;
|
|
4242
|
+
target.port = relative6.port;
|
|
4243
|
+
target.path = removeDotSegments(relative6.path || "");
|
|
4244
|
+
target.query = relative6.query;
|
|
4070
4245
|
} else {
|
|
4071
|
-
if (!
|
|
4246
|
+
if (!relative6.path) {
|
|
4072
4247
|
target.path = base.path;
|
|
4073
|
-
if (
|
|
4074
|
-
target.query =
|
|
4248
|
+
if (relative6.query !== void 0) {
|
|
4249
|
+
target.query = relative6.query;
|
|
4075
4250
|
} else {
|
|
4076
4251
|
target.query = base.query;
|
|
4077
4252
|
}
|
|
4078
4253
|
} else {
|
|
4079
|
-
if (
|
|
4080
|
-
target.path = removeDotSegments(
|
|
4254
|
+
if (relative6.path[0] === "/") {
|
|
4255
|
+
target.path = removeDotSegments(relative6.path);
|
|
4081
4256
|
} else {
|
|
4082
4257
|
if ((base.userinfo !== void 0 || base.host !== void 0 || base.port !== void 0) && !base.path) {
|
|
4083
|
-
target.path = "/" +
|
|
4258
|
+
target.path = "/" + relative6.path;
|
|
4084
4259
|
} else if (!base.path) {
|
|
4085
|
-
target.path =
|
|
4260
|
+
target.path = relative6.path;
|
|
4086
4261
|
} else {
|
|
4087
|
-
target.path = base.path.slice(0, base.path.lastIndexOf("/") + 1) +
|
|
4262
|
+
target.path = base.path.slice(0, base.path.lastIndexOf("/") + 1) + relative6.path;
|
|
4088
4263
|
}
|
|
4089
4264
|
target.path = removeDotSegments(target.path);
|
|
4090
4265
|
}
|
|
4091
|
-
target.query =
|
|
4266
|
+
target.query = relative6.query;
|
|
4092
4267
|
}
|
|
4093
4268
|
target.userinfo = base.userinfo;
|
|
4094
4269
|
target.host = base.host;
|
|
@@ -4096,7 +4271,7 @@ var require_fast_uri = __commonJS({
|
|
|
4096
4271
|
}
|
|
4097
4272
|
target.scheme = base.scheme;
|
|
4098
4273
|
}
|
|
4099
|
-
target.fragment =
|
|
4274
|
+
target.fragment = relative6.fragment;
|
|
4100
4275
|
return target;
|
|
4101
4276
|
}
|
|
4102
4277
|
function equal(uriA, uriB, options) {
|
|
@@ -7468,1877 +7643,2184 @@ var init_freshness = __esm({
|
|
|
7468
7643
|
}
|
|
7469
7644
|
});
|
|
7470
7645
|
|
|
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
|
-
}
|
|
7503
|
-
}
|
|
7504
|
-
return lines;
|
|
7505
|
-
}
|
|
7506
|
-
function renderFunction(f) {
|
|
7507
|
-
const asyncPrefix = f.async ? "async " : "";
|
|
7508
|
-
let comment = "";
|
|
7509
|
-
if (f.decorators.length > 0) {
|
|
7510
|
-
const truncated = truncateDecorator(f.decorators[0]);
|
|
7511
|
-
comment = ` # @${truncated}`;
|
|
7646
|
+
// src/commands/migrate.ts
|
|
7647
|
+
var migrate_exports = {};
|
|
7648
|
+
__export(migrate_exports, {
|
|
7649
|
+
migrate: () => migrate
|
|
7650
|
+
});
|
|
7651
|
+
import { join as join16 } from "path";
|
|
7652
|
+
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";
|
|
7653
|
+
import yaml3 from "js-yaml";
|
|
7654
|
+
function backupChangeDir(cwd, changeId, sessionStamp) {
|
|
7655
|
+
const backupRoot = join16(cwd, ".cdd", "migrate-backup", sessionStamp);
|
|
7656
|
+
const backupDir2 = join16(backupRoot, changeId);
|
|
7657
|
+
mkdirSync6(backupRoot, { recursive: true });
|
|
7658
|
+
const sourceDir = join16(cwd, "specs", "changes", changeId);
|
|
7659
|
+
if (existsSync14(sourceDir)) {
|
|
7660
|
+
cpSync2(sourceDir, backupDir2, { recursive: true });
|
|
7512
7661
|
}
|
|
7513
|
-
return
|
|
7662
|
+
return backupDir2;
|
|
7514
7663
|
}
|
|
7515
|
-
function
|
|
7516
|
-
|
|
7517
|
-
|
|
7664
|
+
function buildLegacyContextManifest(changeId) {
|
|
7665
|
+
return [
|
|
7666
|
+
"# Context Manifest",
|
|
7667
|
+
"",
|
|
7668
|
+
"Generated by `cdd-kit migrate` for an existing change.",
|
|
7669
|
+
"Legacy manifest. Forbidden paths come from `.cdd/context-policy.json`.",
|
|
7670
|
+
"",
|
|
7671
|
+
"## Affected Surfaces",
|
|
7672
|
+
"- legacy-unknown",
|
|
7673
|
+
"",
|
|
7674
|
+
"## Allowed Paths",
|
|
7675
|
+
`- specs/changes/${changeId}/`,
|
|
7676
|
+
"",
|
|
7677
|
+
"## Required Contracts",
|
|
7678
|
+
"- legacy-unknown",
|
|
7679
|
+
"",
|
|
7680
|
+
"## Required Tests",
|
|
7681
|
+
"- legacy-unknown",
|
|
7682
|
+
"",
|
|
7683
|
+
"## Agent Work Packets",
|
|
7684
|
+
"",
|
|
7685
|
+
"## Context Expansion Requests",
|
|
7686
|
+
"-",
|
|
7687
|
+
"",
|
|
7688
|
+
"## Approved Expansions",
|
|
7689
|
+
"-",
|
|
7690
|
+
""
|
|
7691
|
+
].join("\n");
|
|
7518
7692
|
}
|
|
7519
|
-
function
|
|
7520
|
-
|
|
7521
|
-
|
|
7522
|
-
|
|
7523
|
-
|
|
7524
|
-
|
|
7525
|
-
|
|
7526
|
-
|
|
7527
|
-
|
|
7693
|
+
function buildContextGovernedManifest(changeId) {
|
|
7694
|
+
return [
|
|
7695
|
+
"# Context Manifest",
|
|
7696
|
+
"",
|
|
7697
|
+
"Generated by `cdd-kit migrate --enable-context-governance` for an existing change.",
|
|
7698
|
+
"Review and narrow the allowed paths before assigning implementation work.",
|
|
7699
|
+
"Forbidden paths come from `.cdd/context-policy.json`.",
|
|
7700
|
+
"",
|
|
7701
|
+
"## Affected Surfaces",
|
|
7702
|
+
"- legacy-unknown",
|
|
7703
|
+
"",
|
|
7704
|
+
"## Allowed Paths",
|
|
7705
|
+
`- specs/changes/${changeId}/`,
|
|
7706
|
+
"- specs/context/project-map.md",
|
|
7707
|
+
"- specs/context/contracts-index.md",
|
|
7708
|
+
"",
|
|
7709
|
+
"## Required Contracts",
|
|
7710
|
+
"- legacy-unknown",
|
|
7711
|
+
"",
|
|
7712
|
+
"## Required Tests",
|
|
7713
|
+
"- legacy-unknown",
|
|
7714
|
+
"",
|
|
7715
|
+
"## Agent Work Packets",
|
|
7716
|
+
"",
|
|
7717
|
+
"### change-classifier",
|
|
7718
|
+
"- allowed:",
|
|
7719
|
+
` - specs/changes/${changeId}/`,
|
|
7720
|
+
" - specs/context/project-map.md",
|
|
7721
|
+
" - specs/context/contracts-index.md",
|
|
7722
|
+
"",
|
|
7723
|
+
"## Context Expansion Requests",
|
|
7724
|
+
"",
|
|
7725
|
+
"<!--",
|
|
7726
|
+
"Agents must request context expansion instead of reading outside their work packet.",
|
|
7727
|
+
"Use this format only for real requests:",
|
|
7728
|
+
"",
|
|
7729
|
+
"- request-id: CER-001",
|
|
7730
|
+
" requested_paths:",
|
|
7731
|
+
" - src/example.ts",
|
|
7732
|
+
" reason: why this file is required",
|
|
7733
|
+
" status: pending",
|
|
7734
|
+
"-->",
|
|
7735
|
+
"",
|
|
7736
|
+
"## Approved Expansions",
|
|
7737
|
+
"-",
|
|
7738
|
+
""
|
|
7739
|
+
].join("\n");
|
|
7528
7740
|
}
|
|
7529
|
-
function
|
|
7530
|
-
const
|
|
7531
|
-
|
|
7532
|
-
|
|
7533
|
-
|
|
7534
|
-
|
|
7535
|
-
|
|
7536
|
-
|
|
7537
|
-
|
|
7538
|
-
|
|
7539
|
-
if (
|
|
7540
|
-
|
|
7541
|
-
|
|
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
|
-
}
|
|
7741
|
+
function parseLegacyFrontmatter(content) {
|
|
7742
|
+
const m = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
|
|
7743
|
+
if (!m)
|
|
7744
|
+
return {};
|
|
7745
|
+
const out = {};
|
|
7746
|
+
for (const line of m[1].split(/\r?\n/)) {
|
|
7747
|
+
const colon = line.indexOf(":");
|
|
7748
|
+
if (colon === -1)
|
|
7749
|
+
continue;
|
|
7750
|
+
const key = line.slice(0, colon).trim();
|
|
7751
|
+
if (!key)
|
|
7752
|
+
continue;
|
|
7753
|
+
out[key] = line.slice(colon + 1).trim();
|
|
7582
7754
|
}
|
|
7583
|
-
|
|
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";
|
|
7755
|
+
return out;
|
|
7591
7756
|
}
|
|
7592
|
-
|
|
7593
|
-
|
|
7594
|
-
|
|
7595
|
-
|
|
7596
|
-
|
|
7597
|
-
|
|
7598
|
-
|
|
7599
|
-
|
|
7600
|
-
|
|
7601
|
-
|
|
7602
|
-
const
|
|
7603
|
-
const
|
|
7604
|
-
|
|
7605
|
-
|
|
7606
|
-
|
|
7607
|
-
|
|
7608
|
-
|
|
7609
|
-
|
|
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}` });
|
|
7757
|
+
function parseListField(raw) {
|
|
7758
|
+
if (!raw)
|
|
7759
|
+
return [];
|
|
7760
|
+
const trimmed = raw.trim();
|
|
7761
|
+
if (!trimmed || trimmed === "[]")
|
|
7762
|
+
return [];
|
|
7763
|
+
const inner = trimmed.startsWith("[") && trimmed.endsWith("]") ? trimmed.slice(1, -1) : trimmed;
|
|
7764
|
+
return inner.split(",").map((item) => item.trim().replace(/^["']|["']$/g, "")).filter(Boolean);
|
|
7765
|
+
}
|
|
7766
|
+
function parseLegacyTaskList(body) {
|
|
7767
|
+
const lines = body.split(/\r?\n/);
|
|
7768
|
+
const rows = [];
|
|
7769
|
+
let currentSection;
|
|
7770
|
+
for (const raw of lines) {
|
|
7771
|
+
const headerMatch = raw.match(/^##\s+\d+\.\s+(.*)\s*$/);
|
|
7772
|
+
if (headerMatch) {
|
|
7773
|
+
currentSection = headerMatch[1].trim();
|
|
7774
|
+
continue;
|
|
7616
7775
|
}
|
|
7776
|
+
const itemMatch = raw.match(/^\s*-\s*\[([ xX\-])\]\s+(\d+(?:\.\d+)*)\s+(.*)\s*$/);
|
|
7777
|
+
if (!itemMatch)
|
|
7778
|
+
continue;
|
|
7779
|
+
const mark = itemMatch[1];
|
|
7780
|
+
const id = itemMatch[2];
|
|
7781
|
+
const title = itemMatch[3].trim();
|
|
7782
|
+
let status = "pending";
|
|
7783
|
+
if (mark === "x" || mark === "X")
|
|
7784
|
+
status = "done";
|
|
7785
|
+
else if (mark === "-")
|
|
7786
|
+
status = "skipped";
|
|
7787
|
+
rows.push({ id, title, status, section: currentSection });
|
|
7617
7788
|
}
|
|
7618
|
-
return
|
|
7789
|
+
return rows;
|
|
7619
7790
|
}
|
|
7620
|
-
function
|
|
7621
|
-
const
|
|
7622
|
-
|
|
7623
|
-
|
|
7624
|
-
|
|
7625
|
-
if (!out[ext])
|
|
7626
|
-
out[ext] = [];
|
|
7627
|
-
out[ext].push(f);
|
|
7791
|
+
function migrateTasksFile(changeId, changeDir, enableContextGovernance, detectedTier, changed, warnings, pendingWrites, pendingDeletes) {
|
|
7792
|
+
const newPath = join16(changeDir, "tasks.yml");
|
|
7793
|
+
const legacyPath = join16(changeDir, "tasks.md");
|
|
7794
|
+
if (existsSync14(newPath)) {
|
|
7795
|
+
return;
|
|
7628
7796
|
}
|
|
7629
|
-
|
|
7630
|
-
|
|
7631
|
-
|
|
7632
|
-
"src/code-map/orchestrator.ts"() {
|
|
7633
|
-
"use strict";
|
|
7634
|
-
init_include_exclude();
|
|
7797
|
+
if (!existsSync14(legacyPath)) {
|
|
7798
|
+
warnings.push("tasks.md not found and tasks.yml missing \u2014 skipping tasks migration");
|
|
7799
|
+
return;
|
|
7635
7800
|
}
|
|
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";
|
|
7801
|
+
const raw = readFileSync11(legacyPath, "utf8");
|
|
7802
|
+
const fm = parseLegacyFrontmatter(raw);
|
|
7803
|
+
const bodyMatch = raw.match(/^---\r?\n[\s\S]*?\r?\n---\r?\n?([\s\S]*)$/);
|
|
7804
|
+
const body = bodyMatch ? bodyMatch[1] : raw;
|
|
7805
|
+
const tasksRows = parseLegacyTaskList(body);
|
|
7806
|
+
const data = {};
|
|
7807
|
+
data["change-id"] = fm["change-id"] || changeId;
|
|
7808
|
+
data["status"] = fm["status"] || "in-progress";
|
|
7809
|
+
if (fm["tier"] && /^\d+$/.test(fm["tier"])) {
|
|
7810
|
+
data["tier"] = parseInt(fm["tier"], 10);
|
|
7811
|
+
} else if (detectedTier) {
|
|
7812
|
+
data["tier"] = parseInt(detectedTier, 10);
|
|
7813
|
+
} else {
|
|
7814
|
+
data["tier"] = null;
|
|
7654
7815
|
}
|
|
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
|
-
}
|
|
7816
|
+
if (enableContextGovernance || fm["context-governance"] === "v1") {
|
|
7817
|
+
data["context-governance"] = "v1";
|
|
7677
7818
|
}
|
|
7678
|
-
|
|
7679
|
-
|
|
7680
|
-
|
|
7681
|
-
|
|
7682
|
-
"
|
|
7683
|
-
|
|
7684
|
-
|
|
7685
|
-
|
|
7686
|
-
|
|
7687
|
-
PythonScanner = class {
|
|
7688
|
-
extensions = [".py"];
|
|
7689
|
-
_interpreter = void 0;
|
|
7690
|
-
getInterpreter() {
|
|
7691
|
-
if (this._interpreter === void 0) {
|
|
7692
|
-
this._interpreter = detectPython2();
|
|
7693
|
-
}
|
|
7694
|
-
return this._interpreter;
|
|
7695
|
-
}
|
|
7696
|
-
async scan(absolutePath, repoRoot) {
|
|
7697
|
-
const result = await this.scanBatch([absolutePath], repoRoot);
|
|
7698
|
-
return result.entries[0] ?? null;
|
|
7699
|
-
}
|
|
7700
|
-
async scanBatch(absolutePaths, repoRoot) {
|
|
7701
|
-
const interpreter = this.getInterpreter();
|
|
7702
|
-
const entries = [];
|
|
7703
|
-
const warnings = [];
|
|
7704
|
-
if (!interpreter) {
|
|
7705
|
-
const count = absolutePaths.length;
|
|
7706
|
-
warnings.push({
|
|
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;
|
|
7781
|
-
}
|
|
7782
|
-
const r = parsed;
|
|
7783
|
-
entries.push({
|
|
7784
|
-
path: canonicalRelPath(join16(repoRoot, r.path), repoRoot),
|
|
7785
|
-
total_lines: r.total_lines,
|
|
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 };
|
|
7812
|
-
}
|
|
7813
|
-
};
|
|
7814
|
-
pythonScanner = new PythonScanner();
|
|
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
|
|
7819
|
+
const archive2 = parseListField(fm["archive-tasks"]);
|
|
7820
|
+
data["archive-tasks"] = archive2.length > 0 ? archive2 : ["7.1", "7.2"];
|
|
7821
|
+
const deps = parseListField(fm["depends-on"]);
|
|
7822
|
+
data["depends-on"] = deps;
|
|
7823
|
+
data["tasks"] = tasksRows.map((r) => {
|
|
7824
|
+
const out = { id: r.id, title: r.title, status: r.status };
|
|
7825
|
+
if (r.section)
|
|
7826
|
+
out["section"] = r.section;
|
|
7827
|
+
return out;
|
|
7837
7828
|
});
|
|
7829
|
+
const yamlOut = yaml3.dump(data, { lineWidth: -1, noRefs: true });
|
|
7830
|
+
pendingWrites.push({ path: newPath, content: yamlOut });
|
|
7831
|
+
pendingDeletes.push({ path: legacyPath });
|
|
7832
|
+
changed.push(`tasks.md -> tasks.yml (${tasksRows.length} task(s) migrated)`);
|
|
7838
7833
|
}
|
|
7839
|
-
function
|
|
7840
|
-
|
|
7841
|
-
|
|
7842
|
-
|
|
7843
|
-
|
|
7844
|
-
|
|
7845
|
-
|
|
7846
|
-
|
|
7847
|
-
|
|
7848
|
-
|
|
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);
|
|
7871
|
-
}
|
|
7872
|
-
}
|
|
7873
|
-
return {
|
|
7874
|
-
module: node.source.value,
|
|
7875
|
-
items,
|
|
7876
|
-
line: node.loc?.start.line ?? 1
|
|
7877
|
-
};
|
|
7878
|
-
}
|
|
7879
|
-
function processFunctionDeclaration(node, nameOverride) {
|
|
7880
|
-
const name = nameOverride ?? node.id?.name;
|
|
7881
|
-
if (!name)
|
|
7882
|
-
return null;
|
|
7883
|
-
return {
|
|
7884
|
-
name,
|
|
7885
|
-
lines: getLineRange(node),
|
|
7886
|
-
decorators: [],
|
|
7887
|
-
async: node.async
|
|
7888
|
-
};
|
|
7889
|
-
}
|
|
7890
|
-
function processClassDeclaration(node, nameOverride) {
|
|
7891
|
-
const name = nameOverride ?? node.id?.name;
|
|
7892
|
-
if (!name)
|
|
7893
|
-
return null;
|
|
7894
|
-
const methods = [];
|
|
7895
|
-
for (const member of node.body.body) {
|
|
7896
|
-
if (member.type === "ClassMethod" || member.type === "ClassPrivateMethod") {
|
|
7897
|
-
const m = member;
|
|
7898
|
-
let methodName;
|
|
7899
|
-
if (m.key.type === "Identifier") {
|
|
7900
|
-
methodName = m.key.name;
|
|
7901
|
-
} else if (m.key.type === "PrivateName") {
|
|
7902
|
-
methodName = `#${m.key.id.name}`;
|
|
7903
|
-
} else {
|
|
7904
|
-
methodName = "<computed>";
|
|
7905
|
-
}
|
|
7906
|
-
methods.push({
|
|
7907
|
-
name: methodName,
|
|
7908
|
-
lines: getLineRange(m),
|
|
7909
|
-
async: m.async
|
|
7910
|
-
});
|
|
7911
|
-
}
|
|
7912
|
-
}
|
|
7913
|
-
return {
|
|
7914
|
-
name,
|
|
7915
|
-
lines: getLineRange(node),
|
|
7916
|
-
methods
|
|
7917
|
-
};
|
|
7918
|
-
}
|
|
7919
|
-
function processVariableDeclaration(node, imports, constants, functions) {
|
|
7920
|
-
for (const decl of node.declarations) {
|
|
7921
|
-
if (!decl.id || decl.id.type !== "Identifier")
|
|
7922
|
-
continue;
|
|
7923
|
-
const varName = decl.id.name;
|
|
7924
|
-
const init2 = decl.init;
|
|
7925
|
-
if (init2 && isCallExpression(init2, "require")) {
|
|
7926
|
-
const mod = extractRequireModule(init2);
|
|
7927
|
-
if (mod) {
|
|
7928
|
-
imports.push({ module: mod, items: [`default:${varName}`], line: node.loc?.start.line ?? 1 });
|
|
7929
|
-
}
|
|
7930
|
-
continue;
|
|
7931
|
-
}
|
|
7932
|
-
if (isAllCapsConst(varName) && init2 !== null && init2 !== void 0) {
|
|
7933
|
-
constants.push({ name: varName, line: node.loc?.start.line ?? 1 });
|
|
7834
|
+
function parseLegacyAgentLog(content) {
|
|
7835
|
+
const lines = content.split(/\r?\n/);
|
|
7836
|
+
const data = {};
|
|
7837
|
+
let i = 0;
|
|
7838
|
+
const topFieldRe = /^[ ]{0,1}-\s*([\w-]+):\s*(.*)$/;
|
|
7839
|
+
while (i < lines.length) {
|
|
7840
|
+
const line = lines[i];
|
|
7841
|
+
const fieldMatch = line.match(topFieldRe);
|
|
7842
|
+
if (!fieldMatch) {
|
|
7843
|
+
i++;
|
|
7934
7844
|
continue;
|
|
7935
7845
|
}
|
|
7936
|
-
|
|
7937
|
-
|
|
7938
|
-
|
|
7939
|
-
|
|
7940
|
-
|
|
7941
|
-
|
|
7942
|
-
|
|
7943
|
-
|
|
7944
|
-
|
|
7945
|
-
|
|
7946
|
-
|
|
7947
|
-
|
|
7948
|
-
|
|
7949
|
-
|
|
7846
|
+
const key = fieldMatch[1];
|
|
7847
|
+
const inline = fieldMatch[2].trim();
|
|
7848
|
+
if (key === "files-read" || key === "artifacts") {
|
|
7849
|
+
const items = [];
|
|
7850
|
+
let j = i + 1;
|
|
7851
|
+
while (j < lines.length) {
|
|
7852
|
+
const sub = lines[j];
|
|
7853
|
+
if (topFieldRe.test(sub))
|
|
7854
|
+
break;
|
|
7855
|
+
if (/^#/.test(sub))
|
|
7856
|
+
break;
|
|
7857
|
+
const itemMatch = sub.match(/^\s{2,}-\s+(.+?)\s*$/);
|
|
7858
|
+
if (itemMatch) {
|
|
7859
|
+
items.push(itemMatch[1].trim());
|
|
7860
|
+
}
|
|
7861
|
+
j++;
|
|
7862
|
+
}
|
|
7863
|
+
if (key === "files-read") {
|
|
7864
|
+
data["files-read"] = items;
|
|
7865
|
+
} else {
|
|
7866
|
+
data["artifacts"] = items.map((s) => {
|
|
7867
|
+
const idx = s.indexOf(":");
|
|
7868
|
+
if (idx === -1) {
|
|
7869
|
+
return { type: "note", pointer: s };
|
|
7870
|
+
}
|
|
7871
|
+
const type = s.slice(0, idx).trim();
|
|
7872
|
+
const pointer = s.slice(idx + 1).trim();
|
|
7873
|
+
return { type, pointer };
|
|
7874
|
+
});
|
|
7875
|
+
}
|
|
7876
|
+
i = j;
|
|
7877
|
+
continue;
|
|
7950
7878
|
}
|
|
7879
|
+
data[key] = inline;
|
|
7880
|
+
i++;
|
|
7951
7881
|
}
|
|
7882
|
+
return data;
|
|
7952
7883
|
}
|
|
7953
|
-
function
|
|
7954
|
-
|
|
7955
|
-
|
|
7956
|
-
|
|
7957
|
-
|
|
7958
|
-
|
|
7959
|
-
|
|
7960
|
-
|
|
7961
|
-
|
|
7962
|
-
|
|
7963
|
-
}
|
|
7964
|
-
function processTsTypeAliasDeclaration(node, exported) {
|
|
7965
|
-
if (!node || node.type !== "TSTypeAliasDeclaration")
|
|
7966
|
-
return null;
|
|
7967
|
-
if (!node.id || node.id.type !== "Identifier")
|
|
7968
|
-
return null;
|
|
7969
|
-
return {
|
|
7970
|
-
name: node.id.name,
|
|
7971
|
-
lines: getLineRange(node),
|
|
7972
|
-
exported
|
|
7973
|
-
};
|
|
7974
|
-
}
|
|
7975
|
-
function processTsEnumDeclaration(node, exported) {
|
|
7976
|
-
if (!node || node.type !== "TSEnumDeclaration")
|
|
7977
|
-
return null;
|
|
7978
|
-
if (!node.id || node.id.type !== "Identifier")
|
|
7979
|
-
return null;
|
|
7980
|
-
const members = [];
|
|
7981
|
-
for (const m of node.members ?? []) {
|
|
7982
|
-
if (!m || !m.id)
|
|
7884
|
+
function migrateAgentLogs(changeDir, changed, pendingWrites, pendingDeletes) {
|
|
7885
|
+
const agentLogDir = join16(changeDir, "agent-log");
|
|
7886
|
+
if (!existsSync14(agentLogDir))
|
|
7887
|
+
return;
|
|
7888
|
+
const mdLogs = readdirSync8(agentLogDir).filter((f) => f.endsWith(".md"));
|
|
7889
|
+
for (const f of mdLogs) {
|
|
7890
|
+
const fullPath = join16(agentLogDir, f);
|
|
7891
|
+
const yamlName = f.replace(/\.md$/, ".yml");
|
|
7892
|
+
const yamlFull = join16(agentLogDir, yamlName);
|
|
7893
|
+
if (existsSync14(yamlFull))
|
|
7983
7894
|
continue;
|
|
7984
|
-
|
|
7985
|
-
|
|
7986
|
-
|
|
7987
|
-
|
|
7895
|
+
const raw = readFileSync11(fullPath, "utf8");
|
|
7896
|
+
const parsed = parseLegacyAgentLog(raw);
|
|
7897
|
+
const yamlOut = yaml3.dump(parsed, { lineWidth: -1, noRefs: true });
|
|
7898
|
+
pendingWrites.push({ path: yamlFull, content: yamlOut });
|
|
7899
|
+
pendingDeletes.push({ path: fullPath });
|
|
7900
|
+
changed.push(`agent-log/${f} -> agent-log/${yamlName}`);
|
|
7988
7901
|
}
|
|
7989
|
-
return {
|
|
7990
|
-
name: node.id.name,
|
|
7991
|
-
lines: getLineRange(node),
|
|
7992
|
-
exported,
|
|
7993
|
-
members
|
|
7994
|
-
};
|
|
7995
7902
|
}
|
|
7996
|
-
function
|
|
7997
|
-
|
|
7998
|
-
|
|
7999
|
-
|
|
8000
|
-
|
|
8001
|
-
|
|
8002
|
-
|
|
8003
|
-
|
|
8004
|
-
|
|
8005
|
-
|
|
8006
|
-
|
|
8007
|
-
|
|
8008
|
-
|
|
8009
|
-
|
|
8010
|
-
|
|
7903
|
+
function migrateOne(changeId, changeDir, enableContextGovernance) {
|
|
7904
|
+
const changed = [];
|
|
7905
|
+
const warnings = [];
|
|
7906
|
+
const pending = [];
|
|
7907
|
+
const deletes = [];
|
|
7908
|
+
let detectedTier = null;
|
|
7909
|
+
const classifPath = join16(changeDir, "change-classification.md");
|
|
7910
|
+
if (existsSync14(classifPath)) {
|
|
7911
|
+
const content = readFileSync11(classifPath, "utf8");
|
|
7912
|
+
const hasNewTierFormat = /^## Tier\s*\n\s*-\s*\d\s*$/m.test(content);
|
|
7913
|
+
const oldMatch = content.match(/\*\*Tier[:\*]+\s*(?:Tier\s*)?(\d)/i) ?? content.match(/^-?\s*Tier:\s*(?:Tier\s*)?(\d)/mi);
|
|
7914
|
+
if (oldMatch)
|
|
7915
|
+
detectedTier = oldMatch[1];
|
|
7916
|
+
if (!hasNewTierFormat) {
|
|
7917
|
+
if (detectedTier) {
|
|
7918
|
+
const addition = `
|
|
7919
|
+
## Tier
|
|
7920
|
+
- ${detectedTier}
|
|
7921
|
+
`;
|
|
7922
|
+
if (!content.includes("\n## Tier\n")) {
|
|
7923
|
+
changed.push(
|
|
7924
|
+
`change-classification.md: appended "## Tier\\n- ${detectedTier}" (converted from old format)`
|
|
7925
|
+
);
|
|
7926
|
+
pending.push({ path: classifPath, content: content + addition });
|
|
7927
|
+
}
|
|
7928
|
+
} else {
|
|
7929
|
+
warnings.push(
|
|
7930
|
+
"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."
|
|
7931
|
+
);
|
|
7932
|
+
}
|
|
7933
|
+
} else {
|
|
7934
|
+
const structured = content.match(/^## Tier\s*\n\s*-\s*(\d)\s*$/m);
|
|
7935
|
+
if (structured)
|
|
7936
|
+
detectedTier = structured[1];
|
|
8011
7937
|
}
|
|
8012
|
-
return;
|
|
8013
|
-
}
|
|
8014
|
-
if (stmt.type === "ImportDeclaration") {
|
|
8015
|
-
buckets.imports.push(processImportDeclaration(stmt));
|
|
8016
|
-
return;
|
|
8017
7938
|
}
|
|
8018
|
-
|
|
8019
|
-
|
|
8020
|
-
|
|
8021
|
-
|
|
8022
|
-
|
|
7939
|
+
migrateTasksFile(changeId, changeDir, enableContextGovernance, detectedTier, changed, warnings, pending, deletes);
|
|
7940
|
+
migrateAgentLogs(changeDir, changed, pending, deletes);
|
|
7941
|
+
const manifestPath = join16(changeDir, "context-manifest.md");
|
|
7942
|
+
if (!existsSync14(manifestPath)) {
|
|
7943
|
+
changed.push(enableContextGovernance ? "context-manifest.md: added context-governance v1 manifest scaffold" : "context-manifest.md: added legacy context manifest scaffold");
|
|
7944
|
+
pending.push({
|
|
7945
|
+
path: manifestPath,
|
|
7946
|
+
content: enableContextGovernance ? buildContextGovernedManifest(changeId) : buildLegacyContextManifest(changeId)
|
|
7947
|
+
});
|
|
7948
|
+
} else if (enableContextGovernance) {
|
|
7949
|
+
warnings.push("context-manifest.md already exists \u2014 review allowed paths before relying on context-governance: v1");
|
|
8023
7950
|
}
|
|
8024
|
-
|
|
8025
|
-
|
|
8026
|
-
|
|
8027
|
-
|
|
8028
|
-
|
|
7951
|
+
return { result: { changed, warnings }, pending, deletes };
|
|
7952
|
+
}
|
|
7953
|
+
function commitWritesAtomically(pending, deletes) {
|
|
7954
|
+
const renames = [];
|
|
7955
|
+
try {
|
|
7956
|
+
for (const write of pending) {
|
|
7957
|
+
const tmp = `${write.path}.cdd-migrate.tmp`;
|
|
7958
|
+
writeFileSync6(tmp, write.content, "utf8");
|
|
7959
|
+
renames.push({ tmp, final: write.path });
|
|
7960
|
+
}
|
|
7961
|
+
} catch (err) {
|
|
7962
|
+
for (const r of renames) {
|
|
7963
|
+
try {
|
|
7964
|
+
rmSync2(r.tmp, { force: true });
|
|
7965
|
+
} catch {
|
|
7966
|
+
}
|
|
7967
|
+
}
|
|
7968
|
+
throw err;
|
|
8029
7969
|
}
|
|
8030
|
-
|
|
8031
|
-
|
|
8032
|
-
return;
|
|
7970
|
+
for (const r of renames) {
|
|
7971
|
+
renameSync(r.tmp, r.final);
|
|
8033
7972
|
}
|
|
8034
|
-
|
|
8035
|
-
|
|
8036
|
-
|
|
8037
|
-
|
|
8038
|
-
if (e)
|
|
8039
|
-
buckets.interfaces.push(e);
|
|
8040
|
-
return;
|
|
7973
|
+
for (const d of deletes) {
|
|
7974
|
+
try {
|
|
7975
|
+
rmSync2(d.path, { force: true });
|
|
7976
|
+
} catch {
|
|
8041
7977
|
}
|
|
8042
|
-
|
|
8043
|
-
|
|
8044
|
-
|
|
8045
|
-
|
|
7978
|
+
}
|
|
7979
|
+
}
|
|
7980
|
+
async function migrate(changeId, opts = {}) {
|
|
7981
|
+
const cwd = process.cwd();
|
|
7982
|
+
const dryRun = opts.dryRun ?? false;
|
|
7983
|
+
const enableContextGovernance = opts.enableContextGovernance ?? false;
|
|
7984
|
+
const noBackup = opts.noBackup ?? false;
|
|
7985
|
+
const idsToMigrate = [];
|
|
7986
|
+
if (opts.all) {
|
|
7987
|
+
const changesDir = join16(cwd, "specs", "changes");
|
|
7988
|
+
if (!existsSync14(changesDir)) {
|
|
7989
|
+
log.info("No specs/changes/ directory found \u2014 nothing to migrate.");
|
|
8046
7990
|
return;
|
|
8047
7991
|
}
|
|
8048
|
-
|
|
8049
|
-
|
|
8050
|
-
|
|
8051
|
-
|
|
8052
|
-
|
|
7992
|
+
idsToMigrate.push(
|
|
7993
|
+
...readdirSync8(changesDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name)
|
|
7994
|
+
);
|
|
7995
|
+
} else if (changeId) {
|
|
7996
|
+
const specificDir = join16(cwd, "specs", "changes", changeId);
|
|
7997
|
+
if (!existsSync14(specificDir)) {
|
|
7998
|
+
log.error(`Change not found: specs/changes/${changeId}`);
|
|
7999
|
+
process.exit(1);
|
|
8053
8000
|
}
|
|
8001
|
+
idsToMigrate.push(changeId);
|
|
8002
|
+
} else {
|
|
8003
|
+
log.error("Usage: cdd-kit migrate <change-id> | cdd-kit migrate --all [--dry-run] [--no-backup]");
|
|
8004
|
+
process.exit(1);
|
|
8054
8005
|
}
|
|
8055
|
-
if (
|
|
8056
|
-
|
|
8057
|
-
|
|
8058
|
-
|
|
8059
|
-
|
|
8060
|
-
|
|
8006
|
+
if (idsToMigrate.length === 0) {
|
|
8007
|
+
log.info("No changes found to migrate.");
|
|
8008
|
+
return;
|
|
8009
|
+
}
|
|
8010
|
+
if (dryRun) {
|
|
8011
|
+
log.info("Dry run \u2014 no files will be written.");
|
|
8012
|
+
log.blank();
|
|
8013
|
+
}
|
|
8014
|
+
const sessionStamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
8015
|
+
let migratedCount = 0;
|
|
8016
|
+
let upToDateCount = 0;
|
|
8017
|
+
const backupRoot = join16(cwd, ".cdd", "migrate-backup", sessionStamp);
|
|
8018
|
+
for (const id of idsToMigrate) {
|
|
8019
|
+
const changeDir = join16(cwd, "specs", "changes", id);
|
|
8020
|
+
if (!existsSync14(changeDir)) {
|
|
8021
|
+
log.warn(` ${id}: directory not found \u2014 skipping`);
|
|
8022
|
+
continue;
|
|
8023
|
+
}
|
|
8024
|
+
const { result, pending, deletes } = migrateOne(id, changeDir, enableContextGovernance);
|
|
8025
|
+
const { changed, warnings } = result;
|
|
8026
|
+
if (changed.length === 0) {
|
|
8027
|
+
log.info(` ${id}: already up to date`);
|
|
8028
|
+
upToDateCount++;
|
|
8029
|
+
for (const w of warnings)
|
|
8030
|
+
log.warn(` ${id}: ${w}`);
|
|
8031
|
+
continue;
|
|
8032
|
+
}
|
|
8033
|
+
if (!dryRun) {
|
|
8034
|
+
try {
|
|
8035
|
+
if (!noBackup)
|
|
8036
|
+
backupChangeDir(cwd, id, sessionStamp);
|
|
8037
|
+
commitWritesAtomically(pending, deletes);
|
|
8038
|
+
} catch (err) {
|
|
8039
|
+
log.error(` ${id}: migration failed \u2014 ${err.message}`);
|
|
8040
|
+
if (!noBackup) {
|
|
8041
|
+
log.error(` ${id}: restore from .cdd/migrate-backup/${sessionStamp}/${id}/`);
|
|
8042
|
+
}
|
|
8043
|
+
process.exit(1);
|
|
8061
8044
|
}
|
|
8062
8045
|
}
|
|
8046
|
+
log.ok(` ${id}: migrated`);
|
|
8047
|
+
for (const c of changed)
|
|
8048
|
+
log.info(` + ${c}`);
|
|
8049
|
+
migratedCount++;
|
|
8050
|
+
for (const w of warnings)
|
|
8051
|
+
log.warn(` ${id}: ${w}`);
|
|
8063
8052
|
}
|
|
8064
|
-
|
|
8065
|
-
|
|
8066
|
-
|
|
8067
|
-
|
|
8068
|
-
|
|
8069
|
-
|
|
8070
|
-
|
|
8071
|
-
|
|
8072
|
-
|
|
8073
|
-
|
|
8074
|
-
constants: [],
|
|
8075
|
-
functions: [],
|
|
8076
|
-
classes: [],
|
|
8077
|
-
interfaces: [],
|
|
8078
|
-
types: [],
|
|
8079
|
-
enums: []
|
|
8080
|
-
};
|
|
8081
|
-
for (const stmt of ast.program.body) {
|
|
8082
|
-
processStatement(stmt, buckets, !!opts.extractTsTypes);
|
|
8053
|
+
log.blank();
|
|
8054
|
+
if (dryRun) {
|
|
8055
|
+
log.info(`Dry run complete: ${migratedCount} change(s) would be updated, ${upToDateCount} already up to date.`);
|
|
8056
|
+
} else {
|
|
8057
|
+
log.ok(`Migration complete: ${migratedCount} updated, ${upToDateCount} already up to date.`);
|
|
8058
|
+
if (migratedCount > 0 && !noBackup) {
|
|
8059
|
+
log.info(`Backup: ${backupRoot}`);
|
|
8060
|
+
log.info('Next: git add specs/changes/ && git commit -m "chore: migrate changes to YAML format"');
|
|
8061
|
+
log.info("When stable, remove backup: rm -rf .cdd/migrate-backup/");
|
|
8062
|
+
}
|
|
8083
8063
|
}
|
|
8084
|
-
return buckets;
|
|
8085
|
-
}
|
|
8086
|
-
function countSourceLines(source) {
|
|
8087
|
-
if (source === "")
|
|
8088
|
-
return 0;
|
|
8089
|
-
return source.split(/\r?\n/).length - (source.endsWith("\n") || source.endsWith("\r\n") ? 1 : 0);
|
|
8090
|
-
}
|
|
8091
|
-
function parseJsSource(source, relPath) {
|
|
8092
|
-
const total_lines = countSourceLines(source);
|
|
8093
|
-
const r = parseAndExtract(source, { plugins: JS_PLUGINS, extractTsTypes: false });
|
|
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
|
-
};
|
|
8104
8064
|
}
|
|
8105
|
-
var
|
|
8106
|
-
|
|
8107
|
-
"src/code-map/scanners/javascript.ts"() {
|
|
8065
|
+
var init_migrate = __esm({
|
|
8066
|
+
"src/commands/migrate.ts"() {
|
|
8108
8067
|
"use strict";
|
|
8109
|
-
|
|
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();
|
|
8068
|
+
init_logger();
|
|
8142
8069
|
}
|
|
8143
8070
|
});
|
|
8144
8071
|
|
|
8145
|
-
// src/
|
|
8146
|
-
var
|
|
8147
|
-
__export(
|
|
8148
|
-
|
|
8072
|
+
// src/commands/upgrade.ts
|
|
8073
|
+
var upgrade_exports = {};
|
|
8074
|
+
__export(upgrade_exports, {
|
|
8075
|
+
upgrade: () => upgrade
|
|
8149
8076
|
});
|
|
8150
|
-
import { readFileSync as
|
|
8151
|
-
|
|
8152
|
-
|
|
8153
|
-
|
|
8154
|
-
|
|
8155
|
-
|
|
8156
|
-
|
|
8157
|
-
|
|
8158
|
-
|
|
8159
|
-
|
|
8160
|
-
|
|
8161
|
-
|
|
8162
|
-
|
|
8163
|
-
|
|
8164
|
-
|
|
8165
|
-
}
|
|
8166
|
-
if (isBinary(source)) {
|
|
8167
|
-
return null;
|
|
8168
|
-
}
|
|
8169
|
-
const isTsx = absolutePath.toLowerCase().endsWith(".tsx");
|
|
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
|
-
};
|
|
8187
|
-
}
|
|
8188
|
-
};
|
|
8189
|
-
tsScanner = new TypeScriptScanner();
|
|
8077
|
+
import { existsSync as existsSync15, mkdirSync as mkdirSync7, readdirSync as readdirSync9, copyFileSync as copyFileSync3, readFileSync as readFileSync12, writeFileSync as writeFileSync7 } from "fs";
|
|
8078
|
+
import { dirname as dirname4, join as join17, relative as relative3 } from "path";
|
|
8079
|
+
function planMissingFiles(srcDir, destDir, label, planned) {
|
|
8080
|
+
if (!existsSync15(srcDir))
|
|
8081
|
+
return;
|
|
8082
|
+
for (const entry of readdirSync9(srcDir, { withFileTypes: true })) {
|
|
8083
|
+
const src = join17(srcDir, entry.name);
|
|
8084
|
+
const dest = join17(destDir, entry.name);
|
|
8085
|
+
if (entry.isDirectory()) {
|
|
8086
|
+
planMissingFiles(src, dest, join17(label, entry.name), planned);
|
|
8087
|
+
continue;
|
|
8088
|
+
}
|
|
8089
|
+
if (!existsSync15(dest)) {
|
|
8090
|
+
planned.push({ src, dest, rel: join17(label, relative3(srcDir, src)) });
|
|
8091
|
+
}
|
|
8190
8092
|
}
|
|
8191
|
-
}
|
|
8192
|
-
|
|
8193
|
-
|
|
8194
|
-
|
|
8195
|
-
|
|
8196
|
-
|
|
8197
|
-
|
|
8198
|
-
|
|
8199
|
-
|
|
8200
|
-
|
|
8201
|
-
|
|
8202
|
-
|
|
8203
|
-
|
|
8204
|
-
|
|
8205
|
-
|
|
8206
|
-
|
|
8207
|
-
|
|
8208
|
-
|
|
8209
|
-
|
|
8210
|
-
|
|
8211
|
-
|
|
8212
|
-
|
|
8213
|
-
|
|
8214
|
-
|
|
8215
|
-
|
|
8216
|
-
|
|
8217
|
-
|
|
8218
|
-
|
|
8219
|
-
|
|
8220
|
-
|
|
8221
|
-
|
|
8222
|
-
|
|
8223
|
-
|
|
8224
|
-
|
|
8225
|
-
|
|
8226
|
-
|
|
8227
|
-
|
|
8228
|
-
|
|
8229
|
-
|
|
8230
|
-
|
|
8231
|
-
|
|
8232
|
-
|
|
8233
|
-
|
|
8234
|
-
|
|
8235
|
-
|
|
8236
|
-
|
|
8237
|
-
|
|
8238
|
-
|
|
8239
|
-
|
|
8240
|
-
|
|
8241
|
-
|
|
8242
|
-
|
|
8243
|
-
|
|
8244
|
-
|
|
8245
|
-
|
|
8246
|
-
|
|
8247
|
-
|
|
8248
|
-
|
|
8249
|
-
|
|
8250
|
-
|
|
8251
|
-
|
|
8252
|
-
|
|
8253
|
-
|
|
8254
|
-
|
|
8255
|
-
|
|
8256
|
-
|
|
8257
|
-
|
|
8258
|
-
|
|
8259
|
-
|
|
8260
|
-
|
|
8261
|
-
|
|
8262
|
-
|
|
8263
|
-
|
|
8264
|
-
|
|
8265
|
-
|
|
8266
|
-
|
|
8267
|
-
|
|
8268
|
-
|
|
8269
|
-
|
|
8270
|
-
|
|
8271
|
-
|
|
8272
|
-
|
|
8273
|
-
|
|
8274
|
-
}))
|
|
8275
|
-
});
|
|
8276
|
-
}
|
|
8277
|
-
}
|
|
8278
|
-
return {
|
|
8279
|
-
path: relPath,
|
|
8280
|
-
total_lines,
|
|
8281
|
-
imports: allImports,
|
|
8282
|
-
constants: allConstants,
|
|
8283
|
-
classes: allClasses,
|
|
8284
|
-
functions: allFunctions
|
|
8285
|
-
};
|
|
8286
|
-
}
|
|
8093
|
+
}
|
|
8094
|
+
function planProviderGuidance(cwd, provider, planned) {
|
|
8095
|
+
if (provider === "claude" || provider === "both") {
|
|
8096
|
+
if (!existsSync15(join17(cwd, "CLAUDE.md"))) {
|
|
8097
|
+
planned.push({ src: ASSET.claudeTemplate, dest: join17(cwd, "CLAUDE.md"), rel: "CLAUDE.md" });
|
|
8098
|
+
}
|
|
8099
|
+
if (!existsSync15(join17(cwd, "AGENTS.md"))) {
|
|
8100
|
+
planned.push({ src: ASSET.agentsTemplate, dest: join17(cwd, "AGENTS.md"), rel: "AGENTS.md" });
|
|
8101
|
+
}
|
|
8102
|
+
}
|
|
8103
|
+
if ((provider === "codex" || provider === "both") && !existsSync15(join17(cwd, "CODEX.md"))) {
|
|
8104
|
+
planned.push({ src: ASSET.codexTemplate, dest: join17(cwd, "CODEX.md"), rel: "CODEX.md" });
|
|
8105
|
+
}
|
|
8106
|
+
}
|
|
8107
|
+
function applyCopy(plan) {
|
|
8108
|
+
for (const item of plan) {
|
|
8109
|
+
mkdirSync7(dirname4(item.dest), { recursive: true });
|
|
8110
|
+
copyFileSync3(item.src, item.dest);
|
|
8111
|
+
}
|
|
8112
|
+
}
|
|
8113
|
+
async function upgrade(opts = {}) {
|
|
8114
|
+
const cwd = process.cwd();
|
|
8115
|
+
const requestedProvider = opts.provider ?? "auto";
|
|
8116
|
+
if (!validateProviderOption(requestedProvider)) {
|
|
8117
|
+
log.error(`Invalid provider: ${requestedProvider}. Use auto, claude, codex, or both.`);
|
|
8118
|
+
process.exit(1);
|
|
8119
|
+
}
|
|
8120
|
+
const provider = inferProvider(cwd, requestedProvider);
|
|
8121
|
+
const plan = [];
|
|
8122
|
+
planMissingFiles(ASSET.contracts, join17(cwd, "contracts"), "contracts", plan);
|
|
8123
|
+
planMissingFiles(ASSET.specsTemplates, join17(cwd, "specs", "templates"), "specs/templates", plan);
|
|
8124
|
+
planMissingFiles(ASSET.testsTemplates, join17(cwd, "tests", "templates"), "tests/templates", plan);
|
|
8125
|
+
planMissingFiles(ASSET.ci, join17(cwd, "ci"), "ci", plan);
|
|
8126
|
+
planMissingFiles(ASSET.githubWorkflows, join17(cwd, ".github", "workflows"), ".github/workflows", plan);
|
|
8127
|
+
planMissingFiles(ASSET.cddConfig, join17(cwd, ".cdd"), ".cdd", plan);
|
|
8128
|
+
planProviderGuidance(cwd, provider, plan);
|
|
8129
|
+
log.blank();
|
|
8130
|
+
log.info(`Upgrade provider: ${provider}`);
|
|
8131
|
+
if (plan.length === 0) {
|
|
8132
|
+
log.ok("No missing cdd-kit project files found.");
|
|
8133
|
+
if (opts.migrateChanges) {
|
|
8134
|
+
log.blank();
|
|
8135
|
+
log.info("Running change migration flow...");
|
|
8136
|
+
await migrate(void 0, {
|
|
8137
|
+
all: true,
|
|
8138
|
+
dryRun: !opts.yes,
|
|
8139
|
+
enableContextGovernance: opts.enableContextGovernance
|
|
8140
|
+
});
|
|
8141
|
+
}
|
|
8142
|
+
log.blank();
|
|
8143
|
+
return;
|
|
8144
|
+
}
|
|
8145
|
+
log.info(`${plan.length} missing file(s) detected:`);
|
|
8146
|
+
for (const item of plan)
|
|
8147
|
+
log.dim(` + ${item.rel.replace(/\\/g, "/")}`);
|
|
8148
|
+
if (!opts.yes) {
|
|
8149
|
+
log.blank();
|
|
8150
|
+
log.info("Dry run only. Re-run with --yes to write missing files.");
|
|
8151
|
+
if (opts.migrateChanges) {
|
|
8152
|
+
log.blank();
|
|
8153
|
+
log.info("Previewing existing change migration because --migrate-changes was requested.");
|
|
8154
|
+
await migrate(void 0, {
|
|
8155
|
+
all: true,
|
|
8156
|
+
dryRun: true,
|
|
8157
|
+
enableContextGovernance: opts.enableContextGovernance
|
|
8158
|
+
});
|
|
8159
|
+
}
|
|
8160
|
+
log.blank();
|
|
8161
|
+
return;
|
|
8162
|
+
}
|
|
8163
|
+
applyCopy(plan);
|
|
8164
|
+
const modelPolicyPath = join17(cwd, ".cdd", "model-policy.json");
|
|
8165
|
+
if (existsSync15(modelPolicyPath)) {
|
|
8166
|
+
let existing = {};
|
|
8167
|
+
try {
|
|
8168
|
+
existing = JSON.parse(readFileSync12(modelPolicyPath, "utf8"));
|
|
8169
|
+
} catch {
|
|
8170
|
+
}
|
|
8171
|
+
const merged = {
|
|
8172
|
+
...existing,
|
|
8173
|
+
provider,
|
|
8174
|
+
generated_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
8175
|
+
roles: existing.roles && typeof existing.roles === "object" ? existing.roles : {}
|
|
8287
8176
|
};
|
|
8288
|
-
|
|
8177
|
+
writeFileSync7(modelPolicyPath, JSON.stringify(merged, null, 2) + "\n", "utf8");
|
|
8178
|
+
}
|
|
8179
|
+
log.blank();
|
|
8180
|
+
log.ok(`Upgrade complete: ${plan.length} missing file(s) added.`);
|
|
8181
|
+
log.info("Existing project guidance and contracts were preserved.");
|
|
8182
|
+
if (opts.migrateChanges) {
|
|
8183
|
+
log.blank();
|
|
8184
|
+
log.info("Running change migration flow...");
|
|
8185
|
+
await migrate(void 0, {
|
|
8186
|
+
all: true,
|
|
8187
|
+
dryRun: false,
|
|
8188
|
+
enableContextGovernance: opts.enableContextGovernance
|
|
8189
|
+
});
|
|
8190
|
+
}
|
|
8191
|
+
log.blank();
|
|
8192
|
+
}
|
|
8193
|
+
var init_upgrade = __esm({
|
|
8194
|
+
"src/commands/upgrade.ts"() {
|
|
8195
|
+
"use strict";
|
|
8196
|
+
init_paths();
|
|
8197
|
+
init_logger();
|
|
8198
|
+
init_provider();
|
|
8199
|
+
init_migrate();
|
|
8289
8200
|
}
|
|
8290
8201
|
});
|
|
8291
8202
|
|
|
8292
|
-
// src/
|
|
8293
|
-
|
|
8294
|
-
|
|
8295
|
-
|
|
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;
|
|
8203
|
+
// src/code-map/yaml-writer.ts
|
|
8204
|
+
function quoteScalar(s) {
|
|
8205
|
+
if (s.startsWith(".") || YAML_RESERVED.test(s) || s.includes(" ") || s === "" || s === "null" || s === "true" || s === "false") {
|
|
8206
|
+
return `'${s.replace(/'/g, "''")}'`;
|
|
8311
8207
|
}
|
|
8312
|
-
|
|
8313
|
-
|
|
8314
|
-
|
|
8315
|
-
|
|
8316
|
-
|
|
8317
|
-
|
|
8318
|
-
|
|
8319
|
-
|
|
8320
|
-
|
|
8321
|
-
|
|
8208
|
+
return s;
|
|
8209
|
+
}
|
|
8210
|
+
function quotePath(p) {
|
|
8211
|
+
if (YAML_RESERVED.test(p) || p.includes(" ")) {
|
|
8212
|
+
return `'${p.replace(/'/g, "''")}'`;
|
|
8213
|
+
}
|
|
8214
|
+
return p;
|
|
8215
|
+
}
|
|
8216
|
+
function renderItems(items) {
|
|
8217
|
+
if (items.length === 0)
|
|
8218
|
+
return "[]";
|
|
8219
|
+
return `[${items.map(quoteScalar).join(", ")}]`;
|
|
8220
|
+
}
|
|
8221
|
+
function truncateDecorator(d, max = 80) {
|
|
8222
|
+
const cleaned = d.replace(/\r?\n/g, " ");
|
|
8223
|
+
return cleaned.length <= max ? cleaned : cleaned.slice(0, max) + "...";
|
|
8224
|
+
}
|
|
8225
|
+
function renderClass(c) {
|
|
8226
|
+
const lines = [];
|
|
8227
|
+
lines.push(` - name: ${c.name}`);
|
|
8228
|
+
lines.push(` lines: ${c.lines[0]}-${c.lines[1]}`);
|
|
8229
|
+
if (c.methods.length > 0) {
|
|
8230
|
+
lines.push(" methods:");
|
|
8231
|
+
for (const m of c.methods) {
|
|
8232
|
+
const asyncPrefix = m.async ? "async " : "";
|
|
8233
|
+
lines.push(` - { name: ${asyncPrefix}${m.name}, lines: ${m.lines[0]}-${m.lines[1]} }`);
|
|
8322
8234
|
}
|
|
8323
8235
|
}
|
|
8324
|
-
|
|
8325
|
-
|
|
8326
|
-
|
|
8327
|
-
|
|
8328
|
-
|
|
8329
|
-
|
|
8330
|
-
|
|
8331
|
-
|
|
8332
|
-
tasks.push(scanInProcess(jsScanner2, jsFiles, root));
|
|
8236
|
+
return lines;
|
|
8237
|
+
}
|
|
8238
|
+
function renderFunction(f) {
|
|
8239
|
+
const asyncPrefix = f.async ? "async " : "";
|
|
8240
|
+
let comment = "";
|
|
8241
|
+
if (f.decorators.length > 0) {
|
|
8242
|
+
const truncated = truncateDecorator(f.decorators[0]);
|
|
8243
|
+
comment = ` # @${truncated}`;
|
|
8333
8244
|
}
|
|
8334
|
-
|
|
8335
|
-
|
|
8336
|
-
|
|
8337
|
-
|
|
8338
|
-
|
|
8339
|
-
|
|
8340
|
-
|
|
8245
|
+
return ` - { name: ${asyncPrefix}${f.name}, lines: ${f.lines[0]}-${f.lines[1]} }${comment}`;
|
|
8246
|
+
}
|
|
8247
|
+
function renderTypeDef(t) {
|
|
8248
|
+
const exportedSuffix = t.exported ? "" : " # local";
|
|
8249
|
+
return ` - { name: ${t.name}, lines: ${t.lines[0]}-${t.lines[1]} }${exportedSuffix}`;
|
|
8250
|
+
}
|
|
8251
|
+
function renderEnum(e) {
|
|
8252
|
+
const lines = [];
|
|
8253
|
+
const exportedSuffix = e.exported ? "" : " # local";
|
|
8254
|
+
lines.push(` - name: ${e.name}`);
|
|
8255
|
+
lines.push(` lines: ${e.lines[0]}-${e.lines[1]}${exportedSuffix}`);
|
|
8256
|
+
if (e.members.length > 0) {
|
|
8257
|
+
lines.push(` members: [${e.members.join(", ")}]`);
|
|
8341
8258
|
}
|
|
8342
|
-
|
|
8343
|
-
|
|
8344
|
-
|
|
8259
|
+
return lines;
|
|
8260
|
+
}
|
|
8261
|
+
function renderYaml(entries, opts) {
|
|
8262
|
+
const now = (/* @__PURE__ */ new Date()).toISOString().replace(/\.\d+Z$/, "Z");
|
|
8263
|
+
const totalSrc = entries.reduce((s, e) => s + e.total_lines, 0);
|
|
8264
|
+
const bodyLines = [];
|
|
8265
|
+
for (let i = 0; i < entries.length; i++) {
|
|
8266
|
+
const e = entries[i];
|
|
8267
|
+
if (i > 0)
|
|
8268
|
+
bodyLines.push("");
|
|
8269
|
+
const pathKey = quotePath(e.path);
|
|
8270
|
+
bodyLines.push(`${pathKey}: # ${e.total_lines} lines`);
|
|
8271
|
+
if (e.imports.length > 0) {
|
|
8272
|
+
bodyLines.push(" imports:");
|
|
8273
|
+
for (const imp of e.imports) {
|
|
8274
|
+
const mod = quoteScalar(imp.module);
|
|
8275
|
+
bodyLines.push(` - { module: ${mod}, items: ${renderItems(imp.items)}, line: ${imp.line} }`);
|
|
8276
|
+
}
|
|
8277
|
+
}
|
|
8278
|
+
if (e.constants.length > 0) {
|
|
8279
|
+
bodyLines.push(" constants:");
|
|
8280
|
+
for (const c of e.constants) {
|
|
8281
|
+
bodyLines.push(` - { name: ${c.name}, line: ${c.line} }`);
|
|
8282
|
+
}
|
|
8283
|
+
}
|
|
8284
|
+
if (e.classes.length > 0) {
|
|
8285
|
+
bodyLines.push(" classes:");
|
|
8286
|
+
for (const c of e.classes) {
|
|
8287
|
+
bodyLines.push(...renderClass(c));
|
|
8288
|
+
}
|
|
8289
|
+
}
|
|
8290
|
+
if (e.functions.length > 0) {
|
|
8291
|
+
bodyLines.push(" functions:");
|
|
8292
|
+
for (const f of e.functions) {
|
|
8293
|
+
bodyLines.push(renderFunction(f));
|
|
8294
|
+
}
|
|
8295
|
+
}
|
|
8296
|
+
if (e.interfaces && e.interfaces.length > 0) {
|
|
8297
|
+
bodyLines.push(" interfaces:");
|
|
8298
|
+
for (const t of e.interfaces) {
|
|
8299
|
+
bodyLines.push(renderTypeDef(t));
|
|
8300
|
+
}
|
|
8301
|
+
}
|
|
8302
|
+
if (e.types && e.types.length > 0) {
|
|
8303
|
+
bodyLines.push(" types:");
|
|
8304
|
+
for (const t of e.types) {
|
|
8305
|
+
bodyLines.push(renderTypeDef(t));
|
|
8306
|
+
}
|
|
8307
|
+
}
|
|
8308
|
+
if (e.enums && e.enums.length > 0) {
|
|
8309
|
+
bodyLines.push(" enums:");
|
|
8310
|
+
for (const en of e.enums) {
|
|
8311
|
+
bodyLines.push(...renderEnum(en));
|
|
8312
|
+
}
|
|
8313
|
+
}
|
|
8345
8314
|
}
|
|
8346
|
-
|
|
8347
|
-
|
|
8348
|
-
|
|
8315
|
+
const mapLines = bodyLines.length + 2;
|
|
8316
|
+
const compression = totalSrc === 0 ? 0 : totalSrc / mapLines;
|
|
8317
|
+
const fileCount = entries.length;
|
|
8318
|
+
const header = [
|
|
8319
|
+
`# generated: ${now} by ${opts.generator}`,
|
|
8320
|
+
`# files: ${fileCount}, src-lines: ${totalSrc}, map-lines: ${mapLines}, compression: ${compression.toFixed(1)}x`
|
|
8321
|
+
];
|
|
8322
|
+
return [...header, ...bodyLines].join("\n") + "\n";
|
|
8323
|
+
}
|
|
8324
|
+
var YAML_RESERVED;
|
|
8325
|
+
var init_yaml_writer = __esm({
|
|
8326
|
+
"src/code-map/yaml-writer.ts"() {
|
|
8327
|
+
"use strict";
|
|
8328
|
+
YAML_RESERVED = /[:[\]{},&*!|>'"%@`#]/;
|
|
8349
8329
|
}
|
|
8350
|
-
|
|
8351
|
-
|
|
8352
|
-
|
|
8353
|
-
|
|
8354
|
-
|
|
8355
|
-
|
|
8356
|
-
|
|
8330
|
+
});
|
|
8331
|
+
|
|
8332
|
+
// src/code-map/orchestrator.ts
|
|
8333
|
+
async function scanInProcess(scanner, absolutePaths, repoRoot) {
|
|
8334
|
+
const entries = [];
|
|
8335
|
+
const warnings = [];
|
|
8336
|
+
for (const absPath of absolutePaths) {
|
|
8337
|
+
try {
|
|
8338
|
+
const entry = await scanner.scan(absPath, repoRoot);
|
|
8339
|
+
if (entry === null) {
|
|
8340
|
+
const rel = absPath.replace(/\\/g, "/").replace(repoRoot.replace(/\\/g, "/") + "/", "");
|
|
8341
|
+
warnings.push({ path: rel, message: "parse error (scanner returned null)" });
|
|
8342
|
+
} else {
|
|
8343
|
+
entries.push(entry);
|
|
8344
|
+
}
|
|
8345
|
+
} catch (err) {
|
|
8346
|
+
const rel = absPath.replace(/\\/g, "/").replace(repoRoot.replace(/\\/g, "/") + "/", "");
|
|
8347
|
+
warnings.push({ path: rel, message: `IO error: ${err.message}` });
|
|
8357
8348
|
}
|
|
8358
8349
|
}
|
|
8359
|
-
|
|
8360
|
-
|
|
8361
|
-
|
|
8362
|
-
const
|
|
8363
|
-
const
|
|
8364
|
-
|
|
8365
|
-
|
|
8350
|
+
return { entries, warnings };
|
|
8351
|
+
}
|
|
8352
|
+
function bucketByExtension(files) {
|
|
8353
|
+
const out = {};
|
|
8354
|
+
for (const f of files) {
|
|
8355
|
+
const dot = f.lastIndexOf(".");
|
|
8356
|
+
const ext = dot >= 0 ? f.slice(dot).toLowerCase() : "";
|
|
8357
|
+
if (!out[ext])
|
|
8358
|
+
out[ext] = [];
|
|
8359
|
+
out[ext].push(f);
|
|
8366
8360
|
}
|
|
8367
|
-
|
|
8368
|
-
|
|
8369
|
-
|
|
8370
|
-
|
|
8371
|
-
|
|
8372
|
-
|
|
8373
|
-
}
|
|
8374
|
-
log.ok(`code-map up to date: ${opts.out}`);
|
|
8375
|
-
return 0;
|
|
8361
|
+
return out;
|
|
8362
|
+
}
|
|
8363
|
+
var init_orchestrator = __esm({
|
|
8364
|
+
"src/code-map/orchestrator.ts"() {
|
|
8365
|
+
"use strict";
|
|
8366
|
+
init_include_exclude();
|
|
8376
8367
|
}
|
|
8377
|
-
|
|
8378
|
-
|
|
8379
|
-
|
|
8380
|
-
|
|
8368
|
+
});
|
|
8369
|
+
|
|
8370
|
+
// src/code-map/scanners/common.ts
|
|
8371
|
+
import { relative as relative4 } from "path";
|
|
8372
|
+
function canonicalRelPath(absolutePath, repoRoot) {
|
|
8373
|
+
const rel = relative4(repoRoot, absolutePath);
|
|
8374
|
+
return rel.replace(/\\/g, "/").normalize("NFC");
|
|
8381
8375
|
}
|
|
8382
|
-
|
|
8383
|
-
|
|
8384
|
-
|
|
8376
|
+
function isAllCapsConst(name) {
|
|
8377
|
+
return name.length >= 2 && /^[A-Z][A-Z0-9_]*$/.test(name);
|
|
8378
|
+
}
|
|
8379
|
+
function isBinary(content) {
|
|
8380
|
+
const head = content.slice(0, 4096);
|
|
8381
|
+
return head.includes("\0");
|
|
8382
|
+
}
|
|
8383
|
+
var init_common = __esm({
|
|
8384
|
+
"src/code-map/scanners/common.ts"() {
|
|
8385
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"));
|
|
8393
8386
|
}
|
|
8394
8387
|
});
|
|
8395
8388
|
|
|
8396
|
-
// src/
|
|
8397
|
-
var
|
|
8398
|
-
__export(
|
|
8399
|
-
|
|
8389
|
+
// src/code-map/scanners/python.ts
|
|
8390
|
+
var python_exports = {};
|
|
8391
|
+
__export(python_exports, {
|
|
8392
|
+
pythonScanner: () => pythonScanner
|
|
8400
8393
|
});
|
|
8401
|
-
import {
|
|
8402
|
-
import {
|
|
8394
|
+
import { spawnSync as spawnSync2 } from "child_process";
|
|
8395
|
+
import { writeFileSync as writeFileSync8, unlinkSync } from "fs";
|
|
8403
8396
|
import { join as join18 } from "path";
|
|
8404
|
-
|
|
8405
|
-
|
|
8397
|
+
import { tmpdir } from "os";
|
|
8398
|
+
function detectPython2() {
|
|
8399
|
+
for (const candidate of ["python3", "python"]) {
|
|
8400
|
+
try {
|
|
8401
|
+
const result = spawnSync2(candidate, ["--version"], {
|
|
8402
|
+
encoding: "utf8",
|
|
8403
|
+
timeout: 5e3
|
|
8404
|
+
});
|
|
8405
|
+
if (result.status === 0)
|
|
8406
|
+
return candidate;
|
|
8407
|
+
} catch {
|
|
8408
|
+
}
|
|
8409
|
+
}
|
|
8410
|
+
return null;
|
|
8406
8411
|
}
|
|
8407
|
-
|
|
8408
|
-
|
|
8409
|
-
|
|
8410
|
-
|
|
8411
|
-
|
|
8412
|
-
|
|
8413
|
-
|
|
8414
|
-
|
|
8415
|
-
|
|
8412
|
+
var TIMEOUT_MS, PythonScanner, pythonScanner;
|
|
8413
|
+
var init_python = __esm({
|
|
8414
|
+
"src/code-map/scanners/python.ts"() {
|
|
8415
|
+
"use strict";
|
|
8416
|
+
init_paths();
|
|
8417
|
+
init_common();
|
|
8418
|
+
TIMEOUT_MS = parseInt(process.env["CDD_CODE_MAP_TIMEOUT_MS"] ?? "30000", 10);
|
|
8419
|
+
PythonScanner = class {
|
|
8420
|
+
extensions = [".py"];
|
|
8421
|
+
_interpreter = void 0;
|
|
8422
|
+
getInterpreter() {
|
|
8423
|
+
if (this._interpreter === void 0) {
|
|
8424
|
+
this._interpreter = detectPython2();
|
|
8425
|
+
}
|
|
8426
|
+
return this._interpreter;
|
|
8427
|
+
}
|
|
8428
|
+
async scan(absolutePath, repoRoot) {
|
|
8429
|
+
const result = await this.scanBatch([absolutePath], repoRoot);
|
|
8430
|
+
return result.entries[0] ?? null;
|
|
8431
|
+
}
|
|
8432
|
+
async scanBatch(absolutePaths, repoRoot) {
|
|
8433
|
+
const interpreter = this.getInterpreter();
|
|
8434
|
+
const entries = [];
|
|
8435
|
+
const warnings = [];
|
|
8436
|
+
if (!interpreter) {
|
|
8437
|
+
const count = absolutePaths.length;
|
|
8438
|
+
warnings.push({
|
|
8439
|
+
path: "",
|
|
8440
|
+
message: `python interpreter not found on PATH; skipping ${count} .py file${count === 1 ? "" : "s"}`
|
|
8441
|
+
});
|
|
8442
|
+
return { entries, warnings };
|
|
8443
|
+
}
|
|
8444
|
+
const scriptPath = ASSET.codeMapPython;
|
|
8445
|
+
const rand = Math.random().toString(36).slice(2);
|
|
8446
|
+
const listFile = join18(tmpdir(), `cdd-codemap-${process.pid}-${rand}.txt`);
|
|
8447
|
+
writeFileSync8(listFile, absolutePaths.join("\n") + "\n", "utf8");
|
|
8448
|
+
let stdout = "";
|
|
8449
|
+
let stderr = "";
|
|
8450
|
+
let exitCode = 0;
|
|
8451
|
+
try {
|
|
8452
|
+
const result = spawnSync2(
|
|
8453
|
+
interpreter,
|
|
8454
|
+
[scriptPath, "--batch-file", listFile, "--repo-root", repoRoot],
|
|
8455
|
+
{
|
|
8456
|
+
encoding: "utf8",
|
|
8457
|
+
timeout: TIMEOUT_MS,
|
|
8458
|
+
maxBuffer: 50 * 1024 * 1024
|
|
8459
|
+
// 50MB
|
|
8460
|
+
}
|
|
8461
|
+
);
|
|
8462
|
+
stdout = result.stdout ?? "";
|
|
8463
|
+
stderr = result.stderr ?? "";
|
|
8464
|
+
exitCode = result.status ?? -1;
|
|
8465
|
+
if (result.error) {
|
|
8466
|
+
const errMsg = result.error.message ?? String(result.error);
|
|
8467
|
+
if (errMsg.includes("ENOENT")) {
|
|
8468
|
+
warnings.push({
|
|
8469
|
+
path: "",
|
|
8470
|
+
message: `python interpreter not found (ENOENT); skipping ${absolutePaths.length} .py file(s)`
|
|
8471
|
+
});
|
|
8472
|
+
return { entries, warnings };
|
|
8473
|
+
}
|
|
8474
|
+
if (errMsg.includes("ETIMEDOUT") || errMsg.includes("timeout")) {
|
|
8475
|
+
warnings.push({
|
|
8476
|
+
path: "",
|
|
8477
|
+
message: `python scanner timed out after ${TIMEOUT_MS}ms; skipping .py files`
|
|
8478
|
+
});
|
|
8479
|
+
return { entries, warnings };
|
|
8480
|
+
}
|
|
8481
|
+
warnings.push({
|
|
8482
|
+
path: "",
|
|
8483
|
+
message: `python scanner error: ${errMsg}; skipping .py files`
|
|
8484
|
+
});
|
|
8485
|
+
return { entries, warnings };
|
|
8486
|
+
}
|
|
8487
|
+
} finally {
|
|
8488
|
+
try {
|
|
8489
|
+
unlinkSync(listFile);
|
|
8490
|
+
} catch {
|
|
8491
|
+
}
|
|
8492
|
+
}
|
|
8493
|
+
if (exitCode === 3) {
|
|
8494
|
+
warnings.push({
|
|
8495
|
+
path: "",
|
|
8496
|
+
message: "python interpreter is < 3.9 (need ast.unparse); skipping .py files"
|
|
8497
|
+
});
|
|
8498
|
+
return { entries, warnings };
|
|
8499
|
+
}
|
|
8500
|
+
for (const line of stdout.split("\n")) {
|
|
8501
|
+
const trimmed = line.trim();
|
|
8502
|
+
if (!trimmed)
|
|
8503
|
+
continue;
|
|
8504
|
+
let parsed;
|
|
8505
|
+
try {
|
|
8506
|
+
parsed = JSON.parse(trimmed);
|
|
8507
|
+
} catch {
|
|
8508
|
+
continue;
|
|
8509
|
+
}
|
|
8510
|
+
if (!parsed.ok) {
|
|
8511
|
+
warnings.push({ path: parsed.path, message: parsed.error });
|
|
8512
|
+
continue;
|
|
8513
|
+
}
|
|
8514
|
+
const r = parsed;
|
|
8515
|
+
entries.push({
|
|
8516
|
+
path: canonicalRelPath(join18(repoRoot, r.path), repoRoot),
|
|
8517
|
+
total_lines: r.total_lines,
|
|
8518
|
+
imports: r.imports ?? [],
|
|
8519
|
+
constants: r.constants ?? [],
|
|
8520
|
+
classes: (r.classes ?? []).map((c) => ({
|
|
8521
|
+
name: c.name,
|
|
8522
|
+
lines: [c.lines[0], c.lines[1]],
|
|
8523
|
+
methods: (c.methods ?? []).map((m) => ({
|
|
8524
|
+
name: m.name,
|
|
8525
|
+
lines: [m.lines[0], m.lines[1]],
|
|
8526
|
+
async: m.async
|
|
8527
|
+
}))
|
|
8528
|
+
})),
|
|
8529
|
+
functions: (r.functions ?? []).map((f) => ({
|
|
8530
|
+
name: f.name,
|
|
8531
|
+
lines: [f.lines[0], f.lines[1]],
|
|
8532
|
+
decorators: f.decorators ?? [],
|
|
8533
|
+
async: f.async
|
|
8534
|
+
}))
|
|
8535
|
+
});
|
|
8536
|
+
}
|
|
8537
|
+
if (exitCode === 2) {
|
|
8538
|
+
warnings.push({
|
|
8539
|
+
path: "",
|
|
8540
|
+
message: `python scanner exited with code 2 (fatal); partial results only. stderr: ${stderr.slice(0, 200)}`
|
|
8541
|
+
});
|
|
8542
|
+
}
|
|
8543
|
+
return { entries, warnings };
|
|
8544
|
+
}
|
|
8545
|
+
};
|
|
8546
|
+
pythonScanner = new PythonScanner();
|
|
8416
8547
|
}
|
|
8417
|
-
|
|
8548
|
+
});
|
|
8549
|
+
|
|
8550
|
+
// src/code-map/scanners/javascript.ts
|
|
8551
|
+
var javascript_exports = {};
|
|
8552
|
+
__export(javascript_exports, {
|
|
8553
|
+
COMMON_PLUGINS: () => COMMON_PLUGINS,
|
|
8554
|
+
countSourceLines: () => countSourceLines,
|
|
8555
|
+
jsScanner: () => jsScanner,
|
|
8556
|
+
parseAndExtract: () => parseAndExtract,
|
|
8557
|
+
parseJsSource: () => parseJsSource,
|
|
8558
|
+
parseSourceWithPlugins: () => parseSourceWithPlugins
|
|
8559
|
+
});
|
|
8560
|
+
import { readFileSync as readFileSync14 } from "fs";
|
|
8561
|
+
import { parse } from "@babel/parser";
|
|
8562
|
+
function parseSourceWithPlugins(source, plugins) {
|
|
8563
|
+
return parse(source, {
|
|
8564
|
+
sourceType: "unambiguous",
|
|
8565
|
+
allowReturnOutsideFunction: true,
|
|
8566
|
+
allowAwaitOutsideFunction: true,
|
|
8567
|
+
errorRecovery: true,
|
|
8568
|
+
plugins
|
|
8569
|
+
});
|
|
8418
8570
|
}
|
|
8419
|
-
function
|
|
8420
|
-
|
|
8421
|
-
return
|
|
8422
|
-
|
|
8423
|
-
|
|
8424
|
-
}
|
|
8571
|
+
function isCallExpression(node, callee) {
|
|
8572
|
+
if (!node || node.type !== "CallExpression")
|
|
8573
|
+
return false;
|
|
8574
|
+
const c = node.callee;
|
|
8575
|
+
return c.type === "Identifier" && c.name === callee;
|
|
8425
8576
|
}
|
|
8426
|
-
function
|
|
8427
|
-
|
|
8428
|
-
|
|
8577
|
+
function extractRequireModule(node) {
|
|
8578
|
+
if (!node || node.type !== "CallExpression")
|
|
8579
|
+
return null;
|
|
8580
|
+
const c = node.callee;
|
|
8581
|
+
if (c.type !== "Identifier" || c.name !== "require")
|
|
8582
|
+
return null;
|
|
8583
|
+
const arg = node.arguments[0];
|
|
8584
|
+
if (!arg || arg.type !== "StringLiteral")
|
|
8585
|
+
return null;
|
|
8586
|
+
return arg.value;
|
|
8429
8587
|
}
|
|
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;
|
|
8588
|
+
function getLineRange(node) {
|
|
8589
|
+
const start = node.loc?.start.line ?? 1;
|
|
8590
|
+
const end = node.loc?.end.line ?? start;
|
|
8591
|
+
return [start, end];
|
|
8442
8592
|
}
|
|
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" });
|
|
8593
|
+
function processImportDeclaration(node) {
|
|
8594
|
+
const items = [];
|
|
8595
|
+
for (const spec of node.specifiers) {
|
|
8596
|
+
if (spec.type === "ImportDefaultSpecifier") {
|
|
8597
|
+
items.push(`default:${spec.local.name}`);
|
|
8598
|
+
} else if (spec.type === "ImportNamespaceSpecifier") {
|
|
8599
|
+
items.push(`*:${spec.local.name}`);
|
|
8600
|
+
} else if (spec.type === "ImportSpecifier") {
|
|
8601
|
+
const imported = spec.imported;
|
|
8602
|
+
items.push(imported.type === "Identifier" ? imported.name : spec.local.name);
|
|
8603
|
+
}
|
|
8493
8604
|
}
|
|
8494
|
-
return
|
|
8605
|
+
return {
|
|
8606
|
+
module: node.source.value,
|
|
8607
|
+
items,
|
|
8608
|
+
line: node.loc?.start.line ?? 1
|
|
8609
|
+
};
|
|
8495
8610
|
}
|
|
8496
|
-
function
|
|
8497
|
-
|
|
8498
|
-
|
|
8499
|
-
const m = text.match(/^model:\s*(\S+)/m);
|
|
8500
|
-
return m ? m[1] : null;
|
|
8501
|
-
} catch {
|
|
8611
|
+
function processFunctionDeclaration(node, nameOverride) {
|
|
8612
|
+
const name = nameOverride ?? node.id?.name;
|
|
8613
|
+
if (!name)
|
|
8502
8614
|
return null;
|
|
8503
|
-
|
|
8615
|
+
return {
|
|
8616
|
+
name,
|
|
8617
|
+
lines: getLineRange(node),
|
|
8618
|
+
decorators: [],
|
|
8619
|
+
async: node.async
|
|
8620
|
+
};
|
|
8504
8621
|
}
|
|
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
|
-
});
|
|
8622
|
+
function processClassDeclaration(node, nameOverride) {
|
|
8623
|
+
const name = nameOverride ?? node.id?.name;
|
|
8624
|
+
if (!name)
|
|
8625
|
+
return null;
|
|
8626
|
+
const methods = [];
|
|
8627
|
+
for (const member of node.body.body) {
|
|
8628
|
+
if (member.type === "ClassMethod" || member.type === "ClassPrivateMethod") {
|
|
8629
|
+
const m = member;
|
|
8630
|
+
let methodName;
|
|
8631
|
+
if (m.key.type === "Identifier") {
|
|
8632
|
+
methodName = m.key.name;
|
|
8633
|
+
} else if (m.key.type === "PrivateName") {
|
|
8634
|
+
methodName = `#${m.key.id.name}`;
|
|
8635
|
+
} else {
|
|
8636
|
+
methodName = "<computed>";
|
|
8543
8637
|
}
|
|
8638
|
+
methods.push({
|
|
8639
|
+
name: methodName,
|
|
8640
|
+
lines: getLineRange(m),
|
|
8641
|
+
async: m.async
|
|
8642
|
+
});
|
|
8544
8643
|
}
|
|
8545
|
-
if (!foundAny) {
|
|
8546
|
-
}
|
|
8547
|
-
}
|
|
8548
|
-
if (findings.length === 0) {
|
|
8549
|
-
findings.push({ level: "ok", message: "model-policy roles match installed agent prompts" });
|
|
8550
8644
|
}
|
|
8551
|
-
return
|
|
8645
|
+
return {
|
|
8646
|
+
name,
|
|
8647
|
+
lines: getLineRange(node),
|
|
8648
|
+
methods
|
|
8649
|
+
};
|
|
8552
8650
|
}
|
|
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
|
-
});
|
|
8651
|
+
function processVariableDeclaration(node, imports, constants, functions) {
|
|
8652
|
+
for (const decl of node.declarations) {
|
|
8653
|
+
if (!decl.id || decl.id.type !== "Identifier")
|
|
8654
|
+
continue;
|
|
8655
|
+
const varName = decl.id.name;
|
|
8656
|
+
const init2 = decl.init;
|
|
8657
|
+
if (init2 && isCallExpression(init2, "require")) {
|
|
8658
|
+
const mod = extractRequireModule(init2);
|
|
8659
|
+
if (mod) {
|
|
8660
|
+
imports.push({ module: mod, items: [`default:${varName}`], line: node.loc?.start.line ?? 1 });
|
|
8580
8661
|
}
|
|
8662
|
+
continue;
|
|
8581
8663
|
}
|
|
8582
|
-
if (
|
|
8583
|
-
|
|
8664
|
+
if (isAllCapsConst(varName) && init2 !== null && init2 !== void 0) {
|
|
8665
|
+
constants.push({ name: varName, line: node.loc?.start.line ?? 1 });
|
|
8666
|
+
continue;
|
|
8584
8667
|
}
|
|
8585
|
-
|
|
8586
|
-
|
|
8587
|
-
|
|
8668
|
+
if (init2 && (init2.type === "ArrowFunctionExpression" || init2.type === "FunctionExpression")) {
|
|
8669
|
+
functions.push({
|
|
8670
|
+
name: varName,
|
|
8671
|
+
lines: getLineRange(node),
|
|
8672
|
+
decorators: [],
|
|
8673
|
+
async: init2.async
|
|
8674
|
+
});
|
|
8675
|
+
} else if (init2 && init2.type === "CallExpression" && /^[A-Z]/.test(varName)) {
|
|
8676
|
+
functions.push({
|
|
8677
|
+
name: varName,
|
|
8678
|
+
lines: getLineRange(node),
|
|
8679
|
+
decorators: [],
|
|
8680
|
+
async: false
|
|
8681
|
+
});
|
|
8682
|
+
}
|
|
8683
|
+
}
|
|
8684
|
+
}
|
|
8685
|
+
function processTsInterfaceDeclaration(node, exported) {
|
|
8686
|
+
if (!node || node.type !== "TSInterfaceDeclaration")
|
|
8687
|
+
return null;
|
|
8688
|
+
if (!node.id || node.id.type !== "Identifier")
|
|
8689
|
+
return null;
|
|
8690
|
+
return {
|
|
8691
|
+
name: node.id.name,
|
|
8692
|
+
lines: getLineRange(node),
|
|
8693
|
+
exported
|
|
8694
|
+
};
|
|
8695
|
+
}
|
|
8696
|
+
function processTsTypeAliasDeclaration(node, exported) {
|
|
8697
|
+
if (!node || node.type !== "TSTypeAliasDeclaration")
|
|
8698
|
+
return null;
|
|
8699
|
+
if (!node.id || node.id.type !== "Identifier")
|
|
8700
|
+
return null;
|
|
8701
|
+
return {
|
|
8702
|
+
name: node.id.name,
|
|
8703
|
+
lines: getLineRange(node),
|
|
8704
|
+
exported
|
|
8705
|
+
};
|
|
8706
|
+
}
|
|
8707
|
+
function processTsEnumDeclaration(node, exported) {
|
|
8708
|
+
if (!node || node.type !== "TSEnumDeclaration")
|
|
8709
|
+
return null;
|
|
8710
|
+
if (!node.id || node.id.type !== "Identifier")
|
|
8711
|
+
return null;
|
|
8712
|
+
const members = [];
|
|
8713
|
+
for (const m of node.members ?? []) {
|
|
8714
|
+
if (!m || !m.id)
|
|
8715
|
+
continue;
|
|
8716
|
+
if (m.id.type === "Identifier")
|
|
8717
|
+
members.push(m.id.name);
|
|
8718
|
+
else if (m.id.type === "StringLiteral")
|
|
8719
|
+
members.push(m.id.value);
|
|
8588
8720
|
}
|
|
8721
|
+
return {
|
|
8722
|
+
name: node.id.name,
|
|
8723
|
+
lines: getLineRange(node),
|
|
8724
|
+
exported,
|
|
8725
|
+
members
|
|
8726
|
+
};
|
|
8589
8727
|
}
|
|
8590
|
-
function
|
|
8591
|
-
|
|
8592
|
-
|
|
8593
|
-
|
|
8594
|
-
|
|
8595
|
-
|
|
8596
|
-
|
|
8597
|
-
|
|
8598
|
-
|
|
8728
|
+
function processStatement(stmt, buckets, extractTsTypes, exportedFromWrapper = false) {
|
|
8729
|
+
if (stmt.type === "ExportNamedDeclaration" && stmt.declaration) {
|
|
8730
|
+
processStatement(stmt.declaration, buckets, extractTsTypes, true);
|
|
8731
|
+
return;
|
|
8732
|
+
}
|
|
8733
|
+
if (stmt.type === "ExportDefaultDeclaration") {
|
|
8734
|
+
const decl = stmt.declaration;
|
|
8735
|
+
if (decl.type === "FunctionDeclaration") {
|
|
8736
|
+
const fe = processFunctionDeclaration(decl, decl.id?.name ?? "default");
|
|
8737
|
+
if (fe)
|
|
8738
|
+
buckets.functions.push(fe);
|
|
8739
|
+
} else if (decl.type === "ClassDeclaration") {
|
|
8740
|
+
const ce = processClassDeclaration(decl, decl.id?.name ?? "default");
|
|
8741
|
+
if (ce)
|
|
8742
|
+
buckets.classes.push(ce);
|
|
8599
8743
|
}
|
|
8600
|
-
return
|
|
8744
|
+
return;
|
|
8601
8745
|
}
|
|
8602
|
-
|
|
8603
|
-
|
|
8604
|
-
|
|
8605
|
-
return findings;
|
|
8746
|
+
if (stmt.type === "ImportDeclaration") {
|
|
8747
|
+
buckets.imports.push(processImportDeclaration(stmt));
|
|
8748
|
+
return;
|
|
8606
8749
|
}
|
|
8607
|
-
if (
|
|
8608
|
-
const
|
|
8609
|
-
|
|
8610
|
-
|
|
8750
|
+
if (stmt.type === "FunctionDeclaration") {
|
|
8751
|
+
const fe = processFunctionDeclaration(stmt);
|
|
8752
|
+
if (fe)
|
|
8753
|
+
buckets.functions.push(fe);
|
|
8754
|
+
return;
|
|
8611
8755
|
}
|
|
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+)" });
|
|
8756
|
+
if (stmt.type === "ClassDeclaration") {
|
|
8757
|
+
const ce = processClassDeclaration(stmt);
|
|
8758
|
+
if (ce)
|
|
8759
|
+
buckets.classes.push(ce);
|
|
8760
|
+
return;
|
|
8618
8761
|
}
|
|
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);
|
|
8762
|
+
if (stmt.type === "VariableDeclaration") {
|
|
8763
|
+
processVariableDeclaration(stmt, buckets.imports, buckets.constants, buckets.functions);
|
|
8764
|
+
return;
|
|
8626
8765
|
}
|
|
8627
|
-
|
|
8628
|
-
|
|
8629
|
-
|
|
8630
|
-
|
|
8631
|
-
|
|
8766
|
+
if (extractTsTypes) {
|
|
8767
|
+
const anyStmt = stmt;
|
|
8768
|
+
if (anyStmt.type === "TSInterfaceDeclaration") {
|
|
8769
|
+
const e = processTsInterfaceDeclaration(anyStmt, exportedFromWrapper);
|
|
8770
|
+
if (e)
|
|
8771
|
+
buckets.interfaces.push(e);
|
|
8772
|
+
return;
|
|
8773
|
+
}
|
|
8774
|
+
if (anyStmt.type === "TSTypeAliasDeclaration") {
|
|
8775
|
+
const e = processTsTypeAliasDeclaration(anyStmt, exportedFromWrapper);
|
|
8776
|
+
if (e)
|
|
8777
|
+
buckets.types.push(e);
|
|
8778
|
+
return;
|
|
8779
|
+
}
|
|
8780
|
+
if (anyStmt.type === "TSEnumDeclaration") {
|
|
8781
|
+
const e = processTsEnumDeclaration(anyStmt, exportedFromWrapper);
|
|
8782
|
+
if (e)
|
|
8783
|
+
buckets.enums.push(e);
|
|
8784
|
+
return;
|
|
8785
|
+
}
|
|
8632
8786
|
}
|
|
8633
|
-
if (
|
|
8634
|
-
|
|
8787
|
+
if (stmt.type === "ExpressionStatement") {
|
|
8788
|
+
const expr = stmt.expression;
|
|
8789
|
+
if (isCallExpression(expr, "require")) {
|
|
8790
|
+
const mod = extractRequireModule(expr);
|
|
8791
|
+
if (mod) {
|
|
8792
|
+
buckets.imports.push({ module: mod, items: [], line: stmt.loc?.start.line ?? 1 });
|
|
8793
|
+
}
|
|
8794
|
+
}
|
|
8635
8795
|
}
|
|
8636
|
-
|
|
8637
|
-
|
|
8796
|
+
}
|
|
8797
|
+
function parseAndExtract(source, opts) {
|
|
8798
|
+
let ast;
|
|
8799
|
+
try {
|
|
8800
|
+
ast = parseSourceWithPlugins(source, opts.plugins);
|
|
8801
|
+
} catch {
|
|
8802
|
+
return null;
|
|
8638
8803
|
}
|
|
8639
|
-
|
|
8640
|
-
|
|
8804
|
+
const buckets = {
|
|
8805
|
+
imports: [],
|
|
8806
|
+
constants: [],
|
|
8807
|
+
functions: [],
|
|
8808
|
+
classes: [],
|
|
8809
|
+
interfaces: [],
|
|
8810
|
+
types: [],
|
|
8811
|
+
enums: []
|
|
8812
|
+
};
|
|
8813
|
+
for (const stmt of ast.program.body) {
|
|
8814
|
+
processStatement(stmt, buckets, !!opts.extractTsTypes);
|
|
8641
8815
|
}
|
|
8642
|
-
|
|
8643
|
-
|
|
8644
|
-
|
|
8645
|
-
|
|
8646
|
-
|
|
8647
|
-
|
|
8816
|
+
return buckets;
|
|
8817
|
+
}
|
|
8818
|
+
function countSourceLines(source) {
|
|
8819
|
+
if (source === "")
|
|
8820
|
+
return 0;
|
|
8821
|
+
return source.split(/\r?\n/).length - (source.endsWith("\n") || source.endsWith("\r\n") ? 1 : 0);
|
|
8822
|
+
}
|
|
8823
|
+
function parseJsSource(source, relPath) {
|
|
8824
|
+
const total_lines = countSourceLines(source);
|
|
8825
|
+
const r = parseAndExtract(source, { plugins: JS_PLUGINS, extractTsTypes: false });
|
|
8826
|
+
if (!r)
|
|
8827
|
+
return null;
|
|
8648
8828
|
return {
|
|
8649
|
-
|
|
8650
|
-
|
|
8651
|
-
|
|
8652
|
-
|
|
8653
|
-
|
|
8654
|
-
|
|
8829
|
+
path: relPath,
|
|
8830
|
+
total_lines,
|
|
8831
|
+
imports: r.imports,
|
|
8832
|
+
constants: r.constants,
|
|
8833
|
+
classes: r.classes,
|
|
8834
|
+
functions: r.functions
|
|
8655
8835
|
};
|
|
8656
8836
|
}
|
|
8657
|
-
|
|
8658
|
-
|
|
8659
|
-
|
|
8660
|
-
|
|
8661
|
-
|
|
8662
|
-
|
|
8663
|
-
|
|
8664
|
-
|
|
8665
|
-
|
|
8666
|
-
|
|
8667
|
-
|
|
8668
|
-
|
|
8669
|
-
|
|
8670
|
-
|
|
8671
|
-
|
|
8672
|
-
|
|
8673
|
-
|
|
8837
|
+
var COMMON_PLUGINS, JS_PLUGINS, JavaScriptScanner, jsScanner;
|
|
8838
|
+
var init_javascript = __esm({
|
|
8839
|
+
"src/code-map/scanners/javascript.ts"() {
|
|
8840
|
+
"use strict";
|
|
8841
|
+
init_common();
|
|
8842
|
+
COMMON_PLUGINS = [
|
|
8843
|
+
"classProperties",
|
|
8844
|
+
"classPrivateProperties",
|
|
8845
|
+
"classPrivateMethods",
|
|
8846
|
+
"decorators-legacy",
|
|
8847
|
+
"topLevelAwait",
|
|
8848
|
+
"dynamicImport",
|
|
8849
|
+
"optionalChaining",
|
|
8850
|
+
"nullishCoalescingOperator",
|
|
8851
|
+
"objectRestSpread",
|
|
8852
|
+
"asyncGenerators",
|
|
8853
|
+
"numericSeparator",
|
|
8854
|
+
"logicalAssignment"
|
|
8855
|
+
];
|
|
8856
|
+
JS_PLUGINS = ["jsx", ...COMMON_PLUGINS];
|
|
8857
|
+
JavaScriptScanner = class {
|
|
8858
|
+
extensions = [".js"];
|
|
8859
|
+
async scan(absolutePath, repoRoot) {
|
|
8860
|
+
let source;
|
|
8861
|
+
try {
|
|
8862
|
+
source = readFileSync14(absolutePath, "utf8");
|
|
8863
|
+
} catch (err) {
|
|
8864
|
+
throw err;
|
|
8865
|
+
}
|
|
8866
|
+
if (isBinary(source)) {
|
|
8867
|
+
return null;
|
|
8868
|
+
}
|
|
8869
|
+
const relPath = canonicalRelPath(absolutePath, repoRoot);
|
|
8870
|
+
return parseJsSource(source, relPath);
|
|
8674
8871
|
}
|
|
8675
|
-
}
|
|
8676
|
-
|
|
8677
|
-
|
|
8678
|
-
|
|
8679
|
-
|
|
8680
|
-
|
|
8681
|
-
|
|
8682
|
-
|
|
8683
|
-
|
|
8684
|
-
|
|
8872
|
+
};
|
|
8873
|
+
jsScanner = new JavaScriptScanner();
|
|
8874
|
+
}
|
|
8875
|
+
});
|
|
8876
|
+
|
|
8877
|
+
// src/code-map/scanners/typescript.ts
|
|
8878
|
+
var typescript_exports = {};
|
|
8879
|
+
__export(typescript_exports, {
|
|
8880
|
+
tsScanner: () => tsScanner
|
|
8881
|
+
});
|
|
8882
|
+
import { readFileSync as readFileSync15 } from "fs";
|
|
8883
|
+
var TypeScriptScanner, tsScanner;
|
|
8884
|
+
var init_typescript = __esm({
|
|
8885
|
+
"src/code-map/scanners/typescript.ts"() {
|
|
8886
|
+
"use strict";
|
|
8887
|
+
init_common();
|
|
8888
|
+
init_javascript();
|
|
8889
|
+
TypeScriptScanner = class {
|
|
8890
|
+
extensions = [".ts", ".tsx"];
|
|
8891
|
+
async scan(absolutePath, repoRoot) {
|
|
8892
|
+
let source;
|
|
8893
|
+
try {
|
|
8894
|
+
source = readFileSync15(absolutePath, "utf8");
|
|
8895
|
+
} catch (err) {
|
|
8896
|
+
throw err;
|
|
8897
|
+
}
|
|
8898
|
+
if (isBinary(source)) {
|
|
8899
|
+
return null;
|
|
8900
|
+
}
|
|
8901
|
+
const isTsx = absolutePath.toLowerCase().endsWith(".tsx");
|
|
8902
|
+
const plugins = isTsx ? ["typescript", "jsx", ...COMMON_PLUGINS] : ["typescript", ...COMMON_PLUGINS];
|
|
8903
|
+
const r = parseAndExtract(source, { plugins, extractTsTypes: true });
|
|
8904
|
+
if (!r)
|
|
8905
|
+
return null;
|
|
8906
|
+
const relPath = canonicalRelPath(absolutePath, repoRoot);
|
|
8907
|
+
const total_lines = countSourceLines(source);
|
|
8908
|
+
return {
|
|
8909
|
+
path: relPath,
|
|
8910
|
+
total_lines,
|
|
8911
|
+
imports: r.imports,
|
|
8912
|
+
constants: r.constants,
|
|
8913
|
+
classes: r.classes,
|
|
8914
|
+
functions: r.functions,
|
|
8915
|
+
interfaces: r.interfaces,
|
|
8916
|
+
types: r.types,
|
|
8917
|
+
enums: r.enums
|
|
8918
|
+
};
|
|
8685
8919
|
}
|
|
8686
|
-
}
|
|
8687
|
-
|
|
8688
|
-
|
|
8689
|
-
|
|
8690
|
-
|
|
8920
|
+
};
|
|
8921
|
+
tsScanner = new TypeScriptScanner();
|
|
8922
|
+
}
|
|
8923
|
+
});
|
|
8924
|
+
|
|
8925
|
+
// src/code-map/scanners/vue.ts
|
|
8926
|
+
var vue_exports = {};
|
|
8927
|
+
__export(vue_exports, {
|
|
8928
|
+
vueScanner: () => vueScanner
|
|
8929
|
+
});
|
|
8930
|
+
import { readFileSync as readFileSync16 } from "fs";
|
|
8931
|
+
import { parse as parse2 } from "@vue/compiler-sfc";
|
|
8932
|
+
var VueScanner, vueScanner;
|
|
8933
|
+
var init_vue = __esm({
|
|
8934
|
+
"src/code-map/scanners/vue.ts"() {
|
|
8935
|
+
"use strict";
|
|
8936
|
+
init_common();
|
|
8937
|
+
init_javascript();
|
|
8938
|
+
VueScanner = class {
|
|
8939
|
+
extensions = [".vue"];
|
|
8940
|
+
async scan(absolutePath, repoRoot) {
|
|
8941
|
+
let source;
|
|
8691
8942
|
try {
|
|
8692
|
-
|
|
8693
|
-
} catch {
|
|
8943
|
+
source = readFileSync16(absolutePath, "utf8");
|
|
8944
|
+
} catch (err) {
|
|
8945
|
+
throw err;
|
|
8694
8946
|
}
|
|
8695
|
-
|
|
8696
|
-
|
|
8697
|
-
|
|
8698
|
-
|
|
8699
|
-
|
|
8700
|
-
|
|
8701
|
-
|
|
8702
|
-
|
|
8703
|
-
|
|
8704
|
-
|
|
8705
|
-
|
|
8706
|
-
|
|
8707
|
-
|
|
8708
|
-
|
|
8709
|
-
|
|
8710
|
-
|
|
8711
|
-
|
|
8712
|
-
|
|
8713
|
-
|
|
8947
|
+
if (isBinary(source)) {
|
|
8948
|
+
return null;
|
|
8949
|
+
}
|
|
8950
|
+
const relPath = canonicalRelPath(absolutePath, repoRoot);
|
|
8951
|
+
const total_lines = source.split(/\r?\n/).length - (source.endsWith("\n") || source.endsWith("\r\n") ? 1 : 0);
|
|
8952
|
+
const { descriptor } = parse2(source, { filename: absolutePath });
|
|
8953
|
+
const allImports = [];
|
|
8954
|
+
const allConstants = [];
|
|
8955
|
+
const allFunctions = [];
|
|
8956
|
+
const allClasses = [];
|
|
8957
|
+
const warnings = [];
|
|
8958
|
+
const scriptBlocks = [descriptor.script, descriptor.scriptSetup].filter(Boolean);
|
|
8959
|
+
if (scriptBlocks.length === 0) {
|
|
8960
|
+
return {
|
|
8961
|
+
path: relPath,
|
|
8962
|
+
total_lines,
|
|
8963
|
+
imports: [],
|
|
8964
|
+
constants: [],
|
|
8965
|
+
classes: [],
|
|
8966
|
+
functions: []
|
|
8967
|
+
};
|
|
8968
|
+
}
|
|
8969
|
+
for (const block of scriptBlocks) {
|
|
8970
|
+
if (!block)
|
|
8971
|
+
continue;
|
|
8972
|
+
if (block.lang === "ts" || block.lang === "tsx") {
|
|
8973
|
+
warnings.push({
|
|
8974
|
+
path: relPath,
|
|
8975
|
+
message: `skipping <script lang=${block.lang}> block (TypeScript not supported in v1)`
|
|
8976
|
+
});
|
|
8977
|
+
continue;
|
|
8978
|
+
}
|
|
8979
|
+
const blockContent = block.content;
|
|
8980
|
+
const lineOffset = block.loc.start.line;
|
|
8981
|
+
const scriptEntry = parseJsSource(blockContent, relPath);
|
|
8982
|
+
if (!scriptEntry) {
|
|
8983
|
+
warnings.push({ path: relPath, message: "parse error in script block" });
|
|
8984
|
+
continue;
|
|
8985
|
+
}
|
|
8986
|
+
const offset = lineOffset - 1;
|
|
8987
|
+
for (const imp of scriptEntry.imports) {
|
|
8988
|
+
allImports.push({ ...imp, line: imp.line + offset });
|
|
8989
|
+
}
|
|
8990
|
+
for (const c of scriptEntry.constants) {
|
|
8991
|
+
allConstants.push({ ...c, line: c.line + offset });
|
|
8992
|
+
}
|
|
8993
|
+
for (const fn of scriptEntry.functions) {
|
|
8994
|
+
allFunctions.push({
|
|
8995
|
+
...fn,
|
|
8996
|
+
lines: [fn.lines[0] + offset, fn.lines[1] + offset]
|
|
8997
|
+
});
|
|
8998
|
+
}
|
|
8999
|
+
for (const cls of scriptEntry.classes) {
|
|
9000
|
+
allClasses.push({
|
|
9001
|
+
...cls,
|
|
9002
|
+
lines: [cls.lines[0] + offset, cls.lines[1] + offset],
|
|
9003
|
+
methods: cls.methods.map((m) => ({
|
|
9004
|
+
...m,
|
|
9005
|
+
lines: [m.lines[0] + offset, m.lines[1] + offset]
|
|
9006
|
+
}))
|
|
9007
|
+
});
|
|
8714
9008
|
}
|
|
9009
|
+
}
|
|
9010
|
+
return {
|
|
9011
|
+
path: relPath,
|
|
9012
|
+
total_lines,
|
|
9013
|
+
imports: allImports,
|
|
9014
|
+
constants: allConstants,
|
|
9015
|
+
classes: allClasses,
|
|
9016
|
+
functions: allFunctions
|
|
8715
9017
|
};
|
|
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
9018
|
}
|
|
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);
|
|
9019
|
+
};
|
|
9020
|
+
vueScanner = new VueScanner();
|
|
8733
9021
|
}
|
|
8734
|
-
|
|
8735
|
-
|
|
8736
|
-
|
|
8737
|
-
|
|
8738
|
-
|
|
8739
|
-
|
|
8740
|
-
|
|
8741
|
-
|
|
8742
|
-
|
|
8743
|
-
|
|
8744
|
-
|
|
8745
|
-
|
|
8746
|
-
|
|
8747
|
-
|
|
8748
|
-
|
|
9022
|
+
});
|
|
9023
|
+
|
|
9024
|
+
// src/commands/code-map.ts
|
|
9025
|
+
var code_map_exports = {};
|
|
9026
|
+
__export(code_map_exports, {
|
|
9027
|
+
codeMap: () => codeMap
|
|
9028
|
+
});
|
|
9029
|
+
import { existsSync as existsSync16, mkdirSync as mkdirSync8, readFileSync as readFileSync17, writeFileSync as writeFileSync9 } from "fs";
|
|
9030
|
+
import { resolve, dirname as dirname5 } from "path";
|
|
9031
|
+
import { createRequire } from "module";
|
|
9032
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
9033
|
+
import { join as join19 } from "path";
|
|
9034
|
+
async function codeMap(opts) {
|
|
9035
|
+
const root = resolve(process.cwd(), opts.path);
|
|
9036
|
+
const start = Date.now();
|
|
9037
|
+
let cfg;
|
|
9038
|
+
try {
|
|
9039
|
+
cfg = loadCodeMapConfig(root);
|
|
9040
|
+
} catch (err) {
|
|
9041
|
+
log.error(`code-map: ${err.message}`);
|
|
9042
|
+
return 1;
|
|
9043
|
+
}
|
|
9044
|
+
const include = [...cfg.include, ...opts.include];
|
|
9045
|
+
const exclude = [...cfg.exclude, ...opts.exclude];
|
|
9046
|
+
const files = walkRepo(root, { include, exclude });
|
|
9047
|
+
const buckets = bucketByExtension(files);
|
|
9048
|
+
const result = { entries: [], warnings: [] };
|
|
9049
|
+
const tasks = [];
|
|
9050
|
+
if (buckets[".py"]?.length) {
|
|
9051
|
+
const { pythonScanner: pythonScanner2 } = await Promise.resolve().then(() => (init_python(), python_exports));
|
|
9052
|
+
if (pythonScanner2.scanBatch) {
|
|
9053
|
+
tasks.push(pythonScanner2.scanBatch(buckets[".py"], root));
|
|
8749
9054
|
}
|
|
8750
9055
|
}
|
|
8751
|
-
|
|
8752
|
-
|
|
8753
|
-
|
|
8754
|
-
|
|
8755
|
-
|
|
9056
|
+
const jsFiles = [
|
|
9057
|
+
...buckets[".js"] ?? [],
|
|
9058
|
+
...buckets[".jsx"] ?? [],
|
|
9059
|
+
...buckets[".mjs"] ?? [],
|
|
9060
|
+
...buckets[".cjs"] ?? []
|
|
9061
|
+
];
|
|
9062
|
+
if (jsFiles.length) {
|
|
9063
|
+
const { jsScanner: jsScanner2 } = await Promise.resolve().then(() => (init_javascript(), javascript_exports));
|
|
9064
|
+
tasks.push(scanInProcess(jsScanner2, jsFiles, root));
|
|
8756
9065
|
}
|
|
8757
|
-
|
|
8758
|
-
|
|
8759
|
-
|
|
8760
|
-
|
|
8761
|
-
|
|
8762
|
-
|
|
8763
|
-
|
|
8764
|
-
else
|
|
8765
|
-
log.error(finding.message);
|
|
9066
|
+
const tsFiles = [
|
|
9067
|
+
...buckets[".ts"] ?? [],
|
|
9068
|
+
...buckets[".tsx"] ?? []
|
|
9069
|
+
];
|
|
9070
|
+
if (tsFiles.length) {
|
|
9071
|
+
const { tsScanner: tsScanner2 } = await Promise.resolve().then(() => (init_typescript(), typescript_exports));
|
|
9072
|
+
tasks.push(scanInProcess(tsScanner2, tsFiles, root));
|
|
8766
9073
|
}
|
|
8767
|
-
|
|
8768
|
-
|
|
8769
|
-
|
|
8770
|
-
|
|
9074
|
+
if (buckets[".vue"]?.length) {
|
|
9075
|
+
const { vueScanner: vueScanner2 } = await Promise.resolve().then(() => (init_vue(), vue_exports));
|
|
9076
|
+
tasks.push(scanInProcess(vueScanner2, buckets[".vue"], root));
|
|
9077
|
+
}
|
|
9078
|
+
for (const r of await Promise.all(tasks)) {
|
|
9079
|
+
result.entries.push(...r.entries);
|
|
9080
|
+
result.warnings.push(...r.warnings);
|
|
9081
|
+
}
|
|
9082
|
+
result.entries.sort((a, b) => a.path < b.path ? -1 : a.path > b.path ? 1 : 0);
|
|
9083
|
+
for (const e of result.entries) {
|
|
9084
|
+
if (e.total_lines > opts.maxLines) {
|
|
9085
|
+
result.warnings.push({
|
|
9086
|
+
path: e.path,
|
|
9087
|
+
message: `file exceeds --max-lines (${e.total_lines} > ${opts.maxLines})`
|
|
9088
|
+
});
|
|
9089
|
+
}
|
|
9090
|
+
}
|
|
9091
|
+
const yamlBody = renderYaml(result.entries, { generator: `cdd-kit ${_pkg.version}` });
|
|
9092
|
+
const totalSrc = result.entries.reduce((s, e) => s + e.total_lines, 0);
|
|
9093
|
+
const mapLines = yamlBody.split("\n").length;
|
|
9094
|
+
const compression = totalSrc === 0 ? 0 : totalSrc / mapLines;
|
|
9095
|
+
const summaryLine = `scanned ${result.entries.length} files, ${totalSrc} src lines -> ${opts.out} (${mapLines} lines, compression ${compression.toFixed(1)}x)`;
|
|
9096
|
+
for (const w of result.warnings) {
|
|
9097
|
+
log.warn(`${w.path}: ${w.message}`);
|
|
8771
9098
|
}
|
|
8772
|
-
if (
|
|
8773
|
-
|
|
8774
|
-
|
|
8775
|
-
|
|
9099
|
+
if (opts.check) {
|
|
9100
|
+
const existing = existsSync16(opts.out) ? readFileSync17(opts.out, "utf8") : "";
|
|
9101
|
+
const normalize = (s) => s.replace(/^# generated: [^\n]+\n/m, "# generated: <normalized>\n");
|
|
9102
|
+
if (normalize(existing) !== normalize(yamlBody)) {
|
|
9103
|
+
log.error(`code-map out of date: ${opts.out} would change. Run \`cdd-kit code-map\` to regenerate.`);
|
|
9104
|
+
return 1;
|
|
9105
|
+
}
|
|
9106
|
+
log.ok(`code-map up to date: ${opts.out}`);
|
|
9107
|
+
return 0;
|
|
8776
9108
|
}
|
|
8777
|
-
|
|
9109
|
+
mkdirSync8(dirname5(opts.out), { recursive: true });
|
|
9110
|
+
writeFileSync9(opts.out, yamlBody, "utf8");
|
|
9111
|
+
log.ok(`${summaryLine} (${Date.now() - start}ms)`);
|
|
9112
|
+
return 0;
|
|
8778
9113
|
}
|
|
8779
|
-
var
|
|
8780
|
-
|
|
9114
|
+
var _require, _pkgPath, _pkg;
|
|
9115
|
+
var init_code_map = __esm({
|
|
9116
|
+
"src/commands/code-map.ts"() {
|
|
8781
9117
|
"use strict";
|
|
8782
9118
|
init_logger();
|
|
8783
|
-
|
|
8784
|
-
|
|
9119
|
+
init_yaml_writer();
|
|
9120
|
+
init_orchestrator();
|
|
9121
|
+
init_config();
|
|
9122
|
+
_require = createRequire(import.meta.url);
|
|
9123
|
+
_pkgPath = join19(fileURLToPath2(import.meta.url), "..", "..", "..", "package.json");
|
|
9124
|
+
_pkg = JSON.parse(readFileSync17(_pkgPath, "utf8"));
|
|
8785
9125
|
}
|
|
8786
9126
|
});
|
|
8787
9127
|
|
|
8788
|
-
// src/commands/
|
|
8789
|
-
var
|
|
8790
|
-
__export(
|
|
8791
|
-
|
|
9128
|
+
// src/commands/refresh.ts
|
|
9129
|
+
var refresh_exports = {};
|
|
9130
|
+
__export(refresh_exports, {
|
|
9131
|
+
refresh: () => refresh
|
|
8792
9132
|
});
|
|
8793
|
-
import {
|
|
8794
|
-
import {
|
|
8795
|
-
import
|
|
8796
|
-
function
|
|
8797
|
-
|
|
8798
|
-
const backupDir2 = join19(backupRoot, changeId);
|
|
8799
|
-
mkdirSync7(backupRoot, { recursive: true });
|
|
8800
|
-
const sourceDir = join19(cwd, "specs", "changes", changeId);
|
|
8801
|
-
if (existsSync16(sourceDir)) {
|
|
8802
|
-
cpSync2(sourceDir, backupDir2, { recursive: true });
|
|
8803
|
-
}
|
|
8804
|
-
return backupDir2;
|
|
9133
|
+
import { existsSync as existsSync17, mkdirSync as mkdirSync9, readdirSync as readdirSync10, copyFileSync as copyFileSync4, readFileSync as readFileSync18, writeFileSync as writeFileSync10 } from "fs";
|
|
9134
|
+
import { dirname as dirname6, join as join20, relative as relative5 } from "path";
|
|
9135
|
+
import { createHash as createHash4 } from "crypto";
|
|
9136
|
+
function fileHash2(filePath) {
|
|
9137
|
+
return createHash4("sha256").update(readFileSync18(filePath)).digest("hex");
|
|
8805
9138
|
}
|
|
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
|
-
|
|
9139
|
+
function planForceRefresh(srcDir, destDir, sectionLabel) {
|
|
9140
|
+
const plan = [];
|
|
9141
|
+
if (!existsSync17(srcDir))
|
|
9142
|
+
return plan;
|
|
9143
|
+
function walk(curSrc, curDest) {
|
|
9144
|
+
let items;
|
|
9145
|
+
try {
|
|
9146
|
+
items = readdirSync10(curSrc, { withFileTypes: true });
|
|
9147
|
+
} catch {
|
|
9148
|
+
return;
|
|
9149
|
+
}
|
|
9150
|
+
for (const item of items) {
|
|
9151
|
+
const sp = join20(curSrc, item.name);
|
|
9152
|
+
const dp = join20(curDest, item.name);
|
|
9153
|
+
if (item.isDirectory()) {
|
|
9154
|
+
walk(sp, dp);
|
|
9155
|
+
continue;
|
|
9156
|
+
}
|
|
9157
|
+
if (!item.isFile())
|
|
9158
|
+
continue;
|
|
9159
|
+
const rel = join20(sectionLabel, relative5(srcDir, sp)).replace(/\\/g, "/");
|
|
9160
|
+
if (!existsSync17(dp)) {
|
|
9161
|
+
plan.push({ src: sp, dest: dp, rel, action: "add" });
|
|
9162
|
+
} else if (fileHash2(sp) !== fileHash2(dp)) {
|
|
9163
|
+
plan.push({ src: sp, dest: dp, rel, action: "overwrite" });
|
|
9164
|
+
} else {
|
|
9165
|
+
plan.push({ src: sp, dest: dp, rel, action: "skip" });
|
|
9166
|
+
}
|
|
9167
|
+
}
|
|
9168
|
+
}
|
|
9169
|
+
walk(srcDir, destDir);
|
|
9170
|
+
return plan;
|
|
8834
9171
|
}
|
|
8835
|
-
function
|
|
8836
|
-
|
|
8837
|
-
|
|
8838
|
-
|
|
8839
|
-
|
|
8840
|
-
|
|
8841
|
-
|
|
8842
|
-
|
|
8843
|
-
|
|
8844
|
-
|
|
8845
|
-
|
|
8846
|
-
|
|
8847
|
-
|
|
8848
|
-
|
|
8849
|
-
|
|
8850
|
-
""
|
|
8851
|
-
|
|
8852
|
-
|
|
8853
|
-
|
|
8854
|
-
|
|
8855
|
-
|
|
8856
|
-
|
|
8857
|
-
|
|
8858
|
-
|
|
8859
|
-
|
|
8860
|
-
|
|
8861
|
-
|
|
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");
|
|
9172
|
+
function planSingleFile(src, dest, rel) {
|
|
9173
|
+
if (!existsSync17(src))
|
|
9174
|
+
return null;
|
|
9175
|
+
if (!existsSync17(dest))
|
|
9176
|
+
return { src, dest, rel, action: "add" };
|
|
9177
|
+
if (fileHash2(src) !== fileHash2(dest))
|
|
9178
|
+
return { src, dest, rel, action: "overwrite" };
|
|
9179
|
+
return { src, dest, rel, action: "skip" };
|
|
9180
|
+
}
|
|
9181
|
+
function applyPlan(plan, backupRoot) {
|
|
9182
|
+
let added = 0;
|
|
9183
|
+
let overwritten = 0;
|
|
9184
|
+
for (const item of plan) {
|
|
9185
|
+
if (item.action === "skip")
|
|
9186
|
+
continue;
|
|
9187
|
+
if (item.action === "overwrite") {
|
|
9188
|
+
const backupPath = join20(backupRoot, item.rel);
|
|
9189
|
+
mkdirSync9(dirname6(backupPath), { recursive: true });
|
|
9190
|
+
copyFileSync4(item.dest, backupPath);
|
|
9191
|
+
overwritten += 1;
|
|
9192
|
+
} else {
|
|
9193
|
+
added += 1;
|
|
9194
|
+
}
|
|
9195
|
+
mkdirSync9(dirname6(item.dest), { recursive: true });
|
|
9196
|
+
copyFileSync4(item.src, item.dest);
|
|
9197
|
+
}
|
|
9198
|
+
return { added, overwritten };
|
|
8882
9199
|
}
|
|
8883
|
-
function
|
|
9200
|
+
function parseAgentFrontmatter(content) {
|
|
8884
9201
|
const m = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
|
|
8885
9202
|
if (!m)
|
|
8886
9203
|
return {};
|
|
8887
|
-
const
|
|
9204
|
+
const fm = {};
|
|
8888
9205
|
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();
|
|
9206
|
+
const km = line.match(/^([a-zA-Z_-]+):\s*(.+?)\s*$/);
|
|
9207
|
+
if (!km)
|
|
8916
9208
|
continue;
|
|
9209
|
+
if (km[1] === "name")
|
|
9210
|
+
fm.name = km[2].trim();
|
|
9211
|
+
if (km[1] === "model")
|
|
9212
|
+
fm.model = km[2].trim();
|
|
9213
|
+
}
|
|
9214
|
+
return fm;
|
|
9215
|
+
}
|
|
9216
|
+
function resyncModelPolicy(cwd) {
|
|
9217
|
+
const policyPath = join20(cwd, ".cdd", "model-policy.json");
|
|
9218
|
+
const result = { changed: false, diff: [], policyPath };
|
|
9219
|
+
if (!existsSync17(AGENTS_HOME))
|
|
9220
|
+
return result;
|
|
9221
|
+
const desired = {};
|
|
9222
|
+
const agentFiles = readdirSync10(AGENTS_HOME, { withFileTypes: true }).filter((d) => d.isFile() && d.name.endsWith(".md"));
|
|
9223
|
+
for (const f of agentFiles) {
|
|
9224
|
+
const content = readFileSync18(join20(AGENTS_HOME, f.name), "utf8");
|
|
9225
|
+
const fm = parseAgentFrontmatter(content);
|
|
9226
|
+
if (fm.name && fm.model)
|
|
9227
|
+
desired[fm.name] = fm.model;
|
|
9228
|
+
}
|
|
9229
|
+
if (Object.keys(desired).length === 0)
|
|
9230
|
+
return result;
|
|
9231
|
+
let existing = {};
|
|
9232
|
+
if (existsSync17(policyPath)) {
|
|
9233
|
+
try {
|
|
9234
|
+
existing = JSON.parse(readFileSync18(policyPath, "utf8"));
|
|
9235
|
+
} catch {
|
|
8917
9236
|
}
|
|
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
9237
|
}
|
|
8931
|
-
|
|
8932
|
-
|
|
8933
|
-
|
|
8934
|
-
|
|
8935
|
-
|
|
8936
|
-
if (existsSync16(newPath)) {
|
|
8937
|
-
return;
|
|
9238
|
+
const existingRoles = existing.roles && typeof existing.roles === "object" ? existing.roles : {};
|
|
9239
|
+
for (const [agent, model] of Object.entries(desired)) {
|
|
9240
|
+
if (existingRoles[agent] !== model) {
|
|
9241
|
+
result.diff.push({ agent, from: existingRoles[agent] ?? null, to: model });
|
|
9242
|
+
}
|
|
8938
9243
|
}
|
|
8939
|
-
if (
|
|
8940
|
-
|
|
8941
|
-
|
|
9244
|
+
if (result.diff.length === 0)
|
|
9245
|
+
return result;
|
|
9246
|
+
const merged = {
|
|
9247
|
+
...existing,
|
|
9248
|
+
roles: desired
|
|
9249
|
+
};
|
|
9250
|
+
if (!("schema-version" in merged))
|
|
9251
|
+
merged["schema-version"] = "0.2.0";
|
|
9252
|
+
if (!("provider" in merged))
|
|
9253
|
+
merged["provider"] = "claude";
|
|
9254
|
+
mkdirSync9(dirname6(policyPath), { recursive: true });
|
|
9255
|
+
writeFileSync10(policyPath, JSON.stringify(merged, null, 2) + "\n", "utf8");
|
|
9256
|
+
result.changed = true;
|
|
9257
|
+
return result;
|
|
9258
|
+
}
|
|
9259
|
+
function planTemplateRefresh(cwd) {
|
|
9260
|
+
const sections = [];
|
|
9261
|
+
sections.push({
|
|
9262
|
+
name: "specs/templates",
|
|
9263
|
+
plan: planForceRefresh(ASSET.specsTemplates, join20(cwd, "specs", "templates"), "specs/templates")
|
|
9264
|
+
});
|
|
9265
|
+
sections.push({
|
|
9266
|
+
name: "tests/templates",
|
|
9267
|
+
plan: planForceRefresh(ASSET.testsTemplates, join20(cwd, "tests", "templates"), "tests/templates")
|
|
9268
|
+
});
|
|
9269
|
+
const ciTemplatesAsset = join20(ASSET.ci, "..", "ci-templates");
|
|
9270
|
+
if (existsSync17(ciTemplatesAsset)) {
|
|
9271
|
+
sections.push({
|
|
9272
|
+
name: "ci-templates",
|
|
9273
|
+
plan: planForceRefresh(ciTemplatesAsset, join20(cwd, "ci-templates"), "ci-templates")
|
|
9274
|
+
});
|
|
8942
9275
|
}
|
|
8943
|
-
const
|
|
8944
|
-
const
|
|
8945
|
-
const
|
|
8946
|
-
|
|
8947
|
-
|
|
8948
|
-
const
|
|
8949
|
-
|
|
8950
|
-
|
|
8951
|
-
|
|
8952
|
-
|
|
8953
|
-
|
|
8954
|
-
|
|
9276
|
+
const wfAsset = join20(ASSET.githubWorkflows, "contract-driven-gates.yml");
|
|
9277
|
+
const wfDest = join20(cwd, ".github", "workflows", "contract-driven-gates.yml");
|
|
9278
|
+
const wfPlan = planSingleFile(wfAsset, wfDest, ".github/workflows/contract-driven-gates.yml");
|
|
9279
|
+
if (wfPlan)
|
|
9280
|
+
sections.push({ name: ".github/workflows/contract-driven-gates.yml", plan: [wfPlan] });
|
|
9281
|
+
const total = sections.flatMap((s) => s.plan);
|
|
9282
|
+
return { sections, total };
|
|
9283
|
+
}
|
|
9284
|
+
function summarizePlan(items) {
|
|
9285
|
+
return {
|
|
9286
|
+
add: items.filter((i) => i.action === "add").length,
|
|
9287
|
+
overwrite: items.filter((i) => i.action === "overwrite").length,
|
|
9288
|
+
skip: items.filter((i) => i.action === "skip").length
|
|
9289
|
+
};
|
|
9290
|
+
}
|
|
9291
|
+
async function refresh(opts) {
|
|
9292
|
+
const cwd = process.cwd();
|
|
9293
|
+
const apply = !!opts.yes;
|
|
9294
|
+
log.blank();
|
|
9295
|
+
log.info(apply ? "cdd-kit refresh \u2014 applying changes" : "cdd-kit refresh \u2014 dry-run preview (re-run with --yes to apply)");
|
|
9296
|
+
log.blank();
|
|
9297
|
+
if (!opts.noUpdate) {
|
|
9298
|
+
log.info("[1/6] ~/.claude/agents and ~/.claude/skills (via cdd-kit update)");
|
|
9299
|
+
await update({ yes: apply, provider: opts.provider ?? "auto" });
|
|
8955
9300
|
} else {
|
|
8956
|
-
|
|
9301
|
+
log.dim("[1/6] skipped (--no-update)");
|
|
8957
9302
|
}
|
|
8958
|
-
|
|
8959
|
-
|
|
9303
|
+
log.blank();
|
|
9304
|
+
if (!opts.noUpgrade) {
|
|
9305
|
+
log.info("[2/6] add missing project files (via cdd-kit upgrade)");
|
|
9306
|
+
await upgrade({ yes: apply, provider: opts.provider ?? "auto" });
|
|
9307
|
+
} else {
|
|
9308
|
+
log.dim("[2/6] skipped (--no-upgrade)");
|
|
8960
9309
|
}
|
|
8961
|
-
|
|
8962
|
-
|
|
8963
|
-
|
|
8964
|
-
|
|
8965
|
-
|
|
8966
|
-
|
|
8967
|
-
|
|
8968
|
-
|
|
8969
|
-
|
|
8970
|
-
|
|
8971
|
-
|
|
8972
|
-
|
|
8973
|
-
|
|
8974
|
-
|
|
8975
|
-
|
|
8976
|
-
|
|
8977
|
-
|
|
8978
|
-
|
|
8979
|
-
let i = 0;
|
|
8980
|
-
const topFieldRe = /^[ ]{0,1}-\s*([\w-]+):\s*(.*)$/;
|
|
8981
|
-
while (i < lines.length) {
|
|
8982
|
-
const line = lines[i];
|
|
8983
|
-
const fieldMatch = line.match(topFieldRe);
|
|
8984
|
-
if (!fieldMatch) {
|
|
8985
|
-
i++;
|
|
8986
|
-
continue;
|
|
8987
|
-
}
|
|
8988
|
-
const key = fieldMatch[1];
|
|
8989
|
-
const inline = fieldMatch[2].trim();
|
|
8990
|
-
if (key === "files-read" || key === "artifacts") {
|
|
8991
|
-
const items = [];
|
|
8992
|
-
let j = i + 1;
|
|
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());
|
|
9310
|
+
log.blank();
|
|
9311
|
+
let templateAdded = 0;
|
|
9312
|
+
let templateOverwritten = 0;
|
|
9313
|
+
let backupRoot = null;
|
|
9314
|
+
if (!opts.noTemplates) {
|
|
9315
|
+
log.info("[3/6] force-refresh kit-shipped templates");
|
|
9316
|
+
const { sections, total } = planTemplateRefresh(cwd);
|
|
9317
|
+
const summary = summarizePlan(total);
|
|
9318
|
+
if (summary.add === 0 && summary.overwrite === 0) {
|
|
9319
|
+
log.ok(" templates already up to date");
|
|
9320
|
+
} else {
|
|
9321
|
+
for (const s of sections) {
|
|
9322
|
+
const ss = summarizePlan(s.plan);
|
|
9323
|
+
if (ss.add === 0 && ss.overwrite === 0)
|
|
9324
|
+
continue;
|
|
9325
|
+
log.info(` ${s.name}: +${ss.add} ~${ss.overwrite} =${ss.skip}`);
|
|
9326
|
+
for (const item of s.plan.filter((i) => i.action !== "skip")) {
|
|
9327
|
+
log.dim(` ${item.action === "add" ? "+" : "~"} ${item.rel}`);
|
|
9002
9328
|
}
|
|
9003
|
-
j++;
|
|
9004
9329
|
}
|
|
9005
|
-
if (
|
|
9006
|
-
|
|
9330
|
+
if (apply) {
|
|
9331
|
+
const ts = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
9332
|
+
backupRoot = join20(cwd, ".cdd", ".refresh-backup", ts);
|
|
9333
|
+
const result = applyPlan(total, backupRoot);
|
|
9334
|
+
templateAdded = result.added;
|
|
9335
|
+
templateOverwritten = result.overwritten;
|
|
9336
|
+
log.ok(` applied: +${templateAdded} added, ~${templateOverwritten} overwritten`);
|
|
9337
|
+
if (templateOverwritten > 0) {
|
|
9338
|
+
log.info(` backup saved to: ${relative5(cwd, backupRoot).replace(/\\/g, "/")}`);
|
|
9339
|
+
}
|
|
9007
9340
|
} 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
|
-
});
|
|
9341
|
+
log.dim(" (dry-run \u2014 no changes written)");
|
|
9017
9342
|
}
|
|
9018
|
-
i = j;
|
|
9019
|
-
continue;
|
|
9020
9343
|
}
|
|
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}`);
|
|
9344
|
+
} else {
|
|
9345
|
+
log.dim("[3/6] skipped (--no-templates)");
|
|
9043
9346
|
}
|
|
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 });
|
|
9347
|
+
log.blank();
|
|
9348
|
+
if (!opts.noHooks) {
|
|
9349
|
+
const markerPath = join20(cwd, HOOKS_MARKER_PATH);
|
|
9350
|
+
if (existsSync17(markerPath)) {
|
|
9351
|
+
log.info("[4/6] re-install code-map pre-commit hook (marker found)");
|
|
9352
|
+
if (apply) {
|
|
9353
|
+
try {
|
|
9354
|
+
await installCodeMapHook(cwd);
|
|
9355
|
+
} catch (err) {
|
|
9356
|
+
log.warn(` hook re-install skipped: ${err.message}`);
|
|
9069
9357
|
}
|
|
9070
9358
|
} 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
|
-
);
|
|
9359
|
+
log.dim(" (dry-run \u2014 would re-install)");
|
|
9074
9360
|
}
|
|
9075
9361
|
} else {
|
|
9076
|
-
|
|
9077
|
-
if (structured)
|
|
9078
|
-
detectedTier = structured[1];
|
|
9362
|
+
log.dim("[4/6] no .cdd/.hooks-installed marker \u2014 skipping (run `cdd-kit init --hooks` to install)");
|
|
9079
9363
|
}
|
|
9364
|
+
} else {
|
|
9365
|
+
log.dim("[4/6] skipped (--no-hooks)");
|
|
9080
9366
|
}
|
|
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 });
|
|
9367
|
+
log.blank();
|
|
9368
|
+
log.info("[5/6] resync .cdd/model-policy.json roles from agent frontmatter");
|
|
9369
|
+
if (apply) {
|
|
9370
|
+
const r = resyncModelPolicy(cwd);
|
|
9371
|
+
if (r.diff.length === 0) {
|
|
9372
|
+
log.ok(" roles already match agent prompts");
|
|
9373
|
+
} else {
|
|
9374
|
+
for (const d of r.diff) {
|
|
9375
|
+
log.info(` ${d.agent}: ${d.from ?? "(absent)"} \u2192 ${d.to}`);
|
|
9376
|
+
}
|
|
9377
|
+
log.ok(` ${r.diff.length} role binding(s) updated`);
|
|
9102
9378
|
}
|
|
9103
|
-
}
|
|
9104
|
-
|
|
9379
|
+
} else {
|
|
9380
|
+
const fakeApply = () => {
|
|
9381
|
+
};
|
|
9382
|
+
fakeApply();
|
|
9383
|
+
log.dim(" (dry-run \u2014 drift will be reported only when applied)");
|
|
9384
|
+
}
|
|
9385
|
+
log.blank();
|
|
9386
|
+
if (!opts.noCodeMap) {
|
|
9387
|
+
log.info("[6/6] regenerate .cdd/code-map.yml");
|
|
9388
|
+
if (apply) {
|
|
9105
9389
|
try {
|
|
9106
|
-
|
|
9107
|
-
|
|
9390
|
+
await codeMap({
|
|
9391
|
+
path: ".",
|
|
9392
|
+
out: ".cdd/code-map.yml",
|
|
9393
|
+
include: [],
|
|
9394
|
+
exclude: [],
|
|
9395
|
+
check: false,
|
|
9396
|
+
maxLines: 600
|
|
9397
|
+
});
|
|
9398
|
+
} catch (err) {
|
|
9399
|
+
log.warn(` code-map skipped: ${err.message}`);
|
|
9108
9400
|
}
|
|
9401
|
+
} else {
|
|
9402
|
+
log.dim(" (dry-run \u2014 would regenerate)");
|
|
9109
9403
|
}
|
|
9110
|
-
|
|
9404
|
+
} else {
|
|
9405
|
+
log.dim("[6/6] skipped (--no-code-map)");
|
|
9111
9406
|
}
|
|
9112
|
-
|
|
9113
|
-
|
|
9407
|
+
log.blank();
|
|
9408
|
+
if (apply) {
|
|
9409
|
+
log.ok("refresh complete.");
|
|
9410
|
+
if (backupRoot) {
|
|
9411
|
+
log.info(`Backup of overwritten templates: ${relative5(cwd, backupRoot).replace(/\\/g, "/")}`);
|
|
9412
|
+
}
|
|
9413
|
+
log.info("Next: review changes with `git diff`, then commit:");
|
|
9414
|
+
log.dim(" git add .cdd/code-map.yml .cdd/model-policy.json specs/templates tests/templates ci-templates .github/workflows");
|
|
9415
|
+
log.dim(' git commit -m "chore: cdd-kit refresh"');
|
|
9416
|
+
} else {
|
|
9417
|
+
log.info("Dry-run finished. Re-run with `--yes` to apply.");
|
|
9114
9418
|
}
|
|
9115
|
-
|
|
9116
|
-
|
|
9117
|
-
|
|
9118
|
-
|
|
9119
|
-
|
|
9419
|
+
log.blank();
|
|
9420
|
+
}
|
|
9421
|
+
var HOOKS_MARKER_PATH;
|
|
9422
|
+
var init_refresh = __esm({
|
|
9423
|
+
"src/commands/refresh.ts"() {
|
|
9424
|
+
"use strict";
|
|
9425
|
+
init_paths();
|
|
9426
|
+
init_logger();
|
|
9427
|
+
init_update();
|
|
9428
|
+
init_upgrade();
|
|
9429
|
+
init_code_map();
|
|
9430
|
+
init_code_map_hook();
|
|
9431
|
+
HOOKS_MARKER_PATH = ".cdd/.hooks-installed";
|
|
9120
9432
|
}
|
|
9433
|
+
});
|
|
9434
|
+
|
|
9435
|
+
// src/commands/doctor.ts
|
|
9436
|
+
var doctor_exports = {};
|
|
9437
|
+
__export(doctor_exports, {
|
|
9438
|
+
doctor: () => doctor
|
|
9439
|
+
});
|
|
9440
|
+
import { existsSync as existsSync18, readdirSync as readdirSync11, readFileSync as readFileSync19 } from "fs";
|
|
9441
|
+
import { createHash as createHash5 } from "crypto";
|
|
9442
|
+
import { join as join21 } from "path";
|
|
9443
|
+
function fileExists(cwd, relPath) {
|
|
9444
|
+
return existsSync18(join21(cwd, relPath));
|
|
9121
9445
|
}
|
|
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);
|
|
9446
|
+
function findFiles(dir, predicate, found = []) {
|
|
9447
|
+
if (!existsSync18(dir))
|
|
9448
|
+
return found;
|
|
9449
|
+
for (const entry of readdirSync11(dir, { withFileTypes: true })) {
|
|
9450
|
+
const fullPath = join21(dir, entry.name);
|
|
9451
|
+
if (entry.isDirectory())
|
|
9452
|
+
findFiles(fullPath, predicate, found);
|
|
9453
|
+
else if (entry.isFile() && predicate(entry.name))
|
|
9454
|
+
found.push(fullPath);
|
|
9147
9455
|
}
|
|
9148
|
-
|
|
9149
|
-
|
|
9150
|
-
|
|
9456
|
+
return found;
|
|
9457
|
+
}
|
|
9458
|
+
function sha256OfFile3(path) {
|
|
9459
|
+
try {
|
|
9460
|
+
return createHash5("sha256").update(readFileSync19(path)).digest("hex");
|
|
9461
|
+
} catch {
|
|
9462
|
+
return "";
|
|
9151
9463
|
}
|
|
9152
|
-
|
|
9153
|
-
|
|
9154
|
-
|
|
9464
|
+
}
|
|
9465
|
+
function inputDigest(paths) {
|
|
9466
|
+
const combined = paths.slice().sort().map((p) => `${p}:${sha256OfFile3(p)}`).join("\n");
|
|
9467
|
+
return createHash5("sha256").update(combined).digest("hex");
|
|
9468
|
+
}
|
|
9469
|
+
function readContextIndexMetadata(filePath) {
|
|
9470
|
+
if (!existsSync18(filePath))
|
|
9471
|
+
return {};
|
|
9472
|
+
const text = readFileSync19(filePath, "utf8");
|
|
9473
|
+
const out = {};
|
|
9474
|
+
const digestMatch = text.match(/^inputs-digest:\s*([a-f0-9]+)/m);
|
|
9475
|
+
if (digestMatch)
|
|
9476
|
+
out.inputsDigest = digestMatch[1];
|
|
9477
|
+
const missingMatch = text.match(/^missing-summary-count:\s*(\d+)/m);
|
|
9478
|
+
if (missingMatch)
|
|
9479
|
+
out.missingSummary = Number(missingMatch[1]);
|
|
9480
|
+
return out;
|
|
9481
|
+
}
|
|
9482
|
+
function checkContextFreshness(cwd) {
|
|
9483
|
+
const findings = [];
|
|
9484
|
+
const projectMap = join21(cwd, "specs", "context", "project-map.md");
|
|
9485
|
+
const contractsIndex = join21(cwd, "specs", "context", "contracts-index.md");
|
|
9486
|
+
const contextPolicy = join21(cwd, ".cdd", "context-policy.json");
|
|
9487
|
+
const contractFiles = findFiles(
|
|
9488
|
+
join21(cwd, "contracts"),
|
|
9489
|
+
(name) => name.endsWith(".md") && name !== "INDEX.md" && name !== "CHANGELOG.md"
|
|
9490
|
+
);
|
|
9491
|
+
if (!existsSync18(projectMap) || !existsSync18(contractsIndex)) {
|
|
9492
|
+
findings.push({
|
|
9493
|
+
level: "warning",
|
|
9494
|
+
message: "specs/context indexes are missing; run cdd-kit context-scan before classification"
|
|
9495
|
+
});
|
|
9496
|
+
return findings;
|
|
9155
9497
|
}
|
|
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
|
-
|
|
9498
|
+
const projectMapMeta = readContextIndexMetadata(projectMap);
|
|
9499
|
+
const contractsIndexMeta = readContextIndexMetadata(contractsIndex);
|
|
9500
|
+
const projectInputDigest = inputDigest([contextPolicy].filter(existsSync18));
|
|
9501
|
+
if (projectMapMeta.inputsDigest === void 0) {
|
|
9502
|
+
findings.push({
|
|
9503
|
+
level: "warning",
|
|
9504
|
+
message: "specs/context/project-map.md was generated by an older cdd-kit (no inputs-digest); re-run cdd-kit context-scan"
|
|
9505
|
+
});
|
|
9506
|
+
} else if (projectInputDigest && projectMapMeta.inputsDigest !== projectInputDigest) {
|
|
9507
|
+
findings.push({
|
|
9508
|
+
level: "warning",
|
|
9509
|
+
message: "specs/context/project-map.md inputs changed (.cdd/context-policy.json); re-run cdd-kit context-scan"
|
|
9510
|
+
});
|
|
9511
|
+
}
|
|
9512
|
+
const contractsInputDigest = inputDigest(contractFiles);
|
|
9513
|
+
if (contractsIndexMeta.inputsDigest === void 0) {
|
|
9514
|
+
findings.push({
|
|
9515
|
+
level: "warning",
|
|
9516
|
+
message: "specs/context/contracts-index.md was generated by an older cdd-kit (no inputs-digest); re-run cdd-kit context-scan"
|
|
9517
|
+
});
|
|
9518
|
+
} else if (contractsInputDigest && contractsIndexMeta.inputsDigest !== contractsInputDigest) {
|
|
9519
|
+
findings.push({
|
|
9520
|
+
level: "warning",
|
|
9521
|
+
message: "specs/context/contracts-index.md inputs changed (contracts/*); re-run cdd-kit context-scan"
|
|
9522
|
+
});
|
|
9523
|
+
}
|
|
9524
|
+
if (contractsIndexMeta.missingSummary !== void 0 && contractsIndexMeta.missingSummary > 0) {
|
|
9525
|
+
findings.push({
|
|
9526
|
+
level: "warning",
|
|
9527
|
+
message: `contracts-index reports ${contractsIndexMeta.missingSummary} contract(s) without deterministic summary metadata`
|
|
9528
|
+
});
|
|
9529
|
+
}
|
|
9530
|
+
if (findings.length === 0) {
|
|
9531
|
+
findings.push({ level: "ok", message: "context indexes are present and fresh" });
|
|
9532
|
+
}
|
|
9533
|
+
return findings;
|
|
9534
|
+
}
|
|
9535
|
+
function readAgentModel(path) {
|
|
9536
|
+
try {
|
|
9537
|
+
const text = readFileSync19(path, "utf8");
|
|
9538
|
+
const m = text.match(/^model:\s*(\S+)/m);
|
|
9539
|
+
return m ? m[1] : null;
|
|
9540
|
+
} catch {
|
|
9541
|
+
return null;
|
|
9542
|
+
}
|
|
9543
|
+
}
|
|
9544
|
+
function checkModelPolicyDrift(cwd) {
|
|
9545
|
+
const policyPath = join21(cwd, ".cdd", "model-policy.json");
|
|
9546
|
+
if (!existsSync18(policyPath))
|
|
9547
|
+
return [];
|
|
9548
|
+
let policy;
|
|
9549
|
+
try {
|
|
9550
|
+
policy = JSON.parse(readFileSync19(policyPath, "utf8"));
|
|
9551
|
+
} catch {
|
|
9552
|
+
return [{ level: "warning", message: ".cdd/model-policy.json is not valid JSON" }];
|
|
9553
|
+
}
|
|
9554
|
+
const roles = policy.roles ?? {};
|
|
9555
|
+
if (Object.keys(roles).length === 0) {
|
|
9556
|
+
return [{
|
|
9557
|
+
level: "warning",
|
|
9558
|
+
message: ".cdd/model-policy.json has no role bindings; run cdd-kit upgrade to install defaults"
|
|
9559
|
+
}];
|
|
9560
|
+
}
|
|
9561
|
+
const candidateDirs = [
|
|
9562
|
+
join21(cwd, ".claude", "agents"),
|
|
9563
|
+
process.env.HOME ? join21(process.env.HOME, ".claude", "agents") : "",
|
|
9564
|
+
process.env.USERPROFILE ? join21(process.env.USERPROFILE, ".claude", "agents") : ""
|
|
9565
|
+
].filter((p) => p && existsSync18(p));
|
|
9566
|
+
if (candidateDirs.length === 0)
|
|
9567
|
+
return [];
|
|
9568
|
+
const findings = [];
|
|
9569
|
+
for (const [role, expected] of Object.entries(roles)) {
|
|
9570
|
+
let foundAny = false;
|
|
9571
|
+
for (const dir of candidateDirs) {
|
|
9572
|
+
const path = join21(dir, `${role}.md`);
|
|
9573
|
+
if (!existsSync18(path))
|
|
9574
|
+
continue;
|
|
9575
|
+
foundAny = true;
|
|
9576
|
+
const actual = readAgentModel(path);
|
|
9577
|
+
if (actual && actual !== expected) {
|
|
9578
|
+
findings.push({
|
|
9579
|
+
level: "warning",
|
|
9580
|
+
message: `model-policy drift: ${role} expected ${expected}, agent prompt uses ${actual} (${path})`
|
|
9581
|
+
});
|
|
9186
9582
|
}
|
|
9187
9583
|
}
|
|
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/");
|
|
9584
|
+
if (!foundAny) {
|
|
9204
9585
|
}
|
|
9205
9586
|
}
|
|
9206
|
-
|
|
9207
|
-
|
|
9208
|
-
"src/commands/migrate.ts"() {
|
|
9209
|
-
"use strict";
|
|
9210
|
-
init_logger();
|
|
9587
|
+
if (findings.length === 0) {
|
|
9588
|
+
findings.push({ level: "ok", message: "model-policy roles match installed agent prompts" });
|
|
9211
9589
|
}
|
|
9212
|
-
|
|
9213
|
-
|
|
9214
|
-
|
|
9215
|
-
|
|
9216
|
-
|
|
9217
|
-
|
|
9218
|
-
|
|
9219
|
-
|
|
9220
|
-
|
|
9221
|
-
|
|
9222
|
-
|
|
9223
|
-
|
|
9224
|
-
|
|
9225
|
-
|
|
9226
|
-
|
|
9227
|
-
|
|
9228
|
-
|
|
9229
|
-
|
|
9590
|
+
return findings;
|
|
9591
|
+
}
|
|
9592
|
+
async function checkAgentLint(cwd) {
|
|
9593
|
+
const agentsDir = join21(cwd, ".claude", "agents");
|
|
9594
|
+
if (!existsSync18(agentsDir))
|
|
9595
|
+
return [];
|
|
9596
|
+
try {
|
|
9597
|
+
const { readdirSync: rds, readFileSync: rfs } = await import("fs");
|
|
9598
|
+
const files = rds(agentsDir).filter((f) => f.endsWith(".md"));
|
|
9599
|
+
if (files.length === 0)
|
|
9600
|
+
return [];
|
|
9601
|
+
const findings = [];
|
|
9602
|
+
for (const filename of files) {
|
|
9603
|
+
const content = rfs(join21(agentsDir, filename), "utf8");
|
|
9604
|
+
const artifactsSection = content.match(
|
|
9605
|
+
/### Required artifacts for this agent\s*\n[\s\S]*?(?=\n#{2,3} |\n---|\s*$)/
|
|
9606
|
+
)?.[0];
|
|
9607
|
+
if (!artifactsSection || !/```ya?ml\s*\nartifacts:/.test(artifactsSection)) {
|
|
9608
|
+
findings.push({
|
|
9609
|
+
level: "warning",
|
|
9610
|
+
message: `lint-agents: ${filename}: missing artifacts YAML block in Required artifacts section`
|
|
9611
|
+
});
|
|
9612
|
+
}
|
|
9613
|
+
const readScopeCount = (content.match(/^## Read scope\s*$/gm) ?? []).length;
|
|
9614
|
+
if (readScopeCount > 1) {
|
|
9615
|
+
findings.push({
|
|
9616
|
+
level: "warning",
|
|
9617
|
+
message: `lint-agents: ${filename}: duplicate ## Read scope headings (${readScopeCount})`
|
|
9618
|
+
});
|
|
9619
|
+
}
|
|
9230
9620
|
}
|
|
9231
|
-
if (
|
|
9232
|
-
|
|
9621
|
+
if (findings.length === 0) {
|
|
9622
|
+
findings.push({ level: "ok", message: "lint-agents: all agent prompts pass shape checks" });
|
|
9233
9623
|
}
|
|
9624
|
+
return findings;
|
|
9625
|
+
} catch {
|
|
9626
|
+
return [];
|
|
9234
9627
|
}
|
|
9235
9628
|
}
|
|
9236
|
-
function
|
|
9237
|
-
|
|
9238
|
-
|
|
9239
|
-
|
|
9240
|
-
|
|
9241
|
-
if (
|
|
9242
|
-
|
|
9629
|
+
function checkCodeMap(cwd) {
|
|
9630
|
+
const findings = [];
|
|
9631
|
+
const mapPath = join21(cwd, ".cdd", "code-map.yml");
|
|
9632
|
+
if (!existsSync18(mapPath)) {
|
|
9633
|
+
const probe2 = checkCodeMapFreshness(cwd);
|
|
9634
|
+
if (probe2.status === "config-error") {
|
|
9635
|
+
findings.push({ level: "warning", message: `.cdd/code-map-config.yml is invalid: ${probe2.configError}` });
|
|
9636
|
+
} else if (probe2.status === "missing-with-sources") {
|
|
9637
|
+
findings.push({ level: "warning", message: ".cdd/code-map.yml is missing; run `cdd-kit code-map`" });
|
|
9243
9638
|
}
|
|
9639
|
+
return findings;
|
|
9640
|
+
}
|
|
9641
|
+
const probe = checkCodeMapFreshness(cwd);
|
|
9642
|
+
if (probe.status === "config-error") {
|
|
9643
|
+
findings.push({ level: "warning", message: `.cdd/code-map-config.yml is invalid: ${probe.configError}` });
|
|
9644
|
+
return findings;
|
|
9244
9645
|
}
|
|
9245
|
-
if (
|
|
9246
|
-
|
|
9646
|
+
if (probe.status === "stale") {
|
|
9647
|
+
const top = probe.staleFiles.slice(0, 3).join(", ");
|
|
9648
|
+
const more = probe.staleCount > 3 ? ` (+${probe.staleCount - 3} more)` : "";
|
|
9649
|
+
findings.push({ level: "warning", message: `code-map stale: ${top}${more}; run \`cdd-kit code-map\`` });
|
|
9247
9650
|
}
|
|
9248
|
-
|
|
9249
|
-
|
|
9250
|
-
|
|
9251
|
-
|
|
9252
|
-
|
|
9651
|
+
const text = readFileSync19(mapPath, "utf8");
|
|
9652
|
+
const m = text.match(/^# files: (\d+), src-lines: (\d+), map-lines: (\d+), compression: ([\d.]+)x/m);
|
|
9653
|
+
if (m) {
|
|
9654
|
+
findings.push({ level: "ok", message: `code-map: ${m[1]} files, ${m[4]}x compression` });
|
|
9655
|
+
} else {
|
|
9656
|
+
findings.push({ level: "warning", message: ".cdd/code-map.yml has no metadata header (regenerate with cdd-kit 2.0.5+)" });
|
|
9253
9657
|
}
|
|
9658
|
+
return findings;
|
|
9254
9659
|
}
|
|
9255
|
-
async function
|
|
9256
|
-
const cwd = process.cwd();
|
|
9660
|
+
async function buildDoctorReport(cwd, opts) {
|
|
9257
9661
|
const requestedProvider = opts.provider ?? "auto";
|
|
9258
9662
|
if (!validateProviderOption(requestedProvider)) {
|
|
9259
9663
|
log.error(`Invalid provider: ${requestedProvider}. Use auto, claude, codex, or both.`);
|
|
9260
9664
|
process.exit(1);
|
|
9261
9665
|
}
|
|
9666
|
+
const strict = opts.strict ?? false;
|
|
9262
9667
|
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;
|
|
9668
|
+
const findings = [];
|
|
9669
|
+
for (const relPath of ["contracts", "specs/templates", ".cdd/context-policy.json", ".cdd/model-policy.json"]) {
|
|
9670
|
+
findings.push(fileExists(cwd, relPath) ? { level: "ok", message: `${relPath} exists` } : { level: "warning", message: `${relPath} is missing; run cdd-kit upgrade --yes` });
|
|
9286
9671
|
}
|
|
9287
|
-
|
|
9288
|
-
|
|
9289
|
-
|
|
9290
|
-
if (!
|
|
9291
|
-
|
|
9292
|
-
|
|
9293
|
-
|
|
9294
|
-
|
|
9295
|
-
|
|
9296
|
-
|
|
9297
|
-
|
|
9298
|
-
|
|
9299
|
-
|
|
9672
|
+
if ((provider === "claude" || provider === "both") && !fileExists(cwd, "CLAUDE.md")) {
|
|
9673
|
+
findings.push({ level: "warning", message: "CLAUDE.md is missing for Claude provider; run cdd-kit upgrade --provider claude --yes" });
|
|
9674
|
+
}
|
|
9675
|
+
if ((provider === "claude" || provider === "both") && !fileExists(cwd, "AGENTS.md")) {
|
|
9676
|
+
findings.push({ level: "warning", message: "AGENTS.md is missing for Claude provider; run cdd-kit upgrade --provider claude --yes" });
|
|
9677
|
+
}
|
|
9678
|
+
if ((provider === "codex" || provider === "both") && !fileExists(cwd, "CODEX.md")) {
|
|
9679
|
+
findings.push({ level: "warning", message: "CODEX.md is missing for Codex provider; run cdd-kit upgrade --provider codex --yes" });
|
|
9680
|
+
}
|
|
9681
|
+
findings.push(...checkContextFreshness(cwd));
|
|
9682
|
+
findings.push(...checkModelPolicyDrift(cwd));
|
|
9683
|
+
findings.push(...await checkAgentLint(cwd));
|
|
9684
|
+
findings.push(...checkCodeMap(cwd));
|
|
9685
|
+
const errors = findings.filter((finding) => finding.level === "error").length;
|
|
9686
|
+
const warnings = findings.filter((finding) => finding.level === "warning").length;
|
|
9687
|
+
return {
|
|
9688
|
+
provider,
|
|
9689
|
+
strict,
|
|
9690
|
+
findings,
|
|
9691
|
+
errors,
|
|
9692
|
+
warnings,
|
|
9693
|
+
ok: errors === 0 && (!strict || warnings === 0)
|
|
9694
|
+
};
|
|
9695
|
+
}
|
|
9696
|
+
async function attemptAutoFixes(cwd, report) {
|
|
9697
|
+
const fixed = [];
|
|
9698
|
+
const remaining = [];
|
|
9699
|
+
for (const finding of report.findings) {
|
|
9700
|
+
if (finding.level !== "warning") {
|
|
9701
|
+
remaining.push(finding);
|
|
9702
|
+
continue;
|
|
9703
|
+
}
|
|
9704
|
+
if (/specs\/context indexes are missing|inputs changed|older cdd-kit|older than/i.test(finding.message)) {
|
|
9705
|
+
try {
|
|
9706
|
+
const { contextScan: contextScan2 } = await Promise.resolve().then(() => (init_context_scan(), context_scan_exports));
|
|
9707
|
+
await contextScan2();
|
|
9708
|
+
fixed.push(`ran context-scan to refresh specs/context/`);
|
|
9709
|
+
continue;
|
|
9710
|
+
} catch (err) {
|
|
9711
|
+
remaining.push({ level: "warning", message: `${finding.message} (auto-fix failed: ${err.message})` });
|
|
9712
|
+
continue;
|
|
9713
|
+
}
|
|
9714
|
+
}
|
|
9715
|
+
if (/code-map stale|code-map\.yml is missing/i.test(finding.message)) {
|
|
9716
|
+
try {
|
|
9717
|
+
const { codeMap: codeMap2 } = await Promise.resolve().then(() => (init_code_map(), code_map_exports));
|
|
9718
|
+
await codeMap2({ path: ".", out: ".cdd/code-map.yml", include: [], exclude: [], check: false, maxLines: 1e5 });
|
|
9719
|
+
fixed.push("regenerated .cdd/code-map.yml");
|
|
9720
|
+
continue;
|
|
9721
|
+
} catch (err) {
|
|
9722
|
+
remaining.push({ level: "warning", message: `${finding.message} (auto-fix failed: ${err.message})` });
|
|
9723
|
+
continue;
|
|
9724
|
+
}
|
|
9725
|
+
}
|
|
9726
|
+
if (/model-policy\.json has no role bindings/i.test(finding.message)) {
|
|
9727
|
+
const policyPath = join21(cwd, ".cdd", "model-policy.json");
|
|
9728
|
+
try {
|
|
9729
|
+
let existing = {};
|
|
9730
|
+
try {
|
|
9731
|
+
existing = JSON.parse(readFileSync19(policyPath, "utf8"));
|
|
9732
|
+
} catch {
|
|
9733
|
+
}
|
|
9734
|
+
const merged = {
|
|
9735
|
+
...existing,
|
|
9736
|
+
roles: {
|
|
9737
|
+
"change-classifier": "claude-opus-4-7",
|
|
9738
|
+
"spec-architect": "claude-opus-4-7",
|
|
9739
|
+
"qa-reviewer": "claude-opus-4-7",
|
|
9740
|
+
"contract-reviewer": "claude-sonnet-4-6",
|
|
9741
|
+
"test-strategist": "claude-sonnet-4-6",
|
|
9742
|
+
"backend-engineer": "claude-sonnet-4-6",
|
|
9743
|
+
"frontend-engineer": "claude-sonnet-4-6",
|
|
9744
|
+
"ci-cd-gatekeeper": "claude-sonnet-4-6",
|
|
9745
|
+
"e2e-resilience-engineer": "claude-sonnet-4-6",
|
|
9746
|
+
"monkey-test-engineer": "claude-sonnet-4-6",
|
|
9747
|
+
"stress-soak-engineer": "claude-sonnet-4-6",
|
|
9748
|
+
"ui-ux-reviewer": "claude-sonnet-4-6",
|
|
9749
|
+
"visual-reviewer": "claude-haiku-4-5-20251001",
|
|
9750
|
+
"dependency-security-reviewer": "claude-sonnet-4-6",
|
|
9751
|
+
"spec-drift-auditor": "claude-opus-4-7",
|
|
9752
|
+
"repo-context-scanner": "claude-haiku-4-5-20251001"
|
|
9753
|
+
}
|
|
9754
|
+
};
|
|
9755
|
+
const { writeFileSync: writeFileSync14 } = await import("fs");
|
|
9756
|
+
writeFileSync14(policyPath, JSON.stringify(merged, null, 2) + "\n", "utf8");
|
|
9757
|
+
fixed.push(`populated .cdd/model-policy.json with default role bindings`);
|
|
9758
|
+
continue;
|
|
9759
|
+
} catch (err) {
|
|
9760
|
+
remaining.push({ level: "warning", message: `${finding.message} (auto-fix failed: ${err.message})` });
|
|
9761
|
+
continue;
|
|
9762
|
+
}
|
|
9763
|
+
}
|
|
9764
|
+
if (/\.cdd\/.*is missing|run cdd-kit upgrade/i.test(finding.message)) {
|
|
9765
|
+
remaining.push({
|
|
9766
|
+
level: "warning",
|
|
9767
|
+
message: `${finding.message} (run \`cdd-kit upgrade --yes\` manually \u2014 too invasive for --fix)`
|
|
9300
9768
|
});
|
|
9769
|
+
continue;
|
|
9301
9770
|
}
|
|
9771
|
+
remaining.push(finding);
|
|
9772
|
+
}
|
|
9773
|
+
return { fixed, remaining };
|
|
9774
|
+
}
|
|
9775
|
+
async function doctor(opts = {}) {
|
|
9776
|
+
const cwd = process.cwd();
|
|
9777
|
+
let report = await buildDoctorReport(cwd, opts);
|
|
9778
|
+
if (opts.fix && !opts.json) {
|
|
9302
9779
|
log.blank();
|
|
9780
|
+
log.info("Doctor --fix: attempting safe auto-resolutions\u2026");
|
|
9781
|
+
const { fixed, remaining } = await attemptAutoFixes(cwd, report);
|
|
9782
|
+
for (const f of fixed)
|
|
9783
|
+
log.ok(`fixed: ${f}`);
|
|
9784
|
+
if (fixed.length > 0) {
|
|
9785
|
+
report = await buildDoctorReport(cwd, opts);
|
|
9786
|
+
} else {
|
|
9787
|
+
log.info("no auto-fixable findings");
|
|
9788
|
+
}
|
|
9789
|
+
}
|
|
9790
|
+
if (opts.json) {
|
|
9791
|
+
console.log(JSON.stringify(report, null, 2));
|
|
9792
|
+
if (!report.ok)
|
|
9793
|
+
process.exit(1);
|
|
9303
9794
|
return;
|
|
9304
9795
|
}
|
|
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");
|
|
9796
|
+
log.blank();
|
|
9797
|
+
log.info(`Doctor provider: ${report.provider}`);
|
|
9798
|
+
for (const finding of report.findings) {
|
|
9799
|
+
if (finding.level === "ok")
|
|
9800
|
+
log.ok(finding.message);
|
|
9801
|
+
else if (finding.level === "warning")
|
|
9802
|
+
log.warn(finding.message);
|
|
9803
|
+
else
|
|
9804
|
+
log.error(finding.message);
|
|
9320
9805
|
}
|
|
9321
9806
|
log.blank();
|
|
9322
|
-
|
|
9323
|
-
|
|
9324
|
-
|
|
9325
|
-
|
|
9326
|
-
|
|
9327
|
-
|
|
9328
|
-
|
|
9329
|
-
|
|
9330
|
-
enableContextGovernance: opts.enableContextGovernance
|
|
9331
|
-
});
|
|
9807
|
+
if (!report.ok) {
|
|
9808
|
+
log.error(report.strict && report.errors === 0 ? `doctor failed in strict mode with ${report.warnings} warning(s)` : `doctor failed with ${report.errors} error(s)`);
|
|
9809
|
+
process.exit(1);
|
|
9810
|
+
}
|
|
9811
|
+
if (report.warnings > 0) {
|
|
9812
|
+
log.warn(`doctor completed with ${report.warnings} warning(s)`);
|
|
9813
|
+
} else {
|
|
9814
|
+
log.ok("doctor passed");
|
|
9332
9815
|
}
|
|
9333
9816
|
log.blank();
|
|
9334
9817
|
}
|
|
9335
|
-
var
|
|
9336
|
-
"src/commands/
|
|
9818
|
+
var init_doctor = __esm({
|
|
9819
|
+
"src/commands/doctor.ts"() {
|
|
9337
9820
|
"use strict";
|
|
9338
|
-
init_paths();
|
|
9339
9821
|
init_logger();
|
|
9340
9822
|
init_provider();
|
|
9341
|
-
|
|
9823
|
+
init_freshness();
|
|
9342
9824
|
}
|
|
9343
9825
|
});
|
|
9344
9826
|
|
|
@@ -9347,8 +9829,8 @@ var lint_agents_exports = {};
|
|
|
9347
9829
|
__export(lint_agents_exports, {
|
|
9348
9830
|
lintAgents: () => lintAgents
|
|
9349
9831
|
});
|
|
9350
|
-
import { readdirSync as
|
|
9351
|
-
import { join as
|
|
9832
|
+
import { readdirSync as readdirSync12, readFileSync as readFileSync20 } from "fs";
|
|
9833
|
+
import { join as join22 } from "path";
|
|
9352
9834
|
import { load as yamlLoad2 } from "js-yaml";
|
|
9353
9835
|
function extractRequiredArtifactsSection(content) {
|
|
9354
9836
|
const match = content.match(
|
|
@@ -9389,20 +9871,20 @@ function hasFlatBacktickKeysWithoutFence(section) {
|
|
|
9389
9871
|
}
|
|
9390
9872
|
async function lintAgents(opts) {
|
|
9391
9873
|
const cwd = process.cwd();
|
|
9392
|
-
const agentsDir =
|
|
9874
|
+
const agentsDir = join22(cwd, ".claude", "agents");
|
|
9393
9875
|
let files;
|
|
9394
9876
|
try {
|
|
9395
|
-
files =
|
|
9877
|
+
files = readdirSync12(agentsDir).filter((f) => f.endsWith(".md")).sort();
|
|
9396
9878
|
} catch {
|
|
9397
9879
|
log.error(`lint-agents: cannot read ${agentsDir} \u2014 is this a cdd-kit project?`);
|
|
9398
9880
|
return 1;
|
|
9399
9881
|
}
|
|
9400
9882
|
const violations = [];
|
|
9401
9883
|
for (const filename of files) {
|
|
9402
|
-
const filePath =
|
|
9884
|
+
const filePath = join22(agentsDir, filename);
|
|
9403
9885
|
let content;
|
|
9404
9886
|
try {
|
|
9405
|
-
content =
|
|
9887
|
+
content = readFileSync20(filePath, "utf8");
|
|
9406
9888
|
} catch {
|
|
9407
9889
|
violations.push({
|
|
9408
9890
|
file: filename,
|
|
@@ -9514,28 +9996,28 @@ var archive_exports = {};
|
|
|
9514
9996
|
__export(archive_exports, {
|
|
9515
9997
|
archive: () => archive
|
|
9516
9998
|
});
|
|
9517
|
-
import { join as
|
|
9518
|
-
import { existsSync as
|
|
9999
|
+
import { join as join23 } from "path";
|
|
10000
|
+
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
10001
|
import yaml4 from "js-yaml";
|
|
9520
10002
|
async function archive(changeId) {
|
|
9521
10003
|
const cwd = process.cwd();
|
|
9522
|
-
const changeDir =
|
|
10004
|
+
const changeDir = join23(cwd, "specs", "changes", changeId);
|
|
9523
10005
|
const archiveYear = (/* @__PURE__ */ new Date()).getFullYear().toString();
|
|
9524
|
-
const archiveBase =
|
|
9525
|
-
const archiveDir =
|
|
9526
|
-
const indexPath =
|
|
9527
|
-
if (!
|
|
10006
|
+
const archiveBase = join23(cwd, "specs", "archive", archiveYear);
|
|
10007
|
+
const archiveDir = join23(archiveBase, changeId);
|
|
10008
|
+
const indexPath = join23(cwd, "specs", "archive", "INDEX.md");
|
|
10009
|
+
if (!existsSync19(changeDir)) {
|
|
9528
10010
|
log.error(`Change not found: specs/changes/${changeId}`);
|
|
9529
10011
|
process.exit(1);
|
|
9530
10012
|
}
|
|
9531
|
-
if (
|
|
10013
|
+
if (existsSync19(archiveDir)) {
|
|
9532
10014
|
log.error(`Already archived: specs/archive/${archiveYear}/${changeId}`);
|
|
9533
10015
|
process.exit(1);
|
|
9534
10016
|
}
|
|
9535
|
-
const tasksPath =
|
|
9536
|
-
if (
|
|
10017
|
+
const tasksPath = join23(changeDir, "tasks.yml");
|
|
10018
|
+
if (existsSync19(tasksPath)) {
|
|
9537
10019
|
try {
|
|
9538
|
-
const raw =
|
|
10020
|
+
const raw = readFileSync21(tasksPath, "utf8");
|
|
9539
10021
|
const data = yaml4.load(raw);
|
|
9540
10022
|
if (data?.status === "gate-blocked") {
|
|
9541
10023
|
log.warn("tasks.yml has status: gate-blocked \u2014 archiving anyway (change was paused).");
|
|
@@ -9548,8 +10030,8 @@ async function archive(changeId) {
|
|
|
9548
10030
|
log.warn("tasks.yml could not be parsed \u2014 archiving anyway.");
|
|
9549
10031
|
}
|
|
9550
10032
|
}
|
|
9551
|
-
if (!
|
|
9552
|
-
|
|
10033
|
+
if (!existsSync19(archiveBase)) {
|
|
10034
|
+
mkdirSync10(archiveBase, { recursive: true });
|
|
9553
10035
|
}
|
|
9554
10036
|
try {
|
|
9555
10037
|
renameSync2(changeDir, archiveDir);
|
|
@@ -9565,8 +10047,8 @@ async function archive(changeId) {
|
|
|
9565
10047
|
const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
9566
10048
|
const indexLine = `| ${changeId} | ${archiveYear} | ${today} | specs/archive/${archiveYear}/${changeId}/ |
|
|
9567
10049
|
`;
|
|
9568
|
-
if (!
|
|
9569
|
-
|
|
10050
|
+
if (!existsSync19(indexPath)) {
|
|
10051
|
+
writeFileSync11(indexPath, `# Archive Index
|
|
9570
10052
|
|
|
9571
10053
|
| change-id | year | archived-date | path |
|
|
9572
10054
|
|---|---|---|---|
|
|
@@ -9590,37 +10072,37 @@ var abandon_exports = {};
|
|
|
9590
10072
|
__export(abandon_exports, {
|
|
9591
10073
|
abandon: () => abandon
|
|
9592
10074
|
});
|
|
9593
|
-
import { join as
|
|
9594
|
-
import { existsSync as
|
|
10075
|
+
import { join as join24 } from "path";
|
|
10076
|
+
import { existsSync as existsSync20, readFileSync as readFileSync22, writeFileSync as writeFileSync12, appendFileSync as appendFileSync2, mkdirSync as mkdirSync11 } from "fs";
|
|
9595
10077
|
import yaml5 from "js-yaml";
|
|
9596
10078
|
async function abandon(changeId, opts) {
|
|
9597
10079
|
const cwd = process.cwd();
|
|
9598
|
-
const changeDir =
|
|
9599
|
-
const tasksPath =
|
|
9600
|
-
if (!
|
|
10080
|
+
const changeDir = join24(cwd, "specs", "changes", changeId);
|
|
10081
|
+
const tasksPath = join24(changeDir, "tasks.yml");
|
|
10082
|
+
if (!existsSync20(changeDir)) {
|
|
9601
10083
|
log.error(`Change not found: specs/changes/${changeId}`);
|
|
9602
10084
|
process.exit(1);
|
|
9603
10085
|
}
|
|
9604
|
-
if (
|
|
9605
|
-
const raw =
|
|
10086
|
+
if (existsSync20(tasksPath)) {
|
|
10087
|
+
const raw = readFileSync22(tasksPath, "utf8");
|
|
9606
10088
|
const data = yaml5.load(raw) ?? {};
|
|
9607
10089
|
data["status"] = "abandoned";
|
|
9608
10090
|
if (!data["change-id"]) {
|
|
9609
10091
|
data["change-id"] = changeId;
|
|
9610
10092
|
}
|
|
9611
|
-
|
|
10093
|
+
writeFileSync12(tasksPath, yaml5.dump(data, { lineWidth: -1, noRefs: true }), "utf8");
|
|
9612
10094
|
}
|
|
9613
10095
|
const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
9614
|
-
const archiveDir =
|
|
9615
|
-
const indexPath =
|
|
10096
|
+
const archiveDir = join24(cwd, "specs", "archive");
|
|
10097
|
+
const indexPath = join24(archiveDir, "INDEX.md");
|
|
9616
10098
|
const reason = opts.reason ?? "no reason given";
|
|
9617
10099
|
const indexLine = `| ${changeId} | abandoned | ${today} | ${reason} |
|
|
9618
10100
|
`;
|
|
9619
|
-
if (!
|
|
9620
|
-
|
|
10101
|
+
if (!existsSync20(archiveDir)) {
|
|
10102
|
+
mkdirSync11(archiveDir, { recursive: true });
|
|
9621
10103
|
}
|
|
9622
|
-
if (!
|
|
9623
|
-
|
|
10104
|
+
if (!existsSync20(indexPath)) {
|
|
10105
|
+
writeFileSync12(indexPath, `# Archive Index
|
|
9624
10106
|
|
|
9625
10107
|
| change-id | status | date | notes |
|
|
9626
10108
|
|---|---|---|---|
|
|
@@ -9644,28 +10126,28 @@ var list_changes_exports = {};
|
|
|
9644
10126
|
__export(list_changes_exports, {
|
|
9645
10127
|
listChanges: () => listChanges
|
|
9646
10128
|
});
|
|
9647
|
-
import { join as
|
|
9648
|
-
import { existsSync as
|
|
10129
|
+
import { join as join25 } from "path";
|
|
10130
|
+
import { existsSync as existsSync21, readdirSync as readdirSync13, readFileSync as readFileSync23 } from "fs";
|
|
9649
10131
|
import yaml6 from "js-yaml";
|
|
9650
10132
|
async function listChanges() {
|
|
9651
10133
|
const cwd = process.cwd();
|
|
9652
|
-
const changesDir =
|
|
10134
|
+
const changesDir = join25(cwd, "specs", "changes");
|
|
9653
10135
|
log.blank();
|
|
9654
10136
|
const active = [];
|
|
9655
|
-
if (
|
|
9656
|
-
active.push(...
|
|
10137
|
+
if (existsSync21(changesDir)) {
|
|
10138
|
+
active.push(...readdirSync13(changesDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name));
|
|
9657
10139
|
}
|
|
9658
10140
|
if (active.length === 0) {
|
|
9659
10141
|
log.info("No active changes in specs/changes/");
|
|
9660
10142
|
} else {
|
|
9661
10143
|
log.info("Active changes:");
|
|
9662
10144
|
for (const id of active) {
|
|
9663
|
-
const tasksPath =
|
|
10145
|
+
const tasksPath = join25(changesDir, id, "tasks.yml");
|
|
9664
10146
|
let status = "in-progress";
|
|
9665
10147
|
let pending = 0;
|
|
9666
|
-
if (
|
|
10148
|
+
if (existsSync21(tasksPath)) {
|
|
9667
10149
|
try {
|
|
9668
|
-
const raw =
|
|
10150
|
+
const raw = readFileSync23(tasksPath, "utf8");
|
|
9669
10151
|
const data = yaml6.load(raw);
|
|
9670
10152
|
if (data?.status)
|
|
9671
10153
|
status = data.status;
|
|
@@ -9696,8 +10178,8 @@ __export(context_exports, {
|
|
|
9696
10178
|
rejectContextExpansion: () => rejectContextExpansion,
|
|
9697
10179
|
requestContextExpansion: () => requestContextExpansion
|
|
9698
10180
|
});
|
|
9699
|
-
import { existsSync as
|
|
9700
|
-
import { join as
|
|
10181
|
+
import { existsSync as existsSync22, readFileSync as readFileSync24, writeFileSync as writeFileSync13 } from "fs";
|
|
10182
|
+
import { join as join26 } from "path";
|
|
9701
10183
|
function normalizePath(path) {
|
|
9702
10184
|
return path.replace(/\\/g, "/").replace(/^\.\//, "").trim();
|
|
9703
10185
|
}
|
|
@@ -9711,18 +10193,18 @@ function validateRepoRelativePath(path) {
|
|
|
9711
10193
|
return null;
|
|
9712
10194
|
}
|
|
9713
10195
|
function manifestPathFor(changeId) {
|
|
9714
|
-
return
|
|
10196
|
+
return join26(process.cwd(), "specs", "changes", changeId, "context-manifest.md");
|
|
9715
10197
|
}
|
|
9716
10198
|
function readManifest(changeId) {
|
|
9717
10199
|
const manifestPath = manifestPathFor(changeId);
|
|
9718
|
-
if (!
|
|
10200
|
+
if (!existsSync22(manifestPath)) {
|
|
9719
10201
|
log.error(`context manifest not found: specs/changes/${changeId}/context-manifest.md`);
|
|
9720
10202
|
process.exit(1);
|
|
9721
10203
|
}
|
|
9722
|
-
return
|
|
10204
|
+
return readFileSync24(manifestPath, "utf8");
|
|
9723
10205
|
}
|
|
9724
10206
|
function writeManifest(changeId, content) {
|
|
9725
|
-
|
|
10207
|
+
writeFileSync13(manifestPathFor(changeId), content.endsWith("\n") ? content : `${content}
|
|
9726
10208
|
`, "utf8");
|
|
9727
10209
|
}
|
|
9728
10210
|
function sectionBody(content, heading) {
|
|
@@ -9947,9 +10429,9 @@ var init_context = __esm({
|
|
|
9947
10429
|
});
|
|
9948
10430
|
|
|
9949
10431
|
// src/cli/index.ts
|
|
9950
|
-
import { readFileSync as
|
|
10432
|
+
import { readFileSync as readFileSync25 } from "fs";
|
|
9951
10433
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
9952
|
-
import { dirname as
|
|
10434
|
+
import { dirname as dirname7, join as join27 } from "path";
|
|
9953
10435
|
import { Command } from "commander";
|
|
9954
10436
|
|
|
9955
10437
|
// src/commands/init.ts
|
|
@@ -10364,167 +10846,8 @@ async function init(opts) {
|
|
|
10364
10846
|
log.blank();
|
|
10365
10847
|
}
|
|
10366
10848
|
|
|
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
|
-
}
|
|
10849
|
+
// src/cli/index.ts
|
|
10850
|
+
init_update();
|
|
10528
10851
|
|
|
10529
10852
|
// src/commands/new-change.ts
|
|
10530
10853
|
init_paths();
|
|
@@ -11513,8 +11836,8 @@ async function installHooks() {
|
|
|
11513
11836
|
}
|
|
11514
11837
|
|
|
11515
11838
|
// src/cli/index.ts
|
|
11516
|
-
var __dirname2 =
|
|
11517
|
-
var pkg = JSON.parse(
|
|
11839
|
+
var __dirname2 = dirname7(fileURLToPath3(import.meta.url));
|
|
11840
|
+
var pkg = JSON.parse(readFileSync25(join27(__dirname2, "..", "..", "package.json"), "utf8"));
|
|
11518
11841
|
var program = new Command();
|
|
11519
11842
|
program.name("cdd-kit").description("Contract-Driven Delivery Kit CLI").version(pkg.version);
|
|
11520
11843
|
program.command("init").description(
|
|
@@ -11529,6 +11852,18 @@ program.command("init").description(
|
|
|
11529
11852
|
})
|
|
11530
11853
|
);
|
|
11531
11854
|
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 }));
|
|
11855
|
+
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) => {
|
|
11856
|
+
const { refresh: refresh2 } = await Promise.resolve().then(() => (init_refresh(), refresh_exports));
|
|
11857
|
+
await refresh2({
|
|
11858
|
+
yes: opts.yes,
|
|
11859
|
+
noTemplates: opts.templates === false,
|
|
11860
|
+
noHooks: opts.hooks === false,
|
|
11861
|
+
noCodeMap: opts.codeMap === false,
|
|
11862
|
+
noUpdate: opts.update === false,
|
|
11863
|
+
noUpgrade: opts.upgrade === false,
|
|
11864
|
+
provider: opts.provider
|
|
11865
|
+
});
|
|
11866
|
+
});
|
|
11532
11867
|
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
11868
|
const { doctor: doctor2 } = await Promise.resolve().then(() => (init_doctor(), doctor_exports));
|
|
11534
11869
|
await doctor2({ strict: opts.strict, json: opts.json, provider: opts.provider, fix: opts.fix });
|