contract-driven-delivery 2.0.8 → 2.0.9

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 CHANGED
@@ -1,5 +1,57 @@
1
1
  # Changelog
2
2
 
3
+ ## [2.0.9] - 2026-05-04
4
+
5
+ Bug-fix patch. Discovered when verifying a real consumer repo (TODOLIST)
6
+ after a successful 2.0.8 upgrade: the user's committed
7
+ `specs/context/project-map.md` listed local kit-generated backup directories
8
+ (`.cdd/.refresh-backup/...`, `.cdd/migrate-backup/...`), which then never
9
+ matched fresh-clone digests — `cdd-kit doctor` reported "inputs changed"
10
+ forever even when nothing was wrong.
11
+
12
+ ### Fixed
13
+
14
+ - **`cdd-kit context-scan` excludes kit runtime artifacts AND common
15
+ transient cache directories at any depth**:
16
+ - `DEFAULT_FORBIDDEN` (path-prefix list) now includes
17
+ `.cdd/.refresh-backup`, `.cdd/migrate-backup`, `.cdd/runtime`.
18
+ - New `FORBIDDEN_DIRECTORY_NAMES` (basename-anywhere list) catches
19
+ Python (`__pycache__`, `.pytest_cache`, `.mypy_cache`, `.ruff_cache`,
20
+ `.tox`), JS/TS framework caches (`.next`, `.nuxt`, `.svelte-kit`,
21
+ `.parcel-cache`, `.turbo`, `.nyc_output`), generic build/coverage
22
+ (`coverage`, `htmlcov`, `.cache`), virtualenvs (`venv`, `.venv`),
23
+ nested `node_modules`, and IDE noise (`.idea`, `.vscode`, `.DS_Store`).
24
+ - Mirrors `code-map`'s `BUILTIN_EXCLUDE` so the two indexes ignore the
25
+ same noise. Previously context-scan only had 8 path-prefix entries
26
+ (`.claude / .git / node_modules / dist / build / assets / specs/archive /
27
+ specs/changes`), so locally-generated caches polluted the project-map
28
+ tree section and broke `inputs-digest` determinism for fresh clones.
29
+
30
+ ### Added
31
+
32
+ - **`cdd-kit refresh`** auto-appends `.cdd/.refresh-backup/` to `.gitignore`
33
+ the first time it overwrites a template (idempotent — no duplicate
34
+ entries on subsequent runs). Logs the action so users know what changed.
35
+ - **`cdd-kit migrate`** auto-appends `.cdd/migrate-backup/` to `.gitignore`
36
+ with identical idempotent semantics.
37
+
38
+ ### Migration
39
+
40
+ If you already committed a polluted `specs/context/project-map.md` (signs:
41
+ the tree section contains paths under `.cdd/.refresh-backup/` or
42
+ `.cdd/migrate-backup/`, doctor reports "inputs changed" after a successful
43
+ refresh), recover with:
44
+
45
+ ```bash
46
+ echo ".cdd/.refresh-backup/" >> .gitignore
47
+ rm -rf .cdd/.refresh-backup
48
+ cdd-kit context-scan
49
+ git add .gitignore specs/context/
50
+ git commit -m "fix: exclude cdd-kit refresh-backup from context-scan"
51
+ ```
52
+
53
+ Future refreshes will handle the gitignore entry automatically.
54
+
3
55
  ## [2.0.8] - 2026-05-04
4
56
 
5
57
  Adds `cdd-kit refresh` — a one-shot complete upgrade command. The previous
package/dist/cli/index.js CHANGED
@@ -415,7 +415,14 @@ function getForbiddenPaths(cwd) {
415
415
  }
416
416
  function isForbidden(relPath, forbidden) {
417
417
  const normalized = relPath.replace(/\\/g, "/");
418
- return forbidden.some((pattern) => normalized === pattern || normalized.startsWith(`${pattern}/`));
418
+ if (forbidden.some((pattern) => normalized === pattern || normalized.startsWith(`${pattern}/`))) {
419
+ return true;
420
+ }
421
+ for (const segment of normalized.split("/")) {
422
+ if (FORBIDDEN_DIRECTORY_NAMES.has(segment))
423
+ return true;
424
+ }
425
+ return false;
419
426
  }
420
427
  function buildTree(dir, cwd, forbidden, stats, prefix = "", depth = 0) {
421
428
  const entries = readdirSync4(dir, { withFileTypes: true }).sort((a, b) => {
@@ -632,7 +639,7 @@ async function contextScan(opts = {}) {
632
639
  log.ok("Created specs/context/contracts-index.md");
633
640
  }
634
641
  }
635
- var DEFAULT_FORBIDDEN, PER_DIR_ENTRY_CAP;
642
+ var DEFAULT_FORBIDDEN, FORBIDDEN_DIRECTORY_NAMES, PER_DIR_ENTRY_CAP;
636
643
  var init_context_scan = __esm({
637
644
  "src/commands/context-scan.ts"() {
638
645
  "use strict";
@@ -645,8 +652,43 @@ var init_context_scan = __esm({
645
652
  "build",
646
653
  "assets",
647
654
  "specs/archive",
648
- "specs/changes"
655
+ "specs/changes",
656
+ // cdd-kit runtime artifacts. Without these the user's local backups land
657
+ // in `specs/context/project-map.md`, polluting it and breaking the
658
+ // inputs-digest match for any fresh clone.
659
+ ".cdd/.refresh-backup",
660
+ ".cdd/migrate-backup",
661
+ ".cdd/runtime"
649
662
  ];
663
+ FORBIDDEN_DIRECTORY_NAMES = /* @__PURE__ */ new Set([
664
+ // Node — also caught by top-level prefix, but a nested
665
+ // `frontend/node_modules` requires basename matching
666
+ "node_modules",
667
+ // Python
668
+ "__pycache__",
669
+ ".pytest_cache",
670
+ ".mypy_cache",
671
+ ".ruff_cache",
672
+ ".tox",
673
+ // JS/TS frameworks
674
+ ".next",
675
+ ".nuxt",
676
+ ".svelte-kit",
677
+ ".parcel-cache",
678
+ ".turbo",
679
+ ".nyc_output",
680
+ // Generic build / coverage caches
681
+ "coverage",
682
+ "htmlcov",
683
+ ".cache",
684
+ // Virtualenvs
685
+ "venv",
686
+ ".venv",
687
+ // IDE / OS noise
688
+ ".idea",
689
+ ".vscode",
690
+ ".DS_Store"
691
+ ]);
650
692
  PER_DIR_ENTRY_CAP = 50;
651
693
  }
652
694
  });
@@ -8057,11 +8099,35 @@ async function migrate(changeId, opts = {}) {
8057
8099
  log.ok(`Migration complete: ${migratedCount} updated, ${upToDateCount} already up to date.`);
8058
8100
  if (migratedCount > 0 && !noBackup) {
8059
8101
  log.info(`Backup: ${backupRoot}`);
8102
+ if (ensureGitignoreEntry(cwd, ".cdd/migrate-backup/")) {
8103
+ log.info("Added `.cdd/migrate-backup/` to .gitignore");
8104
+ }
8060
8105
  log.info('Next: git add specs/changes/ && git commit -m "chore: migrate changes to YAML format"');
8061
8106
  log.info("When stable, remove backup: rm -rf .cdd/migrate-backup/");
8062
8107
  }
8063
8108
  }
8064
8109
  }
8110
+ function ensureGitignoreEntry(cwd, entry) {
8111
+ const path = join16(cwd, ".gitignore");
8112
+ const trimmed = entry.trim();
8113
+ if (!trimmed)
8114
+ return false;
8115
+ const re = new RegExp(`^\\s*${trimmed.replace(/[.*+?^${}()|[\\]\\\\]/g, "\\$&")}\\s*$`, "m");
8116
+ let existing = "";
8117
+ if (existsSync14(path))
8118
+ existing = readFileSync11(path, "utf8");
8119
+ if (re.test(existing))
8120
+ return false;
8121
+ const sep = existing.length > 0 && !existing.endsWith("\n") ? "\n" : "";
8122
+ const block = existing.length === 0 ? `# cdd-kit generated backups (do not commit)
8123
+ ${trimmed}
8124
+ ` : `${sep}
8125
+ # cdd-kit generated backups (do not commit)
8126
+ ${trimmed}
8127
+ `;
8128
+ writeFileSync6(path, existing + block, "utf8");
8129
+ return true;
8130
+ }
8065
8131
  var init_migrate = __esm({
8066
8132
  "src/commands/migrate.ts"() {
8067
8133
  "use strict";
@@ -9178,6 +9244,27 @@ function planSingleFile(src, dest, rel) {
9178
9244
  return { src, dest, rel, action: "overwrite" };
9179
9245
  return { src, dest, rel, action: "skip" };
9180
9246
  }
9247
+ function ensureGitignoreEntry2(cwd, entry) {
9248
+ const path = join20(cwd, ".gitignore");
9249
+ const trimmed = entry.trim();
9250
+ if (!trimmed)
9251
+ return false;
9252
+ const re = new RegExp(`^\\s*${trimmed.replace(/[.*+?^${}()|[\\]\\\\]/g, "\\$&")}\\s*$`, "m");
9253
+ let existing = "";
9254
+ if (existsSync17(path))
9255
+ existing = readFileSync18(path, "utf8");
9256
+ if (re.test(existing))
9257
+ return false;
9258
+ const sep = existing.length > 0 && !existing.endsWith("\n") ? "\n" : "";
9259
+ const block = existing.length === 0 ? `# cdd-kit generated backups (do not commit)
9260
+ ${trimmed}
9261
+ ` : `${sep}
9262
+ # cdd-kit generated backups (do not commit)
9263
+ ${trimmed}
9264
+ `;
9265
+ writeFileSync10(path, existing + block, "utf8");
9266
+ return true;
9267
+ }
9181
9268
  function applyPlan(plan, backupRoot) {
9182
9269
  let added = 0;
9183
9270
  let overwritten = 0;
@@ -9336,6 +9423,9 @@ async function refresh(opts) {
9336
9423
  log.ok(` applied: +${templateAdded} added, ~${templateOverwritten} overwritten`);
9337
9424
  if (templateOverwritten > 0) {
9338
9425
  log.info(` backup saved to: ${relative5(cwd, backupRoot).replace(/\\/g, "/")}`);
9426
+ if (ensureGitignoreEntry2(cwd, ".cdd/.refresh-backup/")) {
9427
+ log.info(" added `.cdd/.refresh-backup/` to .gitignore");
9428
+ }
9339
9429
  }
9340
9430
  } else {
9341
9431
  log.dim(" (dry-run \u2014 no changes written)");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "contract-driven-delivery",
3
- "version": "2.0.8",
3
+ "version": "2.0.9",
4
4
  "description": "Contract-driven delivery kit for AI coding agents with deterministic context indexes, manifest-backed read-scope governance, and orchestrated contracts-first delivery.",
5
5
  "keywords": [
6
6
  "contract-driven",