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/CHANGELOG.md +87 -0
- package/README.md +62 -0
- package/assets/agents/bug-fix-engineer.md +97 -0
- package/assets/agents/change-classifier.md +1 -1
- package/assets/agents/frontend-engineer.md +4 -4
- package/assets/agents/qa-reviewer.md +1 -1
- package/assets/cdd/model-policy.json +1 -0
- package/assets/code-map/python_scanner.py +39 -0
- package/assets/skills/cdd-new/SKILL.md +31 -6
- package/assets/skills/cdd-resume/SKILL.md +1 -1
- package/assets/skills/contract-driven-delivery/references/code-map-protocol.md +15 -2
- package/dist/cli/index.js +1947 -746
- package/package.json +3 -2
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
|
|
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
|
-
|
|
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
|
-
|
|
2800
|
+
traverse2.arrayKeywords = {
|
|
2801
2801
|
items: true,
|
|
2802
2802
|
allOf: true,
|
|
2803
2803
|
anyOf: true,
|
|
2804
2804
|
oneOf: true
|
|
2805
2805
|
};
|
|
2806
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
7508
|
-
import { cpSync as cpSync2, existsSync as
|
|
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 =
|
|
7512
|
-
const backupDir2 =
|
|
7541
|
+
const backupRoot = join14(cwd, ".cdd", "migrate-backup", sessionStamp);
|
|
7542
|
+
const backupDir2 = join14(backupRoot, changeId);
|
|
7513
7543
|
mkdirSync6(backupRoot, { recursive: true });
|
|
7514
|
-
const sourceDir =
|
|
7515
|
-
if (
|
|
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 =
|
|
7649
|
-
const legacyPath =
|
|
7650
|
-
if (
|
|
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 (!
|
|
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 =
|
|
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 =
|
|
7742
|
-
if (!
|
|
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 =
|
|
7776
|
+
const fullPath = join14(agentLogDir, f);
|
|
7747
7777
|
const yamlName = f.replace(/\.md$/, ".yml");
|
|
7748
|
-
const yamlFull =
|
|
7749
|
-
if (
|
|
7778
|
+
const yamlFull = join14(agentLogDir, yamlName);
|
|
7779
|
+
if (existsSync13(yamlFull))
|
|
7750
7780
|
continue;
|
|
7751
|
-
const raw =
|
|
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 =
|
|
7761
|
-
if (
|
|
7790
|
+
const planPath = join14(changeDir, "implementation-plan.md");
|
|
7791
|
+
if (existsSync13(planPath))
|
|
7762
7792
|
return;
|
|
7763
|
-
const templatePath =
|
|
7764
|
-
if (!
|
|
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 =
|
|
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 =
|
|
7780
|
-
if (
|
|
7781
|
-
const content =
|
|
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 =
|
|
7813
|
-
if (!
|
|
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
|
-
|
|
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 =
|
|
7859
|
-
if (!
|
|
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 =
|
|
7868
|
-
if (!
|
|
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 =
|
|
7918
|
+
const backupRoot = join14(cwd, ".cdd", "migrate-backup", sessionStamp);
|
|
7889
7919
|
for (const id of idsToMigrate) {
|
|
7890
|
-
const changeDir =
|
|
7891
|
-
if (!
|
|
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
|
|
7974
|
-
import { dirname as dirname4, join as
|
|
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 (!
|
|
7986
|
+
if (!existsSync14(srcDir))
|
|
7977
7987
|
return;
|
|
7978
7988
|
for (const entry of readdirSync8(srcDir, { withFileTypes: true })) {
|
|
7979
|
-
const src =
|
|
7980
|
-
const dest =
|
|
7989
|
+
const src = join15(srcDir, entry.name);
|
|
7990
|
+
const dest = join15(destDir, entry.name);
|
|
7981
7991
|
if (entry.isDirectory()) {
|
|
7982
|
-
planMissingFiles(src, dest,
|
|
7992
|
+
planMissingFiles(src, dest, join15(label, entry.name), planned);
|
|
7983
7993
|
continue;
|
|
7984
7994
|
}
|
|
7985
|
-
if (!
|
|
7986
|
-
planned.push({ src, dest, rel:
|
|
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 (!
|
|
7993
|
-
planned.push({ src: ASSET.claudeTemplate, dest:
|
|
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 (!
|
|
7996
|
-
planned.push({ src: ASSET.agentsTemplate, dest:
|
|
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") && !
|
|
8000
|
-
planned.push({ src: ASSET.codexTemplate, dest:
|
|
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,
|
|
8019
|
-
planMissingFiles(ASSET.specsTemplates,
|
|
8020
|
-
planMissingFiles(ASSET.testsTemplates,
|
|
8021
|
-
planMissingFiles(ASSET.ci,
|
|
8022
|
-
planMissingFiles(ASSET.githubWorkflows,
|
|
8023
|
-
planMissingFiles(ASSET.cddConfig,
|
|
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 =
|
|
8061
|
-
if (
|
|
8070
|
+
const modelPolicyPath = join15(cwd, ".cdd", "model-policy.json");
|
|
8071
|
+
if (existsSync14(modelPolicyPath)) {
|
|
8062
8072
|
let existing = {};
|
|
8063
8073
|
try {
|
|
8064
|
-
existing = JSON.parse(
|
|
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
|
-
|
|
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
|
|
8234
|
-
import { join as
|
|
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 =
|
|
8251
|
-
if (!
|
|
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 =
|
|
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
|
|
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 =
|
|
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/
|
|
8422
|
-
import {
|
|
8423
|
-
|
|
8424
|
-
|
|
8425
|
-
|
|
8426
|
-
|
|
8427
|
-
|
|
8428
|
-
|
|
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
|
|
8431
|
-
|
|
8432
|
-
|
|
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
|
|
8435
|
-
"src/code-map/
|
|
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/
|
|
8441
|
-
|
|
8442
|
-
|
|
8443
|
-
|
|
8444
|
-
|
|
8445
|
-
|
|
8446
|
-
|
|
8447
|
-
|
|
8448
|
-
|
|
8449
|
-
|
|
8450
|
-
|
|
8451
|
-
|
|
8452
|
-
|
|
8453
|
-
|
|
8454
|
-
|
|
8455
|
-
|
|
8456
|
-
|
|
8457
|
-
|
|
8458
|
-
|
|
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
|
-
|
|
8464
|
-
|
|
8465
|
-
|
|
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
|
-
|
|
8468
|
-
|
|
8469
|
-
|
|
8470
|
-
|
|
8471
|
-
|
|
8472
|
-
|
|
8473
|
-
|
|
8474
|
-
|
|
8475
|
-
|
|
8476
|
-
|
|
8477
|
-
|
|
8478
|
-
|
|
8479
|
-
|
|
8480
|
-
|
|
8481
|
-
|
|
8482
|
-
|
|
8483
|
-
|
|
8484
|
-
|
|
8485
|
-
|
|
8486
|
-
|
|
8487
|
-
|
|
8488
|
-
|
|
8489
|
-
|
|
8490
|
-
|
|
8491
|
-
|
|
8492
|
-
|
|
8493
|
-
|
|
8494
|
-
|
|
8495
|
-
|
|
8496
|
-
|
|
8497
|
-
|
|
8498
|
-
|
|
8499
|
-
|
|
8500
|
-
|
|
8501
|
-
|
|
8502
|
-
|
|
8503
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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(
|
|
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
|
|
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
|
-
|
|
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 =
|
|
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
|
|
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 =
|
|
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
|
|
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 =
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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(
|
|
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(
|
|
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(
|
|
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 -> ${
|
|
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 =
|
|
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: ${
|
|
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: ${
|
|
9948
|
+
log.ok(`code-map up to date: ${out}`);
|
|
9175
9949
|
return 0;
|
|
9176
9950
|
}
|
|
9177
|
-
mkdirSync8(dirname5(
|
|
9178
|
-
|
|
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 =
|
|
9194
|
-
_pkg = JSON.parse(
|
|
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
|
|
9204
|
-
import { dirname as dirname6, join as
|
|
9205
|
-
import { createHash as
|
|
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
|
|
10004
|
+
return createHash7("sha256").update(readFileSync23(filePath)).digest("hex");
|
|
9208
10005
|
}
|
|
9209
10006
|
function planForceRefresh(srcDir, destDir, sectionLabel) {
|
|
9210
10007
|
const plan = [];
|
|
9211
|
-
if (!
|
|
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 =
|
|
9222
|
-
const dp =
|
|
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 =
|
|
9230
|
-
if (!
|
|
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 (!
|
|
10040
|
+
if (!existsSync20(src))
|
|
9244
10041
|
return null;
|
|
9245
|
-
if (!
|
|
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 =
|
|
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 =
|
|
10084
|
+
const policyPath = join22(cwd, ".cdd", "model-policy.json");
|
|
9309
10085
|
const result = { changed: false, diff: [], policyPath };
|
|
9310
|
-
if (!
|
|
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 =
|
|
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 (
|
|
10099
|
+
if (existsSync20(policyPath)) {
|
|
9324
10100
|
try {
|
|
9325
|
-
existing = JSON.parse(
|
|
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
|
-
|
|
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,
|
|
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,
|
|
10134
|
+
plan: planForceRefresh(ASSET.testsTemplates, join22(cwd, "tests", "templates"), "tests/templates")
|
|
9359
10135
|
});
|
|
9360
|
-
const ciTemplatesAsset =
|
|
9361
|
-
if (
|
|
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,
|
|
10140
|
+
plan: planForceRefresh(ciTemplatesAsset, join22(cwd, "ci-templates"), "ci-templates")
|
|
9365
10141
|
});
|
|
9366
10142
|
}
|
|
9367
|
-
const wfAsset =
|
|
9368
|
-
const wfDest =
|
|
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 =
|
|
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 (
|
|
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 =
|
|
9444
|
-
if (
|
|
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/
|
|
9530
|
-
|
|
9531
|
-
|
|
9532
|
-
|
|
9533
|
-
|
|
9534
|
-
|
|
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
|
-
|
|
9537
|
-
} catch
|
|
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
|
-
|
|
9547
|
-
|
|
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
|
|
9556
|
-
|
|
9557
|
-
|
|
9558
|
-
|
|
9559
|
-
|
|
9560
|
-
|
|
9561
|
-
|
|
9562
|
-
|
|
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
|
-
}
|
|
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
|
-
|
|
9568
|
-
|
|
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
|
-
|
|
9571
|
-
|
|
9572
|
-
|
|
9573
|
-
|
|
9574
|
-
|
|
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
|
-
|
|
9578
|
-
|
|
9579
|
-
|
|
9580
|
-
|
|
9581
|
-
|
|
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
|
|
10442
|
+
function collectAgentViolations(cwd, opts = {}) {
|
|
10443
|
+
const agentsDir = join23(cwd, ".claude", "agents");
|
|
10444
|
+
let files;
|
|
9585
10445
|
try {
|
|
9586
|
-
|
|
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
|
-
|
|
9594
|
-
|
|
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
|
-
|
|
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
|
|
9608
|
-
import { createHash as
|
|
9609
|
-
import { join as
|
|
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
|
|
10507
|
+
return existsSync21(join24(cwd, relPath));
|
|
9612
10508
|
}
|
|
9613
10509
|
function findFiles(dir, predicate, found = []) {
|
|
9614
|
-
if (!
|
|
10510
|
+
if (!existsSync21(dir))
|
|
9615
10511
|
return found;
|
|
9616
|
-
for (const entry of
|
|
9617
|
-
const fullPath =
|
|
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
|
|
10526
|
+
return createHash8("sha256").update(combined).digest("hex");
|
|
9631
10527
|
}
|
|
9632
10528
|
function readContextIndexMetadata(filePath) {
|
|
9633
|
-
if (!
|
|
10529
|
+
if (!existsSync21(filePath))
|
|
9634
10530
|
return {};
|
|
9635
|
-
const text =
|
|
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 =
|
|
9648
|
-
const contractsIndex =
|
|
9649
|
-
const contextPolicy =
|
|
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
|
-
|
|
10547
|
+
join24(cwd, "contracts"),
|
|
9652
10548
|
(name) => name.endsWith(".md") && name !== "INDEX.md" && name !== "CHANGELOG.md"
|
|
9653
10549
|
);
|
|
9654
|
-
if (!
|
|
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(
|
|
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 =
|
|
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 =
|
|
9709
|
-
if (!
|
|
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(
|
|
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
|
-
|
|
9726
|
-
process.env.HOME ?
|
|
9727
|
-
process.env.USERPROFILE ?
|
|
9728
|
-
].filter((p) => 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 =
|
|
9736
|
-
if (!
|
|
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
|
-
|
|
9756
|
-
const agentsDir =
|
|
9757
|
-
if (!
|
|
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 =
|
|
9795
|
-
if (!
|
|
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 =
|
|
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(...
|
|
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 =
|
|
10769
|
+
const policyPath = join24(cwd, ".cdd", "model-policy.json");
|
|
9891
10770
|
try {
|
|
9892
10771
|
let existing = {};
|
|
9893
10772
|
try {
|
|
9894
|
-
existing = JSON.parse(
|
|
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:
|
|
9919
|
-
|
|
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
|
-
|
|
10228
|
-
|
|
10229
|
-
|
|
10230
|
-
|
|
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
|
-
|
|
10238
|
-
}
|
|
10239
|
-
|
|
10240
|
-
|
|
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
|
-
|
|
10859
|
+
log.blank();
|
|
10244
10860
|
}
|
|
10245
|
-
var
|
|
10246
|
-
"src/
|
|
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
|
|
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 (!
|
|
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
|
|
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 (!
|
|
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 &&
|
|
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()) ||
|
|
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 =
|
|
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
|
|
10542
|
-
if (!
|
|
11204
|
+
function resolveLocalModule2(importerPath, moduleName, pathSet) {
|
|
11205
|
+
if (!isLocalImport2(moduleName))
|
|
10543
11206
|
return void 0;
|
|
10544
|
-
const base = moduleName.startsWith("./") || moduleName.startsWith("../") ?
|
|
10545
|
-
for (const candidate of
|
|
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
|
|
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 =
|
|
11219
|
+
let baseDir = posix2.dirname(importerPath);
|
|
10557
11220
|
for (let i = 0; i < upLevels; i++) {
|
|
10558
|
-
baseDir =
|
|
11221
|
+
baseDir = posix2.dirname(baseDir);
|
|
10559
11222
|
}
|
|
10560
11223
|
const rest = match[2].replace(/^\./, "").replace(/\./g, "/");
|
|
10561
|
-
return rest ?
|
|
11224
|
+
return rest ? posix2.normalize(posix2.join(baseDir, rest)) : baseDir;
|
|
10562
11225
|
}
|
|
10563
|
-
function
|
|
10564
|
-
const ext =
|
|
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
|
|
11236
|
+
for (const candidateExt of RESOLUTION_EXTENSIONS2) {
|
|
10574
11237
|
candidates.push(`${base}${candidateExt}`);
|
|
10575
11238
|
}
|
|
10576
11239
|
}
|
|
10577
|
-
for (const candidateExt of
|
|
10578
|
-
candidates.push(
|
|
11240
|
+
for (const candidateExt of RESOLUTION_EXTENSIONS2) {
|
|
11241
|
+
candidates.push(posix2.join(base, `index${candidateExt}`));
|
|
10579
11242
|
}
|
|
10580
|
-
candidates.push(
|
|
11243
|
+
candidates.push(posix2.join(base, "__init__.py"));
|
|
10581
11244
|
return [...new Set(candidates)];
|
|
10582
11245
|
}
|
|
10583
|
-
function
|
|
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
|
|
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
|
-
|
|
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
|
|
10688
|
-
import { existsSync as
|
|
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 =
|
|
11829
|
+
const changeDir = join26(cwd, "specs", "changes", changeId);
|
|
10693
11830
|
const archiveYear = (/* @__PURE__ */ new Date()).getFullYear().toString();
|
|
10694
|
-
const archiveBase =
|
|
10695
|
-
const archiveDir =
|
|
10696
|
-
const indexPath =
|
|
10697
|
-
if (!
|
|
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 (
|
|
11838
|
+
if (existsSync25(archiveDir)) {
|
|
10702
11839
|
log.error(`Already archived: specs/archive/${archiveYear}/${changeId}`);
|
|
10703
11840
|
process.exit(1);
|
|
10704
11841
|
}
|
|
10705
|
-
const tasksPath =
|
|
10706
|
-
if (
|
|
11842
|
+
const tasksPath = join26(changeDir, "tasks.yml");
|
|
11843
|
+
if (existsSync25(tasksPath)) {
|
|
10707
11844
|
try {
|
|
10708
|
-
const raw =
|
|
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 (!
|
|
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 (!
|
|
10739
|
-
|
|
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
|
|
10764
|
-
import { existsSync as
|
|
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 =
|
|
10769
|
-
const tasksPath =
|
|
10770
|
-
if (!
|
|
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 (
|
|
10775
|
-
const raw =
|
|
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
|
-
|
|
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 =
|
|
10785
|
-
const indexPath =
|
|
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 (!
|
|
11926
|
+
if (!existsSync26(archiveDir)) {
|
|
10790
11927
|
mkdirSync11(archiveDir, { recursive: true });
|
|
10791
11928
|
}
|
|
10792
|
-
if (!
|
|
10793
|
-
|
|
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
|
|
10818
|
-
import { existsSync as
|
|
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 =
|
|
11959
|
+
const changesDir = join28(cwd, "specs", "changes");
|
|
10823
11960
|
log.blank();
|
|
10824
11961
|
const active = [];
|
|
10825
|
-
if (
|
|
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 =
|
|
11970
|
+
const tasksPath = join28(changesDir, id, "tasks.yml");
|
|
10834
11971
|
let status = "in-progress";
|
|
10835
11972
|
let pending = 0;
|
|
10836
|
-
if (
|
|
11973
|
+
if (existsSync27(tasksPath)) {
|
|
10837
11974
|
try {
|
|
10838
|
-
const raw =
|
|
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
|
|
10871
|
-
import { join as
|
|
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
|
|
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 (!
|
|
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
|
|
12031
|
+
return readFileSync30(manifestPath, "utf8");
|
|
10895
12032
|
}
|
|
10896
12033
|
function writeManifest(changeId, content) {
|
|
10897
|
-
|
|
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 =
|
|
10937
|
-
if (!
|
|
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(
|
|
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
|
|
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
|
|
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(
|
|
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
|
-
|
|
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
|
});
|