contract-driven-delivery 2.0.8 → 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 +101 -0
- package/dist/cli/index.js +1666 -1523
- 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) {
|
|
@@ -415,7 +418,14 @@ function getForbiddenPaths(cwd) {
|
|
|
415
418
|
}
|
|
416
419
|
function isForbidden(relPath, forbidden) {
|
|
417
420
|
const normalized = relPath.replace(/\\/g, "/");
|
|
418
|
-
|
|
421
|
+
if (forbidden.some((pattern) => normalized === pattern || normalized.startsWith(`${pattern}/`))) {
|
|
422
|
+
return true;
|
|
423
|
+
}
|
|
424
|
+
for (const segment of normalized.split("/")) {
|
|
425
|
+
if (FORBIDDEN_DIRECTORY_NAMES.has(segment))
|
|
426
|
+
return true;
|
|
427
|
+
}
|
|
428
|
+
return false;
|
|
419
429
|
}
|
|
420
430
|
function buildTree(dir, cwd, forbidden, stats, prefix = "", depth = 0) {
|
|
421
431
|
const entries = readdirSync4(dir, { withFileTypes: true }).sort((a, b) => {
|
|
@@ -542,7 +552,7 @@ async function contextScan(opts = {}) {
|
|
|
542
552
|
`visible-files: ${treeStats.files}`,
|
|
543
553
|
`omitted-dirs: ${treeStats.omittedDirs}`,
|
|
544
554
|
`truncated-dirs: ${treeStats.truncatedDirs}`,
|
|
545
|
-
`inputs-digest: ${inputsDigest(projectMapInputs)}`,
|
|
555
|
+
`inputs-digest: ${inputsDigest(projectMapInputs, cwd)}`,
|
|
546
556
|
"---",
|
|
547
557
|
"",
|
|
548
558
|
"# Project Map",
|
|
@@ -608,7 +618,7 @@ async function contextScan(opts = {}) {
|
|
|
608
618
|
"schema-version: 1",
|
|
609
619
|
`contract-count: ${contractFiles.length}`,
|
|
610
620
|
`missing-summary-count: ${missingSummary}`,
|
|
611
|
-
`inputs-digest: ${inputsDigest(contractFiles)}`,
|
|
621
|
+
`inputs-digest: ${inputsDigest(contractFiles, cwd)}`,
|
|
612
622
|
"---",
|
|
613
623
|
"",
|
|
614
624
|
"# Contracts Index",
|
|
@@ -632,7 +642,7 @@ async function contextScan(opts = {}) {
|
|
|
632
642
|
log.ok("Created specs/context/contracts-index.md");
|
|
633
643
|
}
|
|
634
644
|
}
|
|
635
|
-
var DEFAULT_FORBIDDEN, PER_DIR_ENTRY_CAP;
|
|
645
|
+
var DEFAULT_FORBIDDEN, FORBIDDEN_DIRECTORY_NAMES, PER_DIR_ENTRY_CAP;
|
|
636
646
|
var init_context_scan = __esm({
|
|
637
647
|
"src/commands/context-scan.ts"() {
|
|
638
648
|
"use strict";
|
|
@@ -645,8 +655,47 @@ var init_context_scan = __esm({
|
|
|
645
655
|
"build",
|
|
646
656
|
"assets",
|
|
647
657
|
"specs/archive",
|
|
648
|
-
"specs/changes"
|
|
658
|
+
"specs/changes",
|
|
659
|
+
// cdd-kit runtime artifacts. Without these the user's local backups land
|
|
660
|
+
// in `specs/context/project-map.md`, polluting it and breaking the
|
|
661
|
+
// inputs-digest match for any fresh clone.
|
|
662
|
+
".cdd/.refresh-backup",
|
|
663
|
+
".cdd/migrate-backup",
|
|
664
|
+
".cdd/runtime"
|
|
649
665
|
];
|
|
666
|
+
FORBIDDEN_DIRECTORY_NAMES = /* @__PURE__ */ new Set([
|
|
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.
|
|
670
|
+
"node_modules",
|
|
671
|
+
"dist",
|
|
672
|
+
"build",
|
|
673
|
+
"out",
|
|
674
|
+
// Python
|
|
675
|
+
"__pycache__",
|
|
676
|
+
".pytest_cache",
|
|
677
|
+
".mypy_cache",
|
|
678
|
+
".ruff_cache",
|
|
679
|
+
".tox",
|
|
680
|
+
// JS/TS frameworks
|
|
681
|
+
".next",
|
|
682
|
+
".nuxt",
|
|
683
|
+
".svelte-kit",
|
|
684
|
+
".parcel-cache",
|
|
685
|
+
".turbo",
|
|
686
|
+
".nyc_output",
|
|
687
|
+
// Generic build / coverage caches
|
|
688
|
+
"coverage",
|
|
689
|
+
"htmlcov",
|
|
690
|
+
".cache",
|
|
691
|
+
// Virtualenvs
|
|
692
|
+
"venv",
|
|
693
|
+
".venv",
|
|
694
|
+
// IDE / OS noise
|
|
695
|
+
".idea",
|
|
696
|
+
".vscode",
|
|
697
|
+
".DS_Store"
|
|
698
|
+
]);
|
|
650
699
|
PER_DIR_ENTRY_CAP = 50;
|
|
651
700
|
}
|
|
652
701
|
});
|
|
@@ -4221,49 +4270,49 @@ var require_fast_uri = __commonJS({
|
|
|
4221
4270
|
schemelessOptions.skipEscape = true;
|
|
4222
4271
|
return serialize(resolved, schemelessOptions);
|
|
4223
4272
|
}
|
|
4224
|
-
function resolveComponent(base,
|
|
4273
|
+
function resolveComponent(base, relative9, options, skipNormalization) {
|
|
4225
4274
|
const target = {};
|
|
4226
4275
|
if (!skipNormalization) {
|
|
4227
4276
|
base = parse3(serialize(base, options), options);
|
|
4228
|
-
|
|
4277
|
+
relative9 = parse3(serialize(relative9, options), options);
|
|
4229
4278
|
}
|
|
4230
4279
|
options = options || {};
|
|
4231
|
-
if (!options.tolerant &&
|
|
4232
|
-
target.scheme =
|
|
4233
|
-
target.userinfo =
|
|
4234
|
-
target.host =
|
|
4235
|
-
target.port =
|
|
4236
|
-
target.path = removeDotSegments(
|
|
4237
|
-
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;
|
|
4238
4287
|
} else {
|
|
4239
|
-
if (
|
|
4240
|
-
target.userinfo =
|
|
4241
|
-
target.host =
|
|
4242
|
-
target.port =
|
|
4243
|
-
target.path = removeDotSegments(
|
|
4244
|
-
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;
|
|
4245
4294
|
} else {
|
|
4246
|
-
if (!
|
|
4295
|
+
if (!relative9.path) {
|
|
4247
4296
|
target.path = base.path;
|
|
4248
|
-
if (
|
|
4249
|
-
target.query =
|
|
4297
|
+
if (relative9.query !== void 0) {
|
|
4298
|
+
target.query = relative9.query;
|
|
4250
4299
|
} else {
|
|
4251
4300
|
target.query = base.query;
|
|
4252
4301
|
}
|
|
4253
4302
|
} else {
|
|
4254
|
-
if (
|
|
4255
|
-
target.path = removeDotSegments(
|
|
4303
|
+
if (relative9.path[0] === "/") {
|
|
4304
|
+
target.path = removeDotSegments(relative9.path);
|
|
4256
4305
|
} else {
|
|
4257
4306
|
if ((base.userinfo !== void 0 || base.host !== void 0 || base.port !== void 0) && !base.path) {
|
|
4258
|
-
target.path = "/" +
|
|
4307
|
+
target.path = "/" + relative9.path;
|
|
4259
4308
|
} else if (!base.path) {
|
|
4260
|
-
target.path =
|
|
4309
|
+
target.path = relative9.path;
|
|
4261
4310
|
} else {
|
|
4262
|
-
target.path = base.path.slice(0, base.path.lastIndexOf("/") + 1) +
|
|
4311
|
+
target.path = base.path.slice(0, base.path.lastIndexOf("/") + 1) + relative9.path;
|
|
4263
4312
|
}
|
|
4264
4313
|
target.path = removeDotSegments(target.path);
|
|
4265
4314
|
}
|
|
4266
|
-
target.query =
|
|
4315
|
+
target.query = relative9.query;
|
|
4267
4316
|
}
|
|
4268
4317
|
target.userinfo = base.userinfo;
|
|
4269
4318
|
target.host = base.host;
|
|
@@ -4271,7 +4320,7 @@ var require_fast_uri = __commonJS({
|
|
|
4271
4320
|
}
|
|
4272
4321
|
target.scheme = base.scheme;
|
|
4273
4322
|
}
|
|
4274
|
-
target.fragment =
|
|
4323
|
+
target.fragment = relative9.fragment;
|
|
4275
4324
|
return target;
|
|
4276
4325
|
}
|
|
4277
4326
|
function equal(uriA, uriB, options) {
|
|
@@ -7583,1545 +7632,1609 @@ var init_include_exclude = __esm({
|
|
|
7583
7632
|
}
|
|
7584
7633
|
});
|
|
7585
7634
|
|
|
7586
|
-
// src/code-map/
|
|
7587
|
-
|
|
7588
|
-
|
|
7589
|
-
|
|
7590
|
-
});
|
|
7591
|
-
import { existsSync as existsSync11, statSync as statSync3 } from "fs";
|
|
7592
|
-
import { join as join13 } from "path";
|
|
7593
|
-
function checkCodeMapFreshness(cwd, mapRel = ".cdd/code-map.yml", include, exclude) {
|
|
7594
|
-
const mapPath = join13(cwd, mapRel);
|
|
7595
|
-
let cfg;
|
|
7596
|
-
try {
|
|
7597
|
-
cfg = loadCodeMapConfig(cwd);
|
|
7598
|
-
} catch (err) {
|
|
7599
|
-
return {
|
|
7600
|
-
status: "config-error",
|
|
7601
|
-
staleFiles: [],
|
|
7602
|
-
staleCount: 0,
|
|
7603
|
-
mapPath,
|
|
7604
|
-
configError: err.message
|
|
7605
|
-
};
|
|
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, "''")}'`;
|
|
7606
7639
|
}
|
|
7607
|
-
|
|
7608
|
-
|
|
7609
|
-
|
|
7610
|
-
if (
|
|
7611
|
-
|
|
7612
|
-
|
|
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]} }`);
|
|
7613
7666
|
}
|
|
7614
|
-
return { status: "missing-with-sources", staleFiles: [], staleCount: 0, mapPath };
|
|
7615
7667
|
}
|
|
7616
|
-
|
|
7617
|
-
|
|
7618
|
-
|
|
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) {
|
|
7619
7773
|
try {
|
|
7620
|
-
const
|
|
7621
|
-
if (
|
|
7622
|
-
const rel = absPath.replace(/\\/g, "/").replace(
|
|
7623
|
-
|
|
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);
|
|
7624
7780
|
}
|
|
7625
|
-
} catch {
|
|
7781
|
+
} catch (err) {
|
|
7782
|
+
const rel = absPath.replace(/\\/g, "/").replace(repoRoot.replace(/\\/g, "/") + "/", "");
|
|
7783
|
+
warnings.push({ path: rel, message: `IO error: ${err.message}` });
|
|
7626
7784
|
}
|
|
7627
7785
|
}
|
|
7628
|
-
|
|
7629
|
-
|
|
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);
|
|
7630
7796
|
}
|
|
7631
|
-
return
|
|
7632
|
-
status: "stale",
|
|
7633
|
-
staleFiles: staleAll.slice(0, 5),
|
|
7634
|
-
staleCount: staleAll.length,
|
|
7635
|
-
mapPath
|
|
7636
|
-
};
|
|
7797
|
+
return out;
|
|
7637
7798
|
}
|
|
7638
|
-
var
|
|
7639
|
-
"src/code-map/
|
|
7799
|
+
var init_orchestrator = __esm({
|
|
7800
|
+
"src/code-map/orchestrator.ts"() {
|
|
7640
7801
|
"use strict";
|
|
7641
7802
|
init_include_exclude();
|
|
7642
|
-
init_config();
|
|
7643
7803
|
}
|
|
7644
7804
|
});
|
|
7645
7805
|
|
|
7646
|
-
// src/
|
|
7647
|
-
|
|
7648
|
-
|
|
7649
|
-
|
|
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
|
+
}
|
|
7650
7823
|
});
|
|
7651
|
-
|
|
7652
|
-
|
|
7653
|
-
|
|
7654
|
-
|
|
7655
|
-
|
|
7656
|
-
|
|
7657
|
-
|
|
7658
|
-
|
|
7659
|
-
|
|
7660
|
-
|
|
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
|
+
}
|
|
7661
7845
|
}
|
|
7662
|
-
return
|
|
7846
|
+
return null;
|
|
7663
7847
|
}
|
|
7664
|
-
|
|
7665
|
-
|
|
7666
|
-
|
|
7667
|
-
""
|
|
7668
|
-
|
|
7669
|
-
|
|
7670
|
-
"",
|
|
7671
|
-
|
|
7672
|
-
|
|
7673
|
-
|
|
7674
|
-
|
|
7675
|
-
|
|
7676
|
-
|
|
7677
|
-
"## Required Contracts",
|
|
7678
|
-
"- legacy-unknown",
|
|
7679
|
-
"",
|
|
7680
|
-
"## Required Tests",
|
|
7681
|
-
"- legacy-unknown",
|
|
7682
|
-
"",
|
|
7683
|
-
"## Agent Work Packets",
|
|
7684
|
-
"",
|
|
7685
|
-
"## Context Expansion Requests",
|
|
7686
|
-
"-",
|
|
7687
|
-
"",
|
|
7688
|
-
"## Approved Expansions",
|
|
7689
|
-
"-",
|
|
7690
|
-
""
|
|
7691
|
-
].join("\n");
|
|
7692
|
-
}
|
|
7693
|
-
function buildContextGovernedManifest(changeId) {
|
|
7694
|
-
return [
|
|
7695
|
-
"# Context Manifest",
|
|
7696
|
-
"",
|
|
7697
|
-
"Generated by `cdd-kit migrate --enable-context-governance` for an existing change.",
|
|
7698
|
-
"Review and narrow the allowed paths before assigning implementation work.",
|
|
7699
|
-
"Forbidden paths come from `.cdd/context-policy.json`.",
|
|
7700
|
-
"",
|
|
7701
|
-
"## Affected Surfaces",
|
|
7702
|
-
"- legacy-unknown",
|
|
7703
|
-
"",
|
|
7704
|
-
"## Allowed Paths",
|
|
7705
|
-
`- specs/changes/${changeId}/`,
|
|
7706
|
-
"- specs/context/project-map.md",
|
|
7707
|
-
"- specs/context/contracts-index.md",
|
|
7708
|
-
"",
|
|
7709
|
-
"## Required Contracts",
|
|
7710
|
-
"- legacy-unknown",
|
|
7711
|
-
"",
|
|
7712
|
-
"## Required Tests",
|
|
7713
|
-
"- legacy-unknown",
|
|
7714
|
-
"",
|
|
7715
|
-
"## Agent Work Packets",
|
|
7716
|
-
"",
|
|
7717
|
-
"### change-classifier",
|
|
7718
|
-
"- allowed:",
|
|
7719
|
-
` - specs/changes/${changeId}/`,
|
|
7720
|
-
" - specs/context/project-map.md",
|
|
7721
|
-
" - specs/context/contracts-index.md",
|
|
7722
|
-
"",
|
|
7723
|
-
"## Context Expansion Requests",
|
|
7724
|
-
"",
|
|
7725
|
-
"<!--",
|
|
7726
|
-
"Agents must request context expansion instead of reading outside their work packet.",
|
|
7727
|
-
"Use this format only for real requests:",
|
|
7728
|
-
"",
|
|
7729
|
-
"- request-id: CER-001",
|
|
7730
|
-
" requested_paths:",
|
|
7731
|
-
" - src/example.ts",
|
|
7732
|
-
" reason: why this file is required",
|
|
7733
|
-
" status: pending",
|
|
7734
|
-
"-->",
|
|
7735
|
-
"",
|
|
7736
|
-
"## Approved Expansions",
|
|
7737
|
-
"-",
|
|
7738
|
-
""
|
|
7739
|
-
].join("\n");
|
|
7740
|
-
}
|
|
7741
|
-
function parseLegacyFrontmatter(content) {
|
|
7742
|
-
const m = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
|
|
7743
|
-
if (!m)
|
|
7744
|
-
return {};
|
|
7745
|
-
const out = {};
|
|
7746
|
-
for (const line of m[1].split(/\r?\n/)) {
|
|
7747
|
-
const colon = line.indexOf(":");
|
|
7748
|
-
if (colon === -1)
|
|
7749
|
-
continue;
|
|
7750
|
-
const key = line.slice(0, colon).trim();
|
|
7751
|
-
if (!key)
|
|
7752
|
-
continue;
|
|
7753
|
-
out[key] = line.slice(colon + 1).trim();
|
|
7754
|
-
}
|
|
7755
|
-
return out;
|
|
7756
|
-
}
|
|
7757
|
-
function parseListField(raw) {
|
|
7758
|
-
if (!raw)
|
|
7759
|
-
return [];
|
|
7760
|
-
const trimmed = raw.trim();
|
|
7761
|
-
if (!trimmed || trimmed === "[]")
|
|
7762
|
-
return [];
|
|
7763
|
-
const inner = trimmed.startsWith("[") && trimmed.endsWith("]") ? trimmed.slice(1, -1) : trimmed;
|
|
7764
|
-
return inner.split(",").map((item) => item.trim().replace(/^["']|["']$/g, "")).filter(Boolean);
|
|
7765
|
-
}
|
|
7766
|
-
function parseLegacyTaskList(body) {
|
|
7767
|
-
const lines = body.split(/\r?\n/);
|
|
7768
|
-
const rows = [];
|
|
7769
|
-
let currentSection;
|
|
7770
|
-
for (const raw of lines) {
|
|
7771
|
-
const headerMatch = raw.match(/^##\s+\d+\.\s+(.*)\s*$/);
|
|
7772
|
-
if (headerMatch) {
|
|
7773
|
-
currentSection = headerMatch[1].trim();
|
|
7774
|
-
continue;
|
|
7775
|
-
}
|
|
7776
|
-
const itemMatch = raw.match(/^\s*-\s*\[([ xX\-])\]\s+(\d+(?:\.\d+)*)\s+(.*)\s*$/);
|
|
7777
|
-
if (!itemMatch)
|
|
7778
|
-
continue;
|
|
7779
|
-
const mark = itemMatch[1];
|
|
7780
|
-
const id = itemMatch[2];
|
|
7781
|
-
const title = itemMatch[3].trim();
|
|
7782
|
-
let status = "pending";
|
|
7783
|
-
if (mark === "x" || mark === "X")
|
|
7784
|
-
status = "done";
|
|
7785
|
-
else if (mark === "-")
|
|
7786
|
-
status = "skipped";
|
|
7787
|
-
rows.push({ id, title, status, section: currentSection });
|
|
7788
|
-
}
|
|
7789
|
-
return rows;
|
|
7790
|
-
}
|
|
7791
|
-
function migrateTasksFile(changeId, changeDir, enableContextGovernance, detectedTier, changed, warnings, pendingWrites, pendingDeletes) {
|
|
7792
|
-
const newPath = join16(changeDir, "tasks.yml");
|
|
7793
|
-
const legacyPath = join16(changeDir, "tasks.md");
|
|
7794
|
-
if (existsSync14(newPath)) {
|
|
7795
|
-
return;
|
|
7796
|
-
}
|
|
7797
|
-
if (!existsSync14(legacyPath)) {
|
|
7798
|
-
warnings.push("tasks.md not found and tasks.yml missing \u2014 skipping tasks migration");
|
|
7799
|
-
return;
|
|
7800
|
-
}
|
|
7801
|
-
const raw = readFileSync11(legacyPath, "utf8");
|
|
7802
|
-
const fm = parseLegacyFrontmatter(raw);
|
|
7803
|
-
const bodyMatch = raw.match(/^---\r?\n[\s\S]*?\r?\n---\r?\n?([\s\S]*)$/);
|
|
7804
|
-
const body = bodyMatch ? bodyMatch[1] : raw;
|
|
7805
|
-
const tasksRows = parseLegacyTaskList(body);
|
|
7806
|
-
const data = {};
|
|
7807
|
-
data["change-id"] = fm["change-id"] || changeId;
|
|
7808
|
-
data["status"] = fm["status"] || "in-progress";
|
|
7809
|
-
if (fm["tier"] && /^\d+$/.test(fm["tier"])) {
|
|
7810
|
-
data["tier"] = parseInt(fm["tier"], 10);
|
|
7811
|
-
} else if (detectedTier) {
|
|
7812
|
-
data["tier"] = parseInt(detectedTier, 10);
|
|
7813
|
-
} else {
|
|
7814
|
-
data["tier"] = null;
|
|
7815
|
-
}
|
|
7816
|
-
if (enableContextGovernance || fm["context-governance"] === "v1") {
|
|
7817
|
-
data["context-governance"] = "v1";
|
|
7818
|
-
}
|
|
7819
|
-
const archive2 = parseListField(fm["archive-tasks"]);
|
|
7820
|
-
data["archive-tasks"] = archive2.length > 0 ? archive2 : ["7.1", "7.2"];
|
|
7821
|
-
const deps = parseListField(fm["depends-on"]);
|
|
7822
|
-
data["depends-on"] = deps;
|
|
7823
|
-
data["tasks"] = tasksRows.map((r) => {
|
|
7824
|
-
const out = { id: r.id, title: r.title, status: r.status };
|
|
7825
|
-
if (r.section)
|
|
7826
|
-
out["section"] = r.section;
|
|
7827
|
-
return out;
|
|
7828
|
-
});
|
|
7829
|
-
const yamlOut = yaml3.dump(data, { lineWidth: -1, noRefs: true });
|
|
7830
|
-
pendingWrites.push({ path: newPath, content: yamlOut });
|
|
7831
|
-
pendingDeletes.push({ path: legacyPath });
|
|
7832
|
-
changed.push(`tasks.md -> tasks.yml (${tasksRows.length} task(s) migrated)`);
|
|
7833
|
-
}
|
|
7834
|
-
function parseLegacyAgentLog(content) {
|
|
7835
|
-
const lines = content.split(/\r?\n/);
|
|
7836
|
-
const data = {};
|
|
7837
|
-
let i = 0;
|
|
7838
|
-
const topFieldRe = /^[ ]{0,1}-\s*([\w-]+):\s*(.*)$/;
|
|
7839
|
-
while (i < lines.length) {
|
|
7840
|
-
const line = lines[i];
|
|
7841
|
-
const fieldMatch = line.match(topFieldRe);
|
|
7842
|
-
if (!fieldMatch) {
|
|
7843
|
-
i++;
|
|
7844
|
-
continue;
|
|
7845
|
-
}
|
|
7846
|
-
const key = fieldMatch[1];
|
|
7847
|
-
const inline = fieldMatch[2].trim();
|
|
7848
|
-
if (key === "files-read" || key === "artifacts") {
|
|
7849
|
-
const items = [];
|
|
7850
|
-
let j = i + 1;
|
|
7851
|
-
while (j < lines.length) {
|
|
7852
|
-
const sub = lines[j];
|
|
7853
|
-
if (topFieldRe.test(sub))
|
|
7854
|
-
break;
|
|
7855
|
-
if (/^#/.test(sub))
|
|
7856
|
-
break;
|
|
7857
|
-
const itemMatch = sub.match(/^\s{2,}-\s+(.+?)\s*$/);
|
|
7858
|
-
if (itemMatch) {
|
|
7859
|
-
items.push(itemMatch[1].trim());
|
|
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();
|
|
7860
7861
|
}
|
|
7861
|
-
|
|
7862
|
+
return this._interpreter;
|
|
7862
7863
|
}
|
|
7863
|
-
|
|
7864
|
-
|
|
7865
|
-
|
|
7866
|
-
|
|
7867
|
-
|
|
7868
|
-
|
|
7869
|
-
|
|
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 };
|
|
7870
7922
|
}
|
|
7871
|
-
|
|
7872
|
-
|
|
7873
|
-
|
|
7874
|
-
|
|
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 };
|
|
7875
7980
|
}
|
|
7876
|
-
|
|
7877
|
-
|
|
7878
|
-
}
|
|
7879
|
-
data[key] = inline;
|
|
7880
|
-
i++;
|
|
7981
|
+
};
|
|
7982
|
+
pythonScanner = new PythonScanner();
|
|
7881
7983
|
}
|
|
7882
|
-
|
|
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
|
+
});
|
|
8006
|
+
}
|
|
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;
|
|
7883
8023
|
}
|
|
7884
|
-
function
|
|
7885
|
-
const
|
|
7886
|
-
|
|
7887
|
-
|
|
7888
|
-
const mdLogs = readdirSync8(agentLogDir).filter((f) => f.endsWith(".md"));
|
|
7889
|
-
for (const f of mdLogs) {
|
|
7890
|
-
const fullPath = join16(agentLogDir, f);
|
|
7891
|
-
const yamlName = f.replace(/\.md$/, ".yml");
|
|
7892
|
-
const yamlFull = join16(agentLogDir, yamlName);
|
|
7893
|
-
if (existsSync14(yamlFull))
|
|
7894
|
-
continue;
|
|
7895
|
-
const raw = readFileSync11(fullPath, "utf8");
|
|
7896
|
-
const parsed = parseLegacyAgentLog(raw);
|
|
7897
|
-
const yamlOut = yaml3.dump(parsed, { lineWidth: -1, noRefs: true });
|
|
7898
|
-
pendingWrites.push({ path: yamlFull, content: yamlOut });
|
|
7899
|
-
pendingDeletes.push({ path: fullPath });
|
|
7900
|
-
changed.push(`agent-log/${f} -> agent-log/${yamlName}`);
|
|
7901
|
-
}
|
|
8024
|
+
function getLineRange(node) {
|
|
8025
|
+
const start = node.loc?.start.line ?? 1;
|
|
8026
|
+
const end = node.loc?.end.line ?? start;
|
|
8027
|
+
return [start, end];
|
|
7902
8028
|
}
|
|
7903
|
-
function
|
|
7904
|
-
const
|
|
7905
|
-
const
|
|
7906
|
-
|
|
7907
|
-
|
|
7908
|
-
|
|
7909
|
-
|
|
7910
|
-
|
|
7911
|
-
|
|
7912
|
-
|
|
7913
|
-
const oldMatch = content.match(/\*\*Tier[:\*]+\s*(?:Tier\s*)?(\d)/i) ?? content.match(/^-?\s*Tier:\s*(?:Tier\s*)?(\d)/mi);
|
|
7914
|
-
if (oldMatch)
|
|
7915
|
-
detectedTier = oldMatch[1];
|
|
7916
|
-
if (!hasNewTierFormat) {
|
|
7917
|
-
if (detectedTier) {
|
|
7918
|
-
const addition = `
|
|
7919
|
-
## Tier
|
|
7920
|
-
- ${detectedTier}
|
|
7921
|
-
`;
|
|
7922
|
-
if (!content.includes("\n## Tier\n")) {
|
|
7923
|
-
changed.push(
|
|
7924
|
-
`change-classification.md: appended "## Tier\\n- ${detectedTier}" (converted from old format)`
|
|
7925
|
-
);
|
|
7926
|
-
pending.push({ path: classifPath, content: content + addition });
|
|
7927
|
-
}
|
|
7928
|
-
} else {
|
|
7929
|
-
warnings.push(
|
|
7930
|
-
"change-classification.md: could not detect tier (no **Tier:** N or ## Tier N found). Set `tier: <0-5>` in tasks.yml frontmatter to enable tier-based gate checks."
|
|
7931
|
-
);
|
|
7932
|
-
}
|
|
7933
|
-
} else {
|
|
7934
|
-
const structured = content.match(/^## Tier\s*\n\s*-\s*(\d)\s*$/m);
|
|
7935
|
-
if (structured)
|
|
7936
|
-
detectedTier = structured[1];
|
|
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);
|
|
7937
8039
|
}
|
|
7938
8040
|
}
|
|
7939
|
-
|
|
7940
|
-
|
|
7941
|
-
|
|
7942
|
-
|
|
7943
|
-
|
|
7944
|
-
pending.push({
|
|
7945
|
-
path: manifestPath,
|
|
7946
|
-
content: enableContextGovernance ? buildContextGovernedManifest(changeId) : buildLegacyContextManifest(changeId)
|
|
7947
|
-
});
|
|
7948
|
-
} else if (enableContextGovernance) {
|
|
7949
|
-
warnings.push("context-manifest.md already exists \u2014 review allowed paths before relying on context-governance: v1");
|
|
7950
|
-
}
|
|
7951
|
-
return { result: { changed, warnings }, pending, deletes };
|
|
8041
|
+
return {
|
|
8042
|
+
module: node.source.value,
|
|
8043
|
+
items,
|
|
8044
|
+
line: node.loc?.start.line ?? 1
|
|
8045
|
+
};
|
|
7952
8046
|
}
|
|
7953
|
-
function
|
|
7954
|
-
const
|
|
7955
|
-
|
|
7956
|
-
|
|
7957
|
-
|
|
7958
|
-
|
|
7959
|
-
|
|
7960
|
-
|
|
7961
|
-
|
|
7962
|
-
|
|
7963
|
-
|
|
7964
|
-
|
|
7965
|
-
|
|
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}`;
|
|
8071
|
+
} else {
|
|
8072
|
+
methodName = "<computed>";
|
|
7966
8073
|
}
|
|
7967
|
-
|
|
7968
|
-
|
|
7969
|
-
|
|
7970
|
-
|
|
7971
|
-
|
|
7972
|
-
}
|
|
7973
|
-
for (const d of deletes) {
|
|
7974
|
-
try {
|
|
7975
|
-
rmSync2(d.path, { force: true });
|
|
7976
|
-
} catch {
|
|
8074
|
+
methods.push({
|
|
8075
|
+
name: methodName,
|
|
8076
|
+
lines: getLineRange(m),
|
|
8077
|
+
async: m.async
|
|
8078
|
+
});
|
|
7977
8079
|
}
|
|
7978
8080
|
}
|
|
8081
|
+
return {
|
|
8082
|
+
name,
|
|
8083
|
+
lines: getLineRange(node),
|
|
8084
|
+
methods
|
|
8085
|
+
};
|
|
7979
8086
|
}
|
|
7980
|
-
|
|
7981
|
-
const
|
|
7982
|
-
|
|
7983
|
-
const enableContextGovernance = opts.enableContextGovernance ?? false;
|
|
7984
|
-
const noBackup = opts.noBackup ?? false;
|
|
7985
|
-
const idsToMigrate = [];
|
|
7986
|
-
if (opts.all) {
|
|
7987
|
-
const changesDir = join16(cwd, "specs", "changes");
|
|
7988
|
-
if (!existsSync14(changesDir)) {
|
|
7989
|
-
log.info("No specs/changes/ directory found \u2014 nothing to migrate.");
|
|
7990
|
-
return;
|
|
7991
|
-
}
|
|
7992
|
-
idsToMigrate.push(
|
|
7993
|
-
...readdirSync8(changesDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name)
|
|
7994
|
-
);
|
|
7995
|
-
} else if (changeId) {
|
|
7996
|
-
const specificDir = join16(cwd, "specs", "changes", changeId);
|
|
7997
|
-
if (!existsSync14(specificDir)) {
|
|
7998
|
-
log.error(`Change not found: specs/changes/${changeId}`);
|
|
7999
|
-
process.exit(1);
|
|
8000
|
-
}
|
|
8001
|
-
idsToMigrate.push(changeId);
|
|
8002
|
-
} else {
|
|
8003
|
-
log.error("Usage: cdd-kit migrate <change-id> | cdd-kit migrate --all [--dry-run] [--no-backup]");
|
|
8004
|
-
process.exit(1);
|
|
8005
|
-
}
|
|
8006
|
-
if (idsToMigrate.length === 0) {
|
|
8007
|
-
log.info("No changes found to migrate.");
|
|
8008
|
-
return;
|
|
8009
|
-
}
|
|
8010
|
-
if (dryRun) {
|
|
8011
|
-
log.info("Dry run \u2014 no files will be written.");
|
|
8012
|
-
log.blank();
|
|
8013
|
-
}
|
|
8014
|
-
const sessionStamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
8015
|
-
let migratedCount = 0;
|
|
8016
|
-
let upToDateCount = 0;
|
|
8017
|
-
const backupRoot = join16(cwd, ".cdd", "migrate-backup", sessionStamp);
|
|
8018
|
-
for (const id of idsToMigrate) {
|
|
8019
|
-
const changeDir = join16(cwd, "specs", "changes", id);
|
|
8020
|
-
if (!existsSync14(changeDir)) {
|
|
8021
|
-
log.warn(` ${id}: directory not found \u2014 skipping`);
|
|
8022
|
-
continue;
|
|
8023
|
-
}
|
|
8024
|
-
const { result, pending, deletes } = migrateOne(id, changeDir, enableContextGovernance);
|
|
8025
|
-
const { changed, warnings } = result;
|
|
8026
|
-
if (changed.length === 0) {
|
|
8027
|
-
log.info(` ${id}: already up to date`);
|
|
8028
|
-
upToDateCount++;
|
|
8029
|
-
for (const w of warnings)
|
|
8030
|
-
log.warn(` ${id}: ${w}`);
|
|
8087
|
+
function processVariableDeclaration(node, imports, constants, functions) {
|
|
8088
|
+
for (const decl of node.declarations) {
|
|
8089
|
+
if (!decl.id || decl.id.type !== "Identifier")
|
|
8031
8090
|
continue;
|
|
8032
|
-
|
|
8033
|
-
|
|
8034
|
-
|
|
8035
|
-
|
|
8036
|
-
|
|
8037
|
-
|
|
8038
|
-
} catch (err) {
|
|
8039
|
-
log.error(` ${id}: migration failed \u2014 ${err.message}`);
|
|
8040
|
-
if (!noBackup) {
|
|
8041
|
-
log.error(` ${id}: restore from .cdd/migrate-backup/${sessionStamp}/${id}/`);
|
|
8042
|
-
}
|
|
8043
|
-
process.exit(1);
|
|
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 });
|
|
8044
8097
|
}
|
|
8045
|
-
}
|
|
8046
|
-
log.ok(` ${id}: migrated`);
|
|
8047
|
-
for (const c of changed)
|
|
8048
|
-
log.info(` + ${c}`);
|
|
8049
|
-
migratedCount++;
|
|
8050
|
-
for (const w of warnings)
|
|
8051
|
-
log.warn(` ${id}: ${w}`);
|
|
8052
|
-
}
|
|
8053
|
-
log.blank();
|
|
8054
|
-
if (dryRun) {
|
|
8055
|
-
log.info(`Dry run complete: ${migratedCount} change(s) would be updated, ${upToDateCount} already up to date.`);
|
|
8056
|
-
} else {
|
|
8057
|
-
log.ok(`Migration complete: ${migratedCount} updated, ${upToDateCount} already up to date.`);
|
|
8058
|
-
if (migratedCount > 0 && !noBackup) {
|
|
8059
|
-
log.info(`Backup: ${backupRoot}`);
|
|
8060
|
-
log.info('Next: git add specs/changes/ && git commit -m "chore: migrate changes to YAML format"');
|
|
8061
|
-
log.info("When stable, remove backup: rm -rf .cdd/migrate-backup/");
|
|
8062
|
-
}
|
|
8063
|
-
}
|
|
8064
|
-
}
|
|
8065
|
-
var init_migrate = __esm({
|
|
8066
|
-
"src/commands/migrate.ts"() {
|
|
8067
|
-
"use strict";
|
|
8068
|
-
init_logger();
|
|
8069
|
-
}
|
|
8070
|
-
});
|
|
8071
|
-
|
|
8072
|
-
// src/commands/upgrade.ts
|
|
8073
|
-
var upgrade_exports = {};
|
|
8074
|
-
__export(upgrade_exports, {
|
|
8075
|
-
upgrade: () => upgrade
|
|
8076
|
-
});
|
|
8077
|
-
import { existsSync as existsSync15, mkdirSync as mkdirSync7, readdirSync as readdirSync9, copyFileSync as copyFileSync3, readFileSync as readFileSync12, writeFileSync as writeFileSync7 } from "fs";
|
|
8078
|
-
import { dirname as dirname4, join as join17, relative as relative3 } from "path";
|
|
8079
|
-
function planMissingFiles(srcDir, destDir, label, planned) {
|
|
8080
|
-
if (!existsSync15(srcDir))
|
|
8081
|
-
return;
|
|
8082
|
-
for (const entry of readdirSync9(srcDir, { withFileTypes: true })) {
|
|
8083
|
-
const src = join17(srcDir, entry.name);
|
|
8084
|
-
const dest = join17(destDir, entry.name);
|
|
8085
|
-
if (entry.isDirectory()) {
|
|
8086
|
-
planMissingFiles(src, dest, join17(label, entry.name), planned);
|
|
8087
8098
|
continue;
|
|
8088
8099
|
}
|
|
8089
|
-
if (
|
|
8090
|
-
|
|
8091
|
-
|
|
8092
|
-
}
|
|
8093
|
-
}
|
|
8094
|
-
function planProviderGuidance(cwd, provider, planned) {
|
|
8095
|
-
if (provider === "claude" || provider === "both") {
|
|
8096
|
-
if (!existsSync15(join17(cwd, "CLAUDE.md"))) {
|
|
8097
|
-
planned.push({ src: ASSET.claudeTemplate, dest: join17(cwd, "CLAUDE.md"), rel: "CLAUDE.md" });
|
|
8100
|
+
if (isAllCapsConst(varName) && init2 !== null && init2 !== void 0) {
|
|
8101
|
+
constants.push({ name: varName, line: node.loc?.start.line ?? 1 });
|
|
8102
|
+
continue;
|
|
8098
8103
|
}
|
|
8099
|
-
if (
|
|
8100
|
-
|
|
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
|
+
});
|
|
8101
8118
|
}
|
|
8102
8119
|
}
|
|
8103
|
-
if ((provider === "codex" || provider === "both") && !existsSync15(join17(cwd, "CODEX.md"))) {
|
|
8104
|
-
planned.push({ src: ASSET.codexTemplate, dest: join17(cwd, "CODEX.md"), rel: "CODEX.md" });
|
|
8105
|
-
}
|
|
8106
8120
|
}
|
|
8107
|
-
function
|
|
8108
|
-
|
|
8109
|
-
|
|
8110
|
-
|
|
8111
|
-
|
|
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
|
+
};
|
|
8112
8131
|
}
|
|
8113
|
-
|
|
8114
|
-
|
|
8115
|
-
|
|
8116
|
-
if (!
|
|
8117
|
-
|
|
8118
|
-
|
|
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);
|
|
8119
8156
|
}
|
|
8120
|
-
|
|
8121
|
-
|
|
8122
|
-
|
|
8123
|
-
|
|
8124
|
-
|
|
8125
|
-
|
|
8126
|
-
|
|
8127
|
-
|
|
8128
|
-
|
|
8129
|
-
|
|
8130
|
-
log.info(`Upgrade provider: ${provider}`);
|
|
8131
|
-
if (plan.length === 0) {
|
|
8132
|
-
log.ok("No missing cdd-kit project files found.");
|
|
8133
|
-
if (opts.migrateChanges) {
|
|
8134
|
-
log.blank();
|
|
8135
|
-
log.info("Running change migration flow...");
|
|
8136
|
-
await migrate(void 0, {
|
|
8137
|
-
all: true,
|
|
8138
|
-
dryRun: !opts.yes,
|
|
8139
|
-
enableContextGovernance: opts.enableContextGovernance
|
|
8140
|
-
});
|
|
8141
|
-
}
|
|
8142
|
-
log.blank();
|
|
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);
|
|
8143
8167
|
return;
|
|
8144
8168
|
}
|
|
8145
|
-
|
|
8146
|
-
|
|
8147
|
-
|
|
8148
|
-
|
|
8149
|
-
|
|
8150
|
-
|
|
8151
|
-
if (
|
|
8152
|
-
|
|
8153
|
-
|
|
8154
|
-
|
|
8155
|
-
all: true,
|
|
8156
|
-
dryRun: true,
|
|
8157
|
-
enableContextGovernance: opts.enableContextGovernance
|
|
8158
|
-
});
|
|
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);
|
|
8159
8179
|
}
|
|
8160
|
-
log.blank();
|
|
8161
8180
|
return;
|
|
8162
8181
|
}
|
|
8163
|
-
|
|
8164
|
-
|
|
8165
|
-
|
|
8166
|
-
let existing = {};
|
|
8167
|
-
try {
|
|
8168
|
-
existing = JSON.parse(readFileSync12(modelPolicyPath, "utf8"));
|
|
8169
|
-
} catch {
|
|
8170
|
-
}
|
|
8171
|
-
const merged = {
|
|
8172
|
-
...existing,
|
|
8173
|
-
provider,
|
|
8174
|
-
generated_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
8175
|
-
roles: existing.roles && typeof existing.roles === "object" ? existing.roles : {}
|
|
8176
|
-
};
|
|
8177
|
-
writeFileSync7(modelPolicyPath, JSON.stringify(merged, null, 2) + "\n", "utf8");
|
|
8178
|
-
}
|
|
8179
|
-
log.blank();
|
|
8180
|
-
log.ok(`Upgrade complete: ${plan.length} missing file(s) added.`);
|
|
8181
|
-
log.info("Existing project guidance and contracts were preserved.");
|
|
8182
|
-
if (opts.migrateChanges) {
|
|
8183
|
-
log.blank();
|
|
8184
|
-
log.info("Running change migration flow...");
|
|
8185
|
-
await migrate(void 0, {
|
|
8186
|
-
all: true,
|
|
8187
|
-
dryRun: false,
|
|
8188
|
-
enableContextGovernance: opts.enableContextGovernance
|
|
8189
|
-
});
|
|
8190
|
-
}
|
|
8191
|
-
log.blank();
|
|
8192
|
-
}
|
|
8193
|
-
var init_upgrade = __esm({
|
|
8194
|
-
"src/commands/upgrade.ts"() {
|
|
8195
|
-
"use strict";
|
|
8196
|
-
init_paths();
|
|
8197
|
-
init_logger();
|
|
8198
|
-
init_provider();
|
|
8199
|
-
init_migrate();
|
|
8200
|
-
}
|
|
8201
|
-
});
|
|
8202
|
-
|
|
8203
|
-
// src/code-map/yaml-writer.ts
|
|
8204
|
-
function quoteScalar(s) {
|
|
8205
|
-
if (s.startsWith(".") || YAML_RESERVED.test(s) || s.includes(" ") || s === "" || s === "null" || s === "true" || s === "false") {
|
|
8206
|
-
return `'${s.replace(/'/g, "''")}'`;
|
|
8207
|
-
}
|
|
8208
|
-
return s;
|
|
8209
|
-
}
|
|
8210
|
-
function quotePath(p) {
|
|
8211
|
-
if (YAML_RESERVED.test(p) || p.includes(" ")) {
|
|
8212
|
-
return `'${p.replace(/'/g, "''")}'`;
|
|
8182
|
+
if (stmt.type === "ImportDeclaration") {
|
|
8183
|
+
buckets.imports.push(processImportDeclaration(stmt));
|
|
8184
|
+
return;
|
|
8213
8185
|
}
|
|
8214
|
-
|
|
8215
|
-
|
|
8216
|
-
|
|
8217
|
-
|
|
8218
|
-
return
|
|
8219
|
-
return `[${items.map(quoteScalar).join(", ")}]`;
|
|
8220
|
-
}
|
|
8221
|
-
function truncateDecorator(d, max = 80) {
|
|
8222
|
-
const cleaned = d.replace(/\r?\n/g, " ");
|
|
8223
|
-
return cleaned.length <= max ? cleaned : cleaned.slice(0, max) + "...";
|
|
8224
|
-
}
|
|
8225
|
-
function renderClass(c) {
|
|
8226
|
-
const lines = [];
|
|
8227
|
-
lines.push(` - name: ${c.name}`);
|
|
8228
|
-
lines.push(` lines: ${c.lines[0]}-${c.lines[1]}`);
|
|
8229
|
-
if (c.methods.length > 0) {
|
|
8230
|
-
lines.push(" methods:");
|
|
8231
|
-
for (const m of c.methods) {
|
|
8232
|
-
const asyncPrefix = m.async ? "async " : "";
|
|
8233
|
-
lines.push(` - { name: ${asyncPrefix}${m.name}, lines: ${m.lines[0]}-${m.lines[1]} }`);
|
|
8234
|
-
}
|
|
8186
|
+
if (stmt.type === "FunctionDeclaration") {
|
|
8187
|
+
const fe = processFunctionDeclaration(stmt);
|
|
8188
|
+
if (fe)
|
|
8189
|
+
buckets.functions.push(fe);
|
|
8190
|
+
return;
|
|
8235
8191
|
}
|
|
8236
|
-
|
|
8237
|
-
|
|
8238
|
-
|
|
8239
|
-
|
|
8240
|
-
|
|
8241
|
-
if (f.decorators.length > 0) {
|
|
8242
|
-
const truncated = truncateDecorator(f.decorators[0]);
|
|
8243
|
-
comment = ` # @${truncated}`;
|
|
8192
|
+
if (stmt.type === "ClassDeclaration") {
|
|
8193
|
+
const ce = processClassDeclaration(stmt);
|
|
8194
|
+
if (ce)
|
|
8195
|
+
buckets.classes.push(ce);
|
|
8196
|
+
return;
|
|
8244
8197
|
}
|
|
8245
|
-
|
|
8246
|
-
|
|
8247
|
-
|
|
8248
|
-
const exportedSuffix = t.exported ? "" : " # local";
|
|
8249
|
-
return ` - { name: ${t.name}, lines: ${t.lines[0]}-${t.lines[1]} }${exportedSuffix}`;
|
|
8250
|
-
}
|
|
8251
|
-
function renderEnum(e) {
|
|
8252
|
-
const lines = [];
|
|
8253
|
-
const exportedSuffix = e.exported ? "" : " # local";
|
|
8254
|
-
lines.push(` - name: ${e.name}`);
|
|
8255
|
-
lines.push(` lines: ${e.lines[0]}-${e.lines[1]}${exportedSuffix}`);
|
|
8256
|
-
if (e.members.length > 0) {
|
|
8257
|
-
lines.push(` members: [${e.members.join(", ")}]`);
|
|
8198
|
+
if (stmt.type === "VariableDeclaration") {
|
|
8199
|
+
processVariableDeclaration(stmt, buckets.imports, buckets.constants, buckets.functions);
|
|
8200
|
+
return;
|
|
8258
8201
|
}
|
|
8259
|
-
|
|
8260
|
-
|
|
8261
|
-
|
|
8262
|
-
|
|
8263
|
-
|
|
8264
|
-
|
|
8265
|
-
|
|
8266
|
-
const e = entries[i];
|
|
8267
|
-
if (i > 0)
|
|
8268
|
-
bodyLines.push("");
|
|
8269
|
-
const pathKey = quotePath(e.path);
|
|
8270
|
-
bodyLines.push(`${pathKey}: # ${e.total_lines} lines`);
|
|
8271
|
-
if (e.imports.length > 0) {
|
|
8272
|
-
bodyLines.push(" imports:");
|
|
8273
|
-
for (const imp of e.imports) {
|
|
8274
|
-
const mod = quoteScalar(imp.module);
|
|
8275
|
-
bodyLines.push(` - { module: ${mod}, items: ${renderItems(imp.items)}, line: ${imp.line} }`);
|
|
8276
|
-
}
|
|
8277
|
-
}
|
|
8278
|
-
if (e.constants.length > 0) {
|
|
8279
|
-
bodyLines.push(" constants:");
|
|
8280
|
-
for (const c of e.constants) {
|
|
8281
|
-
bodyLines.push(` - { name: ${c.name}, line: ${c.line} }`);
|
|
8282
|
-
}
|
|
8283
|
-
}
|
|
8284
|
-
if (e.classes.length > 0) {
|
|
8285
|
-
bodyLines.push(" classes:");
|
|
8286
|
-
for (const c of e.classes) {
|
|
8287
|
-
bodyLines.push(...renderClass(c));
|
|
8288
|
-
}
|
|
8289
|
-
}
|
|
8290
|
-
if (e.functions.length > 0) {
|
|
8291
|
-
bodyLines.push(" functions:");
|
|
8292
|
-
for (const f of e.functions) {
|
|
8293
|
-
bodyLines.push(renderFunction(f));
|
|
8294
|
-
}
|
|
8295
|
-
}
|
|
8296
|
-
if (e.interfaces && e.interfaces.length > 0) {
|
|
8297
|
-
bodyLines.push(" interfaces:");
|
|
8298
|
-
for (const t of e.interfaces) {
|
|
8299
|
-
bodyLines.push(renderTypeDef(t));
|
|
8300
|
-
}
|
|
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;
|
|
8301
8209
|
}
|
|
8302
|
-
if (
|
|
8303
|
-
|
|
8304
|
-
|
|
8305
|
-
|
|
8306
|
-
|
|
8210
|
+
if (anyStmt.type === "TSTypeAliasDeclaration") {
|
|
8211
|
+
const e = processTsTypeAliasDeclaration(anyStmt, exportedFromWrapper);
|
|
8212
|
+
if (e)
|
|
8213
|
+
buckets.types.push(e);
|
|
8214
|
+
return;
|
|
8307
8215
|
}
|
|
8308
|
-
if (
|
|
8309
|
-
|
|
8310
|
-
|
|
8311
|
-
|
|
8312
|
-
|
|
8216
|
+
if (anyStmt.type === "TSEnumDeclaration") {
|
|
8217
|
+
const e = processTsEnumDeclaration(anyStmt, exportedFromWrapper);
|
|
8218
|
+
if (e)
|
|
8219
|
+
buckets.enums.push(e);
|
|
8220
|
+
return;
|
|
8313
8221
|
}
|
|
8314
8222
|
}
|
|
8315
|
-
|
|
8316
|
-
|
|
8317
|
-
|
|
8318
|
-
|
|
8319
|
-
|
|
8320
|
-
|
|
8321
|
-
];
|
|
8322
|
-
return [...header, ...bodyLines].join("\n") + "\n";
|
|
8323
|
-
}
|
|
8324
|
-
var YAML_RESERVED;
|
|
8325
|
-
var init_yaml_writer = __esm({
|
|
8326
|
-
"src/code-map/yaml-writer.ts"() {
|
|
8327
|
-
"use strict";
|
|
8328
|
-
YAML_RESERVED = /[:[\]{},&*!|>'"%@`#]/;
|
|
8329
|
-
}
|
|
8330
|
-
});
|
|
8331
|
-
|
|
8332
|
-
// src/code-map/orchestrator.ts
|
|
8333
|
-
async function scanInProcess(scanner, absolutePaths, repoRoot) {
|
|
8334
|
-
const entries = [];
|
|
8335
|
-
const warnings = [];
|
|
8336
|
-
for (const absPath of absolutePaths) {
|
|
8337
|
-
try {
|
|
8338
|
-
const entry = await scanner.scan(absPath, repoRoot);
|
|
8339
|
-
if (entry === null) {
|
|
8340
|
-
const rel = absPath.replace(/\\/g, "/").replace(repoRoot.replace(/\\/g, "/") + "/", "");
|
|
8341
|
-
warnings.push({ path: rel, message: "parse error (scanner returned null)" });
|
|
8342
|
-
} else {
|
|
8343
|
-
entries.push(entry);
|
|
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 });
|
|
8344
8229
|
}
|
|
8345
|
-
} catch (err) {
|
|
8346
|
-
const rel = absPath.replace(/\\/g, "/").replace(repoRoot.replace(/\\/g, "/") + "/", "");
|
|
8347
|
-
warnings.push({ path: rel, message: `IO error: ${err.message}` });
|
|
8348
8230
|
}
|
|
8349
8231
|
}
|
|
8350
|
-
return { entries, warnings };
|
|
8351
8232
|
}
|
|
8352
|
-
function
|
|
8353
|
-
|
|
8354
|
-
|
|
8355
|
-
|
|
8356
|
-
|
|
8357
|
-
|
|
8358
|
-
out[ext] = [];
|
|
8359
|
-
out[ext].push(f);
|
|
8233
|
+
function parseAndExtract(source, opts) {
|
|
8234
|
+
let ast;
|
|
8235
|
+
try {
|
|
8236
|
+
ast = parseSourceWithPlugins(source, opts.plugins);
|
|
8237
|
+
} catch {
|
|
8238
|
+
return null;
|
|
8360
8239
|
}
|
|
8361
|
-
|
|
8362
|
-
|
|
8363
|
-
|
|
8364
|
-
|
|
8365
|
-
|
|
8366
|
-
|
|
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);
|
|
8367
8251
|
}
|
|
8368
|
-
|
|
8369
|
-
|
|
8370
|
-
// src/code-map/scanners/common.ts
|
|
8371
|
-
import { relative as relative4 } from "path";
|
|
8372
|
-
function canonicalRelPath(absolutePath, repoRoot) {
|
|
8373
|
-
const rel = relative4(repoRoot, absolutePath);
|
|
8374
|
-
return rel.replace(/\\/g, "/").normalize("NFC");
|
|
8252
|
+
return buckets;
|
|
8375
8253
|
}
|
|
8376
|
-
function
|
|
8377
|
-
|
|
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);
|
|
8378
8258
|
}
|
|
8379
|
-
function
|
|
8380
|
-
const
|
|
8381
|
-
|
|
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
|
+
};
|
|
8382
8272
|
}
|
|
8383
|
-
var
|
|
8384
|
-
|
|
8273
|
+
var COMMON_PLUGINS, JS_PLUGINS, JavaScriptScanner, jsScanner;
|
|
8274
|
+
var init_javascript = __esm({
|
|
8275
|
+
"src/code-map/scanners/javascript.ts"() {
|
|
8385
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();
|
|
8386
8310
|
}
|
|
8387
8311
|
});
|
|
8388
8312
|
|
|
8389
|
-
// src/code-map/scanners/
|
|
8390
|
-
var
|
|
8391
|
-
__export(
|
|
8392
|
-
|
|
8313
|
+
// src/code-map/scanners/typescript.ts
|
|
8314
|
+
var typescript_exports = {};
|
|
8315
|
+
__export(typescript_exports, {
|
|
8316
|
+
tsScanner: () => tsScanner
|
|
8393
8317
|
});
|
|
8394
|
-
import {
|
|
8395
|
-
|
|
8396
|
-
|
|
8397
|
-
|
|
8398
|
-
function detectPython2() {
|
|
8399
|
-
for (const candidate of ["python3", "python"]) {
|
|
8400
|
-
try {
|
|
8401
|
-
const result = spawnSync2(candidate, ["--version"], {
|
|
8402
|
-
encoding: "utf8",
|
|
8403
|
-
timeout: 5e3
|
|
8404
|
-
});
|
|
8405
|
-
if (result.status === 0)
|
|
8406
|
-
return candidate;
|
|
8407
|
-
} catch {
|
|
8408
|
-
}
|
|
8409
|
-
}
|
|
8410
|
-
return null;
|
|
8411
|
-
}
|
|
8412
|
-
var TIMEOUT_MS, PythonScanner, pythonScanner;
|
|
8413
|
-
var init_python = __esm({
|
|
8414
|
-
"src/code-map/scanners/python.ts"() {
|
|
8318
|
+
import { readFileSync as readFileSync11 } from "fs";
|
|
8319
|
+
var TypeScriptScanner, tsScanner;
|
|
8320
|
+
var init_typescript = __esm({
|
|
8321
|
+
"src/code-map/scanners/typescript.ts"() {
|
|
8415
8322
|
"use strict";
|
|
8416
|
-
init_paths();
|
|
8417
8323
|
init_common();
|
|
8418
|
-
|
|
8419
|
-
|
|
8420
|
-
extensions = [".
|
|
8421
|
-
_interpreter = void 0;
|
|
8422
|
-
getInterpreter() {
|
|
8423
|
-
if (this._interpreter === void 0) {
|
|
8424
|
-
this._interpreter = detectPython2();
|
|
8425
|
-
}
|
|
8426
|
-
return this._interpreter;
|
|
8427
|
-
}
|
|
8324
|
+
init_javascript();
|
|
8325
|
+
TypeScriptScanner = class {
|
|
8326
|
+
extensions = [".ts", ".tsx"];
|
|
8428
8327
|
async scan(absolutePath, repoRoot) {
|
|
8429
|
-
|
|
8430
|
-
|
|
8431
|
-
|
|
8432
|
-
|
|
8433
|
-
|
|
8434
|
-
const entries = [];
|
|
8435
|
-
const warnings = [];
|
|
8436
|
-
if (!interpreter) {
|
|
8437
|
-
const count = absolutePaths.length;
|
|
8438
|
-
warnings.push({
|
|
8439
|
-
path: "",
|
|
8440
|
-
message: `python interpreter not found on PATH; skipping ${count} .py file${count === 1 ? "" : "s"}`
|
|
8441
|
-
});
|
|
8442
|
-
return { entries, warnings };
|
|
8328
|
+
let source;
|
|
8329
|
+
try {
|
|
8330
|
+
source = readFileSync11(absolutePath, "utf8");
|
|
8331
|
+
} catch (err) {
|
|
8332
|
+
throw err;
|
|
8443
8333
|
}
|
|
8444
|
-
|
|
8445
|
-
|
|
8446
|
-
|
|
8447
|
-
|
|
8448
|
-
|
|
8449
|
-
|
|
8450
|
-
|
|
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();
|
|
8358
|
+
}
|
|
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;
|
|
8451
8378
|
try {
|
|
8452
|
-
|
|
8453
|
-
|
|
8454
|
-
|
|
8455
|
-
{
|
|
8456
|
-
encoding: "utf8",
|
|
8457
|
-
timeout: TIMEOUT_MS,
|
|
8458
|
-
maxBuffer: 50 * 1024 * 1024
|
|
8459
|
-
// 50MB
|
|
8460
|
-
}
|
|
8461
|
-
);
|
|
8462
|
-
stdout = result.stdout ?? "";
|
|
8463
|
-
stderr = result.stderr ?? "";
|
|
8464
|
-
exitCode = result.status ?? -1;
|
|
8465
|
-
if (result.error) {
|
|
8466
|
-
const errMsg = result.error.message ?? String(result.error);
|
|
8467
|
-
if (errMsg.includes("ENOENT")) {
|
|
8468
|
-
warnings.push({
|
|
8469
|
-
path: "",
|
|
8470
|
-
message: `python interpreter not found (ENOENT); skipping ${absolutePaths.length} .py file(s)`
|
|
8471
|
-
});
|
|
8472
|
-
return { entries, warnings };
|
|
8473
|
-
}
|
|
8474
|
-
if (errMsg.includes("ETIMEDOUT") || errMsg.includes("timeout")) {
|
|
8475
|
-
warnings.push({
|
|
8476
|
-
path: "",
|
|
8477
|
-
message: `python scanner timed out after ${TIMEOUT_MS}ms; skipping .py files`
|
|
8478
|
-
});
|
|
8479
|
-
return { entries, warnings };
|
|
8480
|
-
}
|
|
8481
|
-
warnings.push({
|
|
8482
|
-
path: "",
|
|
8483
|
-
message: `python scanner error: ${errMsg}; skipping .py files`
|
|
8484
|
-
});
|
|
8485
|
-
return { entries, warnings };
|
|
8486
|
-
}
|
|
8487
|
-
} finally {
|
|
8488
|
-
try {
|
|
8489
|
-
unlinkSync(listFile);
|
|
8490
|
-
} catch {
|
|
8491
|
-
}
|
|
8379
|
+
source = readFileSync12(absolutePath, "utf8");
|
|
8380
|
+
} catch (err) {
|
|
8381
|
+
throw err;
|
|
8492
8382
|
}
|
|
8493
|
-
if (
|
|
8494
|
-
|
|
8495
|
-
path: "",
|
|
8496
|
-
message: "python interpreter is < 3.9 (need ast.unparse); skipping .py files"
|
|
8497
|
-
});
|
|
8498
|
-
return { entries, warnings };
|
|
8383
|
+
if (isBinary(source)) {
|
|
8384
|
+
return null;
|
|
8499
8385
|
}
|
|
8500
|
-
|
|
8501
|
-
|
|
8502
|
-
|
|
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)
|
|
8503
8407
|
continue;
|
|
8504
|
-
|
|
8505
|
-
|
|
8506
|
-
|
|
8507
|
-
|
|
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
|
+
});
|
|
8508
8413
|
continue;
|
|
8509
8414
|
}
|
|
8510
|
-
|
|
8511
|
-
|
|
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" });
|
|
8512
8420
|
continue;
|
|
8513
8421
|
}
|
|
8514
|
-
const
|
|
8515
|
-
|
|
8516
|
-
|
|
8517
|
-
|
|
8518
|
-
|
|
8519
|
-
|
|
8520
|
-
|
|
8521
|
-
|
|
8522
|
-
|
|
8523
|
-
|
|
8524
|
-
|
|
8525
|
-
|
|
8526
|
-
|
|
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]
|
|
8527
8442
|
}))
|
|
8528
|
-
})
|
|
8529
|
-
|
|
8530
|
-
name: f.name,
|
|
8531
|
-
lines: [f.lines[0], f.lines[1]],
|
|
8532
|
-
decorators: f.decorators ?? [],
|
|
8533
|
-
async: f.async
|
|
8534
|
-
}))
|
|
8535
|
-
});
|
|
8536
|
-
}
|
|
8537
|
-
if (exitCode === 2) {
|
|
8538
|
-
warnings.push({
|
|
8539
|
-
path: "",
|
|
8540
|
-
message: `python scanner exited with code 2 (fatal); partial results only. stderr: ${stderr.slice(0, 200)}`
|
|
8541
|
-
});
|
|
8443
|
+
});
|
|
8444
|
+
}
|
|
8542
8445
|
}
|
|
8543
|
-
return {
|
|
8446
|
+
return {
|
|
8447
|
+
path: relPath,
|
|
8448
|
+
total_lines,
|
|
8449
|
+
imports: allImports,
|
|
8450
|
+
constants: allConstants,
|
|
8451
|
+
classes: allClasses,
|
|
8452
|
+
functions: allFunctions
|
|
8453
|
+
};
|
|
8544
8454
|
}
|
|
8545
8455
|
};
|
|
8546
|
-
|
|
8456
|
+
vueScanner = new VueScanner();
|
|
8547
8457
|
}
|
|
8548
8458
|
});
|
|
8549
8459
|
|
|
8550
|
-
// src/code-map
|
|
8551
|
-
var
|
|
8552
|
-
__export(
|
|
8553
|
-
|
|
8554
|
-
|
|
8555
|
-
jsScanner: () => jsScanner,
|
|
8556
|
-
parseAndExtract: () => parseAndExtract,
|
|
8557
|
-
parseJsSource: () => parseJsSource,
|
|
8558
|
-
parseSourceWithPlugins: () => parseSourceWithPlugins
|
|
8460
|
+
// src/commands/code-map.ts
|
|
8461
|
+
var code_map_exports = {};
|
|
8462
|
+
__export(code_map_exports, {
|
|
8463
|
+
codeMap: () => codeMap,
|
|
8464
|
+
computeSourcesDigest: () => computeSourcesDigest
|
|
8559
8465
|
});
|
|
8560
|
-
import { readFileSync as
|
|
8561
|
-
import {
|
|
8562
|
-
|
|
8563
|
-
|
|
8564
|
-
|
|
8565
|
-
|
|
8566
|
-
|
|
8567
|
-
|
|
8568
|
-
|
|
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";
|
|
8480
|
+
}
|
|
8481
|
+
return `${rel}:${contentHash}`;
|
|
8569
8482
|
});
|
|
8483
|
+
return createHash4("sha256").update(lines.join("\n")).digest("hex");
|
|
8570
8484
|
}
|
|
8571
|
-
function
|
|
8572
|
-
|
|
8573
|
-
|
|
8574
|
-
|
|
8575
|
-
|
|
8576
|
-
|
|
8577
|
-
|
|
8578
|
-
|
|
8579
|
-
return
|
|
8580
|
-
|
|
8581
|
-
|
|
8582
|
-
|
|
8583
|
-
const
|
|
8584
|
-
|
|
8585
|
-
|
|
8586
|
-
|
|
8587
|
-
|
|
8588
|
-
|
|
8589
|
-
|
|
8590
|
-
|
|
8591
|
-
return [start, end];
|
|
8592
|
-
}
|
|
8593
|
-
function processImportDeclaration(node) {
|
|
8594
|
-
const items = [];
|
|
8595
|
-
for (const spec of node.specifiers) {
|
|
8596
|
-
if (spec.type === "ImportDefaultSpecifier") {
|
|
8597
|
-
items.push(`default:${spec.local.name}`);
|
|
8598
|
-
} else if (spec.type === "ImportNamespaceSpecifier") {
|
|
8599
|
-
items.push(`*:${spec.local.name}`);
|
|
8600
|
-
} else if (spec.type === "ImportSpecifier") {
|
|
8601
|
-
const imported = spec.imported;
|
|
8602
|
-
items.push(imported.type === "Identifier" ? imported.name : spec.local.name);
|
|
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;
|
|
8494
|
+
}
|
|
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));
|
|
8603
8505
|
}
|
|
8604
8506
|
}
|
|
8605
|
-
|
|
8606
|
-
|
|
8607
|
-
|
|
8608
|
-
|
|
8609
|
-
|
|
8610
|
-
|
|
8611
|
-
|
|
8612
|
-
|
|
8613
|
-
|
|
8614
|
-
|
|
8615
|
-
|
|
8616
|
-
|
|
8617
|
-
|
|
8618
|
-
|
|
8619
|
-
|
|
8620
|
-
|
|
8621
|
-
|
|
8622
|
-
|
|
8623
|
-
|
|
8624
|
-
|
|
8625
|
-
|
|
8626
|
-
|
|
8627
|
-
for (const
|
|
8628
|
-
|
|
8629
|
-
|
|
8630
|
-
|
|
8631
|
-
|
|
8632
|
-
|
|
8633
|
-
|
|
8634
|
-
|
|
8635
|
-
|
|
8636
|
-
|
|
8637
|
-
}
|
|
8638
|
-
methods.push({
|
|
8639
|
-
name: methodName,
|
|
8640
|
-
lines: getLineRange(m),
|
|
8641
|
-
async: m.async
|
|
8507
|
+
const jsFiles = [
|
|
8508
|
+
...buckets[".js"] ?? [],
|
|
8509
|
+
...buckets[".jsx"] ?? [],
|
|
8510
|
+
...buckets[".mjs"] ?? [],
|
|
8511
|
+
...buckets[".cjs"] ?? []
|
|
8512
|
+
];
|
|
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));
|
|
8528
|
+
}
|
|
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})`
|
|
8642
8539
|
});
|
|
8643
8540
|
}
|
|
8644
8541
|
}
|
|
8645
|
-
|
|
8646
|
-
|
|
8647
|
-
|
|
8648
|
-
|
|
8649
|
-
};
|
|
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}`);
|
|
8553
|
+
}
|
|
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;
|
|
8563
|
+
}
|
|
8564
|
+
mkdirSync5(dirname4(opts.out), { recursive: true });
|
|
8565
|
+
writeFileSync6(opts.out, yamlBody, "utf8");
|
|
8566
|
+
log.ok(`${summaryLine} (${Date.now() - start}ms)`);
|
|
8567
|
+
return 0;
|
|
8650
8568
|
}
|
|
8651
|
-
|
|
8652
|
-
|
|
8653
|
-
|
|
8654
|
-
|
|
8655
|
-
|
|
8656
|
-
|
|
8657
|
-
|
|
8658
|
-
|
|
8659
|
-
|
|
8660
|
-
|
|
8661
|
-
|
|
8662
|
-
|
|
8569
|
+
var _require, _pkgPath, _pkg;
|
|
8570
|
+
var init_code_map = __esm({
|
|
8571
|
+
"src/commands/code-map.ts"() {
|
|
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"));
|
|
8580
|
+
}
|
|
8581
|
+
});
|
|
8582
|
+
|
|
8583
|
+
// src/code-map/freshness.ts
|
|
8584
|
+
var freshness_exports = {};
|
|
8585
|
+
__export(freshness_exports, {
|
|
8586
|
+
checkCodeMapFreshness: () => checkCodeMapFreshness
|
|
8587
|
+
});
|
|
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
|
+
};
|
|
8603
|
+
}
|
|
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 };
|
|
8663
8610
|
}
|
|
8664
|
-
|
|
8665
|
-
|
|
8666
|
-
|
|
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);
|
|
8621
|
+
}
|
|
8622
|
+
} catch {
|
|
8667
8623
|
}
|
|
8668
|
-
|
|
8669
|
-
|
|
8670
|
-
|
|
8671
|
-
|
|
8672
|
-
|
|
8673
|
-
|
|
8674
|
-
|
|
8675
|
-
|
|
8676
|
-
|
|
8677
|
-
name: varName,
|
|
8678
|
-
lines: getLineRange(node),
|
|
8679
|
-
decorators: [],
|
|
8680
|
-
async: false
|
|
8681
|
-
});
|
|
8624
|
+
}
|
|
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 };
|
|
8682
8633
|
}
|
|
8683
8634
|
}
|
|
8684
|
-
}
|
|
8685
|
-
function processTsInterfaceDeclaration(node, exported) {
|
|
8686
|
-
if (!node || node.type !== "TSInterfaceDeclaration")
|
|
8687
|
-
return null;
|
|
8688
|
-
if (!node.id || node.id.type !== "Identifier")
|
|
8689
|
-
return null;
|
|
8690
8635
|
return {
|
|
8691
|
-
|
|
8692
|
-
|
|
8693
|
-
|
|
8636
|
+
status: "stale",
|
|
8637
|
+
staleFiles: staleAll.slice(0, 5),
|
|
8638
|
+
staleCount: staleAll.length,
|
|
8639
|
+
mapPath
|
|
8694
8640
|
};
|
|
8695
8641
|
}
|
|
8696
|
-
function
|
|
8697
|
-
|
|
8698
|
-
|
|
8699
|
-
|
|
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 {
|
|
8700
8648
|
return null;
|
|
8701
|
-
|
|
8702
|
-
|
|
8703
|
-
|
|
8704
|
-
|
|
8705
|
-
|
|
8649
|
+
}
|
|
8650
|
+
}
|
|
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();
|
|
8657
|
+
}
|
|
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;
|
|
8677
|
+
}
|
|
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");
|
|
8706
8754
|
}
|
|
8707
|
-
function
|
|
8708
|
-
|
|
8709
|
-
|
|
8710
|
-
|
|
8711
|
-
|
|
8712
|
-
const
|
|
8713
|
-
|
|
8714
|
-
if (
|
|
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)
|
|
8715
8763
|
continue;
|
|
8716
|
-
|
|
8717
|
-
|
|
8718
|
-
|
|
8719
|
-
|
|
8764
|
+
const key = line.slice(0, colon).trim();
|
|
8765
|
+
if (!key)
|
|
8766
|
+
continue;
|
|
8767
|
+
out[key] = line.slice(colon + 1).trim();
|
|
8720
8768
|
}
|
|
8721
|
-
return
|
|
8722
|
-
name: node.id.name,
|
|
8723
|
-
lines: getLineRange(node),
|
|
8724
|
-
exported,
|
|
8725
|
-
members
|
|
8726
|
-
};
|
|
8769
|
+
return out;
|
|
8727
8770
|
}
|
|
8728
|
-
function
|
|
8729
|
-
if (
|
|
8730
|
-
|
|
8731
|
-
|
|
8732
|
-
|
|
8733
|
-
|
|
8734
|
-
|
|
8735
|
-
|
|
8736
|
-
|
|
8737
|
-
|
|
8738
|
-
|
|
8739
|
-
|
|
8740
|
-
|
|
8741
|
-
|
|
8742
|
-
|
|
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);
|
|
8779
|
+
}
|
|
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();
|
|
8788
|
+
continue;
|
|
8743
8789
|
}
|
|
8744
|
-
|
|
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 });
|
|
8745
8802
|
}
|
|
8746
|
-
|
|
8747
|
-
|
|
8803
|
+
return rows;
|
|
8804
|
+
}
|
|
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)) {
|
|
8748
8809
|
return;
|
|
8749
8810
|
}
|
|
8750
|
-
if (
|
|
8751
|
-
|
|
8752
|
-
if (fe)
|
|
8753
|
-
buckets.functions.push(fe);
|
|
8811
|
+
if (!existsSync15(legacyPath)) {
|
|
8812
|
+
warnings.push("tasks.md not found and tasks.yml missing \u2014 skipping tasks migration");
|
|
8754
8813
|
return;
|
|
8755
8814
|
}
|
|
8756
|
-
|
|
8757
|
-
|
|
8758
|
-
|
|
8759
|
-
|
|
8760
|
-
|
|
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;
|
|
8761
8829
|
}
|
|
8762
|
-
if (
|
|
8763
|
-
|
|
8764
|
-
return;
|
|
8830
|
+
if (enableContextGovernance || fm["context-governance"] === "v1") {
|
|
8831
|
+
data["context-governance"] = "v1";
|
|
8765
8832
|
}
|
|
8766
|
-
|
|
8767
|
-
|
|
8768
|
-
|
|
8769
|
-
|
|
8770
|
-
|
|
8771
|
-
|
|
8772
|
-
|
|
8773
|
-
|
|
8774
|
-
|
|
8775
|
-
|
|
8776
|
-
|
|
8777
|
-
|
|
8778
|
-
|
|
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;
|
|
8779
8859
|
}
|
|
8780
|
-
|
|
8781
|
-
|
|
8782
|
-
|
|
8783
|
-
|
|
8784
|
-
|
|
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;
|
|
8785
8892
|
}
|
|
8893
|
+
data[key] = inline;
|
|
8894
|
+
i++;
|
|
8786
8895
|
}
|
|
8787
|
-
|
|
8788
|
-
|
|
8789
|
-
|
|
8790
|
-
|
|
8791
|
-
|
|
8792
|
-
|
|
8896
|
+
return data;
|
|
8897
|
+
}
|
|
8898
|
+
function migrateAgentLogs(changeDir, changed, pendingWrites, pendingDeletes) {
|
|
8899
|
+
const agentLogDir = join18(changeDir, "agent-log");
|
|
8900
|
+
if (!existsSync15(agentLogDir))
|
|
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}`);
|
|
8915
|
+
}
|
|
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
|
+
);
|
|
8793
8946
|
}
|
|
8947
|
+
} else {
|
|
8948
|
+
const structured = content.match(/^## Tier\s*\n\s*-\s*(\d)\s*$/m);
|
|
8949
|
+
if (structured)
|
|
8950
|
+
detectedTier = structured[1];
|
|
8794
8951
|
}
|
|
8795
8952
|
}
|
|
8796
|
-
|
|
8797
|
-
|
|
8798
|
-
|
|
8799
|
-
|
|
8800
|
-
|
|
8801
|
-
|
|
8802
|
-
|
|
8803
|
-
|
|
8804
|
-
|
|
8805
|
-
|
|
8806
|
-
|
|
8807
|
-
functions: [],
|
|
8808
|
-
classes: [],
|
|
8809
|
-
interfaces: [],
|
|
8810
|
-
types: [],
|
|
8811
|
-
enums: []
|
|
8812
|
-
};
|
|
8813
|
-
for (const stmt of ast.program.body) {
|
|
8814
|
-
processStatement(stmt, buckets, !!opts.extractTsTypes);
|
|
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");
|
|
8815
8964
|
}
|
|
8816
|
-
return
|
|
8817
|
-
}
|
|
8818
|
-
function countSourceLines(source) {
|
|
8819
|
-
if (source === "")
|
|
8820
|
-
return 0;
|
|
8821
|
-
return source.split(/\r?\n/).length - (source.endsWith("\n") || source.endsWith("\r\n") ? 1 : 0);
|
|
8822
|
-
}
|
|
8823
|
-
function parseJsSource(source, relPath) {
|
|
8824
|
-
const total_lines = countSourceLines(source);
|
|
8825
|
-
const r = parseAndExtract(source, { plugins: JS_PLUGINS, extractTsTypes: false });
|
|
8826
|
-
if (!r)
|
|
8827
|
-
return null;
|
|
8828
|
-
return {
|
|
8829
|
-
path: relPath,
|
|
8830
|
-
total_lines,
|
|
8831
|
-
imports: r.imports,
|
|
8832
|
-
constants: r.constants,
|
|
8833
|
-
classes: r.classes,
|
|
8834
|
-
functions: r.functions
|
|
8835
|
-
};
|
|
8965
|
+
return { result: { changed, warnings }, pending, deletes };
|
|
8836
8966
|
}
|
|
8837
|
-
|
|
8838
|
-
|
|
8839
|
-
|
|
8840
|
-
|
|
8841
|
-
|
|
8842
|
-
|
|
8843
|
-
|
|
8844
|
-
|
|
8845
|
-
|
|
8846
|
-
|
|
8847
|
-
|
|
8848
|
-
|
|
8849
|
-
|
|
8850
|
-
"nullishCoalescingOperator",
|
|
8851
|
-
"objectRestSpread",
|
|
8852
|
-
"asyncGenerators",
|
|
8853
|
-
"numericSeparator",
|
|
8854
|
-
"logicalAssignment"
|
|
8855
|
-
];
|
|
8856
|
-
JS_PLUGINS = ["jsx", ...COMMON_PLUGINS];
|
|
8857
|
-
JavaScriptScanner = class {
|
|
8858
|
-
extensions = [".js"];
|
|
8859
|
-
async scan(absolutePath, repoRoot) {
|
|
8860
|
-
let source;
|
|
8861
|
-
try {
|
|
8862
|
-
source = readFileSync14(absolutePath, "utf8");
|
|
8863
|
-
} catch (err) {
|
|
8864
|
-
throw err;
|
|
8865
|
-
}
|
|
8866
|
-
if (isBinary(source)) {
|
|
8867
|
-
return null;
|
|
8868
|
-
}
|
|
8869
|
-
const relPath = canonicalRelPath(absolutePath, repoRoot);
|
|
8870
|
-
return parseJsSource(source, relPath);
|
|
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 {
|
|
8871
8980
|
}
|
|
8872
|
-
}
|
|
8873
|
-
|
|
8981
|
+
}
|
|
8982
|
+
throw err;
|
|
8874
8983
|
}
|
|
8875
|
-
|
|
8876
|
-
|
|
8877
|
-
// src/code-map/scanners/typescript.ts
|
|
8878
|
-
var typescript_exports = {};
|
|
8879
|
-
__export(typescript_exports, {
|
|
8880
|
-
tsScanner: () => tsScanner
|
|
8881
|
-
});
|
|
8882
|
-
import { readFileSync as readFileSync15 } from "fs";
|
|
8883
|
-
var TypeScriptScanner, tsScanner;
|
|
8884
|
-
var init_typescript = __esm({
|
|
8885
|
-
"src/code-map/scanners/typescript.ts"() {
|
|
8886
|
-
"use strict";
|
|
8887
|
-
init_common();
|
|
8888
|
-
init_javascript();
|
|
8889
|
-
TypeScriptScanner = class {
|
|
8890
|
-
extensions = [".ts", ".tsx"];
|
|
8891
|
-
async scan(absolutePath, repoRoot) {
|
|
8892
|
-
let source;
|
|
8893
|
-
try {
|
|
8894
|
-
source = readFileSync15(absolutePath, "utf8");
|
|
8895
|
-
} catch (err) {
|
|
8896
|
-
throw err;
|
|
8897
|
-
}
|
|
8898
|
-
if (isBinary(source)) {
|
|
8899
|
-
return null;
|
|
8900
|
-
}
|
|
8901
|
-
const isTsx = absolutePath.toLowerCase().endsWith(".tsx");
|
|
8902
|
-
const plugins = isTsx ? ["typescript", "jsx", ...COMMON_PLUGINS] : ["typescript", ...COMMON_PLUGINS];
|
|
8903
|
-
const r = parseAndExtract(source, { plugins, extractTsTypes: true });
|
|
8904
|
-
if (!r)
|
|
8905
|
-
return null;
|
|
8906
|
-
const relPath = canonicalRelPath(absolutePath, repoRoot);
|
|
8907
|
-
const total_lines = countSourceLines(source);
|
|
8908
|
-
return {
|
|
8909
|
-
path: relPath,
|
|
8910
|
-
total_lines,
|
|
8911
|
-
imports: r.imports,
|
|
8912
|
-
constants: r.constants,
|
|
8913
|
-
classes: r.classes,
|
|
8914
|
-
functions: r.functions,
|
|
8915
|
-
interfaces: r.interfaces,
|
|
8916
|
-
types: r.types,
|
|
8917
|
-
enums: r.enums
|
|
8918
|
-
};
|
|
8919
|
-
}
|
|
8920
|
-
};
|
|
8921
|
-
tsScanner = new TypeScriptScanner();
|
|
8984
|
+
for (const r of renames) {
|
|
8985
|
+
renameSync(r.tmp, r.final);
|
|
8922
8986
|
}
|
|
8923
|
-
|
|
8924
|
-
|
|
8925
|
-
|
|
8926
|
-
|
|
8927
|
-
|
|
8928
|
-
|
|
8929
|
-
}
|
|
8930
|
-
|
|
8931
|
-
|
|
8932
|
-
|
|
8933
|
-
|
|
8934
|
-
|
|
8935
|
-
|
|
8936
|
-
|
|
8937
|
-
|
|
8938
|
-
|
|
8939
|
-
|
|
8940
|
-
|
|
8941
|
-
|
|
8942
|
-
|
|
8943
|
-
|
|
8944
|
-
|
|
8945
|
-
|
|
8946
|
-
|
|
8947
|
-
|
|
8948
|
-
|
|
8949
|
-
|
|
8950
|
-
|
|
8951
|
-
|
|
8952
|
-
|
|
8953
|
-
|
|
8954
|
-
|
|
8955
|
-
|
|
8956
|
-
|
|
8957
|
-
|
|
8958
|
-
|
|
8959
|
-
|
|
8960
|
-
|
|
8961
|
-
|
|
8962
|
-
|
|
8963
|
-
|
|
8964
|
-
|
|
8965
|
-
|
|
8966
|
-
|
|
8967
|
-
|
|
8968
|
-
|
|
8969
|
-
|
|
8970
|
-
|
|
8971
|
-
|
|
8972
|
-
|
|
8973
|
-
|
|
8974
|
-
|
|
8975
|
-
|
|
8976
|
-
|
|
8977
|
-
|
|
8978
|
-
|
|
8979
|
-
|
|
8980
|
-
|
|
8981
|
-
|
|
8982
|
-
|
|
8983
|
-
|
|
8984
|
-
|
|
8985
|
-
|
|
8986
|
-
|
|
8987
|
-
|
|
8988
|
-
|
|
8989
|
-
|
|
8990
|
-
|
|
8991
|
-
|
|
8992
|
-
}
|
|
8993
|
-
for (const fn of scriptEntry.functions) {
|
|
8994
|
-
allFunctions.push({
|
|
8995
|
-
...fn,
|
|
8996
|
-
lines: [fn.lines[0] + offset, fn.lines[1] + offset]
|
|
8997
|
-
});
|
|
8998
|
-
}
|
|
8999
|
-
for (const cls of scriptEntry.classes) {
|
|
9000
|
-
allClasses.push({
|
|
9001
|
-
...cls,
|
|
9002
|
-
lines: [cls.lines[0] + offset, cls.lines[1] + offset],
|
|
9003
|
-
methods: cls.methods.map((m) => ({
|
|
9004
|
-
...m,
|
|
9005
|
-
lines: [m.lines[0] + offset, m.lines[1] + offset]
|
|
9006
|
-
}))
|
|
9007
|
-
});
|
|
9008
|
-
}
|
|
8987
|
+
for (const d of deletes) {
|
|
8988
|
+
try {
|
|
8989
|
+
rmSync2(d.path, { force: true });
|
|
8990
|
+
} catch {
|
|
8991
|
+
}
|
|
8992
|
+
}
|
|
8993
|
+
}
|
|
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);
|
|
9019
|
+
}
|
|
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}/`);
|
|
9009
9056
|
}
|
|
9010
|
-
|
|
9011
|
-
path: relPath,
|
|
9012
|
-
total_lines,
|
|
9013
|
-
imports: allImports,
|
|
9014
|
-
constants: allConstants,
|
|
9015
|
-
classes: allClasses,
|
|
9016
|
-
functions: allFunctions
|
|
9017
|
-
};
|
|
9057
|
+
process.exit(1);
|
|
9018
9058
|
}
|
|
9019
|
-
}
|
|
9020
|
-
|
|
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();
|
|
9021
9107
|
}
|
|
9022
9108
|
});
|
|
9023
9109
|
|
|
9024
|
-
// src/commands/
|
|
9025
|
-
var
|
|
9026
|
-
__export(
|
|
9027
|
-
|
|
9110
|
+
// src/commands/upgrade.ts
|
|
9111
|
+
var upgrade_exports = {};
|
|
9112
|
+
__export(upgrade_exports, {
|
|
9113
|
+
upgrade: () => upgrade
|
|
9028
9114
|
});
|
|
9029
|
-
import { existsSync as existsSync16, mkdirSync as mkdirSync8, readFileSync as
|
|
9030
|
-
import {
|
|
9031
|
-
|
|
9032
|
-
|
|
9033
|
-
|
|
9034
|
-
|
|
9035
|
-
|
|
9036
|
-
|
|
9037
|
-
|
|
9038
|
-
|
|
9039
|
-
|
|
9040
|
-
|
|
9041
|
-
|
|
9042
|
-
|
|
9043
|
-
}
|
|
9044
|
-
const include = [...cfg.include, ...opts.include];
|
|
9045
|
-
const exclude = [...cfg.exclude, ...opts.exclude];
|
|
9046
|
-
const files = walkRepo(root, { include, exclude });
|
|
9047
|
-
const buckets = bucketByExtension(files);
|
|
9048
|
-
const result = { entries: [], warnings: [] };
|
|
9049
|
-
const tasks = [];
|
|
9050
|
-
if (buckets[".py"]?.length) {
|
|
9051
|
-
const { pythonScanner: pythonScanner2 } = await Promise.resolve().then(() => (init_python(), python_exports));
|
|
9052
|
-
if (pythonScanner2.scanBatch) {
|
|
9053
|
-
tasks.push(pythonScanner2.scanBatch(buckets[".py"], root));
|
|
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)) });
|
|
9054
9129
|
}
|
|
9055
9130
|
}
|
|
9056
|
-
|
|
9057
|
-
|
|
9058
|
-
|
|
9059
|
-
|
|
9060
|
-
|
|
9061
|
-
|
|
9062
|
-
|
|
9063
|
-
|
|
9064
|
-
|
|
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
|
+
}
|
|
9065
9140
|
}
|
|
9066
|
-
|
|
9067
|
-
|
|
9068
|
-
...buckets[".tsx"] ?? []
|
|
9069
|
-
];
|
|
9070
|
-
if (tsFiles.length) {
|
|
9071
|
-
const { tsScanner: tsScanner2 } = await Promise.resolve().then(() => (init_typescript(), typescript_exports));
|
|
9072
|
-
tasks.push(scanInProcess(tsScanner2, tsFiles, root));
|
|
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" });
|
|
9073
9143
|
}
|
|
9074
|
-
|
|
9075
|
-
|
|
9076
|
-
|
|
9144
|
+
}
|
|
9145
|
+
function applyCopy(plan) {
|
|
9146
|
+
for (const item of plan) {
|
|
9147
|
+
mkdirSync8(dirname5(item.dest), { recursive: true });
|
|
9148
|
+
copyFileSync3(item.src, item.dest);
|
|
9077
9149
|
}
|
|
9078
|
-
|
|
9079
|
-
|
|
9080
|
-
|
|
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);
|
|
9081
9157
|
}
|
|
9082
|
-
|
|
9083
|
-
|
|
9084
|
-
|
|
9085
|
-
|
|
9086
|
-
|
|
9087
|
-
|
|
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
|
|
9088
9178
|
});
|
|
9089
9179
|
}
|
|
9180
|
+
log.blank();
|
|
9181
|
+
return;
|
|
9090
9182
|
}
|
|
9091
|
-
|
|
9092
|
-
|
|
9093
|
-
|
|
9094
|
-
|
|
9095
|
-
|
|
9096
|
-
|
|
9097
|
-
|
|
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;
|
|
9098
9200
|
}
|
|
9099
|
-
|
|
9100
|
-
|
|
9101
|
-
|
|
9102
|
-
|
|
9103
|
-
|
|
9104
|
-
|
|
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 {
|
|
9105
9208
|
}
|
|
9106
|
-
|
|
9107
|
-
|
|
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");
|
|
9108
9216
|
}
|
|
9109
|
-
|
|
9110
|
-
|
|
9111
|
-
log.
|
|
9112
|
-
|
|
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();
|
|
9113
9230
|
}
|
|
9114
|
-
var
|
|
9115
|
-
|
|
9116
|
-
"src/commands/code-map.ts"() {
|
|
9231
|
+
var init_upgrade = __esm({
|
|
9232
|
+
"src/commands/upgrade.ts"() {
|
|
9117
9233
|
"use strict";
|
|
9234
|
+
init_paths();
|
|
9118
9235
|
init_logger();
|
|
9119
|
-
|
|
9120
|
-
|
|
9121
|
-
init_config();
|
|
9122
|
-
_require = createRequire(import.meta.url);
|
|
9123
|
-
_pkgPath = join19(fileURLToPath2(import.meta.url), "..", "..", "..", "package.json");
|
|
9124
|
-
_pkg = JSON.parse(readFileSync17(_pkgPath, "utf8"));
|
|
9236
|
+
init_provider();
|
|
9237
|
+
init_migrate();
|
|
9125
9238
|
}
|
|
9126
9239
|
});
|
|
9127
9240
|
|
|
@@ -9130,11 +9243,11 @@ var refresh_exports = {};
|
|
|
9130
9243
|
__export(refresh_exports, {
|
|
9131
9244
|
refresh: () => refresh
|
|
9132
9245
|
});
|
|
9133
|
-
import { existsSync as existsSync17, mkdirSync as mkdirSync9, readdirSync as readdirSync10, copyFileSync as copyFileSync4, readFileSync as
|
|
9134
|
-
import { dirname as dirname6, join as join20, relative as
|
|
9135
|
-
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";
|
|
9136
9249
|
function fileHash2(filePath) {
|
|
9137
|
-
return
|
|
9250
|
+
return createHash5("sha256").update(readFileSync19(filePath)).digest("hex");
|
|
9138
9251
|
}
|
|
9139
9252
|
function planForceRefresh(srcDir, destDir, sectionLabel) {
|
|
9140
9253
|
const plan = [];
|
|
@@ -9156,7 +9269,7 @@ function planForceRefresh(srcDir, destDir, sectionLabel) {
|
|
|
9156
9269
|
}
|
|
9157
9270
|
if (!item.isFile())
|
|
9158
9271
|
continue;
|
|
9159
|
-
const rel = join20(sectionLabel,
|
|
9272
|
+
const rel = join20(sectionLabel, relative7(srcDir, sp)).replace(/\\/g, "/");
|
|
9160
9273
|
if (!existsSync17(dp)) {
|
|
9161
9274
|
plan.push({ src: sp, dest: dp, rel, action: "add" });
|
|
9162
9275
|
} else if (fileHash2(sp) !== fileHash2(dp)) {
|
|
@@ -9178,6 +9291,27 @@ function planSingleFile(src, dest, rel) {
|
|
|
9178
9291
|
return { src, dest, rel, action: "overwrite" };
|
|
9179
9292
|
return { src, dest, rel, action: "skip" };
|
|
9180
9293
|
}
|
|
9294
|
+
function ensureGitignoreEntry2(cwd, entry) {
|
|
9295
|
+
const path = join20(cwd, ".gitignore");
|
|
9296
|
+
const trimmed = entry.trim();
|
|
9297
|
+
if (!trimmed)
|
|
9298
|
+
return false;
|
|
9299
|
+
const re = new RegExp(`^\\s*${trimmed.replace(/[.*+?^${}()|[\\]\\\\]/g, "\\$&")}\\s*$`, "m");
|
|
9300
|
+
let existing = "";
|
|
9301
|
+
if (existsSync17(path))
|
|
9302
|
+
existing = readFileSync19(path, "utf8");
|
|
9303
|
+
if (re.test(existing))
|
|
9304
|
+
return false;
|
|
9305
|
+
const sep = existing.length > 0 && !existing.endsWith("\n") ? "\n" : "";
|
|
9306
|
+
const block = existing.length === 0 ? `# cdd-kit generated backups (do not commit)
|
|
9307
|
+
${trimmed}
|
|
9308
|
+
` : `${sep}
|
|
9309
|
+
# cdd-kit generated backups (do not commit)
|
|
9310
|
+
${trimmed}
|
|
9311
|
+
`;
|
|
9312
|
+
writeFileSync10(path, existing + block, "utf8");
|
|
9313
|
+
return true;
|
|
9314
|
+
}
|
|
9181
9315
|
function applyPlan(plan, backupRoot) {
|
|
9182
9316
|
let added = 0;
|
|
9183
9317
|
let overwritten = 0;
|
|
@@ -9221,7 +9355,7 @@ function resyncModelPolicy(cwd) {
|
|
|
9221
9355
|
const desired = {};
|
|
9222
9356
|
const agentFiles = readdirSync10(AGENTS_HOME, { withFileTypes: true }).filter((d) => d.isFile() && d.name.endsWith(".md"));
|
|
9223
9357
|
for (const f of agentFiles) {
|
|
9224
|
-
const content =
|
|
9358
|
+
const content = readFileSync19(join20(AGENTS_HOME, f.name), "utf8");
|
|
9225
9359
|
const fm = parseAgentFrontmatter(content);
|
|
9226
9360
|
if (fm.name && fm.model)
|
|
9227
9361
|
desired[fm.name] = fm.model;
|
|
@@ -9231,7 +9365,7 @@ function resyncModelPolicy(cwd) {
|
|
|
9231
9365
|
let existing = {};
|
|
9232
9366
|
if (existsSync17(policyPath)) {
|
|
9233
9367
|
try {
|
|
9234
|
-
existing = JSON.parse(
|
|
9368
|
+
existing = JSON.parse(readFileSync19(policyPath, "utf8"));
|
|
9235
9369
|
} catch {
|
|
9236
9370
|
}
|
|
9237
9371
|
}
|
|
@@ -9335,7 +9469,10 @@ async function refresh(opts) {
|
|
|
9335
9469
|
templateOverwritten = result.overwritten;
|
|
9336
9470
|
log.ok(` applied: +${templateAdded} added, ~${templateOverwritten} overwritten`);
|
|
9337
9471
|
if (templateOverwritten > 0) {
|
|
9338
|
-
log.info(` backup saved to: ${
|
|
9472
|
+
log.info(` backup saved to: ${relative7(cwd, backupRoot).replace(/\\/g, "/")}`);
|
|
9473
|
+
if (ensureGitignoreEntry2(cwd, ".cdd/.refresh-backup/")) {
|
|
9474
|
+
log.info(" added `.cdd/.refresh-backup/` to .gitignore");
|
|
9475
|
+
}
|
|
9339
9476
|
}
|
|
9340
9477
|
} else {
|
|
9341
9478
|
log.dim(" (dry-run \u2014 no changes written)");
|
|
@@ -9408,7 +9545,7 @@ async function refresh(opts) {
|
|
|
9408
9545
|
if (apply) {
|
|
9409
9546
|
log.ok("refresh complete.");
|
|
9410
9547
|
if (backupRoot) {
|
|
9411
|
-
log.info(`Backup of overwritten templates: ${
|
|
9548
|
+
log.info(`Backup of overwritten templates: ${relative7(cwd, backupRoot).replace(/\\/g, "/")}`);
|
|
9412
9549
|
}
|
|
9413
9550
|
log.info("Next: review changes with `git diff`, then commit:");
|
|
9414
9551
|
log.dim(" git add .cdd/code-map.yml .cdd/model-policy.json specs/templates tests/templates ci-templates .github/workflows");
|
|
@@ -9437,9 +9574,9 @@ var doctor_exports = {};
|
|
|
9437
9574
|
__export(doctor_exports, {
|
|
9438
9575
|
doctor: () => doctor
|
|
9439
9576
|
});
|
|
9440
|
-
import { existsSync as existsSync18, readdirSync as readdirSync11, readFileSync as
|
|
9441
|
-
import { createHash as
|
|
9442
|
-
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";
|
|
9443
9580
|
function fileExists(cwd, relPath) {
|
|
9444
9581
|
return existsSync18(join21(cwd, relPath));
|
|
9445
9582
|
}
|
|
@@ -9457,19 +9594,22 @@ function findFiles(dir, predicate, found = []) {
|
|
|
9457
9594
|
}
|
|
9458
9595
|
function sha256OfFile3(path) {
|
|
9459
9596
|
try {
|
|
9460
|
-
return
|
|
9597
|
+
return createHash6("sha256").update(readFileSync20(path)).digest("hex");
|
|
9461
9598
|
} catch {
|
|
9462
9599
|
return "";
|
|
9463
9600
|
}
|
|
9464
9601
|
}
|
|
9465
|
-
function inputDigest(paths) {
|
|
9466
|
-
const combined = paths.slice().sort().map((p) =>
|
|
9467
|
-
|
|
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");
|
|
9468
9608
|
}
|
|
9469
9609
|
function readContextIndexMetadata(filePath) {
|
|
9470
9610
|
if (!existsSync18(filePath))
|
|
9471
9611
|
return {};
|
|
9472
|
-
const text =
|
|
9612
|
+
const text = readFileSync20(filePath, "utf8");
|
|
9473
9613
|
const out = {};
|
|
9474
9614
|
const digestMatch = text.match(/^inputs-digest:\s*([a-f0-9]+)/m);
|
|
9475
9615
|
if (digestMatch)
|
|
@@ -9497,7 +9637,7 @@ function checkContextFreshness(cwd) {
|
|
|
9497
9637
|
}
|
|
9498
9638
|
const projectMapMeta = readContextIndexMetadata(projectMap);
|
|
9499
9639
|
const contractsIndexMeta = readContextIndexMetadata(contractsIndex);
|
|
9500
|
-
const projectInputDigest = inputDigest([contextPolicy].filter(existsSync18));
|
|
9640
|
+
const projectInputDigest = inputDigest([contextPolicy].filter(existsSync18), cwd);
|
|
9501
9641
|
if (projectMapMeta.inputsDigest === void 0) {
|
|
9502
9642
|
findings.push({
|
|
9503
9643
|
level: "warning",
|
|
@@ -9509,7 +9649,7 @@ function checkContextFreshness(cwd) {
|
|
|
9509
9649
|
message: "specs/context/project-map.md inputs changed (.cdd/context-policy.json); re-run cdd-kit context-scan"
|
|
9510
9650
|
});
|
|
9511
9651
|
}
|
|
9512
|
-
const contractsInputDigest = inputDigest(contractFiles);
|
|
9652
|
+
const contractsInputDigest = inputDigest(contractFiles, cwd);
|
|
9513
9653
|
if (contractsIndexMeta.inputsDigest === void 0) {
|
|
9514
9654
|
findings.push({
|
|
9515
9655
|
level: "warning",
|
|
@@ -9534,7 +9674,7 @@ function checkContextFreshness(cwd) {
|
|
|
9534
9674
|
}
|
|
9535
9675
|
function readAgentModel(path) {
|
|
9536
9676
|
try {
|
|
9537
|
-
const text =
|
|
9677
|
+
const text = readFileSync20(path, "utf8");
|
|
9538
9678
|
const m = text.match(/^model:\s*(\S+)/m);
|
|
9539
9679
|
return m ? m[1] : null;
|
|
9540
9680
|
} catch {
|
|
@@ -9547,7 +9687,7 @@ function checkModelPolicyDrift(cwd) {
|
|
|
9547
9687
|
return [];
|
|
9548
9688
|
let policy;
|
|
9549
9689
|
try {
|
|
9550
|
-
policy = JSON.parse(
|
|
9690
|
+
policy = JSON.parse(readFileSync20(policyPath, "utf8"));
|
|
9551
9691
|
} catch {
|
|
9552
9692
|
return [{ level: "warning", message: ".cdd/model-policy.json is not valid JSON" }];
|
|
9553
9693
|
}
|
|
@@ -9648,7 +9788,7 @@ function checkCodeMap(cwd) {
|
|
|
9648
9788
|
const more = probe.staleCount > 3 ? ` (+${probe.staleCount - 3} more)` : "";
|
|
9649
9789
|
findings.push({ level: "warning", message: `code-map stale: ${top}${more}; run \`cdd-kit code-map\`` });
|
|
9650
9790
|
}
|
|
9651
|
-
const text =
|
|
9791
|
+
const text = readFileSync20(mapPath, "utf8");
|
|
9652
9792
|
const m = text.match(/^# files: (\d+), src-lines: (\d+), map-lines: (\d+), compression: ([\d.]+)x/m);
|
|
9653
9793
|
if (m) {
|
|
9654
9794
|
findings.push({ level: "ok", message: `code-map: ${m[1]} files, ${m[4]}x compression` });
|
|
@@ -9728,7 +9868,7 @@ async function attemptAutoFixes(cwd, report) {
|
|
|
9728
9868
|
try {
|
|
9729
9869
|
let existing = {};
|
|
9730
9870
|
try {
|
|
9731
|
-
existing = JSON.parse(
|
|
9871
|
+
existing = JSON.parse(readFileSync20(policyPath, "utf8"));
|
|
9732
9872
|
} catch {
|
|
9733
9873
|
}
|
|
9734
9874
|
const merged = {
|
|
@@ -9829,7 +9969,7 @@ var lint_agents_exports = {};
|
|
|
9829
9969
|
__export(lint_agents_exports, {
|
|
9830
9970
|
lintAgents: () => lintAgents
|
|
9831
9971
|
});
|
|
9832
|
-
import { readdirSync as readdirSync12, readFileSync as
|
|
9972
|
+
import { readdirSync as readdirSync12, readFileSync as readFileSync21 } from "fs";
|
|
9833
9973
|
import { join as join22 } from "path";
|
|
9834
9974
|
import { load as yamlLoad2 } from "js-yaml";
|
|
9835
9975
|
function extractRequiredArtifactsSection(content) {
|
|
@@ -9884,7 +10024,7 @@ async function lintAgents(opts) {
|
|
|
9884
10024
|
const filePath = join22(agentsDir, filename);
|
|
9885
10025
|
let content;
|
|
9886
10026
|
try {
|
|
9887
|
-
content =
|
|
10027
|
+
content = readFileSync21(filePath, "utf8");
|
|
9888
10028
|
} catch {
|
|
9889
10029
|
violations.push({
|
|
9890
10030
|
file: filename,
|
|
@@ -9997,7 +10137,7 @@ __export(archive_exports, {
|
|
|
9997
10137
|
archive: () => archive
|
|
9998
10138
|
});
|
|
9999
10139
|
import { join as join23 } from "path";
|
|
10000
|
-
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";
|
|
10001
10141
|
import yaml4 from "js-yaml";
|
|
10002
10142
|
async function archive(changeId) {
|
|
10003
10143
|
const cwd = process.cwd();
|
|
@@ -10017,7 +10157,7 @@ async function archive(changeId) {
|
|
|
10017
10157
|
const tasksPath = join23(changeDir, "tasks.yml");
|
|
10018
10158
|
if (existsSync19(tasksPath)) {
|
|
10019
10159
|
try {
|
|
10020
|
-
const raw =
|
|
10160
|
+
const raw = readFileSync22(tasksPath, "utf8");
|
|
10021
10161
|
const data = yaml4.load(raw);
|
|
10022
10162
|
if (data?.status === "gate-blocked") {
|
|
10023
10163
|
log.warn("tasks.yml has status: gate-blocked \u2014 archiving anyway (change was paused).");
|
|
@@ -10073,7 +10213,7 @@ __export(abandon_exports, {
|
|
|
10073
10213
|
abandon: () => abandon
|
|
10074
10214
|
});
|
|
10075
10215
|
import { join as join24 } from "path";
|
|
10076
|
-
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";
|
|
10077
10217
|
import yaml5 from "js-yaml";
|
|
10078
10218
|
async function abandon(changeId, opts) {
|
|
10079
10219
|
const cwd = process.cwd();
|
|
@@ -10084,7 +10224,7 @@ async function abandon(changeId, opts) {
|
|
|
10084
10224
|
process.exit(1);
|
|
10085
10225
|
}
|
|
10086
10226
|
if (existsSync20(tasksPath)) {
|
|
10087
|
-
const raw =
|
|
10227
|
+
const raw = readFileSync23(tasksPath, "utf8");
|
|
10088
10228
|
const data = yaml5.load(raw) ?? {};
|
|
10089
10229
|
data["status"] = "abandoned";
|
|
10090
10230
|
if (!data["change-id"]) {
|
|
@@ -10127,7 +10267,7 @@ __export(list_changes_exports, {
|
|
|
10127
10267
|
listChanges: () => listChanges
|
|
10128
10268
|
});
|
|
10129
10269
|
import { join as join25 } from "path";
|
|
10130
|
-
import { existsSync as existsSync21, readdirSync as readdirSync13, readFileSync as
|
|
10270
|
+
import { existsSync as existsSync21, readdirSync as readdirSync13, readFileSync as readFileSync24 } from "fs";
|
|
10131
10271
|
import yaml6 from "js-yaml";
|
|
10132
10272
|
async function listChanges() {
|
|
10133
10273
|
const cwd = process.cwd();
|
|
@@ -10147,7 +10287,7 @@ async function listChanges() {
|
|
|
10147
10287
|
let pending = 0;
|
|
10148
10288
|
if (existsSync21(tasksPath)) {
|
|
10149
10289
|
try {
|
|
10150
|
-
const raw =
|
|
10290
|
+
const raw = readFileSync24(tasksPath, "utf8");
|
|
10151
10291
|
const data = yaml6.load(raw);
|
|
10152
10292
|
if (data?.status)
|
|
10153
10293
|
status = data.status;
|
|
@@ -10178,7 +10318,7 @@ __export(context_exports, {
|
|
|
10178
10318
|
rejectContextExpansion: () => rejectContextExpansion,
|
|
10179
10319
|
requestContextExpansion: () => requestContextExpansion
|
|
10180
10320
|
});
|
|
10181
|
-
import { existsSync as existsSync22, readFileSync as
|
|
10321
|
+
import { existsSync as existsSync22, readFileSync as readFileSync25, writeFileSync as writeFileSync13 } from "fs";
|
|
10182
10322
|
import { join as join26 } from "path";
|
|
10183
10323
|
function normalizePath(path) {
|
|
10184
10324
|
return path.replace(/\\/g, "/").replace(/^\.\//, "").trim();
|
|
@@ -10201,7 +10341,7 @@ function readManifest(changeId) {
|
|
|
10201
10341
|
log.error(`context manifest not found: specs/changes/${changeId}/context-manifest.md`);
|
|
10202
10342
|
process.exit(1);
|
|
10203
10343
|
}
|
|
10204
|
-
return
|
|
10344
|
+
return readFileSync25(manifestPath, "utf8");
|
|
10205
10345
|
}
|
|
10206
10346
|
function writeManifest(changeId, content) {
|
|
10207
10347
|
writeFileSync13(manifestPathFor(changeId), content.endsWith("\n") ? content : `${content}
|
|
@@ -10429,7 +10569,7 @@ var init_context = __esm({
|
|
|
10429
10569
|
});
|
|
10430
10570
|
|
|
10431
10571
|
// src/cli/index.ts
|
|
10432
|
-
import { readFileSync as
|
|
10572
|
+
import { readFileSync as readFileSync26 } from "fs";
|
|
10433
10573
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
10434
10574
|
import { dirname as dirname7, join as join27 } from "path";
|
|
10435
10575
|
import { Command } from "commander";
|
|
@@ -10851,7 +10991,7 @@ init_update();
|
|
|
10851
10991
|
|
|
10852
10992
|
// src/commands/new-change.ts
|
|
10853
10993
|
init_paths();
|
|
10854
|
-
import { join as join9 } from "path";
|
|
10994
|
+
import { join as join9, relative as relative3 } from "path";
|
|
10855
10995
|
import { createHash as createHash3 } from "crypto";
|
|
10856
10996
|
import { existsSync as existsSync8, readFileSync as readFileSync7, readdirSync as readdirSync5, writeFileSync as writeFileSync4 } from "fs";
|
|
10857
10997
|
import yaml from "js-yaml";
|
|
@@ -10864,8 +11004,11 @@ function sha256OfFile2(path) {
|
|
|
10864
11004
|
return "";
|
|
10865
11005
|
}
|
|
10866
11006
|
}
|
|
10867
|
-
function inputsDigest2(paths) {
|
|
10868
|
-
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");
|
|
10869
11012
|
return createHash3("sha256").update(combined).digest("hex");
|
|
10870
11013
|
}
|
|
10871
11014
|
function findContractFiles2(dir, found = []) {
|
|
@@ -10893,8 +11036,8 @@ async function ensureFreshContextIndexes(cwd) {
|
|
|
10893
11036
|
const policyPath = join9(cwd, ".cdd", "context-policy.json");
|
|
10894
11037
|
const policyInputs = [policyPath].filter(existsSync8);
|
|
10895
11038
|
const contractFiles = findContractFiles2(join9(cwd, "contracts"));
|
|
10896
|
-
const wantProjectDigest = inputsDigest2(policyInputs);
|
|
10897
|
-
const wantContractsDigest = inputsDigest2(contractFiles);
|
|
11039
|
+
const wantProjectDigest = inputsDigest2(policyInputs, cwd);
|
|
11040
|
+
const wantContractsDigest = inputsDigest2(contractFiles, cwd);
|
|
10898
11041
|
const haveProjectDigest = readIndexDigest(projectMap);
|
|
10899
11042
|
const haveContractsDigest = readIndexDigest(contractsIndex);
|
|
10900
11043
|
const needsScan = !existsSync8(projectMap) || !existsSync8(contractsIndex) || haveProjectDigest !== wantProjectDigest || haveContractsDigest !== wantContractsDigest;
|
|
@@ -11080,9 +11223,9 @@ async function validate(opts) {
|
|
|
11080
11223
|
var import_ajv = __toESM(require_ajv(), 1);
|
|
11081
11224
|
var import_ajv_formats = __toESM(require_dist(), 1);
|
|
11082
11225
|
init_logger();
|
|
11083
|
-
import { existsSync as
|
|
11226
|
+
import { existsSync as existsSync13, readFileSync as readFileSync15, readdirSync as readdirSync7 } from "fs";
|
|
11084
11227
|
import { homedir as homedir3 } from "os";
|
|
11085
|
-
import { join as
|
|
11228
|
+
import { join as join16 } from "path";
|
|
11086
11229
|
import yaml2 from "js-yaml";
|
|
11087
11230
|
import picomatch2 from "picomatch";
|
|
11088
11231
|
|
|
@@ -11260,15 +11403,15 @@ function parseContextManifest(content) {
|
|
|
11260
11403
|
}
|
|
11261
11404
|
function extractRequiredArtifactTypes(cwd, agentName) {
|
|
11262
11405
|
const candidates = [
|
|
11263
|
-
|
|
11264
|
-
|
|
11406
|
+
join16(cwd, ".claude", "agents", `${agentName}.md`),
|
|
11407
|
+
join16(homedir3(), ".claude", "agents", `${agentName}.md`)
|
|
11265
11408
|
];
|
|
11266
11409
|
let content = null;
|
|
11267
11410
|
for (const candidate of candidates) {
|
|
11268
|
-
if (!
|
|
11411
|
+
if (!existsSync13(candidate))
|
|
11269
11412
|
continue;
|
|
11270
11413
|
try {
|
|
11271
|
-
content =
|
|
11414
|
+
content = readFileSync15(candidate, "utf8");
|
|
11272
11415
|
break;
|
|
11273
11416
|
} catch {
|
|
11274
11417
|
}
|
|
@@ -11305,11 +11448,11 @@ function loadContextPolicy(cwd) {
|
|
|
11305
11448
|
unknownFilesRead: "warn-for-legacy-fail-for-new"
|
|
11306
11449
|
}
|
|
11307
11450
|
};
|
|
11308
|
-
const policyPath =
|
|
11309
|
-
if (!
|
|
11451
|
+
const policyPath = join16(cwd, ".cdd", "context-policy.json");
|
|
11452
|
+
if (!existsSync13(policyPath))
|
|
11310
11453
|
return defaults;
|
|
11311
11454
|
try {
|
|
11312
|
-
const custom = JSON.parse(
|
|
11455
|
+
const custom = JSON.parse(readFileSync15(policyPath, "utf8"));
|
|
11313
11456
|
return {
|
|
11314
11457
|
...defaults,
|
|
11315
11458
|
...custom,
|
|
@@ -11323,7 +11466,7 @@ function loadContextPolicy(cwd) {
|
|
|
11323
11466
|
}
|
|
11324
11467
|
function loadYamlFile(path) {
|
|
11325
11468
|
try {
|
|
11326
|
-
const raw =
|
|
11469
|
+
const raw = readFileSync15(path, "utf8");
|
|
11327
11470
|
return { data: yaml2.load(raw), parseError: null };
|
|
11328
11471
|
} catch (err) {
|
|
11329
11472
|
return { data: null, parseError: err.message };
|
|
@@ -11354,8 +11497,8 @@ function ajvErrorsToMessages(errors, prefix, knownKeys) {
|
|
|
11354
11497
|
return out;
|
|
11355
11498
|
}
|
|
11356
11499
|
function isContextGovernedChange(changeDir) {
|
|
11357
|
-
const tasksPath =
|
|
11358
|
-
if (!
|
|
11500
|
+
const tasksPath = join16(changeDir, "tasks.yml");
|
|
11501
|
+
if (!existsSync13(tasksPath))
|
|
11359
11502
|
return false;
|
|
11360
11503
|
const { data } = loadYamlFile(tasksPath);
|
|
11361
11504
|
return data?.["context-governance"] === "v1";
|
|
@@ -11383,12 +11526,12 @@ function lintTasksFile(tasksPath, errors, warnings) {
|
|
|
11383
11526
|
return data;
|
|
11384
11527
|
}
|
|
11385
11528
|
function resolveTier(changeDir) {
|
|
11386
|
-
const classifPath =
|
|
11387
|
-
const classificationPresent =
|
|
11388
|
-
const classificationText = classificationPresent ?
|
|
11529
|
+
const classifPath = join16(changeDir, "change-classification.md");
|
|
11530
|
+
const classificationPresent = existsSync13(classifPath);
|
|
11531
|
+
const classificationText = classificationPresent ? readFileSync15(classifPath, "utf8") : "";
|
|
11389
11532
|
const classificationHasLooseMarker = classificationPresent && TIER_PATTERN.test(classificationText);
|
|
11390
|
-
const tasksPath =
|
|
11391
|
-
if (
|
|
11533
|
+
const tasksPath = join16(changeDir, "tasks.yml");
|
|
11534
|
+
if (existsSync13(tasksPath)) {
|
|
11392
11535
|
const { data } = loadYamlFile(tasksPath);
|
|
11393
11536
|
const t = data?.tier;
|
|
11394
11537
|
if (typeof t === "number" && t >= 0 && t <= 5) {
|
|
@@ -11434,7 +11577,7 @@ function enforceTierRequirements(changeDir, agentLogDir, errors, warnings) {
|
|
|
11434
11577
|
return;
|
|
11435
11578
|
}
|
|
11436
11579
|
const tier = resolution.tier;
|
|
11437
|
-
const agentLogFiles = agentLogDir &&
|
|
11580
|
+
const agentLogFiles = agentLogDir && existsSync13(agentLogDir) ? readdirSync7(agentLogDir).map((f) => f.replace(/\.ya?ml$/, "")) : [];
|
|
11438
11581
|
if (tier <= 1) {
|
|
11439
11582
|
for (const required of ["e2e-resilience-engineer", "monkey-test-engineer", "stress-soak-engineer"]) {
|
|
11440
11583
|
if (!agentLogFiles.includes(required)) {
|
|
@@ -11450,7 +11593,7 @@ function enforceTierRequirements(changeDir, agentLogDir, errors, warnings) {
|
|
|
11450
11593
|
}
|
|
11451
11594
|
}
|
|
11452
11595
|
if (resolution.source === "tasks-frontmatter" && resolution.classificationPresent) {
|
|
11453
|
-
const text =
|
|
11596
|
+
const text = readFileSync15(join16(changeDir, "change-classification.md"), "utf8");
|
|
11454
11597
|
const structured = text.match(/^## Tier\s*\n\s*-\s*(\d)\s*$/m);
|
|
11455
11598
|
const bold = text.match(/\*\*Tier:\*\*\s*Tier\s*(\d)\b/i);
|
|
11456
11599
|
const classifTier = structured ? parseInt(structured[1], 10) : bold ? parseInt(bold[1], 10) : NaN;
|
|
@@ -11462,11 +11605,11 @@ function enforceTierRequirements(changeDir, agentLogDir, errors, warnings) {
|
|
|
11462
11605
|
}
|
|
11463
11606
|
}
|
|
11464
11607
|
function isArchivedChange(cwd, changeId) {
|
|
11465
|
-
const archiveRoot =
|
|
11466
|
-
if (!
|
|
11608
|
+
const archiveRoot = join16(cwd, "specs", "archive");
|
|
11609
|
+
if (!existsSync13(archiveRoot))
|
|
11467
11610
|
return false;
|
|
11468
11611
|
const years = readdirSync7(archiveRoot, { withFileTypes: true }).filter((d) => d.isDirectory());
|
|
11469
|
-
return years.some((year) =>
|
|
11612
|
+
return years.some((year) => existsSync13(join16(archiveRoot, year.name, changeId)));
|
|
11470
11613
|
}
|
|
11471
11614
|
function detectDependencyCycle(cwd, startChangeId) {
|
|
11472
11615
|
const visited = /* @__PURE__ */ new Set();
|
|
@@ -11479,8 +11622,8 @@ function detectDependencyCycle(cwd, startChangeId) {
|
|
|
11479
11622
|
return null;
|
|
11480
11623
|
visited.add(id);
|
|
11481
11624
|
stack.push(id);
|
|
11482
|
-
const tasksPath =
|
|
11483
|
-
if (
|
|
11625
|
+
const tasksPath = join16(cwd, "specs", "changes", id, "tasks.yml");
|
|
11626
|
+
if (existsSync13(tasksPath)) {
|
|
11484
11627
|
const { data } = loadYamlFile(tasksPath);
|
|
11485
11628
|
const deps = data?.["depends-on"] ?? [];
|
|
11486
11629
|
for (const dep of deps) {
|
|
@@ -11495,8 +11638,8 @@ function detectDependencyCycle(cwd, startChangeId) {
|
|
|
11495
11638
|
return visit(startChangeId);
|
|
11496
11639
|
}
|
|
11497
11640
|
function validateDependencies(cwd, changeId, changeDir) {
|
|
11498
|
-
const tasksPath =
|
|
11499
|
-
if (!
|
|
11641
|
+
const tasksPath = join16(changeDir, "tasks.yml");
|
|
11642
|
+
if (!existsSync13(tasksPath))
|
|
11500
11643
|
return [];
|
|
11501
11644
|
const { data } = loadYamlFile(tasksPath);
|
|
11502
11645
|
const dependencies = data?.["depends-on"] ?? [];
|
|
@@ -11510,10 +11653,10 @@ function validateDependencies(cwd, changeId, changeDir) {
|
|
|
11510
11653
|
errors.push(`tasks.yml: change cannot depend on itself (${dep})`);
|
|
11511
11654
|
continue;
|
|
11512
11655
|
}
|
|
11513
|
-
const upstreamDir =
|
|
11514
|
-
if (
|
|
11515
|
-
const upstreamTasks =
|
|
11516
|
-
if (!
|
|
11656
|
+
const upstreamDir = join16(cwd, "specs", "changes", dep);
|
|
11657
|
+
if (existsSync13(upstreamDir)) {
|
|
11658
|
+
const upstreamTasks = join16(upstreamDir, "tasks.yml");
|
|
11659
|
+
if (!existsSync13(upstreamTasks)) {
|
|
11517
11660
|
errors.push(`dependency ${dep}: missing tasks.yml`);
|
|
11518
11661
|
continue;
|
|
11519
11662
|
}
|
|
@@ -11534,8 +11677,8 @@ async function gate(changeId, opts = {}) {
|
|
|
11534
11677
|
const strict = opts.strict ?? false;
|
|
11535
11678
|
const lax = opts.lax ?? false;
|
|
11536
11679
|
const cwd = process.cwd();
|
|
11537
|
-
const changeDir =
|
|
11538
|
-
if (!
|
|
11680
|
+
const changeDir = join16(cwd, "specs", "changes", changeId);
|
|
11681
|
+
if (!existsSync13(changeDir)) {
|
|
11539
11682
|
log.error(`change not found: ${changeId} (looked in ${changeDir})`);
|
|
11540
11683
|
process.exit(1);
|
|
11541
11684
|
}
|
|
@@ -11561,13 +11704,13 @@ async function gate(changeId, opts = {}) {
|
|
|
11561
11704
|
}
|
|
11562
11705
|
const contextPolicy = loadContextPolicy(cwd);
|
|
11563
11706
|
const isNewChange = isContextGovernedChange(changeDir);
|
|
11564
|
-
const manifestPath =
|
|
11565
|
-
const hasManifest =
|
|
11707
|
+
const manifestPath = join16(changeDir, "context-manifest.md");
|
|
11708
|
+
const hasManifest = existsSync13(manifestPath);
|
|
11566
11709
|
let allowedPaths = [];
|
|
11567
11710
|
let approvedExpansions = [];
|
|
11568
11711
|
errors.push(...validateDependencies(cwd, changeId, changeDir));
|
|
11569
11712
|
if (hasManifest) {
|
|
11570
|
-
const manifest = parseContextManifest(
|
|
11713
|
+
const manifest = parseContextManifest(readFileSync15(manifestPath, "utf8"));
|
|
11571
11714
|
allowedPaths = manifest.allowedPaths;
|
|
11572
11715
|
approvedExpansions = manifest.approvedExpansions;
|
|
11573
11716
|
if (manifest.pendingExpansions > 0) {
|
|
@@ -11585,7 +11728,7 @@ async function gate(changeId, opts = {}) {
|
|
|
11585
11728
|
}
|
|
11586
11729
|
continue;
|
|
11587
11730
|
}
|
|
11588
|
-
if (!
|
|
11731
|
+
if (!existsSync13(join16(changeDir, f))) {
|
|
11589
11732
|
errors.push(`missing required artifact: ${f}`);
|
|
11590
11733
|
}
|
|
11591
11734
|
}
|
|
@@ -11595,21 +11738,21 @@ async function gate(changeId, opts = {}) {
|
|
|
11595
11738
|
continue;
|
|
11596
11739
|
if (f === "tasks.yml")
|
|
11597
11740
|
continue;
|
|
11598
|
-
const content =
|
|
11741
|
+
const content = readFileSync15(join16(changeDir, f), "utf8");
|
|
11599
11742
|
const minChars = MIN_CHARS[f] ?? 100;
|
|
11600
11743
|
if (meaningfulChars(content) < minChars) {
|
|
11601
11744
|
errors.push(`${f}: appears to be a stub (< ${minChars} meaningful chars)`);
|
|
11602
11745
|
}
|
|
11603
11746
|
}
|
|
11604
|
-
const classifPath =
|
|
11747
|
+
const classifPath = join16(changeDir, "change-classification.md");
|
|
11605
11748
|
const tierResolution = resolveTier(changeDir);
|
|
11606
|
-
if (tierResolution.tier === null &&
|
|
11749
|
+
if (tierResolution.tier === null && existsSync13(classifPath) && !tierResolution.classificationHasLooseMarker) {
|
|
11607
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)");
|
|
11608
11751
|
}
|
|
11609
11752
|
}
|
|
11610
|
-
const tasksPath =
|
|
11753
|
+
const tasksPath = join16(changeDir, "tasks.yml");
|
|
11611
11754
|
let tasksData = null;
|
|
11612
|
-
if (
|
|
11755
|
+
if (existsSync13(tasksPath)) {
|
|
11613
11756
|
tasksData = lintTasksFile(tasksPath, errors, warnings);
|
|
11614
11757
|
}
|
|
11615
11758
|
if (tasksData) {
|
|
@@ -11623,11 +11766,11 @@ async function gate(changeId, opts = {}) {
|
|
|
11623
11766
|
}
|
|
11624
11767
|
}
|
|
11625
11768
|
}
|
|
11626
|
-
const agentLogDir =
|
|
11627
|
-
if (
|
|
11769
|
+
const agentLogDir = join16(changeDir, "agent-log");
|
|
11770
|
+
if (existsSync13(agentLogDir)) {
|
|
11628
11771
|
const logFiles = readdirSync7(agentLogDir).filter((f) => f.endsWith(".yml"));
|
|
11629
11772
|
for (const f of logFiles) {
|
|
11630
|
-
const fullPath =
|
|
11773
|
+
const fullPath = join16(agentLogDir, f);
|
|
11631
11774
|
const { data, parseError } = loadYamlFile(fullPath);
|
|
11632
11775
|
if (parseError) {
|
|
11633
11776
|
errors.push(`agent-log/${f}: invalid YAML: ${parseError}`);
|
|
@@ -11695,9 +11838,9 @@ async function gate(changeId, opts = {}) {
|
|
|
11695
11838
|
errors.push(`agent-log/${f}: read unauthorized path -> ${p} (not in allowed paths or approved expansions)`);
|
|
11696
11839
|
}
|
|
11697
11840
|
}
|
|
11698
|
-
const runtimeLog =
|
|
11699
|
-
if (
|
|
11700
|
-
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) => {
|
|
11701
11844
|
try {
|
|
11702
11845
|
return JSON.parse(line).path;
|
|
11703
11846
|
} catch {
|
|
@@ -11733,8 +11876,8 @@ async function gate(changeId, opts = {}) {
|
|
|
11733
11876
|
continue;
|
|
11734
11877
|
const pathPart = pointer.split(":")[0];
|
|
11735
11878
|
if (pathPart.includes("/") && !pointer.startsWith("http")) {
|
|
11736
|
-
const abs =
|
|
11737
|
-
if (!
|
|
11879
|
+
const abs = join16(cwd, pathPart);
|
|
11880
|
+
if (!existsSync13(abs)) {
|
|
11738
11881
|
errors.push(`agent-log/${f}: artifact pointer not found: ${pathPart}`);
|
|
11739
11882
|
}
|
|
11740
11883
|
}
|
|
@@ -11783,26 +11926,26 @@ async function gate(changeId, opts = {}) {
|
|
|
11783
11926
|
// src/commands/install-hooks.ts
|
|
11784
11927
|
init_paths();
|
|
11785
11928
|
init_logger();
|
|
11786
|
-
import { existsSync as
|
|
11787
|
-
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";
|
|
11788
11931
|
var START_MARKER2 = "# cdd-kit-managed-block-start";
|
|
11789
11932
|
var END_MARKER2 = "# cdd-kit-managed-block-end";
|
|
11790
11933
|
async function installHooks() {
|
|
11791
11934
|
const cwd = process.cwd();
|
|
11792
|
-
const gitDir =
|
|
11793
|
-
if (!
|
|
11935
|
+
const gitDir = join17(cwd, ".git");
|
|
11936
|
+
if (!existsSync14(gitDir)) {
|
|
11794
11937
|
log.error("not a git repository (no .git/ found in cwd)");
|
|
11795
11938
|
process.exit(1);
|
|
11796
11939
|
}
|
|
11797
|
-
const hooksDir =
|
|
11798
|
-
|
|
11799
|
-
const dest =
|
|
11800
|
-
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");
|
|
11801
11944
|
let final;
|
|
11802
|
-
if (!
|
|
11945
|
+
if (!existsSync14(dest)) {
|
|
11803
11946
|
final = ourHook;
|
|
11804
11947
|
} else {
|
|
11805
|
-
const existing =
|
|
11948
|
+
const existing = readFileSync16(dest, "utf8");
|
|
11806
11949
|
const startIdx = existing.indexOf(START_MARKER2);
|
|
11807
11950
|
const endIdx = existing.indexOf(END_MARKER2);
|
|
11808
11951
|
if (startIdx >= 0 && endIdx > startIdx) {
|
|
@@ -11826,7 +11969,7 @@ async function installHooks() {
|
|
|
11826
11969
|
}
|
|
11827
11970
|
}
|
|
11828
11971
|
}
|
|
11829
|
-
|
|
11972
|
+
writeFileSync7(dest, final, "utf8");
|
|
11830
11973
|
try {
|
|
11831
11974
|
chmodSync2(dest, 493);
|
|
11832
11975
|
} catch {
|
|
@@ -11837,7 +11980,7 @@ async function installHooks() {
|
|
|
11837
11980
|
|
|
11838
11981
|
// src/cli/index.ts
|
|
11839
11982
|
var __dirname2 = dirname7(fileURLToPath3(import.meta.url));
|
|
11840
|
-
var pkg = JSON.parse(
|
|
11983
|
+
var pkg = JSON.parse(readFileSync26(join27(__dirname2, "..", "..", "package.json"), "utf8"));
|
|
11841
11984
|
var program = new Command();
|
|
11842
11985
|
program.name("cdd-kit").description("Contract-Driven Delivery Kit CLI").version(pkg.version);
|
|
11843
11986
|
program.command("init").description(
|