contract-driven-delivery 2.0.9 → 2.0.10

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