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