contract-driven-delivery 2.0.14 → 2.0.16

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,42 @@
1
1
  # Changelog
2
2
 
3
+ ## [2.0.16] - 2026-05-06
4
+
5
+ New-change scaffold hardening so freshly opened proposals use the installed
6
+ kit version even when an existing project has stale templates on disk.
7
+
8
+ ### Changed
9
+
10
+ - **`cdd-kit new` stamps `tasks.yml` with the real change id**: new changes no
11
+ longer start with the `<change-id>` placeholder in the machine-validated task
12
+ metadata.
13
+
14
+ ### Fixed
15
+
16
+ - **Fresh proposals ignore stale project templates**: regression coverage now
17
+ locks `cdd-kit new` to bundled package templates, so old
18
+ `specs/templates/*` files in a user repo cannot leak into a newly created
19
+ change.
20
+ - **Postinstall sync coverage includes workflow skills**: regression coverage
21
+ now verifies npm postinstall updates standalone skills such as `/cdd-new`,
22
+ keeping agent-log instructions aligned with the installed gate.
23
+
24
+ ## [2.0.15] - 2026-05-06
25
+
26
+ Prompt guidance patch for agent-log evidence and closeout learning ownership.
27
+
28
+ ### Changed
29
+
30
+ - **Agent-log pointer guidance matches gate behavior**: `/cdd-new` and
31
+ `agent-log-protocol.md` now spell out that a pointer whose text before the
32
+ first `:` contains `/` is validated as a single repo-relative file path, so
33
+ agents avoid parenthetical path notes and slash-containing labels such as
34
+ `I/O:` or `WARNING/OVERDUE:`.
35
+ - **Durable learning ownership is explicit**: prompts now consistently say
36
+ general agents record evidence and findings only, while durable learning
37
+ promotion happens during `/cdd-close` Step 3 and targets `contracts/` or
38
+ project guidance (`CLAUDE.md`/`CODEX.md`).
39
+
3
40
  ## [2.0.14] - 2026-05-06
4
41
 
5
42
  Operational hardening for real multi-agent CDD runs.
package/README.md CHANGED
@@ -190,7 +190,7 @@ After the PR is merged:
190
190
  **What happens:**
191
191
  1. Runs `cdd-kit gate` to confirm the change still passes
192
192
  2. Synthesizes `archive.md` — a permanent record of what changed, what tests were added, and what lessons were found
193
- 3. Invokes `contract-reviewer` to propose any durable learnings back into `contracts/`
193
+ 3. Promotes only evidence-backed durable learnings to `contracts/` or project guidance (`CLAUDE.md`/`CODEX.md`). General agents record evidence and findings only; durable learning promotion happens during `/cdd-close` Step 3.
194
194
  4. Runs `cdd-kit archive add-jwt-auth` — moves the change from `specs/changes/` to `specs/archive/2026/`
195
195
  5. Reduces the active context that future Claude sessions need to load
196
196
 
@@ -10,7 +10,7 @@ description: Close and archive a completed change. Confirms all tasks are done,
10
10
  A change is "done" when:
11
11
  1. Gate has passed (`cdd-kit gate <change-id>` exits 0)
12
12
  2. PR is merged (or change is abandoned)
13
- 3. Durable learnings have been promoted to hot sources: `contracts/`, `CLAUDE.md`, or `CODEX.md`
13
+ 3. Durable learnings have been promoted to hot sources: `contracts/` or project guidance (`CLAUDE.md`/`CODEX.md`)
14
14
 
15
15
  This skill drives steps 2–3 and physically moves the change to `specs/archive/`.
16
16
 
@@ -58,7 +58,7 @@ Read `specs/changes/<change-id>/tasks.yml`.
58
58
 
59
59
  Check section 7:
60
60
  - `7.1 Archive change` — will be ticked after Step 4
61
- - `7.2 Promote durable learnings to contracts or CLAUDE.md` — must be done NOW
61
+ - `7.2 Promote durable learnings to contracts or project guidance` — must be done NOW
62
62
 
63
63
  If `7.2` is `[ ]`, proceed to Step 2.5. If already `[x]` or `[-]`, skip Steps 2.5 and 3.
64
64
 
@@ -92,6 +92,11 @@ This file records the close-out evidence, but Step 3 promotion must still be evi
92
92
 
93
93
  ## Step 3: Promote learnings (task 7.2)
94
94
 
95
+ General agents do not perform durable learning promotion during `/cdd-new`; they
96
+ only record evidence and findings in artifacts and `agent-log/*.yml`. Durable
97
+ learning promotion happens here, during `/cdd-close` Step 3, and main Claude
98
+ owns the final writes.
99
+
95
100
  Read `specs/changes/<change-id>/archive.md` section `## Lessons Promoted to Standards` and cross-check every proposed lesson against agent-log, QA report, contract/test changes, or gate evidence from this change.
96
101
 
97
102
  Classify each candidate:
@@ -132,7 +137,7 @@ If successful, set task `7.1` to `status: done` in tasks.yml (the file is now in
132
137
 
133
138
  Change ID: <change-id>
134
139
  Archived to: specs/archive/<year>/<change-id>/
135
- Learnings promoted: <list what was added to contracts/CLAUDE.md/CODEX.md, or "none">
140
+ Learnings promoted: <list what was added to contracts/ or project guidance (CLAUDE.md/CODEX.md), or "none">
136
141
 
137
142
  specs/changes/<change-id>/ has been removed from the active surface.
138
143
  Token cost of future sessions reduced by ~<N> files.
@@ -96,6 +96,21 @@ inevitable re-classification when the agents discover the ambiguity.
96
96
 
97
97
  **Rule**: After EVERY agent completes (whether it writes itself or you write for it), YOU must update the relevant `tasks.yml` task `status:` from `pending` to `done`.
98
98
 
99
+ **Agent-log pointer rule**: When you or an agent writes `artifacts[].pointer`,
100
+ follow `references/agent-log-protocol.md` exactly. If the text before the first
101
+ `:` contains `/`, `cdd-kit gate` treats that text as a repo-relative file path
102
+ and verifies that the file exists. Therefore each pointer may name only one
103
+ file, file pointers must not include parenthetical notes on the path, and
104
+ slash-containing labels such as `I/O:` or `WARNING/OVERDUE:` must not be used as
105
+ pointer prefixes. Put extra explanation in `notes` or a separate non-path
106
+ artifact pointer instead.
107
+
108
+ **Durable learning rule**: During `/cdd-new`, agents record evidence and
109
+ findings in artifacts and `agent-log/*.yml` only. Do not promote durable lessons
110
+ while the change is still active. Durable learning promotion happens only during
111
+ `/cdd-close` Step 3, where main Claude cross-checks evidence and writes approved
112
+ rules to `contracts/` or project guidance (`CLAUDE.md`/`CODEX.md`).
113
+
99
114
  ---
100
115
 
101
116
  ## Artifact opt-in policy
@@ -515,4 +530,4 @@ Please review the above items and re-run: cdd-kit gate <change-id>
515
530
 
516
531
  The `/cdd-new` workflow is now complete. **Return to normal assistant mode immediately.** Answer any question the user asks — including questions unrelated to this change, new feature discussions, debugging help, or general conversation — without requiring them to use a specific command. The git commit shown in the report is a suggestion, not a required next step; do not wait for it before resuming normal behavior.
517
532
 
518
- When the change is merged and ready to close, run `/cdd-close <change-id>` to promote learnings and archive the change directory.
533
+ When the change is merged and ready to close, run `/cdd-close <change-id>` to promote durable learnings to `contracts/` or project guidance (`CLAUDE.md`/`CODEX.md`) and archive the change directory.
@@ -57,7 +57,10 @@ Use this skill to turn software requests into traceable, testable, CI/CD-gated c
57
57
  - Invoke ci-cd-gatekeeper to design and enforce the gate plan.
58
58
  8. Archive and audit drift.
59
59
  - Use `references/spec-drift-policy.md`.
60
- - Durable learnings must be promoted back to contracts or CLAUDE.md.
60
+ - General agents record evidence and findings only; durable learning
61
+ promotion happens only during `/cdd-close` Step 3.
62
+ - Durable learnings must be promoted back to `contracts/` or project
63
+ guidance (`CLAUDE.md`/`CODEX.md`).
61
64
  - `spec-drift-auditor` must run before every release to main and weekly during active multi-iteration development.
62
65
 
63
66
  ## Required gates by risk
@@ -75,6 +75,22 @@ Concrete pointers only. Allowed forms:
75
75
  - `cdd-kit gate <id>: 0 errors`
76
76
  - `contracts/api/api-contract.md#endpoints`
77
77
 
78
+ Gate path-existence rule: unless gate is run with `--lax`, any pointer whose
79
+ text before the first `:` contains `/` is treated as a repo-relative file path
80
+ and that file must exist. This makes path-like pointers useful, but it also
81
+ means:
82
+
83
+ - One pointer names one file only. Use separate `artifacts` items for multiple
84
+ files.
85
+ - Do not attach parenthetical notes to a file path, e.g. use
86
+ `src/api/users.ts:45-67`, not `src/api/users.ts (updated):45-67`.
87
+ - Do not start a pointer with slash-containing prose labels such as `I/O:` or
88
+ `WARNING/OVERDUE:`; gate will try to validate `I/O` or `WARNING/OVERDUE` as a
89
+ path. Write those labels in `notes` or after a non-path command/result
90
+ pointer.
91
+ - `n/a (<reason>)` is exempt from path validation and is allowed for genuinely
92
+ inapplicable required artifact types.
93
+
78
94
  Never `verified`, `OK`, `done`, or unscoped prose.
79
95
 
80
96
  #### `next-action`
@@ -133,7 +149,12 @@ verify each item:
133
149
  - BAD: `{ type: tests-added, pointer: verified }`
134
150
  - BAD: `{ type: files-changed, pointer: yes }`
135
151
  - BAD: `{ type: contract, pointer: OK }`
152
+ - BAD: `{ type: files-changed, pointer: "src/api/users.ts (updated):45-67" }`
153
+ - BAD: `{ type: test-output, pointer: "I/O: warning reproduced" }`
154
+ - BAD: `{ type: test-output, pointer: "WARNING/OVERDUE: manual follow-up" }`
136
155
  Reject any line whose pointer would not let a reviewer click through.
156
+ If the text before the first `:` contains `/`, confirm it is exactly one
157
+ existing repo-relative file path with no parenthetical note.
137
158
  - [ ] **If `status: blocked`**, `next-action` is ≥ 10 chars, is NOT `none`,
138
159
  `investigate further`, `tbd`, or `n/a`, and names the actual next step
139
160
  a human can act on.
@@ -160,8 +181,9 @@ ship a known-bad log and rely on the gate to catch it.
160
181
  `Allowed Paths` and `Approved Expansions`.
161
182
  6. Any `artifacts` item is missing `type` or `pointer`, or the array is empty.
162
183
  7. A required per-agent artifact `type` declared in the agent prompt is missing.
163
- 8. With `--strict`: any `artifacts` pointer that looks like a path but does
164
- not exist on disk; or any runtime-logged read not declared in `files-read`.
184
+ 8. Unless gate is run with `--lax`: any `artifacts` pointer whose text before
185
+ the first `:` contains `/` but does not exist on disk; or any
186
+ runtime-logged read not declared in `files-read`.
165
187
 
166
188
  ## Why this lives in references/
167
189
 
@@ -36,4 +36,4 @@ tasks:
36
36
  - { id: "6.3", section: Verification, title: "Informational gates", status: pending }
37
37
  - { id: "6.4", section: Verification, title: "Nightly/weekly/manual gates if required", status: pending }
38
38
  - { id: "7.1", section: Archive, title: "Archive change", status: pending }
39
- - { id: "7.2", section: Archive, title: "Promote durable learnings to contracts or CLAUDE.md", status: pending }
39
+ - { id: "7.2", section: Archive, title: "Promote durable learnings to contracts or project guidance", status: pending }
package/dist/cli/index.js CHANGED
@@ -8678,7 +8678,7 @@ __export(migrate_exports, {
8678
8678
  });
8679
8679
  import { join as join18 } from "path";
8680
8680
  import { cpSync as cpSync2, existsSync as existsSync15, mkdirSync as mkdirSync7, readdirSync as readdirSync8, readFileSync as readFileSync18, renameSync, rmSync as rmSync2, writeFileSync as writeFileSync8 } from "fs";
8681
- import yaml3 from "js-yaml";
8681
+ import yaml2 from "js-yaml";
8682
8682
  function backupChangeDir(cwd, changeId, sessionStamp) {
8683
8683
  const backupRoot = join18(cwd, ".cdd", "migrate-backup", sessionStamp);
8684
8684
  const backupDir2 = join18(backupRoot, changeId);
@@ -8854,7 +8854,7 @@ function migrateTasksFile(changeId, changeDir, enableContextGovernance, detected
8854
8854
  out["section"] = r.section;
8855
8855
  return out;
8856
8856
  });
8857
- const yamlOut = yaml3.dump(data, { lineWidth: -1, noRefs: true });
8857
+ const yamlOut = yaml2.dump(data, { lineWidth: -1, noRefs: true });
8858
8858
  pendingWrites.push({ path: newPath, content: yamlOut });
8859
8859
  pendingDeletes.push({ path: legacyPath });
8860
8860
  changed.push(`tasks.md -> tasks.yml (${tasksRows.length} task(s) migrated)`);
@@ -8922,7 +8922,7 @@ function migrateAgentLogs(changeDir, changed, pendingWrites, pendingDeletes) {
8922
8922
  continue;
8923
8923
  const raw = readFileSync18(fullPath, "utf8");
8924
8924
  const parsed = parseLegacyAgentLog(raw);
8925
- const yamlOut = yaml3.dump(parsed, { lineWidth: -1, noRefs: true });
8925
+ const yamlOut = yaml2.dump(parsed, { lineWidth: -1, noRefs: true });
8926
8926
  pendingWrites.push({ path: yamlFull, content: yamlOut });
8927
8927
  pendingDeletes.push({ path: fullPath });
8928
8928
  changed.push(`agent-log/${f} -> agent-log/${yamlName}`);
@@ -10146,7 +10146,7 @@ __export(archive_exports, {
10146
10146
  });
10147
10147
  import { join as join23 } from "path";
10148
10148
  import { existsSync as existsSync19, mkdirSync as mkdirSync10, renameSync as renameSync2, readFileSync as readFileSync23, writeFileSync as writeFileSync11, appendFileSync, cpSync as cpSync3, rmSync as rmSync3 } from "fs";
10149
- import yaml4 from "js-yaml";
10149
+ import yaml3 from "js-yaml";
10150
10150
  async function archive(changeId) {
10151
10151
  const cwd = process.cwd();
10152
10152
  const changeDir = join23(cwd, "specs", "changes", changeId);
@@ -10166,7 +10166,7 @@ async function archive(changeId) {
10166
10166
  if (existsSync19(tasksPath)) {
10167
10167
  try {
10168
10168
  const raw = readFileSync23(tasksPath, "utf8");
10169
- const data = yaml4.load(raw);
10169
+ const data = yaml3.load(raw);
10170
10170
  if (data?.status === "gate-blocked") {
10171
10171
  log.warn("tasks.yml has status: gate-blocked \u2014 archiving anyway (change was paused).");
10172
10172
  }
@@ -10222,7 +10222,7 @@ __export(abandon_exports, {
10222
10222
  });
10223
10223
  import { join as join24 } from "path";
10224
10224
  import { existsSync as existsSync20, readFileSync as readFileSync24, writeFileSync as writeFileSync12, appendFileSync as appendFileSync2, mkdirSync as mkdirSync11 } from "fs";
10225
- import yaml5 from "js-yaml";
10225
+ import yaml4 from "js-yaml";
10226
10226
  async function abandon(changeId, opts) {
10227
10227
  const cwd = process.cwd();
10228
10228
  const changeDir = join24(cwd, "specs", "changes", changeId);
@@ -10233,12 +10233,12 @@ async function abandon(changeId, opts) {
10233
10233
  }
10234
10234
  if (existsSync20(tasksPath)) {
10235
10235
  const raw = readFileSync24(tasksPath, "utf8");
10236
- const data = yaml5.load(raw) ?? {};
10236
+ const data = yaml4.load(raw) ?? {};
10237
10237
  data["status"] = "abandoned";
10238
10238
  if (!data["change-id"]) {
10239
10239
  data["change-id"] = changeId;
10240
10240
  }
10241
- writeFileSync12(tasksPath, yaml5.dump(data, { lineWidth: -1, noRefs: true }), "utf8");
10241
+ writeFileSync12(tasksPath, yaml4.dump(data, { lineWidth: -1, noRefs: true }), "utf8");
10242
10242
  }
10243
10243
  const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
10244
10244
  const archiveDir = join24(cwd, "specs", "archive");
@@ -10276,7 +10276,7 @@ __export(list_changes_exports, {
10276
10276
  });
10277
10277
  import { join as join25 } from "path";
10278
10278
  import { existsSync as existsSync21, readdirSync as readdirSync13, readFileSync as readFileSync25 } from "fs";
10279
- import yaml6 from "js-yaml";
10279
+ import yaml5 from "js-yaml";
10280
10280
  async function listChanges() {
10281
10281
  const cwd = process.cwd();
10282
10282
  const changesDir = join25(cwd, "specs", "changes");
@@ -10296,7 +10296,7 @@ async function listChanges() {
10296
10296
  if (existsSync21(tasksPath)) {
10297
10297
  try {
10298
10298
  const raw = readFileSync25(tasksPath, "utf8");
10299
- const data = yaml6.load(raw);
10299
+ const data = yaml5.load(raw);
10300
10300
  if (data?.status)
10301
10301
  status = data.status;
10302
10302
  pending = (data?.tasks ?? []).filter((t) => t.status === "pending").length;
@@ -11104,7 +11104,6 @@ init_paths();
11104
11104
  import { join as join9, relative as relative3 } from "path";
11105
11105
  import { createHash as createHash4 } from "crypto";
11106
11106
  import { existsSync as existsSync8, readFileSync as readFileSync8, readdirSync as readdirSync5, writeFileSync as writeFileSync4 } from "fs";
11107
- import yaml from "js-yaml";
11108
11107
  init_logger();
11109
11108
  init_context_scan();
11110
11109
  init_digest();
@@ -11173,6 +11172,20 @@ function parseDependsOn(raw) {
11173
11172
  return [];
11174
11173
  return raw.split(",").map((item) => item.trim()).filter(Boolean);
11175
11174
  }
11175
+ function applyScaffoldMetadata(tasksPath, changeId, dependencies) {
11176
+ if (!existsSync8(tasksPath))
11177
+ return;
11178
+ let raw = readFileSync8(tasksPath, "utf8");
11179
+ raw = raw.replace(/^change-id:\s*<change-id>\s*$/m, `change-id: ${changeId}`);
11180
+ if (dependencies.length > 0) {
11181
+ const dependsOn = [
11182
+ "depends-on:",
11183
+ ...dependencies.map((dep) => ` - ${JSON.stringify(dep)}`)
11184
+ ].join("\n");
11185
+ raw = raw.replace(/^depends-on:\s*\[\]\s*$/m, dependsOn);
11186
+ }
11187
+ writeFileSync4(tasksPath, raw, "utf8");
11188
+ }
11176
11189
  async function newChange(name, opts) {
11177
11190
  if (!SAFE_NAME.test(name)) {
11178
11191
  log.error(`Invalid change name: "${name}". Use letters, numbers, hyphens, or underscores (max 64 chars).`);
@@ -11220,15 +11233,10 @@ async function newChange(name, opts) {
11220
11233
  log.dim(tmpl);
11221
11234
  written += 1;
11222
11235
  }
11236
+ const tasksPath = join9(changeDir, "tasks.yml");
11237
+ applyScaffoldMetadata(tasksPath, name, dependencies);
11223
11238
  if (dependencies.length > 0) {
11224
- const tasksPath = join9(changeDir, "tasks.yml");
11225
- if (existsSync8(tasksPath)) {
11226
- const raw = readFileSync8(tasksPath, "utf8");
11227
- const data = yaml.load(raw) ?? {};
11228
- data["depends-on"] = dependencies;
11229
- writeFileSync4(tasksPath, yaml.dump(data, { lineWidth: -1, noRefs: true }), "utf8");
11230
- log.dim(`depends-on: ${dependencies.join(", ")}`);
11231
- }
11239
+ log.dim(`depends-on: ${dependencies.join(", ")}`);
11232
11240
  }
11233
11241
  log.blank();
11234
11242
  log.ok(`${written} template(s) created in specs/changes/${name}`);
@@ -11330,7 +11338,7 @@ init_logger();
11330
11338
  import { existsSync as existsSync13, readFileSync as readFileSync16, readdirSync as readdirSync7 } from "fs";
11331
11339
  import { homedir as homedir3 } from "os";
11332
11340
  import { join as join16 } from "path";
11333
- import yaml2 from "js-yaml";
11341
+ import yaml from "js-yaml";
11334
11342
  import picomatch2 from "picomatch";
11335
11343
 
11336
11344
  // src/schemas/agent-log.schema.ts
@@ -11571,7 +11579,7 @@ function loadContextPolicy(cwd) {
11571
11579
  function loadYamlFile(path) {
11572
11580
  try {
11573
11581
  const raw = readFileSync16(path, "utf8");
11574
- return { data: yaml2.load(raw, { schema: yaml2.JSON_SCHEMA }), parseError: null };
11582
+ return { data: yaml.load(raw, { schema: yaml.JSON_SCHEMA }), parseError: null };
11575
11583
  } catch (err) {
11576
11584
  return { data: null, parseError: err.message };
11577
11585
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "contract-driven-delivery",
3
- "version": "2.0.14",
3
+ "version": "2.0.16",
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",