contract-driven-delivery 2.0.20 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli/index.js CHANGED
@@ -2774,7 +2774,7 @@ var require_fast_deep_equal = __commonJS({
2774
2774
  var require_json_schema_traverse = __commonJS({
2775
2775
  "node_modules/json-schema-traverse/index.js"(exports, module) {
2776
2776
  "use strict";
2777
- var traverse = module.exports = function(schema, opts, cb) {
2777
+ var traverse2 = module.exports = function(schema, opts, cb) {
2778
2778
  if (typeof opts == "function") {
2779
2779
  cb = opts;
2780
2780
  opts = {};
@@ -2786,7 +2786,7 @@ var require_json_schema_traverse = __commonJS({
2786
2786
  };
2787
2787
  _traverse(opts, pre, post, schema, "", schema);
2788
2788
  };
2789
- traverse.keywords = {
2789
+ traverse2.keywords = {
2790
2790
  additionalItems: true,
2791
2791
  items: true,
2792
2792
  contains: true,
@@ -2797,20 +2797,20 @@ var require_json_schema_traverse = __commonJS({
2797
2797
  then: true,
2798
2798
  else: true
2799
2799
  };
2800
- traverse.arrayKeywords = {
2800
+ traverse2.arrayKeywords = {
2801
2801
  items: true,
2802
2802
  allOf: true,
2803
2803
  anyOf: true,
2804
2804
  oneOf: true
2805
2805
  };
2806
- traverse.propsKeywords = {
2806
+ traverse2.propsKeywords = {
2807
2807
  $defs: true,
2808
2808
  definitions: true,
2809
2809
  properties: true,
2810
2810
  patternProperties: true,
2811
2811
  dependencies: true
2812
2812
  };
2813
- traverse.skipKeywords = {
2813
+ traverse2.skipKeywords = {
2814
2814
  default: true,
2815
2815
  enum: true,
2816
2816
  const: true,
@@ -2836,16 +2836,16 @@ var require_json_schema_traverse = __commonJS({
2836
2836
  for (var key in schema) {
2837
2837
  var sch = schema[key];
2838
2838
  if (Array.isArray(sch)) {
2839
- if (key in traverse.arrayKeywords) {
2839
+ if (key in traverse2.arrayKeywords) {
2840
2840
  for (var i = 0; i < sch.length; i++)
2841
2841
  _traverse(opts, pre, post, sch[i], jsonPtr + "/" + key + "/" + i, rootSchema, jsonPtr, key, schema, i);
2842
2842
  }
2843
- } else if (key in traverse.propsKeywords) {
2843
+ } else if (key in traverse2.propsKeywords) {
2844
2844
  if (sch && typeof sch == "object") {
2845
2845
  for (var prop in sch)
2846
2846
  _traverse(opts, pre, post, sch[prop], jsonPtr + "/" + key + "/" + escapeJsonPtr(prop), rootSchema, jsonPtr, key, schema, prop);
2847
2847
  }
2848
- } else if (key in traverse.keywords || opts.allKeys && !(key in traverse.skipKeywords)) {
2848
+ } else if (key in traverse2.keywords || opts.allKeys && !(key in traverse2.skipKeywords)) {
2849
2849
  _traverse(opts, pre, post, sch, jsonPtr + "/" + key, rootSchema, jsonPtr, key, schema);
2850
2850
  }
2851
2851
  }
@@ -2866,7 +2866,7 @@ var require_resolve = __commonJS({
2866
2866
  exports.getSchemaRefs = exports.resolveUrl = exports.normalizeId = exports._getFullPath = exports.getFullPath = exports.inlineRef = void 0;
2867
2867
  var util_1 = require_util();
2868
2868
  var equal = require_fast_deep_equal();
2869
- var traverse = require_json_schema_traverse();
2869
+ var traverse2 = require_json_schema_traverse();
2870
2870
  var SIMPLE_INLINED = /* @__PURE__ */ new Set([
2871
2871
  "type",
2872
2872
  "format",
@@ -2962,7 +2962,7 @@ var require_resolve = __commonJS({
2962
2962
  const pathPrefix = getFullPath(uriResolver, schId, false);
2963
2963
  const localRefs = {};
2964
2964
  const schemaRefs = /* @__PURE__ */ new Set();
2965
- traverse(schema, { allKeys: true }, (sch, jsonPtr, _, parentJsonPtr) => {
2965
+ traverse2(schema, { allKeys: true }, (sch, jsonPtr, _, parentJsonPtr) => {
2966
2966
  if (parentJsonPtr === void 0)
2967
2967
  return;
2968
2968
  const fullPath = pathPrefix + jsonPtr;
@@ -7499,20 +7499,50 @@ var require_dist = __commonJS({
7499
7499
  }
7500
7500
  });
7501
7501
 
7502
+ // src/utils/gitignore.ts
7503
+ import { existsSync as existsSync12, readFileSync as readFileSync11, writeFileSync as writeFileSync6 } from "fs";
7504
+ import { join as join13 } from "path";
7505
+ function ensureGitignoreEntry(cwd, entry, comment = "cdd-kit generated backups (do not commit)") {
7506
+ const path = join13(cwd, ".gitignore");
7507
+ const trimmed = entry.trim();
7508
+ if (!trimmed)
7509
+ return false;
7510
+ const re = new RegExp(`^\\s*${trimmed.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\s*$`, "m");
7511
+ let existing = "";
7512
+ if (existsSync12(path))
7513
+ existing = readFileSync11(path, "utf8");
7514
+ if (re.test(existing))
7515
+ return false;
7516
+ const sep = existing.length > 0 && !existing.endsWith("\n") ? "\n" : "";
7517
+ const block = existing.length === 0 ? `# ${comment}
7518
+ ${trimmed}
7519
+ ` : `${sep}
7520
+ # ${comment}
7521
+ ${trimmed}
7522
+ `;
7523
+ writeFileSync6(path, existing + block, "utf8");
7524
+ return true;
7525
+ }
7526
+ var init_gitignore = __esm({
7527
+ "src/utils/gitignore.ts"() {
7528
+ "use strict";
7529
+ }
7530
+ });
7531
+
7502
7532
  // src/commands/migrate.ts
7503
7533
  var migrate_exports = {};
7504
7534
  __export(migrate_exports, {
7505
7535
  migrate: () => migrate
7506
7536
  });
7507
- import { join as join13 } from "path";
7508
- import { cpSync as cpSync2, existsSync as existsSync12, mkdirSync as mkdirSync6, readdirSync as readdirSync7, readFileSync as readFileSync11, renameSync, rmSync as rmSync2, writeFileSync as writeFileSync6 } from "fs";
7537
+ import { join as join14 } from "path";
7538
+ import { cpSync as cpSync2, existsSync as existsSync13, mkdirSync as mkdirSync6, readdirSync as readdirSync7, readFileSync as readFileSync12, renameSync, rmSync as rmSync2, writeFileSync as writeFileSync7 } from "fs";
7509
7539
  import yaml2 from "js-yaml";
7510
7540
  function backupChangeDir(cwd, changeId, sessionStamp) {
7511
- const backupRoot = join13(cwd, ".cdd", "migrate-backup", sessionStamp);
7512
- const backupDir2 = join13(backupRoot, changeId);
7541
+ const backupRoot = join14(cwd, ".cdd", "migrate-backup", sessionStamp);
7542
+ const backupDir2 = join14(backupRoot, changeId);
7513
7543
  mkdirSync6(backupRoot, { recursive: true });
7514
- const sourceDir = join13(cwd, "specs", "changes", changeId);
7515
- if (existsSync12(sourceDir)) {
7544
+ const sourceDir = join14(cwd, "specs", "changes", changeId);
7545
+ if (existsSync13(sourceDir)) {
7516
7546
  cpSync2(sourceDir, backupDir2, { recursive: true });
7517
7547
  }
7518
7548
  return backupDir2;
@@ -7645,16 +7675,16 @@ function parseLegacyTaskList(body) {
7645
7675
  return rows;
7646
7676
  }
7647
7677
  function migrateTasksFile(changeId, changeDir, enableContextGovernance, detectedTier, changed, warnings, pendingWrites, pendingDeletes) {
7648
- const newPath = join13(changeDir, "tasks.yml");
7649
- const legacyPath = join13(changeDir, "tasks.md");
7650
- if (existsSync12(newPath)) {
7678
+ const newPath = join14(changeDir, "tasks.yml");
7679
+ const legacyPath = join14(changeDir, "tasks.md");
7680
+ if (existsSync13(newPath)) {
7651
7681
  return;
7652
7682
  }
7653
- if (!existsSync12(legacyPath)) {
7683
+ if (!existsSync13(legacyPath)) {
7654
7684
  warnings.push("tasks.md not found and tasks.yml missing \u2014 skipping tasks migration");
7655
7685
  return;
7656
7686
  }
7657
- const raw = readFileSync11(legacyPath, "utf8");
7687
+ const raw = readFileSync12(legacyPath, "utf8");
7658
7688
  const fm = parseLegacyFrontmatter(raw);
7659
7689
  const bodyMatch = raw.match(/^---\r?\n[\s\S]*?\r?\n---\r?\n?([\s\S]*)$/);
7660
7690
  const body = bodyMatch ? bodyMatch[1] : raw;
@@ -7738,17 +7768,17 @@ function parseLegacyAgentLog(content) {
7738
7768
  return data;
7739
7769
  }
7740
7770
  function migrateAgentLogs(changeDir, changed, pendingWrites, pendingDeletes) {
7741
- const agentLogDir = join13(changeDir, "agent-log");
7742
- if (!existsSync12(agentLogDir))
7771
+ const agentLogDir = join14(changeDir, "agent-log");
7772
+ if (!existsSync13(agentLogDir))
7743
7773
  return;
7744
7774
  const mdLogs = readdirSync7(agentLogDir).filter((f) => f.endsWith(".md"));
7745
7775
  for (const f of mdLogs) {
7746
- const fullPath = join13(agentLogDir, f);
7776
+ const fullPath = join14(agentLogDir, f);
7747
7777
  const yamlName = f.replace(/\.md$/, ".yml");
7748
- const yamlFull = join13(agentLogDir, yamlName);
7749
- if (existsSync12(yamlFull))
7778
+ const yamlFull = join14(agentLogDir, yamlName);
7779
+ if (existsSync13(yamlFull))
7750
7780
  continue;
7751
- const raw = readFileSync11(fullPath, "utf8");
7781
+ const raw = readFileSync12(fullPath, "utf8");
7752
7782
  const parsed = parseLegacyAgentLog(raw);
7753
7783
  const yamlOut = yaml2.dump(parsed, { lineWidth: -1, noRefs: true });
7754
7784
  pendingWrites.push({ path: yamlFull, content: yamlOut });
@@ -7757,15 +7787,15 @@ function migrateAgentLogs(changeDir, changed, pendingWrites, pendingDeletes) {
7757
7787
  }
7758
7788
  }
7759
7789
  function ensureImplementationPlanScaffold(changeId, changeDir, changed, warnings, pendingWrites) {
7760
- const planPath = join13(changeDir, "implementation-plan.md");
7761
- if (existsSync12(planPath))
7790
+ const planPath = join14(changeDir, "implementation-plan.md");
7791
+ if (existsSync13(planPath))
7762
7792
  return;
7763
- const templatePath = join13(ASSET.specsTemplates, "implementation-plan.md");
7764
- if (!existsSync12(templatePath)) {
7793
+ const templatePath = join14(ASSET.specsTemplates, "implementation-plan.md");
7794
+ if (!existsSync13(templatePath)) {
7765
7795
  warnings.push("implementation-plan.md template not found; run cdd-kit upgrade --yes after updating cdd-kit");
7766
7796
  return;
7767
7797
  }
7768
- const template = readFileSync11(templatePath, "utf8").replace(/<change-id>/g, changeId).replace(/<id>/g, changeId);
7798
+ const template = readFileSync12(templatePath, "utf8").replace(/<change-id>/g, changeId).replace(/<id>/g, changeId);
7769
7799
  pendingWrites.push({ path: planPath, content: template });
7770
7800
  changed.push("implementation-plan.md: added scaffold");
7771
7801
  warnings.push("implementation-plan.md scaffold added; fill it before implementation agents continue");
@@ -7776,9 +7806,9 @@ function migrateOne(changeId, changeDir, enableContextGovernance) {
7776
7806
  const pending = [];
7777
7807
  const deletes = [];
7778
7808
  let detectedTier = null;
7779
- const classifPath = join13(changeDir, "change-classification.md");
7780
- if (existsSync12(classifPath)) {
7781
- const content = readFileSync11(classifPath, "utf8");
7809
+ const classifPath = join14(changeDir, "change-classification.md");
7810
+ if (existsSync13(classifPath)) {
7811
+ const content = readFileSync12(classifPath, "utf8");
7782
7812
  const hasNewTierFormat = /^## Tier\s*\n\s*-\s*\d\s*$/m.test(content);
7783
7813
  const oldMatch = content.match(/\*\*Tier[:\*]+\s*(?:Tier\s*)?(\d)/i) ?? content.match(/^-?\s*Tier:\s*(?:Tier\s*)?(\d)/mi);
7784
7814
  if (oldMatch)
@@ -7809,8 +7839,8 @@ function migrateOne(changeId, changeDir, enableContextGovernance) {
7809
7839
  migrateTasksFile(changeId, changeDir, enableContextGovernance, detectedTier, changed, warnings, pending, deletes);
7810
7840
  ensureImplementationPlanScaffold(changeId, changeDir, changed, warnings, pending);
7811
7841
  migrateAgentLogs(changeDir, changed, pending, deletes);
7812
- const manifestPath = join13(changeDir, "context-manifest.md");
7813
- if (!existsSync12(manifestPath)) {
7842
+ const manifestPath = join14(changeDir, "context-manifest.md");
7843
+ if (!existsSync13(manifestPath)) {
7814
7844
  changed.push(enableContextGovernance ? "context-manifest.md: added context-governance v1 manifest scaffold" : "context-manifest.md: added legacy context manifest scaffold");
7815
7845
  pending.push({
7816
7846
  path: manifestPath,
@@ -7826,7 +7856,7 @@ function commitWritesAtomically(pending, deletes) {
7826
7856
  try {
7827
7857
  for (const write of pending) {
7828
7858
  const tmp = `${write.path}.cdd-migrate.tmp`;
7829
- writeFileSync6(tmp, write.content, "utf8");
7859
+ writeFileSync7(tmp, write.content, "utf8");
7830
7860
  renames.push({ tmp, final: write.path });
7831
7861
  }
7832
7862
  } catch (err) {
@@ -7855,8 +7885,8 @@ async function migrate(changeId, opts = {}) {
7855
7885
  const noBackup = opts.noBackup ?? false;
7856
7886
  const idsToMigrate = [];
7857
7887
  if (opts.all) {
7858
- const changesDir = join13(cwd, "specs", "changes");
7859
- if (!existsSync12(changesDir)) {
7888
+ const changesDir = join14(cwd, "specs", "changes");
7889
+ if (!existsSync13(changesDir)) {
7860
7890
  log.info("No specs/changes/ directory found \u2014 nothing to migrate.");
7861
7891
  return;
7862
7892
  }
@@ -7864,8 +7894,8 @@ async function migrate(changeId, opts = {}) {
7864
7894
  ...readdirSync7(changesDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name)
7865
7895
  );
7866
7896
  } else if (changeId) {
7867
- const specificDir = join13(cwd, "specs", "changes", changeId);
7868
- if (!existsSync12(specificDir)) {
7897
+ const specificDir = join14(cwd, "specs", "changes", changeId);
7898
+ if (!existsSync13(specificDir)) {
7869
7899
  log.error(`Change not found: specs/changes/${changeId}`);
7870
7900
  process.exit(1);
7871
7901
  }
@@ -7885,10 +7915,10 @@ async function migrate(changeId, opts = {}) {
7885
7915
  const sessionStamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
7886
7916
  let migratedCount = 0;
7887
7917
  let upToDateCount = 0;
7888
- const backupRoot = join13(cwd, ".cdd", "migrate-backup", sessionStamp);
7918
+ const backupRoot = join14(cwd, ".cdd", "migrate-backup", sessionStamp);
7889
7919
  for (const id of idsToMigrate) {
7890
- const changeDir = join13(cwd, "specs", "changes", id);
7891
- if (!existsSync12(changeDir)) {
7920
+ const changeDir = join14(cwd, "specs", "changes", id);
7921
+ if (!existsSync13(changeDir)) {
7892
7922
  log.warn(` ${id}: directory not found \u2014 skipping`);
7893
7923
  continue;
7894
7924
  }
@@ -7936,32 +7966,12 @@ async function migrate(changeId, opts = {}) {
7936
7966
  }
7937
7967
  }
7938
7968
  }
7939
- function ensureGitignoreEntry(cwd, entry) {
7940
- const path = join13(cwd, ".gitignore");
7941
- const trimmed = entry.trim();
7942
- if (!trimmed)
7943
- return false;
7944
- const re = new RegExp(`^\\s*${trimmed.replace(/[.*+?^${}()|[\\]\\\\]/g, "\\$&")}\\s*$`, "m");
7945
- let existing = "";
7946
- if (existsSync12(path))
7947
- existing = readFileSync11(path, "utf8");
7948
- if (re.test(existing))
7949
- return false;
7950
- const sep = existing.length > 0 && !existing.endsWith("\n") ? "\n" : "";
7951
- const block = existing.length === 0 ? `# cdd-kit generated backups (do not commit)
7952
- ${trimmed}
7953
- ` : `${sep}
7954
- # cdd-kit generated backups (do not commit)
7955
- ${trimmed}
7956
- `;
7957
- writeFileSync6(path, existing + block, "utf8");
7958
- return true;
7959
- }
7960
7969
  var init_migrate = __esm({
7961
7970
  "src/commands/migrate.ts"() {
7962
7971
  "use strict";
7963
7972
  init_logger();
7964
7973
  init_paths();
7974
+ init_gitignore();
7965
7975
  }
7966
7976
  });
7967
7977
 
@@ -7970,34 +7980,34 @@ var upgrade_exports = {};
7970
7980
  __export(upgrade_exports, {
7971
7981
  upgrade: () => upgrade
7972
7982
  });
7973
- import { existsSync as existsSync13, mkdirSync as mkdirSync7, readdirSync as readdirSync8, copyFileSync as copyFileSync3, readFileSync as readFileSync12, writeFileSync as writeFileSync7 } from "fs";
7974
- import { dirname as dirname4, join as join14, relative as relative4 } from "path";
7983
+ import { existsSync as existsSync14, mkdirSync as mkdirSync7, readdirSync as readdirSync8, copyFileSync as copyFileSync3, readFileSync as readFileSync13, writeFileSync as writeFileSync8 } from "fs";
7984
+ import { dirname as dirname4, join as join15, relative as relative4 } from "path";
7975
7985
  function planMissingFiles(srcDir, destDir, label, planned) {
7976
- if (!existsSync13(srcDir))
7986
+ if (!existsSync14(srcDir))
7977
7987
  return;
7978
7988
  for (const entry of readdirSync8(srcDir, { withFileTypes: true })) {
7979
- const src = join14(srcDir, entry.name);
7980
- const dest = join14(destDir, entry.name);
7989
+ const src = join15(srcDir, entry.name);
7990
+ const dest = join15(destDir, entry.name);
7981
7991
  if (entry.isDirectory()) {
7982
- planMissingFiles(src, dest, join14(label, entry.name), planned);
7992
+ planMissingFiles(src, dest, join15(label, entry.name), planned);
7983
7993
  continue;
7984
7994
  }
7985
- if (!existsSync13(dest)) {
7986
- planned.push({ src, dest, rel: join14(label, relative4(srcDir, src)) });
7995
+ if (!existsSync14(dest)) {
7996
+ planned.push({ src, dest, rel: join15(label, relative4(srcDir, src)) });
7987
7997
  }
7988
7998
  }
7989
7999
  }
7990
8000
  function planProviderGuidance(cwd, provider, planned) {
7991
8001
  if (provider === "claude" || provider === "both") {
7992
- if (!existsSync13(join14(cwd, "CLAUDE.md"))) {
7993
- planned.push({ src: ASSET.claudeTemplate, dest: join14(cwd, "CLAUDE.md"), rel: "CLAUDE.md" });
8002
+ if (!existsSync14(join15(cwd, "CLAUDE.md"))) {
8003
+ planned.push({ src: ASSET.claudeTemplate, dest: join15(cwd, "CLAUDE.md"), rel: "CLAUDE.md" });
7994
8004
  }
7995
- if (!existsSync13(join14(cwd, "AGENTS.md"))) {
7996
- planned.push({ src: ASSET.agentsTemplate, dest: join14(cwd, "AGENTS.md"), rel: "AGENTS.md" });
8005
+ if (!existsSync14(join15(cwd, "AGENTS.md"))) {
8006
+ planned.push({ src: ASSET.agentsTemplate, dest: join15(cwd, "AGENTS.md"), rel: "AGENTS.md" });
7997
8007
  }
7998
8008
  }
7999
- if ((provider === "codex" || provider === "both") && !existsSync13(join14(cwd, "CODEX.md"))) {
8000
- planned.push({ src: ASSET.codexTemplate, dest: join14(cwd, "CODEX.md"), rel: "CODEX.md" });
8009
+ if ((provider === "codex" || provider === "both") && !existsSync14(join15(cwd, "CODEX.md"))) {
8010
+ planned.push({ src: ASSET.codexTemplate, dest: join15(cwd, "CODEX.md"), rel: "CODEX.md" });
8001
8011
  }
8002
8012
  }
8003
8013
  function applyCopy(plan) {
@@ -8015,12 +8025,12 @@ async function upgrade(opts = {}) {
8015
8025
  }
8016
8026
  const provider = inferProvider(cwd, requestedProvider);
8017
8027
  const plan = [];
8018
- planMissingFiles(ASSET.contracts, join14(cwd, "contracts"), "contracts", plan);
8019
- planMissingFiles(ASSET.specsTemplates, join14(cwd, "specs", "templates"), "specs/templates", plan);
8020
- planMissingFiles(ASSET.testsTemplates, join14(cwd, "tests", "templates"), "tests/templates", plan);
8021
- planMissingFiles(ASSET.ci, join14(cwd, "ci"), "ci", plan);
8022
- planMissingFiles(ASSET.githubWorkflows, join14(cwd, ".github", "workflows"), ".github/workflows", plan);
8023
- planMissingFiles(ASSET.cddConfig, join14(cwd, ".cdd"), ".cdd", plan);
8028
+ planMissingFiles(ASSET.contracts, join15(cwd, "contracts"), "contracts", plan);
8029
+ planMissingFiles(ASSET.specsTemplates, join15(cwd, "specs", "templates"), "specs/templates", plan);
8030
+ planMissingFiles(ASSET.testsTemplates, join15(cwd, "tests", "templates"), "tests/templates", plan);
8031
+ planMissingFiles(ASSET.ci, join15(cwd, "ci"), "ci", plan);
8032
+ planMissingFiles(ASSET.githubWorkflows, join15(cwd, ".github", "workflows"), ".github/workflows", plan);
8033
+ planMissingFiles(ASSET.cddConfig, join15(cwd, ".cdd"), ".cdd", plan);
8024
8034
  planProviderGuidance(cwd, provider, plan);
8025
8035
  log.blank();
8026
8036
  log.info(`Upgrade provider: ${provider}`);
@@ -8057,11 +8067,11 @@ async function upgrade(opts = {}) {
8057
8067
  return;
8058
8068
  }
8059
8069
  applyCopy(plan);
8060
- const modelPolicyPath = join14(cwd, ".cdd", "model-policy.json");
8061
- if (existsSync13(modelPolicyPath)) {
8070
+ const modelPolicyPath = join15(cwd, ".cdd", "model-policy.json");
8071
+ if (existsSync14(modelPolicyPath)) {
8062
8072
  let existing = {};
8063
8073
  try {
8064
- existing = JSON.parse(readFileSync12(modelPolicyPath, "utf8"));
8074
+ existing = JSON.parse(readFileSync13(modelPolicyPath, "utf8"));
8065
8075
  } catch {
8066
8076
  }
8067
8077
  const merged = {
@@ -8070,7 +8080,7 @@ async function upgrade(opts = {}) {
8070
8080
  generated_at: (/* @__PURE__ */ new Date()).toISOString(),
8071
8081
  roles: existing.roles && typeof existing.roles === "object" ? existing.roles : {}
8072
8082
  };
8073
- writeFileSync7(modelPolicyPath, JSON.stringify(merged, null, 2) + "\n", "utf8");
8083
+ writeFileSync8(modelPolicyPath, JSON.stringify(merged, null, 2) + "\n", "utf8");
8074
8084
  }
8075
8085
  log.blank();
8076
8086
  log.ok(`Upgrade complete: ${plan.length} missing file(s) added.`);
@@ -8230,8 +8240,8 @@ var init_yaml_writer = __esm({
8230
8240
  });
8231
8241
 
8232
8242
  // src/code-map/config.ts
8233
- import { existsSync as existsSync14, readFileSync as readFileSync13 } from "fs";
8234
- import { join as join15 } from "path";
8243
+ import { existsSync as existsSync15, readFileSync as readFileSync14 } from "fs";
8244
+ import { join as join16 } from "path";
8235
8245
  import { load as yamlLoad } from "js-yaml";
8236
8246
  function asStringArray(value, key, where) {
8237
8247
  if (value === void 0)
@@ -8247,8 +8257,8 @@ function asStringArray(value, key, where) {
8247
8257
  return value;
8248
8258
  }
8249
8259
  function loadCodeMapConfig(cwd) {
8250
- const filePath = join15(cwd, CONFIG_REL_PATH);
8251
- if (!existsSync14(filePath)) {
8260
+ const filePath = join16(cwd, CONFIG_REL_PATH);
8261
+ if (!existsSync15(filePath)) {
8252
8262
  return {
8253
8263
  include: [...BUILTIN_INCLUDE],
8254
8264
  exclude: [...BUILTIN_EXCLUDE],
@@ -8257,7 +8267,7 @@ function loadCodeMapConfig(cwd) {
8257
8267
  }
8258
8268
  let text;
8259
8269
  try {
8260
- text = readFileSync13(filePath, "utf8");
8270
+ text = readFileSync14(filePath, "utf8");
8261
8271
  } catch (err) {
8262
8272
  throw new Error(`failed to read ${CONFIG_REL_PATH}: ${err.message}`);
8263
8273
  }
@@ -8328,7 +8338,7 @@ var init_config = __esm({
8328
8338
 
8329
8339
  // src/code-map/include-exclude.ts
8330
8340
  import { readdirSync as readdirSync9, statSync as statSync2 } from "fs";
8331
- import { join as join16 } from "path";
8341
+ import { join as join17 } from "path";
8332
8342
  import picomatch from "picomatch";
8333
8343
  function walkRepo(root, opts = {}) {
8334
8344
  const includes = opts.include ?? [];
@@ -8347,7 +8357,7 @@ function walkRepo(root, opts = {}) {
8347
8357
  }
8348
8358
  for (const entry of entries) {
8349
8359
  const relPath = relDir ? `${relDir}/${entry.name}` : entry.name;
8350
- const absPath = join16(dir, entry.name);
8360
+ const absPath = join17(dir, entry.name);
8351
8361
  if (entry.isDirectory() || entry.isSymbolicLink()) {
8352
8362
  const dirPattern = `${relPath}/**`;
8353
8363
  if (isExcluded(relPath) || isExcluded(dirPattern))
@@ -8418,89 +8428,713 @@ var init_orchestrator = __esm({
8418
8428
  }
8419
8429
  });
8420
8430
 
8421
- // src/code-map/scanners/common.ts
8422
- import { relative as relative5 } from "path";
8423
- function canonicalRelPath(absolutePath, repoRoot) {
8424
- const rel = relative5(repoRoot, absolutePath);
8425
- return rel.replace(/\\/g, "/").normalize("NFC");
8426
- }
8427
- function isAllCapsConst(name) {
8428
- return name.length >= 2 && /^[A-Z][A-Z0-9_]*$/.test(name);
8431
+ // src/code-map/freshness.ts
8432
+ import { existsSync as existsSync16, readFileSync as readFileSync15, statSync as statSync3 } from "fs";
8433
+ import { join as join18 } from "path";
8434
+ function checkCodeMapFreshness(cwd, mapRel = ".cdd/code-map.yml", include, exclude) {
8435
+ const mapPath = join18(cwd, mapRel);
8436
+ let cfg;
8437
+ try {
8438
+ cfg = loadCodeMapConfig(cwd);
8439
+ } catch (err) {
8440
+ return {
8441
+ status: "config-error",
8442
+ staleFiles: [],
8443
+ staleCount: 0,
8444
+ mapPath,
8445
+ configError: err.message
8446
+ };
8447
+ }
8448
+ const includeFinal = [...cfg.include, ...include ?? []];
8449
+ const excludeFinal = [...cfg.exclude, ...exclude ?? []];
8450
+ const sourceFiles = walkRepo(cwd, { include: includeFinal, exclude: excludeFinal });
8451
+ if (!existsSync16(mapPath)) {
8452
+ if (sourceFiles.length === 0) {
8453
+ return { status: "missing-greenfield", staleFiles: [], staleCount: 0, mapPath };
8454
+ }
8455
+ return { status: "missing-with-sources", staleFiles: [], staleCount: 0, mapPath };
8456
+ }
8457
+ const mapMtime = statSync3(mapPath).mtimeMs;
8458
+ const staleAll = [];
8459
+ for (const absPath of sourceFiles) {
8460
+ try {
8461
+ const mtime = statSync3(absPath).mtimeMs;
8462
+ if (mtime > mapMtime) {
8463
+ const rel = absPath.replace(/\\/g, "/").replace(cwd.replace(/\\/g, "/") + "/", "");
8464
+ staleAll.push(rel);
8465
+ }
8466
+ } catch {
8467
+ }
8468
+ }
8469
+ if (staleAll.length === 0) {
8470
+ return { status: "ok", staleFiles: [], staleCount: 0, mapPath };
8471
+ }
8472
+ const declaredDigest = readSourcesDigest(mapPath);
8473
+ if (declaredDigest !== null) {
8474
+ const actualDigest = computeSourcesDigest(sourceFiles, cwd);
8475
+ if (actualDigest === declaredDigest) {
8476
+ return { status: "ok", staleFiles: [], staleCount: 0, mapPath };
8477
+ }
8478
+ }
8479
+ return {
8480
+ status: "stale",
8481
+ staleFiles: staleAll.slice(0, 5),
8482
+ staleCount: staleAll.length,
8483
+ mapPath
8484
+ };
8429
8485
  }
8430
- function isBinary(content) {
8431
- const head = content.slice(0, 4096);
8432
- return head.includes("\0");
8486
+ function readSourcesDigest(mapPath) {
8487
+ try {
8488
+ const head = readFileSync15(mapPath, "utf8").slice(0, 2048);
8489
+ const m = head.match(/^# sources-digest:\s*([a-f0-9]+)/m);
8490
+ return m ? m[1] : null;
8491
+ } catch {
8492
+ return null;
8493
+ }
8433
8494
  }
8434
- var init_common = __esm({
8435
- "src/code-map/scanners/common.ts"() {
8495
+ var init_freshness = __esm({
8496
+ "src/code-map/freshness.ts"() {
8436
8497
  "use strict";
8498
+ init_include_exclude();
8499
+ init_config();
8500
+ init_code_map();
8437
8501
  }
8438
8502
  });
8439
8503
 
8440
- // src/code-map/scanners/python.ts
8441
- var python_exports = {};
8442
- __export(python_exports, {
8443
- pythonScanner: () => pythonScanner
8444
- });
8445
- import { spawnSync as spawnSync2 } from "child_process";
8446
- import { writeFileSync as writeFileSync8, unlinkSync } from "fs";
8447
- import { join as join17 } from "path";
8448
- import { tmpdir } from "os";
8449
- function detectPython2() {
8450
- for (const candidate of ["python3", "python"]) {
8451
- try {
8452
- const result = spawnSync2(candidate, ["--version"], {
8453
- encoding: "utf8",
8454
- timeout: 5e3
8455
- });
8456
- if (result.status === 0)
8457
- return candidate;
8458
- } catch {
8504
+ // src/code-map/index-reader.ts
8505
+ import { existsSync as existsSync17, readFileSync as readFileSync16 } from "fs";
8506
+ import yaml3 from "js-yaml";
8507
+ async function ensureCodeMapFresh(mapPath, refresh2) {
8508
+ if (!refresh2)
8509
+ return { refreshed: false };
8510
+ const freshness = checkCodeMapFreshness(process.cwd(), mapPath);
8511
+ if (freshness.status === "config-error") {
8512
+ return {
8513
+ refreshed: false,
8514
+ error: `.cdd/code-map-config.yml is invalid: ${freshness.configError}`
8515
+ };
8516
+ }
8517
+ if (freshness.status === "missing-with-sources" || freshness.status === "missing-greenfield" || freshness.status === "stale") {
8518
+ const { codeMap: codeMap2 } = await Promise.resolve().then(() => (init_code_map(), code_map_exports));
8519
+ const exit = await codeMap2({
8520
+ path: ".",
8521
+ out: mapPath,
8522
+ include: [],
8523
+ exclude: [],
8524
+ check: false,
8525
+ maxLines: 1e5,
8526
+ silent: true
8527
+ });
8528
+ if (exit !== 0) {
8529
+ return {
8530
+ refreshed: false,
8531
+ error: `could not refresh ${mapPath}; run \`cdd-kit code-map\` for details.`
8532
+ };
8533
+ }
8534
+ return { refreshed: true };
8535
+ }
8536
+ return { refreshed: false };
8537
+ }
8538
+ function sidecarPathFor(mapPath) {
8539
+ return `${mapPath.replace(/\.ya?ml$/i, "")}.index.json`;
8540
+ }
8541
+ function tryLoadSidecar(mapPath, mapText) {
8542
+ const sidecarPath = sidecarPathFor(mapPath);
8543
+ if (!existsSync17(sidecarPath))
8544
+ return null;
8545
+ const headerDigest = mapText.match(/^# sources-digest:\s*([a-f0-9]+)/m)?.[1];
8546
+ if (!headerDigest)
8547
+ return null;
8548
+ try {
8549
+ const raw = JSON.parse(readFileSync16(sidecarPath, "utf8"));
8550
+ if (raw && raw.sourcesDigest === headerDigest && Array.isArray(raw.entries)) {
8551
+ return raw.entries;
8459
8552
  }
8553
+ } catch {
8460
8554
  }
8461
8555
  return null;
8462
8556
  }
8463
- var TIMEOUT_MS, PythonScanner, pythonScanner;
8464
- var init_python = __esm({
8465
- "src/code-map/scanners/python.ts"() {
8557
+ function loadCodeMapEntries(mapPath) {
8558
+ if (!existsSync17(mapPath)) {
8559
+ throw new Error(`${mapPath} is missing; run \`cdd-kit code-map\` first.`);
8560
+ }
8561
+ const text = readFileSync16(mapPath, "utf8");
8562
+ const fromSidecar = tryLoadSidecar(mapPath, text);
8563
+ if (fromSidecar)
8564
+ return fromSidecar;
8565
+ const totalLinesByPath = extractTotalLines(text);
8566
+ const raw = yaml3.load(text, { schema: yaml3.JSON_SCHEMA });
8567
+ if (!raw || typeof raw !== "object" || Array.isArray(raw))
8568
+ return [];
8569
+ const entries = [];
8570
+ for (const [path, value] of Object.entries(raw)) {
8571
+ if (!value || typeof value !== "object" || Array.isArray(value))
8572
+ continue;
8573
+ const obj = value;
8574
+ entries.push({
8575
+ path,
8576
+ total_lines: totalLinesByPath.get(path) ?? (typeof obj.total_lines === "number" ? obj.total_lines : 0),
8577
+ imports: Array.isArray(obj.imports) ? obj.imports : [],
8578
+ constants: Array.isArray(obj.constants) ? obj.constants : [],
8579
+ classes: Array.isArray(obj.classes) ? obj.classes : [],
8580
+ functions: Array.isArray(obj.functions) ? obj.functions : [],
8581
+ interfaces: Array.isArray(obj.interfaces) ? obj.interfaces : [],
8582
+ types: Array.isArray(obj.types) ? obj.types : [],
8583
+ enums: Array.isArray(obj.enums) ? obj.enums : []
8584
+ });
8585
+ }
8586
+ return entries;
8587
+ }
8588
+ function extractTotalLines(text) {
8589
+ const totals = /* @__PURE__ */ new Map();
8590
+ for (const line of text.split(/\r?\n/)) {
8591
+ const m = line.match(/^((?:'[^']*(?:''[^']*)*')|[^#:\s][^#]*?):\s*#\s*(\d+)\s+lines\b/);
8592
+ if (!m)
8593
+ continue;
8594
+ totals.set(unquoteYamlKey(m[1].trim()), Number(m[2]));
8595
+ }
8596
+ return totals;
8597
+ }
8598
+ function unquoteYamlKey(key) {
8599
+ if (key.startsWith("'") && key.endsWith("'")) {
8600
+ return key.slice(1, -1).replace(/''/g, "'");
8601
+ }
8602
+ return key;
8603
+ }
8604
+ var init_index_reader = __esm({
8605
+ "src/code-map/index-reader.ts"() {
8466
8606
  "use strict";
8467
- init_paths();
8468
- init_common();
8469
- TIMEOUT_MS = parseInt(process.env["CDD_CODE_MAP_TIMEOUT_MS"] ?? "30000", 10);
8470
- PythonScanner = class {
8471
- extensions = [".py"];
8472
- _interpreter = void 0;
8473
- getInterpreter() {
8474
- if (this._interpreter === void 0) {
8475
- this._interpreter = detectPython2();
8476
- }
8477
- return this._interpreter;
8478
- }
8479
- async scan(absolutePath, repoRoot) {
8480
- const result = await this.scanBatch([absolutePath], repoRoot);
8481
- return result.entries[0] ?? null;
8482
- }
8483
- async scanBatch(absolutePaths, repoRoot) {
8484
- const interpreter = this.getInterpreter();
8485
- const entries = [];
8486
- const warnings = [];
8487
- if (!interpreter) {
8488
- const count = absolutePaths.length;
8489
- warnings.push({
8490
- path: "",
8491
- message: `python interpreter not found on PATH; skipping ${count} .py file${count === 1 ? "" : "s"}`
8492
- });
8493
- return { entries, warnings };
8494
- }
8495
- const scriptPath = ASSET.codeMapPython;
8496
- const rand = Math.random().toString(36).slice(2);
8497
- const listFile = join17(tmpdir(), `cdd-codemap-${process.pid}-${rand}.txt`);
8498
- writeFileSync8(listFile, absolutePaths.join("\n") + "\n", "utf8");
8499
- let stdout = "";
8500
- let stderr = "";
8501
- let exitCode = 0;
8502
- try {
8503
- const result = spawnSync2(
8607
+ init_freshness();
8608
+ }
8609
+ });
8610
+
8611
+ // src/code-graph/builder.ts
8612
+ import { createHash as createHash5 } from "crypto";
8613
+ import { posix } from "path";
8614
+ function stableId(parts) {
8615
+ return createHash5("sha1").update(parts.join("\0")).digest("hex").slice(0, 16);
8616
+ }
8617
+ function nodeId(filePath, kind, name, startLine) {
8618
+ return `n:${stableId([filePath, kind, name, String(startLine)])}`;
8619
+ }
8620
+ function edgeId(source, target, kind, line, name) {
8621
+ return `e:${stableId([source, target, kind, String(line ?? 0), name ?? ""])}`;
8622
+ }
8623
+ function fileNode(path, totalLines) {
8624
+ return {
8625
+ id: nodeId(path, "file", path, 1),
8626
+ kind: "file",
8627
+ name: posix.basename(path),
8628
+ qualified_name: path,
8629
+ file_path: path,
8630
+ start_line: totalLines > 0 ? 1 : 0,
8631
+ end_line: totalLines
8632
+ };
8633
+ }
8634
+ function symbolNode(filePath, kind, name, lines, exported) {
8635
+ const start = lines[0] ?? 1;
8636
+ const end = lines[1] ?? start;
8637
+ return {
8638
+ id: nodeId(filePath, kind, name, start),
8639
+ kind,
8640
+ name,
8641
+ qualified_name: `${filePath}::${name}`,
8642
+ file_path: filePath,
8643
+ start_line: start,
8644
+ end_line: end,
8645
+ ...exported !== void 0 ? { exported } : {}
8646
+ };
8647
+ }
8648
+ function addEdge(edges, source, target, kind, line, provenance, metadata) {
8649
+ edges.push({
8650
+ id: edgeId(source, target, kind, line, metadata?.name),
8651
+ source,
8652
+ target,
8653
+ kind,
8654
+ ...line ? { line } : {},
8655
+ provenance,
8656
+ ...metadata ? { metadata } : {}
8657
+ });
8658
+ }
8659
+ function isLocalImport(moduleName) {
8660
+ return moduleName.startsWith(".");
8661
+ }
8662
+ function resolvePythonRelativeImport(importerPath, moduleName) {
8663
+ const match = moduleName.match(/^(\.+)(.*)$/);
8664
+ if (!match)
8665
+ return moduleName;
8666
+ const upLevels = Math.max(0, match[1].length - 1);
8667
+ let baseDir = posix.dirname(importerPath);
8668
+ for (let i = 0; i < upLevels; i++) {
8669
+ baseDir = posix.dirname(baseDir);
8670
+ }
8671
+ const rest = match[2].replace(/^\./, "").replace(/\./g, "/");
8672
+ return rest ? posix.normalize(posix.join(baseDir, rest)) : baseDir;
8673
+ }
8674
+ function resolutionCandidates(base) {
8675
+ const ext = posix.extname(base);
8676
+ const candidates = [];
8677
+ if (ext) {
8678
+ candidates.push(base);
8679
+ const withoutExt = base.slice(0, -ext.length);
8680
+ if ([".js", ".jsx", ".mjs", ".cjs"].includes(ext)) {
8681
+ candidates.push(`${withoutExt}.ts`, `${withoutExt}.tsx`, `${withoutExt}.vue`);
8682
+ }
8683
+ } else {
8684
+ for (const candidateExt of RESOLUTION_EXTENSIONS) {
8685
+ candidates.push(`${base}${candidateExt}`);
8686
+ }
8687
+ }
8688
+ for (const candidateExt of RESOLUTION_EXTENSIONS) {
8689
+ candidates.push(posix.join(base, `index${candidateExt}`));
8690
+ }
8691
+ candidates.push(posix.join(base, "__init__.py"));
8692
+ return [...new Set(candidates)];
8693
+ }
8694
+ function resolveLocalModule(importerPath, moduleName, pathSet) {
8695
+ if (!isLocalImport(moduleName))
8696
+ return void 0;
8697
+ const base = moduleName.startsWith("./") || moduleName.startsWith("../") ? posix.normalize(posix.join(posix.dirname(importerPath), moduleName)) : resolvePythonRelativeImport(importerPath, moduleName);
8698
+ for (const candidate of resolutionCandidates(base)) {
8699
+ if (pathSet.has(candidate))
8700
+ return candidate;
8701
+ }
8702
+ return void 0;
8703
+ }
8704
+ function importedBindings(imp, resolvedPath) {
8705
+ const out = [];
8706
+ for (const item of imp.items ?? []) {
8707
+ if (item.startsWith("default:")) {
8708
+ const local = item.slice("default:".length);
8709
+ out.push({ local, imported: "default", ...resolvedPath ? { resolvedPath } : {} });
8710
+ } else if (item.startsWith("*:")) {
8711
+ const local = item.slice("*:".length);
8712
+ out.push({ local, imported: "*", ...resolvedPath ? { resolvedPath } : {} });
8713
+ } else if (/^[A-Za-z_$][\w$]*:[A-Za-z_$][\w$]*$/.test(item)) {
8714
+ const [imported, local] = item.split(":");
8715
+ out.push({ local, imported, ...resolvedPath ? { resolvedPath } : {} });
8716
+ } else {
8717
+ out.push({ local: item, imported: item, ...resolvedPath ? { resolvedPath } : {} });
8718
+ }
8719
+ }
8720
+ return out;
8721
+ }
8722
+ function simpleName(name) {
8723
+ return name.replace(/^async\s+/, "").split(".").pop() ?? name;
8724
+ }
8725
+ function isWithin(node, line) {
8726
+ return node.start_line <= line && line <= node.end_line;
8727
+ }
8728
+ function findCallerNode(nodes, caller, line) {
8729
+ const exact = nodes.find((n) => n.name === caller || n.qualified_name.endsWith(`::${caller}`));
8730
+ if (exact)
8731
+ return exact;
8732
+ return nodes.filter((n) => (n.kind === "function" || n.kind === "method") && isWithin(n, line)).sort((a, b) => a.end_line - a.start_line - (b.end_line - b.start_line))[0];
8733
+ }
8734
+ function buildCodeGraph(entries, opts) {
8735
+ const nodes = [];
8736
+ const edges = [];
8737
+ const unresolved = [];
8738
+ const files = [];
8739
+ const pathSet = new Set(entries.map((e) => e.path));
8740
+ const fileNodes = /* @__PURE__ */ new Map();
8741
+ const nodesByFile = /* @__PURE__ */ new Map();
8742
+ const symbolsByFile = /* @__PURE__ */ new Map();
8743
+ const globalSymbols = /* @__PURE__ */ new Map();
8744
+ const registerSymbol = (filePath, node, exported) => {
8745
+ nodes.push(node);
8746
+ const fileMap = symbolsByFile.get(filePath) ?? /* @__PURE__ */ new Map();
8747
+ const target = { node, exported };
8748
+ fileMap.set(node.name, [...fileMap.get(node.name) ?? [], target]);
8749
+ const shortName = simpleName(node.name);
8750
+ if (shortName !== node.name) {
8751
+ fileMap.set(shortName, [...fileMap.get(shortName) ?? [], target]);
8752
+ }
8753
+ symbolsByFile.set(filePath, fileMap);
8754
+ globalSymbols.set(node.name, [...globalSymbols.get(node.name) ?? [], target]);
8755
+ if (shortName !== node.name) {
8756
+ globalSymbols.set(shortName, [...globalSymbols.get(shortName) ?? [], target]);
8757
+ }
8758
+ };
8759
+ for (const entry of entries) {
8760
+ const file = fileNode(entry.path, entry.total_lines);
8761
+ fileNodes.set(entry.path, file);
8762
+ nodesByFile.set(entry.path, [file]);
8763
+ nodes.push(file);
8764
+ }
8765
+ for (const entry of entries) {
8766
+ const file = fileNodes.get(entry.path);
8767
+ const localNodes = nodesByFile.get(entry.path);
8768
+ for (const c of entry.classes) {
8769
+ const n = symbolNode(entry.path, "class", c.name, c.lines, c.exported);
8770
+ localNodes.push(n);
8771
+ registerSymbol(entry.path, n, c.exported === true);
8772
+ addEdge(edges, file.id, n.id, "contains", c.lines[0], "codemap");
8773
+ for (const m of c.methods ?? []) {
8774
+ const methodName = `${c.name}.${m.name}`;
8775
+ const mn = symbolNode(entry.path, "method", methodName, m.lines);
8776
+ localNodes.push(mn);
8777
+ registerSymbol(entry.path, mn, false);
8778
+ addEdge(edges, n.id, mn.id, "contains", m.lines[0], "codemap");
8779
+ }
8780
+ }
8781
+ for (const f of entry.functions) {
8782
+ const n = symbolNode(entry.path, "function", f.name, f.lines, f.exported);
8783
+ localNodes.push(n);
8784
+ registerSymbol(entry.path, n, f.exported === true);
8785
+ addEdge(edges, file.id, n.id, "contains", f.lines[0], "codemap");
8786
+ }
8787
+ for (const t of entry.interfaces ?? []) {
8788
+ const n = symbolNode(entry.path, "interface", t.name, t.lines, t.exported);
8789
+ localNodes.push(n);
8790
+ registerSymbol(entry.path, n, t.exported === true);
8791
+ addEdge(edges, file.id, n.id, "contains", t.lines[0], "codemap");
8792
+ }
8793
+ for (const t of entry.types ?? []) {
8794
+ const n = symbolNode(entry.path, "type_alias", t.name, t.lines, t.exported);
8795
+ localNodes.push(n);
8796
+ registerSymbol(entry.path, n, t.exported === true);
8797
+ addEdge(edges, file.id, n.id, "contains", t.lines[0], "codemap");
8798
+ }
8799
+ for (const e of entry.enums ?? []) {
8800
+ const n = symbolNode(entry.path, "enum", e.name, e.lines, e.exported);
8801
+ localNodes.push(n);
8802
+ registerSymbol(entry.path, n, e.exported === true);
8803
+ addEdge(edges, file.id, n.id, "contains", e.lines[0], "codemap");
8804
+ }
8805
+ for (const c of entry.constants) {
8806
+ const n = symbolNode(entry.path, "constant", c.name, [c.line, c.line], true);
8807
+ localNodes.push(n);
8808
+ registerSymbol(entry.path, n, true);
8809
+ addEdge(edges, file.id, n.id, "contains", c.line, "codemap");
8810
+ }
8811
+ }
8812
+ const importBindingsByFile = /* @__PURE__ */ new Map();
8813
+ for (const entry of entries) {
8814
+ const file = fileNodes.get(entry.path);
8815
+ const bindings = /* @__PURE__ */ new Map();
8816
+ importBindingsByFile.set(entry.path, bindings);
8817
+ for (const imp of entry.imports) {
8818
+ const resolved = resolveLocalModule(entry.path, imp.module, pathSet);
8819
+ if (resolved) {
8820
+ addEdge(edges, file.id, fileNodes.get(resolved).id, "imports", imp.line, "ast", { module: imp.module });
8821
+ }
8822
+ for (const binding of importedBindings(imp, resolved)) {
8823
+ if (!binding.resolvedPath)
8824
+ continue;
8825
+ const fileSymbols = symbolsByFile.get(binding.resolvedPath);
8826
+ const candidates = binding.imported === "*" ? Array.from(fileSymbols?.values() ?? []).flat() : fileSymbols?.get(binding.imported) ?? [];
8827
+ const exported = candidates.filter((c) => c.exported || candidates.length === 1);
8828
+ if (exported.length > 0) {
8829
+ bindings.set(binding.local, [...bindings.get(binding.local) ?? [], ...exported]);
8830
+ }
8831
+ }
8832
+ }
8833
+ }
8834
+ for (const entry of entries) {
8835
+ const localNodes = nodesByFile.get(entry.path) ?? [];
8836
+ const localSymbols = symbolsByFile.get(entry.path) ?? /* @__PURE__ */ new Map();
8837
+ const importBindings = importBindingsByFile.get(entry.path) ?? /* @__PURE__ */ new Map();
8838
+ for (const c of entry.classes) {
8839
+ const source = localSymbols.get(c.name)?.[0]?.node;
8840
+ if (!source)
8841
+ continue;
8842
+ for (const base of c.extends ?? []) {
8843
+ const target = resolveSymbol(base, entry.path, localSymbols, importBindings, globalSymbols);
8844
+ if (target)
8845
+ addEdge(edges, source.id, target.node.id, "extends", c.lines[0], "ast", { name: base });
8846
+ else
8847
+ unresolved.push({ from: source.id, kind: "extends", name: base, line: c.lines[0], file_path: entry.path });
8848
+ }
8849
+ for (const iface of c.implements ?? []) {
8850
+ const target = resolveSymbol(iface, entry.path, localSymbols, importBindings, globalSymbols);
8851
+ if (target)
8852
+ addEdge(edges, source.id, target.node.id, "implements", c.lines[0], "ast", { name: iface });
8853
+ else
8854
+ unresolved.push({ from: source.id, kind: "implements", name: iface, line: c.lines[0], file_path: entry.path });
8855
+ }
8856
+ }
8857
+ for (const call of entry.calls ?? []) {
8858
+ const caller = findCallerNode(localNodes, call.caller, call.line);
8859
+ if (!caller)
8860
+ continue;
8861
+ const target = resolveSymbol(call.callee, entry.path, localSymbols, importBindings, globalSymbols);
8862
+ if (target) {
8863
+ addEdge(edges, caller.id, target.node.id, "calls", call.line, "ast", { name: call.callee });
8864
+ } else {
8865
+ unresolved.push({ from: caller.id, kind: "calls", name: call.callee, line: call.line, file_path: entry.path });
8866
+ }
8867
+ }
8868
+ }
8869
+ const uniqueNodes = dedupeBy(nodes, (n) => n.id);
8870
+ const uniqueEdges = dedupeBy(edges, (e) => e.id);
8871
+ for (const entry of entries) {
8872
+ files.push({
8873
+ path: entry.path,
8874
+ total_lines: entry.total_lines,
8875
+ node_count: uniqueNodes.filter((n) => n.file_path === entry.path).length
8876
+ });
8877
+ }
8878
+ return {
8879
+ schema_version: "1.0",
8880
+ generator: opts.generator,
8881
+ sources_digest: opts.sourcesDigest,
8882
+ files,
8883
+ nodes: uniqueNodes.sort((a, b) => a.file_path.localeCompare(b.file_path) || a.start_line - b.start_line || a.kind.localeCompare(b.kind) || a.name.localeCompare(b.name)),
8884
+ edges: uniqueEdges.sort((a, b) => a.source.localeCompare(b.source) || a.kind.localeCompare(b.kind) || a.target.localeCompare(b.target)),
8885
+ unresolved: unresolved.sort((a, b) => a.file_path.localeCompare(b.file_path) || a.line - b.line || a.name.localeCompare(b.name))
8886
+ };
8887
+ }
8888
+ function resolveSymbol(rawName, filePath, localSymbols, importBindings, globalSymbols) {
8889
+ const candidates = symbolLookupNames(rawName);
8890
+ for (const name of candidates) {
8891
+ const imported = importBindings.get(name);
8892
+ if (imported?.length === 1)
8893
+ return imported[0];
8894
+ }
8895
+ for (const name of candidates) {
8896
+ const local = localSymbols.get(name);
8897
+ if (local?.length === 1)
8898
+ return local[0];
8899
+ }
8900
+ for (const name of candidates) {
8901
+ const global = (globalSymbols.get(name) ?? []).filter((t) => t.node.file_path !== filePath || t.exported);
8902
+ if (global.length === 1)
8903
+ return global[0];
8904
+ }
8905
+ return void 0;
8906
+ }
8907
+ function symbolLookupNames(name) {
8908
+ const cleaned = name.replace(/^this\./, "").replace(/^self\./, "");
8909
+ const parts = cleaned.split(".").filter(Boolean);
8910
+ return [.../* @__PURE__ */ new Set([cleaned, parts[parts.length - 1] ?? cleaned])];
8911
+ }
8912
+ function dedupeBy(items, key) {
8913
+ const seen = /* @__PURE__ */ new Set();
8914
+ const out = [];
8915
+ for (const item of items) {
8916
+ const k = key(item);
8917
+ if (seen.has(k))
8918
+ continue;
8919
+ seen.add(k);
8920
+ out.push(item);
8921
+ }
8922
+ return out;
8923
+ }
8924
+ var RESOLUTION_EXTENSIONS;
8925
+ var init_builder = __esm({
8926
+ "src/code-graph/builder.ts"() {
8927
+ "use strict";
8928
+ RESOLUTION_EXTENSIONS = [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".vue", ".py"];
8929
+ }
8930
+ });
8931
+
8932
+ // src/code-graph/reader.ts
8933
+ import { existsSync as existsSync18, readFileSync as readFileSync17 } from "fs";
8934
+ function graphPathFor(mapPath) {
8935
+ const match = mapPath.match(/^(.*?)(?:code-map)(.*)\.ya?ml$/i);
8936
+ if (match)
8937
+ return `${match[1]}code-graph${match[2]}.index.json`;
8938
+ return `${mapPath.replace(/\.ya?ml$/i, "")}.graph.json`;
8939
+ }
8940
+ function loadCodeGraph(graphPath) {
8941
+ if (!existsSync18(graphPath)) {
8942
+ throw new Error(`${graphPath} is missing; run \`cdd-kit code-map\` first.`);
8943
+ }
8944
+ const raw = JSON.parse(readFileSync17(graphPath, "utf8"));
8945
+ if (!raw || raw.schema_version !== "1.0" || !Array.isArray(raw.nodes) || !Array.isArray(raw.edges)) {
8946
+ throw new Error(`${graphPath} is not a cdd-kit code graph v1 index.`);
8947
+ }
8948
+ return raw;
8949
+ }
8950
+ var init_reader = __esm({
8951
+ "src/code-graph/reader.ts"() {
8952
+ "use strict";
8953
+ }
8954
+ });
8955
+
8956
+ // src/code-map/worker-dispatch.ts
8957
+ var worker_dispatch_exports = {};
8958
+ __export(worker_dispatch_exports, {
8959
+ scanLangWithWorkers: () => scanLangWithWorkers
8960
+ });
8961
+ import { execFile } from "child_process";
8962
+ import { writeFileSync as writeFileSync9, unlinkSync } from "fs";
8963
+ import { randomBytes } from "crypto";
8964
+ import { join as join19 } from "path";
8965
+ import { tmpdir } from "os";
8966
+ function chunk(arr, parts) {
8967
+ const size = Math.ceil(arr.length / parts);
8968
+ const out = [];
8969
+ for (let i = 0; i < arr.length; i += size)
8970
+ out.push(arr.slice(i, i + size));
8971
+ return out;
8972
+ }
8973
+ function scanChunkInChild(cliEntry, lang, files, repoRoot) {
8974
+ return new Promise((resolve2, reject) => {
8975
+ if (!ALLOWED_LANGS.has(lang)) {
8976
+ return reject(new Error(`refusing to spawn worker for unknown lang: ${lang}`));
8977
+ }
8978
+ const listFile = join19(
8979
+ tmpdir(),
8980
+ `cdd-cm-worker-${process.pid}-${randomBytes(12).toString("hex")}.txt`
8981
+ );
8982
+ writeFileSync9(listFile, files.join("\n") + "\n", { encoding: "utf8", mode: 384 });
8983
+ execFile(
8984
+ process.execPath,
8985
+ [cliEntry, "__code-map-scan", "--lang", lang, "--batch-file", listFile, "--repo-root", repoRoot],
8986
+ { encoding: "utf8", timeout: CHILD_TIMEOUT_MS, maxBuffer: 100 * 1024 * 1024, shell: false, windowsHide: true },
8987
+ (err, stdout) => {
8988
+ try {
8989
+ unlinkSync(listFile);
8990
+ } catch {
8991
+ }
8992
+ if (err)
8993
+ return reject(err);
8994
+ try {
8995
+ const parsed = JSON.parse(stdout);
8996
+ resolve2({ entries: parsed.entries ?? [], warnings: parsed.warnings ?? [] });
8997
+ } catch (e) {
8998
+ reject(e);
8999
+ }
9000
+ }
9001
+ );
9002
+ });
9003
+ }
9004
+ async function scanLangWithWorkers(scanner, lang, files, repoRoot, workers, cliEntry) {
9005
+ if (files.length === 0)
9006
+ return { entries: [], warnings: [] };
9007
+ const chunks = chunk(files, Math.max(1, Math.min(workers, files.length)));
9008
+ try {
9009
+ const results = await Promise.all(
9010
+ chunks.map((c) => scanChunkInChild(cliEntry, lang, c, repoRoot))
9011
+ );
9012
+ return {
9013
+ entries: results.flatMap((r) => r.entries),
9014
+ warnings: results.flatMap((r) => r.warnings)
9015
+ };
9016
+ } catch {
9017
+ return scanInProcess(scanner, files, repoRoot);
9018
+ }
9019
+ }
9020
+ var CHILD_TIMEOUT_MS, ALLOWED_LANGS;
9021
+ var init_worker_dispatch = __esm({
9022
+ "src/code-map/worker-dispatch.ts"() {
9023
+ "use strict";
9024
+ init_orchestrator();
9025
+ CHILD_TIMEOUT_MS = parseInt(process.env["CDD_CODE_MAP_WORKER_TIMEOUT_MS"] ?? "120000", 10);
9026
+ ALLOWED_LANGS = /* @__PURE__ */ new Set(["js", "ts", "vue"]);
9027
+ }
9028
+ });
9029
+
9030
+ // src/code-map/scanners/common.ts
9031
+ import { relative as relative5 } from "path";
9032
+ function canonicalRelPath(absolutePath, repoRoot) {
9033
+ const rel = relative5(repoRoot, absolutePath);
9034
+ return rel.replace(/\\/g, "/").normalize("NFC");
9035
+ }
9036
+ function isAllCapsConst(name) {
9037
+ return name.length >= 2 && /^[A-Z][A-Z0-9_]*$/.test(name);
9038
+ }
9039
+ function isBinary(content) {
9040
+ const head = content.slice(0, 4096);
9041
+ return head.includes("\0");
9042
+ }
9043
+ var init_common = __esm({
9044
+ "src/code-map/scanners/common.ts"() {
9045
+ "use strict";
9046
+ }
9047
+ });
9048
+
9049
+ // src/code-map/scanners/python.ts
9050
+ var python_exports = {};
9051
+ __export(python_exports, {
9052
+ pythonScanner: () => pythonScanner
9053
+ });
9054
+ import { spawnSync as spawnSync2 } from "child_process";
9055
+ import { writeFileSync as writeFileSync10, unlinkSync as unlinkSync2 } from "fs";
9056
+ import { randomBytes as randomBytes2 } from "crypto";
9057
+ import { join as join20 } from "path";
9058
+ import { tmpdir as tmpdir2 } from "os";
9059
+ function detectPython2() {
9060
+ for (const candidate of ["python3", "python"]) {
9061
+ try {
9062
+ const result = spawnSync2(candidate, ["--version"], {
9063
+ encoding: "utf8",
9064
+ timeout: 5e3
9065
+ });
9066
+ if (result.status === 0)
9067
+ return candidate;
9068
+ } catch {
9069
+ }
9070
+ }
9071
+ return null;
9072
+ }
9073
+ var TIMEOUT_MS, BATCH_SIZE, PythonScanner, pythonScanner;
9074
+ var init_python = __esm({
9075
+ "src/code-map/scanners/python.ts"() {
9076
+ "use strict";
9077
+ init_paths();
9078
+ init_common();
9079
+ TIMEOUT_MS = parseInt(process.env["CDD_CODE_MAP_TIMEOUT_MS"] ?? "30000", 10);
9080
+ BATCH_SIZE = Math.max(
9081
+ 1,
9082
+ parseInt(process.env["CDD_CODE_MAP_BATCH_SIZE"] ?? "400", 10) || 400
9083
+ );
9084
+ PythonScanner = class {
9085
+ extensions = [".py"];
9086
+ _interpreter = void 0;
9087
+ getInterpreter() {
9088
+ if (this._interpreter === void 0) {
9089
+ this._interpreter = detectPython2();
9090
+ }
9091
+ return this._interpreter;
9092
+ }
9093
+ async scan(absolutePath, repoRoot) {
9094
+ const result = await this.scanBatch([absolutePath], repoRoot);
9095
+ return result.entries[0] ?? null;
9096
+ }
9097
+ async scanBatch(absolutePaths, repoRoot) {
9098
+ const interpreter = this.getInterpreter();
9099
+ const entries = [];
9100
+ const warnings = [];
9101
+ if (!interpreter) {
9102
+ const count = absolutePaths.length;
9103
+ warnings.push({
9104
+ path: "",
9105
+ message: `python interpreter not found on PATH; skipping ${count} .py file${count === 1 ? "" : "s"}`
9106
+ });
9107
+ return { entries, warnings };
9108
+ }
9109
+ for (let i = 0; i < absolutePaths.length; i += BATCH_SIZE) {
9110
+ const chunk2 = absolutePaths.slice(i, i + BATCH_SIZE);
9111
+ const result = this.scanChunk(interpreter, chunk2, repoRoot);
9112
+ entries.push(...result.entries);
9113
+ warnings.push(...result.warnings);
9114
+ if (result.abortRemaining) {
9115
+ const remaining = absolutePaths.length - (i + chunk2.length);
9116
+ if (remaining > 0) {
9117
+ warnings.push({
9118
+ path: "",
9119
+ message: `skipping ${remaining} more .py file(s) after fatal interpreter error`
9120
+ });
9121
+ }
9122
+ break;
9123
+ }
9124
+ }
9125
+ return { entries, warnings };
9126
+ }
9127
+ scanChunk(interpreter, absolutePaths, repoRoot) {
9128
+ const entries = [];
9129
+ const warnings = [];
9130
+ const scriptPath = ASSET.codeMapPython;
9131
+ const listFile = join20(tmpdir2(), `cdd-codemap-${process.pid}-${randomBytes2(12).toString("hex")}.txt`);
9132
+ writeFileSync10(listFile, absolutePaths.join("\n") + "\n", { encoding: "utf8", mode: 384 });
9133
+ let stdout = "";
9134
+ let stderr = "";
9135
+ let exitCode = 0;
9136
+ try {
9137
+ const result = spawnSync2(
8504
9138
  interpreter,
8505
9139
  [scriptPath, "--batch-file", listFile, "--repo-root", repoRoot],
8506
9140
  {
@@ -8520,24 +9154,24 @@ var init_python = __esm({
8520
9154
  path: "",
8521
9155
  message: `python interpreter not found (ENOENT); skipping ${absolutePaths.length} .py file(s)`
8522
9156
  });
8523
- return { entries, warnings };
9157
+ return { entries, warnings, abortRemaining: true };
8524
9158
  }
8525
9159
  if (errMsg.includes("ETIMEDOUT") || errMsg.includes("timeout")) {
8526
9160
  warnings.push({
8527
9161
  path: "",
8528
- message: `python scanner timed out after ${TIMEOUT_MS}ms; skipping .py files`
9162
+ message: `python scanner timed out after ${TIMEOUT_MS}ms on a batch of ${absolutePaths.length} file(s); skipping this batch (raise CDD_CODE_MAP_TIMEOUT_MS or lower CDD_CODE_MAP_BATCH_SIZE)`
8529
9163
  });
8530
9164
  return { entries, warnings };
8531
9165
  }
8532
9166
  warnings.push({
8533
9167
  path: "",
8534
- message: `python scanner error: ${errMsg}; skipping .py files`
9168
+ message: `python scanner error: ${errMsg}; skipping this batch of ${absolutePaths.length} .py file(s)`
8535
9169
  });
8536
9170
  return { entries, warnings };
8537
9171
  }
8538
9172
  } finally {
8539
9173
  try {
8540
- unlinkSync(listFile);
9174
+ unlinkSync2(listFile);
8541
9175
  } catch {
8542
9176
  }
8543
9177
  }
@@ -8546,7 +9180,7 @@ var init_python = __esm({
8546
9180
  path: "",
8547
9181
  message: "python interpreter is < 3.9 (need ast.unparse); skipping .py files"
8548
9182
  });
8549
- return { entries, warnings };
9183
+ return { entries, warnings, abortRemaining: true };
8550
9184
  }
8551
9185
  for (const line of stdout.split("\n")) {
8552
9186
  const trimmed = line.trim();
@@ -8564,7 +9198,7 @@ var init_python = __esm({
8564
9198
  }
8565
9199
  const r = parsed;
8566
9200
  entries.push({
8567
- path: canonicalRelPath(join17(repoRoot, r.path), repoRoot),
9201
+ path: canonicalRelPath(join20(repoRoot, r.path), repoRoot),
8568
9202
  total_lines: r.total_lines,
8569
9203
  imports: r.imports ?? [],
8570
9204
  constants: r.constants ?? [],
@@ -8575,14 +9209,20 @@ var init_python = __esm({
8575
9209
  name: m.name,
8576
9210
  lines: [m.lines[0], m.lines[1]],
8577
9211
  async: m.async
8578
- }))
9212
+ })),
9213
+ extends: c.extends ?? [],
9214
+ implements: c.implements ?? [],
9215
+ exported: c.exported
8579
9216
  })),
8580
9217
  functions: (r.functions ?? []).map((f) => ({
8581
9218
  name: f.name,
8582
9219
  lines: [f.lines[0], f.lines[1]],
8583
9220
  decorators: f.decorators ?? [],
8584
- async: f.async
8585
- }))
9221
+ async: f.async,
9222
+ exported: f.exported
9223
+ })),
9224
+ calls: r.calls ?? [],
9225
+ exports: r.exports ?? []
8586
9226
  });
8587
9227
  }
8588
9228
  if (exitCode === 2) {
@@ -8608,7 +9248,7 @@ __export(javascript_exports, {
8608
9248
  parseJsSource: () => parseJsSource,
8609
9249
  parseSourceWithPlugins: () => parseSourceWithPlugins
8610
9250
  });
8611
- import { readFileSync as readFileSync15 } from "fs";
9251
+ import { readFileSync as readFileSync19 } from "fs";
8612
9252
  import { parse } from "@babel/parser";
8613
9253
  function parseSourceWithPlugins(source, plugins) {
8614
9254
  return parse(source, {
@@ -8641,6 +9281,51 @@ function getLineRange(node) {
8641
9281
  const end = node.loc?.end.line ?? start;
8642
9282
  return [start, end];
8643
9283
  }
9284
+ function expressionName(node) {
9285
+ if (!node)
9286
+ return null;
9287
+ if (node.type === "Identifier")
9288
+ return node.name;
9289
+ if (node.type === "ThisExpression")
9290
+ return "this";
9291
+ if (node.type === "Super")
9292
+ return "super";
9293
+ if (node.type === "PrivateName")
9294
+ return `#${node.id?.name ?? "private"}`;
9295
+ if (node.type === "StringLiteral")
9296
+ return node.value;
9297
+ if (node.type === "MemberExpression" || node.type === "OptionalMemberExpression") {
9298
+ const object = expressionName(node.object);
9299
+ const prop = node.computed ? expressionName(node.property) : expressionName(node.property);
9300
+ if (object && prop)
9301
+ return `${object}.${prop}`;
9302
+ return prop ?? object;
9303
+ }
9304
+ if (node.type === "CallExpression" || node.type === "OptionalCallExpression")
9305
+ return expressionName(node.callee);
9306
+ if (node.type === "TSExpressionWithTypeArguments")
9307
+ return expressionName(node.expression);
9308
+ return null;
9309
+ }
9310
+ function collectCalls(node, caller, calls) {
9311
+ if (!node || typeof node !== "object")
9312
+ return;
9313
+ if (node.type === "CallExpression" || node.type === "OptionalCallExpression" || node.type === "NewExpression") {
9314
+ const callee = expressionName(node.callee);
9315
+ if (callee)
9316
+ calls.push({ caller, callee, line: node.loc?.start.line ?? 1 });
9317
+ }
9318
+ for (const value of Object.values(node)) {
9319
+ if (!value)
9320
+ continue;
9321
+ if (Array.isArray(value)) {
9322
+ for (const item of value)
9323
+ collectCalls(item, caller, calls);
9324
+ } else if (typeof value === "object" && "type" in value) {
9325
+ collectCalls(value, caller, calls);
9326
+ }
9327
+ }
9328
+ }
8644
9329
  function processImportDeclaration(node) {
8645
9330
  const items = [];
8646
9331
  for (const spec of node.specifiers) {
@@ -8650,7 +9335,8 @@ function processImportDeclaration(node) {
8650
9335
  items.push(`*:${spec.local.name}`);
8651
9336
  } else if (spec.type === "ImportSpecifier") {
8652
9337
  const imported = spec.imported;
8653
- items.push(imported.type === "Identifier" ? imported.name : spec.local.name);
9338
+ const importedName = imported.type === "Identifier" ? imported.name : imported.value;
9339
+ items.push(spec.local.name !== importedName ? `${importedName}:${spec.local.name}` : importedName);
8654
9340
  }
8655
9341
  }
8656
9342
  return {
@@ -8659,7 +9345,7 @@ function processImportDeclaration(node) {
8659
9345
  line: node.loc?.start.line ?? 1
8660
9346
  };
8661
9347
  }
8662
- function processFunctionDeclaration(node, nameOverride) {
9348
+ function processFunctionDeclaration(node, nameOverride, exported = false) {
8663
9349
  const name = nameOverride ?? node.id?.name;
8664
9350
  if (!name)
8665
9351
  return null;
@@ -8667,10 +9353,11 @@ function processFunctionDeclaration(node, nameOverride) {
8667
9353
  name,
8668
9354
  lines: getLineRange(node),
8669
9355
  decorators: [],
8670
- async: node.async
9356
+ async: node.async,
9357
+ exported
8671
9358
  };
8672
9359
  }
8673
- function processClassDeclaration(node, nameOverride) {
9360
+ function processClassDeclaration(node, nameOverride, exported = false) {
8674
9361
  const name = nameOverride ?? node.id?.name;
8675
9362
  if (!name)
8676
9363
  return null;
@@ -8689,17 +9376,20 @@ function processClassDeclaration(node, nameOverride) {
8689
9376
  methods.push({
8690
9377
  name: methodName,
8691
9378
  lines: getLineRange(m),
8692
- async: m.async
9379
+ async: m.async ?? false
8693
9380
  });
8694
9381
  }
8695
9382
  }
8696
9383
  return {
8697
9384
  name,
8698
9385
  lines: getLineRange(node),
8699
- methods
9386
+ methods,
9387
+ extends: node.superClass ? [expressionName(node.superClass) ?? "<computed>"] : [],
9388
+ implements: (node.implements ?? []).map((impl) => impl?.expression ? expressionName(impl.expression) : impl?.id?.name).filter(Boolean),
9389
+ exported
8700
9390
  };
8701
9391
  }
8702
- function processVariableDeclaration(node, imports, constants, functions) {
9392
+ function processVariableDeclaration(node, imports, constants, functions, calls, exports, exportedFromWrapper = false) {
8703
9393
  for (const decl of node.declarations) {
8704
9394
  if (!decl.id || decl.id.type !== "Identifier")
8705
9395
  continue;
@@ -8714,6 +9404,8 @@ function processVariableDeclaration(node, imports, constants, functions) {
8714
9404
  }
8715
9405
  if (isAllCapsConst(varName) && init2 !== null && init2 !== void 0) {
8716
9406
  constants.push({ name: varName, line: node.loc?.start.line ?? 1 });
9407
+ if (exportedFromWrapper)
9408
+ exports.push({ name: varName, kind: "constant", line: node.loc?.start.line ?? 1 });
8717
9409
  continue;
8718
9410
  }
8719
9411
  if (init2 && (init2.type === "ArrowFunctionExpression" || init2.type === "FunctionExpression")) {
@@ -8721,15 +9413,23 @@ function processVariableDeclaration(node, imports, constants, functions) {
8721
9413
  name: varName,
8722
9414
  lines: getLineRange(node),
8723
9415
  decorators: [],
8724
- async: init2.async
9416
+ async: init2.async,
9417
+ exported: exportedFromWrapper
8725
9418
  });
9419
+ collectCalls(init2.body, varName, calls);
9420
+ if (exportedFromWrapper)
9421
+ exports.push({ name: varName, kind: "function", line: node.loc?.start.line ?? 1 });
8726
9422
  } else if (init2 && init2.type === "CallExpression" && /^[A-Z]/.test(varName)) {
8727
9423
  functions.push({
8728
9424
  name: varName,
8729
9425
  lines: getLineRange(node),
8730
9426
  decorators: [],
8731
- async: false
9427
+ async: false,
9428
+ exported: exportedFromWrapper
8732
9429
  });
9430
+ collectCalls(init2, varName, calls);
9431
+ if (exportedFromWrapper)
9432
+ exports.push({ name: varName, kind: "function", line: node.loc?.start.line ?? 1 });
8733
9433
  }
8734
9434
  }
8735
9435
  }
@@ -8781,16 +9481,31 @@ function processStatement(stmt, buckets, extractTsTypes, exportedFromWrapper = f
8781
9481
  processStatement(stmt.declaration, buckets, extractTsTypes, true);
8782
9482
  return;
8783
9483
  }
9484
+ if (stmt.type === "ExportNamedDeclaration" && !stmt.declaration) {
9485
+ for (const spec of stmt.specifiers ?? []) {
9486
+ if (spec.type === "ExportSpecifier") {
9487
+ const exported = spec.exported.type === "Identifier" ? spec.exported.name : spec.exported.value;
9488
+ buckets.exports.push({ name: exported, kind: "unknown", line: stmt.loc?.start.line ?? 1 });
9489
+ }
9490
+ }
9491
+ return;
9492
+ }
8784
9493
  if (stmt.type === "ExportDefaultDeclaration") {
8785
9494
  const decl = stmt.declaration;
8786
9495
  if (decl.type === "FunctionDeclaration") {
8787
- const fe = processFunctionDeclaration(decl, decl.id?.name ?? "default");
9496
+ const fe = processFunctionDeclaration(decl, decl.id?.name ?? "default", true);
8788
9497
  if (fe)
8789
9498
  buckets.functions.push(fe);
9499
+ if (fe)
9500
+ buckets.exports.push({ name: fe.name, kind: "function", line: fe.lines[0] });
9501
+ if (fe)
9502
+ collectCalls(decl.body, fe.name, buckets.calls);
8790
9503
  } else if (decl.type === "ClassDeclaration") {
8791
- const ce = processClassDeclaration(decl, decl.id?.name ?? "default");
9504
+ const ce = processClassDeclaration(decl, decl.id?.name ?? "default", true);
8792
9505
  if (ce)
8793
9506
  buckets.classes.push(ce);
9507
+ if (ce)
9508
+ buckets.exports.push({ name: ce.name, kind: "class", line: ce.lines[0] });
8794
9509
  }
8795
9510
  return;
8796
9511
  }
@@ -8799,19 +9514,35 @@ function processStatement(stmt, buckets, extractTsTypes, exportedFromWrapper = f
8799
9514
  return;
8800
9515
  }
8801
9516
  if (stmt.type === "FunctionDeclaration") {
8802
- const fe = processFunctionDeclaration(stmt);
9517
+ const fe = processFunctionDeclaration(stmt, void 0, exportedFromWrapper);
8803
9518
  if (fe)
8804
9519
  buckets.functions.push(fe);
9520
+ if (fe) {
9521
+ collectCalls(stmt.body, fe.name, buckets.calls);
9522
+ if (exportedFromWrapper)
9523
+ buckets.exports.push({ name: fe.name, kind: "function", line: fe.lines[0] });
9524
+ }
8805
9525
  return;
8806
9526
  }
8807
9527
  if (stmt.type === "ClassDeclaration") {
8808
- const ce = processClassDeclaration(stmt);
9528
+ const ce = processClassDeclaration(stmt, void 0, exportedFromWrapper);
8809
9529
  if (ce)
8810
9530
  buckets.classes.push(ce);
9531
+ if (ce && exportedFromWrapper)
9532
+ buckets.exports.push({ name: ce.name, kind: "class", line: ce.lines[0] });
9533
+ if (ce) {
9534
+ for (const member of stmt.body.body) {
9535
+ if (member.type === "ClassMethod" || member.type === "ClassPrivateMethod") {
9536
+ const method = ce.methods.find((m) => m.lines[0] === (member.loc?.start.line ?? 0));
9537
+ if (method)
9538
+ collectCalls(member.body, `${ce.name}.${method.name}`, buckets.calls);
9539
+ }
9540
+ }
9541
+ }
8811
9542
  return;
8812
9543
  }
8813
9544
  if (stmt.type === "VariableDeclaration") {
8814
- processVariableDeclaration(stmt, buckets.imports, buckets.constants, buckets.functions);
9545
+ processVariableDeclaration(stmt, buckets.imports, buckets.constants, buckets.functions, buckets.calls, buckets.exports, exportedFromWrapper);
8815
9546
  return;
8816
9547
  }
8817
9548
  if (extractTsTypes) {
@@ -8820,18 +9551,24 @@ function processStatement(stmt, buckets, extractTsTypes, exportedFromWrapper = f
8820
9551
  const e = processTsInterfaceDeclaration(anyStmt, exportedFromWrapper);
8821
9552
  if (e)
8822
9553
  buckets.interfaces.push(e);
9554
+ if (e && exportedFromWrapper)
9555
+ buckets.exports.push({ name: e.name, kind: "interface", line: e.lines[0] });
8823
9556
  return;
8824
9557
  }
8825
9558
  if (anyStmt.type === "TSTypeAliasDeclaration") {
8826
9559
  const e = processTsTypeAliasDeclaration(anyStmt, exportedFromWrapper);
8827
9560
  if (e)
8828
9561
  buckets.types.push(e);
9562
+ if (e && exportedFromWrapper)
9563
+ buckets.exports.push({ name: e.name, kind: "type", line: e.lines[0] });
8829
9564
  return;
8830
9565
  }
8831
9566
  if (anyStmt.type === "TSEnumDeclaration") {
8832
9567
  const e = processTsEnumDeclaration(anyStmt, exportedFromWrapper);
8833
9568
  if (e)
8834
9569
  buckets.enums.push(e);
9570
+ if (e && exportedFromWrapper)
9571
+ buckets.exports.push({ name: e.name, kind: "enum", line: e.lines[0] });
8835
9572
  return;
8836
9573
  }
8837
9574
  }
@@ -8859,7 +9596,9 @@ function parseAndExtract(source, opts) {
8859
9596
  classes: [],
8860
9597
  interfaces: [],
8861
9598
  types: [],
8862
- enums: []
9599
+ enums: [],
9600
+ calls: [],
9601
+ exports: []
8863
9602
  };
8864
9603
  for (const stmt of ast.program.body) {
8865
9604
  processStatement(stmt, buckets, !!opts.extractTsTypes);
@@ -8882,7 +9621,9 @@ function parseJsSource(source, relPath) {
8882
9621
  imports: r.imports,
8883
9622
  constants: r.constants,
8884
9623
  classes: r.classes,
8885
- functions: r.functions
9624
+ functions: r.functions,
9625
+ calls: r.calls,
9626
+ exports: r.exports
8886
9627
  };
8887
9628
  }
8888
9629
  var COMMON_PLUGINS, JS_PLUGINS, JavaScriptScanner, jsScanner;
@@ -8910,7 +9651,7 @@ var init_javascript = __esm({
8910
9651
  async scan(absolutePath, repoRoot) {
8911
9652
  let source;
8912
9653
  try {
8913
- source = readFileSync15(absolutePath, "utf8");
9654
+ source = readFileSync19(absolutePath, "utf8");
8914
9655
  } catch (err) {
8915
9656
  throw err;
8916
9657
  }
@@ -8930,7 +9671,7 @@ var typescript_exports = {};
8930
9671
  __export(typescript_exports, {
8931
9672
  tsScanner: () => tsScanner
8932
9673
  });
8933
- import { readFileSync as readFileSync16 } from "fs";
9674
+ import { readFileSync as readFileSync20 } from "fs";
8934
9675
  var TypeScriptScanner, tsScanner;
8935
9676
  var init_typescript = __esm({
8936
9677
  "src/code-map/scanners/typescript.ts"() {
@@ -8942,7 +9683,7 @@ var init_typescript = __esm({
8942
9683
  async scan(absolutePath, repoRoot) {
8943
9684
  let source;
8944
9685
  try {
8945
- source = readFileSync16(absolutePath, "utf8");
9686
+ source = readFileSync20(absolutePath, "utf8");
8946
9687
  } catch (err) {
8947
9688
  throw err;
8948
9689
  }
@@ -8963,6 +9704,8 @@ var init_typescript = __esm({
8963
9704
  constants: r.constants,
8964
9705
  classes: r.classes,
8965
9706
  functions: r.functions,
9707
+ calls: r.calls,
9708
+ exports: r.exports,
8966
9709
  interfaces: r.interfaces,
8967
9710
  types: r.types,
8968
9711
  enums: r.enums
@@ -8978,7 +9721,7 @@ var vue_exports = {};
8978
9721
  __export(vue_exports, {
8979
9722
  vueScanner: () => vueScanner
8980
9723
  });
8981
- import { readFileSync as readFileSync17 } from "fs";
9724
+ import { readFileSync as readFileSync21 } from "fs";
8982
9725
  import { parse as parse2 } from "@vue/compiler-sfc";
8983
9726
  var VueScanner, vueScanner;
8984
9727
  var init_vue = __esm({
@@ -8991,7 +9734,7 @@ var init_vue = __esm({
8991
9734
  async scan(absolutePath, repoRoot) {
8992
9735
  let source;
8993
9736
  try {
8994
- source = readFileSync17(absolutePath, "utf8");
9737
+ source = readFileSync21(absolutePath, "utf8");
8995
9738
  } catch (err) {
8996
9739
  throw err;
8997
9740
  }
@@ -9005,6 +9748,8 @@ var init_vue = __esm({
9005
9748
  const allConstants = [];
9006
9749
  const allFunctions = [];
9007
9750
  const allClasses = [];
9751
+ const allCalls = [];
9752
+ const allExports = [];
9008
9753
  const warnings = [];
9009
9754
  const scriptBlocks = [descriptor.script, descriptor.scriptSetup].filter(Boolean);
9010
9755
  if (scriptBlocks.length === 0) {
@@ -9057,6 +9802,12 @@ var init_vue = __esm({
9057
9802
  }))
9058
9803
  });
9059
9804
  }
9805
+ for (const call of scriptEntry.calls ?? []) {
9806
+ allCalls.push({ ...call, line: call.line + offset });
9807
+ }
9808
+ for (const exp of scriptEntry.exports ?? []) {
9809
+ allExports.push({ ...exp, line: exp.line + offset });
9810
+ }
9060
9811
  }
9061
9812
  return {
9062
9813
  path: relPath,
@@ -9064,7 +9815,9 @@ var init_vue = __esm({
9064
9815
  imports: allImports,
9065
9816
  constants: allConstants,
9066
9817
  classes: allClasses,
9067
- functions: allFunctions
9818
+ functions: allFunctions,
9819
+ calls: allCalls,
9820
+ exports: allExports
9068
9821
  };
9069
9822
  }
9070
9823
  };
@@ -9078,23 +9831,35 @@ __export(code_map_exports, {
9078
9831
  codeMap: () => codeMap,
9079
9832
  computeSourcesDigest: () => computeSourcesDigest
9080
9833
  });
9081
- import { existsSync as existsSync15, mkdirSync as mkdirSync8, readFileSync as readFileSync18, writeFileSync as writeFileSync9 } from "fs";
9834
+ import { existsSync as existsSync19, mkdirSync as mkdirSync8, readFileSync as readFileSync22, writeFileSync as writeFileSync11 } from "fs";
9082
9835
  import { resolve, dirname as dirname5, relative as relative6 } from "path";
9083
- import { createHash as createHash5 } from "crypto";
9836
+ import { createHash as createHash6 } from "crypto";
9084
9837
  import { createRequire } from "module";
9085
9838
  import { fileURLToPath as fileURLToPath2 } from "url";
9086
- import { join as join18 } from "path";
9839
+ import { join as join21 } from "path";
9087
9840
  function computeSourcesDigest(absolutePaths, cwd) {
9088
9841
  const lines = absolutePaths.slice().sort().map((p) => {
9089
9842
  const rel = relative6(cwd, p).replace(/\\/g, "/");
9090
9843
  const contentHash = sha256OfFileNormalized(p) || "missing";
9091
9844
  return `${rel}:${contentHash}`;
9092
9845
  });
9093
- return createHash5("sha256").update(lines.join("\n")).digest("hex");
9846
+ return createHash6("sha256").update(lines.join("\n")).digest("hex");
9847
+ }
9848
+ function slugifySurface(surface) {
9849
+ return surface.replace(/^[./]+/, "").replace(/\/+$/, "").replace(/[\\/]+/g, "-") || "root";
9094
9850
  }
9095
9851
  async function codeMap(opts) {
9096
- const root = resolve(process.cwd(), opts.path);
9097
9852
  const start = Date.now();
9853
+ if (opts.surface) {
9854
+ const resolvedSurface = resolve(process.cwd(), opts.surface);
9855
+ if (!existsSync19(resolvedSurface)) {
9856
+ log.error(`code-map --surface path not found: ${opts.surface}`);
9857
+ return 1;
9858
+ }
9859
+ }
9860
+ const scanPath = opts.surface ?? opts.path;
9861
+ const root = resolve(process.cwd(), scanPath);
9862
+ const out = opts.out ?? (opts.surface ? `.cdd/code-map.${slugifySurface(opts.surface)}.yml` : ".cdd/code-map.yml");
9098
9863
  let cfg;
9099
9864
  try {
9100
9865
  cfg = loadCodeMapConfig(root);
@@ -9107,6 +9872,15 @@ async function codeMap(opts) {
9107
9872
  const files = walkRepo(root, { include, exclude });
9108
9873
  const buckets = bucketByExtension(files);
9109
9874
  const result = { entries: [], warnings: [] };
9875
+ const cliEntry = process.argv[1];
9876
+ const workers = opts.workers ?? 0;
9877
+ const useWorkers = workers > 1 && typeof cliEntry === "string" && cliEntry.length > 0;
9878
+ const dispatch = async (scanner, lang, langFiles) => {
9879
+ if (!useWorkers)
9880
+ return scanInProcess(scanner, langFiles, root);
9881
+ const { scanLangWithWorkers: scanLangWithWorkers2 } = await Promise.resolve().then(() => (init_worker_dispatch(), worker_dispatch_exports));
9882
+ return scanLangWithWorkers2(scanner, lang, langFiles, root, workers, cliEntry);
9883
+ };
9110
9884
  const tasks = [];
9111
9885
  if (buckets[".py"]?.length) {
9112
9886
  const { pythonScanner: pythonScanner2 } = await Promise.resolve().then(() => (init_python(), python_exports));
@@ -9122,7 +9896,7 @@ async function codeMap(opts) {
9122
9896
  ];
9123
9897
  if (jsFiles.length) {
9124
9898
  const { jsScanner: jsScanner2 } = await Promise.resolve().then(() => (init_javascript(), javascript_exports));
9125
- tasks.push(scanInProcess(jsScanner2, jsFiles, root));
9899
+ tasks.push(dispatch(jsScanner2, "js", jsFiles));
9126
9900
  }
9127
9901
  const tsFiles = [
9128
9902
  ...buckets[".ts"] ?? [],
@@ -9130,11 +9904,11 @@ async function codeMap(opts) {
9130
9904
  ];
9131
9905
  if (tsFiles.length) {
9132
9906
  const { tsScanner: tsScanner2 } = await Promise.resolve().then(() => (init_typescript(), typescript_exports));
9133
- tasks.push(scanInProcess(tsScanner2, tsFiles, root));
9907
+ tasks.push(dispatch(tsScanner2, "ts", tsFiles));
9134
9908
  }
9135
9909
  if (buckets[".vue"]?.length) {
9136
9910
  const { vueScanner: vueScanner2 } = await Promise.resolve().then(() => (init_vue(), vue_exports));
9137
- tasks.push(scanInProcess(vueScanner2, buckets[".vue"], root));
9911
+ tasks.push(dispatch(vueScanner2, "vue", buckets[".vue"]));
9138
9912
  }
9139
9913
  for (const r of await Promise.all(tasks)) {
9140
9914
  result.entries.push(...r.entries);
@@ -9157,25 +9931,44 @@ async function codeMap(opts) {
9157
9931
  const totalSrc = result.entries.reduce((s, e) => s + e.total_lines, 0);
9158
9932
  const mapLines = yamlBody.split("\n").length;
9159
9933
  const compression = totalSrc === 0 ? 0 : totalSrc / mapLines;
9160
- const summaryLine = `scanned ${result.entries.length} files, ${totalSrc} src lines -> ${opts.out} (${mapLines} lines, compression ${compression.toFixed(1)}x)`;
9934
+ const summaryLine = `scanned ${result.entries.length} files, ${totalSrc} src lines -> ${out} (${mapLines} lines, compression ${compression.toFixed(1)}x)`;
9161
9935
  for (const w of result.warnings) {
9162
9936
  if (!opts.silent)
9163
9937
  log.warn(`${w.path}: ${w.message}`);
9164
9938
  }
9165
9939
  if (opts.check) {
9166
- const existing = existsSync15(opts.out) ? readFileSync18(opts.out, "utf8") : "";
9940
+ const existing = existsSync19(out) ? readFileSync22(out, "utf8") : "";
9167
9941
  const normalize = (s) => s.replace(/\r\n/g, "\n").replace(/\r/g, "\n").replace(/^# generated: [^\n]+\n/m, "# generated: <normalized>\n");
9168
9942
  if (normalize(existing) !== normalize(yamlBody)) {
9169
9943
  if (!opts.silent)
9170
- log.error(`code-map out of date: ${opts.out} would change. Run \`cdd-kit code-map\` to regenerate.`);
9944
+ log.error(`code-map out of date: ${out} would change. Run \`cdd-kit code-map\` to regenerate.`);
9171
9945
  return 1;
9172
9946
  }
9173
9947
  if (!opts.silent)
9174
- log.ok(`code-map up to date: ${opts.out}`);
9948
+ log.ok(`code-map up to date: ${out}`);
9175
9949
  return 0;
9176
9950
  }
9177
- mkdirSync8(dirname5(opts.out), { recursive: true });
9178
- writeFileSync9(opts.out, yamlBody, "utf8");
9951
+ mkdirSync8(dirname5(out), { recursive: true });
9952
+ writeFileSync11(out, yamlBody, "utf8");
9953
+ try {
9954
+ const sidecarPath = sidecarPathFor(out);
9955
+ writeFileSync11(sidecarPath, JSON.stringify({ sourcesDigest, entries: result.entries }), "utf8");
9956
+ const rel = relative6(process.cwd(), sidecarPath).replace(/\\/g, "/");
9957
+ if (!rel.startsWith("..")) {
9958
+ ensureGitignoreEntry(process.cwd(), rel, "cdd-kit local cache (do not commit)");
9959
+ }
9960
+ const graphPath = graphPathFor(out);
9961
+ const graph2 = buildCodeGraph(result.entries, {
9962
+ generator: `cdd-kit ${_pkg.version}`,
9963
+ sourcesDigest
9964
+ });
9965
+ writeFileSync11(graphPath, JSON.stringify(graph2, null, 2) + "\n", "utf8");
9966
+ const graphRel = relative6(process.cwd(), graphPath).replace(/\\/g, "/");
9967
+ if (!graphRel.startsWith("..")) {
9968
+ ensureGitignoreEntry(process.cwd(), graphRel, "cdd-kit local cache (do not commit)");
9969
+ }
9970
+ } catch {
9971
+ }
9179
9972
  if (!opts.silent)
9180
9973
  log.ok(`${summaryLine} (${Date.now() - start}ms)`);
9181
9974
  return 0;
@@ -9188,10 +9981,14 @@ var init_code_map = __esm({
9188
9981
  init_yaml_writer();
9189
9982
  init_orchestrator();
9190
9983
  init_config();
9984
+ init_index_reader();
9985
+ init_builder();
9986
+ init_reader();
9191
9987
  init_digest();
9988
+ init_gitignore();
9192
9989
  _require = createRequire(import.meta.url);
9193
- _pkgPath = join18(fileURLToPath2(import.meta.url), "..", "..", "..", "package.json");
9194
- _pkg = JSON.parse(readFileSync18(_pkgPath, "utf8"));
9990
+ _pkgPath = join21(fileURLToPath2(import.meta.url), "..", "..", "..", "package.json");
9991
+ _pkg = JSON.parse(readFileSync22(_pkgPath, "utf8"));
9195
9992
  }
9196
9993
  });
9197
9994
 
@@ -9200,15 +9997,15 @@ var refresh_exports = {};
9200
9997
  __export(refresh_exports, {
9201
9998
  refresh: () => refresh
9202
9999
  });
9203
- import { existsSync as existsSync16, mkdirSync as mkdirSync9, readdirSync as readdirSync10, copyFileSync as copyFileSync4, readFileSync as readFileSync19, writeFileSync as writeFileSync10 } from "fs";
9204
- import { dirname as dirname6, join as join19, relative as relative7 } from "path";
9205
- import { createHash as createHash6 } from "crypto";
10000
+ import { existsSync as existsSync20, mkdirSync as mkdirSync9, readdirSync as readdirSync10, copyFileSync as copyFileSync4, readFileSync as readFileSync23, writeFileSync as writeFileSync12 } from "fs";
10001
+ import { dirname as dirname6, join as join22, relative as relative7 } from "path";
10002
+ import { createHash as createHash7 } from "crypto";
9206
10003
  function fileHash2(filePath) {
9207
- return createHash6("sha256").update(readFileSync19(filePath)).digest("hex");
10004
+ return createHash7("sha256").update(readFileSync23(filePath)).digest("hex");
9208
10005
  }
9209
10006
  function planForceRefresh(srcDir, destDir, sectionLabel) {
9210
10007
  const plan = [];
9211
- if (!existsSync16(srcDir))
10008
+ if (!existsSync20(srcDir))
9212
10009
  return plan;
9213
10010
  function walk(curSrc, curDest) {
9214
10011
  let items;
@@ -9218,16 +10015,16 @@ function planForceRefresh(srcDir, destDir, sectionLabel) {
9218
10015
  return;
9219
10016
  }
9220
10017
  for (const item of items) {
9221
- const sp = join19(curSrc, item.name);
9222
- const dp = join19(curDest, item.name);
10018
+ const sp = join22(curSrc, item.name);
10019
+ const dp = join22(curDest, item.name);
9223
10020
  if (item.isDirectory()) {
9224
10021
  walk(sp, dp);
9225
10022
  continue;
9226
10023
  }
9227
10024
  if (!item.isFile())
9228
10025
  continue;
9229
- const rel = join19(sectionLabel, relative7(srcDir, sp)).replace(/\\/g, "/");
9230
- if (!existsSync16(dp)) {
10026
+ const rel = join22(sectionLabel, relative7(srcDir, sp)).replace(/\\/g, "/");
10027
+ if (!existsSync20(dp)) {
9231
10028
  plan.push({ src: sp, dest: dp, rel, action: "add" });
9232
10029
  } else if (fileHash2(sp) !== fileHash2(dp)) {
9233
10030
  plan.push({ src: sp, dest: dp, rel, action: "overwrite" });
@@ -9240,35 +10037,14 @@ function planForceRefresh(srcDir, destDir, sectionLabel) {
9240
10037
  return plan;
9241
10038
  }
9242
10039
  function planSingleFile(src, dest, rel) {
9243
- if (!existsSync16(src))
10040
+ if (!existsSync20(src))
9244
10041
  return null;
9245
- if (!existsSync16(dest))
10042
+ if (!existsSync20(dest))
9246
10043
  return { src, dest, rel, action: "add" };
9247
10044
  if (fileHash2(src) !== fileHash2(dest))
9248
10045
  return { src, dest, rel, action: "overwrite" };
9249
10046
  return { src, dest, rel, action: "skip" };
9250
10047
  }
9251
- function ensureGitignoreEntry2(cwd, entry) {
9252
- const path = join19(cwd, ".gitignore");
9253
- const trimmed = entry.trim();
9254
- if (!trimmed)
9255
- return false;
9256
- const re = new RegExp(`^\\s*${trimmed.replace(/[.*+?^${}()|[\\]\\\\]/g, "\\$&")}\\s*$`, "m");
9257
- let existing = "";
9258
- if (existsSync16(path))
9259
- existing = readFileSync19(path, "utf8");
9260
- if (re.test(existing))
9261
- return false;
9262
- const sep = existing.length > 0 && !existing.endsWith("\n") ? "\n" : "";
9263
- const block = existing.length === 0 ? `# cdd-kit generated backups (do not commit)
9264
- ${trimmed}
9265
- ` : `${sep}
9266
- # cdd-kit generated backups (do not commit)
9267
- ${trimmed}
9268
- `;
9269
- writeFileSync10(path, existing + block, "utf8");
9270
- return true;
9271
- }
9272
10048
  function applyPlan(plan, backupRoot) {
9273
10049
  let added = 0;
9274
10050
  let overwritten = 0;
@@ -9276,7 +10052,7 @@ function applyPlan(plan, backupRoot) {
9276
10052
  if (item.action === "skip")
9277
10053
  continue;
9278
10054
  if (item.action === "overwrite") {
9279
- const backupPath = join19(backupRoot, item.rel);
10055
+ const backupPath = join22(backupRoot, item.rel);
9280
10056
  mkdirSync9(dirname6(backupPath), { recursive: true });
9281
10057
  copyFileSync4(item.dest, backupPath);
9282
10058
  overwritten += 1;
@@ -9305,14 +10081,14 @@ function parseAgentFrontmatter(content) {
9305
10081
  return fm;
9306
10082
  }
9307
10083
  function resyncModelPolicy(cwd) {
9308
- const policyPath = join19(cwd, ".cdd", "model-policy.json");
10084
+ const policyPath = join22(cwd, ".cdd", "model-policy.json");
9309
10085
  const result = { changed: false, diff: [], policyPath };
9310
- if (!existsSync16(AGENTS_HOME))
10086
+ if (!existsSync20(AGENTS_HOME))
9311
10087
  return result;
9312
10088
  const desired = {};
9313
10089
  const agentFiles = readdirSync10(AGENTS_HOME, { withFileTypes: true }).filter((d) => d.isFile() && d.name.endsWith(".md"));
9314
10090
  for (const f of agentFiles) {
9315
- const content = readFileSync19(join19(AGENTS_HOME, f.name), "utf8");
10091
+ const content = readFileSync23(join22(AGENTS_HOME, f.name), "utf8");
9316
10092
  const fm = parseAgentFrontmatter(content);
9317
10093
  if (fm.name && fm.model)
9318
10094
  desired[fm.name] = fm.model;
@@ -9320,9 +10096,9 @@ function resyncModelPolicy(cwd) {
9320
10096
  if (Object.keys(desired).length === 0)
9321
10097
  return result;
9322
10098
  let existing = {};
9323
- if (existsSync16(policyPath)) {
10099
+ if (existsSync20(policyPath)) {
9324
10100
  try {
9325
- existing = JSON.parse(readFileSync19(policyPath, "utf8"));
10101
+ existing = JSON.parse(readFileSync23(policyPath, "utf8"));
9326
10102
  } catch {
9327
10103
  }
9328
10104
  }
@@ -9343,7 +10119,7 @@ function resyncModelPolicy(cwd) {
9343
10119
  if (!("provider" in merged))
9344
10120
  merged["provider"] = "claude";
9345
10121
  mkdirSync9(dirname6(policyPath), { recursive: true });
9346
- writeFileSync10(policyPath, JSON.stringify(merged, null, 2) + "\n", "utf8");
10122
+ writeFileSync12(policyPath, JSON.stringify(merged, null, 2) + "\n", "utf8");
9347
10123
  result.changed = true;
9348
10124
  return result;
9349
10125
  }
@@ -9351,21 +10127,21 @@ function planTemplateRefresh(cwd) {
9351
10127
  const sections = [];
9352
10128
  sections.push({
9353
10129
  name: "specs/templates",
9354
- plan: planForceRefresh(ASSET.specsTemplates, join19(cwd, "specs", "templates"), "specs/templates")
10130
+ plan: planForceRefresh(ASSET.specsTemplates, join22(cwd, "specs", "templates"), "specs/templates")
9355
10131
  });
9356
10132
  sections.push({
9357
10133
  name: "tests/templates",
9358
- plan: planForceRefresh(ASSET.testsTemplates, join19(cwd, "tests", "templates"), "tests/templates")
10134
+ plan: planForceRefresh(ASSET.testsTemplates, join22(cwd, "tests", "templates"), "tests/templates")
9359
10135
  });
9360
- const ciTemplatesAsset = join19(ASSET.ci, "..", "ci-templates");
9361
- if (existsSync16(ciTemplatesAsset)) {
10136
+ const ciTemplatesAsset = join22(ASSET.ci, "..", "ci-templates");
10137
+ if (existsSync20(ciTemplatesAsset)) {
9362
10138
  sections.push({
9363
10139
  name: "ci-templates",
9364
- plan: planForceRefresh(ciTemplatesAsset, join19(cwd, "ci-templates"), "ci-templates")
10140
+ plan: planForceRefresh(ciTemplatesAsset, join22(cwd, "ci-templates"), "ci-templates")
9365
10141
  });
9366
10142
  }
9367
- const wfAsset = join19(ASSET.githubWorkflows, "contract-driven-gates.yml");
9368
- const wfDest = join19(cwd, ".github", "workflows", "contract-driven-gates.yml");
10143
+ const wfAsset = join22(ASSET.githubWorkflows, "contract-driven-gates.yml");
10144
+ const wfDest = join22(cwd, ".github", "workflows", "contract-driven-gates.yml");
9369
10145
  const wfPlan = planSingleFile(wfAsset, wfDest, ".github/workflows/contract-driven-gates.yml");
9370
10146
  if (wfPlan)
9371
10147
  sections.push({ name: ".github/workflows/contract-driven-gates.yml", plan: [wfPlan] });
@@ -9420,14 +10196,14 @@ async function refresh(opts) {
9420
10196
  }
9421
10197
  if (apply) {
9422
10198
  const ts = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
9423
- backupRoot = join19(cwd, ".cdd", ".refresh-backup", ts);
10199
+ backupRoot = join22(cwd, ".cdd", ".refresh-backup", ts);
9424
10200
  const result = applyPlan(total, backupRoot);
9425
10201
  templateAdded = result.added;
9426
10202
  templateOverwritten = result.overwritten;
9427
10203
  log.ok(` applied: +${templateAdded} added, ~${templateOverwritten} overwritten`);
9428
10204
  if (templateOverwritten > 0) {
9429
10205
  log.info(` backup saved to: ${relative7(cwd, backupRoot).replace(/\\/g, "/")}`);
9430
- if (ensureGitignoreEntry2(cwd, ".cdd/.refresh-backup/")) {
10206
+ if (ensureGitignoreEntry(cwd, ".cdd/.refresh-backup/")) {
9431
10207
  log.info(" added `.cdd/.refresh-backup/` to .gitignore");
9432
10208
  }
9433
10209
  }
@@ -9440,8 +10216,8 @@ async function refresh(opts) {
9440
10216
  }
9441
10217
  log.blank();
9442
10218
  if (!opts.noHooks) {
9443
- const markerPath = join19(cwd, HOOKS_MARKER_PATH);
9444
- if (existsSync16(markerPath)) {
10219
+ const markerPath = join22(cwd, HOOKS_MARKER_PATH);
10220
+ if (existsSync20(markerPath)) {
9445
10221
  log.info("[4/6] re-install code-map pre-commit hook (marker found)");
9446
10222
  if (apply) {
9447
10223
  try {
@@ -9518,6 +10294,7 @@ var init_refresh = __esm({
9518
10294
  "use strict";
9519
10295
  init_paths();
9520
10296
  init_logger();
10297
+ init_gitignore();
9521
10298
  init_update();
9522
10299
  init_upgrade();
9523
10300
  init_code_map();
@@ -9526,76 +10303,195 @@ var init_refresh = __esm({
9526
10303
  }
9527
10304
  });
9528
10305
 
9529
- // src/code-map/freshness.ts
9530
- import { existsSync as existsSync17, readFileSync as readFileSync20, statSync as statSync3 } from "fs";
9531
- import { join as join20 } from "path";
9532
- function checkCodeMapFreshness(cwd, mapRel = ".cdd/code-map.yml", include, exclude) {
9533
- const mapPath = join20(cwd, mapRel);
9534
- let cfg;
10306
+ // src/commands/lint-agents.ts
10307
+ var lint_agents_exports = {};
10308
+ __export(lint_agents_exports, {
10309
+ collectAgentViolations: () => collectAgentViolations,
10310
+ lintAgentContent: () => lintAgentContent,
10311
+ lintAgents: () => lintAgents
10312
+ });
10313
+ import { readdirSync as readdirSync11, readFileSync as readFileSync24 } from "fs";
10314
+ import { join as join23 } from "path";
10315
+ import { load as yamlLoad2 } from "js-yaml";
10316
+ function extractRequiredArtifactsSection(content) {
10317
+ const match = content.match(
10318
+ /### (?:Suggested|Required) artifacts for this agent\s*\n([\s\S]*?)(?=\n#{2,3} |\n---|\s*$)/
10319
+ );
10320
+ return match ? match[0] : null;
10321
+ }
10322
+ function extractFirstReadScopeSection(content) {
10323
+ const match = content.match(/## Read scope\s*\n([\s\S]*?)(?=\n## |\s*$)/);
10324
+ return match ? match[0] : null;
10325
+ }
10326
+ function extractYamlBlock(section) {
10327
+ const match = section.match(/```ya?ml\s*\n([\s\S]*?)```/);
10328
+ return match ? match[0] : null;
10329
+ }
10330
+ function extractYamlBody(section) {
10331
+ const match = section.match(/```ya?ml\s*\n([\s\S]*?)```/);
10332
+ return match ? match[1] : null;
10333
+ }
10334
+ function strayTopLevelKeys(yamlBody) {
10335
+ let parsed;
9535
10336
  try {
9536
- cfg = loadCodeMapConfig(cwd);
9537
- } catch (err) {
9538
- return {
9539
- status: "config-error",
9540
- staleFiles: [],
9541
- staleCount: 0,
9542
- mapPath,
9543
- configError: err.message
9544
- };
10337
+ parsed = yamlLoad2(yamlBody);
10338
+ } catch {
10339
+ return null;
9545
10340
  }
9546
- const includeFinal = [...cfg.include, ...include ?? []];
9547
- const excludeFinal = [...cfg.exclude, ...exclude ?? []];
9548
- const sourceFiles = walkRepo(cwd, { include: includeFinal, exclude: excludeFinal });
9549
- if (!existsSync17(mapPath)) {
9550
- if (sourceFiles.length === 0) {
9551
- return { status: "missing-greenfield", staleFiles: [], staleCount: 0, mapPath };
9552
- }
9553
- return { status: "missing-with-sources", staleFiles: [], staleCount: 0, mapPath };
10341
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
10342
+ return null;
9554
10343
  }
9555
- const mapMtime = statSync3(mapPath).mtimeMs;
9556
- const staleAll = [];
9557
- for (const absPath of sourceFiles) {
9558
- try {
9559
- const mtime = statSync3(absPath).mtimeMs;
9560
- if (mtime > mapMtime) {
9561
- const rel = absPath.replace(/\\/g, "/").replace(cwd.replace(/\\/g, "/") + "/", "");
9562
- staleAll.push(rel);
10344
+ const keys = Object.keys(parsed);
10345
+ return keys.filter((k) => k !== "artifacts");
10346
+ }
10347
+ function hasFlatBacktickKeysWithoutFence(section) {
10348
+ if (/```ya?ml\s*\nartifacts:/.test(section))
10349
+ return false;
10350
+ const withoutFence = section.replace(/```ya?ml[\s\S]*?```/g, "");
10351
+ return /^- `[a-z][a-z0-9-]+`:/m.test(withoutFence);
10352
+ }
10353
+ function lintAgentContent(filename, rawContent, opts = {}) {
10354
+ const violations = [];
10355
+ let content = rawContent;
10356
+ if (content.charCodeAt(0) === 65279) {
10357
+ violations.push({
10358
+ file: filename,
10359
+ rule: "Meta",
10360
+ message: "file starts with UTF-8 BOM (U+FEFF); frontmatter parsers may treat the first key as invalid",
10361
+ level: "error"
10362
+ });
10363
+ content = content.slice(1);
10364
+ }
10365
+ const artifactsSection = extractRequiredArtifactsSection(content);
10366
+ if (!artifactsSection) {
10367
+ violations.push({
10368
+ file: filename,
10369
+ rule: "A",
10370
+ message: "missing ### Suggested artifacts for this agent section",
10371
+ level: "error"
10372
+ });
10373
+ } else {
10374
+ const yamlBlock = extractYamlBlock(artifactsSection);
10375
+ if (!yamlBlock || !/```ya?ml\s*\nartifacts:/.test(yamlBlock)) {
10376
+ violations.push({
10377
+ file: filename,
10378
+ rule: "A",
10379
+ message: 'bad Suggested-artifacts format: missing fenced yaml block starting with "artifacts:"',
10380
+ level: "error"
10381
+ });
10382
+ } else if (!yamlBlock.includes("{ type:") && !/- \{/.test(yamlBlock)) {
10383
+ violations.push({
10384
+ file: filename,
10385
+ rule: "A",
10386
+ message: "bad Suggested-artifacts format: YAML block has no { type: ..., pointer: ... } items",
10387
+ level: "error"
10388
+ });
10389
+ } else {
10390
+ const yamlBody = extractYamlBody(artifactsSection);
10391
+ if (yamlBody) {
10392
+ const stray = strayTopLevelKeys(yamlBody);
10393
+ if (stray && stray.length > 0) {
10394
+ violations.push({
10395
+ file: filename,
10396
+ rule: "A",
10397
+ message: `bad Suggested-artifacts format: stray top-level key(s) alongside artifacts: [${stray.join(", ")}] -- these are item keys, not log keys`,
10398
+ level: "error"
10399
+ });
10400
+ }
9563
10401
  }
9564
- } catch {
10402
+ }
10403
+ if (hasFlatBacktickKeysWithoutFence(artifactsSection)) {
10404
+ violations.push({
10405
+ file: filename,
10406
+ rule: "A",
10407
+ message: "bad Suggested-artifacts format: flat backtick-keyed lines found outside YAML fence (remove old `key: value` bullet style)",
10408
+ level: "error"
10409
+ });
9565
10410
  }
9566
10411
  }
9567
- if (staleAll.length === 0) {
9568
- return { status: "ok", staleFiles: [], staleCount: 0, mapPath };
10412
+ const readScopeCount = (content.match(/^## Read scope\s*$/gm) ?? []).length;
10413
+ if (readScopeCount > 1) {
10414
+ violations.push({
10415
+ file: filename,
10416
+ rule: "B",
10417
+ message: `duplicate ## Read scope headings found (${readScopeCount} occurrences -- remove all but the first)`,
10418
+ level: "error"
10419
+ });
9569
10420
  }
9570
- const declaredDigest = readSourcesDigest(mapPath);
9571
- if (declaredDigest !== null) {
9572
- const actualDigest = computeSourcesDigest(sourceFiles, cwd);
9573
- if (actualDigest === declaredDigest) {
9574
- return { status: "ok", staleFiles: [], staleCount: 0, mapPath };
10421
+ if (readScopeCount >= 1) {
10422
+ const readScopeSection = extractFirstReadScopeSection(content);
10423
+ if (readScopeSection && !readScopeSection.includes("context-manifest.md")) {
10424
+ violations.push({
10425
+ file: filename,
10426
+ rule: "C",
10427
+ message: "## Read scope section does not reference context-manifest.md",
10428
+ level: "error"
10429
+ });
9575
10430
  }
9576
10431
  }
9577
- return {
9578
- status: "stale",
9579
- staleFiles: staleAll.slice(0, 5),
9580
- staleCount: staleAll.length,
9581
- mapPath
9582
- };
10432
+ if (!content.includes("references/agent-log-protocol.md")) {
10433
+ violations.push({
10434
+ file: filename,
10435
+ rule: "D",
10436
+ message: "missing reference to references/agent-log-protocol.md",
10437
+ level: opts.strict ? "error" : "warning"
10438
+ });
10439
+ }
10440
+ return violations;
9583
10441
  }
9584
- function readSourcesDigest(mapPath) {
10442
+ function collectAgentViolations(cwd, opts = {}) {
10443
+ const agentsDir = join23(cwd, ".claude", "agents");
10444
+ let files;
9585
10445
  try {
9586
- const head = readFileSync20(mapPath, "utf8").slice(0, 2048);
9587
- const m = head.match(/^# sources-digest:\s*([a-f0-9]+)/m);
9588
- return m ? m[1] : null;
10446
+ files = readdirSync11(agentsDir).filter((f) => f.endsWith(".md")).sort();
9589
10447
  } catch {
9590
10448
  return null;
9591
10449
  }
10450
+ const violations = [];
10451
+ for (const filename of files) {
10452
+ let content;
10453
+ try {
10454
+ content = readFileSync24(join23(agentsDir, filename), "utf8");
10455
+ } catch {
10456
+ violations.push({
10457
+ file: filename,
10458
+ rule: "A",
10459
+ message: "cannot read file",
10460
+ level: "error"
10461
+ });
10462
+ continue;
10463
+ }
10464
+ violations.push(...lintAgentContent(filename, content, opts));
10465
+ }
10466
+ return violations;
9592
10467
  }
9593
- var init_freshness = __esm({
9594
- "src/code-map/freshness.ts"() {
10468
+ async function lintAgents(opts) {
10469
+ const cwd = process.cwd();
10470
+ const violations = collectAgentViolations(cwd, opts);
10471
+ if (violations === null) {
10472
+ log.error(
10473
+ `lint-agents: cannot read ${join23(cwd, ".claude", "agents")} -- is this a cdd-kit project?`
10474
+ );
10475
+ return 1;
10476
+ }
10477
+ for (const v of violations) {
10478
+ const prefix = v.level === "error" ? "error" : "warning";
10479
+ process.stderr.write(`${v.file}: [Rule ${v.rule}] ${prefix} -- ${v.message}
10480
+ `);
10481
+ }
10482
+ const errorCount = violations.filter((v) => v.level === "error").length;
10483
+ const warnCount = violations.filter((v) => v.level === "warning").length;
10484
+ console.log(`lint-agents: ${errorCount} error(s), ${warnCount} warning(s)`);
10485
+ if (errorCount > 0)
10486
+ return 1;
10487
+ if (opts.strict && warnCount > 0)
10488
+ return 1;
10489
+ return 0;
10490
+ }
10491
+ var init_lint_agents = __esm({
10492
+ "src/commands/lint-agents.ts"() {
9595
10493
  "use strict";
9596
- init_include_exclude();
9597
- init_config();
9598
- init_code_map();
10494
+ init_logger();
9599
10495
  }
9600
10496
  });
9601
10497
 
@@ -9604,17 +10500,17 @@ var doctor_exports = {};
9604
10500
  __export(doctor_exports, {
9605
10501
  doctor: () => doctor
9606
10502
  });
9607
- import { existsSync as existsSync18, readdirSync as readdirSync11, readFileSync as readFileSync21 } from "fs";
9608
- import { createHash as createHash7 } from "crypto";
9609
- import { join as join21, relative as relative8 } from "path";
10503
+ import { existsSync as existsSync21, readdirSync as readdirSync12, readFileSync as readFileSync25 } from "fs";
10504
+ import { createHash as createHash8 } from "crypto";
10505
+ import { join as join24, relative as relative8 } from "path";
9610
10506
  function fileExists(cwd, relPath) {
9611
- return existsSync18(join21(cwd, relPath));
10507
+ return existsSync21(join24(cwd, relPath));
9612
10508
  }
9613
10509
  function findFiles(dir, predicate, found = []) {
9614
- if (!existsSync18(dir))
10510
+ if (!existsSync21(dir))
9615
10511
  return found;
9616
- for (const entry of readdirSync11(dir, { withFileTypes: true })) {
9617
- const fullPath = join21(dir, entry.name);
10512
+ for (const entry of readdirSync12(dir, { withFileTypes: true })) {
10513
+ const fullPath = join24(dir, entry.name);
9618
10514
  if (entry.isDirectory())
9619
10515
  findFiles(fullPath, predicate, found);
9620
10516
  else if (entry.isFile() && predicate(entry.name))
@@ -9627,12 +10523,12 @@ function inputDigest(paths, cwd) {
9627
10523
  const rel = relative8(cwd, p).replace(/\\/g, "/");
9628
10524
  return `${rel}:${sha256OfFileNormalized(p)}`;
9629
10525
  }).join("\n");
9630
- return createHash7("sha256").update(combined).digest("hex");
10526
+ return createHash8("sha256").update(combined).digest("hex");
9631
10527
  }
9632
10528
  function readContextIndexMetadata(filePath) {
9633
- if (!existsSync18(filePath))
10529
+ if (!existsSync21(filePath))
9634
10530
  return {};
9635
- const text = readFileSync21(filePath, "utf8");
10531
+ const text = readFileSync25(filePath, "utf8");
9636
10532
  const out = {};
9637
10533
  const digestMatch = text.match(/^inputs-digest:\s*([a-f0-9]+)/m);
9638
10534
  if (digestMatch)
@@ -9644,14 +10540,14 @@ function readContextIndexMetadata(filePath) {
9644
10540
  }
9645
10541
  function checkContextFreshness(cwd) {
9646
10542
  const findings = [];
9647
- const projectMap = join21(cwd, "specs", "context", "project-map.md");
9648
- const contractsIndex = join21(cwd, "specs", "context", "contracts-index.md");
9649
- const contextPolicy = join21(cwd, ".cdd", "context-policy.json");
10543
+ const projectMap = join24(cwd, "specs", "context", "project-map.md");
10544
+ const contractsIndex = join24(cwd, "specs", "context", "contracts-index.md");
10545
+ const contextPolicy = join24(cwd, ".cdd", "context-policy.json");
9650
10546
  const contractFiles = findFiles(
9651
- join21(cwd, "contracts"),
10547
+ join24(cwd, "contracts"),
9652
10548
  (name) => name.endsWith(".md") && name !== "INDEX.md" && name !== "CHANGELOG.md"
9653
10549
  );
9654
- if (!existsSync18(projectMap) || !existsSync18(contractsIndex)) {
10550
+ if (!existsSync21(projectMap) || !existsSync21(contractsIndex)) {
9655
10551
  findings.push({
9656
10552
  level: "warning",
9657
10553
  message: "specs/context indexes are missing; run cdd-kit context-scan before classification"
@@ -9660,7 +10556,7 @@ function checkContextFreshness(cwd) {
9660
10556
  }
9661
10557
  const projectMapMeta = readContextIndexMetadata(projectMap);
9662
10558
  const contractsIndexMeta = readContextIndexMetadata(contractsIndex);
9663
- const projectInputDigest = inputDigest([contextPolicy].filter(existsSync18), cwd);
10559
+ const projectInputDigest = inputDigest([contextPolicy].filter(existsSync21), cwd);
9664
10560
  if (projectMapMeta.inputsDigest === void 0) {
9665
10561
  findings.push({
9666
10562
  level: "warning",
@@ -9697,7 +10593,7 @@ function checkContextFreshness(cwd) {
9697
10593
  }
9698
10594
  function readAgentModel(path) {
9699
10595
  try {
9700
- const text = readFileSync21(path, "utf8");
10596
+ const text = readFileSync25(path, "utf8");
9701
10597
  const m = text.match(/^model:\s*(\S+)/m);
9702
10598
  return m ? m[1] : null;
9703
10599
  } catch {
@@ -9705,12 +10601,12 @@ function readAgentModel(path) {
9705
10601
  }
9706
10602
  }
9707
10603
  function checkModelPolicyDrift(cwd) {
9708
- const policyPath = join21(cwd, ".cdd", "model-policy.json");
9709
- if (!existsSync18(policyPath))
10604
+ const policyPath = join24(cwd, ".cdd", "model-policy.json");
10605
+ if (!existsSync21(policyPath))
9710
10606
  return [];
9711
10607
  let policy;
9712
10608
  try {
9713
- policy = JSON.parse(readFileSync21(policyPath, "utf8"));
10609
+ policy = JSON.parse(readFileSync25(policyPath, "utf8"));
9714
10610
  } catch {
9715
10611
  return [{ level: "warning", message: ".cdd/model-policy.json is not valid JSON" }];
9716
10612
  }
@@ -9722,18 +10618,18 @@ function checkModelPolicyDrift(cwd) {
9722
10618
  }];
9723
10619
  }
9724
10620
  const candidateDirs = [
9725
- join21(cwd, ".claude", "agents"),
9726
- process.env.HOME ? join21(process.env.HOME, ".claude", "agents") : "",
9727
- process.env.USERPROFILE ? join21(process.env.USERPROFILE, ".claude", "agents") : ""
9728
- ].filter((p) => p && existsSync18(p));
10621
+ join24(cwd, ".claude", "agents"),
10622
+ process.env.HOME ? join24(process.env.HOME, ".claude", "agents") : "",
10623
+ process.env.USERPROFILE ? join24(process.env.USERPROFILE, ".claude", "agents") : ""
10624
+ ].filter((p) => p && existsSync21(p));
9729
10625
  if (candidateDirs.length === 0)
9730
10626
  return [];
9731
10627
  const findings = [];
9732
10628
  for (const [role, expected] of Object.entries(roles)) {
9733
10629
  let foundAny = false;
9734
10630
  for (const dir of candidateDirs) {
9735
- const path = join21(dir, `${role}.md`);
9736
- if (!existsSync18(path))
10631
+ const path = join24(dir, `${role}.md`);
10632
+ if (!existsSync21(path))
9737
10633
  continue;
9738
10634
  foundAny = true;
9739
10635
  const actual = readAgentModel(path);
@@ -9752,47 +10648,30 @@ function checkModelPolicyDrift(cwd) {
9752
10648
  }
9753
10649
  return findings;
9754
10650
  }
9755
- async function checkAgentLint(cwd) {
9756
- const agentsDir = join21(cwd, ".claude", "agents");
9757
- if (!existsSync18(agentsDir))
9758
- return [];
9759
- try {
9760
- const { readdirSync: rds, readFileSync: rfs } = await import("fs");
9761
- const files = rds(agentsDir).filter((f) => f.endsWith(".md"));
9762
- if (files.length === 0)
9763
- return [];
9764
- const findings = [];
9765
- for (const filename of files) {
9766
- const content = rfs(join21(agentsDir, filename), "utf8");
9767
- const artifactsSection = content.match(
9768
- /### Required artifacts for this agent\s*\n[\s\S]*?(?=\n#{2,3} |\n---|\s*$)/
9769
- )?.[0];
9770
- if (!artifactsSection || !/```ya?ml\s*\nartifacts:/.test(artifactsSection)) {
9771
- findings.push({
9772
- level: "warning",
9773
- message: `lint-agents: ${filename}: missing artifacts YAML block in Required artifacts section`
9774
- });
9775
- }
9776
- const readScopeCount = (content.match(/^## Read scope\s*$/gm) ?? []).length;
9777
- if (readScopeCount > 1) {
9778
- findings.push({
9779
- level: "warning",
9780
- message: `lint-agents: ${filename}: duplicate ## Read scope headings (${readScopeCount})`
9781
- });
9782
- }
9783
- }
9784
- if (findings.length === 0) {
9785
- findings.push({ level: "ok", message: "lint-agents: all agent prompts pass shape checks" });
9786
- }
9787
- return findings;
9788
- } catch {
10651
+ function checkAgentLint(cwd) {
10652
+ const agentsDir = join24(cwd, ".claude", "agents");
10653
+ if (!existsSync21(agentsDir))
9789
10654
  return [];
10655
+ const violations = collectAgentViolations(cwd);
10656
+ if (violations === null) {
10657
+ return [{
10658
+ level: "warning",
10659
+ message: `lint-agents: could not read ${join24(".claude", "agents")} -- agent prompts were not scanned`
10660
+ }];
10661
+ }
10662
+ const findings = violations.map((v) => ({
10663
+ level: "warning",
10664
+ message: `lint-agents: ${v.file}: [Rule ${v.rule}] ${v.message}`
10665
+ }));
10666
+ if (findings.length === 0) {
10667
+ findings.push({ level: "ok", message: "lint-agents: all agent prompts pass shape checks" });
9790
10668
  }
10669
+ return findings;
9791
10670
  }
9792
10671
  function checkCodeMap(cwd) {
9793
10672
  const findings = [];
9794
- const mapPath = join21(cwd, ".cdd", "code-map.yml");
9795
- if (!existsSync18(mapPath)) {
10673
+ const mapPath = join24(cwd, ".cdd", "code-map.yml");
10674
+ if (!existsSync21(mapPath)) {
9796
10675
  const probe2 = checkCodeMapFreshness(cwd);
9797
10676
  if (probe2.status === "config-error") {
9798
10677
  findings.push({ level: "warning", message: `.cdd/code-map-config.yml is invalid: ${probe2.configError}` });
@@ -9811,7 +10690,7 @@ function checkCodeMap(cwd) {
9811
10690
  const more = probe.staleCount > 3 ? ` (+${probe.staleCount - 3} more)` : "";
9812
10691
  findings.push({ level: "warning", message: `code-map stale: ${top}${more}; run \`cdd-kit code-map\`` });
9813
10692
  }
9814
- const text = readFileSync21(mapPath, "utf8");
10693
+ const text = readFileSync25(mapPath, "utf8");
9815
10694
  const m = text.match(/^# files: (\d+), src-lines: (\d+), map-lines: (\d+), compression: ([\d.]+)x/m);
9816
10695
  if (m) {
9817
10696
  findings.push({ level: "ok", message: `code-map: ${m[1]} files, ${m[4]}x compression` });
@@ -9843,7 +10722,7 @@ async function buildDoctorReport(cwd, opts) {
9843
10722
  }
9844
10723
  findings.push(...checkContextFreshness(cwd));
9845
10724
  findings.push(...checkModelPolicyDrift(cwd));
9846
- findings.push(...await checkAgentLint(cwd));
10725
+ findings.push(...checkAgentLint(cwd));
9847
10726
  findings.push(...checkCodeMap(cwd));
9848
10727
  const errors = findings.filter((finding) => finding.level === "error").length;
9849
10728
  const warnings = findings.filter((finding) => finding.level === "warning").length;
@@ -9887,11 +10766,11 @@ async function attemptAutoFixes(cwd, report) {
9887
10766
  }
9888
10767
  }
9889
10768
  if (/model-policy\.json has no role bindings/i.test(finding.message)) {
9890
- const policyPath = join21(cwd, ".cdd", "model-policy.json");
10769
+ const policyPath = join24(cwd, ".cdd", "model-policy.json");
9891
10770
  try {
9892
10771
  let existing = {};
9893
10772
  try {
9894
- existing = JSON.parse(readFileSync21(policyPath, "utf8"));
10773
+ existing = JSON.parse(readFileSync25(policyPath, "utf8"));
9895
10774
  } catch {
9896
10775
  }
9897
10776
  const merged = {
@@ -9902,6 +10781,7 @@ async function attemptAutoFixes(cwd, report) {
9902
10781
  "qa-reviewer": "opus",
9903
10782
  "contract-reviewer": "sonnet",
9904
10783
  "test-strategist": "sonnet",
10784
+ "bug-fix-engineer": "sonnet",
9905
10785
  "backend-engineer": "sonnet",
9906
10786
  "frontend-engineer": "sonnet",
9907
10787
  "ci-cd-gatekeeper": "sonnet",
@@ -9915,8 +10795,8 @@ async function attemptAutoFixes(cwd, report) {
9915
10795
  "repo-context-scanner": "haiku"
9916
10796
  }
9917
10797
  };
9918
- const { writeFileSync: writeFileSync14 } = await import("fs");
9919
- writeFileSync14(policyPath, JSON.stringify(merged, null, 2) + "\n", "utf8");
10798
+ const { writeFileSync: writeFileSync16 } = await import("fs");
10799
+ writeFileSync16(policyPath, JSON.stringify(merged, null, 2) + "\n", "utf8");
9920
10800
  fixed.push(`populated .cdd/model-policy.json with default role bindings`);
9921
10801
  continue;
9922
10802
  } catch (err) {
@@ -9956,305 +10836,88 @@ async function doctor(opts = {}) {
9956
10836
  process.exit(1);
9957
10837
  return;
9958
10838
  }
9959
- log.blank();
9960
- log.info(`Doctor provider: ${report.provider}`);
9961
- for (const finding of report.findings) {
9962
- if (finding.level === "ok")
9963
- log.ok(finding.message);
9964
- else if (finding.level === "warning")
9965
- log.warn(finding.message);
9966
- else
9967
- log.error(finding.message);
9968
- }
9969
- log.blank();
9970
- if (!report.ok) {
9971
- log.error(report.strict && report.errors === 0 ? `doctor failed in strict mode with ${report.warnings} warning(s)` : `doctor failed with ${report.errors} error(s)`);
9972
- process.exit(1);
9973
- }
9974
- if (report.warnings > 0) {
9975
- log.warn(`doctor completed with ${report.warnings} warning(s)`);
9976
- } else {
9977
- log.ok("doctor passed");
9978
- }
9979
- log.blank();
9980
- }
9981
- var init_doctor = __esm({
9982
- "src/commands/doctor.ts"() {
9983
- "use strict";
9984
- init_logger();
9985
- init_provider();
9986
- init_freshness();
9987
- init_digest();
9988
- }
9989
- });
9990
-
9991
- // src/commands/lint-agents.ts
9992
- var lint_agents_exports = {};
9993
- __export(lint_agents_exports, {
9994
- lintAgents: () => lintAgents
9995
- });
9996
- import { readdirSync as readdirSync12, readFileSync as readFileSync22 } from "fs";
9997
- import { join as join22 } from "path";
9998
- import { load as yamlLoad2 } from "js-yaml";
9999
- function extractRequiredArtifactsSection(content) {
10000
- const match = content.match(
10001
- /### (?:Suggested|Required) artifacts for this agent\s*\n([\s\S]*?)(?=\n#{2,3} |\n---|\s*$)/
10002
- );
10003
- return match ? match[0] : null;
10004
- }
10005
- function extractFirstReadScopeSection(content) {
10006
- const match = content.match(/## Read scope\s*\n([\s\S]*?)(?=\n## |\s*$)/);
10007
- return match ? match[0] : null;
10008
- }
10009
- function extractYamlBlock(section) {
10010
- const match = section.match(/```ya?ml\s*\n([\s\S]*?)```/);
10011
- return match ? match[0] : null;
10012
- }
10013
- function extractYamlBody(section) {
10014
- const match = section.match(/```ya?ml\s*\n([\s\S]*?)```/);
10015
- return match ? match[1] : null;
10016
- }
10017
- function strayTopLevelKeys(yamlBody) {
10018
- let parsed;
10019
- try {
10020
- parsed = yamlLoad2(yamlBody);
10021
- } catch {
10022
- return null;
10023
- }
10024
- if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
10025
- return null;
10026
- }
10027
- const keys = Object.keys(parsed);
10028
- return keys.filter((k) => k !== "artifacts");
10029
- }
10030
- function hasFlatBacktickKeysWithoutFence(section) {
10031
- if (/```ya?ml\s*\nartifacts:/.test(section))
10032
- return false;
10033
- const withoutFence = section.replace(/```ya?ml[\s\S]*?```/g, "");
10034
- return /^- `[a-z][a-z0-9-]+`:/m.test(withoutFence);
10035
- }
10036
- async function lintAgents(opts) {
10037
- const cwd = process.cwd();
10038
- const agentsDir = join22(cwd, ".claude", "agents");
10039
- let files;
10040
- try {
10041
- files = readdirSync12(agentsDir).filter((f) => f.endsWith(".md")).sort();
10042
- } catch {
10043
- log.error(`lint-agents: cannot read ${agentsDir} ??is this a cdd-kit project?`);
10044
- return 1;
10045
- }
10046
- const violations = [];
10047
- for (const filename of files) {
10048
- const filePath = join22(agentsDir, filename);
10049
- let content;
10050
- try {
10051
- content = readFileSync22(filePath, "utf8");
10052
- } catch {
10053
- violations.push({
10054
- file: filename,
10055
- rule: "A",
10056
- message: "cannot read file",
10057
- level: "error"
10058
- });
10059
- continue;
10060
- }
10061
- if (content.charCodeAt(0) === 65279) {
10062
- violations.push({
10063
- file: filename,
10064
- rule: "Meta",
10065
- message: "file starts with UTF-8 BOM (U+FEFF); frontmatter parsers may treat the first key as invalid",
10066
- level: "error"
10067
- });
10068
- content = content.slice(1);
10069
- }
10070
- const artifactsSection = extractRequiredArtifactsSection(content);
10071
- if (!artifactsSection) {
10072
- violations.push({
10073
- file: filename,
10074
- rule: "A",
10075
- message: "missing ### Suggested artifacts for this agent section",
10076
- level: "error"
10077
- });
10078
- } else {
10079
- const yamlBlock = extractYamlBlock(artifactsSection);
10080
- if (!yamlBlock || !/```ya?ml\s*\nartifacts:/.test(yamlBlock)) {
10081
- violations.push({
10082
- file: filename,
10083
- rule: "A",
10084
- message: 'bad Suggested-artifacts format: missing fenced yaml block starting with "artifacts:"',
10085
- level: "error"
10086
- });
10087
- } else if (!yamlBlock.includes("{ type:") && !/- \{/.test(yamlBlock)) {
10088
- violations.push({
10089
- file: filename,
10090
- rule: "A",
10091
- message: "bad Suggested-artifacts format: YAML block has no { type: ..., pointer: ... } items",
10092
- level: "error"
10093
- });
10094
- } else {
10095
- const yamlBody = extractYamlBody(artifactsSection);
10096
- if (yamlBody) {
10097
- const stray = strayTopLevelKeys(yamlBody);
10098
- if (stray && stray.length > 0) {
10099
- violations.push({
10100
- file: filename,
10101
- rule: "A",
10102
- message: `bad Suggested-artifacts format: stray top-level key(s) alongside artifacts: [${stray.join(", ")}] ??these are item keys, not log keys`,
10103
- level: "error"
10104
- });
10105
- }
10106
- }
10107
- }
10108
- if (hasFlatBacktickKeysWithoutFence(artifactsSection)) {
10109
- violations.push({
10110
- file: filename,
10111
- rule: "A",
10112
- message: "bad Suggested-artifacts format: flat backtick-keyed lines found outside YAML fence (remove old `key: value` bullet style)",
10113
- level: "error"
10114
- });
10115
- }
10116
- }
10117
- const readScopeCount = (content.match(/^## Read scope\s*$/gm) ?? []).length;
10118
- if (readScopeCount > 1) {
10119
- violations.push({
10120
- file: filename,
10121
- rule: "B",
10122
- message: `duplicate ## Read scope headings found (${readScopeCount} occurrences ??remove all but the first)`,
10123
- level: "error"
10124
- });
10125
- }
10126
- if (readScopeCount >= 1) {
10127
- const readScopeSection = extractFirstReadScopeSection(content);
10128
- if (readScopeSection && !readScopeSection.includes("context-manifest.md")) {
10129
- violations.push({
10130
- file: filename,
10131
- rule: "C",
10132
- message: "## Read scope section does not reference context-manifest.md",
10133
- level: "error"
10134
- });
10135
- }
10136
- }
10137
- if (!content.includes("references/agent-log-protocol.md")) {
10138
- violations.push({
10139
- file: filename,
10140
- rule: "D",
10141
- message: "missing reference to references/agent-log-protocol.md",
10142
- level: opts.strict ? "error" : "warning"
10143
- });
10144
- }
10145
- }
10146
- for (const v of violations) {
10147
- const prefix = v.level === "error" ? "error" : "warning";
10148
- process.stderr.write(`${v.file}: [Rule ${v.rule}] ${prefix} ??${v.message}
10149
- `);
10150
- }
10151
- const errorCount = violations.filter((v) => v.level === "error").length;
10152
- const warnCount = violations.filter((v) => v.level === "warning").length;
10153
- console.log(`lint-agents: ${errorCount} error(s), ${warnCount} warning(s)`);
10154
- if (errorCount > 0)
10155
- return 1;
10156
- if (opts.strict && warnCount > 0)
10157
- return 1;
10158
- return 0;
10159
- }
10160
- var init_lint_agents = __esm({
10161
- "src/commands/lint-agents.ts"() {
10162
- "use strict";
10163
- init_logger();
10164
- }
10165
- });
10166
-
10167
- // src/code-map/index-reader.ts
10168
- import { existsSync as existsSync19, readFileSync as readFileSync23 } from "fs";
10169
- import yaml3 from "js-yaml";
10170
- async function ensureCodeMapFresh(mapPath, refresh2) {
10171
- if (!refresh2)
10172
- return { refreshed: false };
10173
- const freshness = checkCodeMapFreshness(process.cwd(), mapPath);
10174
- if (freshness.status === "config-error") {
10175
- return {
10176
- refreshed: false,
10177
- error: `.cdd/code-map-config.yml is invalid: ${freshness.configError}`
10178
- };
10179
- }
10180
- if (freshness.status === "missing-with-sources" || freshness.status === "missing-greenfield" || freshness.status === "stale") {
10181
- const { codeMap: codeMap2 } = await Promise.resolve().then(() => (init_code_map(), code_map_exports));
10182
- const exit = await codeMap2({
10183
- path: ".",
10184
- out: mapPath,
10185
- include: [],
10186
- exclude: [],
10187
- check: false,
10188
- maxLines: 1e5,
10189
- silent: true
10190
- });
10191
- if (exit !== 0) {
10192
- return {
10193
- refreshed: false,
10194
- error: `could not refresh ${mapPath}; run \`cdd-kit code-map\` for details.`
10195
- };
10196
- }
10197
- return { refreshed: true };
10198
- }
10199
- return { refreshed: false };
10200
- }
10201
- function loadCodeMapEntries(mapPath) {
10202
- if (!existsSync19(mapPath)) {
10203
- throw new Error(`${mapPath} is missing; run \`cdd-kit code-map\` first.`);
10204
- }
10205
- const text = readFileSync23(mapPath, "utf8");
10206
- const totalLinesByPath = extractTotalLines(text);
10207
- const raw = yaml3.load(text, { schema: yaml3.JSON_SCHEMA });
10208
- if (!raw || typeof raw !== "object" || Array.isArray(raw))
10209
- return [];
10210
- const entries = [];
10211
- for (const [path, value] of Object.entries(raw)) {
10212
- if (!value || typeof value !== "object" || Array.isArray(value))
10213
- continue;
10214
- const obj = value;
10215
- entries.push({
10216
- path,
10217
- total_lines: totalLinesByPath.get(path) ?? (typeof obj.total_lines === "number" ? obj.total_lines : 0),
10218
- imports: Array.isArray(obj.imports) ? obj.imports : [],
10219
- constants: Array.isArray(obj.constants) ? obj.constants : [],
10220
- classes: Array.isArray(obj.classes) ? obj.classes : [],
10221
- functions: Array.isArray(obj.functions) ? obj.functions : [],
10222
- interfaces: Array.isArray(obj.interfaces) ? obj.interfaces : [],
10223
- types: Array.isArray(obj.types) ? obj.types : [],
10224
- enums: Array.isArray(obj.enums) ? obj.enums : []
10225
- });
10839
+ log.blank();
10840
+ log.info(`Doctor provider: ${report.provider}`);
10841
+ for (const finding of report.findings) {
10842
+ if (finding.level === "ok")
10843
+ log.ok(finding.message);
10844
+ else if (finding.level === "warning")
10845
+ log.warn(finding.message);
10846
+ else
10847
+ log.error(finding.message);
10226
10848
  }
10227
- return entries;
10228
- }
10229
- function extractTotalLines(text) {
10230
- const totals = /* @__PURE__ */ new Map();
10231
- for (const line of text.split(/\r?\n/)) {
10232
- const m = line.match(/^((?:'[^']*(?:''[^']*)*')|[^#:\s][^#]*?):\s*#\s*(\d+)\s+lines\b/);
10233
- if (!m)
10234
- continue;
10235
- totals.set(unquoteYamlKey(m[1].trim()), Number(m[2]));
10849
+ log.blank();
10850
+ if (!report.ok) {
10851
+ log.error(report.strict && report.errors === 0 ? `doctor failed in strict mode with ${report.warnings} warning(s)` : `doctor failed with ${report.errors} error(s)`);
10852
+ process.exit(1);
10236
10853
  }
10237
- return totals;
10238
- }
10239
- function unquoteYamlKey(key) {
10240
- if (key.startsWith("'") && key.endsWith("'")) {
10241
- return key.slice(1, -1).replace(/''/g, "'");
10854
+ if (report.warnings > 0) {
10855
+ log.warn(`doctor completed with ${report.warnings} warning(s)`);
10856
+ } else {
10857
+ log.ok("doctor passed");
10242
10858
  }
10243
- return key;
10859
+ log.blank();
10244
10860
  }
10245
- var init_index_reader = __esm({
10246
- "src/code-map/index-reader.ts"() {
10861
+ var init_doctor = __esm({
10862
+ "src/commands/doctor.ts"() {
10247
10863
  "use strict";
10864
+ init_logger();
10865
+ init_provider();
10248
10866
  init_freshness();
10867
+ init_lint_agents();
10868
+ init_digest();
10869
+ }
10870
+ });
10871
+
10872
+ // src/commands/code-map-scan-worker.ts
10873
+ var code_map_scan_worker_exports = {};
10874
+ __export(code_map_scan_worker_exports, {
10875
+ runScanWorker: () => runScanWorker
10876
+ });
10877
+ import { readFileSync as readFileSync26 } from "fs";
10878
+ async function runScanWorker(opts) {
10879
+ let scanner;
10880
+ switch (opts.lang) {
10881
+ case "js":
10882
+ scanner = (await Promise.resolve().then(() => (init_javascript(), javascript_exports))).jsScanner;
10883
+ break;
10884
+ case "ts":
10885
+ scanner = (await Promise.resolve().then(() => (init_typescript(), typescript_exports))).tsScanner;
10886
+ break;
10887
+ case "vue":
10888
+ scanner = (await Promise.resolve().then(() => (init_vue(), vue_exports))).vueScanner;
10889
+ break;
10890
+ default:
10891
+ process.stderr.write(`__code-map-scan: unknown --lang "${opts.lang}"
10892
+ `);
10893
+ return 1;
10894
+ }
10895
+ let files;
10896
+ try {
10897
+ files = readFileSync26(opts.batchFile, "utf8").split("\n").map((s) => s.trim()).filter(Boolean);
10898
+ } catch (err) {
10899
+ process.stderr.write(`__code-map-scan: cannot read --batch-file: ${err.message}
10900
+ `);
10901
+ return 1;
10902
+ }
10903
+ const result = await scanInProcess(scanner, files, opts.repoRoot);
10904
+ process.stdout.write(JSON.stringify(result));
10905
+ return 0;
10906
+ }
10907
+ var init_code_map_scan_worker = __esm({
10908
+ "src/commands/code-map-scan-worker.ts"() {
10909
+ "use strict";
10910
+ init_orchestrator();
10249
10911
  }
10250
10912
  });
10251
10913
 
10252
10914
  // src/commands/index-query.ts
10253
10915
  var index_query_exports = {};
10254
10916
  __export(index_query_exports, {
10255
- indexQuery: () => indexQuery
10917
+ indexQuery: () => indexQuery,
10918
+ queryEntries: () => queryEntries
10256
10919
  });
10257
- import { existsSync as existsSync20 } from "fs";
10920
+ import { existsSync as existsSync22 } from "fs";
10258
10921
  async function indexQuery(term, opts) {
10259
10922
  const mapPath = opts.map || ".cdd/code-map.yml";
10260
10923
  const limit = Number.isFinite(opts.limit) && opts.limit > 0 ? Math.floor(opts.limit) : 10;
@@ -10264,7 +10927,7 @@ async function indexQuery(term, opts) {
10264
10927
  return printFailure(freshness.error, opts.json);
10265
10928
  }
10266
10929
  refreshed = freshness.refreshed;
10267
- if (!existsSync20(mapPath)) {
10930
+ if (!existsSync22(mapPath)) {
10268
10931
  return printFailure(`${mapPath} is missing; run \`cdd-kit code-map\` first.`, opts.json);
10269
10932
  }
10270
10933
  let entries;
@@ -10432,8 +11095,8 @@ var index_impact_exports = {};
10432
11095
  __export(index_impact_exports, {
10433
11096
  indexImpact: () => indexImpact
10434
11097
  });
10435
- import { existsSync as existsSync21 } from "fs";
10436
- import { posix } from "path";
11098
+ import { existsSync as existsSync23 } from "fs";
11099
+ import { posix as posix2 } from "path";
10437
11100
  async function indexImpact(term, opts) {
10438
11101
  const mapPath = opts.map || ".cdd/code-map.yml";
10439
11102
  const limit = Number.isFinite(opts.limit) && opts.limit > 0 ? Math.floor(opts.limit) : 20;
@@ -10441,7 +11104,7 @@ async function indexImpact(term, opts) {
10441
11104
  if (freshness.error) {
10442
11105
  return printFailure2(freshness.error, opts.json);
10443
11106
  }
10444
- if (!existsSync21(mapPath)) {
11107
+ if (!existsSync23(mapPath)) {
10445
11108
  return printFailure2(`${mapPath} is missing; run \`cdd-kit code-map\` first.`, opts.json);
10446
11109
  }
10447
11110
  let entries;
@@ -10458,7 +11121,7 @@ async function indexImpact(term, opts) {
10458
11121
  const target = targetResult.entry;
10459
11122
  const targetImports = target.imports.map((imp) => resolveImport(target.path, imp, pathSet));
10460
11123
  const imports = targetImports.filter((imp) => imp.resolved);
10461
- const unresolvedLocalImports = targetImports.filter((imp) => !imp.resolved && isLocalImport(imp.module));
11124
+ const unresolvedLocalImports = targetImports.filter((imp) => !imp.resolved && isLocalImport2(imp.module));
10462
11125
  const dependents = entries.filter((entry) => entry.path !== target.path).map((entry) => ({
10463
11126
  entry,
10464
11127
  imports: entry.imports.map((imp) => resolveImport(entry.path, imp, pathSet)).filter((imp) => imp.resolved === target.path)
@@ -10494,7 +11157,7 @@ function findTarget(entries, term) {
10494
11157
  if (exact)
10495
11158
  return { entry: exact };
10496
11159
  const pathMatches2 = entries.filter(
10497
- (entry) => entry.path.toLowerCase().includes(query.toLowerCase()) || posix.basename(entry.path).toLowerCase() === query.toLowerCase()
11160
+ (entry) => entry.path.toLowerCase().includes(query.toLowerCase()) || posix2.basename(entry.path).toLowerCase() === query.toLowerCase()
10498
11161
  );
10499
11162
  if (pathMatches2.length === 1)
10500
11163
  return { entry: pathMatches2[0] };
@@ -10530,7 +11193,7 @@ function normalizeQueryPath(term) {
10530
11193
  return term.trim().replace(/\\/g, "/").replace(/^\.\//, "");
10531
11194
  }
10532
11195
  function resolveImport(importerPath, imp, pathSet) {
10533
- const resolved = resolveLocalModule(importerPath, imp.module, pathSet);
11196
+ const resolved = resolveLocalModule2(importerPath, imp.module, pathSet);
10534
11197
  return {
10535
11198
  module: imp.module,
10536
11199
  items: Array.isArray(imp.items) ? imp.items : [],
@@ -10538,30 +11201,30 @@ function resolveImport(importerPath, imp, pathSet) {
10538
11201
  ...resolved ? { resolved } : {}
10539
11202
  };
10540
11203
  }
10541
- function resolveLocalModule(importerPath, moduleName, pathSet) {
10542
- if (!isLocalImport(moduleName))
11204
+ function resolveLocalModule2(importerPath, moduleName, pathSet) {
11205
+ if (!isLocalImport2(moduleName))
10543
11206
  return void 0;
10544
- const base = moduleName.startsWith("./") || moduleName.startsWith("../") ? posix.normalize(posix.join(posix.dirname(importerPath), moduleName)) : resolvePythonRelativeImport(importerPath, moduleName);
10545
- for (const candidate of resolutionCandidates(base)) {
11207
+ const base = moduleName.startsWith("./") || moduleName.startsWith("../") ? posix2.normalize(posix2.join(posix2.dirname(importerPath), moduleName)) : resolvePythonRelativeImport2(importerPath, moduleName);
11208
+ for (const candidate of resolutionCandidates2(base)) {
10546
11209
  if (pathSet.has(candidate))
10547
11210
  return candidate;
10548
11211
  }
10549
11212
  return void 0;
10550
11213
  }
10551
- function resolvePythonRelativeImport(importerPath, moduleName) {
11214
+ function resolvePythonRelativeImport2(importerPath, moduleName) {
10552
11215
  const match = moduleName.match(/^(\.+)(.*)$/);
10553
11216
  if (!match)
10554
11217
  return moduleName;
10555
11218
  const upLevels = Math.max(0, match[1].length - 1);
10556
- let baseDir = posix.dirname(importerPath);
11219
+ let baseDir = posix2.dirname(importerPath);
10557
11220
  for (let i = 0; i < upLevels; i++) {
10558
- baseDir = posix.dirname(baseDir);
11221
+ baseDir = posix2.dirname(baseDir);
10559
11222
  }
10560
11223
  const rest = match[2].replace(/^\./, "").replace(/\./g, "/");
10561
- return rest ? posix.normalize(posix.join(baseDir, rest)) : baseDir;
11224
+ return rest ? posix2.normalize(posix2.join(baseDir, rest)) : baseDir;
10562
11225
  }
10563
- function resolutionCandidates(base) {
10564
- const ext = posix.extname(base);
11226
+ function resolutionCandidates2(base) {
11227
+ const ext = posix2.extname(base);
10565
11228
  const candidates = [];
10566
11229
  if (ext) {
10567
11230
  candidates.push(base);
@@ -10570,17 +11233,17 @@ function resolutionCandidates(base) {
10570
11233
  candidates.push(`${withoutExt}.ts`, `${withoutExt}.tsx`, `${withoutExt}.vue`);
10571
11234
  }
10572
11235
  } else {
10573
- for (const candidateExt of RESOLUTION_EXTENSIONS) {
11236
+ for (const candidateExt of RESOLUTION_EXTENSIONS2) {
10574
11237
  candidates.push(`${base}${candidateExt}`);
10575
11238
  }
10576
11239
  }
10577
- for (const candidateExt of RESOLUTION_EXTENSIONS) {
10578
- candidates.push(posix.join(base, `index${candidateExt}`));
11240
+ for (const candidateExt of RESOLUTION_EXTENSIONS2) {
11241
+ candidates.push(posix2.join(base, `index${candidateExt}`));
10579
11242
  }
10580
- candidates.push(posix.join(base, "__init__.py"));
11243
+ candidates.push(posix2.join(base, "__init__.py"));
10581
11244
  return [...new Set(candidates)];
10582
11245
  }
10583
- function isLocalImport(moduleName) {
11246
+ function isLocalImport2(moduleName) {
10584
11247
  return moduleName.startsWith(".");
10585
11248
  }
10586
11249
  function summarizeSymbols(entry) {
@@ -10670,12 +11333,486 @@ function printFailure2(message, json) {
10670
11333
  }
10671
11334
  return 1;
10672
11335
  }
10673
- var RESOLUTION_EXTENSIONS;
11336
+ var RESOLUTION_EXTENSIONS2;
10674
11337
  var init_index_impact = __esm({
10675
11338
  "src/commands/index-impact.ts"() {
10676
11339
  "use strict";
10677
11340
  init_index_reader();
10678
- RESOLUTION_EXTENSIONS = [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".vue", ".py"];
11341
+ RESOLUTION_EXTENSIONS2 = [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".vue", ".py"];
11342
+ }
11343
+ });
11344
+
11345
+ // src/code-graph/queries.ts
11346
+ function searchGraph(graph2, term, limit) {
11347
+ const query = term.trim().toLowerCase();
11348
+ if (!query)
11349
+ return [];
11350
+ return graph2.nodes.map((node) => {
11351
+ const score = Math.max(
11352
+ scoreText2(node.name, query, 120),
11353
+ scoreText2(node.qualified_name, query, 100),
11354
+ scoreText2(node.file_path, query, 80)
11355
+ );
11356
+ return {
11357
+ node,
11358
+ score,
11359
+ edges: {
11360
+ incoming: graph2.edges.filter((e) => e.target === node.id).length,
11361
+ outgoing: graph2.edges.filter((e) => e.source === node.id).length
11362
+ }
11363
+ };
11364
+ }).filter((result) => result.score > 0).sort((a, b) => b.score - a.score || a.node.file_path.localeCompare(b.node.file_path) || a.node.start_line - b.node.start_line).slice(0, limit);
11365
+ }
11366
+ function findGraphNode(graph2, term) {
11367
+ const query = term.trim().toLowerCase().replace(/\\/g, "/").replace(/^\.\//, "");
11368
+ const exact = graph2.nodes.find((n) => n.id === term || n.qualified_name.toLowerCase() === query || n.file_path.toLowerCase() === query);
11369
+ if (exact)
11370
+ return exact;
11371
+ const pathMatches2 = graph2.nodes.filter((n) => n.kind === "file" && n.file_path.toLowerCase().includes(query));
11372
+ if (pathMatches2.length === 1)
11373
+ return pathMatches2[0];
11374
+ const symbolMatches = graph2.nodes.filter((n) => n.name.toLowerCase() === query || n.qualified_name.toLowerCase().endsWith(`::${query}`));
11375
+ if (symbolMatches.length === 1)
11376
+ return symbolMatches[0];
11377
+ return searchGraph(graph2, term, 1)[0]?.node;
11378
+ }
11379
+ function graphImpact(graph2, term, depth, limit) {
11380
+ const target = findGraphNode(graph2, term);
11381
+ if (!target)
11382
+ return void 0;
11383
+ return traverse(graph2, target, ["calls", "imports", "references", "extends", "implements"], "both", depth, limit);
11384
+ }
11385
+ function graphContext(graph2, task, maxNodes) {
11386
+ const entryPoints = searchGraph(graph2, task, Math.min(maxNodes, 10));
11387
+ const nodeIds = new Set(entryPoints.map((r) => r.node.id));
11388
+ const edgeMap = /* @__PURE__ */ new Map();
11389
+ for (const entry of entryPoints) {
11390
+ const neighborhood = traverse(graph2, entry.node, ["contains", "calls", "imports", "references", "extends", "implements"], "both", 1, maxNodes);
11391
+ for (const node of neighborhood.nodes)
11392
+ nodeIds.add(node.id);
11393
+ for (const edge of neighborhood.edges)
11394
+ edgeMap.set(edge.id, edge);
11395
+ }
11396
+ const nodes = graph2.nodes.filter((n) => nodeIds.has(n.id)).slice(0, maxNodes);
11397
+ return { query: task, entry_points: entryPoints, nodes, edges: [...edgeMap.values()] };
11398
+ }
11399
+ function traverse(graph2, target, kinds, direction, depth, limit) {
11400
+ const maxDepth = Math.max(1, depth || 1);
11401
+ const maxNodes = Math.max(1, limit || 50);
11402
+ const nodeById = new Map(graph2.nodes.map((n) => [n.id, n]));
11403
+ const visited = /* @__PURE__ */ new Set([target.id]);
11404
+ const keptEdges = /* @__PURE__ */ new Map();
11405
+ let frontier = [target.id];
11406
+ for (let d = 0; d < maxDepth && frontier.length > 0 && visited.size < maxNodes; d++) {
11407
+ const next = [];
11408
+ for (const nodeId2 of frontier) {
11409
+ const edges = graph2.edges.filter(
11410
+ (e) => kinds.includes(e.kind) && (direction === "both" ? e.source === nodeId2 || e.target === nodeId2 : direction === "incoming" ? e.target === nodeId2 : e.source === nodeId2)
11411
+ );
11412
+ for (const edge of edges) {
11413
+ const other = edge.source === nodeId2 ? edge.target : edge.source;
11414
+ if (!nodeById.has(other))
11415
+ continue;
11416
+ keptEdges.set(edge.id, edge);
11417
+ if (!visited.has(other)) {
11418
+ visited.add(other);
11419
+ next.push(other);
11420
+ if (visited.size >= maxNodes)
11421
+ break;
11422
+ }
11423
+ }
11424
+ }
11425
+ frontier = next;
11426
+ }
11427
+ return {
11428
+ target,
11429
+ depth: maxDepth,
11430
+ nodes: [...visited].map((id) => nodeById.get(id)).filter((node) => !!node),
11431
+ edges: [...keptEdges.values()]
11432
+ };
11433
+ }
11434
+ function scoreText2(text, query, weight) {
11435
+ const haystack = text.toLowerCase();
11436
+ if (haystack === query)
11437
+ return weight + 40;
11438
+ if (haystack.endsWith(`/${query}`) || haystack.endsWith(`.${query}`) || haystack.endsWith(`::${query}`))
11439
+ return weight + 30;
11440
+ if (haystack.startsWith(query))
11441
+ return weight + 20;
11442
+ if (haystack.includes(query))
11443
+ return weight;
11444
+ return 0;
11445
+ }
11446
+ var init_queries = __esm({
11447
+ "src/code-graph/queries.ts"() {
11448
+ "use strict";
11449
+ }
11450
+ });
11451
+
11452
+ // src/commands/graph.ts
11453
+ var graph_exports = {};
11454
+ __export(graph_exports, {
11455
+ graphContext: () => graphContext2,
11456
+ graphImpact: () => graphImpact2,
11457
+ graphQuery: () => graphQuery,
11458
+ graphStatus: () => graphStatus,
11459
+ graphSync: () => graphSync
11460
+ });
11461
+ import { existsSync as existsSync24 } from "fs";
11462
+ import { join as join25 } from "path";
11463
+ import { spawnSync as spawnSync3 } from "child_process";
11464
+ function codeGraphCommand() {
11465
+ return process.env.CDD_CODEGRAPH_BIN || "codegraph";
11466
+ }
11467
+ function runCodeGraph(args, cwd = process.cwd()) {
11468
+ const command = codeGraphCommand();
11469
+ if (command.toLowerCase().endsWith(".js")) {
11470
+ return spawnSync3(process.execPath, [command, ...args], { cwd, encoding: "buffer" });
11471
+ }
11472
+ return spawnSync3(command, args, { cwd, encoding: "buffer" });
11473
+ }
11474
+ function probeCodeGraph(cwd = process.cwd()) {
11475
+ const command = codeGraphCommand();
11476
+ const initialized = existsSync24(join25(cwd, ".codegraph"));
11477
+ const version = runCodeGraph(["--version"], cwd);
11478
+ if (version.error) {
11479
+ return {
11480
+ available: false,
11481
+ command,
11482
+ initialized,
11483
+ reason: version.error.message
11484
+ };
11485
+ }
11486
+ if (version.status !== 0) {
11487
+ return {
11488
+ available: false,
11489
+ command,
11490
+ initialized,
11491
+ reason: (version.stderr?.toString("utf8") || version.stdout?.toString("utf8") || `exit ${version.status}`).trim()
11492
+ };
11493
+ }
11494
+ return { available: true, command, initialized };
11495
+ }
11496
+ function selectEngine(opts, cwd = process.cwd()) {
11497
+ const requested = opts.engine ?? "auto";
11498
+ const probe = probeCodeGraph(cwd);
11499
+ if (!["auto", "native", "codegraph", "codemap"].includes(requested)) {
11500
+ return { error: `Invalid graph engine: ${requested}. Use auto, native, codegraph, or codemap.`, probe };
11501
+ }
11502
+ if (requested === "codemap")
11503
+ return { engine: "codemap", probe };
11504
+ if (requested === "native" || requested === "auto")
11505
+ return { engine: "native", probe };
11506
+ if (requested === "codegraph") {
11507
+ if (!probe.available) {
11508
+ return {
11509
+ error: `CodeGraph is not available (${probe.command}): ${probe.reason ?? "command not found"}`,
11510
+ probe
11511
+ };
11512
+ }
11513
+ return { engine: "codegraph", probe };
11514
+ }
11515
+ return { engine: "native", probe };
11516
+ }
11517
+ function writeJson(value) {
11518
+ process.stdout.write(`${JSON.stringify(value, null, 2)}
11519
+ `);
11520
+ }
11521
+ function pipeResult(result) {
11522
+ if (result.stdout?.length)
11523
+ process.stdout.write(result.stdout);
11524
+ if (result.stderr?.length)
11525
+ process.stderr.write(result.stderr);
11526
+ if (result.error) {
11527
+ process.stderr.write(`${result.error.message}
11528
+ `);
11529
+ return 1;
11530
+ }
11531
+ return result.status ?? 1;
11532
+ }
11533
+ function printEngineError(message, json, probe) {
11534
+ if (json) {
11535
+ writeJson({ error: message, codegraph: probe });
11536
+ } else {
11537
+ console.error(message);
11538
+ }
11539
+ return 1;
11540
+ }
11541
+ async function ensureNativeGraph(mapPath, refresh2) {
11542
+ const freshness = await ensureCodeMapFresh(mapPath, refresh2);
11543
+ if (freshness.error)
11544
+ return { graphPath: graphPathFor(mapPath), refreshed: freshness.refreshed, error: freshness.error };
11545
+ const graphPath = graphPathFor(mapPath);
11546
+ if (!existsSync24(graphPath) && refresh2) {
11547
+ const { codeMap: codeMap2 } = await Promise.resolve().then(() => (init_code_map(), code_map_exports));
11548
+ const exit = await codeMap2({
11549
+ path: ".",
11550
+ out: mapPath,
11551
+ include: [],
11552
+ exclude: [],
11553
+ check: false,
11554
+ maxLines: 1e5,
11555
+ silent: true
11556
+ });
11557
+ if (exit !== 0) {
11558
+ return { graphPath, refreshed: freshness.refreshed, error: `could not refresh ${graphPath}; run \`cdd-kit code-map\` for details.` };
11559
+ }
11560
+ return { graphPath, refreshed: true };
11561
+ }
11562
+ return { graphPath, refreshed: freshness.refreshed };
11563
+ }
11564
+ async function graphStatus(opts = {}) {
11565
+ const cwd = process.cwd();
11566
+ const selected = selectEngine(opts, cwd);
11567
+ if ("error" in selected)
11568
+ return printEngineError(selected.error, opts.json, selected.probe);
11569
+ if (selected.engine === "codegraph") {
11570
+ const args = ["status"];
11571
+ if (opts.path)
11572
+ args.push(opts.path);
11573
+ return pipeResult(runCodeGraph(args, cwd));
11574
+ }
11575
+ const mapPath = opts.map || ".cdd/code-map.yml";
11576
+ const graphPath = graphPathFor(mapPath);
11577
+ const freshness = checkCodeMapFreshness(cwd, mapPath);
11578
+ let graphStats;
11579
+ if (existsSync24(graphPath)) {
11580
+ try {
11581
+ const graph2 = loadCodeGraph(graphPath);
11582
+ graphStats = { graph: graphPath, nodes: graph2.nodes.length, edges: graph2.edges.length, unresolved: graph2.unresolved.length };
11583
+ } catch {
11584
+ graphStats = { graph: graphPath, nodes: 0, edges: 0, unresolved: 0 };
11585
+ }
11586
+ }
11587
+ const payload = {
11588
+ engine: selected.engine,
11589
+ codegraph: selected.probe,
11590
+ code_map: {
11591
+ map: mapPath,
11592
+ status: freshness.status,
11593
+ stale_count: "staleCount" in freshness ? freshness.staleCount : 0,
11594
+ stale_files: "staleFiles" in freshness ? freshness.staleFiles.slice(0, 10) : [],
11595
+ config_error: "configError" in freshness ? freshness.configError : void 0
11596
+ },
11597
+ native_graph: graphStats ?? { graph: graphPath, missing: true }
11598
+ };
11599
+ if (opts.json) {
11600
+ writeJson(payload);
11601
+ } else {
11602
+ console.log(`graph engine: ${selected.engine === "native" ? "native cdd-kit graph" : "code-map fallback"}`);
11603
+ console.log(`code-map: ${payload.code_map.status}`);
11604
+ if (graphStats) {
11605
+ console.log(`code-graph: ${graphStats.nodes} nodes, ${graphStats.edges} edges, ${graphStats.unresolved} unresolved`);
11606
+ } else {
11607
+ console.log(`code-graph: missing (${graphPath}); run cdd-kit code-map`);
11608
+ }
11609
+ if (!selected.probe.available) {
11610
+ console.log(`CodeGraph: unavailable (${selected.probe.command})`);
11611
+ } else if (!selected.probe.initialized) {
11612
+ console.log("CodeGraph: available but project is not initialized (.codegraph/ missing)");
11613
+ }
11614
+ console.log("Next: run `codegraph init -i` to enable semantic graph queries, or continue with code-map fallback.");
11615
+ }
11616
+ return freshness.status === "config-error" ? 1 : 0;
11617
+ }
11618
+ async function graphSync(opts = {}) {
11619
+ const selected = selectEngine({ ...opts, engine: opts.engine ?? "codegraph" });
11620
+ if ("error" in selected)
11621
+ return printEngineError(selected.error, opts.json, selected.probe);
11622
+ if (selected.engine !== "codegraph") {
11623
+ return printEngineError("graph sync requires CodeGraph; code-map fallback refreshes during query/impact.", opts.json, selected.probe);
11624
+ }
11625
+ const args = ["sync"];
11626
+ if (opts.path)
11627
+ args.push(opts.path);
11628
+ return pipeResult(runCodeGraph(args));
11629
+ }
11630
+ async function graphQuery(term, opts) {
11631
+ const selected = selectEngine(opts);
11632
+ if ("error" in selected)
11633
+ return printEngineError(selected.error, opts.json, selected.probe);
11634
+ if (selected.engine === "codegraph") {
11635
+ const args = ["query", term, "--limit", String(opts.limit)];
11636
+ if (opts.json)
11637
+ args.push("--json");
11638
+ return pipeResult(runCodeGraph(args));
11639
+ }
11640
+ if (selected.engine === "native") {
11641
+ const mapPath = opts.map || ".cdd/code-map.yml";
11642
+ const ensured = await ensureNativeGraph(mapPath, opts.refresh !== false);
11643
+ if (ensured.error)
11644
+ return printEngineError(ensured.error, opts.json, selected.probe);
11645
+ try {
11646
+ const graph2 = loadCodeGraph(ensured.graphPath);
11647
+ const results = searchGraph(graph2, term, opts.limit);
11648
+ if (opts.json) {
11649
+ writeJson({ engine: "native", graph: ensured.graphPath, query: term, refreshed: ensured.refreshed, results });
11650
+ } else {
11651
+ console.log(`graph: ${ensured.graphPath}${ensured.refreshed ? " (refreshed)" : ""}`);
11652
+ console.log(`query: ${term}`);
11653
+ console.log(`results: ${results.length}`);
11654
+ for (const result of results) {
11655
+ const n = result.node;
11656
+ console.log(`- ${n.kind}: ${n.qualified_name} lines ${n.start_line}-${n.end_line}`);
11657
+ console.log(` edges: ${result.edges.incoming} incoming, ${result.edges.outgoing} outgoing`);
11658
+ }
11659
+ console.log("Next: run cdd-kit graph impact <node/file/symbol> before editing.");
11660
+ }
11661
+ return results.length === 0 ? 1 : 0;
11662
+ } catch (err) {
11663
+ return printEngineError(err.message, opts.json, selected.probe);
11664
+ }
11665
+ }
11666
+ return indexQuery(term, {
11667
+ map: opts.map || ".cdd/code-map.yml",
11668
+ limit: opts.limit,
11669
+ json: opts.json === true,
11670
+ refresh: opts.refresh !== false
11671
+ });
11672
+ }
11673
+ async function graphImpact2(term, opts) {
11674
+ const selected = selectEngine(opts);
11675
+ if ("error" in selected)
11676
+ return printEngineError(selected.error, opts.json, selected.probe);
11677
+ if (selected.engine === "codegraph") {
11678
+ const args = ["impact", term, "--depth", String(opts.depth)];
11679
+ if (opts.json)
11680
+ args.push("--json");
11681
+ return pipeResult(runCodeGraph(args));
11682
+ }
11683
+ if (selected.engine === "native") {
11684
+ const mapPath = opts.map || ".cdd/code-map.yml";
11685
+ const ensured = await ensureNativeGraph(mapPath, opts.refresh !== false);
11686
+ if (ensured.error)
11687
+ return printEngineError(ensured.error, opts.json, selected.probe);
11688
+ try {
11689
+ const graph2 = loadCodeGraph(ensured.graphPath);
11690
+ const impact = graphImpact(graph2, term, opts.depth, opts.limit);
11691
+ if (!impact)
11692
+ return printEngineError(`No graph node matched "${term}". Try \`cdd-kit graph query "${term}"\` first.`, opts.json, selected.probe);
11693
+ if (opts.json) {
11694
+ writeJson({ engine: "native", graph: ensured.graphPath, query: term, refreshed: ensured.refreshed, ...impact });
11695
+ } else {
11696
+ console.log(`graph: ${ensured.graphPath}${ensured.refreshed ? " (refreshed)" : ""}`);
11697
+ console.log(`target: ${impact.target.qualified_name} (${impact.target.kind})`);
11698
+ console.log(`impact depth: ${impact.depth}`);
11699
+ console.log("nodes:");
11700
+ for (const node of impact.nodes) {
11701
+ console.log(`- ${node.kind}: ${node.qualified_name} lines ${node.start_line}-${node.end_line}`);
11702
+ }
11703
+ console.log("edges:");
11704
+ for (const edge of impact.edges.slice(0, opts.limit)) {
11705
+ const source = graph2.nodes.find((n) => n.id === edge.source)?.qualified_name ?? edge.source;
11706
+ const target = graph2.nodes.find((n) => n.id === edge.target)?.qualified_name ?? edge.target;
11707
+ console.log(`- ${edge.kind}: ${source} -> ${target}${edge.line ? ` line ${edge.line}` : ""} (${edge.provenance})`);
11708
+ }
11709
+ console.log("Next: inspect target plus listed callers/callees/imports before editing.");
11710
+ }
11711
+ return 0;
11712
+ } catch (err) {
11713
+ return printEngineError(err.message, opts.json, selected.probe);
11714
+ }
11715
+ }
11716
+ if (!opts.json && opts.depth > 1) {
11717
+ console.log("graph engine: code-map fallback (depth is limited to direct imports/dependents)");
11718
+ }
11719
+ return indexImpact(term, {
11720
+ map: opts.map || ".cdd/code-map.yml",
11721
+ limit: opts.limit,
11722
+ json: opts.json === true,
11723
+ refresh: opts.refresh !== false
11724
+ });
11725
+ }
11726
+ async function graphContext2(task, opts) {
11727
+ const selected = selectEngine(opts);
11728
+ if ("error" in selected)
11729
+ return printEngineError(selected.error, opts.json, selected.probe);
11730
+ if (selected.engine === "codegraph") {
11731
+ const args = ["context", task, "--max-nodes", String(opts.maxNodes), "--format", opts.json ? "json" : "markdown"];
11732
+ return pipeResult(runCodeGraph(args));
11733
+ }
11734
+ if (selected.engine === "native") {
11735
+ const mapPath2 = opts.map || ".cdd/code-map.yml";
11736
+ const ensured = await ensureNativeGraph(mapPath2, opts.refresh !== false);
11737
+ if (ensured.error)
11738
+ return printEngineError(ensured.error, opts.json, selected.probe);
11739
+ try {
11740
+ const graph2 = loadCodeGraph(ensured.graphPath);
11741
+ const context2 = graphContext(graph2, task, opts.maxNodes);
11742
+ if (opts.json) {
11743
+ writeJson({ engine: "native", graph: ensured.graphPath, refreshed: ensured.refreshed, ...context2 });
11744
+ } else {
11745
+ console.log(`graph: ${ensured.graphPath}${ensured.refreshed ? " (refreshed)" : ""}`);
11746
+ console.log(`task: ${task}`);
11747
+ console.log("entry-points:");
11748
+ for (const entry of context2.entry_points) {
11749
+ console.log(`- ${entry.node.kind}: ${entry.node.qualified_name} lines ${entry.node.start_line}-${entry.node.end_line}`);
11750
+ }
11751
+ console.log("related edges:");
11752
+ for (const edge of context2.edges.slice(0, opts.maxNodes)) {
11753
+ const source = graph2.nodes.find((n) => n.id === edge.source)?.qualified_name ?? edge.source;
11754
+ const target = graph2.nodes.find((n) => n.id === edge.target)?.qualified_name ?? edge.target;
11755
+ console.log(`- ${edge.kind}: ${source} -> ${target}${edge.line ? ` line ${edge.line}` : ""}`);
11756
+ }
11757
+ console.log("Next: run cdd-kit graph impact on the most relevant entry point before editing.");
11758
+ }
11759
+ return context2.entry_points.length === 0 ? 1 : 0;
11760
+ } catch (err) {
11761
+ return printEngineError(err.message, opts.json, selected.probe);
11762
+ }
11763
+ }
11764
+ const mapPath = opts.map || ".cdd/code-map.yml";
11765
+ const freshness = await ensureCodeMapFresh(mapPath, opts.refresh !== false);
11766
+ if (freshness.error)
11767
+ return printEngineError(freshness.error, opts.json, selected.probe);
11768
+ if (!existsSync24(mapPath))
11769
+ return printEngineError(`${mapPath} is missing; run \`cdd-kit code-map\` first.`, opts.json, selected.probe);
11770
+ let entries;
11771
+ try {
11772
+ entries = loadCodeMapEntries(mapPath);
11773
+ } catch (err) {
11774
+ return printEngineError(`${mapPath} is not readable YAML: ${err.message}`, opts.json, selected.probe);
11775
+ }
11776
+ const results = queryEntries(entries, task).slice(0, opts.maxNodes);
11777
+ const payload = {
11778
+ engine: "codemap",
11779
+ query: task,
11780
+ refreshed: freshness.refreshed,
11781
+ warning: "CodeGraph is not active; context is built from code-map symbol/file matches only.",
11782
+ candidates: results,
11783
+ next: results.length > 0 ? "Run cdd-kit graph impact <candidate path or symbol> before editing." : "Try a symbol name, file stem, route/component name, or initialize CodeGraph for semantic context."
11784
+ };
11785
+ if (opts.json) {
11786
+ writeJson(payload);
11787
+ } else {
11788
+ console.log(`graph engine: code-map fallback${freshness.refreshed ? " (refreshed)" : ""}`);
11789
+ console.log(`task: ${task}`);
11790
+ if (results.length === 0) {
11791
+ console.log("candidates: none");
11792
+ console.log(payload.next);
11793
+ return 1;
11794
+ }
11795
+ console.log("candidates:");
11796
+ for (const result of results) {
11797
+ console.log(`- ${result.path} (${result.total_lines} lines)`);
11798
+ for (const match of result.matches.slice(0, 5)) {
11799
+ const loc = match.lines ? ` lines ${match.lines}` : match.line ? ` line ${match.line}` : "";
11800
+ console.log(` - ${match.kind}: ${match.name}${loc}`);
11801
+ }
11802
+ }
11803
+ console.log(payload.next);
11804
+ }
11805
+ return results.length === 0 ? 1 : 0;
11806
+ }
11807
+ var init_graph = __esm({
11808
+ "src/commands/graph.ts"() {
11809
+ "use strict";
11810
+ init_freshness();
11811
+ init_index_reader();
11812
+ init_reader();
11813
+ init_queries();
11814
+ init_index_query();
11815
+ init_index_impact();
10679
11816
  }
10680
11817
  });
10681
11818
 
@@ -10684,28 +11821,28 @@ var archive_exports = {};
10684
11821
  __export(archive_exports, {
10685
11822
  archive: () => archive
10686
11823
  });
10687
- import { join as join23 } from "path";
10688
- import { existsSync as existsSync22, mkdirSync as mkdirSync10, renameSync as renameSync2, readFileSync as readFileSync24, writeFileSync as writeFileSync11, appendFileSync, cpSync as cpSync3, rmSync as rmSync3 } from "fs";
11824
+ import { join as join26 } from "path";
11825
+ import { existsSync as existsSync25, mkdirSync as mkdirSync10, renameSync as renameSync2, readFileSync as readFileSync27, writeFileSync as writeFileSync13, appendFileSync, cpSync as cpSync3, rmSync as rmSync3 } from "fs";
10689
11826
  import yaml4 from "js-yaml";
10690
11827
  async function archive(changeId) {
10691
11828
  const cwd = process.cwd();
10692
- const changeDir = join23(cwd, "specs", "changes", changeId);
11829
+ const changeDir = join26(cwd, "specs", "changes", changeId);
10693
11830
  const archiveYear = (/* @__PURE__ */ new Date()).getFullYear().toString();
10694
- const archiveBase = join23(cwd, "specs", "archive", archiveYear);
10695
- const archiveDir = join23(archiveBase, changeId);
10696
- const indexPath = join23(cwd, "specs", "archive", "INDEX.md");
10697
- if (!existsSync22(changeDir)) {
11831
+ const archiveBase = join26(cwd, "specs", "archive", archiveYear);
11832
+ const archiveDir = join26(archiveBase, changeId);
11833
+ const indexPath = join26(cwd, "specs", "archive", "INDEX.md");
11834
+ if (!existsSync25(changeDir)) {
10698
11835
  log.error(`Change not found: specs/changes/${changeId}`);
10699
11836
  process.exit(1);
10700
11837
  }
10701
- if (existsSync22(archiveDir)) {
11838
+ if (existsSync25(archiveDir)) {
10702
11839
  log.error(`Already archived: specs/archive/${archiveYear}/${changeId}`);
10703
11840
  process.exit(1);
10704
11841
  }
10705
- const tasksPath = join23(changeDir, "tasks.yml");
10706
- if (existsSync22(tasksPath)) {
11842
+ const tasksPath = join26(changeDir, "tasks.yml");
11843
+ if (existsSync25(tasksPath)) {
10707
11844
  try {
10708
- const raw = readFileSync24(tasksPath, "utf8");
11845
+ const raw = readFileSync27(tasksPath, "utf8");
10709
11846
  const data = yaml4.load(raw);
10710
11847
  if (data?.status === "gate-blocked") {
10711
11848
  log.warn("tasks.yml has status: gate-blocked \u2014 archiving anyway (change was paused).");
@@ -10718,7 +11855,7 @@ async function archive(changeId) {
10718
11855
  log.warn("tasks.yml could not be parsed \u2014 archiving anyway.");
10719
11856
  }
10720
11857
  }
10721
- if (!existsSync22(archiveBase)) {
11858
+ if (!existsSync25(archiveBase)) {
10722
11859
  mkdirSync10(archiveBase, { recursive: true });
10723
11860
  }
10724
11861
  try {
@@ -10735,8 +11872,8 @@ async function archive(changeId) {
10735
11872
  const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
10736
11873
  const indexLine = `| ${changeId} | ${archiveYear} | ${today} | specs/archive/${archiveYear}/${changeId}/ |
10737
11874
  `;
10738
- if (!existsSync22(indexPath)) {
10739
- writeFileSync11(indexPath, `# Archive Index
11875
+ if (!existsSync25(indexPath)) {
11876
+ writeFileSync13(indexPath, `# Archive Index
10740
11877
 
10741
11878
  | change-id | year | archived-date | path |
10742
11879
  |---|---|---|---|
@@ -10760,37 +11897,37 @@ var abandon_exports = {};
10760
11897
  __export(abandon_exports, {
10761
11898
  abandon: () => abandon
10762
11899
  });
10763
- import { join as join24 } from "path";
10764
- import { existsSync as existsSync23, readFileSync as readFileSync25, writeFileSync as writeFileSync12, appendFileSync as appendFileSync2, mkdirSync as mkdirSync11 } from "fs";
11900
+ import { join as join27 } from "path";
11901
+ import { existsSync as existsSync26, readFileSync as readFileSync28, writeFileSync as writeFileSync14, appendFileSync as appendFileSync2, mkdirSync as mkdirSync11 } from "fs";
10765
11902
  import yaml5 from "js-yaml";
10766
11903
  async function abandon(changeId, opts) {
10767
11904
  const cwd = process.cwd();
10768
- const changeDir = join24(cwd, "specs", "changes", changeId);
10769
- const tasksPath = join24(changeDir, "tasks.yml");
10770
- if (!existsSync23(changeDir)) {
11905
+ const changeDir = join27(cwd, "specs", "changes", changeId);
11906
+ const tasksPath = join27(changeDir, "tasks.yml");
11907
+ if (!existsSync26(changeDir)) {
10771
11908
  log.error(`Change not found: specs/changes/${changeId}`);
10772
11909
  process.exit(1);
10773
11910
  }
10774
- if (existsSync23(tasksPath)) {
10775
- const raw = readFileSync25(tasksPath, "utf8");
11911
+ if (existsSync26(tasksPath)) {
11912
+ const raw = readFileSync28(tasksPath, "utf8");
10776
11913
  const data = yaml5.load(raw) ?? {};
10777
11914
  data["status"] = "abandoned";
10778
11915
  if (!data["change-id"]) {
10779
11916
  data["change-id"] = changeId;
10780
11917
  }
10781
- writeFileSync12(tasksPath, yaml5.dump(data, { lineWidth: -1, noRefs: true }), "utf8");
11918
+ writeFileSync14(tasksPath, yaml5.dump(data, { lineWidth: -1, noRefs: true }), "utf8");
10782
11919
  }
10783
11920
  const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
10784
- const archiveDir = join24(cwd, "specs", "archive");
10785
- const indexPath = join24(archiveDir, "INDEX.md");
11921
+ const archiveDir = join27(cwd, "specs", "archive");
11922
+ const indexPath = join27(archiveDir, "INDEX.md");
10786
11923
  const reason = opts.reason ?? "no reason given";
10787
11924
  const indexLine = `| ${changeId} | abandoned | ${today} | ${reason} |
10788
11925
  `;
10789
- if (!existsSync23(archiveDir)) {
11926
+ if (!existsSync26(archiveDir)) {
10790
11927
  mkdirSync11(archiveDir, { recursive: true });
10791
11928
  }
10792
- if (!existsSync23(indexPath)) {
10793
- writeFileSync12(indexPath, `# Archive Index
11929
+ if (!existsSync26(indexPath)) {
11930
+ writeFileSync14(indexPath, `# Archive Index
10794
11931
 
10795
11932
  | change-id | status | date | notes |
10796
11933
  |---|---|---|---|
@@ -10814,15 +11951,15 @@ var list_changes_exports = {};
10814
11951
  __export(list_changes_exports, {
10815
11952
  listChanges: () => listChanges
10816
11953
  });
10817
- import { join as join25 } from "path";
10818
- import { existsSync as existsSync24, readdirSync as readdirSync13, readFileSync as readFileSync26 } from "fs";
11954
+ import { join as join28 } from "path";
11955
+ import { existsSync as existsSync27, readdirSync as readdirSync13, readFileSync as readFileSync29 } from "fs";
10819
11956
  import yaml6 from "js-yaml";
10820
11957
  async function listChanges() {
10821
11958
  const cwd = process.cwd();
10822
- const changesDir = join25(cwd, "specs", "changes");
11959
+ const changesDir = join28(cwd, "specs", "changes");
10823
11960
  log.blank();
10824
11961
  const active = [];
10825
- if (existsSync24(changesDir)) {
11962
+ if (existsSync27(changesDir)) {
10826
11963
  active.push(...readdirSync13(changesDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name));
10827
11964
  }
10828
11965
  if (active.length === 0) {
@@ -10830,12 +11967,12 @@ async function listChanges() {
10830
11967
  } else {
10831
11968
  log.info("Active changes:");
10832
11969
  for (const id of active) {
10833
- const tasksPath = join25(changesDir, id, "tasks.yml");
11970
+ const tasksPath = join28(changesDir, id, "tasks.yml");
10834
11971
  let status = "in-progress";
10835
11972
  let pending = 0;
10836
- if (existsSync24(tasksPath)) {
11973
+ if (existsSync27(tasksPath)) {
10837
11974
  try {
10838
- const raw = readFileSync26(tasksPath, "utf8");
11975
+ const raw = readFileSync29(tasksPath, "utf8");
10839
11976
  const data = yaml6.load(raw);
10840
11977
  if (data?.status)
10841
11978
  status = data.status;
@@ -10867,8 +12004,8 @@ __export(context_exports, {
10867
12004
  rejectContextExpansion: () => rejectContextExpansion,
10868
12005
  requestContextExpansion: () => requestContextExpansion
10869
12006
  });
10870
- import { existsSync as existsSync25, readFileSync as readFileSync27, writeFileSync as writeFileSync13 } from "fs";
10871
- import { join as join26 } from "path";
12007
+ import { existsSync as existsSync28, readFileSync as readFileSync30, writeFileSync as writeFileSync15 } from "fs";
12008
+ import { join as join29 } from "path";
10872
12009
  import picomatch2 from "picomatch";
10873
12010
  function normalizePath(path) {
10874
12011
  return path.replace(/\\/g, "/").replace(/^\.\//, "").trim();
@@ -10883,18 +12020,18 @@ function validateRepoRelativePath(path) {
10883
12020
  return null;
10884
12021
  }
10885
12022
  function manifestPathFor(changeId) {
10886
- return join26(process.cwd(), "specs", "changes", changeId, "context-manifest.md");
12023
+ return join29(process.cwd(), "specs", "changes", changeId, "context-manifest.md");
10887
12024
  }
10888
12025
  function readManifest(changeId) {
10889
12026
  const manifestPath = manifestPathFor(changeId);
10890
- if (!existsSync25(manifestPath)) {
12027
+ if (!existsSync28(manifestPath)) {
10891
12028
  log.error(`context manifest not found: specs/changes/${changeId}/context-manifest.md`);
10892
12029
  process.exit(1);
10893
12030
  }
10894
- return readFileSync27(manifestPath, "utf8");
12031
+ return readFileSync30(manifestPath, "utf8");
10895
12032
  }
10896
12033
  function writeManifest(changeId, content) {
10897
- writeFileSync13(manifestPathFor(changeId), content.endsWith("\n") ? content : `${content}
12034
+ writeFileSync15(manifestPathFor(changeId), content.endsWith("\n") ? content : `${content}
10898
12035
  `, "utf8");
10899
12036
  }
10900
12037
  function sectionBody(content, heading) {
@@ -10933,11 +12070,11 @@ function pathMatches(relPath, patterns, currentChangeId) {
10933
12070
  });
10934
12071
  }
10935
12072
  function loadContextPolicy() {
10936
- const policyPath = join26(process.cwd(), ".cdd", "context-policy.json");
10937
- if (!existsSync25(policyPath))
12073
+ const policyPath = join29(process.cwd(), ".cdd", "context-policy.json");
12074
+ if (!existsSync28(policyPath))
10938
12075
  return { forbiddenPaths: DEFAULT_FORBIDDEN_PATHS };
10939
12076
  try {
10940
- const custom = JSON.parse(readFileSync27(policyPath, "utf8"));
12077
+ const custom = JSON.parse(readFileSync30(policyPath, "utf8"));
10941
12078
  return {
10942
12079
  forbiddenPaths: Array.from(/* @__PURE__ */ new Set([
10943
12080
  ...DEFAULT_FORBIDDEN_PATHS,
@@ -11219,9 +12356,10 @@ var init_context = __esm({
11219
12356
  });
11220
12357
 
11221
12358
  // src/cli/index.ts
11222
- import { readFileSync as readFileSync28 } from "fs";
12359
+ import { readFileSync as readFileSync31 } from "fs";
12360
+ import os from "os";
11223
12361
  import { fileURLToPath as fileURLToPath3 } from "url";
11224
- import { dirname as dirname7, join as join27 } from "path";
12362
+ import { dirname as dirname7, join as join30 } from "path";
11225
12363
  import { Command } from "commander";
11226
12364
 
11227
12365
  // src/commands/init.ts
@@ -12327,7 +13465,7 @@ async function installHooks() {
12327
13465
 
12328
13466
  // src/cli/index.ts
12329
13467
  var __dirname2 = dirname7(fileURLToPath3(import.meta.url));
12330
- var pkg = JSON.parse(readFileSync28(join27(__dirname2, "..", "..", "package.json"), "utf8"));
13468
+ var pkg = JSON.parse(readFileSync31(join30(__dirname2, "..", "..", "package.json"), "utf8"));
12331
13469
  var program = new Command();
12332
13470
  program.name("cdd-kit").description("Contract-Driven Delivery Kit CLI").version(pkg.version);
12333
13471
  program.command("init").description(
@@ -12388,11 +13526,24 @@ function collectRepeatable(val, acc) {
12388
13526
  acc.push(val);
12389
13527
  return acc;
12390
13528
  }
12391
- program.command("code-map [path]").description("Scan source files and emit a structural index at .cdd/code-map.yml").option("--out <path>", "Output YAML path", ".cdd/code-map.yml").option("--include <glob>", "Additional include glob (repeatable)", collectRepeatable, []).option("--exclude <glob>", "Additional exclude glob (repeatable)", collectRepeatable, []).option("--check", "Exit 1 if regenerating would change the file (no write)", false).option("--max-lines <n>", "Warn for files exceeding this line count (default 100000)", "100000").action(async (path, opts) => {
13529
+ function resolveWorkers(value) {
13530
+ if (value === void 0)
13531
+ return 0;
13532
+ const cpus = Math.max(1, (os.cpus()?.length ?? 2) - 1);
13533
+ if (value === true)
13534
+ return Math.min(cpus, 16);
13535
+ const n = parseInt(String(value), 10);
13536
+ if (!Number.isFinite(n) || n < 1)
13537
+ return 1;
13538
+ return Math.min(n, 16);
13539
+ }
13540
+ program.command("code-map [path]").description("Scan source files and emit a structural index at .cdd/code-map.yml").option("--out <path>", "Output YAML path (default .cdd/code-map.yml; with --surface, .cdd/code-map.<surface>.yml)").option("--surface <subpath>", "Scope the scan to a monorepo subtree and name the map after it").option("--workers [n]", "Parallelize JS/TS/Vue scanning across N child processes (default: CPU count - 1)").option("--include <glob>", "Additional include glob (repeatable)", collectRepeatable, []).option("--exclude <glob>", "Additional exclude glob (repeatable)", collectRepeatable, []).option("--check", "Exit 1 if regenerating would change the file (no write)", false).option("--max-lines <n>", "Warn for files exceeding this line count (default 100000)", "100000").action(async (path, opts) => {
12392
13541
  const { codeMap: codeMap2 } = await Promise.resolve().then(() => (init_code_map(), code_map_exports));
12393
13542
  const exit = await codeMap2({
12394
13543
  path: path ?? ".",
12395
13544
  out: opts.out,
13545
+ surface: opts.surface,
13546
+ workers: resolveWorkers(opts.workers),
12396
13547
  include: opts.include,
12397
13548
  exclude: opts.exclude,
12398
13549
  check: opts.check,
@@ -12400,6 +13551,11 @@ program.command("code-map [path]").description("Scan source files and emit a str
12400
13551
  });
12401
13552
  process.exit(exit);
12402
13553
  });
13554
+ program.command("__code-map-scan", { hidden: true }).requiredOption("--lang <lang>", "js | ts | vue").requiredOption("--batch-file <path>", "File listing absolute source paths, one per line").requiredOption("--repo-root <path>", "Repo root the scanned paths are relative to").action(async (opts) => {
13555
+ const { runScanWorker: runScanWorker2 } = await Promise.resolve().then(() => (init_code_map_scan_worker(), code_map_scan_worker_exports));
13556
+ const exit = await runScanWorker2({ lang: opts.lang, batchFile: opts.batchFile, repoRoot: opts.repoRoot });
13557
+ process.exit(exit);
13558
+ });
12403
13559
  var index = program.command("index").description("Query machine-readable project indexes before opening source files");
12404
13560
  index.command("query <term>").description("Search .cdd/code-map.yml for files, symbols, imports, and line ranges").option("--map <path>", "Code-map YAML path", ".cdd/code-map.yml").option("--limit <n>", "Maximum result files to print", "10").option("--json", "Print machine-readable JSON", false).option("--no-refresh", "Do not auto-regenerate stale or missing code-map before querying").action(async (term, opts) => {
12405
13561
  const { indexQuery: indexQuery2 } = await Promise.resolve().then(() => (init_index_query(), index_query_exports));
@@ -12421,6 +13577,51 @@ index.command("impact <path-or-symbol>").description("Show indexed local imports
12421
13577
  });
12422
13578
  process.exit(exit);
12423
13579
  });
13580
+ var graph = program.command("graph").description("Query native cdd-kit code graph context with optional CodeGraph adapter and code-map fallback");
13581
+ graph.command("status [path]").description("Show active graph engine and index health").option("--engine <engine>", "Graph engine: auto, native, codegraph, or codemap", "auto").option("--map <path>", "Code-map YAML path for fallback status", ".cdd/code-map.yml").option("--json", "Print machine-readable JSON", false).action(async (path, opts) => {
13582
+ const { graphStatus: graphStatus2 } = await Promise.resolve().then(() => (init_graph(), graph_exports));
13583
+ const exit = await graphStatus2({ path, engine: opts.engine, map: opts.map, json: opts.json === true });
13584
+ process.exit(exit);
13585
+ });
13586
+ graph.command("sync [path]").description("Run CodeGraph incremental sync (requires CodeGraph)").option("--engine <engine>", "Graph engine: codegraph", "codegraph").option("--json", "Print machine-readable JSON on errors", false).action(async (path, opts) => {
13587
+ const { graphSync: graphSync2 } = await Promise.resolve().then(() => (init_graph(), graph_exports));
13588
+ const exit = await graphSync2({ path, engine: opts.engine, json: opts.json === true });
13589
+ process.exit(exit);
13590
+ });
13591
+ graph.command("query <term>").description("Search native graph symbols, optionally delegating to CodeGraph or code-map").option("--engine <engine>", "Graph engine: auto, native, codegraph, or codemap", "auto").option("--map <path>", "Code-map YAML path for fallback", ".cdd/code-map.yml").option("--limit <n>", "Maximum results to print", "10").option("--json", "Print machine-readable JSON", false).option("--no-refresh", "Do not auto-regenerate stale or missing fallback code-map").action(async (term, opts) => {
13592
+ const { graphQuery: graphQuery2 } = await Promise.resolve().then(() => (init_graph(), graph_exports));
13593
+ const exit = await graphQuery2(term, {
13594
+ engine: opts.engine,
13595
+ map: opts.map,
13596
+ limit: parseInt(opts.limit, 10),
13597
+ json: opts.json === true,
13598
+ refresh: opts.refresh !== false
13599
+ });
13600
+ process.exit(exit);
13601
+ });
13602
+ graph.command("impact <path-or-symbol>").description("Analyze impact radius with native graph calls/imports, CodeGraph, or code-map fallback").option("--engine <engine>", "Graph engine: auto, native, codegraph, or codemap", "auto").option("--map <path>", "Code-map YAML path for fallback", ".cdd/code-map.yml").option("--limit <n>", "Maximum fallback dependent files to print", "20").option("--depth <n>", "CodeGraph traversal depth (fallback is direct only)", "2").option("--json", "Print machine-readable JSON", false).option("--no-refresh", "Do not auto-regenerate stale or missing fallback code-map").action(async (term, opts) => {
13603
+ const { graphImpact: graphImpact3 } = await Promise.resolve().then(() => (init_graph(), graph_exports));
13604
+ const exit = await graphImpact3(term, {
13605
+ engine: opts.engine,
13606
+ map: opts.map,
13607
+ limit: parseInt(opts.limit, 10),
13608
+ depth: parseInt(opts.depth, 10),
13609
+ json: opts.json === true,
13610
+ refresh: opts.refresh !== false
13611
+ });
13612
+ process.exit(exit);
13613
+ });
13614
+ graph.command("context <task>").description("Build task context with native graph, CodeGraph, or code-map candidates").option("--engine <engine>", "Graph engine: auto, native, codegraph, or codemap", "auto").option("--map <path>", "Code-map YAML path for fallback", ".cdd/code-map.yml").option("--max-nodes <n>", "Maximum context candidates/nodes", "20").option("--json", "Print machine-readable JSON", false).option("--no-refresh", "Do not auto-regenerate stale or missing fallback code-map").action(async (task, opts) => {
13615
+ const { graphContext: graphContext3 } = await Promise.resolve().then(() => (init_graph(), graph_exports));
13616
+ const exit = await graphContext3(task, {
13617
+ engine: opts.engine,
13618
+ map: opts.map,
13619
+ maxNodes: parseInt(opts.maxNodes, 10),
13620
+ json: opts.json === true,
13621
+ refresh: opts.refresh !== false
13622
+ });
13623
+ process.exit(exit);
13624
+ });
12424
13625
  program.command("gate <change-id>").description("Run delivery-quality gate for a change (required artifacts, tasks, tier, contracts)").option("--strict", "Treat pending tasks (except section 7) as errors", false).action(async (id, opts) => {
12425
13626
  await gate(id, { strict: opts.strict });
12426
13627
  });