contract-driven-delivery 2.0.20 → 2.0.21
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 +56 -0
- package/README.md +42 -0
- package/assets/skills/cdd-new/SKILL.md +27 -5
- package/assets/skills/cdd-resume/SKILL.md +1 -1
- package/dist/cli/index.js +795 -591
- package/package.json +3 -2
package/dist/cli/index.js
CHANGED
|
@@ -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,6 +8428,260 @@ var init_orchestrator = __esm({
|
|
|
8418
8428
|
}
|
|
8419
8429
|
});
|
|
8420
8430
|
|
|
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
|
+
};
|
|
8485
|
+
}
|
|
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
|
+
}
|
|
8494
|
+
}
|
|
8495
|
+
var init_freshness = __esm({
|
|
8496
|
+
"src/code-map/freshness.ts"() {
|
|
8497
|
+
"use strict";
|
|
8498
|
+
init_include_exclude();
|
|
8499
|
+
init_config();
|
|
8500
|
+
init_code_map();
|
|
8501
|
+
}
|
|
8502
|
+
});
|
|
8503
|
+
|
|
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;
|
|
8552
|
+
}
|
|
8553
|
+
} catch {
|
|
8554
|
+
}
|
|
8555
|
+
return null;
|
|
8556
|
+
}
|
|
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"() {
|
|
8606
|
+
"use strict";
|
|
8607
|
+
init_freshness();
|
|
8608
|
+
}
|
|
8609
|
+
});
|
|
8610
|
+
|
|
8611
|
+
// src/code-map/worker-dispatch.ts
|
|
8612
|
+
var worker_dispatch_exports = {};
|
|
8613
|
+
__export(worker_dispatch_exports, {
|
|
8614
|
+
scanLangWithWorkers: () => scanLangWithWorkers
|
|
8615
|
+
});
|
|
8616
|
+
import { execFile } from "child_process";
|
|
8617
|
+
import { writeFileSync as writeFileSync9, unlinkSync } from "fs";
|
|
8618
|
+
import { randomBytes } from "crypto";
|
|
8619
|
+
import { join as join19 } from "path";
|
|
8620
|
+
import { tmpdir } from "os";
|
|
8621
|
+
function chunk(arr, parts) {
|
|
8622
|
+
const size = Math.ceil(arr.length / parts);
|
|
8623
|
+
const out = [];
|
|
8624
|
+
for (let i = 0; i < arr.length; i += size)
|
|
8625
|
+
out.push(arr.slice(i, i + size));
|
|
8626
|
+
return out;
|
|
8627
|
+
}
|
|
8628
|
+
function scanChunkInChild(cliEntry, lang, files, repoRoot) {
|
|
8629
|
+
return new Promise((resolve2, reject) => {
|
|
8630
|
+
if (!ALLOWED_LANGS.has(lang)) {
|
|
8631
|
+
return reject(new Error(`refusing to spawn worker for unknown lang: ${lang}`));
|
|
8632
|
+
}
|
|
8633
|
+
const listFile = join19(
|
|
8634
|
+
tmpdir(),
|
|
8635
|
+
`cdd-cm-worker-${process.pid}-${randomBytes(12).toString("hex")}.txt`
|
|
8636
|
+
);
|
|
8637
|
+
writeFileSync9(listFile, files.join("\n") + "\n", { encoding: "utf8", mode: 384 });
|
|
8638
|
+
execFile(
|
|
8639
|
+
process.execPath,
|
|
8640
|
+
[cliEntry, "__code-map-scan", "--lang", lang, "--batch-file", listFile, "--repo-root", repoRoot],
|
|
8641
|
+
{ encoding: "utf8", timeout: CHILD_TIMEOUT_MS, maxBuffer: 100 * 1024 * 1024, shell: false, windowsHide: true },
|
|
8642
|
+
(err, stdout) => {
|
|
8643
|
+
try {
|
|
8644
|
+
unlinkSync(listFile);
|
|
8645
|
+
} catch {
|
|
8646
|
+
}
|
|
8647
|
+
if (err)
|
|
8648
|
+
return reject(err);
|
|
8649
|
+
try {
|
|
8650
|
+
const parsed = JSON.parse(stdout);
|
|
8651
|
+
resolve2({ entries: parsed.entries ?? [], warnings: parsed.warnings ?? [] });
|
|
8652
|
+
} catch (e) {
|
|
8653
|
+
reject(e);
|
|
8654
|
+
}
|
|
8655
|
+
}
|
|
8656
|
+
);
|
|
8657
|
+
});
|
|
8658
|
+
}
|
|
8659
|
+
async function scanLangWithWorkers(scanner, lang, files, repoRoot, workers, cliEntry) {
|
|
8660
|
+
if (files.length === 0)
|
|
8661
|
+
return { entries: [], warnings: [] };
|
|
8662
|
+
const chunks = chunk(files, Math.max(1, Math.min(workers, files.length)));
|
|
8663
|
+
try {
|
|
8664
|
+
const results = await Promise.all(
|
|
8665
|
+
chunks.map((c) => scanChunkInChild(cliEntry, lang, c, repoRoot))
|
|
8666
|
+
);
|
|
8667
|
+
return {
|
|
8668
|
+
entries: results.flatMap((r) => r.entries),
|
|
8669
|
+
warnings: results.flatMap((r) => r.warnings)
|
|
8670
|
+
};
|
|
8671
|
+
} catch {
|
|
8672
|
+
return scanInProcess(scanner, files, repoRoot);
|
|
8673
|
+
}
|
|
8674
|
+
}
|
|
8675
|
+
var CHILD_TIMEOUT_MS, ALLOWED_LANGS;
|
|
8676
|
+
var init_worker_dispatch = __esm({
|
|
8677
|
+
"src/code-map/worker-dispatch.ts"() {
|
|
8678
|
+
"use strict";
|
|
8679
|
+
init_orchestrator();
|
|
8680
|
+
CHILD_TIMEOUT_MS = parseInt(process.env["CDD_CODE_MAP_WORKER_TIMEOUT_MS"] ?? "120000", 10);
|
|
8681
|
+
ALLOWED_LANGS = /* @__PURE__ */ new Set(["js", "ts", "vue"]);
|
|
8682
|
+
}
|
|
8683
|
+
});
|
|
8684
|
+
|
|
8421
8685
|
// src/code-map/scanners/common.ts
|
|
8422
8686
|
import { relative as relative5 } from "path";
|
|
8423
8687
|
function canonicalRelPath(absolutePath, repoRoot) {
|
|
@@ -8443,9 +8707,10 @@ __export(python_exports, {
|
|
|
8443
8707
|
pythonScanner: () => pythonScanner
|
|
8444
8708
|
});
|
|
8445
8709
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
8446
|
-
import { writeFileSync as
|
|
8447
|
-
import {
|
|
8448
|
-
import {
|
|
8710
|
+
import { writeFileSync as writeFileSync10, unlinkSync as unlinkSync2 } from "fs";
|
|
8711
|
+
import { randomBytes as randomBytes2 } from "crypto";
|
|
8712
|
+
import { join as join20 } from "path";
|
|
8713
|
+
import { tmpdir as tmpdir2 } from "os";
|
|
8449
8714
|
function detectPython2() {
|
|
8450
8715
|
for (const candidate of ["python3", "python"]) {
|
|
8451
8716
|
try {
|
|
@@ -8460,13 +8725,17 @@ function detectPython2() {
|
|
|
8460
8725
|
}
|
|
8461
8726
|
return null;
|
|
8462
8727
|
}
|
|
8463
|
-
var TIMEOUT_MS, PythonScanner, pythonScanner;
|
|
8728
|
+
var TIMEOUT_MS, BATCH_SIZE, PythonScanner, pythonScanner;
|
|
8464
8729
|
var init_python = __esm({
|
|
8465
8730
|
"src/code-map/scanners/python.ts"() {
|
|
8466
8731
|
"use strict";
|
|
8467
8732
|
init_paths();
|
|
8468
8733
|
init_common();
|
|
8469
8734
|
TIMEOUT_MS = parseInt(process.env["CDD_CODE_MAP_TIMEOUT_MS"] ?? "30000", 10);
|
|
8735
|
+
BATCH_SIZE = Math.max(
|
|
8736
|
+
1,
|
|
8737
|
+
parseInt(process.env["CDD_CODE_MAP_BATCH_SIZE"] ?? "400", 10) || 400
|
|
8738
|
+
);
|
|
8470
8739
|
PythonScanner = class {
|
|
8471
8740
|
extensions = [".py"];
|
|
8472
8741
|
_interpreter = void 0;
|
|
@@ -8492,10 +8761,30 @@ var init_python = __esm({
|
|
|
8492
8761
|
});
|
|
8493
8762
|
return { entries, warnings };
|
|
8494
8763
|
}
|
|
8764
|
+
for (let i = 0; i < absolutePaths.length; i += BATCH_SIZE) {
|
|
8765
|
+
const chunk2 = absolutePaths.slice(i, i + BATCH_SIZE);
|
|
8766
|
+
const result = this.scanChunk(interpreter, chunk2, repoRoot);
|
|
8767
|
+
entries.push(...result.entries);
|
|
8768
|
+
warnings.push(...result.warnings);
|
|
8769
|
+
if (result.abortRemaining) {
|
|
8770
|
+
const remaining = absolutePaths.length - (i + chunk2.length);
|
|
8771
|
+
if (remaining > 0) {
|
|
8772
|
+
warnings.push({
|
|
8773
|
+
path: "",
|
|
8774
|
+
message: `skipping ${remaining} more .py file(s) after fatal interpreter error`
|
|
8775
|
+
});
|
|
8776
|
+
}
|
|
8777
|
+
break;
|
|
8778
|
+
}
|
|
8779
|
+
}
|
|
8780
|
+
return { entries, warnings };
|
|
8781
|
+
}
|
|
8782
|
+
scanChunk(interpreter, absolutePaths, repoRoot) {
|
|
8783
|
+
const entries = [];
|
|
8784
|
+
const warnings = [];
|
|
8495
8785
|
const scriptPath = ASSET.codeMapPython;
|
|
8496
|
-
const
|
|
8497
|
-
|
|
8498
|
-
writeFileSync8(listFile, absolutePaths.join("\n") + "\n", "utf8");
|
|
8786
|
+
const listFile = join20(tmpdir2(), `cdd-codemap-${process.pid}-${randomBytes2(12).toString("hex")}.txt`);
|
|
8787
|
+
writeFileSync10(listFile, absolutePaths.join("\n") + "\n", { encoding: "utf8", mode: 384 });
|
|
8499
8788
|
let stdout = "";
|
|
8500
8789
|
let stderr = "";
|
|
8501
8790
|
let exitCode = 0;
|
|
@@ -8520,24 +8809,24 @@ var init_python = __esm({
|
|
|
8520
8809
|
path: "",
|
|
8521
8810
|
message: `python interpreter not found (ENOENT); skipping ${absolutePaths.length} .py file(s)`
|
|
8522
8811
|
});
|
|
8523
|
-
return { entries, warnings };
|
|
8812
|
+
return { entries, warnings, abortRemaining: true };
|
|
8524
8813
|
}
|
|
8525
8814
|
if (errMsg.includes("ETIMEDOUT") || errMsg.includes("timeout")) {
|
|
8526
8815
|
warnings.push({
|
|
8527
8816
|
path: "",
|
|
8528
|
-
message: `python scanner timed out after ${TIMEOUT_MS}ms; skipping
|
|
8817
|
+
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
8818
|
});
|
|
8530
8819
|
return { entries, warnings };
|
|
8531
8820
|
}
|
|
8532
8821
|
warnings.push({
|
|
8533
8822
|
path: "",
|
|
8534
|
-
message: `python scanner error: ${errMsg}; skipping .py
|
|
8823
|
+
message: `python scanner error: ${errMsg}; skipping this batch of ${absolutePaths.length} .py file(s)`
|
|
8535
8824
|
});
|
|
8536
8825
|
return { entries, warnings };
|
|
8537
8826
|
}
|
|
8538
8827
|
} finally {
|
|
8539
8828
|
try {
|
|
8540
|
-
|
|
8829
|
+
unlinkSync2(listFile);
|
|
8541
8830
|
} catch {
|
|
8542
8831
|
}
|
|
8543
8832
|
}
|
|
@@ -8546,7 +8835,7 @@ var init_python = __esm({
|
|
|
8546
8835
|
path: "",
|
|
8547
8836
|
message: "python interpreter is < 3.9 (need ast.unparse); skipping .py files"
|
|
8548
8837
|
});
|
|
8549
|
-
return { entries, warnings };
|
|
8838
|
+
return { entries, warnings, abortRemaining: true };
|
|
8550
8839
|
}
|
|
8551
8840
|
for (const line of stdout.split("\n")) {
|
|
8552
8841
|
const trimmed = line.trim();
|
|
@@ -8564,7 +8853,7 @@ var init_python = __esm({
|
|
|
8564
8853
|
}
|
|
8565
8854
|
const r = parsed;
|
|
8566
8855
|
entries.push({
|
|
8567
|
-
path: canonicalRelPath(
|
|
8856
|
+
path: canonicalRelPath(join20(repoRoot, r.path), repoRoot),
|
|
8568
8857
|
total_lines: r.total_lines,
|
|
8569
8858
|
imports: r.imports ?? [],
|
|
8570
8859
|
constants: r.constants ?? [],
|
|
@@ -8608,7 +8897,7 @@ __export(javascript_exports, {
|
|
|
8608
8897
|
parseJsSource: () => parseJsSource,
|
|
8609
8898
|
parseSourceWithPlugins: () => parseSourceWithPlugins
|
|
8610
8899
|
});
|
|
8611
|
-
import { readFileSync as
|
|
8900
|
+
import { readFileSync as readFileSync18 } from "fs";
|
|
8612
8901
|
import { parse } from "@babel/parser";
|
|
8613
8902
|
function parseSourceWithPlugins(source, plugins) {
|
|
8614
8903
|
return parse(source, {
|
|
@@ -8689,7 +8978,7 @@ function processClassDeclaration(node, nameOverride) {
|
|
|
8689
8978
|
methods.push({
|
|
8690
8979
|
name: methodName,
|
|
8691
8980
|
lines: getLineRange(m),
|
|
8692
|
-
async: m.async
|
|
8981
|
+
async: m.async ?? false
|
|
8693
8982
|
});
|
|
8694
8983
|
}
|
|
8695
8984
|
}
|
|
@@ -8910,7 +9199,7 @@ var init_javascript = __esm({
|
|
|
8910
9199
|
async scan(absolutePath, repoRoot) {
|
|
8911
9200
|
let source;
|
|
8912
9201
|
try {
|
|
8913
|
-
source =
|
|
9202
|
+
source = readFileSync18(absolutePath, "utf8");
|
|
8914
9203
|
} catch (err) {
|
|
8915
9204
|
throw err;
|
|
8916
9205
|
}
|
|
@@ -8930,7 +9219,7 @@ var typescript_exports = {};
|
|
|
8930
9219
|
__export(typescript_exports, {
|
|
8931
9220
|
tsScanner: () => tsScanner
|
|
8932
9221
|
});
|
|
8933
|
-
import { readFileSync as
|
|
9222
|
+
import { readFileSync as readFileSync19 } from "fs";
|
|
8934
9223
|
var TypeScriptScanner, tsScanner;
|
|
8935
9224
|
var init_typescript = __esm({
|
|
8936
9225
|
"src/code-map/scanners/typescript.ts"() {
|
|
@@ -8942,7 +9231,7 @@ var init_typescript = __esm({
|
|
|
8942
9231
|
async scan(absolutePath, repoRoot) {
|
|
8943
9232
|
let source;
|
|
8944
9233
|
try {
|
|
8945
|
-
source =
|
|
9234
|
+
source = readFileSync19(absolutePath, "utf8");
|
|
8946
9235
|
} catch (err) {
|
|
8947
9236
|
throw err;
|
|
8948
9237
|
}
|
|
@@ -8978,7 +9267,7 @@ var vue_exports = {};
|
|
|
8978
9267
|
__export(vue_exports, {
|
|
8979
9268
|
vueScanner: () => vueScanner
|
|
8980
9269
|
});
|
|
8981
|
-
import { readFileSync as
|
|
9270
|
+
import { readFileSync as readFileSync20 } from "fs";
|
|
8982
9271
|
import { parse as parse2 } from "@vue/compiler-sfc";
|
|
8983
9272
|
var VueScanner, vueScanner;
|
|
8984
9273
|
var init_vue = __esm({
|
|
@@ -8991,7 +9280,7 @@ var init_vue = __esm({
|
|
|
8991
9280
|
async scan(absolutePath, repoRoot) {
|
|
8992
9281
|
let source;
|
|
8993
9282
|
try {
|
|
8994
|
-
source =
|
|
9283
|
+
source = readFileSync20(absolutePath, "utf8");
|
|
8995
9284
|
} catch (err) {
|
|
8996
9285
|
throw err;
|
|
8997
9286
|
}
|
|
@@ -9078,12 +9367,12 @@ __export(code_map_exports, {
|
|
|
9078
9367
|
codeMap: () => codeMap,
|
|
9079
9368
|
computeSourcesDigest: () => computeSourcesDigest
|
|
9080
9369
|
});
|
|
9081
|
-
import { existsSync as
|
|
9370
|
+
import { existsSync as existsSync18, mkdirSync as mkdirSync8, readFileSync as readFileSync21, writeFileSync as writeFileSync11 } from "fs";
|
|
9082
9371
|
import { resolve, dirname as dirname5, relative as relative6 } from "path";
|
|
9083
9372
|
import { createHash as createHash5 } from "crypto";
|
|
9084
9373
|
import { createRequire } from "module";
|
|
9085
9374
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
9086
|
-
import { join as
|
|
9375
|
+
import { join as join21 } from "path";
|
|
9087
9376
|
function computeSourcesDigest(absolutePaths, cwd) {
|
|
9088
9377
|
const lines = absolutePaths.slice().sort().map((p) => {
|
|
9089
9378
|
const rel = relative6(cwd, p).replace(/\\/g, "/");
|
|
@@ -9092,9 +9381,21 @@ function computeSourcesDigest(absolutePaths, cwd) {
|
|
|
9092
9381
|
});
|
|
9093
9382
|
return createHash5("sha256").update(lines.join("\n")).digest("hex");
|
|
9094
9383
|
}
|
|
9384
|
+
function slugifySurface(surface) {
|
|
9385
|
+
return surface.replace(/^[./]+/, "").replace(/\/+$/, "").replace(/[\\/]+/g, "-") || "root";
|
|
9386
|
+
}
|
|
9095
9387
|
async function codeMap(opts) {
|
|
9096
|
-
const root = resolve(process.cwd(), opts.path);
|
|
9097
9388
|
const start = Date.now();
|
|
9389
|
+
if (opts.surface) {
|
|
9390
|
+
const resolvedSurface = resolve(process.cwd(), opts.surface);
|
|
9391
|
+
if (!existsSync18(resolvedSurface)) {
|
|
9392
|
+
log.error(`code-map --surface path not found: ${opts.surface}`);
|
|
9393
|
+
return 1;
|
|
9394
|
+
}
|
|
9395
|
+
}
|
|
9396
|
+
const scanPath = opts.surface ?? opts.path;
|
|
9397
|
+
const root = resolve(process.cwd(), scanPath);
|
|
9398
|
+
const out = opts.out ?? (opts.surface ? `.cdd/code-map.${slugifySurface(opts.surface)}.yml` : ".cdd/code-map.yml");
|
|
9098
9399
|
let cfg;
|
|
9099
9400
|
try {
|
|
9100
9401
|
cfg = loadCodeMapConfig(root);
|
|
@@ -9107,6 +9408,15 @@ async function codeMap(opts) {
|
|
|
9107
9408
|
const files = walkRepo(root, { include, exclude });
|
|
9108
9409
|
const buckets = bucketByExtension(files);
|
|
9109
9410
|
const result = { entries: [], warnings: [] };
|
|
9411
|
+
const cliEntry = process.argv[1];
|
|
9412
|
+
const workers = opts.workers ?? 0;
|
|
9413
|
+
const useWorkers = workers > 1 && typeof cliEntry === "string" && cliEntry.length > 0;
|
|
9414
|
+
const dispatch = async (scanner, lang, langFiles) => {
|
|
9415
|
+
if (!useWorkers)
|
|
9416
|
+
return scanInProcess(scanner, langFiles, root);
|
|
9417
|
+
const { scanLangWithWorkers: scanLangWithWorkers2 } = await Promise.resolve().then(() => (init_worker_dispatch(), worker_dispatch_exports));
|
|
9418
|
+
return scanLangWithWorkers2(scanner, lang, langFiles, root, workers, cliEntry);
|
|
9419
|
+
};
|
|
9110
9420
|
const tasks = [];
|
|
9111
9421
|
if (buckets[".py"]?.length) {
|
|
9112
9422
|
const { pythonScanner: pythonScanner2 } = await Promise.resolve().then(() => (init_python(), python_exports));
|
|
@@ -9122,7 +9432,7 @@ async function codeMap(opts) {
|
|
|
9122
9432
|
];
|
|
9123
9433
|
if (jsFiles.length) {
|
|
9124
9434
|
const { jsScanner: jsScanner2 } = await Promise.resolve().then(() => (init_javascript(), javascript_exports));
|
|
9125
|
-
tasks.push(
|
|
9435
|
+
tasks.push(dispatch(jsScanner2, "js", jsFiles));
|
|
9126
9436
|
}
|
|
9127
9437
|
const tsFiles = [
|
|
9128
9438
|
...buckets[".ts"] ?? [],
|
|
@@ -9130,11 +9440,11 @@ async function codeMap(opts) {
|
|
|
9130
9440
|
];
|
|
9131
9441
|
if (tsFiles.length) {
|
|
9132
9442
|
const { tsScanner: tsScanner2 } = await Promise.resolve().then(() => (init_typescript(), typescript_exports));
|
|
9133
|
-
tasks.push(
|
|
9443
|
+
tasks.push(dispatch(tsScanner2, "ts", tsFiles));
|
|
9134
9444
|
}
|
|
9135
9445
|
if (buckets[".vue"]?.length) {
|
|
9136
9446
|
const { vueScanner: vueScanner2 } = await Promise.resolve().then(() => (init_vue(), vue_exports));
|
|
9137
|
-
tasks.push(
|
|
9447
|
+
tasks.push(dispatch(vueScanner2, "vue", buckets[".vue"]));
|
|
9138
9448
|
}
|
|
9139
9449
|
for (const r of await Promise.all(tasks)) {
|
|
9140
9450
|
result.entries.push(...r.entries);
|
|
@@ -9157,25 +9467,34 @@ async function codeMap(opts) {
|
|
|
9157
9467
|
const totalSrc = result.entries.reduce((s, e) => s + e.total_lines, 0);
|
|
9158
9468
|
const mapLines = yamlBody.split("\n").length;
|
|
9159
9469
|
const compression = totalSrc === 0 ? 0 : totalSrc / mapLines;
|
|
9160
|
-
const summaryLine = `scanned ${result.entries.length} files, ${totalSrc} src lines -> ${
|
|
9470
|
+
const summaryLine = `scanned ${result.entries.length} files, ${totalSrc} src lines -> ${out} (${mapLines} lines, compression ${compression.toFixed(1)}x)`;
|
|
9161
9471
|
for (const w of result.warnings) {
|
|
9162
9472
|
if (!opts.silent)
|
|
9163
9473
|
log.warn(`${w.path}: ${w.message}`);
|
|
9164
9474
|
}
|
|
9165
9475
|
if (opts.check) {
|
|
9166
|
-
const existing =
|
|
9476
|
+
const existing = existsSync18(out) ? readFileSync21(out, "utf8") : "";
|
|
9167
9477
|
const normalize = (s) => s.replace(/\r\n/g, "\n").replace(/\r/g, "\n").replace(/^# generated: [^\n]+\n/m, "# generated: <normalized>\n");
|
|
9168
9478
|
if (normalize(existing) !== normalize(yamlBody)) {
|
|
9169
9479
|
if (!opts.silent)
|
|
9170
|
-
log.error(`code-map out of date: ${
|
|
9480
|
+
log.error(`code-map out of date: ${out} would change. Run \`cdd-kit code-map\` to regenerate.`);
|
|
9171
9481
|
return 1;
|
|
9172
9482
|
}
|
|
9173
9483
|
if (!opts.silent)
|
|
9174
|
-
log.ok(`code-map up to date: ${
|
|
9484
|
+
log.ok(`code-map up to date: ${out}`);
|
|
9175
9485
|
return 0;
|
|
9176
9486
|
}
|
|
9177
|
-
mkdirSync8(dirname5(
|
|
9178
|
-
|
|
9487
|
+
mkdirSync8(dirname5(out), { recursive: true });
|
|
9488
|
+
writeFileSync11(out, yamlBody, "utf8");
|
|
9489
|
+
try {
|
|
9490
|
+
const sidecarPath = sidecarPathFor(out);
|
|
9491
|
+
writeFileSync11(sidecarPath, JSON.stringify({ sourcesDigest, entries: result.entries }), "utf8");
|
|
9492
|
+
const rel = relative6(process.cwd(), sidecarPath).replace(/\\/g, "/");
|
|
9493
|
+
if (!rel.startsWith("..")) {
|
|
9494
|
+
ensureGitignoreEntry(process.cwd(), rel, "cdd-kit local cache (do not commit)");
|
|
9495
|
+
}
|
|
9496
|
+
} catch {
|
|
9497
|
+
}
|
|
9179
9498
|
if (!opts.silent)
|
|
9180
9499
|
log.ok(`${summaryLine} (${Date.now() - start}ms)`);
|
|
9181
9500
|
return 0;
|
|
@@ -9188,10 +9507,12 @@ var init_code_map = __esm({
|
|
|
9188
9507
|
init_yaml_writer();
|
|
9189
9508
|
init_orchestrator();
|
|
9190
9509
|
init_config();
|
|
9510
|
+
init_index_reader();
|
|
9191
9511
|
init_digest();
|
|
9512
|
+
init_gitignore();
|
|
9192
9513
|
_require = createRequire(import.meta.url);
|
|
9193
|
-
_pkgPath =
|
|
9194
|
-
_pkg = JSON.parse(
|
|
9514
|
+
_pkgPath = join21(fileURLToPath2(import.meta.url), "..", "..", "..", "package.json");
|
|
9515
|
+
_pkg = JSON.parse(readFileSync21(_pkgPath, "utf8"));
|
|
9195
9516
|
}
|
|
9196
9517
|
});
|
|
9197
9518
|
|
|
@@ -9200,15 +9521,15 @@ var refresh_exports = {};
|
|
|
9200
9521
|
__export(refresh_exports, {
|
|
9201
9522
|
refresh: () => refresh
|
|
9202
9523
|
});
|
|
9203
|
-
import { existsSync as
|
|
9204
|
-
import { dirname as dirname6, join as
|
|
9524
|
+
import { existsSync as existsSync19, mkdirSync as mkdirSync9, readdirSync as readdirSync10, copyFileSync as copyFileSync4, readFileSync as readFileSync22, writeFileSync as writeFileSync12 } from "fs";
|
|
9525
|
+
import { dirname as dirname6, join as join22, relative as relative7 } from "path";
|
|
9205
9526
|
import { createHash as createHash6 } from "crypto";
|
|
9206
9527
|
function fileHash2(filePath) {
|
|
9207
|
-
return createHash6("sha256").update(
|
|
9528
|
+
return createHash6("sha256").update(readFileSync22(filePath)).digest("hex");
|
|
9208
9529
|
}
|
|
9209
9530
|
function planForceRefresh(srcDir, destDir, sectionLabel) {
|
|
9210
9531
|
const plan = [];
|
|
9211
|
-
if (!
|
|
9532
|
+
if (!existsSync19(srcDir))
|
|
9212
9533
|
return plan;
|
|
9213
9534
|
function walk(curSrc, curDest) {
|
|
9214
9535
|
let items;
|
|
@@ -9218,16 +9539,16 @@ function planForceRefresh(srcDir, destDir, sectionLabel) {
|
|
|
9218
9539
|
return;
|
|
9219
9540
|
}
|
|
9220
9541
|
for (const item of items) {
|
|
9221
|
-
const sp =
|
|
9222
|
-
const dp =
|
|
9542
|
+
const sp = join22(curSrc, item.name);
|
|
9543
|
+
const dp = join22(curDest, item.name);
|
|
9223
9544
|
if (item.isDirectory()) {
|
|
9224
9545
|
walk(sp, dp);
|
|
9225
9546
|
continue;
|
|
9226
9547
|
}
|
|
9227
9548
|
if (!item.isFile())
|
|
9228
9549
|
continue;
|
|
9229
|
-
const rel =
|
|
9230
|
-
if (!
|
|
9550
|
+
const rel = join22(sectionLabel, relative7(srcDir, sp)).replace(/\\/g, "/");
|
|
9551
|
+
if (!existsSync19(dp)) {
|
|
9231
9552
|
plan.push({ src: sp, dest: dp, rel, action: "add" });
|
|
9232
9553
|
} else if (fileHash2(sp) !== fileHash2(dp)) {
|
|
9233
9554
|
plan.push({ src: sp, dest: dp, rel, action: "overwrite" });
|
|
@@ -9240,35 +9561,14 @@ function planForceRefresh(srcDir, destDir, sectionLabel) {
|
|
|
9240
9561
|
return plan;
|
|
9241
9562
|
}
|
|
9242
9563
|
function planSingleFile(src, dest, rel) {
|
|
9243
|
-
if (!
|
|
9564
|
+
if (!existsSync19(src))
|
|
9244
9565
|
return null;
|
|
9245
|
-
if (!
|
|
9566
|
+
if (!existsSync19(dest))
|
|
9246
9567
|
return { src, dest, rel, action: "add" };
|
|
9247
9568
|
if (fileHash2(src) !== fileHash2(dest))
|
|
9248
9569
|
return { src, dest, rel, action: "overwrite" };
|
|
9249
9570
|
return { src, dest, rel, action: "skip" };
|
|
9250
9571
|
}
|
|
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
9572
|
function applyPlan(plan, backupRoot) {
|
|
9273
9573
|
let added = 0;
|
|
9274
9574
|
let overwritten = 0;
|
|
@@ -9276,7 +9576,7 @@ function applyPlan(plan, backupRoot) {
|
|
|
9276
9576
|
if (item.action === "skip")
|
|
9277
9577
|
continue;
|
|
9278
9578
|
if (item.action === "overwrite") {
|
|
9279
|
-
const backupPath =
|
|
9579
|
+
const backupPath = join22(backupRoot, item.rel);
|
|
9280
9580
|
mkdirSync9(dirname6(backupPath), { recursive: true });
|
|
9281
9581
|
copyFileSync4(item.dest, backupPath);
|
|
9282
9582
|
overwritten += 1;
|
|
@@ -9305,14 +9605,14 @@ function parseAgentFrontmatter(content) {
|
|
|
9305
9605
|
return fm;
|
|
9306
9606
|
}
|
|
9307
9607
|
function resyncModelPolicy(cwd) {
|
|
9308
|
-
const policyPath =
|
|
9608
|
+
const policyPath = join22(cwd, ".cdd", "model-policy.json");
|
|
9309
9609
|
const result = { changed: false, diff: [], policyPath };
|
|
9310
|
-
if (!
|
|
9610
|
+
if (!existsSync19(AGENTS_HOME))
|
|
9311
9611
|
return result;
|
|
9312
9612
|
const desired = {};
|
|
9313
9613
|
const agentFiles = readdirSync10(AGENTS_HOME, { withFileTypes: true }).filter((d) => d.isFile() && d.name.endsWith(".md"));
|
|
9314
9614
|
for (const f of agentFiles) {
|
|
9315
|
-
const content =
|
|
9615
|
+
const content = readFileSync22(join22(AGENTS_HOME, f.name), "utf8");
|
|
9316
9616
|
const fm = parseAgentFrontmatter(content);
|
|
9317
9617
|
if (fm.name && fm.model)
|
|
9318
9618
|
desired[fm.name] = fm.model;
|
|
@@ -9320,9 +9620,9 @@ function resyncModelPolicy(cwd) {
|
|
|
9320
9620
|
if (Object.keys(desired).length === 0)
|
|
9321
9621
|
return result;
|
|
9322
9622
|
let existing = {};
|
|
9323
|
-
if (
|
|
9623
|
+
if (existsSync19(policyPath)) {
|
|
9324
9624
|
try {
|
|
9325
|
-
existing = JSON.parse(
|
|
9625
|
+
existing = JSON.parse(readFileSync22(policyPath, "utf8"));
|
|
9326
9626
|
} catch {
|
|
9327
9627
|
}
|
|
9328
9628
|
}
|
|
@@ -9343,7 +9643,7 @@ function resyncModelPolicy(cwd) {
|
|
|
9343
9643
|
if (!("provider" in merged))
|
|
9344
9644
|
merged["provider"] = "claude";
|
|
9345
9645
|
mkdirSync9(dirname6(policyPath), { recursive: true });
|
|
9346
|
-
|
|
9646
|
+
writeFileSync12(policyPath, JSON.stringify(merged, null, 2) + "\n", "utf8");
|
|
9347
9647
|
result.changed = true;
|
|
9348
9648
|
return result;
|
|
9349
9649
|
}
|
|
@@ -9351,21 +9651,21 @@ function planTemplateRefresh(cwd) {
|
|
|
9351
9651
|
const sections = [];
|
|
9352
9652
|
sections.push({
|
|
9353
9653
|
name: "specs/templates",
|
|
9354
|
-
plan: planForceRefresh(ASSET.specsTemplates,
|
|
9654
|
+
plan: planForceRefresh(ASSET.specsTemplates, join22(cwd, "specs", "templates"), "specs/templates")
|
|
9355
9655
|
});
|
|
9356
9656
|
sections.push({
|
|
9357
9657
|
name: "tests/templates",
|
|
9358
|
-
plan: planForceRefresh(ASSET.testsTemplates,
|
|
9658
|
+
plan: planForceRefresh(ASSET.testsTemplates, join22(cwd, "tests", "templates"), "tests/templates")
|
|
9359
9659
|
});
|
|
9360
|
-
const ciTemplatesAsset =
|
|
9361
|
-
if (
|
|
9660
|
+
const ciTemplatesAsset = join22(ASSET.ci, "..", "ci-templates");
|
|
9661
|
+
if (existsSync19(ciTemplatesAsset)) {
|
|
9362
9662
|
sections.push({
|
|
9363
9663
|
name: "ci-templates",
|
|
9364
|
-
plan: planForceRefresh(ciTemplatesAsset,
|
|
9664
|
+
plan: planForceRefresh(ciTemplatesAsset, join22(cwd, "ci-templates"), "ci-templates")
|
|
9365
9665
|
});
|
|
9366
9666
|
}
|
|
9367
|
-
const wfAsset =
|
|
9368
|
-
const wfDest =
|
|
9667
|
+
const wfAsset = join22(ASSET.githubWorkflows, "contract-driven-gates.yml");
|
|
9668
|
+
const wfDest = join22(cwd, ".github", "workflows", "contract-driven-gates.yml");
|
|
9369
9669
|
const wfPlan = planSingleFile(wfAsset, wfDest, ".github/workflows/contract-driven-gates.yml");
|
|
9370
9670
|
if (wfPlan)
|
|
9371
9671
|
sections.push({ name: ".github/workflows/contract-driven-gates.yml", plan: [wfPlan] });
|
|
@@ -9420,14 +9720,14 @@ async function refresh(opts) {
|
|
|
9420
9720
|
}
|
|
9421
9721
|
if (apply) {
|
|
9422
9722
|
const ts = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
9423
|
-
backupRoot =
|
|
9723
|
+
backupRoot = join22(cwd, ".cdd", ".refresh-backup", ts);
|
|
9424
9724
|
const result = applyPlan(total, backupRoot);
|
|
9425
9725
|
templateAdded = result.added;
|
|
9426
9726
|
templateOverwritten = result.overwritten;
|
|
9427
9727
|
log.ok(` applied: +${templateAdded} added, ~${templateOverwritten} overwritten`);
|
|
9428
9728
|
if (templateOverwritten > 0) {
|
|
9429
9729
|
log.info(` backup saved to: ${relative7(cwd, backupRoot).replace(/\\/g, "/")}`);
|
|
9430
|
-
if (
|
|
9730
|
+
if (ensureGitignoreEntry(cwd, ".cdd/.refresh-backup/")) {
|
|
9431
9731
|
log.info(" added `.cdd/.refresh-backup/` to .gitignore");
|
|
9432
9732
|
}
|
|
9433
9733
|
}
|
|
@@ -9440,8 +9740,8 @@ async function refresh(opts) {
|
|
|
9440
9740
|
}
|
|
9441
9741
|
log.blank();
|
|
9442
9742
|
if (!opts.noHooks) {
|
|
9443
|
-
const markerPath =
|
|
9444
|
-
if (
|
|
9743
|
+
const markerPath = join22(cwd, HOOKS_MARKER_PATH);
|
|
9744
|
+
if (existsSync19(markerPath)) {
|
|
9445
9745
|
log.info("[4/6] re-install code-map pre-commit hook (marker found)");
|
|
9446
9746
|
if (apply) {
|
|
9447
9747
|
try {
|
|
@@ -9518,6 +9818,7 @@ var init_refresh = __esm({
|
|
|
9518
9818
|
"use strict";
|
|
9519
9819
|
init_paths();
|
|
9520
9820
|
init_logger();
|
|
9821
|
+
init_gitignore();
|
|
9521
9822
|
init_update();
|
|
9522
9823
|
init_upgrade();
|
|
9523
9824
|
init_code_map();
|
|
@@ -9526,76 +9827,195 @@ var init_refresh = __esm({
|
|
|
9526
9827
|
}
|
|
9527
9828
|
});
|
|
9528
9829
|
|
|
9529
|
-
// src/
|
|
9530
|
-
|
|
9531
|
-
|
|
9532
|
-
|
|
9533
|
-
|
|
9534
|
-
|
|
9830
|
+
// src/commands/lint-agents.ts
|
|
9831
|
+
var lint_agents_exports = {};
|
|
9832
|
+
__export(lint_agents_exports, {
|
|
9833
|
+
collectAgentViolations: () => collectAgentViolations,
|
|
9834
|
+
lintAgentContent: () => lintAgentContent,
|
|
9835
|
+
lintAgents: () => lintAgents
|
|
9836
|
+
});
|
|
9837
|
+
import { readdirSync as readdirSync11, readFileSync as readFileSync23 } from "fs";
|
|
9838
|
+
import { join as join23 } from "path";
|
|
9839
|
+
import { load as yamlLoad2 } from "js-yaml";
|
|
9840
|
+
function extractRequiredArtifactsSection(content) {
|
|
9841
|
+
const match = content.match(
|
|
9842
|
+
/### (?:Suggested|Required) artifacts for this agent\s*\n([\s\S]*?)(?=\n#{2,3} |\n---|\s*$)/
|
|
9843
|
+
);
|
|
9844
|
+
return match ? match[0] : null;
|
|
9845
|
+
}
|
|
9846
|
+
function extractFirstReadScopeSection(content) {
|
|
9847
|
+
const match = content.match(/## Read scope\s*\n([\s\S]*?)(?=\n## |\s*$)/);
|
|
9848
|
+
return match ? match[0] : null;
|
|
9849
|
+
}
|
|
9850
|
+
function extractYamlBlock(section) {
|
|
9851
|
+
const match = section.match(/```ya?ml\s*\n([\s\S]*?)```/);
|
|
9852
|
+
return match ? match[0] : null;
|
|
9853
|
+
}
|
|
9854
|
+
function extractYamlBody(section) {
|
|
9855
|
+
const match = section.match(/```ya?ml\s*\n([\s\S]*?)```/);
|
|
9856
|
+
return match ? match[1] : null;
|
|
9857
|
+
}
|
|
9858
|
+
function strayTopLevelKeys(yamlBody) {
|
|
9859
|
+
let parsed;
|
|
9535
9860
|
try {
|
|
9536
|
-
|
|
9537
|
-
} catch
|
|
9538
|
-
return
|
|
9539
|
-
status: "config-error",
|
|
9540
|
-
staleFiles: [],
|
|
9541
|
-
staleCount: 0,
|
|
9542
|
-
mapPath,
|
|
9543
|
-
configError: err.message
|
|
9544
|
-
};
|
|
9861
|
+
parsed = yamlLoad2(yamlBody);
|
|
9862
|
+
} catch {
|
|
9863
|
+
return null;
|
|
9545
9864
|
}
|
|
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 };
|
|
9865
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
9866
|
+
return null;
|
|
9554
9867
|
}
|
|
9555
|
-
const
|
|
9556
|
-
|
|
9557
|
-
|
|
9558
|
-
|
|
9559
|
-
|
|
9560
|
-
|
|
9561
|
-
|
|
9562
|
-
|
|
9868
|
+
const keys = Object.keys(parsed);
|
|
9869
|
+
return keys.filter((k) => k !== "artifacts");
|
|
9870
|
+
}
|
|
9871
|
+
function hasFlatBacktickKeysWithoutFence(section) {
|
|
9872
|
+
if (/```ya?ml\s*\nartifacts:/.test(section))
|
|
9873
|
+
return false;
|
|
9874
|
+
const withoutFence = section.replace(/```ya?ml[\s\S]*?```/g, "");
|
|
9875
|
+
return /^- `[a-z][a-z0-9-]+`:/m.test(withoutFence);
|
|
9876
|
+
}
|
|
9877
|
+
function lintAgentContent(filename, rawContent, opts = {}) {
|
|
9878
|
+
const violations = [];
|
|
9879
|
+
let content = rawContent;
|
|
9880
|
+
if (content.charCodeAt(0) === 65279) {
|
|
9881
|
+
violations.push({
|
|
9882
|
+
file: filename,
|
|
9883
|
+
rule: "Meta",
|
|
9884
|
+
message: "file starts with UTF-8 BOM (U+FEFF); frontmatter parsers may treat the first key as invalid",
|
|
9885
|
+
level: "error"
|
|
9886
|
+
});
|
|
9887
|
+
content = content.slice(1);
|
|
9888
|
+
}
|
|
9889
|
+
const artifactsSection = extractRequiredArtifactsSection(content);
|
|
9890
|
+
if (!artifactsSection) {
|
|
9891
|
+
violations.push({
|
|
9892
|
+
file: filename,
|
|
9893
|
+
rule: "A",
|
|
9894
|
+
message: "missing ### Suggested artifacts for this agent section",
|
|
9895
|
+
level: "error"
|
|
9896
|
+
});
|
|
9897
|
+
} else {
|
|
9898
|
+
const yamlBlock = extractYamlBlock(artifactsSection);
|
|
9899
|
+
if (!yamlBlock || !/```ya?ml\s*\nartifacts:/.test(yamlBlock)) {
|
|
9900
|
+
violations.push({
|
|
9901
|
+
file: filename,
|
|
9902
|
+
rule: "A",
|
|
9903
|
+
message: 'bad Suggested-artifacts format: missing fenced yaml block starting with "artifacts:"',
|
|
9904
|
+
level: "error"
|
|
9905
|
+
});
|
|
9906
|
+
} else if (!yamlBlock.includes("{ type:") && !/- \{/.test(yamlBlock)) {
|
|
9907
|
+
violations.push({
|
|
9908
|
+
file: filename,
|
|
9909
|
+
rule: "A",
|
|
9910
|
+
message: "bad Suggested-artifacts format: YAML block has no { type: ..., pointer: ... } items",
|
|
9911
|
+
level: "error"
|
|
9912
|
+
});
|
|
9913
|
+
} else {
|
|
9914
|
+
const yamlBody = extractYamlBody(artifactsSection);
|
|
9915
|
+
if (yamlBody) {
|
|
9916
|
+
const stray = strayTopLevelKeys(yamlBody);
|
|
9917
|
+
if (stray && stray.length > 0) {
|
|
9918
|
+
violations.push({
|
|
9919
|
+
file: filename,
|
|
9920
|
+
rule: "A",
|
|
9921
|
+
message: `bad Suggested-artifacts format: stray top-level key(s) alongside artifacts: [${stray.join(", ")}] -- these are item keys, not log keys`,
|
|
9922
|
+
level: "error"
|
|
9923
|
+
});
|
|
9924
|
+
}
|
|
9563
9925
|
}
|
|
9564
|
-
}
|
|
9926
|
+
}
|
|
9927
|
+
if (hasFlatBacktickKeysWithoutFence(artifactsSection)) {
|
|
9928
|
+
violations.push({
|
|
9929
|
+
file: filename,
|
|
9930
|
+
rule: "A",
|
|
9931
|
+
message: "bad Suggested-artifacts format: flat backtick-keyed lines found outside YAML fence (remove old `key: value` bullet style)",
|
|
9932
|
+
level: "error"
|
|
9933
|
+
});
|
|
9565
9934
|
}
|
|
9566
9935
|
}
|
|
9567
|
-
|
|
9568
|
-
|
|
9936
|
+
const readScopeCount = (content.match(/^## Read scope\s*$/gm) ?? []).length;
|
|
9937
|
+
if (readScopeCount > 1) {
|
|
9938
|
+
violations.push({
|
|
9939
|
+
file: filename,
|
|
9940
|
+
rule: "B",
|
|
9941
|
+
message: `duplicate ## Read scope headings found (${readScopeCount} occurrences -- remove all but the first)`,
|
|
9942
|
+
level: "error"
|
|
9943
|
+
});
|
|
9569
9944
|
}
|
|
9570
|
-
|
|
9571
|
-
|
|
9572
|
-
|
|
9573
|
-
|
|
9574
|
-
|
|
9945
|
+
if (readScopeCount >= 1) {
|
|
9946
|
+
const readScopeSection = extractFirstReadScopeSection(content);
|
|
9947
|
+
if (readScopeSection && !readScopeSection.includes("context-manifest.md")) {
|
|
9948
|
+
violations.push({
|
|
9949
|
+
file: filename,
|
|
9950
|
+
rule: "C",
|
|
9951
|
+
message: "## Read scope section does not reference context-manifest.md",
|
|
9952
|
+
level: "error"
|
|
9953
|
+
});
|
|
9575
9954
|
}
|
|
9576
9955
|
}
|
|
9577
|
-
|
|
9578
|
-
|
|
9579
|
-
|
|
9580
|
-
|
|
9581
|
-
|
|
9582
|
-
|
|
9956
|
+
if (!content.includes("references/agent-log-protocol.md")) {
|
|
9957
|
+
violations.push({
|
|
9958
|
+
file: filename,
|
|
9959
|
+
rule: "D",
|
|
9960
|
+
message: "missing reference to references/agent-log-protocol.md",
|
|
9961
|
+
level: opts.strict ? "error" : "warning"
|
|
9962
|
+
});
|
|
9963
|
+
}
|
|
9964
|
+
return violations;
|
|
9583
9965
|
}
|
|
9584
|
-
function
|
|
9966
|
+
function collectAgentViolations(cwd, opts = {}) {
|
|
9967
|
+
const agentsDir = join23(cwd, ".claude", "agents");
|
|
9968
|
+
let files;
|
|
9585
9969
|
try {
|
|
9586
|
-
|
|
9587
|
-
const m = head.match(/^# sources-digest:\s*([a-f0-9]+)/m);
|
|
9588
|
-
return m ? m[1] : null;
|
|
9970
|
+
files = readdirSync11(agentsDir).filter((f) => f.endsWith(".md")).sort();
|
|
9589
9971
|
} catch {
|
|
9590
9972
|
return null;
|
|
9591
9973
|
}
|
|
9974
|
+
const violations = [];
|
|
9975
|
+
for (const filename of files) {
|
|
9976
|
+
let content;
|
|
9977
|
+
try {
|
|
9978
|
+
content = readFileSync23(join23(agentsDir, filename), "utf8");
|
|
9979
|
+
} catch {
|
|
9980
|
+
violations.push({
|
|
9981
|
+
file: filename,
|
|
9982
|
+
rule: "A",
|
|
9983
|
+
message: "cannot read file",
|
|
9984
|
+
level: "error"
|
|
9985
|
+
});
|
|
9986
|
+
continue;
|
|
9987
|
+
}
|
|
9988
|
+
violations.push(...lintAgentContent(filename, content, opts));
|
|
9989
|
+
}
|
|
9990
|
+
return violations;
|
|
9991
|
+
}
|
|
9992
|
+
async function lintAgents(opts) {
|
|
9993
|
+
const cwd = process.cwd();
|
|
9994
|
+
const violations = collectAgentViolations(cwd, opts);
|
|
9995
|
+
if (violations === null) {
|
|
9996
|
+
log.error(
|
|
9997
|
+
`lint-agents: cannot read ${join23(cwd, ".claude", "agents")} -- is this a cdd-kit project?`
|
|
9998
|
+
);
|
|
9999
|
+
return 1;
|
|
10000
|
+
}
|
|
10001
|
+
for (const v of violations) {
|
|
10002
|
+
const prefix = v.level === "error" ? "error" : "warning";
|
|
10003
|
+
process.stderr.write(`${v.file}: [Rule ${v.rule}] ${prefix} -- ${v.message}
|
|
10004
|
+
`);
|
|
10005
|
+
}
|
|
10006
|
+
const errorCount = violations.filter((v) => v.level === "error").length;
|
|
10007
|
+
const warnCount = violations.filter((v) => v.level === "warning").length;
|
|
10008
|
+
console.log(`lint-agents: ${errorCount} error(s), ${warnCount} warning(s)`);
|
|
10009
|
+
if (errorCount > 0)
|
|
10010
|
+
return 1;
|
|
10011
|
+
if (opts.strict && warnCount > 0)
|
|
10012
|
+
return 1;
|
|
10013
|
+
return 0;
|
|
9592
10014
|
}
|
|
9593
|
-
var
|
|
9594
|
-
"src/
|
|
10015
|
+
var init_lint_agents = __esm({
|
|
10016
|
+
"src/commands/lint-agents.ts"() {
|
|
9595
10017
|
"use strict";
|
|
9596
|
-
|
|
9597
|
-
init_config();
|
|
9598
|
-
init_code_map();
|
|
10018
|
+
init_logger();
|
|
9599
10019
|
}
|
|
9600
10020
|
});
|
|
9601
10021
|
|
|
@@ -9604,17 +10024,17 @@ var doctor_exports = {};
|
|
|
9604
10024
|
__export(doctor_exports, {
|
|
9605
10025
|
doctor: () => doctor
|
|
9606
10026
|
});
|
|
9607
|
-
import { existsSync as
|
|
10027
|
+
import { existsSync as existsSync20, readdirSync as readdirSync12, readFileSync as readFileSync24 } from "fs";
|
|
9608
10028
|
import { createHash as createHash7 } from "crypto";
|
|
9609
|
-
import { join as
|
|
10029
|
+
import { join as join24, relative as relative8 } from "path";
|
|
9610
10030
|
function fileExists(cwd, relPath) {
|
|
9611
|
-
return
|
|
10031
|
+
return existsSync20(join24(cwd, relPath));
|
|
9612
10032
|
}
|
|
9613
10033
|
function findFiles(dir, predicate, found = []) {
|
|
9614
|
-
if (!
|
|
10034
|
+
if (!existsSync20(dir))
|
|
9615
10035
|
return found;
|
|
9616
|
-
for (const entry of
|
|
9617
|
-
const fullPath =
|
|
10036
|
+
for (const entry of readdirSync12(dir, { withFileTypes: true })) {
|
|
10037
|
+
const fullPath = join24(dir, entry.name);
|
|
9618
10038
|
if (entry.isDirectory())
|
|
9619
10039
|
findFiles(fullPath, predicate, found);
|
|
9620
10040
|
else if (entry.isFile() && predicate(entry.name))
|
|
@@ -9630,9 +10050,9 @@ function inputDigest(paths, cwd) {
|
|
|
9630
10050
|
return createHash7("sha256").update(combined).digest("hex");
|
|
9631
10051
|
}
|
|
9632
10052
|
function readContextIndexMetadata(filePath) {
|
|
9633
|
-
if (!
|
|
10053
|
+
if (!existsSync20(filePath))
|
|
9634
10054
|
return {};
|
|
9635
|
-
const text =
|
|
10055
|
+
const text = readFileSync24(filePath, "utf8");
|
|
9636
10056
|
const out = {};
|
|
9637
10057
|
const digestMatch = text.match(/^inputs-digest:\s*([a-f0-9]+)/m);
|
|
9638
10058
|
if (digestMatch)
|
|
@@ -9644,14 +10064,14 @@ function readContextIndexMetadata(filePath) {
|
|
|
9644
10064
|
}
|
|
9645
10065
|
function checkContextFreshness(cwd) {
|
|
9646
10066
|
const findings = [];
|
|
9647
|
-
const projectMap =
|
|
9648
|
-
const contractsIndex =
|
|
9649
|
-
const contextPolicy =
|
|
10067
|
+
const projectMap = join24(cwd, "specs", "context", "project-map.md");
|
|
10068
|
+
const contractsIndex = join24(cwd, "specs", "context", "contracts-index.md");
|
|
10069
|
+
const contextPolicy = join24(cwd, ".cdd", "context-policy.json");
|
|
9650
10070
|
const contractFiles = findFiles(
|
|
9651
|
-
|
|
10071
|
+
join24(cwd, "contracts"),
|
|
9652
10072
|
(name) => name.endsWith(".md") && name !== "INDEX.md" && name !== "CHANGELOG.md"
|
|
9653
10073
|
);
|
|
9654
|
-
if (!
|
|
10074
|
+
if (!existsSync20(projectMap) || !existsSync20(contractsIndex)) {
|
|
9655
10075
|
findings.push({
|
|
9656
10076
|
level: "warning",
|
|
9657
10077
|
message: "specs/context indexes are missing; run cdd-kit context-scan before classification"
|
|
@@ -9660,7 +10080,7 @@ function checkContextFreshness(cwd) {
|
|
|
9660
10080
|
}
|
|
9661
10081
|
const projectMapMeta = readContextIndexMetadata(projectMap);
|
|
9662
10082
|
const contractsIndexMeta = readContextIndexMetadata(contractsIndex);
|
|
9663
|
-
const projectInputDigest = inputDigest([contextPolicy].filter(
|
|
10083
|
+
const projectInputDigest = inputDigest([contextPolicy].filter(existsSync20), cwd);
|
|
9664
10084
|
if (projectMapMeta.inputsDigest === void 0) {
|
|
9665
10085
|
findings.push({
|
|
9666
10086
|
level: "warning",
|
|
@@ -9697,7 +10117,7 @@ function checkContextFreshness(cwd) {
|
|
|
9697
10117
|
}
|
|
9698
10118
|
function readAgentModel(path) {
|
|
9699
10119
|
try {
|
|
9700
|
-
const text =
|
|
10120
|
+
const text = readFileSync24(path, "utf8");
|
|
9701
10121
|
const m = text.match(/^model:\s*(\S+)/m);
|
|
9702
10122
|
return m ? m[1] : null;
|
|
9703
10123
|
} catch {
|
|
@@ -9705,12 +10125,12 @@ function readAgentModel(path) {
|
|
|
9705
10125
|
}
|
|
9706
10126
|
}
|
|
9707
10127
|
function checkModelPolicyDrift(cwd) {
|
|
9708
|
-
const policyPath =
|
|
9709
|
-
if (!
|
|
10128
|
+
const policyPath = join24(cwd, ".cdd", "model-policy.json");
|
|
10129
|
+
if (!existsSync20(policyPath))
|
|
9710
10130
|
return [];
|
|
9711
10131
|
let policy;
|
|
9712
10132
|
try {
|
|
9713
|
-
policy = JSON.parse(
|
|
10133
|
+
policy = JSON.parse(readFileSync24(policyPath, "utf8"));
|
|
9714
10134
|
} catch {
|
|
9715
10135
|
return [{ level: "warning", message: ".cdd/model-policy.json is not valid JSON" }];
|
|
9716
10136
|
}
|
|
@@ -9722,18 +10142,18 @@ function checkModelPolicyDrift(cwd) {
|
|
|
9722
10142
|
}];
|
|
9723
10143
|
}
|
|
9724
10144
|
const candidateDirs = [
|
|
9725
|
-
|
|
9726
|
-
process.env.HOME ?
|
|
9727
|
-
process.env.USERPROFILE ?
|
|
9728
|
-
].filter((p) => p &&
|
|
10145
|
+
join24(cwd, ".claude", "agents"),
|
|
10146
|
+
process.env.HOME ? join24(process.env.HOME, ".claude", "agents") : "",
|
|
10147
|
+
process.env.USERPROFILE ? join24(process.env.USERPROFILE, ".claude", "agents") : ""
|
|
10148
|
+
].filter((p) => p && existsSync20(p));
|
|
9729
10149
|
if (candidateDirs.length === 0)
|
|
9730
10150
|
return [];
|
|
9731
10151
|
const findings = [];
|
|
9732
10152
|
for (const [role, expected] of Object.entries(roles)) {
|
|
9733
10153
|
let foundAny = false;
|
|
9734
10154
|
for (const dir of candidateDirs) {
|
|
9735
|
-
const path =
|
|
9736
|
-
if (!
|
|
10155
|
+
const path = join24(dir, `${role}.md`);
|
|
10156
|
+
if (!existsSync20(path))
|
|
9737
10157
|
continue;
|
|
9738
10158
|
foundAny = true;
|
|
9739
10159
|
const actual = readAgentModel(path);
|
|
@@ -9752,47 +10172,30 @@ function checkModelPolicyDrift(cwd) {
|
|
|
9752
10172
|
}
|
|
9753
10173
|
return findings;
|
|
9754
10174
|
}
|
|
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 {
|
|
10175
|
+
function checkAgentLint(cwd) {
|
|
10176
|
+
const agentsDir = join24(cwd, ".claude", "agents");
|
|
10177
|
+
if (!existsSync20(agentsDir))
|
|
9789
10178
|
return [];
|
|
10179
|
+
const violations = collectAgentViolations(cwd);
|
|
10180
|
+
if (violations === null) {
|
|
10181
|
+
return [{
|
|
10182
|
+
level: "warning",
|
|
10183
|
+
message: `lint-agents: could not read ${join24(".claude", "agents")} -- agent prompts were not scanned`
|
|
10184
|
+
}];
|
|
10185
|
+
}
|
|
10186
|
+
const findings = violations.map((v) => ({
|
|
10187
|
+
level: "warning",
|
|
10188
|
+
message: `lint-agents: ${v.file}: [Rule ${v.rule}] ${v.message}`
|
|
10189
|
+
}));
|
|
10190
|
+
if (findings.length === 0) {
|
|
10191
|
+
findings.push({ level: "ok", message: "lint-agents: all agent prompts pass shape checks" });
|
|
9790
10192
|
}
|
|
10193
|
+
return findings;
|
|
9791
10194
|
}
|
|
9792
10195
|
function checkCodeMap(cwd) {
|
|
9793
10196
|
const findings = [];
|
|
9794
|
-
const mapPath =
|
|
9795
|
-
if (!
|
|
10197
|
+
const mapPath = join24(cwd, ".cdd", "code-map.yml");
|
|
10198
|
+
if (!existsSync20(mapPath)) {
|
|
9796
10199
|
const probe2 = checkCodeMapFreshness(cwd);
|
|
9797
10200
|
if (probe2.status === "config-error") {
|
|
9798
10201
|
findings.push({ level: "warning", message: `.cdd/code-map-config.yml is invalid: ${probe2.configError}` });
|
|
@@ -9811,7 +10214,7 @@ function checkCodeMap(cwd) {
|
|
|
9811
10214
|
const more = probe.staleCount > 3 ? ` (+${probe.staleCount - 3} more)` : "";
|
|
9812
10215
|
findings.push({ level: "warning", message: `code-map stale: ${top}${more}; run \`cdd-kit code-map\`` });
|
|
9813
10216
|
}
|
|
9814
|
-
const text =
|
|
10217
|
+
const text = readFileSync24(mapPath, "utf8");
|
|
9815
10218
|
const m = text.match(/^# files: (\d+), src-lines: (\d+), map-lines: (\d+), compression: ([\d.]+)x/m);
|
|
9816
10219
|
if (m) {
|
|
9817
10220
|
findings.push({ level: "ok", message: `code-map: ${m[1]} files, ${m[4]}x compression` });
|
|
@@ -9843,7 +10246,7 @@ async function buildDoctorReport(cwd, opts) {
|
|
|
9843
10246
|
}
|
|
9844
10247
|
findings.push(...checkContextFreshness(cwd));
|
|
9845
10248
|
findings.push(...checkModelPolicyDrift(cwd));
|
|
9846
|
-
findings.push(...
|
|
10249
|
+
findings.push(...checkAgentLint(cwd));
|
|
9847
10250
|
findings.push(...checkCodeMap(cwd));
|
|
9848
10251
|
const errors = findings.filter((finding) => finding.level === "error").length;
|
|
9849
10252
|
const warnings = findings.filter((finding) => finding.level === "warning").length;
|
|
@@ -9887,11 +10290,11 @@ async function attemptAutoFixes(cwd, report) {
|
|
|
9887
10290
|
}
|
|
9888
10291
|
}
|
|
9889
10292
|
if (/model-policy\.json has no role bindings/i.test(finding.message)) {
|
|
9890
|
-
const policyPath =
|
|
10293
|
+
const policyPath = join24(cwd, ".cdd", "model-policy.json");
|
|
9891
10294
|
try {
|
|
9892
10295
|
let existing = {};
|
|
9893
10296
|
try {
|
|
9894
|
-
existing = JSON.parse(
|
|
10297
|
+
existing = JSON.parse(readFileSync24(policyPath, "utf8"));
|
|
9895
10298
|
} catch {
|
|
9896
10299
|
}
|
|
9897
10300
|
const merged = {
|
|
@@ -9915,8 +10318,8 @@ async function attemptAutoFixes(cwd, report) {
|
|
|
9915
10318
|
"repo-context-scanner": "haiku"
|
|
9916
10319
|
}
|
|
9917
10320
|
};
|
|
9918
|
-
const { writeFileSync:
|
|
9919
|
-
|
|
10321
|
+
const { writeFileSync: writeFileSync16 } = await import("fs");
|
|
10322
|
+
writeFileSync16(policyPath, JSON.stringify(merged, null, 2) + "\n", "utf8");
|
|
9920
10323
|
fixed.push(`populated .cdd/model-policy.json with default role bindings`);
|
|
9921
10324
|
continue;
|
|
9922
10325
|
} catch (err) {
|
|
@@ -9984,268 +10387,50 @@ var init_doctor = __esm({
|
|
|
9984
10387
|
init_logger();
|
|
9985
10388
|
init_provider();
|
|
9986
10389
|
init_freshness();
|
|
10390
|
+
init_lint_agents();
|
|
9987
10391
|
init_digest();
|
|
9988
10392
|
}
|
|
9989
10393
|
});
|
|
9990
10394
|
|
|
9991
|
-
// src/commands/
|
|
9992
|
-
var
|
|
9993
|
-
__export(
|
|
9994
|
-
|
|
9995
|
-
});
|
|
9996
|
-
import {
|
|
9997
|
-
|
|
9998
|
-
|
|
9999
|
-
|
|
10000
|
-
|
|
10001
|
-
|
|
10002
|
-
|
|
10003
|
-
|
|
10004
|
-
|
|
10005
|
-
|
|
10006
|
-
|
|
10007
|
-
|
|
10008
|
-
|
|
10009
|
-
|
|
10010
|
-
|
|
10011
|
-
|
|
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;
|
|
10395
|
+
// src/commands/code-map-scan-worker.ts
|
|
10396
|
+
var code_map_scan_worker_exports = {};
|
|
10397
|
+
__export(code_map_scan_worker_exports, {
|
|
10398
|
+
runScanWorker: () => runScanWorker
|
|
10399
|
+
});
|
|
10400
|
+
import { readFileSync as readFileSync25 } from "fs";
|
|
10401
|
+
async function runScanWorker(opts) {
|
|
10402
|
+
let scanner;
|
|
10403
|
+
switch (opts.lang) {
|
|
10404
|
+
case "js":
|
|
10405
|
+
scanner = (await Promise.resolve().then(() => (init_javascript(), javascript_exports))).jsScanner;
|
|
10406
|
+
break;
|
|
10407
|
+
case "ts":
|
|
10408
|
+
scanner = (await Promise.resolve().then(() => (init_typescript(), typescript_exports))).tsScanner;
|
|
10409
|
+
break;
|
|
10410
|
+
case "vue":
|
|
10411
|
+
scanner = (await Promise.resolve().then(() => (init_vue(), vue_exports))).vueScanner;
|
|
10412
|
+
break;
|
|
10413
|
+
default:
|
|
10414
|
+
process.stderr.write(`__code-map-scan: unknown --lang "${opts.lang}"
|
|
10415
|
+
`);
|
|
10416
|
+
return 1;
|
|
10026
10417
|
}
|
|
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
10418
|
let files;
|
|
10040
10419
|
try {
|
|
10041
|
-
files =
|
|
10042
|
-
} catch {
|
|
10043
|
-
|
|
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}
|
|
10420
|
+
files = readFileSync25(opts.batchFile, "utf8").split("\n").map((s) => s.trim()).filter(Boolean);
|
|
10421
|
+
} catch (err) {
|
|
10422
|
+
process.stderr.write(`__code-map-scan: cannot read --batch-file: ${err.message}
|
|
10149
10423
|
`);
|
|
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
10424
|
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
|
-
});
|
|
10226
|
-
}
|
|
10227
|
-
return entries;
|
|
10228
|
-
}
|
|
10229
|
-
function extractTotalLines(text) {
|
|
10230
|
-
const totals = /* @__PURE__ */ new Map();
|
|
10231
|
-
for (const line of text.split(/\r?\n/)) {
|
|
10232
|
-
const m = line.match(/^((?:'[^']*(?:''[^']*)*')|[^#:\s][^#]*?):\s*#\s*(\d+)\s+lines\b/);
|
|
10233
|
-
if (!m)
|
|
10234
|
-
continue;
|
|
10235
|
-
totals.set(unquoteYamlKey(m[1].trim()), Number(m[2]));
|
|
10236
|
-
}
|
|
10237
|
-
return totals;
|
|
10238
|
-
}
|
|
10239
|
-
function unquoteYamlKey(key) {
|
|
10240
|
-
if (key.startsWith("'") && key.endsWith("'")) {
|
|
10241
|
-
return key.slice(1, -1).replace(/''/g, "'");
|
|
10242
10425
|
}
|
|
10243
|
-
|
|
10426
|
+
const result = await scanInProcess(scanner, files, opts.repoRoot);
|
|
10427
|
+
process.stdout.write(JSON.stringify(result));
|
|
10428
|
+
return 0;
|
|
10244
10429
|
}
|
|
10245
|
-
var
|
|
10246
|
-
"src/code-map
|
|
10430
|
+
var init_code_map_scan_worker = __esm({
|
|
10431
|
+
"src/commands/code-map-scan-worker.ts"() {
|
|
10247
10432
|
"use strict";
|
|
10248
|
-
|
|
10433
|
+
init_orchestrator();
|
|
10249
10434
|
}
|
|
10250
10435
|
});
|
|
10251
10436
|
|
|
@@ -10254,7 +10439,7 @@ var index_query_exports = {};
|
|
|
10254
10439
|
__export(index_query_exports, {
|
|
10255
10440
|
indexQuery: () => indexQuery
|
|
10256
10441
|
});
|
|
10257
|
-
import { existsSync as
|
|
10442
|
+
import { existsSync as existsSync21 } from "fs";
|
|
10258
10443
|
async function indexQuery(term, opts) {
|
|
10259
10444
|
const mapPath = opts.map || ".cdd/code-map.yml";
|
|
10260
10445
|
const limit = Number.isFinite(opts.limit) && opts.limit > 0 ? Math.floor(opts.limit) : 10;
|
|
@@ -10264,7 +10449,7 @@ async function indexQuery(term, opts) {
|
|
|
10264
10449
|
return printFailure(freshness.error, opts.json);
|
|
10265
10450
|
}
|
|
10266
10451
|
refreshed = freshness.refreshed;
|
|
10267
|
-
if (!
|
|
10452
|
+
if (!existsSync21(mapPath)) {
|
|
10268
10453
|
return printFailure(`${mapPath} is missing; run \`cdd-kit code-map\` first.`, opts.json);
|
|
10269
10454
|
}
|
|
10270
10455
|
let entries;
|
|
@@ -10432,7 +10617,7 @@ var index_impact_exports = {};
|
|
|
10432
10617
|
__export(index_impact_exports, {
|
|
10433
10618
|
indexImpact: () => indexImpact
|
|
10434
10619
|
});
|
|
10435
|
-
import { existsSync as
|
|
10620
|
+
import { existsSync as existsSync22 } from "fs";
|
|
10436
10621
|
import { posix } from "path";
|
|
10437
10622
|
async function indexImpact(term, opts) {
|
|
10438
10623
|
const mapPath = opts.map || ".cdd/code-map.yml";
|
|
@@ -10441,7 +10626,7 @@ async function indexImpact(term, opts) {
|
|
|
10441
10626
|
if (freshness.error) {
|
|
10442
10627
|
return printFailure2(freshness.error, opts.json);
|
|
10443
10628
|
}
|
|
10444
|
-
if (!
|
|
10629
|
+
if (!existsSync22(mapPath)) {
|
|
10445
10630
|
return printFailure2(`${mapPath} is missing; run \`cdd-kit code-map\` first.`, opts.json);
|
|
10446
10631
|
}
|
|
10447
10632
|
let entries;
|
|
@@ -10684,28 +10869,28 @@ var archive_exports = {};
|
|
|
10684
10869
|
__export(archive_exports, {
|
|
10685
10870
|
archive: () => archive
|
|
10686
10871
|
});
|
|
10687
|
-
import { join as
|
|
10688
|
-
import { existsSync as
|
|
10872
|
+
import { join as join25 } from "path";
|
|
10873
|
+
import { existsSync as existsSync23, mkdirSync as mkdirSync10, renameSync as renameSync2, readFileSync as readFileSync26, writeFileSync as writeFileSync13, appendFileSync, cpSync as cpSync3, rmSync as rmSync3 } from "fs";
|
|
10689
10874
|
import yaml4 from "js-yaml";
|
|
10690
10875
|
async function archive(changeId) {
|
|
10691
10876
|
const cwd = process.cwd();
|
|
10692
|
-
const changeDir =
|
|
10877
|
+
const changeDir = join25(cwd, "specs", "changes", changeId);
|
|
10693
10878
|
const archiveYear = (/* @__PURE__ */ new Date()).getFullYear().toString();
|
|
10694
|
-
const archiveBase =
|
|
10695
|
-
const archiveDir =
|
|
10696
|
-
const indexPath =
|
|
10697
|
-
if (!
|
|
10879
|
+
const archiveBase = join25(cwd, "specs", "archive", archiveYear);
|
|
10880
|
+
const archiveDir = join25(archiveBase, changeId);
|
|
10881
|
+
const indexPath = join25(cwd, "specs", "archive", "INDEX.md");
|
|
10882
|
+
if (!existsSync23(changeDir)) {
|
|
10698
10883
|
log.error(`Change not found: specs/changes/${changeId}`);
|
|
10699
10884
|
process.exit(1);
|
|
10700
10885
|
}
|
|
10701
|
-
if (
|
|
10886
|
+
if (existsSync23(archiveDir)) {
|
|
10702
10887
|
log.error(`Already archived: specs/archive/${archiveYear}/${changeId}`);
|
|
10703
10888
|
process.exit(1);
|
|
10704
10889
|
}
|
|
10705
|
-
const tasksPath =
|
|
10706
|
-
if (
|
|
10890
|
+
const tasksPath = join25(changeDir, "tasks.yml");
|
|
10891
|
+
if (existsSync23(tasksPath)) {
|
|
10707
10892
|
try {
|
|
10708
|
-
const raw =
|
|
10893
|
+
const raw = readFileSync26(tasksPath, "utf8");
|
|
10709
10894
|
const data = yaml4.load(raw);
|
|
10710
10895
|
if (data?.status === "gate-blocked") {
|
|
10711
10896
|
log.warn("tasks.yml has status: gate-blocked \u2014 archiving anyway (change was paused).");
|
|
@@ -10718,7 +10903,7 @@ async function archive(changeId) {
|
|
|
10718
10903
|
log.warn("tasks.yml could not be parsed \u2014 archiving anyway.");
|
|
10719
10904
|
}
|
|
10720
10905
|
}
|
|
10721
|
-
if (!
|
|
10906
|
+
if (!existsSync23(archiveBase)) {
|
|
10722
10907
|
mkdirSync10(archiveBase, { recursive: true });
|
|
10723
10908
|
}
|
|
10724
10909
|
try {
|
|
@@ -10735,8 +10920,8 @@ async function archive(changeId) {
|
|
|
10735
10920
|
const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
10736
10921
|
const indexLine = `| ${changeId} | ${archiveYear} | ${today} | specs/archive/${archiveYear}/${changeId}/ |
|
|
10737
10922
|
`;
|
|
10738
|
-
if (!
|
|
10739
|
-
|
|
10923
|
+
if (!existsSync23(indexPath)) {
|
|
10924
|
+
writeFileSync13(indexPath, `# Archive Index
|
|
10740
10925
|
|
|
10741
10926
|
| change-id | year | archived-date | path |
|
|
10742
10927
|
|---|---|---|---|
|
|
@@ -10760,37 +10945,37 @@ var abandon_exports = {};
|
|
|
10760
10945
|
__export(abandon_exports, {
|
|
10761
10946
|
abandon: () => abandon
|
|
10762
10947
|
});
|
|
10763
|
-
import { join as
|
|
10764
|
-
import { existsSync as
|
|
10948
|
+
import { join as join26 } from "path";
|
|
10949
|
+
import { existsSync as existsSync24, readFileSync as readFileSync27, writeFileSync as writeFileSync14, appendFileSync as appendFileSync2, mkdirSync as mkdirSync11 } from "fs";
|
|
10765
10950
|
import yaml5 from "js-yaml";
|
|
10766
10951
|
async function abandon(changeId, opts) {
|
|
10767
10952
|
const cwd = process.cwd();
|
|
10768
|
-
const changeDir =
|
|
10769
|
-
const tasksPath =
|
|
10770
|
-
if (!
|
|
10953
|
+
const changeDir = join26(cwd, "specs", "changes", changeId);
|
|
10954
|
+
const tasksPath = join26(changeDir, "tasks.yml");
|
|
10955
|
+
if (!existsSync24(changeDir)) {
|
|
10771
10956
|
log.error(`Change not found: specs/changes/${changeId}`);
|
|
10772
10957
|
process.exit(1);
|
|
10773
10958
|
}
|
|
10774
|
-
if (
|
|
10775
|
-
const raw =
|
|
10959
|
+
if (existsSync24(tasksPath)) {
|
|
10960
|
+
const raw = readFileSync27(tasksPath, "utf8");
|
|
10776
10961
|
const data = yaml5.load(raw) ?? {};
|
|
10777
10962
|
data["status"] = "abandoned";
|
|
10778
10963
|
if (!data["change-id"]) {
|
|
10779
10964
|
data["change-id"] = changeId;
|
|
10780
10965
|
}
|
|
10781
|
-
|
|
10966
|
+
writeFileSync14(tasksPath, yaml5.dump(data, { lineWidth: -1, noRefs: true }), "utf8");
|
|
10782
10967
|
}
|
|
10783
10968
|
const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
10784
|
-
const archiveDir =
|
|
10785
|
-
const indexPath =
|
|
10969
|
+
const archiveDir = join26(cwd, "specs", "archive");
|
|
10970
|
+
const indexPath = join26(archiveDir, "INDEX.md");
|
|
10786
10971
|
const reason = opts.reason ?? "no reason given";
|
|
10787
10972
|
const indexLine = `| ${changeId} | abandoned | ${today} | ${reason} |
|
|
10788
10973
|
`;
|
|
10789
|
-
if (!
|
|
10974
|
+
if (!existsSync24(archiveDir)) {
|
|
10790
10975
|
mkdirSync11(archiveDir, { recursive: true });
|
|
10791
10976
|
}
|
|
10792
|
-
if (!
|
|
10793
|
-
|
|
10977
|
+
if (!existsSync24(indexPath)) {
|
|
10978
|
+
writeFileSync14(indexPath, `# Archive Index
|
|
10794
10979
|
|
|
10795
10980
|
| change-id | status | date | notes |
|
|
10796
10981
|
|---|---|---|---|
|
|
@@ -10814,15 +10999,15 @@ var list_changes_exports = {};
|
|
|
10814
10999
|
__export(list_changes_exports, {
|
|
10815
11000
|
listChanges: () => listChanges
|
|
10816
11001
|
});
|
|
10817
|
-
import { join as
|
|
10818
|
-
import { existsSync as
|
|
11002
|
+
import { join as join27 } from "path";
|
|
11003
|
+
import { existsSync as existsSync25, readdirSync as readdirSync13, readFileSync as readFileSync28 } from "fs";
|
|
10819
11004
|
import yaml6 from "js-yaml";
|
|
10820
11005
|
async function listChanges() {
|
|
10821
11006
|
const cwd = process.cwd();
|
|
10822
|
-
const changesDir =
|
|
11007
|
+
const changesDir = join27(cwd, "specs", "changes");
|
|
10823
11008
|
log.blank();
|
|
10824
11009
|
const active = [];
|
|
10825
|
-
if (
|
|
11010
|
+
if (existsSync25(changesDir)) {
|
|
10826
11011
|
active.push(...readdirSync13(changesDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name));
|
|
10827
11012
|
}
|
|
10828
11013
|
if (active.length === 0) {
|
|
@@ -10830,12 +11015,12 @@ async function listChanges() {
|
|
|
10830
11015
|
} else {
|
|
10831
11016
|
log.info("Active changes:");
|
|
10832
11017
|
for (const id of active) {
|
|
10833
|
-
const tasksPath =
|
|
11018
|
+
const tasksPath = join27(changesDir, id, "tasks.yml");
|
|
10834
11019
|
let status = "in-progress";
|
|
10835
11020
|
let pending = 0;
|
|
10836
|
-
if (
|
|
11021
|
+
if (existsSync25(tasksPath)) {
|
|
10837
11022
|
try {
|
|
10838
|
-
const raw =
|
|
11023
|
+
const raw = readFileSync28(tasksPath, "utf8");
|
|
10839
11024
|
const data = yaml6.load(raw);
|
|
10840
11025
|
if (data?.status)
|
|
10841
11026
|
status = data.status;
|
|
@@ -10867,8 +11052,8 @@ __export(context_exports, {
|
|
|
10867
11052
|
rejectContextExpansion: () => rejectContextExpansion,
|
|
10868
11053
|
requestContextExpansion: () => requestContextExpansion
|
|
10869
11054
|
});
|
|
10870
|
-
import { existsSync as
|
|
10871
|
-
import { join as
|
|
11055
|
+
import { existsSync as existsSync26, readFileSync as readFileSync29, writeFileSync as writeFileSync15 } from "fs";
|
|
11056
|
+
import { join as join28 } from "path";
|
|
10872
11057
|
import picomatch2 from "picomatch";
|
|
10873
11058
|
function normalizePath(path) {
|
|
10874
11059
|
return path.replace(/\\/g, "/").replace(/^\.\//, "").trim();
|
|
@@ -10883,18 +11068,18 @@ function validateRepoRelativePath(path) {
|
|
|
10883
11068
|
return null;
|
|
10884
11069
|
}
|
|
10885
11070
|
function manifestPathFor(changeId) {
|
|
10886
|
-
return
|
|
11071
|
+
return join28(process.cwd(), "specs", "changes", changeId, "context-manifest.md");
|
|
10887
11072
|
}
|
|
10888
11073
|
function readManifest(changeId) {
|
|
10889
11074
|
const manifestPath = manifestPathFor(changeId);
|
|
10890
|
-
if (!
|
|
11075
|
+
if (!existsSync26(manifestPath)) {
|
|
10891
11076
|
log.error(`context manifest not found: specs/changes/${changeId}/context-manifest.md`);
|
|
10892
11077
|
process.exit(1);
|
|
10893
11078
|
}
|
|
10894
|
-
return
|
|
11079
|
+
return readFileSync29(manifestPath, "utf8");
|
|
10895
11080
|
}
|
|
10896
11081
|
function writeManifest(changeId, content) {
|
|
10897
|
-
|
|
11082
|
+
writeFileSync15(manifestPathFor(changeId), content.endsWith("\n") ? content : `${content}
|
|
10898
11083
|
`, "utf8");
|
|
10899
11084
|
}
|
|
10900
11085
|
function sectionBody(content, heading) {
|
|
@@ -10933,11 +11118,11 @@ function pathMatches(relPath, patterns, currentChangeId) {
|
|
|
10933
11118
|
});
|
|
10934
11119
|
}
|
|
10935
11120
|
function loadContextPolicy() {
|
|
10936
|
-
const policyPath =
|
|
10937
|
-
if (!
|
|
11121
|
+
const policyPath = join28(process.cwd(), ".cdd", "context-policy.json");
|
|
11122
|
+
if (!existsSync26(policyPath))
|
|
10938
11123
|
return { forbiddenPaths: DEFAULT_FORBIDDEN_PATHS };
|
|
10939
11124
|
try {
|
|
10940
|
-
const custom = JSON.parse(
|
|
11125
|
+
const custom = JSON.parse(readFileSync29(policyPath, "utf8"));
|
|
10941
11126
|
return {
|
|
10942
11127
|
forbiddenPaths: Array.from(/* @__PURE__ */ new Set([
|
|
10943
11128
|
...DEFAULT_FORBIDDEN_PATHS,
|
|
@@ -11219,9 +11404,10 @@ var init_context = __esm({
|
|
|
11219
11404
|
});
|
|
11220
11405
|
|
|
11221
11406
|
// src/cli/index.ts
|
|
11222
|
-
import { readFileSync as
|
|
11407
|
+
import { readFileSync as readFileSync30 } from "fs";
|
|
11408
|
+
import os from "os";
|
|
11223
11409
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
11224
|
-
import { dirname as dirname7, join as
|
|
11410
|
+
import { dirname as dirname7, join as join29 } from "path";
|
|
11225
11411
|
import { Command } from "commander";
|
|
11226
11412
|
|
|
11227
11413
|
// src/commands/init.ts
|
|
@@ -12327,7 +12513,7 @@ async function installHooks() {
|
|
|
12327
12513
|
|
|
12328
12514
|
// src/cli/index.ts
|
|
12329
12515
|
var __dirname2 = dirname7(fileURLToPath3(import.meta.url));
|
|
12330
|
-
var pkg = JSON.parse(
|
|
12516
|
+
var pkg = JSON.parse(readFileSync30(join29(__dirname2, "..", "..", "package.json"), "utf8"));
|
|
12331
12517
|
var program = new Command();
|
|
12332
12518
|
program.name("cdd-kit").description("Contract-Driven Delivery Kit CLI").version(pkg.version);
|
|
12333
12519
|
program.command("init").description(
|
|
@@ -12388,11 +12574,24 @@ function collectRepeatable(val, acc) {
|
|
|
12388
12574
|
acc.push(val);
|
|
12389
12575
|
return acc;
|
|
12390
12576
|
}
|
|
12391
|
-
|
|
12577
|
+
function resolveWorkers(value) {
|
|
12578
|
+
if (value === void 0)
|
|
12579
|
+
return 0;
|
|
12580
|
+
const cpus = Math.max(1, (os.cpus()?.length ?? 2) - 1);
|
|
12581
|
+
if (value === true)
|
|
12582
|
+
return Math.min(cpus, 16);
|
|
12583
|
+
const n = parseInt(String(value), 10);
|
|
12584
|
+
if (!Number.isFinite(n) || n < 1)
|
|
12585
|
+
return 1;
|
|
12586
|
+
return Math.min(n, 16);
|
|
12587
|
+
}
|
|
12588
|
+
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
12589
|
const { codeMap: codeMap2 } = await Promise.resolve().then(() => (init_code_map(), code_map_exports));
|
|
12393
12590
|
const exit = await codeMap2({
|
|
12394
12591
|
path: path ?? ".",
|
|
12395
12592
|
out: opts.out,
|
|
12593
|
+
surface: opts.surface,
|
|
12594
|
+
workers: resolveWorkers(opts.workers),
|
|
12396
12595
|
include: opts.include,
|
|
12397
12596
|
exclude: opts.exclude,
|
|
12398
12597
|
check: opts.check,
|
|
@@ -12400,6 +12599,11 @@ program.command("code-map [path]").description("Scan source files and emit a str
|
|
|
12400
12599
|
});
|
|
12401
12600
|
process.exit(exit);
|
|
12402
12601
|
});
|
|
12602
|
+
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) => {
|
|
12603
|
+
const { runScanWorker: runScanWorker2 } = await Promise.resolve().then(() => (init_code_map_scan_worker(), code_map_scan_worker_exports));
|
|
12604
|
+
const exit = await runScanWorker2({ lang: opts.lang, batchFile: opts.batchFile, repoRoot: opts.repoRoot });
|
|
12605
|
+
process.exit(exit);
|
|
12606
|
+
});
|
|
12403
12607
|
var index = program.command("index").description("Query machine-readable project indexes before opening source files");
|
|
12404
12608
|
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
12609
|
const { indexQuery: indexQuery2 } = await Promise.resolve().then(() => (init_index_query(), index_query_exports));
|