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 +52 -0
- package/dist/cli/index.js +93 -3
- package/package.json +1 -1
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
|
-
|
|
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.
|
|
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",
|