contract-driven-delivery 2.0.9 → 2.0.11
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 +88 -0
- package/dist/cli/index.js +1639 -1584
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -376,24 +376,44 @@ var init_update = __esm({
|
|
|
376
376
|
}
|
|
377
377
|
});
|
|
378
378
|
|
|
379
|
-
// src/
|
|
380
|
-
|
|
381
|
-
__export(context_scan_exports, {
|
|
382
|
-
contextScan: () => contextScan
|
|
383
|
-
});
|
|
384
|
-
import { existsSync as existsSync7, mkdirSync as mkdirSync4, readFileSync as readFileSync6, readdirSync as readdirSync4, writeFileSync as writeFileSync3 } from "fs";
|
|
379
|
+
// src/utils/digest.ts
|
|
380
|
+
import { readFileSync as readFileSync6 } from "fs";
|
|
385
381
|
import { createHash as createHash2 } from "crypto";
|
|
386
|
-
|
|
387
|
-
|
|
382
|
+
function normalizeContentForHash(buf) {
|
|
383
|
+
if (!buf.includes(13))
|
|
384
|
+
return buf;
|
|
385
|
+
const text = buf.toString("utf8").replace(/\r\n/g, "\n").replace(/\r/g, "\n");
|
|
386
|
+
return Buffer.from(text, "utf8");
|
|
387
|
+
}
|
|
388
|
+
function sha256OfFileNormalized(path) {
|
|
389
|
+
let buf;
|
|
388
390
|
try {
|
|
389
|
-
|
|
391
|
+
buf = readFileSync6(path);
|
|
390
392
|
} catch {
|
|
391
393
|
return "";
|
|
392
394
|
}
|
|
395
|
+
return createHash2("sha256").update(normalizeContentForHash(buf)).digest("hex");
|
|
393
396
|
}
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
+
var init_digest = __esm({
|
|
398
|
+
"src/utils/digest.ts"() {
|
|
399
|
+
"use strict";
|
|
400
|
+
}
|
|
401
|
+
});
|
|
402
|
+
|
|
403
|
+
// src/commands/context-scan.ts
|
|
404
|
+
var context_scan_exports = {};
|
|
405
|
+
__export(context_scan_exports, {
|
|
406
|
+
contextScan: () => contextScan
|
|
407
|
+
});
|
|
408
|
+
import { existsSync as existsSync7, mkdirSync as mkdirSync4, readFileSync as readFileSync7, readdirSync as readdirSync4, writeFileSync as writeFileSync3 } from "fs";
|
|
409
|
+
import { createHash as createHash3 } from "crypto";
|
|
410
|
+
import { basename, dirname as dirname3, join as join8, relative as relative2 } from "path";
|
|
411
|
+
function inputsDigest(paths, cwd) {
|
|
412
|
+
const combined = paths.slice().sort().map((p) => {
|
|
413
|
+
const rel = relative2(cwd, p).replace(/\\/g, "/");
|
|
414
|
+
return `${rel}:${sha256OfFileNormalized(p)}`;
|
|
415
|
+
}).join("\n");
|
|
416
|
+
return createHash3("sha256").update(combined).digest("hex");
|
|
397
417
|
}
|
|
398
418
|
function stripGlobSuffix(pattern) {
|
|
399
419
|
return pattern.replace(/\/\*\*$/, "").replace(/\/\*$/, "");
|
|
@@ -403,7 +423,7 @@ function getForbiddenPaths(cwd) {
|
|
|
403
423
|
const policyPath = join8(cwd, ".cdd", "context-policy.json");
|
|
404
424
|
try {
|
|
405
425
|
if (existsSync7(policyPath)) {
|
|
406
|
-
const policy = JSON.parse(
|
|
426
|
+
const policy = JSON.parse(readFileSync7(policyPath, "utf8"));
|
|
407
427
|
for (const pattern of policy.forbiddenPaths ?? []) {
|
|
408
428
|
forbidden.add(stripGlobSuffix(pattern));
|
|
409
429
|
}
|
|
@@ -549,7 +569,7 @@ async function contextScan(opts = {}) {
|
|
|
549
569
|
`visible-files: ${treeStats.files}`,
|
|
550
570
|
`omitted-dirs: ${treeStats.omittedDirs}`,
|
|
551
571
|
`truncated-dirs: ${treeStats.truncatedDirs}`,
|
|
552
|
-
`inputs-digest: ${inputsDigest(projectMapInputs)}`,
|
|
572
|
+
`inputs-digest: ${inputsDigest(projectMapInputs, cwd)}`,
|
|
553
573
|
"---",
|
|
554
574
|
"",
|
|
555
575
|
"# Project Map",
|
|
@@ -577,7 +597,7 @@ async function contextScan(opts = {}) {
|
|
|
577
597
|
for (const file of contractFiles) {
|
|
578
598
|
const relPath = relative2(cwd, file).replace(/\\/g, "/");
|
|
579
599
|
const dir = dirname3(relPath).replace(/\\/g, "/");
|
|
580
|
-
const { title, summary, metadata } = parseContractMetadata(
|
|
600
|
+
const { title, summary, metadata } = parseContractMetadata(readFileSync7(file, "utf8"));
|
|
581
601
|
const contractType = deriveContractType(relPath, metadata);
|
|
582
602
|
const owner = metadata.owner ?? "unknown";
|
|
583
603
|
const surface2 = metadata.surface ?? dir;
|
|
@@ -615,7 +635,7 @@ async function contextScan(opts = {}) {
|
|
|
615
635
|
"schema-version: 1",
|
|
616
636
|
`contract-count: ${contractFiles.length}`,
|
|
617
637
|
`missing-summary-count: ${missingSummary}`,
|
|
618
|
-
`inputs-digest: ${inputsDigest(contractFiles)}`,
|
|
638
|
+
`inputs-digest: ${inputsDigest(contractFiles, cwd)}`,
|
|
619
639
|
"---",
|
|
620
640
|
"",
|
|
621
641
|
"# Contracts Index",
|
|
@@ -644,6 +664,7 @@ var init_context_scan = __esm({
|
|
|
644
664
|
"src/commands/context-scan.ts"() {
|
|
645
665
|
"use strict";
|
|
646
666
|
init_logger();
|
|
667
|
+
init_digest();
|
|
647
668
|
DEFAULT_FORBIDDEN = [
|
|
648
669
|
".claude",
|
|
649
670
|
".git",
|
|
@@ -661,9 +682,13 @@ var init_context_scan = __esm({
|
|
|
661
682
|
".cdd/runtime"
|
|
662
683
|
];
|
|
663
684
|
FORBIDDEN_DIRECTORY_NAMES = /* @__PURE__ */ new Set([
|
|
664
|
-
// Node — also
|
|
665
|
-
// `frontend/
|
|
685
|
+
// Node / generic build outputs — also in DEFAULT_FORBIDDEN at top level,
|
|
686
|
+
// but nested cases like `frontend/dist`, `apps/foo/build`, `packages/x/out`
|
|
687
|
+
// require basename matching.
|
|
666
688
|
"node_modules",
|
|
689
|
+
"dist",
|
|
690
|
+
"build",
|
|
691
|
+
"out",
|
|
667
692
|
// Python
|
|
668
693
|
"__pycache__",
|
|
669
694
|
".pytest_cache",
|
|
@@ -4263,49 +4288,49 @@ var require_fast_uri = __commonJS({
|
|
|
4263
4288
|
schemelessOptions.skipEscape = true;
|
|
4264
4289
|
return serialize(resolved, schemelessOptions);
|
|
4265
4290
|
}
|
|
4266
|
-
function resolveComponent(base,
|
|
4291
|
+
function resolveComponent(base, relative9, options, skipNormalization) {
|
|
4267
4292
|
const target = {};
|
|
4268
4293
|
if (!skipNormalization) {
|
|
4269
4294
|
base = parse3(serialize(base, options), options);
|
|
4270
|
-
|
|
4295
|
+
relative9 = parse3(serialize(relative9, options), options);
|
|
4271
4296
|
}
|
|
4272
4297
|
options = options || {};
|
|
4273
|
-
if (!options.tolerant &&
|
|
4274
|
-
target.scheme =
|
|
4275
|
-
target.userinfo =
|
|
4276
|
-
target.host =
|
|
4277
|
-
target.port =
|
|
4278
|
-
target.path = removeDotSegments(
|
|
4279
|
-
target.query =
|
|
4298
|
+
if (!options.tolerant && relative9.scheme) {
|
|
4299
|
+
target.scheme = relative9.scheme;
|
|
4300
|
+
target.userinfo = relative9.userinfo;
|
|
4301
|
+
target.host = relative9.host;
|
|
4302
|
+
target.port = relative9.port;
|
|
4303
|
+
target.path = removeDotSegments(relative9.path || "");
|
|
4304
|
+
target.query = relative9.query;
|
|
4280
4305
|
} else {
|
|
4281
|
-
if (
|
|
4282
|
-
target.userinfo =
|
|
4283
|
-
target.host =
|
|
4284
|
-
target.port =
|
|
4285
|
-
target.path = removeDotSegments(
|
|
4286
|
-
target.query =
|
|
4306
|
+
if (relative9.userinfo !== void 0 || relative9.host !== void 0 || relative9.port !== void 0) {
|
|
4307
|
+
target.userinfo = relative9.userinfo;
|
|
4308
|
+
target.host = relative9.host;
|
|
4309
|
+
target.port = relative9.port;
|
|
4310
|
+
target.path = removeDotSegments(relative9.path || "");
|
|
4311
|
+
target.query = relative9.query;
|
|
4287
4312
|
} else {
|
|
4288
|
-
if (!
|
|
4313
|
+
if (!relative9.path) {
|
|
4289
4314
|
target.path = base.path;
|
|
4290
|
-
if (
|
|
4291
|
-
target.query =
|
|
4315
|
+
if (relative9.query !== void 0) {
|
|
4316
|
+
target.query = relative9.query;
|
|
4292
4317
|
} else {
|
|
4293
4318
|
target.query = base.query;
|
|
4294
4319
|
}
|
|
4295
4320
|
} else {
|
|
4296
|
-
if (
|
|
4297
|
-
target.path = removeDotSegments(
|
|
4321
|
+
if (relative9.path[0] === "/") {
|
|
4322
|
+
target.path = removeDotSegments(relative9.path);
|
|
4298
4323
|
} else {
|
|
4299
4324
|
if ((base.userinfo !== void 0 || base.host !== void 0 || base.port !== void 0) && !base.path) {
|
|
4300
|
-
target.path = "/" +
|
|
4325
|
+
target.path = "/" + relative9.path;
|
|
4301
4326
|
} else if (!base.path) {
|
|
4302
|
-
target.path =
|
|
4327
|
+
target.path = relative9.path;
|
|
4303
4328
|
} else {
|
|
4304
|
-
target.path = base.path.slice(0, base.path.lastIndexOf("/") + 1) +
|
|
4329
|
+
target.path = base.path.slice(0, base.path.lastIndexOf("/") + 1) + relative9.path;
|
|
4305
4330
|
}
|
|
4306
4331
|
target.path = removeDotSegments(target.path);
|
|
4307
4332
|
}
|
|
4308
|
-
target.query =
|
|
4333
|
+
target.query = relative9.query;
|
|
4309
4334
|
}
|
|
4310
4335
|
target.userinfo = base.userinfo;
|
|
4311
4336
|
target.host = base.host;
|
|
@@ -4313,7 +4338,7 @@ var require_fast_uri = __commonJS({
|
|
|
4313
4338
|
}
|
|
4314
4339
|
target.scheme = base.scheme;
|
|
4315
4340
|
}
|
|
4316
|
-
target.fragment =
|
|
4341
|
+
target.fragment = relative9.fragment;
|
|
4317
4342
|
return target;
|
|
4318
4343
|
}
|
|
4319
4344
|
function equal(uriA, uriB, options) {
|
|
@@ -7475,7 +7500,7 @@ var require_dist = __commonJS({
|
|
|
7475
7500
|
});
|
|
7476
7501
|
|
|
7477
7502
|
// src/code-map/config.ts
|
|
7478
|
-
import { existsSync as existsSync10, readFileSync as
|
|
7503
|
+
import { existsSync as existsSync10, readFileSync as readFileSync9 } from "fs";
|
|
7479
7504
|
import { join as join11 } from "path";
|
|
7480
7505
|
import { load as yamlLoad } from "js-yaml";
|
|
7481
7506
|
function asStringArray(value, key, where) {
|
|
@@ -7502,7 +7527,7 @@ function loadCodeMapConfig(cwd) {
|
|
|
7502
7527
|
}
|
|
7503
7528
|
let text;
|
|
7504
7529
|
try {
|
|
7505
|
-
text =
|
|
7530
|
+
text = readFileSync9(filePath, "utf8");
|
|
7506
7531
|
} catch (err) {
|
|
7507
7532
|
throw new Error(`failed to read ${CONFIG_REL_PATH}: ${err.message}`);
|
|
7508
7533
|
}
|
|
@@ -7625,1569 +7650,1605 @@ var init_include_exclude = __esm({
|
|
|
7625
7650
|
}
|
|
7626
7651
|
});
|
|
7627
7652
|
|
|
7628
|
-
// src/code-map/
|
|
7629
|
-
|
|
7630
|
-
|
|
7631
|
-
|
|
7632
|
-
});
|
|
7633
|
-
import { existsSync as existsSync11, statSync as statSync3 } from "fs";
|
|
7634
|
-
import { join as join13 } from "path";
|
|
7635
|
-
function checkCodeMapFreshness(cwd, mapRel = ".cdd/code-map.yml", include, exclude) {
|
|
7636
|
-
const mapPath = join13(cwd, mapRel);
|
|
7637
|
-
let cfg;
|
|
7638
|
-
try {
|
|
7639
|
-
cfg = loadCodeMapConfig(cwd);
|
|
7640
|
-
} catch (err) {
|
|
7641
|
-
return {
|
|
7642
|
-
status: "config-error",
|
|
7643
|
-
staleFiles: [],
|
|
7644
|
-
staleCount: 0,
|
|
7645
|
-
mapPath,
|
|
7646
|
-
configError: err.message
|
|
7647
|
-
};
|
|
7653
|
+
// src/code-map/yaml-writer.ts
|
|
7654
|
+
function quoteScalar(s) {
|
|
7655
|
+
if (s.startsWith(".") || YAML_RESERVED.test(s) || s.includes(" ") || s === "" || s === "null" || s === "true" || s === "false") {
|
|
7656
|
+
return `'${s.replace(/'/g, "''")}'`;
|
|
7648
7657
|
}
|
|
7649
|
-
|
|
7650
|
-
|
|
7651
|
-
|
|
7652
|
-
if (
|
|
7653
|
-
|
|
7654
|
-
|
|
7658
|
+
return s;
|
|
7659
|
+
}
|
|
7660
|
+
function quotePath(p) {
|
|
7661
|
+
if (YAML_RESERVED.test(p) || p.includes(" ")) {
|
|
7662
|
+
return `'${p.replace(/'/g, "''")}'`;
|
|
7663
|
+
}
|
|
7664
|
+
return p;
|
|
7665
|
+
}
|
|
7666
|
+
function renderItems(items) {
|
|
7667
|
+
if (items.length === 0)
|
|
7668
|
+
return "[]";
|
|
7669
|
+
return `[${items.map(quoteScalar).join(", ")}]`;
|
|
7670
|
+
}
|
|
7671
|
+
function truncateDecorator(d, max = 80) {
|
|
7672
|
+
const cleaned = d.replace(/\r?\n/g, " ");
|
|
7673
|
+
return cleaned.length <= max ? cleaned : cleaned.slice(0, max) + "...";
|
|
7674
|
+
}
|
|
7675
|
+
function renderClass(c) {
|
|
7676
|
+
const lines = [];
|
|
7677
|
+
lines.push(` - name: ${c.name}`);
|
|
7678
|
+
lines.push(` lines: ${c.lines[0]}-${c.lines[1]}`);
|
|
7679
|
+
if (c.methods.length > 0) {
|
|
7680
|
+
lines.push(" methods:");
|
|
7681
|
+
for (const m of c.methods) {
|
|
7682
|
+
const asyncPrefix = m.async ? "async " : "";
|
|
7683
|
+
lines.push(` - { name: ${asyncPrefix}${m.name}, lines: ${m.lines[0]}-${m.lines[1]} }`);
|
|
7655
7684
|
}
|
|
7656
|
-
return { status: "missing-with-sources", staleFiles: [], staleCount: 0, mapPath };
|
|
7657
7685
|
}
|
|
7658
|
-
|
|
7659
|
-
|
|
7660
|
-
|
|
7661
|
-
|
|
7662
|
-
|
|
7663
|
-
|
|
7664
|
-
|
|
7665
|
-
|
|
7686
|
+
return lines;
|
|
7687
|
+
}
|
|
7688
|
+
function renderFunction(f) {
|
|
7689
|
+
const asyncPrefix = f.async ? "async " : "";
|
|
7690
|
+
let comment = "";
|
|
7691
|
+
if (f.decorators.length > 0) {
|
|
7692
|
+
const truncated = truncateDecorator(f.decorators[0]);
|
|
7693
|
+
comment = ` # @${truncated}`;
|
|
7694
|
+
}
|
|
7695
|
+
return ` - { name: ${asyncPrefix}${f.name}, lines: ${f.lines[0]}-${f.lines[1]} }${comment}`;
|
|
7696
|
+
}
|
|
7697
|
+
function renderTypeDef(t) {
|
|
7698
|
+
const exportedSuffix = t.exported ? "" : " # local";
|
|
7699
|
+
return ` - { name: ${t.name}, lines: ${t.lines[0]}-${t.lines[1]} }${exportedSuffix}`;
|
|
7700
|
+
}
|
|
7701
|
+
function renderEnum(e) {
|
|
7702
|
+
const lines = [];
|
|
7703
|
+
const exportedSuffix = e.exported ? "" : " # local";
|
|
7704
|
+
lines.push(` - name: ${e.name}`);
|
|
7705
|
+
lines.push(` lines: ${e.lines[0]}-${e.lines[1]}${exportedSuffix}`);
|
|
7706
|
+
if (e.members.length > 0) {
|
|
7707
|
+
lines.push(` members: [${e.members.join(", ")}]`);
|
|
7708
|
+
}
|
|
7709
|
+
return lines;
|
|
7710
|
+
}
|
|
7711
|
+
function renderYaml(entries, opts) {
|
|
7712
|
+
const now = (/* @__PURE__ */ new Date()).toISOString().replace(/\.\d+Z$/, "Z");
|
|
7713
|
+
const totalSrc = entries.reduce((s, e) => s + e.total_lines, 0);
|
|
7714
|
+
const bodyLines = [];
|
|
7715
|
+
for (let i = 0; i < entries.length; i++) {
|
|
7716
|
+
const e = entries[i];
|
|
7717
|
+
if (i > 0)
|
|
7718
|
+
bodyLines.push("");
|
|
7719
|
+
const pathKey = quotePath(e.path);
|
|
7720
|
+
bodyLines.push(`${pathKey}: # ${e.total_lines} lines`);
|
|
7721
|
+
if (e.imports.length > 0) {
|
|
7722
|
+
bodyLines.push(" imports:");
|
|
7723
|
+
for (const imp of e.imports) {
|
|
7724
|
+
const mod = quoteScalar(imp.module);
|
|
7725
|
+
bodyLines.push(` - { module: ${mod}, items: ${renderItems(imp.items)}, line: ${imp.line} }`);
|
|
7726
|
+
}
|
|
7727
|
+
}
|
|
7728
|
+
if (e.constants.length > 0) {
|
|
7729
|
+
bodyLines.push(" constants:");
|
|
7730
|
+
for (const c of e.constants) {
|
|
7731
|
+
bodyLines.push(` - { name: ${c.name}, line: ${c.line} }`);
|
|
7732
|
+
}
|
|
7733
|
+
}
|
|
7734
|
+
if (e.classes.length > 0) {
|
|
7735
|
+
bodyLines.push(" classes:");
|
|
7736
|
+
for (const c of e.classes) {
|
|
7737
|
+
bodyLines.push(...renderClass(c));
|
|
7738
|
+
}
|
|
7739
|
+
}
|
|
7740
|
+
if (e.functions.length > 0) {
|
|
7741
|
+
bodyLines.push(" functions:");
|
|
7742
|
+
for (const f of e.functions) {
|
|
7743
|
+
bodyLines.push(renderFunction(f));
|
|
7744
|
+
}
|
|
7745
|
+
}
|
|
7746
|
+
if (e.interfaces && e.interfaces.length > 0) {
|
|
7747
|
+
bodyLines.push(" interfaces:");
|
|
7748
|
+
for (const t of e.interfaces) {
|
|
7749
|
+
bodyLines.push(renderTypeDef(t));
|
|
7750
|
+
}
|
|
7751
|
+
}
|
|
7752
|
+
if (e.types && e.types.length > 0) {
|
|
7753
|
+
bodyLines.push(" types:");
|
|
7754
|
+
for (const t of e.types) {
|
|
7755
|
+
bodyLines.push(renderTypeDef(t));
|
|
7756
|
+
}
|
|
7757
|
+
}
|
|
7758
|
+
if (e.enums && e.enums.length > 0) {
|
|
7759
|
+
bodyLines.push(" enums:");
|
|
7760
|
+
for (const en of e.enums) {
|
|
7761
|
+
bodyLines.push(...renderEnum(en));
|
|
7666
7762
|
}
|
|
7667
|
-
} catch {
|
|
7668
7763
|
}
|
|
7669
7764
|
}
|
|
7670
|
-
|
|
7671
|
-
|
|
7765
|
+
const headerLineCount = opts.sourcesDigest ? 3 : 2;
|
|
7766
|
+
const mapLines = bodyLines.length + headerLineCount;
|
|
7767
|
+
const compression = totalSrc === 0 ? 0 : totalSrc / mapLines;
|
|
7768
|
+
const fileCount = entries.length;
|
|
7769
|
+
const header = [
|
|
7770
|
+
`# generated: ${now} by ${opts.generator}`,
|
|
7771
|
+
`# files: ${fileCount}, src-lines: ${totalSrc}, map-lines: ${mapLines}, compression: ${compression.toFixed(1)}x`
|
|
7772
|
+
];
|
|
7773
|
+
if (opts.sourcesDigest) {
|
|
7774
|
+
header.push(`# sources-digest: ${opts.sourcesDigest}`);
|
|
7672
7775
|
}
|
|
7673
|
-
return
|
|
7674
|
-
status: "stale",
|
|
7675
|
-
staleFiles: staleAll.slice(0, 5),
|
|
7676
|
-
staleCount: staleAll.length,
|
|
7677
|
-
mapPath
|
|
7678
|
-
};
|
|
7776
|
+
return [...header, ...bodyLines].join("\n") + "\n";
|
|
7679
7777
|
}
|
|
7680
|
-
var
|
|
7681
|
-
|
|
7778
|
+
var YAML_RESERVED;
|
|
7779
|
+
var init_yaml_writer = __esm({
|
|
7780
|
+
"src/code-map/yaml-writer.ts"() {
|
|
7682
7781
|
"use strict";
|
|
7683
|
-
|
|
7684
|
-
init_config();
|
|
7782
|
+
YAML_RESERVED = /[:[\]{},&*!|>'"%@`#]/;
|
|
7685
7783
|
}
|
|
7686
7784
|
});
|
|
7687
7785
|
|
|
7688
|
-
// src/
|
|
7689
|
-
|
|
7690
|
-
|
|
7691
|
-
|
|
7692
|
-
|
|
7693
|
-
|
|
7694
|
-
|
|
7695
|
-
|
|
7696
|
-
|
|
7697
|
-
|
|
7698
|
-
|
|
7699
|
-
|
|
7700
|
-
|
|
7701
|
-
|
|
7702
|
-
|
|
7786
|
+
// src/code-map/orchestrator.ts
|
|
7787
|
+
async function scanInProcess(scanner, absolutePaths, repoRoot) {
|
|
7788
|
+
const entries = [];
|
|
7789
|
+
const warnings = [];
|
|
7790
|
+
for (const absPath of absolutePaths) {
|
|
7791
|
+
try {
|
|
7792
|
+
const entry = await scanner.scan(absPath, repoRoot);
|
|
7793
|
+
if (entry === null) {
|
|
7794
|
+
const rel = absPath.replace(/\\/g, "/").replace(repoRoot.replace(/\\/g, "/") + "/", "");
|
|
7795
|
+
warnings.push({ path: rel, message: "parse error (scanner returned null)" });
|
|
7796
|
+
} else {
|
|
7797
|
+
entries.push(entry);
|
|
7798
|
+
}
|
|
7799
|
+
} catch (err) {
|
|
7800
|
+
const rel = absPath.replace(/\\/g, "/").replace(repoRoot.replace(/\\/g, "/") + "/", "");
|
|
7801
|
+
warnings.push({ path: rel, message: `IO error: ${err.message}` });
|
|
7802
|
+
}
|
|
7703
7803
|
}
|
|
7704
|
-
return
|
|
7804
|
+
return { entries, warnings };
|
|
7705
7805
|
}
|
|
7706
|
-
function
|
|
7707
|
-
|
|
7708
|
-
|
|
7709
|
-
""
|
|
7710
|
-
|
|
7711
|
-
|
|
7712
|
-
|
|
7713
|
-
|
|
7714
|
-
|
|
7715
|
-
|
|
7716
|
-
"## Allowed Paths",
|
|
7717
|
-
`- specs/changes/${changeId}/`,
|
|
7718
|
-
"",
|
|
7719
|
-
"## Required Contracts",
|
|
7720
|
-
"- legacy-unknown",
|
|
7721
|
-
"",
|
|
7722
|
-
"## Required Tests",
|
|
7723
|
-
"- legacy-unknown",
|
|
7724
|
-
"",
|
|
7725
|
-
"## Agent Work Packets",
|
|
7726
|
-
"",
|
|
7727
|
-
"## Context Expansion Requests",
|
|
7728
|
-
"-",
|
|
7729
|
-
"",
|
|
7730
|
-
"## Approved Expansions",
|
|
7731
|
-
"-",
|
|
7732
|
-
""
|
|
7733
|
-
].join("\n");
|
|
7806
|
+
function bucketByExtension(files) {
|
|
7807
|
+
const out = {};
|
|
7808
|
+
for (const f of files) {
|
|
7809
|
+
const dot = f.lastIndexOf(".");
|
|
7810
|
+
const ext = dot >= 0 ? f.slice(dot).toLowerCase() : "";
|
|
7811
|
+
if (!out[ext])
|
|
7812
|
+
out[ext] = [];
|
|
7813
|
+
out[ext].push(f);
|
|
7814
|
+
}
|
|
7815
|
+
return out;
|
|
7734
7816
|
}
|
|
7735
|
-
|
|
7736
|
-
|
|
7737
|
-
"
|
|
7738
|
-
|
|
7739
|
-
"Generated by `cdd-kit migrate --enable-context-governance` for an existing change.",
|
|
7740
|
-
"Review and narrow the allowed paths before assigning implementation work.",
|
|
7741
|
-
"Forbidden paths come from `.cdd/context-policy.json`.",
|
|
7742
|
-
"",
|
|
7743
|
-
"## Affected Surfaces",
|
|
7744
|
-
"- legacy-unknown",
|
|
7745
|
-
"",
|
|
7746
|
-
"## Allowed Paths",
|
|
7747
|
-
`- specs/changes/${changeId}/`,
|
|
7748
|
-
"- specs/context/project-map.md",
|
|
7749
|
-
"- specs/context/contracts-index.md",
|
|
7750
|
-
"",
|
|
7751
|
-
"## Required Contracts",
|
|
7752
|
-
"- legacy-unknown",
|
|
7753
|
-
"",
|
|
7754
|
-
"## Required Tests",
|
|
7755
|
-
"- legacy-unknown",
|
|
7756
|
-
"",
|
|
7757
|
-
"## Agent Work Packets",
|
|
7758
|
-
"",
|
|
7759
|
-
"### change-classifier",
|
|
7760
|
-
"- allowed:",
|
|
7761
|
-
` - specs/changes/${changeId}/`,
|
|
7762
|
-
" - specs/context/project-map.md",
|
|
7763
|
-
" - specs/context/contracts-index.md",
|
|
7764
|
-
"",
|
|
7765
|
-
"## Context Expansion Requests",
|
|
7766
|
-
"",
|
|
7767
|
-
"<!--",
|
|
7768
|
-
"Agents must request context expansion instead of reading outside their work packet.",
|
|
7769
|
-
"Use this format only for real requests:",
|
|
7770
|
-
"",
|
|
7771
|
-
"- request-id: CER-001",
|
|
7772
|
-
" requested_paths:",
|
|
7773
|
-
" - src/example.ts",
|
|
7774
|
-
" reason: why this file is required",
|
|
7775
|
-
" status: pending",
|
|
7776
|
-
"-->",
|
|
7777
|
-
"",
|
|
7778
|
-
"## Approved Expansions",
|
|
7779
|
-
"-",
|
|
7780
|
-
""
|
|
7781
|
-
].join("\n");
|
|
7782
|
-
}
|
|
7783
|
-
function parseLegacyFrontmatter(content) {
|
|
7784
|
-
const m = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
|
|
7785
|
-
if (!m)
|
|
7786
|
-
return {};
|
|
7787
|
-
const out = {};
|
|
7788
|
-
for (const line of m[1].split(/\r?\n/)) {
|
|
7789
|
-
const colon = line.indexOf(":");
|
|
7790
|
-
if (colon === -1)
|
|
7791
|
-
continue;
|
|
7792
|
-
const key = line.slice(0, colon).trim();
|
|
7793
|
-
if (!key)
|
|
7794
|
-
continue;
|
|
7795
|
-
out[key] = line.slice(colon + 1).trim();
|
|
7817
|
+
var init_orchestrator = __esm({
|
|
7818
|
+
"src/code-map/orchestrator.ts"() {
|
|
7819
|
+
"use strict";
|
|
7820
|
+
init_include_exclude();
|
|
7796
7821
|
}
|
|
7797
|
-
|
|
7822
|
+
});
|
|
7823
|
+
|
|
7824
|
+
// src/code-map/scanners/common.ts
|
|
7825
|
+
import { relative as relative4 } from "path";
|
|
7826
|
+
function canonicalRelPath(absolutePath, repoRoot) {
|
|
7827
|
+
const rel = relative4(repoRoot, absolutePath);
|
|
7828
|
+
return rel.replace(/\\/g, "/").normalize("NFC");
|
|
7798
7829
|
}
|
|
7799
|
-
function
|
|
7800
|
-
|
|
7801
|
-
return [];
|
|
7802
|
-
const trimmed = raw.trim();
|
|
7803
|
-
if (!trimmed || trimmed === "[]")
|
|
7804
|
-
return [];
|
|
7805
|
-
const inner = trimmed.startsWith("[") && trimmed.endsWith("]") ? trimmed.slice(1, -1) : trimmed;
|
|
7806
|
-
return inner.split(",").map((item) => item.trim().replace(/^["']|["']$/g, "")).filter(Boolean);
|
|
7830
|
+
function isAllCapsConst(name) {
|
|
7831
|
+
return name.length >= 2 && /^[A-Z][A-Z0-9_]*$/.test(name);
|
|
7807
7832
|
}
|
|
7808
|
-
function
|
|
7809
|
-
const
|
|
7810
|
-
|
|
7811
|
-
let currentSection;
|
|
7812
|
-
for (const raw of lines) {
|
|
7813
|
-
const headerMatch = raw.match(/^##\s+\d+\.\s+(.*)\s*$/);
|
|
7814
|
-
if (headerMatch) {
|
|
7815
|
-
currentSection = headerMatch[1].trim();
|
|
7816
|
-
continue;
|
|
7817
|
-
}
|
|
7818
|
-
const itemMatch = raw.match(/^\s*-\s*\[([ xX\-])\]\s+(\d+(?:\.\d+)*)\s+(.*)\s*$/);
|
|
7819
|
-
if (!itemMatch)
|
|
7820
|
-
continue;
|
|
7821
|
-
const mark = itemMatch[1];
|
|
7822
|
-
const id = itemMatch[2];
|
|
7823
|
-
const title = itemMatch[3].trim();
|
|
7824
|
-
let status = "pending";
|
|
7825
|
-
if (mark === "x" || mark === "X")
|
|
7826
|
-
status = "done";
|
|
7827
|
-
else if (mark === "-")
|
|
7828
|
-
status = "skipped";
|
|
7829
|
-
rows.push({ id, title, status, section: currentSection });
|
|
7830
|
-
}
|
|
7831
|
-
return rows;
|
|
7833
|
+
function isBinary(content) {
|
|
7834
|
+
const head = content.slice(0, 4096);
|
|
7835
|
+
return head.includes("\0");
|
|
7832
7836
|
}
|
|
7833
|
-
|
|
7834
|
-
|
|
7835
|
-
|
|
7836
|
-
if (existsSync14(newPath)) {
|
|
7837
|
-
return;
|
|
7838
|
-
}
|
|
7839
|
-
if (!existsSync14(legacyPath)) {
|
|
7840
|
-
warnings.push("tasks.md not found and tasks.yml missing \u2014 skipping tasks migration");
|
|
7841
|
-
return;
|
|
7842
|
-
}
|
|
7843
|
-
const raw = readFileSync11(legacyPath, "utf8");
|
|
7844
|
-
const fm = parseLegacyFrontmatter(raw);
|
|
7845
|
-
const bodyMatch = raw.match(/^---\r?\n[\s\S]*?\r?\n---\r?\n?([\s\S]*)$/);
|
|
7846
|
-
const body = bodyMatch ? bodyMatch[1] : raw;
|
|
7847
|
-
const tasksRows = parseLegacyTaskList(body);
|
|
7848
|
-
const data = {};
|
|
7849
|
-
data["change-id"] = fm["change-id"] || changeId;
|
|
7850
|
-
data["status"] = fm["status"] || "in-progress";
|
|
7851
|
-
if (fm["tier"] && /^\d+$/.test(fm["tier"])) {
|
|
7852
|
-
data["tier"] = parseInt(fm["tier"], 10);
|
|
7853
|
-
} else if (detectedTier) {
|
|
7854
|
-
data["tier"] = parseInt(detectedTier, 10);
|
|
7855
|
-
} else {
|
|
7856
|
-
data["tier"] = null;
|
|
7837
|
+
var init_common = __esm({
|
|
7838
|
+
"src/code-map/scanners/common.ts"() {
|
|
7839
|
+
"use strict";
|
|
7857
7840
|
}
|
|
7858
|
-
|
|
7859
|
-
|
|
7841
|
+
});
|
|
7842
|
+
|
|
7843
|
+
// src/code-map/scanners/python.ts
|
|
7844
|
+
var python_exports = {};
|
|
7845
|
+
__export(python_exports, {
|
|
7846
|
+
pythonScanner: () => pythonScanner
|
|
7847
|
+
});
|
|
7848
|
+
import { spawnSync as spawnSync2 } from "child_process";
|
|
7849
|
+
import { writeFileSync as writeFileSync5, unlinkSync } from "fs";
|
|
7850
|
+
import { join as join13 } from "path";
|
|
7851
|
+
import { tmpdir } from "os";
|
|
7852
|
+
function detectPython2() {
|
|
7853
|
+
for (const candidate of ["python3", "python"]) {
|
|
7854
|
+
try {
|
|
7855
|
+
const result = spawnSync2(candidate, ["--version"], {
|
|
7856
|
+
encoding: "utf8",
|
|
7857
|
+
timeout: 5e3
|
|
7858
|
+
});
|
|
7859
|
+
if (result.status === 0)
|
|
7860
|
+
return candidate;
|
|
7861
|
+
} catch {
|
|
7862
|
+
}
|
|
7860
7863
|
}
|
|
7861
|
-
|
|
7862
|
-
data["archive-tasks"] = archive2.length > 0 ? archive2 : ["7.1", "7.2"];
|
|
7863
|
-
const deps = parseListField(fm["depends-on"]);
|
|
7864
|
-
data["depends-on"] = deps;
|
|
7865
|
-
data["tasks"] = tasksRows.map((r) => {
|
|
7866
|
-
const out = { id: r.id, title: r.title, status: r.status };
|
|
7867
|
-
if (r.section)
|
|
7868
|
-
out["section"] = r.section;
|
|
7869
|
-
return out;
|
|
7870
|
-
});
|
|
7871
|
-
const yamlOut = yaml3.dump(data, { lineWidth: -1, noRefs: true });
|
|
7872
|
-
pendingWrites.push({ path: newPath, content: yamlOut });
|
|
7873
|
-
pendingDeletes.push({ path: legacyPath });
|
|
7874
|
-
changed.push(`tasks.md -> tasks.yml (${tasksRows.length} task(s) migrated)`);
|
|
7864
|
+
return null;
|
|
7875
7865
|
}
|
|
7876
|
-
|
|
7877
|
-
|
|
7878
|
-
|
|
7879
|
-
|
|
7880
|
-
|
|
7881
|
-
|
|
7882
|
-
|
|
7883
|
-
|
|
7884
|
-
|
|
7885
|
-
|
|
7886
|
-
|
|
7887
|
-
|
|
7888
|
-
|
|
7889
|
-
const inline = fieldMatch[2].trim();
|
|
7890
|
-
if (key === "files-read" || key === "artifacts") {
|
|
7891
|
-
const items = [];
|
|
7892
|
-
let j = i + 1;
|
|
7893
|
-
while (j < lines.length) {
|
|
7894
|
-
const sub = lines[j];
|
|
7895
|
-
if (topFieldRe.test(sub))
|
|
7896
|
-
break;
|
|
7897
|
-
if (/^#/.test(sub))
|
|
7898
|
-
break;
|
|
7899
|
-
const itemMatch = sub.match(/^\s{2,}-\s+(.+?)\s*$/);
|
|
7900
|
-
if (itemMatch) {
|
|
7901
|
-
items.push(itemMatch[1].trim());
|
|
7866
|
+
var TIMEOUT_MS, PythonScanner, pythonScanner;
|
|
7867
|
+
var init_python = __esm({
|
|
7868
|
+
"src/code-map/scanners/python.ts"() {
|
|
7869
|
+
"use strict";
|
|
7870
|
+
init_paths();
|
|
7871
|
+
init_common();
|
|
7872
|
+
TIMEOUT_MS = parseInt(process.env["CDD_CODE_MAP_TIMEOUT_MS"] ?? "30000", 10);
|
|
7873
|
+
PythonScanner = class {
|
|
7874
|
+
extensions = [".py"];
|
|
7875
|
+
_interpreter = void 0;
|
|
7876
|
+
getInterpreter() {
|
|
7877
|
+
if (this._interpreter === void 0) {
|
|
7878
|
+
this._interpreter = detectPython2();
|
|
7902
7879
|
}
|
|
7903
|
-
|
|
7880
|
+
return this._interpreter;
|
|
7904
7881
|
}
|
|
7905
|
-
|
|
7906
|
-
|
|
7907
|
-
|
|
7908
|
-
data["artifacts"] = items.map((s) => {
|
|
7909
|
-
const idx = s.indexOf(":");
|
|
7910
|
-
if (idx === -1) {
|
|
7911
|
-
return { type: "note", pointer: s };
|
|
7912
|
-
}
|
|
7913
|
-
const type = s.slice(0, idx).trim();
|
|
7914
|
-
const pointer = s.slice(idx + 1).trim();
|
|
7915
|
-
return { type, pointer };
|
|
7916
|
-
});
|
|
7882
|
+
async scan(absolutePath, repoRoot) {
|
|
7883
|
+
const result = await this.scanBatch([absolutePath], repoRoot);
|
|
7884
|
+
return result.entries[0] ?? null;
|
|
7917
7885
|
}
|
|
7918
|
-
|
|
7919
|
-
|
|
7920
|
-
|
|
7921
|
-
|
|
7922
|
-
|
|
7923
|
-
|
|
7924
|
-
|
|
7925
|
-
|
|
7926
|
-
|
|
7927
|
-
|
|
7928
|
-
|
|
7929
|
-
|
|
7930
|
-
|
|
7931
|
-
|
|
7932
|
-
|
|
7933
|
-
|
|
7934
|
-
|
|
7935
|
-
|
|
7936
|
-
|
|
7937
|
-
|
|
7938
|
-
|
|
7939
|
-
|
|
7940
|
-
|
|
7941
|
-
|
|
7942
|
-
|
|
7943
|
-
|
|
7944
|
-
|
|
7945
|
-
|
|
7946
|
-
|
|
7947
|
-
const warnings = [];
|
|
7948
|
-
const pending = [];
|
|
7949
|
-
const deletes = [];
|
|
7950
|
-
let detectedTier = null;
|
|
7951
|
-
const classifPath = join16(changeDir, "change-classification.md");
|
|
7952
|
-
if (existsSync14(classifPath)) {
|
|
7953
|
-
const content = readFileSync11(classifPath, "utf8");
|
|
7954
|
-
const hasNewTierFormat = /^## Tier\s*\n\s*-\s*\d\s*$/m.test(content);
|
|
7955
|
-
const oldMatch = content.match(/\*\*Tier[:\*]+\s*(?:Tier\s*)?(\d)/i) ?? content.match(/^-?\s*Tier:\s*(?:Tier\s*)?(\d)/mi);
|
|
7956
|
-
if (oldMatch)
|
|
7957
|
-
detectedTier = oldMatch[1];
|
|
7958
|
-
if (!hasNewTierFormat) {
|
|
7959
|
-
if (detectedTier) {
|
|
7960
|
-
const addition = `
|
|
7961
|
-
## Tier
|
|
7962
|
-
- ${detectedTier}
|
|
7963
|
-
`;
|
|
7964
|
-
if (!content.includes("\n## Tier\n")) {
|
|
7965
|
-
changed.push(
|
|
7966
|
-
`change-classification.md: appended "## Tier\\n- ${detectedTier}" (converted from old format)`
|
|
7886
|
+
async scanBatch(absolutePaths, repoRoot) {
|
|
7887
|
+
const interpreter = this.getInterpreter();
|
|
7888
|
+
const entries = [];
|
|
7889
|
+
const warnings = [];
|
|
7890
|
+
if (!interpreter) {
|
|
7891
|
+
const count = absolutePaths.length;
|
|
7892
|
+
warnings.push({
|
|
7893
|
+
path: "",
|
|
7894
|
+
message: `python interpreter not found on PATH; skipping ${count} .py file${count === 1 ? "" : "s"}`
|
|
7895
|
+
});
|
|
7896
|
+
return { entries, warnings };
|
|
7897
|
+
}
|
|
7898
|
+
const scriptPath = ASSET.codeMapPython;
|
|
7899
|
+
const rand = Math.random().toString(36).slice(2);
|
|
7900
|
+
const listFile = join13(tmpdir(), `cdd-codemap-${process.pid}-${rand}.txt`);
|
|
7901
|
+
writeFileSync5(listFile, absolutePaths.join("\n") + "\n", "utf8");
|
|
7902
|
+
let stdout = "";
|
|
7903
|
+
let stderr = "";
|
|
7904
|
+
let exitCode = 0;
|
|
7905
|
+
try {
|
|
7906
|
+
const result = spawnSync2(
|
|
7907
|
+
interpreter,
|
|
7908
|
+
[scriptPath, "--batch-file", listFile, "--repo-root", repoRoot],
|
|
7909
|
+
{
|
|
7910
|
+
encoding: "utf8",
|
|
7911
|
+
timeout: TIMEOUT_MS,
|
|
7912
|
+
maxBuffer: 50 * 1024 * 1024
|
|
7913
|
+
// 50MB
|
|
7914
|
+
}
|
|
7967
7915
|
);
|
|
7968
|
-
|
|
7916
|
+
stdout = result.stdout ?? "";
|
|
7917
|
+
stderr = result.stderr ?? "";
|
|
7918
|
+
exitCode = result.status ?? -1;
|
|
7919
|
+
if (result.error) {
|
|
7920
|
+
const errMsg = result.error.message ?? String(result.error);
|
|
7921
|
+
if (errMsg.includes("ENOENT")) {
|
|
7922
|
+
warnings.push({
|
|
7923
|
+
path: "",
|
|
7924
|
+
message: `python interpreter not found (ENOENT); skipping ${absolutePaths.length} .py file(s)`
|
|
7925
|
+
});
|
|
7926
|
+
return { entries, warnings };
|
|
7927
|
+
}
|
|
7928
|
+
if (errMsg.includes("ETIMEDOUT") || errMsg.includes("timeout")) {
|
|
7929
|
+
warnings.push({
|
|
7930
|
+
path: "",
|
|
7931
|
+
message: `python scanner timed out after ${TIMEOUT_MS}ms; skipping .py files`
|
|
7932
|
+
});
|
|
7933
|
+
return { entries, warnings };
|
|
7934
|
+
}
|
|
7935
|
+
warnings.push({
|
|
7936
|
+
path: "",
|
|
7937
|
+
message: `python scanner error: ${errMsg}; skipping .py files`
|
|
7938
|
+
});
|
|
7939
|
+
return { entries, warnings };
|
|
7940
|
+
}
|
|
7941
|
+
} finally {
|
|
7942
|
+
try {
|
|
7943
|
+
unlinkSync(listFile);
|
|
7944
|
+
} catch {
|
|
7945
|
+
}
|
|
7969
7946
|
}
|
|
7970
|
-
|
|
7971
|
-
|
|
7972
|
-
|
|
7973
|
-
|
|
7947
|
+
if (exitCode === 3) {
|
|
7948
|
+
warnings.push({
|
|
7949
|
+
path: "",
|
|
7950
|
+
message: "python interpreter is < 3.9 (need ast.unparse); skipping .py files"
|
|
7951
|
+
});
|
|
7952
|
+
return { entries, warnings };
|
|
7953
|
+
}
|
|
7954
|
+
for (const line of stdout.split("\n")) {
|
|
7955
|
+
const trimmed = line.trim();
|
|
7956
|
+
if (!trimmed)
|
|
7957
|
+
continue;
|
|
7958
|
+
let parsed;
|
|
7959
|
+
try {
|
|
7960
|
+
parsed = JSON.parse(trimmed);
|
|
7961
|
+
} catch {
|
|
7962
|
+
continue;
|
|
7963
|
+
}
|
|
7964
|
+
if (!parsed.ok) {
|
|
7965
|
+
warnings.push({ path: parsed.path, message: parsed.error });
|
|
7966
|
+
continue;
|
|
7967
|
+
}
|
|
7968
|
+
const r = parsed;
|
|
7969
|
+
entries.push({
|
|
7970
|
+
path: canonicalRelPath(join13(repoRoot, r.path), repoRoot),
|
|
7971
|
+
total_lines: r.total_lines,
|
|
7972
|
+
imports: r.imports ?? [],
|
|
7973
|
+
constants: r.constants ?? [],
|
|
7974
|
+
classes: (r.classes ?? []).map((c) => ({
|
|
7975
|
+
name: c.name,
|
|
7976
|
+
lines: [c.lines[0], c.lines[1]],
|
|
7977
|
+
methods: (c.methods ?? []).map((m) => ({
|
|
7978
|
+
name: m.name,
|
|
7979
|
+
lines: [m.lines[0], m.lines[1]],
|
|
7980
|
+
async: m.async
|
|
7981
|
+
}))
|
|
7982
|
+
})),
|
|
7983
|
+
functions: (r.functions ?? []).map((f) => ({
|
|
7984
|
+
name: f.name,
|
|
7985
|
+
lines: [f.lines[0], f.lines[1]],
|
|
7986
|
+
decorators: f.decorators ?? [],
|
|
7987
|
+
async: f.async
|
|
7988
|
+
}))
|
|
7989
|
+
});
|
|
7990
|
+
}
|
|
7991
|
+
if (exitCode === 2) {
|
|
7992
|
+
warnings.push({
|
|
7993
|
+
path: "",
|
|
7994
|
+
message: `python scanner exited with code 2 (fatal); partial results only. stderr: ${stderr.slice(0, 200)}`
|
|
7995
|
+
});
|
|
7996
|
+
}
|
|
7997
|
+
return { entries, warnings };
|
|
7974
7998
|
}
|
|
7975
|
-
}
|
|
7976
|
-
|
|
7977
|
-
if (structured)
|
|
7978
|
-
detectedTier = structured[1];
|
|
7979
|
-
}
|
|
7980
|
-
}
|
|
7981
|
-
migrateTasksFile(changeId, changeDir, enableContextGovernance, detectedTier, changed, warnings, pending, deletes);
|
|
7982
|
-
migrateAgentLogs(changeDir, changed, pending, deletes);
|
|
7983
|
-
const manifestPath = join16(changeDir, "context-manifest.md");
|
|
7984
|
-
if (!existsSync14(manifestPath)) {
|
|
7985
|
-
changed.push(enableContextGovernance ? "context-manifest.md: added context-governance v1 manifest scaffold" : "context-manifest.md: added legacy context manifest scaffold");
|
|
7986
|
-
pending.push({
|
|
7987
|
-
path: manifestPath,
|
|
7988
|
-
content: enableContextGovernance ? buildContextGovernedManifest(changeId) : buildLegacyContextManifest(changeId)
|
|
7989
|
-
});
|
|
7990
|
-
} else if (enableContextGovernance) {
|
|
7991
|
-
warnings.push("context-manifest.md already exists \u2014 review allowed paths before relying on context-governance: v1");
|
|
7999
|
+
};
|
|
8000
|
+
pythonScanner = new PythonScanner();
|
|
7992
8001
|
}
|
|
7993
|
-
|
|
8002
|
+
});
|
|
8003
|
+
|
|
8004
|
+
// src/code-map/scanners/javascript.ts
|
|
8005
|
+
var javascript_exports = {};
|
|
8006
|
+
__export(javascript_exports, {
|
|
8007
|
+
COMMON_PLUGINS: () => COMMON_PLUGINS,
|
|
8008
|
+
countSourceLines: () => countSourceLines,
|
|
8009
|
+
jsScanner: () => jsScanner,
|
|
8010
|
+
parseAndExtract: () => parseAndExtract,
|
|
8011
|
+
parseJsSource: () => parseJsSource,
|
|
8012
|
+
parseSourceWithPlugins: () => parseSourceWithPlugins
|
|
8013
|
+
});
|
|
8014
|
+
import { readFileSync as readFileSync11 } from "fs";
|
|
8015
|
+
import { parse } from "@babel/parser";
|
|
8016
|
+
function parseSourceWithPlugins(source, plugins) {
|
|
8017
|
+
return parse(source, {
|
|
8018
|
+
sourceType: "unambiguous",
|
|
8019
|
+
allowReturnOutsideFunction: true,
|
|
8020
|
+
allowAwaitOutsideFunction: true,
|
|
8021
|
+
errorRecovery: true,
|
|
8022
|
+
plugins
|
|
8023
|
+
});
|
|
7994
8024
|
}
|
|
7995
|
-
function
|
|
7996
|
-
|
|
7997
|
-
|
|
7998
|
-
|
|
7999
|
-
|
|
8000
|
-
|
|
8001
|
-
|
|
8002
|
-
|
|
8003
|
-
|
|
8004
|
-
|
|
8005
|
-
|
|
8006
|
-
|
|
8007
|
-
|
|
8008
|
-
|
|
8025
|
+
function isCallExpression(node, callee) {
|
|
8026
|
+
if (!node || node.type !== "CallExpression")
|
|
8027
|
+
return false;
|
|
8028
|
+
const c = node.callee;
|
|
8029
|
+
return c.type === "Identifier" && c.name === callee;
|
|
8030
|
+
}
|
|
8031
|
+
function extractRequireModule(node) {
|
|
8032
|
+
if (!node || node.type !== "CallExpression")
|
|
8033
|
+
return null;
|
|
8034
|
+
const c = node.callee;
|
|
8035
|
+
if (c.type !== "Identifier" || c.name !== "require")
|
|
8036
|
+
return null;
|
|
8037
|
+
const arg = node.arguments[0];
|
|
8038
|
+
if (!arg || arg.type !== "StringLiteral")
|
|
8039
|
+
return null;
|
|
8040
|
+
return arg.value;
|
|
8041
|
+
}
|
|
8042
|
+
function getLineRange(node) {
|
|
8043
|
+
const start = node.loc?.start.line ?? 1;
|
|
8044
|
+
const end = node.loc?.end.line ?? start;
|
|
8045
|
+
return [start, end];
|
|
8046
|
+
}
|
|
8047
|
+
function processImportDeclaration(node) {
|
|
8048
|
+
const items = [];
|
|
8049
|
+
for (const spec of node.specifiers) {
|
|
8050
|
+
if (spec.type === "ImportDefaultSpecifier") {
|
|
8051
|
+
items.push(`default:${spec.local.name}`);
|
|
8052
|
+
} else if (spec.type === "ImportNamespaceSpecifier") {
|
|
8053
|
+
items.push(`*:${spec.local.name}`);
|
|
8054
|
+
} else if (spec.type === "ImportSpecifier") {
|
|
8055
|
+
const imported = spec.imported;
|
|
8056
|
+
items.push(imported.type === "Identifier" ? imported.name : spec.local.name);
|
|
8009
8057
|
}
|
|
8010
|
-
throw err;
|
|
8011
|
-
}
|
|
8012
|
-
for (const r of renames) {
|
|
8013
|
-
renameSync(r.tmp, r.final);
|
|
8014
8058
|
}
|
|
8015
|
-
|
|
8016
|
-
|
|
8017
|
-
|
|
8018
|
-
|
|
8059
|
+
return {
|
|
8060
|
+
module: node.source.value,
|
|
8061
|
+
items,
|
|
8062
|
+
line: node.loc?.start.line ?? 1
|
|
8063
|
+
};
|
|
8064
|
+
}
|
|
8065
|
+
function processFunctionDeclaration(node, nameOverride) {
|
|
8066
|
+
const name = nameOverride ?? node.id?.name;
|
|
8067
|
+
if (!name)
|
|
8068
|
+
return null;
|
|
8069
|
+
return {
|
|
8070
|
+
name,
|
|
8071
|
+
lines: getLineRange(node),
|
|
8072
|
+
decorators: [],
|
|
8073
|
+
async: node.async
|
|
8074
|
+
};
|
|
8075
|
+
}
|
|
8076
|
+
function processClassDeclaration(node, nameOverride) {
|
|
8077
|
+
const name = nameOverride ?? node.id?.name;
|
|
8078
|
+
if (!name)
|
|
8079
|
+
return null;
|
|
8080
|
+
const methods = [];
|
|
8081
|
+
for (const member of node.body.body) {
|
|
8082
|
+
if (member.type === "ClassMethod" || member.type === "ClassPrivateMethod") {
|
|
8083
|
+
const m = member;
|
|
8084
|
+
let methodName;
|
|
8085
|
+
if (m.key.type === "Identifier") {
|
|
8086
|
+
methodName = m.key.name;
|
|
8087
|
+
} else if (m.key.type === "PrivateName") {
|
|
8088
|
+
methodName = `#${m.key.id.name}`;
|
|
8089
|
+
} else {
|
|
8090
|
+
methodName = "<computed>";
|
|
8091
|
+
}
|
|
8092
|
+
methods.push({
|
|
8093
|
+
name: methodName,
|
|
8094
|
+
lines: getLineRange(m),
|
|
8095
|
+
async: m.async
|
|
8096
|
+
});
|
|
8019
8097
|
}
|
|
8020
8098
|
}
|
|
8099
|
+
return {
|
|
8100
|
+
name,
|
|
8101
|
+
lines: getLineRange(node),
|
|
8102
|
+
methods
|
|
8103
|
+
};
|
|
8021
8104
|
}
|
|
8022
|
-
|
|
8023
|
-
const
|
|
8024
|
-
|
|
8025
|
-
const enableContextGovernance = opts.enableContextGovernance ?? false;
|
|
8026
|
-
const noBackup = opts.noBackup ?? false;
|
|
8027
|
-
const idsToMigrate = [];
|
|
8028
|
-
if (opts.all) {
|
|
8029
|
-
const changesDir = join16(cwd, "specs", "changes");
|
|
8030
|
-
if (!existsSync14(changesDir)) {
|
|
8031
|
-
log.info("No specs/changes/ directory found \u2014 nothing to migrate.");
|
|
8032
|
-
return;
|
|
8033
|
-
}
|
|
8034
|
-
idsToMigrate.push(
|
|
8035
|
-
...readdirSync8(changesDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name)
|
|
8036
|
-
);
|
|
8037
|
-
} else if (changeId) {
|
|
8038
|
-
const specificDir = join16(cwd, "specs", "changes", changeId);
|
|
8039
|
-
if (!existsSync14(specificDir)) {
|
|
8040
|
-
log.error(`Change not found: specs/changes/${changeId}`);
|
|
8041
|
-
process.exit(1);
|
|
8042
|
-
}
|
|
8043
|
-
idsToMigrate.push(changeId);
|
|
8044
|
-
} else {
|
|
8045
|
-
log.error("Usage: cdd-kit migrate <change-id> | cdd-kit migrate --all [--dry-run] [--no-backup]");
|
|
8046
|
-
process.exit(1);
|
|
8047
|
-
}
|
|
8048
|
-
if (idsToMigrate.length === 0) {
|
|
8049
|
-
log.info("No changes found to migrate.");
|
|
8050
|
-
return;
|
|
8051
|
-
}
|
|
8052
|
-
if (dryRun) {
|
|
8053
|
-
log.info("Dry run \u2014 no files will be written.");
|
|
8054
|
-
log.blank();
|
|
8055
|
-
}
|
|
8056
|
-
const sessionStamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
8057
|
-
let migratedCount = 0;
|
|
8058
|
-
let upToDateCount = 0;
|
|
8059
|
-
const backupRoot = join16(cwd, ".cdd", "migrate-backup", sessionStamp);
|
|
8060
|
-
for (const id of idsToMigrate) {
|
|
8061
|
-
const changeDir = join16(cwd, "specs", "changes", id);
|
|
8062
|
-
if (!existsSync14(changeDir)) {
|
|
8063
|
-
log.warn(` ${id}: directory not found \u2014 skipping`);
|
|
8105
|
+
function processVariableDeclaration(node, imports, constants, functions) {
|
|
8106
|
+
for (const decl of node.declarations) {
|
|
8107
|
+
if (!decl.id || decl.id.type !== "Identifier")
|
|
8064
8108
|
continue;
|
|
8065
|
-
|
|
8066
|
-
const
|
|
8067
|
-
|
|
8068
|
-
|
|
8069
|
-
|
|
8070
|
-
|
|
8071
|
-
|
|
8072
|
-
log.warn(` ${id}: ${w}`);
|
|
8109
|
+
const varName = decl.id.name;
|
|
8110
|
+
const init2 = decl.init;
|
|
8111
|
+
if (init2 && isCallExpression(init2, "require")) {
|
|
8112
|
+
const mod = extractRequireModule(init2);
|
|
8113
|
+
if (mod) {
|
|
8114
|
+
imports.push({ module: mod, items: [`default:${varName}`], line: node.loc?.start.line ?? 1 });
|
|
8115
|
+
}
|
|
8073
8116
|
continue;
|
|
8074
8117
|
}
|
|
8075
|
-
if (
|
|
8076
|
-
|
|
8077
|
-
|
|
8078
|
-
backupChangeDir(cwd, id, sessionStamp);
|
|
8079
|
-
commitWritesAtomically(pending, deletes);
|
|
8080
|
-
} catch (err) {
|
|
8081
|
-
log.error(` ${id}: migration failed \u2014 ${err.message}`);
|
|
8082
|
-
if (!noBackup) {
|
|
8083
|
-
log.error(` ${id}: restore from .cdd/migrate-backup/${sessionStamp}/${id}/`);
|
|
8084
|
-
}
|
|
8085
|
-
process.exit(1);
|
|
8086
|
-
}
|
|
8118
|
+
if (isAllCapsConst(varName) && init2 !== null && init2 !== void 0) {
|
|
8119
|
+
constants.push({ name: varName, line: node.loc?.start.line ?? 1 });
|
|
8120
|
+
continue;
|
|
8087
8121
|
}
|
|
8088
|
-
|
|
8089
|
-
|
|
8090
|
-
|
|
8091
|
-
|
|
8092
|
-
|
|
8093
|
-
|
|
8094
|
-
|
|
8095
|
-
|
|
8096
|
-
|
|
8097
|
-
|
|
8098
|
-
|
|
8099
|
-
|
|
8100
|
-
|
|
8101
|
-
|
|
8102
|
-
if (ensureGitignoreEntry(cwd, ".cdd/migrate-backup/")) {
|
|
8103
|
-
log.info("Added `.cdd/migrate-backup/` to .gitignore");
|
|
8104
|
-
}
|
|
8105
|
-
log.info('Next: git add specs/changes/ && git commit -m "chore: migrate changes to YAML format"');
|
|
8106
|
-
log.info("When stable, remove backup: rm -rf .cdd/migrate-backup/");
|
|
8122
|
+
if (init2 && (init2.type === "ArrowFunctionExpression" || init2.type === "FunctionExpression")) {
|
|
8123
|
+
functions.push({
|
|
8124
|
+
name: varName,
|
|
8125
|
+
lines: getLineRange(node),
|
|
8126
|
+
decorators: [],
|
|
8127
|
+
async: init2.async
|
|
8128
|
+
});
|
|
8129
|
+
} else if (init2 && init2.type === "CallExpression" && /^[A-Z]/.test(varName)) {
|
|
8130
|
+
functions.push({
|
|
8131
|
+
name: varName,
|
|
8132
|
+
lines: getLineRange(node),
|
|
8133
|
+
decorators: [],
|
|
8134
|
+
async: false
|
|
8135
|
+
});
|
|
8107
8136
|
}
|
|
8108
8137
|
}
|
|
8109
8138
|
}
|
|
8110
|
-
function
|
|
8111
|
-
|
|
8112
|
-
|
|
8113
|
-
if (!
|
|
8114
|
-
return
|
|
8115
|
-
|
|
8116
|
-
|
|
8117
|
-
|
|
8118
|
-
|
|
8119
|
-
|
|
8120
|
-
return false;
|
|
8121
|
-
const sep = existing.length > 0 && !existing.endsWith("\n") ? "\n" : "";
|
|
8122
|
-
const block = existing.length === 0 ? `# cdd-kit generated backups (do not commit)
|
|
8123
|
-
${trimmed}
|
|
8124
|
-
` : `${sep}
|
|
8125
|
-
# cdd-kit generated backups (do not commit)
|
|
8126
|
-
${trimmed}
|
|
8127
|
-
`;
|
|
8128
|
-
writeFileSync6(path, existing + block, "utf8");
|
|
8129
|
-
return true;
|
|
8139
|
+
function processTsInterfaceDeclaration(node, exported) {
|
|
8140
|
+
if (!node || node.type !== "TSInterfaceDeclaration")
|
|
8141
|
+
return null;
|
|
8142
|
+
if (!node.id || node.id.type !== "Identifier")
|
|
8143
|
+
return null;
|
|
8144
|
+
return {
|
|
8145
|
+
name: node.id.name,
|
|
8146
|
+
lines: getLineRange(node),
|
|
8147
|
+
exported
|
|
8148
|
+
};
|
|
8130
8149
|
}
|
|
8131
|
-
|
|
8132
|
-
|
|
8133
|
-
|
|
8134
|
-
|
|
8135
|
-
|
|
8136
|
-
|
|
8137
|
-
|
|
8138
|
-
|
|
8139
|
-
|
|
8140
|
-
|
|
8141
|
-
|
|
8142
|
-
|
|
8143
|
-
|
|
8144
|
-
|
|
8145
|
-
|
|
8146
|
-
|
|
8147
|
-
|
|
8148
|
-
for (const
|
|
8149
|
-
|
|
8150
|
-
const dest = join17(destDir, entry.name);
|
|
8151
|
-
if (entry.isDirectory()) {
|
|
8152
|
-
planMissingFiles(src, dest, join17(label, entry.name), planned);
|
|
8150
|
+
function processTsTypeAliasDeclaration(node, exported) {
|
|
8151
|
+
if (!node || node.type !== "TSTypeAliasDeclaration")
|
|
8152
|
+
return null;
|
|
8153
|
+
if (!node.id || node.id.type !== "Identifier")
|
|
8154
|
+
return null;
|
|
8155
|
+
return {
|
|
8156
|
+
name: node.id.name,
|
|
8157
|
+
lines: getLineRange(node),
|
|
8158
|
+
exported
|
|
8159
|
+
};
|
|
8160
|
+
}
|
|
8161
|
+
function processTsEnumDeclaration(node, exported) {
|
|
8162
|
+
if (!node || node.type !== "TSEnumDeclaration")
|
|
8163
|
+
return null;
|
|
8164
|
+
if (!node.id || node.id.type !== "Identifier")
|
|
8165
|
+
return null;
|
|
8166
|
+
const members = [];
|
|
8167
|
+
for (const m of node.members ?? []) {
|
|
8168
|
+
if (!m || !m.id)
|
|
8153
8169
|
continue;
|
|
8154
|
-
|
|
8155
|
-
|
|
8156
|
-
|
|
8157
|
-
|
|
8170
|
+
if (m.id.type === "Identifier")
|
|
8171
|
+
members.push(m.id.name);
|
|
8172
|
+
else if (m.id.type === "StringLiteral")
|
|
8173
|
+
members.push(m.id.value);
|
|
8158
8174
|
}
|
|
8175
|
+
return {
|
|
8176
|
+
name: node.id.name,
|
|
8177
|
+
lines: getLineRange(node),
|
|
8178
|
+
exported,
|
|
8179
|
+
members
|
|
8180
|
+
};
|
|
8159
8181
|
}
|
|
8160
|
-
function
|
|
8161
|
-
if (
|
|
8162
|
-
|
|
8163
|
-
|
|
8164
|
-
}
|
|
8165
|
-
if (!existsSync15(join17(cwd, "AGENTS.md"))) {
|
|
8166
|
-
planned.push({ src: ASSET.agentsTemplate, dest: join17(cwd, "AGENTS.md"), rel: "AGENTS.md" });
|
|
8167
|
-
}
|
|
8182
|
+
function processStatement(stmt, buckets, extractTsTypes, exportedFromWrapper = false) {
|
|
8183
|
+
if (stmt.type === "ExportNamedDeclaration" && stmt.declaration) {
|
|
8184
|
+
processStatement(stmt.declaration, buckets, extractTsTypes, true);
|
|
8185
|
+
return;
|
|
8168
8186
|
}
|
|
8169
|
-
if (
|
|
8170
|
-
|
|
8187
|
+
if (stmt.type === "ExportDefaultDeclaration") {
|
|
8188
|
+
const decl = stmt.declaration;
|
|
8189
|
+
if (decl.type === "FunctionDeclaration") {
|
|
8190
|
+
const fe = processFunctionDeclaration(decl, decl.id?.name ?? "default");
|
|
8191
|
+
if (fe)
|
|
8192
|
+
buckets.functions.push(fe);
|
|
8193
|
+
} else if (decl.type === "ClassDeclaration") {
|
|
8194
|
+
const ce = processClassDeclaration(decl, decl.id?.name ?? "default");
|
|
8195
|
+
if (ce)
|
|
8196
|
+
buckets.classes.push(ce);
|
|
8197
|
+
}
|
|
8198
|
+
return;
|
|
8171
8199
|
}
|
|
8172
|
-
|
|
8173
|
-
|
|
8174
|
-
|
|
8175
|
-
mkdirSync7(dirname4(item.dest), { recursive: true });
|
|
8176
|
-
copyFileSync3(item.src, item.dest);
|
|
8200
|
+
if (stmt.type === "ImportDeclaration") {
|
|
8201
|
+
buckets.imports.push(processImportDeclaration(stmt));
|
|
8202
|
+
return;
|
|
8177
8203
|
}
|
|
8178
|
-
|
|
8179
|
-
|
|
8180
|
-
|
|
8181
|
-
|
|
8182
|
-
|
|
8183
|
-
log.error(`Invalid provider: ${requestedProvider}. Use auto, claude, codex, or both.`);
|
|
8184
|
-
process.exit(1);
|
|
8204
|
+
if (stmt.type === "FunctionDeclaration") {
|
|
8205
|
+
const fe = processFunctionDeclaration(stmt);
|
|
8206
|
+
if (fe)
|
|
8207
|
+
buckets.functions.push(fe);
|
|
8208
|
+
return;
|
|
8185
8209
|
}
|
|
8186
|
-
|
|
8187
|
-
|
|
8188
|
-
|
|
8189
|
-
|
|
8190
|
-
planMissingFiles(ASSET.testsTemplates, join17(cwd, "tests", "templates"), "tests/templates", plan);
|
|
8191
|
-
planMissingFiles(ASSET.ci, join17(cwd, "ci"), "ci", plan);
|
|
8192
|
-
planMissingFiles(ASSET.githubWorkflows, join17(cwd, ".github", "workflows"), ".github/workflows", plan);
|
|
8193
|
-
planMissingFiles(ASSET.cddConfig, join17(cwd, ".cdd"), ".cdd", plan);
|
|
8194
|
-
planProviderGuidance(cwd, provider, plan);
|
|
8195
|
-
log.blank();
|
|
8196
|
-
log.info(`Upgrade provider: ${provider}`);
|
|
8197
|
-
if (plan.length === 0) {
|
|
8198
|
-
log.ok("No missing cdd-kit project files found.");
|
|
8199
|
-
if (opts.migrateChanges) {
|
|
8200
|
-
log.blank();
|
|
8201
|
-
log.info("Running change migration flow...");
|
|
8202
|
-
await migrate(void 0, {
|
|
8203
|
-
all: true,
|
|
8204
|
-
dryRun: !opts.yes,
|
|
8205
|
-
enableContextGovernance: opts.enableContextGovernance
|
|
8206
|
-
});
|
|
8207
|
-
}
|
|
8208
|
-
log.blank();
|
|
8210
|
+
if (stmt.type === "ClassDeclaration") {
|
|
8211
|
+
const ce = processClassDeclaration(stmt);
|
|
8212
|
+
if (ce)
|
|
8213
|
+
buckets.classes.push(ce);
|
|
8209
8214
|
return;
|
|
8210
8215
|
}
|
|
8211
|
-
|
|
8212
|
-
|
|
8213
|
-
log.dim(` + ${item.rel.replace(/\\/g, "/")}`);
|
|
8214
|
-
if (!opts.yes) {
|
|
8215
|
-
log.blank();
|
|
8216
|
-
log.info("Dry run only. Re-run with --yes to write missing files.");
|
|
8217
|
-
if (opts.migrateChanges) {
|
|
8218
|
-
log.blank();
|
|
8219
|
-
log.info("Previewing existing change migration because --migrate-changes was requested.");
|
|
8220
|
-
await migrate(void 0, {
|
|
8221
|
-
all: true,
|
|
8222
|
-
dryRun: true,
|
|
8223
|
-
enableContextGovernance: opts.enableContextGovernance
|
|
8224
|
-
});
|
|
8225
|
-
}
|
|
8226
|
-
log.blank();
|
|
8216
|
+
if (stmt.type === "VariableDeclaration") {
|
|
8217
|
+
processVariableDeclaration(stmt, buckets.imports, buckets.constants, buckets.functions);
|
|
8227
8218
|
return;
|
|
8228
8219
|
}
|
|
8229
|
-
|
|
8230
|
-
|
|
8231
|
-
|
|
8232
|
-
|
|
8233
|
-
|
|
8234
|
-
|
|
8235
|
-
|
|
8236
|
-
}
|
|
8237
|
-
const merged = {
|
|
8238
|
-
...existing,
|
|
8239
|
-
provider,
|
|
8240
|
-
generated_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
8241
|
-
roles: existing.roles && typeof existing.roles === "object" ? existing.roles : {}
|
|
8242
|
-
};
|
|
8243
|
-
writeFileSync7(modelPolicyPath, JSON.stringify(merged, null, 2) + "\n", "utf8");
|
|
8244
|
-
}
|
|
8245
|
-
log.blank();
|
|
8246
|
-
log.ok(`Upgrade complete: ${plan.length} missing file(s) added.`);
|
|
8247
|
-
log.info("Existing project guidance and contracts were preserved.");
|
|
8248
|
-
if (opts.migrateChanges) {
|
|
8249
|
-
log.blank();
|
|
8250
|
-
log.info("Running change migration flow...");
|
|
8251
|
-
await migrate(void 0, {
|
|
8252
|
-
all: true,
|
|
8253
|
-
dryRun: false,
|
|
8254
|
-
enableContextGovernance: opts.enableContextGovernance
|
|
8255
|
-
});
|
|
8256
|
-
}
|
|
8257
|
-
log.blank();
|
|
8258
|
-
}
|
|
8259
|
-
var init_upgrade = __esm({
|
|
8260
|
-
"src/commands/upgrade.ts"() {
|
|
8261
|
-
"use strict";
|
|
8262
|
-
init_paths();
|
|
8263
|
-
init_logger();
|
|
8264
|
-
init_provider();
|
|
8265
|
-
init_migrate();
|
|
8266
|
-
}
|
|
8267
|
-
});
|
|
8268
|
-
|
|
8269
|
-
// src/code-map/yaml-writer.ts
|
|
8270
|
-
function quoteScalar(s) {
|
|
8271
|
-
if (s.startsWith(".") || YAML_RESERVED.test(s) || s.includes(" ") || s === "" || s === "null" || s === "true" || s === "false") {
|
|
8272
|
-
return `'${s.replace(/'/g, "''")}'`;
|
|
8273
|
-
}
|
|
8274
|
-
return s;
|
|
8275
|
-
}
|
|
8276
|
-
function quotePath(p) {
|
|
8277
|
-
if (YAML_RESERVED.test(p) || p.includes(" ")) {
|
|
8278
|
-
return `'${p.replace(/'/g, "''")}'`;
|
|
8279
|
-
}
|
|
8280
|
-
return p;
|
|
8281
|
-
}
|
|
8282
|
-
function renderItems(items) {
|
|
8283
|
-
if (items.length === 0)
|
|
8284
|
-
return "[]";
|
|
8285
|
-
return `[${items.map(quoteScalar).join(", ")}]`;
|
|
8286
|
-
}
|
|
8287
|
-
function truncateDecorator(d, max = 80) {
|
|
8288
|
-
const cleaned = d.replace(/\r?\n/g, " ");
|
|
8289
|
-
return cleaned.length <= max ? cleaned : cleaned.slice(0, max) + "...";
|
|
8290
|
-
}
|
|
8291
|
-
function renderClass(c) {
|
|
8292
|
-
const lines = [];
|
|
8293
|
-
lines.push(` - name: ${c.name}`);
|
|
8294
|
-
lines.push(` lines: ${c.lines[0]}-${c.lines[1]}`);
|
|
8295
|
-
if (c.methods.length > 0) {
|
|
8296
|
-
lines.push(" methods:");
|
|
8297
|
-
for (const m of c.methods) {
|
|
8298
|
-
const asyncPrefix = m.async ? "async " : "";
|
|
8299
|
-
lines.push(` - { name: ${asyncPrefix}${m.name}, lines: ${m.lines[0]}-${m.lines[1]} }`);
|
|
8300
|
-
}
|
|
8301
|
-
}
|
|
8302
|
-
return lines;
|
|
8303
|
-
}
|
|
8304
|
-
function renderFunction(f) {
|
|
8305
|
-
const asyncPrefix = f.async ? "async " : "";
|
|
8306
|
-
let comment = "";
|
|
8307
|
-
if (f.decorators.length > 0) {
|
|
8308
|
-
const truncated = truncateDecorator(f.decorators[0]);
|
|
8309
|
-
comment = ` # @${truncated}`;
|
|
8310
|
-
}
|
|
8311
|
-
return ` - { name: ${asyncPrefix}${f.name}, lines: ${f.lines[0]}-${f.lines[1]} }${comment}`;
|
|
8312
|
-
}
|
|
8313
|
-
function renderTypeDef(t) {
|
|
8314
|
-
const exportedSuffix = t.exported ? "" : " # local";
|
|
8315
|
-
return ` - { name: ${t.name}, lines: ${t.lines[0]}-${t.lines[1]} }${exportedSuffix}`;
|
|
8316
|
-
}
|
|
8317
|
-
function renderEnum(e) {
|
|
8318
|
-
const lines = [];
|
|
8319
|
-
const exportedSuffix = e.exported ? "" : " # local";
|
|
8320
|
-
lines.push(` - name: ${e.name}`);
|
|
8321
|
-
lines.push(` lines: ${e.lines[0]}-${e.lines[1]}${exportedSuffix}`);
|
|
8322
|
-
if (e.members.length > 0) {
|
|
8323
|
-
lines.push(` members: [${e.members.join(", ")}]`);
|
|
8324
|
-
}
|
|
8325
|
-
return lines;
|
|
8326
|
-
}
|
|
8327
|
-
function renderYaml(entries, opts) {
|
|
8328
|
-
const now = (/* @__PURE__ */ new Date()).toISOString().replace(/\.\d+Z$/, "Z");
|
|
8329
|
-
const totalSrc = entries.reduce((s, e) => s + e.total_lines, 0);
|
|
8330
|
-
const bodyLines = [];
|
|
8331
|
-
for (let i = 0; i < entries.length; i++) {
|
|
8332
|
-
const e = entries[i];
|
|
8333
|
-
if (i > 0)
|
|
8334
|
-
bodyLines.push("");
|
|
8335
|
-
const pathKey = quotePath(e.path);
|
|
8336
|
-
bodyLines.push(`${pathKey}: # ${e.total_lines} lines`);
|
|
8337
|
-
if (e.imports.length > 0) {
|
|
8338
|
-
bodyLines.push(" imports:");
|
|
8339
|
-
for (const imp of e.imports) {
|
|
8340
|
-
const mod = quoteScalar(imp.module);
|
|
8341
|
-
bodyLines.push(` - { module: ${mod}, items: ${renderItems(imp.items)}, line: ${imp.line} }`);
|
|
8342
|
-
}
|
|
8343
|
-
}
|
|
8344
|
-
if (e.constants.length > 0) {
|
|
8345
|
-
bodyLines.push(" constants:");
|
|
8346
|
-
for (const c of e.constants) {
|
|
8347
|
-
bodyLines.push(` - { name: ${c.name}, line: ${c.line} }`);
|
|
8348
|
-
}
|
|
8349
|
-
}
|
|
8350
|
-
if (e.classes.length > 0) {
|
|
8351
|
-
bodyLines.push(" classes:");
|
|
8352
|
-
for (const c of e.classes) {
|
|
8353
|
-
bodyLines.push(...renderClass(c));
|
|
8354
|
-
}
|
|
8355
|
-
}
|
|
8356
|
-
if (e.functions.length > 0) {
|
|
8357
|
-
bodyLines.push(" functions:");
|
|
8358
|
-
for (const f of e.functions) {
|
|
8359
|
-
bodyLines.push(renderFunction(f));
|
|
8360
|
-
}
|
|
8361
|
-
}
|
|
8362
|
-
if (e.interfaces && e.interfaces.length > 0) {
|
|
8363
|
-
bodyLines.push(" interfaces:");
|
|
8364
|
-
for (const t of e.interfaces) {
|
|
8365
|
-
bodyLines.push(renderTypeDef(t));
|
|
8366
|
-
}
|
|
8220
|
+
if (extractTsTypes) {
|
|
8221
|
+
const anyStmt = stmt;
|
|
8222
|
+
if (anyStmt.type === "TSInterfaceDeclaration") {
|
|
8223
|
+
const e = processTsInterfaceDeclaration(anyStmt, exportedFromWrapper);
|
|
8224
|
+
if (e)
|
|
8225
|
+
buckets.interfaces.push(e);
|
|
8226
|
+
return;
|
|
8367
8227
|
}
|
|
8368
|
-
if (
|
|
8369
|
-
|
|
8370
|
-
|
|
8371
|
-
|
|
8372
|
-
|
|
8228
|
+
if (anyStmt.type === "TSTypeAliasDeclaration") {
|
|
8229
|
+
const e = processTsTypeAliasDeclaration(anyStmt, exportedFromWrapper);
|
|
8230
|
+
if (e)
|
|
8231
|
+
buckets.types.push(e);
|
|
8232
|
+
return;
|
|
8373
8233
|
}
|
|
8374
|
-
if (
|
|
8375
|
-
|
|
8376
|
-
|
|
8377
|
-
|
|
8378
|
-
|
|
8234
|
+
if (anyStmt.type === "TSEnumDeclaration") {
|
|
8235
|
+
const e = processTsEnumDeclaration(anyStmt, exportedFromWrapper);
|
|
8236
|
+
if (e)
|
|
8237
|
+
buckets.enums.push(e);
|
|
8238
|
+
return;
|
|
8379
8239
|
}
|
|
8380
8240
|
}
|
|
8381
|
-
|
|
8382
|
-
|
|
8383
|
-
|
|
8384
|
-
|
|
8385
|
-
|
|
8386
|
-
|
|
8387
|
-
];
|
|
8388
|
-
return [...header, ...bodyLines].join("\n") + "\n";
|
|
8389
|
-
}
|
|
8390
|
-
var YAML_RESERVED;
|
|
8391
|
-
var init_yaml_writer = __esm({
|
|
8392
|
-
"src/code-map/yaml-writer.ts"() {
|
|
8393
|
-
"use strict";
|
|
8394
|
-
YAML_RESERVED = /[:[\]{},&*!|>'"%@`#]/;
|
|
8395
|
-
}
|
|
8396
|
-
});
|
|
8397
|
-
|
|
8398
|
-
// src/code-map/orchestrator.ts
|
|
8399
|
-
async function scanInProcess(scanner, absolutePaths, repoRoot) {
|
|
8400
|
-
const entries = [];
|
|
8401
|
-
const warnings = [];
|
|
8402
|
-
for (const absPath of absolutePaths) {
|
|
8403
|
-
try {
|
|
8404
|
-
const entry = await scanner.scan(absPath, repoRoot);
|
|
8405
|
-
if (entry === null) {
|
|
8406
|
-
const rel = absPath.replace(/\\/g, "/").replace(repoRoot.replace(/\\/g, "/") + "/", "");
|
|
8407
|
-
warnings.push({ path: rel, message: "parse error (scanner returned null)" });
|
|
8408
|
-
} else {
|
|
8409
|
-
entries.push(entry);
|
|
8241
|
+
if (stmt.type === "ExpressionStatement") {
|
|
8242
|
+
const expr = stmt.expression;
|
|
8243
|
+
if (isCallExpression(expr, "require")) {
|
|
8244
|
+
const mod = extractRequireModule(expr);
|
|
8245
|
+
if (mod) {
|
|
8246
|
+
buckets.imports.push({ module: mod, items: [], line: stmt.loc?.start.line ?? 1 });
|
|
8410
8247
|
}
|
|
8411
|
-
} catch (err) {
|
|
8412
|
-
const rel = absPath.replace(/\\/g, "/").replace(repoRoot.replace(/\\/g, "/") + "/", "");
|
|
8413
|
-
warnings.push({ path: rel, message: `IO error: ${err.message}` });
|
|
8414
8248
|
}
|
|
8415
8249
|
}
|
|
8416
|
-
return { entries, warnings };
|
|
8417
8250
|
}
|
|
8418
|
-
function
|
|
8419
|
-
|
|
8420
|
-
|
|
8421
|
-
|
|
8422
|
-
|
|
8423
|
-
|
|
8424
|
-
out[ext] = [];
|
|
8425
|
-
out[ext].push(f);
|
|
8251
|
+
function parseAndExtract(source, opts) {
|
|
8252
|
+
let ast;
|
|
8253
|
+
try {
|
|
8254
|
+
ast = parseSourceWithPlugins(source, opts.plugins);
|
|
8255
|
+
} catch {
|
|
8256
|
+
return null;
|
|
8426
8257
|
}
|
|
8427
|
-
|
|
8428
|
-
|
|
8429
|
-
|
|
8430
|
-
|
|
8431
|
-
|
|
8432
|
-
|
|
8258
|
+
const buckets = {
|
|
8259
|
+
imports: [],
|
|
8260
|
+
constants: [],
|
|
8261
|
+
functions: [],
|
|
8262
|
+
classes: [],
|
|
8263
|
+
interfaces: [],
|
|
8264
|
+
types: [],
|
|
8265
|
+
enums: []
|
|
8266
|
+
};
|
|
8267
|
+
for (const stmt of ast.program.body) {
|
|
8268
|
+
processStatement(stmt, buckets, !!opts.extractTsTypes);
|
|
8433
8269
|
}
|
|
8434
|
-
|
|
8435
|
-
|
|
8436
|
-
// src/code-map/scanners/common.ts
|
|
8437
|
-
import { relative as relative4 } from "path";
|
|
8438
|
-
function canonicalRelPath(absolutePath, repoRoot) {
|
|
8439
|
-
const rel = relative4(repoRoot, absolutePath);
|
|
8440
|
-
return rel.replace(/\\/g, "/").normalize("NFC");
|
|
8270
|
+
return buckets;
|
|
8441
8271
|
}
|
|
8442
|
-
function
|
|
8443
|
-
|
|
8272
|
+
function countSourceLines(source) {
|
|
8273
|
+
if (source === "")
|
|
8274
|
+
return 0;
|
|
8275
|
+
return source.split(/\r?\n/).length - (source.endsWith("\n") || source.endsWith("\r\n") ? 1 : 0);
|
|
8444
8276
|
}
|
|
8445
|
-
function
|
|
8446
|
-
const
|
|
8447
|
-
|
|
8277
|
+
function parseJsSource(source, relPath) {
|
|
8278
|
+
const total_lines = countSourceLines(source);
|
|
8279
|
+
const r = parseAndExtract(source, { plugins: JS_PLUGINS, extractTsTypes: false });
|
|
8280
|
+
if (!r)
|
|
8281
|
+
return null;
|
|
8282
|
+
return {
|
|
8283
|
+
path: relPath,
|
|
8284
|
+
total_lines,
|
|
8285
|
+
imports: r.imports,
|
|
8286
|
+
constants: r.constants,
|
|
8287
|
+
classes: r.classes,
|
|
8288
|
+
functions: r.functions
|
|
8289
|
+
};
|
|
8448
8290
|
}
|
|
8449
|
-
var
|
|
8450
|
-
|
|
8451
|
-
|
|
8452
|
-
|
|
8291
|
+
var COMMON_PLUGINS, JS_PLUGINS, JavaScriptScanner, jsScanner;
|
|
8292
|
+
var init_javascript = __esm({
|
|
8293
|
+
"src/code-map/scanners/javascript.ts"() {
|
|
8294
|
+
"use strict";
|
|
8295
|
+
init_common();
|
|
8296
|
+
COMMON_PLUGINS = [
|
|
8297
|
+
"classProperties",
|
|
8298
|
+
"classPrivateProperties",
|
|
8299
|
+
"classPrivateMethods",
|
|
8300
|
+
"decorators-legacy",
|
|
8301
|
+
"topLevelAwait",
|
|
8302
|
+
"dynamicImport",
|
|
8303
|
+
"optionalChaining",
|
|
8304
|
+
"nullishCoalescingOperator",
|
|
8305
|
+
"objectRestSpread",
|
|
8306
|
+
"asyncGenerators",
|
|
8307
|
+
"numericSeparator",
|
|
8308
|
+
"logicalAssignment"
|
|
8309
|
+
];
|
|
8310
|
+
JS_PLUGINS = ["jsx", ...COMMON_PLUGINS];
|
|
8311
|
+
JavaScriptScanner = class {
|
|
8312
|
+
extensions = [".js"];
|
|
8313
|
+
async scan(absolutePath, repoRoot) {
|
|
8314
|
+
let source;
|
|
8315
|
+
try {
|
|
8316
|
+
source = readFileSync11(absolutePath, "utf8");
|
|
8317
|
+
} catch (err) {
|
|
8318
|
+
throw err;
|
|
8319
|
+
}
|
|
8320
|
+
if (isBinary(source)) {
|
|
8321
|
+
return null;
|
|
8322
|
+
}
|
|
8323
|
+
const relPath = canonicalRelPath(absolutePath, repoRoot);
|
|
8324
|
+
return parseJsSource(source, relPath);
|
|
8325
|
+
}
|
|
8326
|
+
};
|
|
8327
|
+
jsScanner = new JavaScriptScanner();
|
|
8328
|
+
}
|
|
8453
8329
|
});
|
|
8454
8330
|
|
|
8455
|
-
// src/code-map/scanners/
|
|
8456
|
-
var
|
|
8457
|
-
__export(
|
|
8458
|
-
|
|
8331
|
+
// src/code-map/scanners/typescript.ts
|
|
8332
|
+
var typescript_exports = {};
|
|
8333
|
+
__export(typescript_exports, {
|
|
8334
|
+
tsScanner: () => tsScanner
|
|
8459
8335
|
});
|
|
8460
|
-
import {
|
|
8461
|
-
|
|
8462
|
-
|
|
8463
|
-
|
|
8464
|
-
function detectPython2() {
|
|
8465
|
-
for (const candidate of ["python3", "python"]) {
|
|
8466
|
-
try {
|
|
8467
|
-
const result = spawnSync2(candidate, ["--version"], {
|
|
8468
|
-
encoding: "utf8",
|
|
8469
|
-
timeout: 5e3
|
|
8470
|
-
});
|
|
8471
|
-
if (result.status === 0)
|
|
8472
|
-
return candidate;
|
|
8473
|
-
} catch {
|
|
8474
|
-
}
|
|
8475
|
-
}
|
|
8476
|
-
return null;
|
|
8477
|
-
}
|
|
8478
|
-
var TIMEOUT_MS, PythonScanner, pythonScanner;
|
|
8479
|
-
var init_python = __esm({
|
|
8480
|
-
"src/code-map/scanners/python.ts"() {
|
|
8336
|
+
import { readFileSync as readFileSync12 } from "fs";
|
|
8337
|
+
var TypeScriptScanner, tsScanner;
|
|
8338
|
+
var init_typescript = __esm({
|
|
8339
|
+
"src/code-map/scanners/typescript.ts"() {
|
|
8481
8340
|
"use strict";
|
|
8482
|
-
init_paths();
|
|
8483
8341
|
init_common();
|
|
8484
|
-
|
|
8485
|
-
|
|
8486
|
-
extensions = [".
|
|
8487
|
-
|
|
8488
|
-
|
|
8489
|
-
|
|
8490
|
-
|
|
8342
|
+
init_javascript();
|
|
8343
|
+
TypeScriptScanner = class {
|
|
8344
|
+
extensions = [".ts", ".tsx"];
|
|
8345
|
+
async scan(absolutePath, repoRoot) {
|
|
8346
|
+
let source;
|
|
8347
|
+
try {
|
|
8348
|
+
source = readFileSync12(absolutePath, "utf8");
|
|
8349
|
+
} catch (err) {
|
|
8350
|
+
throw err;
|
|
8491
8351
|
}
|
|
8492
|
-
|
|
8352
|
+
if (isBinary(source)) {
|
|
8353
|
+
return null;
|
|
8354
|
+
}
|
|
8355
|
+
const isTsx = absolutePath.toLowerCase().endsWith(".tsx");
|
|
8356
|
+
const plugins = isTsx ? ["typescript", "jsx", ...COMMON_PLUGINS] : ["typescript", ...COMMON_PLUGINS];
|
|
8357
|
+
const r = parseAndExtract(source, { plugins, extractTsTypes: true });
|
|
8358
|
+
if (!r)
|
|
8359
|
+
return null;
|
|
8360
|
+
const relPath = canonicalRelPath(absolutePath, repoRoot);
|
|
8361
|
+
const total_lines = countSourceLines(source);
|
|
8362
|
+
return {
|
|
8363
|
+
path: relPath,
|
|
8364
|
+
total_lines,
|
|
8365
|
+
imports: r.imports,
|
|
8366
|
+
constants: r.constants,
|
|
8367
|
+
classes: r.classes,
|
|
8368
|
+
functions: r.functions,
|
|
8369
|
+
interfaces: r.interfaces,
|
|
8370
|
+
types: r.types,
|
|
8371
|
+
enums: r.enums
|
|
8372
|
+
};
|
|
8493
8373
|
}
|
|
8374
|
+
};
|
|
8375
|
+
tsScanner = new TypeScriptScanner();
|
|
8376
|
+
}
|
|
8377
|
+
});
|
|
8378
|
+
|
|
8379
|
+
// src/code-map/scanners/vue.ts
|
|
8380
|
+
var vue_exports = {};
|
|
8381
|
+
__export(vue_exports, {
|
|
8382
|
+
vueScanner: () => vueScanner
|
|
8383
|
+
});
|
|
8384
|
+
import { readFileSync as readFileSync13 } from "fs";
|
|
8385
|
+
import { parse as parse2 } from "@vue/compiler-sfc";
|
|
8386
|
+
var VueScanner, vueScanner;
|
|
8387
|
+
var init_vue = __esm({
|
|
8388
|
+
"src/code-map/scanners/vue.ts"() {
|
|
8389
|
+
"use strict";
|
|
8390
|
+
init_common();
|
|
8391
|
+
init_javascript();
|
|
8392
|
+
VueScanner = class {
|
|
8393
|
+
extensions = [".vue"];
|
|
8494
8394
|
async scan(absolutePath, repoRoot) {
|
|
8495
|
-
|
|
8496
|
-
return result.entries[0] ?? null;
|
|
8497
|
-
}
|
|
8498
|
-
async scanBatch(absolutePaths, repoRoot) {
|
|
8499
|
-
const interpreter = this.getInterpreter();
|
|
8500
|
-
const entries = [];
|
|
8501
|
-
const warnings = [];
|
|
8502
|
-
if (!interpreter) {
|
|
8503
|
-
const count = absolutePaths.length;
|
|
8504
|
-
warnings.push({
|
|
8505
|
-
path: "",
|
|
8506
|
-
message: `python interpreter not found on PATH; skipping ${count} .py file${count === 1 ? "" : "s"}`
|
|
8507
|
-
});
|
|
8508
|
-
return { entries, warnings };
|
|
8509
|
-
}
|
|
8510
|
-
const scriptPath = ASSET.codeMapPython;
|
|
8511
|
-
const rand = Math.random().toString(36).slice(2);
|
|
8512
|
-
const listFile = join18(tmpdir(), `cdd-codemap-${process.pid}-${rand}.txt`);
|
|
8513
|
-
writeFileSync8(listFile, absolutePaths.join("\n") + "\n", "utf8");
|
|
8514
|
-
let stdout = "";
|
|
8515
|
-
let stderr = "";
|
|
8516
|
-
let exitCode = 0;
|
|
8395
|
+
let source;
|
|
8517
8396
|
try {
|
|
8518
|
-
|
|
8519
|
-
|
|
8520
|
-
|
|
8521
|
-
{
|
|
8522
|
-
encoding: "utf8",
|
|
8523
|
-
timeout: TIMEOUT_MS,
|
|
8524
|
-
maxBuffer: 50 * 1024 * 1024
|
|
8525
|
-
// 50MB
|
|
8526
|
-
}
|
|
8527
|
-
);
|
|
8528
|
-
stdout = result.stdout ?? "";
|
|
8529
|
-
stderr = result.stderr ?? "";
|
|
8530
|
-
exitCode = result.status ?? -1;
|
|
8531
|
-
if (result.error) {
|
|
8532
|
-
const errMsg = result.error.message ?? String(result.error);
|
|
8533
|
-
if (errMsg.includes("ENOENT")) {
|
|
8534
|
-
warnings.push({
|
|
8535
|
-
path: "",
|
|
8536
|
-
message: `python interpreter not found (ENOENT); skipping ${absolutePaths.length} .py file(s)`
|
|
8537
|
-
});
|
|
8538
|
-
return { entries, warnings };
|
|
8539
|
-
}
|
|
8540
|
-
if (errMsg.includes("ETIMEDOUT") || errMsg.includes("timeout")) {
|
|
8541
|
-
warnings.push({
|
|
8542
|
-
path: "",
|
|
8543
|
-
message: `python scanner timed out after ${TIMEOUT_MS}ms; skipping .py files`
|
|
8544
|
-
});
|
|
8545
|
-
return { entries, warnings };
|
|
8546
|
-
}
|
|
8547
|
-
warnings.push({
|
|
8548
|
-
path: "",
|
|
8549
|
-
message: `python scanner error: ${errMsg}; skipping .py files`
|
|
8550
|
-
});
|
|
8551
|
-
return { entries, warnings };
|
|
8552
|
-
}
|
|
8553
|
-
} finally {
|
|
8554
|
-
try {
|
|
8555
|
-
unlinkSync(listFile);
|
|
8556
|
-
} catch {
|
|
8557
|
-
}
|
|
8397
|
+
source = readFileSync13(absolutePath, "utf8");
|
|
8398
|
+
} catch (err) {
|
|
8399
|
+
throw err;
|
|
8558
8400
|
}
|
|
8559
|
-
if (
|
|
8560
|
-
|
|
8561
|
-
path: "",
|
|
8562
|
-
message: "python interpreter is < 3.9 (need ast.unparse); skipping .py files"
|
|
8563
|
-
});
|
|
8564
|
-
return { entries, warnings };
|
|
8401
|
+
if (isBinary(source)) {
|
|
8402
|
+
return null;
|
|
8565
8403
|
}
|
|
8566
|
-
|
|
8567
|
-
|
|
8568
|
-
|
|
8404
|
+
const relPath = canonicalRelPath(absolutePath, repoRoot);
|
|
8405
|
+
const total_lines = source.split(/\r?\n/).length - (source.endsWith("\n") || source.endsWith("\r\n") ? 1 : 0);
|
|
8406
|
+
const { descriptor } = parse2(source, { filename: absolutePath });
|
|
8407
|
+
const allImports = [];
|
|
8408
|
+
const allConstants = [];
|
|
8409
|
+
const allFunctions = [];
|
|
8410
|
+
const allClasses = [];
|
|
8411
|
+
const warnings = [];
|
|
8412
|
+
const scriptBlocks = [descriptor.script, descriptor.scriptSetup].filter(Boolean);
|
|
8413
|
+
if (scriptBlocks.length === 0) {
|
|
8414
|
+
return {
|
|
8415
|
+
path: relPath,
|
|
8416
|
+
total_lines,
|
|
8417
|
+
imports: [],
|
|
8418
|
+
constants: [],
|
|
8419
|
+
classes: [],
|
|
8420
|
+
functions: []
|
|
8421
|
+
};
|
|
8422
|
+
}
|
|
8423
|
+
for (const block of scriptBlocks) {
|
|
8424
|
+
if (!block)
|
|
8569
8425
|
continue;
|
|
8570
|
-
|
|
8571
|
-
|
|
8572
|
-
|
|
8573
|
-
|
|
8426
|
+
if (block.lang === "ts" || block.lang === "tsx") {
|
|
8427
|
+
warnings.push({
|
|
8428
|
+
path: relPath,
|
|
8429
|
+
message: `skipping <script lang=${block.lang}> block (TypeScript not supported in v1)`
|
|
8430
|
+
});
|
|
8574
8431
|
continue;
|
|
8575
8432
|
}
|
|
8576
|
-
|
|
8577
|
-
|
|
8433
|
+
const blockContent = block.content;
|
|
8434
|
+
const lineOffset = block.loc.start.line;
|
|
8435
|
+
const scriptEntry = parseJsSource(blockContent, relPath);
|
|
8436
|
+
if (!scriptEntry) {
|
|
8437
|
+
warnings.push({ path: relPath, message: "parse error in script block" });
|
|
8578
8438
|
continue;
|
|
8579
8439
|
}
|
|
8580
|
-
const
|
|
8581
|
-
|
|
8582
|
-
|
|
8583
|
-
|
|
8584
|
-
|
|
8585
|
-
|
|
8586
|
-
|
|
8587
|
-
|
|
8588
|
-
|
|
8589
|
-
|
|
8590
|
-
|
|
8591
|
-
|
|
8592
|
-
|
|
8593
|
-
|
|
8594
|
-
|
|
8595
|
-
|
|
8596
|
-
|
|
8597
|
-
|
|
8598
|
-
|
|
8599
|
-
|
|
8600
|
-
|
|
8601
|
-
|
|
8602
|
-
|
|
8603
|
-
if (exitCode === 2) {
|
|
8604
|
-
warnings.push({
|
|
8605
|
-
path: "",
|
|
8606
|
-
message: `python scanner exited with code 2 (fatal); partial results only. stderr: ${stderr.slice(0, 200)}`
|
|
8607
|
-
});
|
|
8440
|
+
const offset = lineOffset - 1;
|
|
8441
|
+
for (const imp of scriptEntry.imports) {
|
|
8442
|
+
allImports.push({ ...imp, line: imp.line + offset });
|
|
8443
|
+
}
|
|
8444
|
+
for (const c of scriptEntry.constants) {
|
|
8445
|
+
allConstants.push({ ...c, line: c.line + offset });
|
|
8446
|
+
}
|
|
8447
|
+
for (const fn of scriptEntry.functions) {
|
|
8448
|
+
allFunctions.push({
|
|
8449
|
+
...fn,
|
|
8450
|
+
lines: [fn.lines[0] + offset, fn.lines[1] + offset]
|
|
8451
|
+
});
|
|
8452
|
+
}
|
|
8453
|
+
for (const cls of scriptEntry.classes) {
|
|
8454
|
+
allClasses.push({
|
|
8455
|
+
...cls,
|
|
8456
|
+
lines: [cls.lines[0] + offset, cls.lines[1] + offset],
|
|
8457
|
+
methods: cls.methods.map((m) => ({
|
|
8458
|
+
...m,
|
|
8459
|
+
lines: [m.lines[0] + offset, m.lines[1] + offset]
|
|
8460
|
+
}))
|
|
8461
|
+
});
|
|
8462
|
+
}
|
|
8608
8463
|
}
|
|
8609
|
-
return {
|
|
8464
|
+
return {
|
|
8465
|
+
path: relPath,
|
|
8466
|
+
total_lines,
|
|
8467
|
+
imports: allImports,
|
|
8468
|
+
constants: allConstants,
|
|
8469
|
+
classes: allClasses,
|
|
8470
|
+
functions: allFunctions
|
|
8471
|
+
};
|
|
8610
8472
|
}
|
|
8611
8473
|
};
|
|
8612
|
-
|
|
8474
|
+
vueScanner = new VueScanner();
|
|
8613
8475
|
}
|
|
8614
8476
|
});
|
|
8615
8477
|
|
|
8616
|
-
// src/code-map
|
|
8617
|
-
var
|
|
8618
|
-
__export(
|
|
8619
|
-
|
|
8620
|
-
|
|
8621
|
-
jsScanner: () => jsScanner,
|
|
8622
|
-
parseAndExtract: () => parseAndExtract,
|
|
8623
|
-
parseJsSource: () => parseJsSource,
|
|
8624
|
-
parseSourceWithPlugins: () => parseSourceWithPlugins
|
|
8478
|
+
// src/commands/code-map.ts
|
|
8479
|
+
var code_map_exports = {};
|
|
8480
|
+
__export(code_map_exports, {
|
|
8481
|
+
codeMap: () => codeMap,
|
|
8482
|
+
computeSourcesDigest: () => computeSourcesDigest
|
|
8625
8483
|
});
|
|
8626
|
-
import { readFileSync as readFileSync14 } from "fs";
|
|
8627
|
-
import {
|
|
8628
|
-
|
|
8629
|
-
|
|
8630
|
-
|
|
8631
|
-
|
|
8632
|
-
|
|
8633
|
-
|
|
8634
|
-
|
|
8484
|
+
import { existsSync as existsSync11, mkdirSync as mkdirSync5, readFileSync as readFileSync14, writeFileSync as writeFileSync6 } from "fs";
|
|
8485
|
+
import { resolve, dirname as dirname4, relative as relative5 } from "path";
|
|
8486
|
+
import { createHash as createHash5 } from "crypto";
|
|
8487
|
+
import { createRequire } from "module";
|
|
8488
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
8489
|
+
import { join as join14 } from "path";
|
|
8490
|
+
function computeSourcesDigest(absolutePaths, cwd) {
|
|
8491
|
+
const lines = absolutePaths.slice().sort().map((p) => {
|
|
8492
|
+
const rel = relative5(cwd, p).replace(/\\/g, "/");
|
|
8493
|
+
const contentHash = sha256OfFileNormalized(p) || "missing";
|
|
8494
|
+
return `${rel}:${contentHash}`;
|
|
8635
8495
|
});
|
|
8496
|
+
return createHash5("sha256").update(lines.join("\n")).digest("hex");
|
|
8636
8497
|
}
|
|
8637
|
-
function
|
|
8638
|
-
|
|
8639
|
-
|
|
8640
|
-
|
|
8641
|
-
|
|
8642
|
-
|
|
8643
|
-
|
|
8644
|
-
|
|
8645
|
-
return
|
|
8646
|
-
|
|
8647
|
-
|
|
8648
|
-
|
|
8649
|
-
const
|
|
8650
|
-
|
|
8651
|
-
|
|
8652
|
-
|
|
8653
|
-
|
|
8654
|
-
|
|
8655
|
-
|
|
8656
|
-
|
|
8657
|
-
return [start, end];
|
|
8658
|
-
}
|
|
8659
|
-
function processImportDeclaration(node) {
|
|
8660
|
-
const items = [];
|
|
8661
|
-
for (const spec of node.specifiers) {
|
|
8662
|
-
if (spec.type === "ImportDefaultSpecifier") {
|
|
8663
|
-
items.push(`default:${spec.local.name}`);
|
|
8664
|
-
} else if (spec.type === "ImportNamespaceSpecifier") {
|
|
8665
|
-
items.push(`*:${spec.local.name}`);
|
|
8666
|
-
} else if (spec.type === "ImportSpecifier") {
|
|
8667
|
-
const imported = spec.imported;
|
|
8668
|
-
items.push(imported.type === "Identifier" ? imported.name : spec.local.name);
|
|
8498
|
+
async function codeMap(opts) {
|
|
8499
|
+
const root = resolve(process.cwd(), opts.path);
|
|
8500
|
+
const start = Date.now();
|
|
8501
|
+
let cfg;
|
|
8502
|
+
try {
|
|
8503
|
+
cfg = loadCodeMapConfig(root);
|
|
8504
|
+
} catch (err) {
|
|
8505
|
+
log.error(`code-map: ${err.message}`);
|
|
8506
|
+
return 1;
|
|
8507
|
+
}
|
|
8508
|
+
const include = [...cfg.include, ...opts.include];
|
|
8509
|
+
const exclude = [...cfg.exclude, ...opts.exclude];
|
|
8510
|
+
const files = walkRepo(root, { include, exclude });
|
|
8511
|
+
const buckets = bucketByExtension(files);
|
|
8512
|
+
const result = { entries: [], warnings: [] };
|
|
8513
|
+
const tasks = [];
|
|
8514
|
+
if (buckets[".py"]?.length) {
|
|
8515
|
+
const { pythonScanner: pythonScanner2 } = await Promise.resolve().then(() => (init_python(), python_exports));
|
|
8516
|
+
if (pythonScanner2.scanBatch) {
|
|
8517
|
+
tasks.push(pythonScanner2.scanBatch(buckets[".py"], root));
|
|
8669
8518
|
}
|
|
8670
8519
|
}
|
|
8671
|
-
|
|
8672
|
-
|
|
8673
|
-
|
|
8674
|
-
|
|
8675
|
-
|
|
8676
|
-
|
|
8677
|
-
|
|
8678
|
-
|
|
8679
|
-
|
|
8680
|
-
|
|
8681
|
-
|
|
8682
|
-
|
|
8683
|
-
|
|
8684
|
-
|
|
8685
|
-
|
|
8686
|
-
|
|
8687
|
-
|
|
8688
|
-
|
|
8689
|
-
|
|
8690
|
-
|
|
8691
|
-
|
|
8692
|
-
|
|
8693
|
-
for (const
|
|
8694
|
-
|
|
8695
|
-
|
|
8696
|
-
|
|
8697
|
-
|
|
8698
|
-
|
|
8699
|
-
|
|
8700
|
-
|
|
8701
|
-
|
|
8702
|
-
|
|
8703
|
-
}
|
|
8704
|
-
methods.push({
|
|
8705
|
-
name: methodName,
|
|
8706
|
-
lines: getLineRange(m),
|
|
8707
|
-
async: m.async
|
|
8520
|
+
const jsFiles = [
|
|
8521
|
+
...buckets[".js"] ?? [],
|
|
8522
|
+
...buckets[".jsx"] ?? [],
|
|
8523
|
+
...buckets[".mjs"] ?? [],
|
|
8524
|
+
...buckets[".cjs"] ?? []
|
|
8525
|
+
];
|
|
8526
|
+
if (jsFiles.length) {
|
|
8527
|
+
const { jsScanner: jsScanner2 } = await Promise.resolve().then(() => (init_javascript(), javascript_exports));
|
|
8528
|
+
tasks.push(scanInProcess(jsScanner2, jsFiles, root));
|
|
8529
|
+
}
|
|
8530
|
+
const tsFiles = [
|
|
8531
|
+
...buckets[".ts"] ?? [],
|
|
8532
|
+
...buckets[".tsx"] ?? []
|
|
8533
|
+
];
|
|
8534
|
+
if (tsFiles.length) {
|
|
8535
|
+
const { tsScanner: tsScanner2 } = await Promise.resolve().then(() => (init_typescript(), typescript_exports));
|
|
8536
|
+
tasks.push(scanInProcess(tsScanner2, tsFiles, root));
|
|
8537
|
+
}
|
|
8538
|
+
if (buckets[".vue"]?.length) {
|
|
8539
|
+
const { vueScanner: vueScanner2 } = await Promise.resolve().then(() => (init_vue(), vue_exports));
|
|
8540
|
+
tasks.push(scanInProcess(vueScanner2, buckets[".vue"], root));
|
|
8541
|
+
}
|
|
8542
|
+
for (const r of await Promise.all(tasks)) {
|
|
8543
|
+
result.entries.push(...r.entries);
|
|
8544
|
+
result.warnings.push(...r.warnings);
|
|
8545
|
+
}
|
|
8546
|
+
result.entries.sort((a, b) => a.path < b.path ? -1 : a.path > b.path ? 1 : 0);
|
|
8547
|
+
for (const e of result.entries) {
|
|
8548
|
+
if (e.total_lines > opts.maxLines) {
|
|
8549
|
+
result.warnings.push({
|
|
8550
|
+
path: e.path,
|
|
8551
|
+
message: `file exceeds --max-lines (${e.total_lines} > ${opts.maxLines})`
|
|
8708
8552
|
});
|
|
8709
8553
|
}
|
|
8710
8554
|
}
|
|
8711
|
-
|
|
8712
|
-
|
|
8713
|
-
|
|
8714
|
-
|
|
8715
|
-
};
|
|
8716
|
-
|
|
8717
|
-
|
|
8718
|
-
|
|
8719
|
-
|
|
8720
|
-
|
|
8721
|
-
|
|
8722
|
-
|
|
8723
|
-
|
|
8724
|
-
|
|
8725
|
-
|
|
8726
|
-
|
|
8727
|
-
}
|
|
8728
|
-
|
|
8729
|
-
}
|
|
8730
|
-
if (isAllCapsConst(varName) && init2 !== null && init2 !== void 0) {
|
|
8731
|
-
constants.push({ name: varName, line: node.loc?.start.line ?? 1 });
|
|
8732
|
-
continue;
|
|
8733
|
-
}
|
|
8734
|
-
if (init2 && (init2.type === "ArrowFunctionExpression" || init2.type === "FunctionExpression")) {
|
|
8735
|
-
functions.push({
|
|
8736
|
-
name: varName,
|
|
8737
|
-
lines: getLineRange(node),
|
|
8738
|
-
decorators: [],
|
|
8739
|
-
async: init2.async
|
|
8740
|
-
});
|
|
8741
|
-
} else if (init2 && init2.type === "CallExpression" && /^[A-Z]/.test(varName)) {
|
|
8742
|
-
functions.push({
|
|
8743
|
-
name: varName,
|
|
8744
|
-
lines: getLineRange(node),
|
|
8745
|
-
decorators: [],
|
|
8746
|
-
async: false
|
|
8747
|
-
});
|
|
8555
|
+
const sourcesDigest = computeSourcesDigest(files, root);
|
|
8556
|
+
const yamlBody = renderYaml(result.entries, {
|
|
8557
|
+
generator: `cdd-kit ${_pkg.version}`,
|
|
8558
|
+
sourcesDigest
|
|
8559
|
+
});
|
|
8560
|
+
const totalSrc = result.entries.reduce((s, e) => s + e.total_lines, 0);
|
|
8561
|
+
const mapLines = yamlBody.split("\n").length;
|
|
8562
|
+
const compression = totalSrc === 0 ? 0 : totalSrc / mapLines;
|
|
8563
|
+
const summaryLine = `scanned ${result.entries.length} files, ${totalSrc} src lines -> ${opts.out} (${mapLines} lines, compression ${compression.toFixed(1)}x)`;
|
|
8564
|
+
for (const w of result.warnings) {
|
|
8565
|
+
log.warn(`${w.path}: ${w.message}`);
|
|
8566
|
+
}
|
|
8567
|
+
if (opts.check) {
|
|
8568
|
+
const existing = existsSync11(opts.out) ? readFileSync14(opts.out, "utf8") : "";
|
|
8569
|
+
const normalize = (s) => s.replace(/^# generated: [^\n]+\n/m, "# generated: <normalized>\n");
|
|
8570
|
+
if (normalize(existing) !== normalize(yamlBody)) {
|
|
8571
|
+
log.error(`code-map out of date: ${opts.out} would change. Run \`cdd-kit code-map\` to regenerate.`);
|
|
8572
|
+
return 1;
|
|
8748
8573
|
}
|
|
8574
|
+
log.ok(`code-map up to date: ${opts.out}`);
|
|
8575
|
+
return 0;
|
|
8749
8576
|
}
|
|
8577
|
+
mkdirSync5(dirname4(opts.out), { recursive: true });
|
|
8578
|
+
writeFileSync6(opts.out, yamlBody, "utf8");
|
|
8579
|
+
log.ok(`${summaryLine} (${Date.now() - start}ms)`);
|
|
8580
|
+
return 0;
|
|
8750
8581
|
}
|
|
8751
|
-
|
|
8752
|
-
|
|
8753
|
-
|
|
8754
|
-
|
|
8755
|
-
|
|
8756
|
-
|
|
8757
|
-
|
|
8758
|
-
|
|
8759
|
-
|
|
8760
|
-
|
|
8761
|
-
|
|
8762
|
-
|
|
8763
|
-
if (!node || node.type !== "TSTypeAliasDeclaration")
|
|
8764
|
-
return null;
|
|
8765
|
-
if (!node.id || node.id.type !== "Identifier")
|
|
8766
|
-
return null;
|
|
8767
|
-
return {
|
|
8768
|
-
name: node.id.name,
|
|
8769
|
-
lines: getLineRange(node),
|
|
8770
|
-
exported
|
|
8771
|
-
};
|
|
8772
|
-
}
|
|
8773
|
-
function processTsEnumDeclaration(node, exported) {
|
|
8774
|
-
if (!node || node.type !== "TSEnumDeclaration")
|
|
8775
|
-
return null;
|
|
8776
|
-
if (!node.id || node.id.type !== "Identifier")
|
|
8777
|
-
return null;
|
|
8778
|
-
const members = [];
|
|
8779
|
-
for (const m of node.members ?? []) {
|
|
8780
|
-
if (!m || !m.id)
|
|
8781
|
-
continue;
|
|
8782
|
-
if (m.id.type === "Identifier")
|
|
8783
|
-
members.push(m.id.name);
|
|
8784
|
-
else if (m.id.type === "StringLiteral")
|
|
8785
|
-
members.push(m.id.value);
|
|
8582
|
+
var _require, _pkgPath, _pkg;
|
|
8583
|
+
var init_code_map = __esm({
|
|
8584
|
+
"src/commands/code-map.ts"() {
|
|
8585
|
+
"use strict";
|
|
8586
|
+
init_logger();
|
|
8587
|
+
init_yaml_writer();
|
|
8588
|
+
init_orchestrator();
|
|
8589
|
+
init_config();
|
|
8590
|
+
init_digest();
|
|
8591
|
+
_require = createRequire(import.meta.url);
|
|
8592
|
+
_pkgPath = join14(fileURLToPath2(import.meta.url), "..", "..", "..", "package.json");
|
|
8593
|
+
_pkg = JSON.parse(readFileSync14(_pkgPath, "utf8"));
|
|
8786
8594
|
}
|
|
8787
|
-
|
|
8788
|
-
|
|
8789
|
-
|
|
8790
|
-
|
|
8791
|
-
|
|
8792
|
-
|
|
8793
|
-
}
|
|
8794
|
-
|
|
8795
|
-
|
|
8796
|
-
|
|
8797
|
-
|
|
8595
|
+
});
|
|
8596
|
+
|
|
8597
|
+
// src/code-map/freshness.ts
|
|
8598
|
+
var freshness_exports = {};
|
|
8599
|
+
__export(freshness_exports, {
|
|
8600
|
+
checkCodeMapFreshness: () => checkCodeMapFreshness
|
|
8601
|
+
});
|
|
8602
|
+
import { existsSync as existsSync12, readFileSync as readFileSync15, statSync as statSync3 } from "fs";
|
|
8603
|
+
import { join as join15 } from "path";
|
|
8604
|
+
function checkCodeMapFreshness(cwd, mapRel = ".cdd/code-map.yml", include, exclude) {
|
|
8605
|
+
const mapPath = join15(cwd, mapRel);
|
|
8606
|
+
let cfg;
|
|
8607
|
+
try {
|
|
8608
|
+
cfg = loadCodeMapConfig(cwd);
|
|
8609
|
+
} catch (err) {
|
|
8610
|
+
return {
|
|
8611
|
+
status: "config-error",
|
|
8612
|
+
staleFiles: [],
|
|
8613
|
+
staleCount: 0,
|
|
8614
|
+
mapPath,
|
|
8615
|
+
configError: err.message
|
|
8616
|
+
};
|
|
8798
8617
|
}
|
|
8799
|
-
|
|
8800
|
-
|
|
8801
|
-
|
|
8802
|
-
|
|
8803
|
-
|
|
8804
|
-
|
|
8805
|
-
} else if (decl.type === "ClassDeclaration") {
|
|
8806
|
-
const ce = processClassDeclaration(decl, decl.id?.name ?? "default");
|
|
8807
|
-
if (ce)
|
|
8808
|
-
buckets.classes.push(ce);
|
|
8618
|
+
const includeFinal = [...cfg.include, ...include ?? []];
|
|
8619
|
+
const excludeFinal = [...cfg.exclude, ...exclude ?? []];
|
|
8620
|
+
const sourceFiles = walkRepo(cwd, { include: includeFinal, exclude: excludeFinal });
|
|
8621
|
+
if (!existsSync12(mapPath)) {
|
|
8622
|
+
if (sourceFiles.length === 0) {
|
|
8623
|
+
return { status: "missing-greenfield", staleFiles: [], staleCount: 0, mapPath };
|
|
8809
8624
|
}
|
|
8810
|
-
return;
|
|
8811
|
-
}
|
|
8812
|
-
if (stmt.type === "ImportDeclaration") {
|
|
8813
|
-
buckets.imports.push(processImportDeclaration(stmt));
|
|
8814
|
-
return;
|
|
8815
|
-
}
|
|
8816
|
-
if (stmt.type === "FunctionDeclaration") {
|
|
8817
|
-
const fe = processFunctionDeclaration(stmt);
|
|
8818
|
-
if (fe)
|
|
8819
|
-
buckets.functions.push(fe);
|
|
8820
|
-
return;
|
|
8821
|
-
}
|
|
8822
|
-
if (stmt.type === "ClassDeclaration") {
|
|
8823
|
-
const ce = processClassDeclaration(stmt);
|
|
8824
|
-
if (ce)
|
|
8825
|
-
buckets.classes.push(ce);
|
|
8826
|
-
return;
|
|
8827
|
-
}
|
|
8828
|
-
if (stmt.type === "VariableDeclaration") {
|
|
8829
|
-
processVariableDeclaration(stmt, buckets.imports, buckets.constants, buckets.functions);
|
|
8830
|
-
return;
|
|
8625
|
+
return { status: "missing-with-sources", staleFiles: [], staleCount: 0, mapPath };
|
|
8831
8626
|
}
|
|
8832
|
-
|
|
8833
|
-
|
|
8834
|
-
|
|
8835
|
-
|
|
8836
|
-
|
|
8837
|
-
|
|
8838
|
-
|
|
8839
|
-
|
|
8840
|
-
|
|
8841
|
-
|
|
8842
|
-
if (e)
|
|
8843
|
-
buckets.types.push(e);
|
|
8844
|
-
return;
|
|
8845
|
-
}
|
|
8846
|
-
if (anyStmt.type === "TSEnumDeclaration") {
|
|
8847
|
-
const e = processTsEnumDeclaration(anyStmt, exportedFromWrapper);
|
|
8848
|
-
if (e)
|
|
8849
|
-
buckets.enums.push(e);
|
|
8850
|
-
return;
|
|
8627
|
+
const mapMtime = statSync3(mapPath).mtimeMs;
|
|
8628
|
+
const staleAll = [];
|
|
8629
|
+
for (const absPath of sourceFiles) {
|
|
8630
|
+
try {
|
|
8631
|
+
const mtime = statSync3(absPath).mtimeMs;
|
|
8632
|
+
if (mtime > mapMtime) {
|
|
8633
|
+
const rel = absPath.replace(/\\/g, "/").replace(cwd.replace(/\\/g, "/") + "/", "");
|
|
8634
|
+
staleAll.push(rel);
|
|
8635
|
+
}
|
|
8636
|
+
} catch {
|
|
8851
8637
|
}
|
|
8852
8638
|
}
|
|
8853
|
-
if (
|
|
8854
|
-
|
|
8855
|
-
|
|
8856
|
-
|
|
8857
|
-
|
|
8858
|
-
|
|
8859
|
-
|
|
8639
|
+
if (staleAll.length === 0) {
|
|
8640
|
+
return { status: "ok", staleFiles: [], staleCount: 0, mapPath };
|
|
8641
|
+
}
|
|
8642
|
+
const declaredDigest = readSourcesDigest(mapPath);
|
|
8643
|
+
if (declaredDigest !== null) {
|
|
8644
|
+
const actualDigest = computeSourcesDigest(sourceFiles, cwd);
|
|
8645
|
+
if (actualDigest === declaredDigest) {
|
|
8646
|
+
return { status: "ok", staleFiles: [], staleCount: 0, mapPath };
|
|
8860
8647
|
}
|
|
8861
8648
|
}
|
|
8649
|
+
return {
|
|
8650
|
+
status: "stale",
|
|
8651
|
+
staleFiles: staleAll.slice(0, 5),
|
|
8652
|
+
staleCount: staleAll.length,
|
|
8653
|
+
mapPath
|
|
8654
|
+
};
|
|
8862
8655
|
}
|
|
8863
|
-
function
|
|
8864
|
-
let ast;
|
|
8656
|
+
function readSourcesDigest(mapPath) {
|
|
8865
8657
|
try {
|
|
8866
|
-
|
|
8658
|
+
const head = readFileSync15(mapPath, "utf8").slice(0, 2048);
|
|
8659
|
+
const m = head.match(/^# sources-digest:\s*([a-f0-9]+)/m);
|
|
8660
|
+
return m ? m[1] : null;
|
|
8867
8661
|
} catch {
|
|
8868
8662
|
return null;
|
|
8869
8663
|
}
|
|
8870
|
-
const buckets = {
|
|
8871
|
-
imports: [],
|
|
8872
|
-
constants: [],
|
|
8873
|
-
functions: [],
|
|
8874
|
-
classes: [],
|
|
8875
|
-
interfaces: [],
|
|
8876
|
-
types: [],
|
|
8877
|
-
enums: []
|
|
8878
|
-
};
|
|
8879
|
-
for (const stmt of ast.program.body) {
|
|
8880
|
-
processStatement(stmt, buckets, !!opts.extractTsTypes);
|
|
8881
|
-
}
|
|
8882
|
-
return buckets;
|
|
8883
|
-
}
|
|
8884
|
-
function countSourceLines(source) {
|
|
8885
|
-
if (source === "")
|
|
8886
|
-
return 0;
|
|
8887
|
-
return source.split(/\r?\n/).length - (source.endsWith("\n") || source.endsWith("\r\n") ? 1 : 0);
|
|
8888
|
-
}
|
|
8889
|
-
function parseJsSource(source, relPath) {
|
|
8890
|
-
const total_lines = countSourceLines(source);
|
|
8891
|
-
const r = parseAndExtract(source, { plugins: JS_PLUGINS, extractTsTypes: false });
|
|
8892
|
-
if (!r)
|
|
8893
|
-
return null;
|
|
8894
|
-
return {
|
|
8895
|
-
path: relPath,
|
|
8896
|
-
total_lines,
|
|
8897
|
-
imports: r.imports,
|
|
8898
|
-
constants: r.constants,
|
|
8899
|
-
classes: r.classes,
|
|
8900
|
-
functions: r.functions
|
|
8901
|
-
};
|
|
8902
8664
|
}
|
|
8903
|
-
var
|
|
8904
|
-
|
|
8905
|
-
"src/code-map/scanners/javascript.ts"() {
|
|
8665
|
+
var init_freshness = __esm({
|
|
8666
|
+
"src/code-map/freshness.ts"() {
|
|
8906
8667
|
"use strict";
|
|
8907
|
-
|
|
8908
|
-
|
|
8909
|
-
|
|
8910
|
-
"classPrivateProperties",
|
|
8911
|
-
"classPrivateMethods",
|
|
8912
|
-
"decorators-legacy",
|
|
8913
|
-
"topLevelAwait",
|
|
8914
|
-
"dynamicImport",
|
|
8915
|
-
"optionalChaining",
|
|
8916
|
-
"nullishCoalescingOperator",
|
|
8917
|
-
"objectRestSpread",
|
|
8918
|
-
"asyncGenerators",
|
|
8919
|
-
"numericSeparator",
|
|
8920
|
-
"logicalAssignment"
|
|
8921
|
-
];
|
|
8922
|
-
JS_PLUGINS = ["jsx", ...COMMON_PLUGINS];
|
|
8923
|
-
JavaScriptScanner = class {
|
|
8924
|
-
extensions = [".js"];
|
|
8925
|
-
async scan(absolutePath, repoRoot) {
|
|
8926
|
-
let source;
|
|
8927
|
-
try {
|
|
8928
|
-
source = readFileSync14(absolutePath, "utf8");
|
|
8929
|
-
} catch (err) {
|
|
8930
|
-
throw err;
|
|
8931
|
-
}
|
|
8932
|
-
if (isBinary(source)) {
|
|
8933
|
-
return null;
|
|
8934
|
-
}
|
|
8935
|
-
const relPath = canonicalRelPath(absolutePath, repoRoot);
|
|
8936
|
-
return parseJsSource(source, relPath);
|
|
8937
|
-
}
|
|
8938
|
-
};
|
|
8939
|
-
jsScanner = new JavaScriptScanner();
|
|
8668
|
+
init_include_exclude();
|
|
8669
|
+
init_config();
|
|
8670
|
+
init_code_map();
|
|
8940
8671
|
}
|
|
8941
8672
|
});
|
|
8942
8673
|
|
|
8943
|
-
// src/
|
|
8944
|
-
var
|
|
8945
|
-
__export(
|
|
8946
|
-
|
|
8674
|
+
// src/commands/migrate.ts
|
|
8675
|
+
var migrate_exports = {};
|
|
8676
|
+
__export(migrate_exports, {
|
|
8677
|
+
migrate: () => migrate
|
|
8947
8678
|
});
|
|
8948
|
-
import {
|
|
8949
|
-
|
|
8950
|
-
|
|
8951
|
-
|
|
8952
|
-
|
|
8953
|
-
|
|
8954
|
-
|
|
8955
|
-
|
|
8956
|
-
|
|
8957
|
-
|
|
8958
|
-
let source;
|
|
8959
|
-
try {
|
|
8960
|
-
source = readFileSync15(absolutePath, "utf8");
|
|
8961
|
-
} catch (err) {
|
|
8962
|
-
throw err;
|
|
8963
|
-
}
|
|
8964
|
-
if (isBinary(source)) {
|
|
8965
|
-
return null;
|
|
8966
|
-
}
|
|
8967
|
-
const isTsx = absolutePath.toLowerCase().endsWith(".tsx");
|
|
8968
|
-
const plugins = isTsx ? ["typescript", "jsx", ...COMMON_PLUGINS] : ["typescript", ...COMMON_PLUGINS];
|
|
8969
|
-
const r = parseAndExtract(source, { plugins, extractTsTypes: true });
|
|
8970
|
-
if (!r)
|
|
8971
|
-
return null;
|
|
8972
|
-
const relPath = canonicalRelPath(absolutePath, repoRoot);
|
|
8973
|
-
const total_lines = countSourceLines(source);
|
|
8974
|
-
return {
|
|
8975
|
-
path: relPath,
|
|
8976
|
-
total_lines,
|
|
8977
|
-
imports: r.imports,
|
|
8978
|
-
constants: r.constants,
|
|
8979
|
-
classes: r.classes,
|
|
8980
|
-
functions: r.functions,
|
|
8981
|
-
interfaces: r.interfaces,
|
|
8982
|
-
types: r.types,
|
|
8983
|
-
enums: r.enums
|
|
8984
|
-
};
|
|
8985
|
-
}
|
|
8986
|
-
};
|
|
8987
|
-
tsScanner = new TypeScriptScanner();
|
|
8679
|
+
import { join as join18 } from "path";
|
|
8680
|
+
import { cpSync as cpSync2, existsSync as existsSync15, mkdirSync as mkdirSync7, readdirSync as readdirSync8, readFileSync as readFileSync18, renameSync, rmSync as rmSync2, writeFileSync as writeFileSync8 } from "fs";
|
|
8681
|
+
import yaml3 from "js-yaml";
|
|
8682
|
+
function backupChangeDir(cwd, changeId, sessionStamp) {
|
|
8683
|
+
const backupRoot = join18(cwd, ".cdd", "migrate-backup", sessionStamp);
|
|
8684
|
+
const backupDir2 = join18(backupRoot, changeId);
|
|
8685
|
+
mkdirSync7(backupRoot, { recursive: true });
|
|
8686
|
+
const sourceDir = join18(cwd, "specs", "changes", changeId);
|
|
8687
|
+
if (existsSync15(sourceDir)) {
|
|
8688
|
+
cpSync2(sourceDir, backupDir2, { recursive: true });
|
|
8988
8689
|
}
|
|
8989
|
-
|
|
8990
|
-
|
|
8991
|
-
|
|
8992
|
-
|
|
8993
|
-
|
|
8994
|
-
|
|
8995
|
-
|
|
8996
|
-
|
|
8997
|
-
|
|
8998
|
-
|
|
8999
|
-
|
|
9000
|
-
|
|
9001
|
-
"
|
|
9002
|
-
|
|
9003
|
-
|
|
9004
|
-
|
|
9005
|
-
|
|
9006
|
-
|
|
9007
|
-
|
|
9008
|
-
|
|
9009
|
-
|
|
9010
|
-
|
|
9011
|
-
|
|
9012
|
-
|
|
9013
|
-
|
|
9014
|
-
|
|
9015
|
-
|
|
9016
|
-
|
|
9017
|
-
|
|
9018
|
-
|
|
9019
|
-
|
|
9020
|
-
|
|
9021
|
-
|
|
9022
|
-
|
|
9023
|
-
|
|
9024
|
-
|
|
9025
|
-
|
|
9026
|
-
|
|
9027
|
-
|
|
9028
|
-
|
|
9029
|
-
|
|
9030
|
-
|
|
9031
|
-
|
|
9032
|
-
|
|
9033
|
-
|
|
8690
|
+
return backupDir2;
|
|
8691
|
+
}
|
|
8692
|
+
function buildLegacyContextManifest(changeId) {
|
|
8693
|
+
return [
|
|
8694
|
+
"# Context Manifest",
|
|
8695
|
+
"",
|
|
8696
|
+
"Generated by `cdd-kit migrate` for an existing change.",
|
|
8697
|
+
"Legacy manifest. Forbidden paths come from `.cdd/context-policy.json`.",
|
|
8698
|
+
"",
|
|
8699
|
+
"## Affected Surfaces",
|
|
8700
|
+
"- legacy-unknown",
|
|
8701
|
+
"",
|
|
8702
|
+
"## Allowed Paths",
|
|
8703
|
+
`- specs/changes/${changeId}/`,
|
|
8704
|
+
"",
|
|
8705
|
+
"## Required Contracts",
|
|
8706
|
+
"- legacy-unknown",
|
|
8707
|
+
"",
|
|
8708
|
+
"## Required Tests",
|
|
8709
|
+
"- legacy-unknown",
|
|
8710
|
+
"",
|
|
8711
|
+
"## Agent Work Packets",
|
|
8712
|
+
"",
|
|
8713
|
+
"## Context Expansion Requests",
|
|
8714
|
+
"-",
|
|
8715
|
+
"",
|
|
8716
|
+
"## Approved Expansions",
|
|
8717
|
+
"-",
|
|
8718
|
+
""
|
|
8719
|
+
].join("\n");
|
|
8720
|
+
}
|
|
8721
|
+
function buildContextGovernedManifest(changeId) {
|
|
8722
|
+
return [
|
|
8723
|
+
"# Context Manifest",
|
|
8724
|
+
"",
|
|
8725
|
+
"Generated by `cdd-kit migrate --enable-context-governance` for an existing change.",
|
|
8726
|
+
"Review and narrow the allowed paths before assigning implementation work.",
|
|
8727
|
+
"Forbidden paths come from `.cdd/context-policy.json`.",
|
|
8728
|
+
"",
|
|
8729
|
+
"## Affected Surfaces",
|
|
8730
|
+
"- legacy-unknown",
|
|
8731
|
+
"",
|
|
8732
|
+
"## Allowed Paths",
|
|
8733
|
+
`- specs/changes/${changeId}/`,
|
|
8734
|
+
"- specs/context/project-map.md",
|
|
8735
|
+
"- specs/context/contracts-index.md",
|
|
8736
|
+
"",
|
|
8737
|
+
"## Required Contracts",
|
|
8738
|
+
"- legacy-unknown",
|
|
8739
|
+
"",
|
|
8740
|
+
"## Required Tests",
|
|
8741
|
+
"- legacy-unknown",
|
|
8742
|
+
"",
|
|
8743
|
+
"## Agent Work Packets",
|
|
8744
|
+
"",
|
|
8745
|
+
"### change-classifier",
|
|
8746
|
+
"- allowed:",
|
|
8747
|
+
` - specs/changes/${changeId}/`,
|
|
8748
|
+
" - specs/context/project-map.md",
|
|
8749
|
+
" - specs/context/contracts-index.md",
|
|
8750
|
+
"",
|
|
8751
|
+
"## Context Expansion Requests",
|
|
8752
|
+
"",
|
|
8753
|
+
"<!--",
|
|
8754
|
+
"Agents must request context expansion instead of reading outside their work packet.",
|
|
8755
|
+
"Use this format only for real requests:",
|
|
8756
|
+
"",
|
|
8757
|
+
"- request-id: CER-001",
|
|
8758
|
+
" requested_paths:",
|
|
8759
|
+
" - src/example.ts",
|
|
8760
|
+
" reason: why this file is required",
|
|
8761
|
+
" status: pending",
|
|
8762
|
+
"-->",
|
|
8763
|
+
"",
|
|
8764
|
+
"## Approved Expansions",
|
|
8765
|
+
"-",
|
|
8766
|
+
""
|
|
8767
|
+
].join("\n");
|
|
8768
|
+
}
|
|
8769
|
+
function parseLegacyFrontmatter(content) {
|
|
8770
|
+
const m = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
|
|
8771
|
+
if (!m)
|
|
8772
|
+
return {};
|
|
8773
|
+
const out = {};
|
|
8774
|
+
for (const line of m[1].split(/\r?\n/)) {
|
|
8775
|
+
const colon = line.indexOf(":");
|
|
8776
|
+
if (colon === -1)
|
|
8777
|
+
continue;
|
|
8778
|
+
const key = line.slice(0, colon).trim();
|
|
8779
|
+
if (!key)
|
|
8780
|
+
continue;
|
|
8781
|
+
out[key] = line.slice(colon + 1).trim();
|
|
8782
|
+
}
|
|
8783
|
+
return out;
|
|
8784
|
+
}
|
|
8785
|
+
function parseListField(raw) {
|
|
8786
|
+
if (!raw)
|
|
8787
|
+
return [];
|
|
8788
|
+
const trimmed = raw.trim();
|
|
8789
|
+
if (!trimmed || trimmed === "[]")
|
|
8790
|
+
return [];
|
|
8791
|
+
const inner = trimmed.startsWith("[") && trimmed.endsWith("]") ? trimmed.slice(1, -1) : trimmed;
|
|
8792
|
+
return inner.split(",").map((item) => item.trim().replace(/^["']|["']$/g, "")).filter(Boolean);
|
|
8793
|
+
}
|
|
8794
|
+
function parseLegacyTaskList(body) {
|
|
8795
|
+
const lines = body.split(/\r?\n/);
|
|
8796
|
+
const rows = [];
|
|
8797
|
+
let currentSection;
|
|
8798
|
+
for (const raw of lines) {
|
|
8799
|
+
const headerMatch = raw.match(/^##\s+\d+\.\s+(.*)\s*$/);
|
|
8800
|
+
if (headerMatch) {
|
|
8801
|
+
currentSection = headerMatch[1].trim();
|
|
8802
|
+
continue;
|
|
8803
|
+
}
|
|
8804
|
+
const itemMatch = raw.match(/^\s*-\s*\[([ xX\-])\]\s+(\d+(?:\.\d+)*)\s+(.*)\s*$/);
|
|
8805
|
+
if (!itemMatch)
|
|
8806
|
+
continue;
|
|
8807
|
+
const mark = itemMatch[1];
|
|
8808
|
+
const id = itemMatch[2];
|
|
8809
|
+
const title = itemMatch[3].trim();
|
|
8810
|
+
let status = "pending";
|
|
8811
|
+
if (mark === "x" || mark === "X")
|
|
8812
|
+
status = "done";
|
|
8813
|
+
else if (mark === "-")
|
|
8814
|
+
status = "skipped";
|
|
8815
|
+
rows.push({ id, title, status, section: currentSection });
|
|
8816
|
+
}
|
|
8817
|
+
return rows;
|
|
8818
|
+
}
|
|
8819
|
+
function migrateTasksFile(changeId, changeDir, enableContextGovernance, detectedTier, changed, warnings, pendingWrites, pendingDeletes) {
|
|
8820
|
+
const newPath = join18(changeDir, "tasks.yml");
|
|
8821
|
+
const legacyPath = join18(changeDir, "tasks.md");
|
|
8822
|
+
if (existsSync15(newPath)) {
|
|
8823
|
+
return;
|
|
8824
|
+
}
|
|
8825
|
+
if (!existsSync15(legacyPath)) {
|
|
8826
|
+
warnings.push("tasks.md not found and tasks.yml missing \u2014 skipping tasks migration");
|
|
8827
|
+
return;
|
|
8828
|
+
}
|
|
8829
|
+
const raw = readFileSync18(legacyPath, "utf8");
|
|
8830
|
+
const fm = parseLegacyFrontmatter(raw);
|
|
8831
|
+
const bodyMatch = raw.match(/^---\r?\n[\s\S]*?\r?\n---\r?\n?([\s\S]*)$/);
|
|
8832
|
+
const body = bodyMatch ? bodyMatch[1] : raw;
|
|
8833
|
+
const tasksRows = parseLegacyTaskList(body);
|
|
8834
|
+
const data = {};
|
|
8835
|
+
data["change-id"] = fm["change-id"] || changeId;
|
|
8836
|
+
data["status"] = fm["status"] || "in-progress";
|
|
8837
|
+
if (fm["tier"] && /^\d+$/.test(fm["tier"])) {
|
|
8838
|
+
data["tier"] = parseInt(fm["tier"], 10);
|
|
8839
|
+
} else if (detectedTier) {
|
|
8840
|
+
data["tier"] = parseInt(detectedTier, 10);
|
|
8841
|
+
} else {
|
|
8842
|
+
data["tier"] = null;
|
|
8843
|
+
}
|
|
8844
|
+
if (enableContextGovernance || fm["context-governance"] === "v1") {
|
|
8845
|
+
data["context-governance"] = "v1";
|
|
8846
|
+
}
|
|
8847
|
+
const archive2 = parseListField(fm["archive-tasks"]);
|
|
8848
|
+
data["archive-tasks"] = archive2.length > 0 ? archive2 : ["7.1", "7.2"];
|
|
8849
|
+
const deps = parseListField(fm["depends-on"]);
|
|
8850
|
+
data["depends-on"] = deps;
|
|
8851
|
+
data["tasks"] = tasksRows.map((r) => {
|
|
8852
|
+
const out = { id: r.id, title: r.title, status: r.status };
|
|
8853
|
+
if (r.section)
|
|
8854
|
+
out["section"] = r.section;
|
|
8855
|
+
return out;
|
|
8856
|
+
});
|
|
8857
|
+
const yamlOut = yaml3.dump(data, { lineWidth: -1, noRefs: true });
|
|
8858
|
+
pendingWrites.push({ path: newPath, content: yamlOut });
|
|
8859
|
+
pendingDeletes.push({ path: legacyPath });
|
|
8860
|
+
changed.push(`tasks.md -> tasks.yml (${tasksRows.length} task(s) migrated)`);
|
|
8861
|
+
}
|
|
8862
|
+
function parseLegacyAgentLog(content) {
|
|
8863
|
+
const lines = content.split(/\r?\n/);
|
|
8864
|
+
const data = {};
|
|
8865
|
+
let i = 0;
|
|
8866
|
+
const topFieldRe = /^[ ]{0,1}-\s*([\w-]+):\s*(.*)$/;
|
|
8867
|
+
while (i < lines.length) {
|
|
8868
|
+
const line = lines[i];
|
|
8869
|
+
const fieldMatch = line.match(topFieldRe);
|
|
8870
|
+
if (!fieldMatch) {
|
|
8871
|
+
i++;
|
|
8872
|
+
continue;
|
|
8873
|
+
}
|
|
8874
|
+
const key = fieldMatch[1];
|
|
8875
|
+
const inline = fieldMatch[2].trim();
|
|
8876
|
+
if (key === "files-read" || key === "artifacts") {
|
|
8877
|
+
const items = [];
|
|
8878
|
+
let j = i + 1;
|
|
8879
|
+
while (j < lines.length) {
|
|
8880
|
+
const sub = lines[j];
|
|
8881
|
+
if (topFieldRe.test(sub))
|
|
8882
|
+
break;
|
|
8883
|
+
if (/^#/.test(sub))
|
|
8884
|
+
break;
|
|
8885
|
+
const itemMatch = sub.match(/^\s{2,}-\s+(.+?)\s*$/);
|
|
8886
|
+
if (itemMatch) {
|
|
8887
|
+
items.push(itemMatch[1].trim());
|
|
9034
8888
|
}
|
|
9035
|
-
|
|
9036
|
-
|
|
9037
|
-
|
|
9038
|
-
|
|
9039
|
-
|
|
9040
|
-
|
|
9041
|
-
|
|
9042
|
-
|
|
9043
|
-
|
|
9044
|
-
}
|
|
9045
|
-
const blockContent = block.content;
|
|
9046
|
-
const lineOffset = block.loc.start.line;
|
|
9047
|
-
const scriptEntry = parseJsSource(blockContent, relPath);
|
|
9048
|
-
if (!scriptEntry) {
|
|
9049
|
-
warnings.push({ path: relPath, message: "parse error in script block" });
|
|
9050
|
-
continue;
|
|
9051
|
-
}
|
|
9052
|
-
const offset = lineOffset - 1;
|
|
9053
|
-
for (const imp of scriptEntry.imports) {
|
|
9054
|
-
allImports.push({ ...imp, line: imp.line + offset });
|
|
9055
|
-
}
|
|
9056
|
-
for (const c of scriptEntry.constants) {
|
|
9057
|
-
allConstants.push({ ...c, line: c.line + offset });
|
|
9058
|
-
}
|
|
9059
|
-
for (const fn of scriptEntry.functions) {
|
|
9060
|
-
allFunctions.push({
|
|
9061
|
-
...fn,
|
|
9062
|
-
lines: [fn.lines[0] + offset, fn.lines[1] + offset]
|
|
9063
|
-
});
|
|
9064
|
-
}
|
|
9065
|
-
for (const cls of scriptEntry.classes) {
|
|
9066
|
-
allClasses.push({
|
|
9067
|
-
...cls,
|
|
9068
|
-
lines: [cls.lines[0] + offset, cls.lines[1] + offset],
|
|
9069
|
-
methods: cls.methods.map((m) => ({
|
|
9070
|
-
...m,
|
|
9071
|
-
lines: [m.lines[0] + offset, m.lines[1] + offset]
|
|
9072
|
-
}))
|
|
9073
|
-
});
|
|
8889
|
+
j++;
|
|
8890
|
+
}
|
|
8891
|
+
if (key === "files-read") {
|
|
8892
|
+
data["files-read"] = items;
|
|
8893
|
+
} else {
|
|
8894
|
+
data["artifacts"] = items.map((s) => {
|
|
8895
|
+
const idx = s.indexOf(":");
|
|
8896
|
+
if (idx === -1) {
|
|
8897
|
+
return { type: "note", pointer: s };
|
|
9074
8898
|
}
|
|
8899
|
+
const type = s.slice(0, idx).trim();
|
|
8900
|
+
const pointer = s.slice(idx + 1).trim();
|
|
8901
|
+
return { type, pointer };
|
|
8902
|
+
});
|
|
8903
|
+
}
|
|
8904
|
+
i = j;
|
|
8905
|
+
continue;
|
|
8906
|
+
}
|
|
8907
|
+
data[key] = inline;
|
|
8908
|
+
i++;
|
|
8909
|
+
}
|
|
8910
|
+
return data;
|
|
8911
|
+
}
|
|
8912
|
+
function migrateAgentLogs(changeDir, changed, pendingWrites, pendingDeletes) {
|
|
8913
|
+
const agentLogDir = join18(changeDir, "agent-log");
|
|
8914
|
+
if (!existsSync15(agentLogDir))
|
|
8915
|
+
return;
|
|
8916
|
+
const mdLogs = readdirSync8(agentLogDir).filter((f) => f.endsWith(".md"));
|
|
8917
|
+
for (const f of mdLogs) {
|
|
8918
|
+
const fullPath = join18(agentLogDir, f);
|
|
8919
|
+
const yamlName = f.replace(/\.md$/, ".yml");
|
|
8920
|
+
const yamlFull = join18(agentLogDir, yamlName);
|
|
8921
|
+
if (existsSync15(yamlFull))
|
|
8922
|
+
continue;
|
|
8923
|
+
const raw = readFileSync18(fullPath, "utf8");
|
|
8924
|
+
const parsed = parseLegacyAgentLog(raw);
|
|
8925
|
+
const yamlOut = yaml3.dump(parsed, { lineWidth: -1, noRefs: true });
|
|
8926
|
+
pendingWrites.push({ path: yamlFull, content: yamlOut });
|
|
8927
|
+
pendingDeletes.push({ path: fullPath });
|
|
8928
|
+
changed.push(`agent-log/${f} -> agent-log/${yamlName}`);
|
|
8929
|
+
}
|
|
8930
|
+
}
|
|
8931
|
+
function migrateOne(changeId, changeDir, enableContextGovernance) {
|
|
8932
|
+
const changed = [];
|
|
8933
|
+
const warnings = [];
|
|
8934
|
+
const pending = [];
|
|
8935
|
+
const deletes = [];
|
|
8936
|
+
let detectedTier = null;
|
|
8937
|
+
const classifPath = join18(changeDir, "change-classification.md");
|
|
8938
|
+
if (existsSync15(classifPath)) {
|
|
8939
|
+
const content = readFileSync18(classifPath, "utf8");
|
|
8940
|
+
const hasNewTierFormat = /^## Tier\s*\n\s*-\s*\d\s*$/m.test(content);
|
|
8941
|
+
const oldMatch = content.match(/\*\*Tier[:\*]+\s*(?:Tier\s*)?(\d)/i) ?? content.match(/^-?\s*Tier:\s*(?:Tier\s*)?(\d)/mi);
|
|
8942
|
+
if (oldMatch)
|
|
8943
|
+
detectedTier = oldMatch[1];
|
|
8944
|
+
if (!hasNewTierFormat) {
|
|
8945
|
+
if (detectedTier) {
|
|
8946
|
+
const addition = `
|
|
8947
|
+
## Tier
|
|
8948
|
+
- ${detectedTier}
|
|
8949
|
+
`;
|
|
8950
|
+
if (!content.includes("\n## Tier\n")) {
|
|
8951
|
+
changed.push(
|
|
8952
|
+
`change-classification.md: appended "## Tier\\n- ${detectedTier}" (converted from old format)`
|
|
8953
|
+
);
|
|
8954
|
+
pending.push({ path: classifPath, content: content + addition });
|
|
8955
|
+
}
|
|
8956
|
+
} else {
|
|
8957
|
+
warnings.push(
|
|
8958
|
+
"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."
|
|
8959
|
+
);
|
|
8960
|
+
}
|
|
8961
|
+
} else {
|
|
8962
|
+
const structured = content.match(/^## Tier\s*\n\s*-\s*(\d)\s*$/m);
|
|
8963
|
+
if (structured)
|
|
8964
|
+
detectedTier = structured[1];
|
|
8965
|
+
}
|
|
8966
|
+
}
|
|
8967
|
+
migrateTasksFile(changeId, changeDir, enableContextGovernance, detectedTier, changed, warnings, pending, deletes);
|
|
8968
|
+
migrateAgentLogs(changeDir, changed, pending, deletes);
|
|
8969
|
+
const manifestPath = join18(changeDir, "context-manifest.md");
|
|
8970
|
+
if (!existsSync15(manifestPath)) {
|
|
8971
|
+
changed.push(enableContextGovernance ? "context-manifest.md: added context-governance v1 manifest scaffold" : "context-manifest.md: added legacy context manifest scaffold");
|
|
8972
|
+
pending.push({
|
|
8973
|
+
path: manifestPath,
|
|
8974
|
+
content: enableContextGovernance ? buildContextGovernedManifest(changeId) : buildLegacyContextManifest(changeId)
|
|
8975
|
+
});
|
|
8976
|
+
} else if (enableContextGovernance) {
|
|
8977
|
+
warnings.push("context-manifest.md already exists \u2014 review allowed paths before relying on context-governance: v1");
|
|
8978
|
+
}
|
|
8979
|
+
return { result: { changed, warnings }, pending, deletes };
|
|
8980
|
+
}
|
|
8981
|
+
function commitWritesAtomically(pending, deletes) {
|
|
8982
|
+
const renames = [];
|
|
8983
|
+
try {
|
|
8984
|
+
for (const write of pending) {
|
|
8985
|
+
const tmp = `${write.path}.cdd-migrate.tmp`;
|
|
8986
|
+
writeFileSync8(tmp, write.content, "utf8");
|
|
8987
|
+
renames.push({ tmp, final: write.path });
|
|
8988
|
+
}
|
|
8989
|
+
} catch (err) {
|
|
8990
|
+
for (const r of renames) {
|
|
8991
|
+
try {
|
|
8992
|
+
rmSync2(r.tmp, { force: true });
|
|
8993
|
+
} catch {
|
|
8994
|
+
}
|
|
8995
|
+
}
|
|
8996
|
+
throw err;
|
|
8997
|
+
}
|
|
8998
|
+
for (const r of renames) {
|
|
8999
|
+
renameSync(r.tmp, r.final);
|
|
9000
|
+
}
|
|
9001
|
+
for (const d of deletes) {
|
|
9002
|
+
try {
|
|
9003
|
+
rmSync2(d.path, { force: true });
|
|
9004
|
+
} catch {
|
|
9005
|
+
}
|
|
9006
|
+
}
|
|
9007
|
+
}
|
|
9008
|
+
async function migrate(changeId, opts = {}) {
|
|
9009
|
+
const cwd = process.cwd();
|
|
9010
|
+
const dryRun = opts.dryRun ?? false;
|
|
9011
|
+
const enableContextGovernance = opts.enableContextGovernance ?? false;
|
|
9012
|
+
const noBackup = opts.noBackup ?? false;
|
|
9013
|
+
const idsToMigrate = [];
|
|
9014
|
+
if (opts.all) {
|
|
9015
|
+
const changesDir = join18(cwd, "specs", "changes");
|
|
9016
|
+
if (!existsSync15(changesDir)) {
|
|
9017
|
+
log.info("No specs/changes/ directory found \u2014 nothing to migrate.");
|
|
9018
|
+
return;
|
|
9019
|
+
}
|
|
9020
|
+
idsToMigrate.push(
|
|
9021
|
+
...readdirSync8(changesDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name)
|
|
9022
|
+
);
|
|
9023
|
+
} else if (changeId) {
|
|
9024
|
+
const specificDir = join18(cwd, "specs", "changes", changeId);
|
|
9025
|
+
if (!existsSync15(specificDir)) {
|
|
9026
|
+
log.error(`Change not found: specs/changes/${changeId}`);
|
|
9027
|
+
process.exit(1);
|
|
9028
|
+
}
|
|
9029
|
+
idsToMigrate.push(changeId);
|
|
9030
|
+
} else {
|
|
9031
|
+
log.error("Usage: cdd-kit migrate <change-id> | cdd-kit migrate --all [--dry-run] [--no-backup]");
|
|
9032
|
+
process.exit(1);
|
|
9033
|
+
}
|
|
9034
|
+
if (idsToMigrate.length === 0) {
|
|
9035
|
+
log.info("No changes found to migrate.");
|
|
9036
|
+
return;
|
|
9037
|
+
}
|
|
9038
|
+
if (dryRun) {
|
|
9039
|
+
log.info("Dry run \u2014 no files will be written.");
|
|
9040
|
+
log.blank();
|
|
9041
|
+
}
|
|
9042
|
+
const sessionStamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
9043
|
+
let migratedCount = 0;
|
|
9044
|
+
let upToDateCount = 0;
|
|
9045
|
+
const backupRoot = join18(cwd, ".cdd", "migrate-backup", sessionStamp);
|
|
9046
|
+
for (const id of idsToMigrate) {
|
|
9047
|
+
const changeDir = join18(cwd, "specs", "changes", id);
|
|
9048
|
+
if (!existsSync15(changeDir)) {
|
|
9049
|
+
log.warn(` ${id}: directory not found \u2014 skipping`);
|
|
9050
|
+
continue;
|
|
9051
|
+
}
|
|
9052
|
+
const { result, pending, deletes } = migrateOne(id, changeDir, enableContextGovernance);
|
|
9053
|
+
const { changed, warnings } = result;
|
|
9054
|
+
if (changed.length === 0) {
|
|
9055
|
+
log.info(` ${id}: already up to date`);
|
|
9056
|
+
upToDateCount++;
|
|
9057
|
+
for (const w of warnings)
|
|
9058
|
+
log.warn(` ${id}: ${w}`);
|
|
9059
|
+
continue;
|
|
9060
|
+
}
|
|
9061
|
+
if (!dryRun) {
|
|
9062
|
+
try {
|
|
9063
|
+
if (!noBackup)
|
|
9064
|
+
backupChangeDir(cwd, id, sessionStamp);
|
|
9065
|
+
commitWritesAtomically(pending, deletes);
|
|
9066
|
+
} catch (err) {
|
|
9067
|
+
log.error(` ${id}: migration failed \u2014 ${err.message}`);
|
|
9068
|
+
if (!noBackup) {
|
|
9069
|
+
log.error(` ${id}: restore from .cdd/migrate-backup/${sessionStamp}/${id}/`);
|
|
9075
9070
|
}
|
|
9076
|
-
|
|
9077
|
-
path: relPath,
|
|
9078
|
-
total_lines,
|
|
9079
|
-
imports: allImports,
|
|
9080
|
-
constants: allConstants,
|
|
9081
|
-
classes: allClasses,
|
|
9082
|
-
functions: allFunctions
|
|
9083
|
-
};
|
|
9071
|
+
process.exit(1);
|
|
9084
9072
|
}
|
|
9085
|
-
}
|
|
9086
|
-
|
|
9073
|
+
}
|
|
9074
|
+
log.ok(` ${id}: migrated`);
|
|
9075
|
+
for (const c of changed)
|
|
9076
|
+
log.info(` + ${c}`);
|
|
9077
|
+
migratedCount++;
|
|
9078
|
+
for (const w of warnings)
|
|
9079
|
+
log.warn(` ${id}: ${w}`);
|
|
9080
|
+
}
|
|
9081
|
+
log.blank();
|
|
9082
|
+
if (dryRun) {
|
|
9083
|
+
log.info(`Dry run complete: ${migratedCount} change(s) would be updated, ${upToDateCount} already up to date.`);
|
|
9084
|
+
} else {
|
|
9085
|
+
log.ok(`Migration complete: ${migratedCount} updated, ${upToDateCount} already up to date.`);
|
|
9086
|
+
if (migratedCount > 0 && !noBackup) {
|
|
9087
|
+
log.info(`Backup: ${backupRoot}`);
|
|
9088
|
+
if (ensureGitignoreEntry(cwd, ".cdd/migrate-backup/")) {
|
|
9089
|
+
log.info("Added `.cdd/migrate-backup/` to .gitignore");
|
|
9090
|
+
}
|
|
9091
|
+
log.info('Next: git add specs/changes/ && git commit -m "chore: migrate changes to YAML format"');
|
|
9092
|
+
log.info("When stable, remove backup: rm -rf .cdd/migrate-backup/");
|
|
9093
|
+
}
|
|
9094
|
+
}
|
|
9095
|
+
}
|
|
9096
|
+
function ensureGitignoreEntry(cwd, entry) {
|
|
9097
|
+
const path = join18(cwd, ".gitignore");
|
|
9098
|
+
const trimmed = entry.trim();
|
|
9099
|
+
if (!trimmed)
|
|
9100
|
+
return false;
|
|
9101
|
+
const re = new RegExp(`^\\s*${trimmed.replace(/[.*+?^${}()|[\\]\\\\]/g, "\\$&")}\\s*$`, "m");
|
|
9102
|
+
let existing = "";
|
|
9103
|
+
if (existsSync15(path))
|
|
9104
|
+
existing = readFileSync18(path, "utf8");
|
|
9105
|
+
if (re.test(existing))
|
|
9106
|
+
return false;
|
|
9107
|
+
const sep = existing.length > 0 && !existing.endsWith("\n") ? "\n" : "";
|
|
9108
|
+
const block = existing.length === 0 ? `# cdd-kit generated backups (do not commit)
|
|
9109
|
+
${trimmed}
|
|
9110
|
+
` : `${sep}
|
|
9111
|
+
# cdd-kit generated backups (do not commit)
|
|
9112
|
+
${trimmed}
|
|
9113
|
+
`;
|
|
9114
|
+
writeFileSync8(path, existing + block, "utf8");
|
|
9115
|
+
return true;
|
|
9116
|
+
}
|
|
9117
|
+
var init_migrate = __esm({
|
|
9118
|
+
"src/commands/migrate.ts"() {
|
|
9119
|
+
"use strict";
|
|
9120
|
+
init_logger();
|
|
9087
9121
|
}
|
|
9088
9122
|
});
|
|
9089
9123
|
|
|
9090
|
-
// src/commands/
|
|
9091
|
-
var
|
|
9092
|
-
__export(
|
|
9093
|
-
|
|
9124
|
+
// src/commands/upgrade.ts
|
|
9125
|
+
var upgrade_exports = {};
|
|
9126
|
+
__export(upgrade_exports, {
|
|
9127
|
+
upgrade: () => upgrade
|
|
9094
9128
|
});
|
|
9095
|
-
import { existsSync as existsSync16, mkdirSync as mkdirSync8, readFileSync as
|
|
9096
|
-
import {
|
|
9097
|
-
|
|
9098
|
-
|
|
9099
|
-
|
|
9100
|
-
|
|
9101
|
-
|
|
9102
|
-
|
|
9103
|
-
|
|
9104
|
-
|
|
9105
|
-
|
|
9106
|
-
|
|
9107
|
-
|
|
9108
|
-
|
|
9109
|
-
}
|
|
9110
|
-
const include = [...cfg.include, ...opts.include];
|
|
9111
|
-
const exclude = [...cfg.exclude, ...opts.exclude];
|
|
9112
|
-
const files = walkRepo(root, { include, exclude });
|
|
9113
|
-
const buckets = bucketByExtension(files);
|
|
9114
|
-
const result = { entries: [], warnings: [] };
|
|
9115
|
-
const tasks = [];
|
|
9116
|
-
if (buckets[".py"]?.length) {
|
|
9117
|
-
const { pythonScanner: pythonScanner2 } = await Promise.resolve().then(() => (init_python(), python_exports));
|
|
9118
|
-
if (pythonScanner2.scanBatch) {
|
|
9119
|
-
tasks.push(pythonScanner2.scanBatch(buckets[".py"], root));
|
|
9129
|
+
import { existsSync as existsSync16, mkdirSync as mkdirSync8, readdirSync as readdirSync9, copyFileSync as copyFileSync3, readFileSync as readFileSync19, writeFileSync as writeFileSync9 } from "fs";
|
|
9130
|
+
import { dirname as dirname5, join as join19, relative as relative6 } from "path";
|
|
9131
|
+
function planMissingFiles(srcDir, destDir, label, planned) {
|
|
9132
|
+
if (!existsSync16(srcDir))
|
|
9133
|
+
return;
|
|
9134
|
+
for (const entry of readdirSync9(srcDir, { withFileTypes: true })) {
|
|
9135
|
+
const src = join19(srcDir, entry.name);
|
|
9136
|
+
const dest = join19(destDir, entry.name);
|
|
9137
|
+
if (entry.isDirectory()) {
|
|
9138
|
+
planMissingFiles(src, dest, join19(label, entry.name), planned);
|
|
9139
|
+
continue;
|
|
9140
|
+
}
|
|
9141
|
+
if (!existsSync16(dest)) {
|
|
9142
|
+
planned.push({ src, dest, rel: join19(label, relative6(srcDir, src)) });
|
|
9120
9143
|
}
|
|
9121
9144
|
}
|
|
9122
|
-
|
|
9123
|
-
|
|
9124
|
-
|
|
9125
|
-
|
|
9126
|
-
|
|
9127
|
-
|
|
9128
|
-
|
|
9129
|
-
|
|
9130
|
-
|
|
9145
|
+
}
|
|
9146
|
+
function planProviderGuidance(cwd, provider, planned) {
|
|
9147
|
+
if (provider === "claude" || provider === "both") {
|
|
9148
|
+
if (!existsSync16(join19(cwd, "CLAUDE.md"))) {
|
|
9149
|
+
planned.push({ src: ASSET.claudeTemplate, dest: join19(cwd, "CLAUDE.md"), rel: "CLAUDE.md" });
|
|
9150
|
+
}
|
|
9151
|
+
if (!existsSync16(join19(cwd, "AGENTS.md"))) {
|
|
9152
|
+
planned.push({ src: ASSET.agentsTemplate, dest: join19(cwd, "AGENTS.md"), rel: "AGENTS.md" });
|
|
9153
|
+
}
|
|
9131
9154
|
}
|
|
9132
|
-
|
|
9133
|
-
|
|
9134
|
-
...buckets[".tsx"] ?? []
|
|
9135
|
-
];
|
|
9136
|
-
if (tsFiles.length) {
|
|
9137
|
-
const { tsScanner: tsScanner2 } = await Promise.resolve().then(() => (init_typescript(), typescript_exports));
|
|
9138
|
-
tasks.push(scanInProcess(tsScanner2, tsFiles, root));
|
|
9155
|
+
if ((provider === "codex" || provider === "both") && !existsSync16(join19(cwd, "CODEX.md"))) {
|
|
9156
|
+
planned.push({ src: ASSET.codexTemplate, dest: join19(cwd, "CODEX.md"), rel: "CODEX.md" });
|
|
9139
9157
|
}
|
|
9140
|
-
|
|
9141
|
-
|
|
9142
|
-
|
|
9158
|
+
}
|
|
9159
|
+
function applyCopy(plan) {
|
|
9160
|
+
for (const item of plan) {
|
|
9161
|
+
mkdirSync8(dirname5(item.dest), { recursive: true });
|
|
9162
|
+
copyFileSync3(item.src, item.dest);
|
|
9143
9163
|
}
|
|
9144
|
-
|
|
9145
|
-
|
|
9146
|
-
|
|
9164
|
+
}
|
|
9165
|
+
async function upgrade(opts = {}) {
|
|
9166
|
+
const cwd = process.cwd();
|
|
9167
|
+
const requestedProvider = opts.provider ?? "auto";
|
|
9168
|
+
if (!validateProviderOption(requestedProvider)) {
|
|
9169
|
+
log.error(`Invalid provider: ${requestedProvider}. Use auto, claude, codex, or both.`);
|
|
9170
|
+
process.exit(1);
|
|
9147
9171
|
}
|
|
9148
|
-
|
|
9149
|
-
|
|
9150
|
-
|
|
9151
|
-
|
|
9152
|
-
|
|
9153
|
-
|
|
9172
|
+
const provider = inferProvider(cwd, requestedProvider);
|
|
9173
|
+
const plan = [];
|
|
9174
|
+
planMissingFiles(ASSET.contracts, join19(cwd, "contracts"), "contracts", plan);
|
|
9175
|
+
planMissingFiles(ASSET.specsTemplates, join19(cwd, "specs", "templates"), "specs/templates", plan);
|
|
9176
|
+
planMissingFiles(ASSET.testsTemplates, join19(cwd, "tests", "templates"), "tests/templates", plan);
|
|
9177
|
+
planMissingFiles(ASSET.ci, join19(cwd, "ci"), "ci", plan);
|
|
9178
|
+
planMissingFiles(ASSET.githubWorkflows, join19(cwd, ".github", "workflows"), ".github/workflows", plan);
|
|
9179
|
+
planMissingFiles(ASSET.cddConfig, join19(cwd, ".cdd"), ".cdd", plan);
|
|
9180
|
+
planProviderGuidance(cwd, provider, plan);
|
|
9181
|
+
log.blank();
|
|
9182
|
+
log.info(`Upgrade provider: ${provider}`);
|
|
9183
|
+
if (plan.length === 0) {
|
|
9184
|
+
log.ok("No missing cdd-kit project files found.");
|
|
9185
|
+
if (opts.migrateChanges) {
|
|
9186
|
+
log.blank();
|
|
9187
|
+
log.info("Running change migration flow...");
|
|
9188
|
+
await migrate(void 0, {
|
|
9189
|
+
all: true,
|
|
9190
|
+
dryRun: !opts.yes,
|
|
9191
|
+
enableContextGovernance: opts.enableContextGovernance
|
|
9154
9192
|
});
|
|
9155
9193
|
}
|
|
9194
|
+
log.blank();
|
|
9195
|
+
return;
|
|
9156
9196
|
}
|
|
9157
|
-
|
|
9158
|
-
|
|
9159
|
-
|
|
9160
|
-
|
|
9161
|
-
|
|
9162
|
-
|
|
9163
|
-
|
|
9197
|
+
log.info(`${plan.length} missing file(s) detected:`);
|
|
9198
|
+
for (const item of plan)
|
|
9199
|
+
log.dim(` + ${item.rel.replace(/\\/g, "/")}`);
|
|
9200
|
+
if (!opts.yes) {
|
|
9201
|
+
log.blank();
|
|
9202
|
+
log.info("Dry run only. Re-run with --yes to write missing files.");
|
|
9203
|
+
if (opts.migrateChanges) {
|
|
9204
|
+
log.blank();
|
|
9205
|
+
log.info("Previewing existing change migration because --migrate-changes was requested.");
|
|
9206
|
+
await migrate(void 0, {
|
|
9207
|
+
all: true,
|
|
9208
|
+
dryRun: true,
|
|
9209
|
+
enableContextGovernance: opts.enableContextGovernance
|
|
9210
|
+
});
|
|
9211
|
+
}
|
|
9212
|
+
log.blank();
|
|
9213
|
+
return;
|
|
9164
9214
|
}
|
|
9165
|
-
|
|
9166
|
-
|
|
9167
|
-
|
|
9168
|
-
|
|
9169
|
-
|
|
9170
|
-
|
|
9215
|
+
applyCopy(plan);
|
|
9216
|
+
const modelPolicyPath = join19(cwd, ".cdd", "model-policy.json");
|
|
9217
|
+
if (existsSync16(modelPolicyPath)) {
|
|
9218
|
+
let existing = {};
|
|
9219
|
+
try {
|
|
9220
|
+
existing = JSON.parse(readFileSync19(modelPolicyPath, "utf8"));
|
|
9221
|
+
} catch {
|
|
9171
9222
|
}
|
|
9172
|
-
|
|
9173
|
-
|
|
9223
|
+
const merged = {
|
|
9224
|
+
...existing,
|
|
9225
|
+
provider,
|
|
9226
|
+
generated_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
9227
|
+
roles: existing.roles && typeof existing.roles === "object" ? existing.roles : {}
|
|
9228
|
+
};
|
|
9229
|
+
writeFileSync9(modelPolicyPath, JSON.stringify(merged, null, 2) + "\n", "utf8");
|
|
9174
9230
|
}
|
|
9175
|
-
|
|
9176
|
-
|
|
9177
|
-
log.
|
|
9178
|
-
|
|
9231
|
+
log.blank();
|
|
9232
|
+
log.ok(`Upgrade complete: ${plan.length} missing file(s) added.`);
|
|
9233
|
+
log.info("Existing project guidance and contracts were preserved.");
|
|
9234
|
+
if (opts.migrateChanges) {
|
|
9235
|
+
log.blank();
|
|
9236
|
+
log.info("Running change migration flow...");
|
|
9237
|
+
await migrate(void 0, {
|
|
9238
|
+
all: true,
|
|
9239
|
+
dryRun: false,
|
|
9240
|
+
enableContextGovernance: opts.enableContextGovernance
|
|
9241
|
+
});
|
|
9242
|
+
}
|
|
9243
|
+
log.blank();
|
|
9179
9244
|
}
|
|
9180
|
-
var
|
|
9181
|
-
|
|
9182
|
-
"src/commands/code-map.ts"() {
|
|
9245
|
+
var init_upgrade = __esm({
|
|
9246
|
+
"src/commands/upgrade.ts"() {
|
|
9183
9247
|
"use strict";
|
|
9248
|
+
init_paths();
|
|
9184
9249
|
init_logger();
|
|
9185
|
-
|
|
9186
|
-
|
|
9187
|
-
init_config();
|
|
9188
|
-
_require = createRequire(import.meta.url);
|
|
9189
|
-
_pkgPath = join19(fileURLToPath2(import.meta.url), "..", "..", "..", "package.json");
|
|
9190
|
-
_pkg = JSON.parse(readFileSync17(_pkgPath, "utf8"));
|
|
9250
|
+
init_provider();
|
|
9251
|
+
init_migrate();
|
|
9191
9252
|
}
|
|
9192
9253
|
});
|
|
9193
9254
|
|
|
@@ -9196,11 +9257,11 @@ var refresh_exports = {};
|
|
|
9196
9257
|
__export(refresh_exports, {
|
|
9197
9258
|
refresh: () => refresh
|
|
9198
9259
|
});
|
|
9199
|
-
import { existsSync as existsSync17, mkdirSync as mkdirSync9, readdirSync as readdirSync10, copyFileSync as copyFileSync4, readFileSync as
|
|
9200
|
-
import { dirname as dirname6, join as join20, relative as
|
|
9201
|
-
import { createHash as
|
|
9260
|
+
import { existsSync as existsSync17, mkdirSync as mkdirSync9, readdirSync as readdirSync10, copyFileSync as copyFileSync4, readFileSync as readFileSync20, writeFileSync as writeFileSync10 } from "fs";
|
|
9261
|
+
import { dirname as dirname6, join as join20, relative as relative7 } from "path";
|
|
9262
|
+
import { createHash as createHash6 } from "crypto";
|
|
9202
9263
|
function fileHash2(filePath) {
|
|
9203
|
-
return
|
|
9264
|
+
return createHash6("sha256").update(readFileSync20(filePath)).digest("hex");
|
|
9204
9265
|
}
|
|
9205
9266
|
function planForceRefresh(srcDir, destDir, sectionLabel) {
|
|
9206
9267
|
const plan = [];
|
|
@@ -9222,7 +9283,7 @@ function planForceRefresh(srcDir, destDir, sectionLabel) {
|
|
|
9222
9283
|
}
|
|
9223
9284
|
if (!item.isFile())
|
|
9224
9285
|
continue;
|
|
9225
|
-
const rel = join20(sectionLabel,
|
|
9286
|
+
const rel = join20(sectionLabel, relative7(srcDir, sp)).replace(/\\/g, "/");
|
|
9226
9287
|
if (!existsSync17(dp)) {
|
|
9227
9288
|
plan.push({ src: sp, dest: dp, rel, action: "add" });
|
|
9228
9289
|
} else if (fileHash2(sp) !== fileHash2(dp)) {
|
|
@@ -9252,7 +9313,7 @@ function ensureGitignoreEntry2(cwd, entry) {
|
|
|
9252
9313
|
const re = new RegExp(`^\\s*${trimmed.replace(/[.*+?^${}()|[\\]\\\\]/g, "\\$&")}\\s*$`, "m");
|
|
9253
9314
|
let existing = "";
|
|
9254
9315
|
if (existsSync17(path))
|
|
9255
|
-
existing =
|
|
9316
|
+
existing = readFileSync20(path, "utf8");
|
|
9256
9317
|
if (re.test(existing))
|
|
9257
9318
|
return false;
|
|
9258
9319
|
const sep = existing.length > 0 && !existing.endsWith("\n") ? "\n" : "";
|
|
@@ -9308,7 +9369,7 @@ function resyncModelPolicy(cwd) {
|
|
|
9308
9369
|
const desired = {};
|
|
9309
9370
|
const agentFiles = readdirSync10(AGENTS_HOME, { withFileTypes: true }).filter((d) => d.isFile() && d.name.endsWith(".md"));
|
|
9310
9371
|
for (const f of agentFiles) {
|
|
9311
|
-
const content =
|
|
9372
|
+
const content = readFileSync20(join20(AGENTS_HOME, f.name), "utf8");
|
|
9312
9373
|
const fm = parseAgentFrontmatter(content);
|
|
9313
9374
|
if (fm.name && fm.model)
|
|
9314
9375
|
desired[fm.name] = fm.model;
|
|
@@ -9318,7 +9379,7 @@ function resyncModelPolicy(cwd) {
|
|
|
9318
9379
|
let existing = {};
|
|
9319
9380
|
if (existsSync17(policyPath)) {
|
|
9320
9381
|
try {
|
|
9321
|
-
existing = JSON.parse(
|
|
9382
|
+
existing = JSON.parse(readFileSync20(policyPath, "utf8"));
|
|
9322
9383
|
} catch {
|
|
9323
9384
|
}
|
|
9324
9385
|
}
|
|
@@ -9422,7 +9483,7 @@ async function refresh(opts) {
|
|
|
9422
9483
|
templateOverwritten = result.overwritten;
|
|
9423
9484
|
log.ok(` applied: +${templateAdded} added, ~${templateOverwritten} overwritten`);
|
|
9424
9485
|
if (templateOverwritten > 0) {
|
|
9425
|
-
log.info(` backup saved to: ${
|
|
9486
|
+
log.info(` backup saved to: ${relative7(cwd, backupRoot).replace(/\\/g, "/")}`);
|
|
9426
9487
|
if (ensureGitignoreEntry2(cwd, ".cdd/.refresh-backup/")) {
|
|
9427
9488
|
log.info(" added `.cdd/.refresh-backup/` to .gitignore");
|
|
9428
9489
|
}
|
|
@@ -9498,7 +9559,7 @@ async function refresh(opts) {
|
|
|
9498
9559
|
if (apply) {
|
|
9499
9560
|
log.ok("refresh complete.");
|
|
9500
9561
|
if (backupRoot) {
|
|
9501
|
-
log.info(`Backup of overwritten templates: ${
|
|
9562
|
+
log.info(`Backup of overwritten templates: ${relative7(cwd, backupRoot).replace(/\\/g, "/")}`);
|
|
9502
9563
|
}
|
|
9503
9564
|
log.info("Next: review changes with `git diff`, then commit:");
|
|
9504
9565
|
log.dim(" git add .cdd/code-map.yml .cdd/model-policy.json specs/templates tests/templates ci-templates .github/workflows");
|
|
@@ -9527,9 +9588,9 @@ var doctor_exports = {};
|
|
|
9527
9588
|
__export(doctor_exports, {
|
|
9528
9589
|
doctor: () => doctor
|
|
9529
9590
|
});
|
|
9530
|
-
import { existsSync as existsSync18, readdirSync as readdirSync11, readFileSync as
|
|
9531
|
-
import { createHash as
|
|
9532
|
-
import { join as join21 } from "path";
|
|
9591
|
+
import { existsSync as existsSync18, readdirSync as readdirSync11, readFileSync as readFileSync21 } from "fs";
|
|
9592
|
+
import { createHash as createHash7 } from "crypto";
|
|
9593
|
+
import { join as join21, relative as relative8 } from "path";
|
|
9533
9594
|
function fileExists(cwd, relPath) {
|
|
9534
9595
|
return existsSync18(join21(cwd, relPath));
|
|
9535
9596
|
}
|
|
@@ -9545,21 +9606,17 @@ function findFiles(dir, predicate, found = []) {
|
|
|
9545
9606
|
}
|
|
9546
9607
|
return found;
|
|
9547
9608
|
}
|
|
9548
|
-
function
|
|
9549
|
-
|
|
9550
|
-
|
|
9551
|
-
|
|
9552
|
-
|
|
9553
|
-
|
|
9554
|
-
}
|
|
9555
|
-
function inputDigest(paths) {
|
|
9556
|
-
const combined = paths.slice().sort().map((p) => `${p}:${sha256OfFile3(p)}`).join("\n");
|
|
9557
|
-
return createHash5("sha256").update(combined).digest("hex");
|
|
9609
|
+
function inputDigest(paths, cwd) {
|
|
9610
|
+
const combined = paths.slice().sort().map((p) => {
|
|
9611
|
+
const rel = relative8(cwd, p).replace(/\\/g, "/");
|
|
9612
|
+
return `${rel}:${sha256OfFileNormalized(p)}`;
|
|
9613
|
+
}).join("\n");
|
|
9614
|
+
return createHash7("sha256").update(combined).digest("hex");
|
|
9558
9615
|
}
|
|
9559
9616
|
function readContextIndexMetadata(filePath) {
|
|
9560
9617
|
if (!existsSync18(filePath))
|
|
9561
9618
|
return {};
|
|
9562
|
-
const text =
|
|
9619
|
+
const text = readFileSync21(filePath, "utf8");
|
|
9563
9620
|
const out = {};
|
|
9564
9621
|
const digestMatch = text.match(/^inputs-digest:\s*([a-f0-9]+)/m);
|
|
9565
9622
|
if (digestMatch)
|
|
@@ -9587,7 +9644,7 @@ function checkContextFreshness(cwd) {
|
|
|
9587
9644
|
}
|
|
9588
9645
|
const projectMapMeta = readContextIndexMetadata(projectMap);
|
|
9589
9646
|
const contractsIndexMeta = readContextIndexMetadata(contractsIndex);
|
|
9590
|
-
const projectInputDigest = inputDigest([contextPolicy].filter(existsSync18));
|
|
9647
|
+
const projectInputDigest = inputDigest([contextPolicy].filter(existsSync18), cwd);
|
|
9591
9648
|
if (projectMapMeta.inputsDigest === void 0) {
|
|
9592
9649
|
findings.push({
|
|
9593
9650
|
level: "warning",
|
|
@@ -9599,7 +9656,7 @@ function checkContextFreshness(cwd) {
|
|
|
9599
9656
|
message: "specs/context/project-map.md inputs changed (.cdd/context-policy.json); re-run cdd-kit context-scan"
|
|
9600
9657
|
});
|
|
9601
9658
|
}
|
|
9602
|
-
const contractsInputDigest = inputDigest(contractFiles);
|
|
9659
|
+
const contractsInputDigest = inputDigest(contractFiles, cwd);
|
|
9603
9660
|
if (contractsIndexMeta.inputsDigest === void 0) {
|
|
9604
9661
|
findings.push({
|
|
9605
9662
|
level: "warning",
|
|
@@ -9624,7 +9681,7 @@ function checkContextFreshness(cwd) {
|
|
|
9624
9681
|
}
|
|
9625
9682
|
function readAgentModel(path) {
|
|
9626
9683
|
try {
|
|
9627
|
-
const text =
|
|
9684
|
+
const text = readFileSync21(path, "utf8");
|
|
9628
9685
|
const m = text.match(/^model:\s*(\S+)/m);
|
|
9629
9686
|
return m ? m[1] : null;
|
|
9630
9687
|
} catch {
|
|
@@ -9637,7 +9694,7 @@ function checkModelPolicyDrift(cwd) {
|
|
|
9637
9694
|
return [];
|
|
9638
9695
|
let policy;
|
|
9639
9696
|
try {
|
|
9640
|
-
policy = JSON.parse(
|
|
9697
|
+
policy = JSON.parse(readFileSync21(policyPath, "utf8"));
|
|
9641
9698
|
} catch {
|
|
9642
9699
|
return [{ level: "warning", message: ".cdd/model-policy.json is not valid JSON" }];
|
|
9643
9700
|
}
|
|
@@ -9738,7 +9795,7 @@ function checkCodeMap(cwd) {
|
|
|
9738
9795
|
const more = probe.staleCount > 3 ? ` (+${probe.staleCount - 3} more)` : "";
|
|
9739
9796
|
findings.push({ level: "warning", message: `code-map stale: ${top}${more}; run \`cdd-kit code-map\`` });
|
|
9740
9797
|
}
|
|
9741
|
-
const text =
|
|
9798
|
+
const text = readFileSync21(mapPath, "utf8");
|
|
9742
9799
|
const m = text.match(/^# files: (\d+), src-lines: (\d+), map-lines: (\d+), compression: ([\d.]+)x/m);
|
|
9743
9800
|
if (m) {
|
|
9744
9801
|
findings.push({ level: "ok", message: `code-map: ${m[1]} files, ${m[4]}x compression` });
|
|
@@ -9818,7 +9875,7 @@ async function attemptAutoFixes(cwd, report) {
|
|
|
9818
9875
|
try {
|
|
9819
9876
|
let existing = {};
|
|
9820
9877
|
try {
|
|
9821
|
-
existing = JSON.parse(
|
|
9878
|
+
existing = JSON.parse(readFileSync21(policyPath, "utf8"));
|
|
9822
9879
|
} catch {
|
|
9823
9880
|
}
|
|
9824
9881
|
const merged = {
|
|
@@ -9911,6 +9968,7 @@ var init_doctor = __esm({
|
|
|
9911
9968
|
init_logger();
|
|
9912
9969
|
init_provider();
|
|
9913
9970
|
init_freshness();
|
|
9971
|
+
init_digest();
|
|
9914
9972
|
}
|
|
9915
9973
|
});
|
|
9916
9974
|
|
|
@@ -9919,7 +9977,7 @@ var lint_agents_exports = {};
|
|
|
9919
9977
|
__export(lint_agents_exports, {
|
|
9920
9978
|
lintAgents: () => lintAgents
|
|
9921
9979
|
});
|
|
9922
|
-
import { readdirSync as readdirSync12, readFileSync as
|
|
9980
|
+
import { readdirSync as readdirSync12, readFileSync as readFileSync22 } from "fs";
|
|
9923
9981
|
import { join as join22 } from "path";
|
|
9924
9982
|
import { load as yamlLoad2 } from "js-yaml";
|
|
9925
9983
|
function extractRequiredArtifactsSection(content) {
|
|
@@ -9974,7 +10032,7 @@ async function lintAgents(opts) {
|
|
|
9974
10032
|
const filePath = join22(agentsDir, filename);
|
|
9975
10033
|
let content;
|
|
9976
10034
|
try {
|
|
9977
|
-
content =
|
|
10035
|
+
content = readFileSync22(filePath, "utf8");
|
|
9978
10036
|
} catch {
|
|
9979
10037
|
violations.push({
|
|
9980
10038
|
file: filename,
|
|
@@ -10087,7 +10145,7 @@ __export(archive_exports, {
|
|
|
10087
10145
|
archive: () => archive
|
|
10088
10146
|
});
|
|
10089
10147
|
import { join as join23 } from "path";
|
|
10090
|
-
import { existsSync as existsSync19, mkdirSync as mkdirSync10, renameSync as renameSync2, readFileSync as
|
|
10148
|
+
import { existsSync as existsSync19, mkdirSync as mkdirSync10, renameSync as renameSync2, readFileSync as readFileSync23, writeFileSync as writeFileSync11, appendFileSync, cpSync as cpSync3, rmSync as rmSync3 } from "fs";
|
|
10091
10149
|
import yaml4 from "js-yaml";
|
|
10092
10150
|
async function archive(changeId) {
|
|
10093
10151
|
const cwd = process.cwd();
|
|
@@ -10107,7 +10165,7 @@ async function archive(changeId) {
|
|
|
10107
10165
|
const tasksPath = join23(changeDir, "tasks.yml");
|
|
10108
10166
|
if (existsSync19(tasksPath)) {
|
|
10109
10167
|
try {
|
|
10110
|
-
const raw =
|
|
10168
|
+
const raw = readFileSync23(tasksPath, "utf8");
|
|
10111
10169
|
const data = yaml4.load(raw);
|
|
10112
10170
|
if (data?.status === "gate-blocked") {
|
|
10113
10171
|
log.warn("tasks.yml has status: gate-blocked \u2014 archiving anyway (change was paused).");
|
|
@@ -10163,7 +10221,7 @@ __export(abandon_exports, {
|
|
|
10163
10221
|
abandon: () => abandon
|
|
10164
10222
|
});
|
|
10165
10223
|
import { join as join24 } from "path";
|
|
10166
|
-
import { existsSync as existsSync20, readFileSync as
|
|
10224
|
+
import { existsSync as existsSync20, readFileSync as readFileSync24, writeFileSync as writeFileSync12, appendFileSync as appendFileSync2, mkdirSync as mkdirSync11 } from "fs";
|
|
10167
10225
|
import yaml5 from "js-yaml";
|
|
10168
10226
|
async function abandon(changeId, opts) {
|
|
10169
10227
|
const cwd = process.cwd();
|
|
@@ -10174,7 +10232,7 @@ async function abandon(changeId, opts) {
|
|
|
10174
10232
|
process.exit(1);
|
|
10175
10233
|
}
|
|
10176
10234
|
if (existsSync20(tasksPath)) {
|
|
10177
|
-
const raw =
|
|
10235
|
+
const raw = readFileSync24(tasksPath, "utf8");
|
|
10178
10236
|
const data = yaml5.load(raw) ?? {};
|
|
10179
10237
|
data["status"] = "abandoned";
|
|
10180
10238
|
if (!data["change-id"]) {
|
|
@@ -10217,7 +10275,7 @@ __export(list_changes_exports, {
|
|
|
10217
10275
|
listChanges: () => listChanges
|
|
10218
10276
|
});
|
|
10219
10277
|
import { join as join25 } from "path";
|
|
10220
|
-
import { existsSync as existsSync21, readdirSync as readdirSync13, readFileSync as
|
|
10278
|
+
import { existsSync as existsSync21, readdirSync as readdirSync13, readFileSync as readFileSync25 } from "fs";
|
|
10221
10279
|
import yaml6 from "js-yaml";
|
|
10222
10280
|
async function listChanges() {
|
|
10223
10281
|
const cwd = process.cwd();
|
|
@@ -10237,7 +10295,7 @@ async function listChanges() {
|
|
|
10237
10295
|
let pending = 0;
|
|
10238
10296
|
if (existsSync21(tasksPath)) {
|
|
10239
10297
|
try {
|
|
10240
|
-
const raw =
|
|
10298
|
+
const raw = readFileSync25(tasksPath, "utf8");
|
|
10241
10299
|
const data = yaml6.load(raw);
|
|
10242
10300
|
if (data?.status)
|
|
10243
10301
|
status = data.status;
|
|
@@ -10268,7 +10326,7 @@ __export(context_exports, {
|
|
|
10268
10326
|
rejectContextExpansion: () => rejectContextExpansion,
|
|
10269
10327
|
requestContextExpansion: () => requestContextExpansion
|
|
10270
10328
|
});
|
|
10271
|
-
import { existsSync as existsSync22, readFileSync as
|
|
10329
|
+
import { existsSync as existsSync22, readFileSync as readFileSync26, writeFileSync as writeFileSync13 } from "fs";
|
|
10272
10330
|
import { join as join26 } from "path";
|
|
10273
10331
|
function normalizePath(path) {
|
|
10274
10332
|
return path.replace(/\\/g, "/").replace(/^\.\//, "").trim();
|
|
@@ -10291,7 +10349,7 @@ function readManifest(changeId) {
|
|
|
10291
10349
|
log.error(`context manifest not found: specs/changes/${changeId}/context-manifest.md`);
|
|
10292
10350
|
process.exit(1);
|
|
10293
10351
|
}
|
|
10294
|
-
return
|
|
10352
|
+
return readFileSync26(manifestPath, "utf8");
|
|
10295
10353
|
}
|
|
10296
10354
|
function writeManifest(changeId, content) {
|
|
10297
10355
|
writeFileSync13(manifestPathFor(changeId), content.endsWith("\n") ? content : `${content}
|
|
@@ -10519,7 +10577,7 @@ var init_context = __esm({
|
|
|
10519
10577
|
});
|
|
10520
10578
|
|
|
10521
10579
|
// src/cli/index.ts
|
|
10522
|
-
import { readFileSync as
|
|
10580
|
+
import { readFileSync as readFileSync27 } from "fs";
|
|
10523
10581
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
10524
10582
|
import { dirname as dirname7, join as join27 } from "path";
|
|
10525
10583
|
import { Command } from "commander";
|
|
@@ -10941,22 +10999,19 @@ init_update();
|
|
|
10941
10999
|
|
|
10942
11000
|
// src/commands/new-change.ts
|
|
10943
11001
|
init_paths();
|
|
10944
|
-
import { join as join9 } from "path";
|
|
10945
|
-
import { createHash as
|
|
10946
|
-
import { existsSync as existsSync8, readFileSync as
|
|
11002
|
+
import { join as join9, relative as relative3 } from "path";
|
|
11003
|
+
import { createHash as createHash4 } from "crypto";
|
|
11004
|
+
import { existsSync as existsSync8, readFileSync as readFileSync8, readdirSync as readdirSync5, writeFileSync as writeFileSync4 } from "fs";
|
|
10947
11005
|
import yaml from "js-yaml";
|
|
10948
11006
|
init_logger();
|
|
10949
11007
|
init_context_scan();
|
|
10950
|
-
|
|
10951
|
-
|
|
10952
|
-
|
|
10953
|
-
|
|
10954
|
-
return
|
|
10955
|
-
}
|
|
10956
|
-
|
|
10957
|
-
function inputsDigest2(paths) {
|
|
10958
|
-
const combined = paths.slice().sort().map((p) => `${p}:${sha256OfFile2(p)}`).join("\n");
|
|
10959
|
-
return createHash3("sha256").update(combined).digest("hex");
|
|
11008
|
+
init_digest();
|
|
11009
|
+
function inputsDigest2(paths, cwd) {
|
|
11010
|
+
const combined = paths.slice().sort().map((p) => {
|
|
11011
|
+
const rel = relative3(cwd, p).replace(/\\/g, "/");
|
|
11012
|
+
return `${rel}:${sha256OfFileNormalized(p)}`;
|
|
11013
|
+
}).join("\n");
|
|
11014
|
+
return createHash4("sha256").update(combined).digest("hex");
|
|
10960
11015
|
}
|
|
10961
11016
|
function findContractFiles2(dir, found = []) {
|
|
10962
11017
|
if (!existsSync8(dir))
|
|
@@ -10974,7 +11029,7 @@ function findContractFiles2(dir, found = []) {
|
|
|
10974
11029
|
function readIndexDigest(filePath) {
|
|
10975
11030
|
if (!existsSync8(filePath))
|
|
10976
11031
|
return null;
|
|
10977
|
-
const m =
|
|
11032
|
+
const m = readFileSync8(filePath, "utf8").match(/^inputs-digest:\s*([a-f0-9]+)/m);
|
|
10978
11033
|
return m ? m[1] : null;
|
|
10979
11034
|
}
|
|
10980
11035
|
async function ensureFreshContextIndexes(cwd) {
|
|
@@ -10983,8 +11038,8 @@ async function ensureFreshContextIndexes(cwd) {
|
|
|
10983
11038
|
const policyPath = join9(cwd, ".cdd", "context-policy.json");
|
|
10984
11039
|
const policyInputs = [policyPath].filter(existsSync8);
|
|
10985
11040
|
const contractFiles = findContractFiles2(join9(cwd, "contracts"));
|
|
10986
|
-
const wantProjectDigest = inputsDigest2(policyInputs);
|
|
10987
|
-
const wantContractsDigest = inputsDigest2(contractFiles);
|
|
11041
|
+
const wantProjectDigest = inputsDigest2(policyInputs, cwd);
|
|
11042
|
+
const wantContractsDigest = inputsDigest2(contractFiles, cwd);
|
|
10988
11043
|
const haveProjectDigest = readIndexDigest(projectMap);
|
|
10989
11044
|
const haveContractsDigest = readIndexDigest(contractsIndex);
|
|
10990
11045
|
const needsScan = !existsSync8(projectMap) || !existsSync8(contractsIndex) || haveProjectDigest !== wantProjectDigest || haveContractsDigest !== wantContractsDigest;
|
|
@@ -11066,7 +11121,7 @@ async function newChange(name, opts) {
|
|
|
11066
11121
|
if (dependencies.length > 0) {
|
|
11067
11122
|
const tasksPath = join9(changeDir, "tasks.yml");
|
|
11068
11123
|
if (existsSync8(tasksPath)) {
|
|
11069
|
-
const raw =
|
|
11124
|
+
const raw = readFileSync8(tasksPath, "utf8");
|
|
11070
11125
|
const data = yaml.load(raw) ?? {};
|
|
11071
11126
|
data["depends-on"] = dependencies;
|
|
11072
11127
|
writeFileSync4(tasksPath, yaml.dump(data, { lineWidth: -1, noRefs: true }), "utf8");
|
|
@@ -11170,9 +11225,9 @@ async function validate(opts) {
|
|
|
11170
11225
|
var import_ajv = __toESM(require_ajv(), 1);
|
|
11171
11226
|
var import_ajv_formats = __toESM(require_dist(), 1);
|
|
11172
11227
|
init_logger();
|
|
11173
|
-
import { existsSync as
|
|
11228
|
+
import { existsSync as existsSync13, readFileSync as readFileSync16, readdirSync as readdirSync7 } from "fs";
|
|
11174
11229
|
import { homedir as homedir3 } from "os";
|
|
11175
|
-
import { join as
|
|
11230
|
+
import { join as join16 } from "path";
|
|
11176
11231
|
import yaml2 from "js-yaml";
|
|
11177
11232
|
import picomatch2 from "picomatch";
|
|
11178
11233
|
|
|
@@ -11350,15 +11405,15 @@ function parseContextManifest(content) {
|
|
|
11350
11405
|
}
|
|
11351
11406
|
function extractRequiredArtifactTypes(cwd, agentName) {
|
|
11352
11407
|
const candidates = [
|
|
11353
|
-
|
|
11354
|
-
|
|
11408
|
+
join16(cwd, ".claude", "agents", `${agentName}.md`),
|
|
11409
|
+
join16(homedir3(), ".claude", "agents", `${agentName}.md`)
|
|
11355
11410
|
];
|
|
11356
11411
|
let content = null;
|
|
11357
11412
|
for (const candidate of candidates) {
|
|
11358
|
-
if (!
|
|
11413
|
+
if (!existsSync13(candidate))
|
|
11359
11414
|
continue;
|
|
11360
11415
|
try {
|
|
11361
|
-
content =
|
|
11416
|
+
content = readFileSync16(candidate, "utf8");
|
|
11362
11417
|
break;
|
|
11363
11418
|
} catch {
|
|
11364
11419
|
}
|
|
@@ -11395,11 +11450,11 @@ function loadContextPolicy(cwd) {
|
|
|
11395
11450
|
unknownFilesRead: "warn-for-legacy-fail-for-new"
|
|
11396
11451
|
}
|
|
11397
11452
|
};
|
|
11398
|
-
const policyPath =
|
|
11399
|
-
if (!
|
|
11453
|
+
const policyPath = join16(cwd, ".cdd", "context-policy.json");
|
|
11454
|
+
if (!existsSync13(policyPath))
|
|
11400
11455
|
return defaults;
|
|
11401
11456
|
try {
|
|
11402
|
-
const custom = JSON.parse(
|
|
11457
|
+
const custom = JSON.parse(readFileSync16(policyPath, "utf8"));
|
|
11403
11458
|
return {
|
|
11404
11459
|
...defaults,
|
|
11405
11460
|
...custom,
|
|
@@ -11413,7 +11468,7 @@ function loadContextPolicy(cwd) {
|
|
|
11413
11468
|
}
|
|
11414
11469
|
function loadYamlFile(path) {
|
|
11415
11470
|
try {
|
|
11416
|
-
const raw =
|
|
11471
|
+
const raw = readFileSync16(path, "utf8");
|
|
11417
11472
|
return { data: yaml2.load(raw), parseError: null };
|
|
11418
11473
|
} catch (err) {
|
|
11419
11474
|
return { data: null, parseError: err.message };
|
|
@@ -11444,8 +11499,8 @@ function ajvErrorsToMessages(errors, prefix, knownKeys) {
|
|
|
11444
11499
|
return out;
|
|
11445
11500
|
}
|
|
11446
11501
|
function isContextGovernedChange(changeDir) {
|
|
11447
|
-
const tasksPath =
|
|
11448
|
-
if (!
|
|
11502
|
+
const tasksPath = join16(changeDir, "tasks.yml");
|
|
11503
|
+
if (!existsSync13(tasksPath))
|
|
11449
11504
|
return false;
|
|
11450
11505
|
const { data } = loadYamlFile(tasksPath);
|
|
11451
11506
|
return data?.["context-governance"] === "v1";
|
|
@@ -11473,12 +11528,12 @@ function lintTasksFile(tasksPath, errors, warnings) {
|
|
|
11473
11528
|
return data;
|
|
11474
11529
|
}
|
|
11475
11530
|
function resolveTier(changeDir) {
|
|
11476
|
-
const classifPath =
|
|
11477
|
-
const classificationPresent =
|
|
11478
|
-
const classificationText = classificationPresent ?
|
|
11531
|
+
const classifPath = join16(changeDir, "change-classification.md");
|
|
11532
|
+
const classificationPresent = existsSync13(classifPath);
|
|
11533
|
+
const classificationText = classificationPresent ? readFileSync16(classifPath, "utf8") : "";
|
|
11479
11534
|
const classificationHasLooseMarker = classificationPresent && TIER_PATTERN.test(classificationText);
|
|
11480
|
-
const tasksPath =
|
|
11481
|
-
if (
|
|
11535
|
+
const tasksPath = join16(changeDir, "tasks.yml");
|
|
11536
|
+
if (existsSync13(tasksPath)) {
|
|
11482
11537
|
const { data } = loadYamlFile(tasksPath);
|
|
11483
11538
|
const t = data?.tier;
|
|
11484
11539
|
if (typeof t === "number" && t >= 0 && t <= 5) {
|
|
@@ -11524,7 +11579,7 @@ function enforceTierRequirements(changeDir, agentLogDir, errors, warnings) {
|
|
|
11524
11579
|
return;
|
|
11525
11580
|
}
|
|
11526
11581
|
const tier = resolution.tier;
|
|
11527
|
-
const agentLogFiles = agentLogDir &&
|
|
11582
|
+
const agentLogFiles = agentLogDir && existsSync13(agentLogDir) ? readdirSync7(agentLogDir).map((f) => f.replace(/\.ya?ml$/, "")) : [];
|
|
11528
11583
|
if (tier <= 1) {
|
|
11529
11584
|
for (const required of ["e2e-resilience-engineer", "monkey-test-engineer", "stress-soak-engineer"]) {
|
|
11530
11585
|
if (!agentLogFiles.includes(required)) {
|
|
@@ -11540,7 +11595,7 @@ function enforceTierRequirements(changeDir, agentLogDir, errors, warnings) {
|
|
|
11540
11595
|
}
|
|
11541
11596
|
}
|
|
11542
11597
|
if (resolution.source === "tasks-frontmatter" && resolution.classificationPresent) {
|
|
11543
|
-
const text =
|
|
11598
|
+
const text = readFileSync16(join16(changeDir, "change-classification.md"), "utf8");
|
|
11544
11599
|
const structured = text.match(/^## Tier\s*\n\s*-\s*(\d)\s*$/m);
|
|
11545
11600
|
const bold = text.match(/\*\*Tier:\*\*\s*Tier\s*(\d)\b/i);
|
|
11546
11601
|
const classifTier = structured ? parseInt(structured[1], 10) : bold ? parseInt(bold[1], 10) : NaN;
|
|
@@ -11552,11 +11607,11 @@ function enforceTierRequirements(changeDir, agentLogDir, errors, warnings) {
|
|
|
11552
11607
|
}
|
|
11553
11608
|
}
|
|
11554
11609
|
function isArchivedChange(cwd, changeId) {
|
|
11555
|
-
const archiveRoot =
|
|
11556
|
-
if (!
|
|
11610
|
+
const archiveRoot = join16(cwd, "specs", "archive");
|
|
11611
|
+
if (!existsSync13(archiveRoot))
|
|
11557
11612
|
return false;
|
|
11558
11613
|
const years = readdirSync7(archiveRoot, { withFileTypes: true }).filter((d) => d.isDirectory());
|
|
11559
|
-
return years.some((year) =>
|
|
11614
|
+
return years.some((year) => existsSync13(join16(archiveRoot, year.name, changeId)));
|
|
11560
11615
|
}
|
|
11561
11616
|
function detectDependencyCycle(cwd, startChangeId) {
|
|
11562
11617
|
const visited = /* @__PURE__ */ new Set();
|
|
@@ -11569,8 +11624,8 @@ function detectDependencyCycle(cwd, startChangeId) {
|
|
|
11569
11624
|
return null;
|
|
11570
11625
|
visited.add(id);
|
|
11571
11626
|
stack.push(id);
|
|
11572
|
-
const tasksPath =
|
|
11573
|
-
if (
|
|
11627
|
+
const tasksPath = join16(cwd, "specs", "changes", id, "tasks.yml");
|
|
11628
|
+
if (existsSync13(tasksPath)) {
|
|
11574
11629
|
const { data } = loadYamlFile(tasksPath);
|
|
11575
11630
|
const deps = data?.["depends-on"] ?? [];
|
|
11576
11631
|
for (const dep of deps) {
|
|
@@ -11585,8 +11640,8 @@ function detectDependencyCycle(cwd, startChangeId) {
|
|
|
11585
11640
|
return visit(startChangeId);
|
|
11586
11641
|
}
|
|
11587
11642
|
function validateDependencies(cwd, changeId, changeDir) {
|
|
11588
|
-
const tasksPath =
|
|
11589
|
-
if (!
|
|
11643
|
+
const tasksPath = join16(changeDir, "tasks.yml");
|
|
11644
|
+
if (!existsSync13(tasksPath))
|
|
11590
11645
|
return [];
|
|
11591
11646
|
const { data } = loadYamlFile(tasksPath);
|
|
11592
11647
|
const dependencies = data?.["depends-on"] ?? [];
|
|
@@ -11600,10 +11655,10 @@ function validateDependencies(cwd, changeId, changeDir) {
|
|
|
11600
11655
|
errors.push(`tasks.yml: change cannot depend on itself (${dep})`);
|
|
11601
11656
|
continue;
|
|
11602
11657
|
}
|
|
11603
|
-
const upstreamDir =
|
|
11604
|
-
if (
|
|
11605
|
-
const upstreamTasks =
|
|
11606
|
-
if (!
|
|
11658
|
+
const upstreamDir = join16(cwd, "specs", "changes", dep);
|
|
11659
|
+
if (existsSync13(upstreamDir)) {
|
|
11660
|
+
const upstreamTasks = join16(upstreamDir, "tasks.yml");
|
|
11661
|
+
if (!existsSync13(upstreamTasks)) {
|
|
11607
11662
|
errors.push(`dependency ${dep}: missing tasks.yml`);
|
|
11608
11663
|
continue;
|
|
11609
11664
|
}
|
|
@@ -11624,8 +11679,8 @@ async function gate(changeId, opts = {}) {
|
|
|
11624
11679
|
const strict = opts.strict ?? false;
|
|
11625
11680
|
const lax = opts.lax ?? false;
|
|
11626
11681
|
const cwd = process.cwd();
|
|
11627
|
-
const changeDir =
|
|
11628
|
-
if (!
|
|
11682
|
+
const changeDir = join16(cwd, "specs", "changes", changeId);
|
|
11683
|
+
if (!existsSync13(changeDir)) {
|
|
11629
11684
|
log.error(`change not found: ${changeId} (looked in ${changeDir})`);
|
|
11630
11685
|
process.exit(1);
|
|
11631
11686
|
}
|
|
@@ -11651,13 +11706,13 @@ async function gate(changeId, opts = {}) {
|
|
|
11651
11706
|
}
|
|
11652
11707
|
const contextPolicy = loadContextPolicy(cwd);
|
|
11653
11708
|
const isNewChange = isContextGovernedChange(changeDir);
|
|
11654
|
-
const manifestPath =
|
|
11655
|
-
const hasManifest =
|
|
11709
|
+
const manifestPath = join16(changeDir, "context-manifest.md");
|
|
11710
|
+
const hasManifest = existsSync13(manifestPath);
|
|
11656
11711
|
let allowedPaths = [];
|
|
11657
11712
|
let approvedExpansions = [];
|
|
11658
11713
|
errors.push(...validateDependencies(cwd, changeId, changeDir));
|
|
11659
11714
|
if (hasManifest) {
|
|
11660
|
-
const manifest = parseContextManifest(
|
|
11715
|
+
const manifest = parseContextManifest(readFileSync16(manifestPath, "utf8"));
|
|
11661
11716
|
allowedPaths = manifest.allowedPaths;
|
|
11662
11717
|
approvedExpansions = manifest.approvedExpansions;
|
|
11663
11718
|
if (manifest.pendingExpansions > 0) {
|
|
@@ -11675,7 +11730,7 @@ async function gate(changeId, opts = {}) {
|
|
|
11675
11730
|
}
|
|
11676
11731
|
continue;
|
|
11677
11732
|
}
|
|
11678
|
-
if (!
|
|
11733
|
+
if (!existsSync13(join16(changeDir, f))) {
|
|
11679
11734
|
errors.push(`missing required artifact: ${f}`);
|
|
11680
11735
|
}
|
|
11681
11736
|
}
|
|
@@ -11685,21 +11740,21 @@ async function gate(changeId, opts = {}) {
|
|
|
11685
11740
|
continue;
|
|
11686
11741
|
if (f === "tasks.yml")
|
|
11687
11742
|
continue;
|
|
11688
|
-
const content =
|
|
11743
|
+
const content = readFileSync16(join16(changeDir, f), "utf8");
|
|
11689
11744
|
const minChars = MIN_CHARS[f] ?? 100;
|
|
11690
11745
|
if (meaningfulChars(content) < minChars) {
|
|
11691
11746
|
errors.push(`${f}: appears to be a stub (< ${minChars} meaningful chars)`);
|
|
11692
11747
|
}
|
|
11693
11748
|
}
|
|
11694
|
-
const classifPath =
|
|
11749
|
+
const classifPath = join16(changeDir, "change-classification.md");
|
|
11695
11750
|
const tierResolution = resolveTier(changeDir);
|
|
11696
|
-
if (tierResolution.tier === null &&
|
|
11751
|
+
if (tierResolution.tier === null && existsSync13(classifPath) && !tierResolution.classificationHasLooseMarker) {
|
|
11697
11752
|
errors.push("change-classification.md: missing tier/risk marker (set tier in tasks.yml frontmatter, or include Tier 0-5 / low|medium|high|critical in change-classification.md)");
|
|
11698
11753
|
}
|
|
11699
11754
|
}
|
|
11700
|
-
const tasksPath =
|
|
11755
|
+
const tasksPath = join16(changeDir, "tasks.yml");
|
|
11701
11756
|
let tasksData = null;
|
|
11702
|
-
if (
|
|
11757
|
+
if (existsSync13(tasksPath)) {
|
|
11703
11758
|
tasksData = lintTasksFile(tasksPath, errors, warnings);
|
|
11704
11759
|
}
|
|
11705
11760
|
if (tasksData) {
|
|
@@ -11713,11 +11768,11 @@ async function gate(changeId, opts = {}) {
|
|
|
11713
11768
|
}
|
|
11714
11769
|
}
|
|
11715
11770
|
}
|
|
11716
|
-
const agentLogDir =
|
|
11717
|
-
if (
|
|
11771
|
+
const agentLogDir = join16(changeDir, "agent-log");
|
|
11772
|
+
if (existsSync13(agentLogDir)) {
|
|
11718
11773
|
const logFiles = readdirSync7(agentLogDir).filter((f) => f.endsWith(".yml"));
|
|
11719
11774
|
for (const f of logFiles) {
|
|
11720
|
-
const fullPath =
|
|
11775
|
+
const fullPath = join16(agentLogDir, f);
|
|
11721
11776
|
const { data, parseError } = loadYamlFile(fullPath);
|
|
11722
11777
|
if (parseError) {
|
|
11723
11778
|
errors.push(`agent-log/${f}: invalid YAML: ${parseError}`);
|
|
@@ -11785,9 +11840,9 @@ async function gate(changeId, opts = {}) {
|
|
|
11785
11840
|
errors.push(`agent-log/${f}: read unauthorized path -> ${p} (not in allowed paths or approved expansions)`);
|
|
11786
11841
|
}
|
|
11787
11842
|
}
|
|
11788
|
-
const runtimeLog =
|
|
11789
|
-
if (
|
|
11790
|
-
const runtimePaths =
|
|
11843
|
+
const runtimeLog = join16(cwd, ".cdd", "runtime", `${changeId}-files-read.jsonl`);
|
|
11844
|
+
if (existsSync13(runtimeLog)) {
|
|
11845
|
+
const runtimePaths = readFileSync16(runtimeLog, "utf8").split("\n").filter(Boolean).map((line) => {
|
|
11791
11846
|
try {
|
|
11792
11847
|
return JSON.parse(line).path;
|
|
11793
11848
|
} catch {
|
|
@@ -11823,8 +11878,8 @@ async function gate(changeId, opts = {}) {
|
|
|
11823
11878
|
continue;
|
|
11824
11879
|
const pathPart = pointer.split(":")[0];
|
|
11825
11880
|
if (pathPart.includes("/") && !pointer.startsWith("http")) {
|
|
11826
|
-
const abs =
|
|
11827
|
-
if (!
|
|
11881
|
+
const abs = join16(cwd, pathPart);
|
|
11882
|
+
if (!existsSync13(abs)) {
|
|
11828
11883
|
errors.push(`agent-log/${f}: artifact pointer not found: ${pathPart}`);
|
|
11829
11884
|
}
|
|
11830
11885
|
}
|
|
@@ -11873,26 +11928,26 @@ async function gate(changeId, opts = {}) {
|
|
|
11873
11928
|
// src/commands/install-hooks.ts
|
|
11874
11929
|
init_paths();
|
|
11875
11930
|
init_logger();
|
|
11876
|
-
import { existsSync as
|
|
11877
|
-
import { join as
|
|
11931
|
+
import { existsSync as existsSync14, readFileSync as readFileSync17, writeFileSync as writeFileSync7, chmodSync as chmodSync2, mkdirSync as mkdirSync6 } from "fs";
|
|
11932
|
+
import { join as join17 } from "path";
|
|
11878
11933
|
var START_MARKER2 = "# cdd-kit-managed-block-start";
|
|
11879
11934
|
var END_MARKER2 = "# cdd-kit-managed-block-end";
|
|
11880
11935
|
async function installHooks() {
|
|
11881
11936
|
const cwd = process.cwd();
|
|
11882
|
-
const gitDir =
|
|
11883
|
-
if (!
|
|
11937
|
+
const gitDir = join17(cwd, ".git");
|
|
11938
|
+
if (!existsSync14(gitDir)) {
|
|
11884
11939
|
log.error("not a git repository (no .git/ found in cwd)");
|
|
11885
11940
|
process.exit(1);
|
|
11886
11941
|
}
|
|
11887
|
-
const hooksDir =
|
|
11888
|
-
|
|
11889
|
-
const dest =
|
|
11890
|
-
const ourHook =
|
|
11942
|
+
const hooksDir = join17(gitDir, "hooks");
|
|
11943
|
+
mkdirSync6(hooksDir, { recursive: true });
|
|
11944
|
+
const dest = join17(hooksDir, "pre-commit");
|
|
11945
|
+
const ourHook = readFileSync17(join17(ASSET.hooks, "pre-commit"), "utf8");
|
|
11891
11946
|
let final;
|
|
11892
|
-
if (!
|
|
11947
|
+
if (!existsSync14(dest)) {
|
|
11893
11948
|
final = ourHook;
|
|
11894
11949
|
} else {
|
|
11895
|
-
const existing =
|
|
11950
|
+
const existing = readFileSync17(dest, "utf8");
|
|
11896
11951
|
const startIdx = existing.indexOf(START_MARKER2);
|
|
11897
11952
|
const endIdx = existing.indexOf(END_MARKER2);
|
|
11898
11953
|
if (startIdx >= 0 && endIdx > startIdx) {
|
|
@@ -11916,7 +11971,7 @@ async function installHooks() {
|
|
|
11916
11971
|
}
|
|
11917
11972
|
}
|
|
11918
11973
|
}
|
|
11919
|
-
|
|
11974
|
+
writeFileSync7(dest, final, "utf8");
|
|
11920
11975
|
try {
|
|
11921
11976
|
chmodSync2(dest, 493);
|
|
11922
11977
|
} catch {
|
|
@@ -11927,7 +11982,7 @@ async function installHooks() {
|
|
|
11927
11982
|
|
|
11928
11983
|
// src/cli/index.ts
|
|
11929
11984
|
var __dirname2 = dirname7(fileURLToPath3(import.meta.url));
|
|
11930
|
-
var pkg = JSON.parse(
|
|
11985
|
+
var pkg = JSON.parse(readFileSync27(join27(__dirname2, "..", "..", "package.json"), "utf8"));
|
|
11931
11986
|
var program = new Command();
|
|
11932
11987
|
program.name("cdd-kit").description("Contract-Driven Delivery Kit CLI").version(pkg.version);
|
|
11933
11988
|
program.command("init").description(
|