contract-driven-delivery 2.0.1 → 2.0.3

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,37 @@
1
1
  # Changelog
2
2
 
3
+ ## [2.0.3] - 2026-04-30
4
+
5
+ ### Fixed
6
+
7
+ - `cdd-kit update` now syncs all installed skills (`cdd-new`, `cdd-close`,
8
+ `cdd-resume`, `cdd-init`, `contract-driven-delivery`) instead of only
9
+ `contract-driven-delivery`. Previously the four standalone skills were silently
10
+ left stale after an npm upgrade.
11
+ - Backup path corrected from `.cdd-kit-backup/<ts>/skill/` to `.../skills/`
12
+ to cover all skill directories.
13
+
14
+ ## [2.0.2] - 2026-04-30
15
+
16
+ ### Added
17
+
18
+ - `npm postinstall` hook: after `npm install -g` or `npm update -g`, skills and
19
+ agents in `~/.claude/` are automatically synced to the newly installed version.
20
+ The sync is a no-op when `cdd-kit init` has never been run (safe for CI and
21
+ local dev installs of the package itself). A backup of any locally modified
22
+ files is created in `~/.claude/.cdd-kit-backup/<timestamp>/` before overwriting,
23
+ matching the existing `cdd-kit update --yes` behaviour.
24
+ - `cdd-kit update --postinstall` flag (internal): quiet mode that implies
25
+ `--yes`, locks provider to `claude`, and silently exits when the skill
26
+ directory is absent. Not intended for direct user invocation.
27
+
28
+ ### Migration note
29
+
30
+ The first `npm update -g contract-driven-delivery` that brings in this version
31
+ will **not** auto-sync (the postinstall hook did not exist in the previously
32
+ installed version). Run `cdd-kit update --yes` once after this upgrade; all
33
+ subsequent upgrades will auto-sync.
34
+
3
35
  ## [2.0.1] - 2026-04-30
4
36
 
5
37
  ### Fixed
@@ -0,0 +1,20 @@
1
+ #!/usr/bin/env node
2
+ import { spawnSync } from 'node:child_process';
3
+ import { existsSync } from 'node:fs';
4
+ import { join, dirname } from 'node:path';
5
+ import { fileURLToPath } from 'node:url';
6
+
7
+ const __dirname = dirname(fileURLToPath(import.meta.url));
8
+
9
+ try {
10
+ const cliPath = join(__dirname, '..', 'dist', 'cli', 'index.js');
11
+ if (!existsSync(cliPath)) process.exit(0);
12
+
13
+ spawnSync(process.execPath, [cliPath, 'update', '--postinstall'], {
14
+ stdio: 'inherit',
15
+ timeout: 30_000,
16
+ });
17
+ process.exit(0);
18
+ } catch {
19
+ process.exit(0);
20
+ }
package/dist/cli/index.js CHANGED
@@ -11673,7 +11673,16 @@ function backupDir(dir, backupDest) {
11673
11673
  walk(dir, backupDest);
11674
11674
  }
11675
11675
  async function update(opts) {
11676
- log.blank();
11676
+ if (opts.postinstall) {
11677
+ if (!existsSync5(join6(SKILLS_HOME, "contract-driven-delivery"))) {
11678
+ return;
11679
+ }
11680
+ opts.yes = true;
11681
+ opts.provider = "claude";
11682
+ }
11683
+ const quiet = !!opts.postinstall;
11684
+ if (!quiet)
11685
+ log.blank();
11677
11686
  const cwd = process.cwd();
11678
11687
  const requestedProvider = opts.provider ?? "auto";
11679
11688
  if (!validateProviderOption(requestedProvider)) {
@@ -11682,35 +11691,38 @@ async function update(opts) {
11682
11691
  }
11683
11692
  const provider = inferProvider(cwd, requestedProvider);
11684
11693
  const updateClaudeAssets = provider === "claude" || provider === "both";
11685
- const skillDest = join6(SKILLS_HOME, "contract-driven-delivery");
11686
11694
  const agentDiff = updateClaudeAssets ? diffDir(ASSET.agents, AGENTS_HOME) : [];
11687
- const skillDiff = updateClaudeAssets ? diffDir(ASSET.skill, skillDest) : [];
11695
+ const skillDiff = updateClaudeAssets ? readdirSync3(ASSET.skills, { withFileTypes: true }).filter((d) => d.isDirectory()).flatMap((d) => diffDir(join6(ASSET.skills, d.name), join6(SKILLS_HOME, d.name))) : [];
11688
11696
  const toWrite = [...agentDiff, ...skillDiff].filter((e) => e.action !== "skip");
11689
11697
  const toAdd = toWrite.filter((e) => e.action === "add");
11690
11698
  const toOver = toWrite.filter((e) => e.action === "overwrite");
11691
11699
  const toSkip = [...agentDiff, ...skillDiff].filter((e) => e.action === "skip");
11692
- log.info(`Provider: ${provider}`);
11693
- if (updateClaudeAssets) {
11694
- log.info(`Dry-run diff \u2014 agents: ${AGENTS_HOME}`);
11695
- log.info(`Dry-run diff \u2014 skill: ${skillDest}`);
11696
- } else {
11697
- log.info("Codex provider has no global cdd-kit assets to update.");
11698
- log.info("Project files are preserved; run cdd-kit init --local-only --provider codex to add missing local guidance.");
11700
+ if (!quiet) {
11701
+ log.info(`Provider: ${provider}`);
11702
+ if (updateClaudeAssets) {
11703
+ log.info(`Dry-run diff \u2014 agents: ${AGENTS_HOME}`);
11704
+ log.info(`Dry-run diff \u2014 skills: ${SKILLS_HOME}`);
11705
+ } else {
11706
+ log.info("Codex provider has no global cdd-kit assets to update.");
11707
+ log.info("Project files are preserved; run cdd-kit init --local-only --provider codex to add missing local guidance.");
11708
+ }
11709
+ log.blank();
11710
+ if (toAdd.length)
11711
+ log.info(` + ${toAdd.length} file(s) would be added`);
11712
+ if (toOver.length)
11713
+ log.warn(` ~ ${toOver.length} file(s) would be overwritten (user edits lost without backup)`);
11714
+ if (toSkip.length)
11715
+ log.dim(` ${toSkip.length} file(s) unchanged (skipped)`);
11699
11716
  }
11700
- log.blank();
11701
- if (toAdd.length)
11702
- log.info(` + ${toAdd.length} file(s) would be added`);
11703
- if (toOver.length)
11704
- log.warn(` ~ ${toOver.length} file(s) would be overwritten (user edits lost without backup)`);
11705
- if (toSkip.length)
11706
- log.dim(` ${toSkip.length} file(s) unchanged (skipped)`);
11707
11717
  if (toWrite.length === 0) {
11708
- log.blank();
11709
- log.ok("Already up to date \u2014 nothing to write.");
11710
- log.blank();
11718
+ if (!quiet) {
11719
+ log.blank();
11720
+ log.ok("Already up to date \u2014 nothing to write.");
11721
+ log.blank();
11722
+ }
11711
11723
  return;
11712
11724
  }
11713
- if (!opts.yes) {
11725
+ if (!quiet && !opts.yes) {
11714
11726
  log.blank();
11715
11727
  log.info("Run with --yes to apply changes. Example:");
11716
11728
  log.dim(" cdd-kit update --yes");
@@ -11719,25 +11731,41 @@ async function update(opts) {
11719
11731
  }
11720
11732
  const timestamp2 = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
11721
11733
  const backupRoot = join6(homedir2(), ".claude", ".cdd-kit-backup", timestamp2);
11722
- log.blank();
11723
- log.info(`Backing up to ${backupRoot} \u2026`);
11734
+ if (!quiet) {
11735
+ log.blank();
11736
+ log.info(`Backing up to ${backupRoot} \u2026`);
11737
+ }
11724
11738
  backupDir(AGENTS_HOME, join6(backupRoot, "agents"));
11725
- backupDir(skillDest, join6(backupRoot, "skill"));
11726
- log.ok(`Backup complete: ${backupRoot}`);
11727
- log.blank();
11739
+ backupDir(SKILLS_HOME, join6(backupRoot, "skills"));
11740
+ if (!quiet)
11741
+ log.ok(`Backup complete: ${backupRoot}`);
11742
+ if (!quiet)
11743
+ log.blank();
11744
+ let totalSynced = 0;
11728
11745
  if (updateClaudeAssets) {
11729
- log.info(`Updating agents \u2192 ${AGENTS_HOME}`);
11746
+ if (!quiet)
11747
+ log.info(`Updating agents \u2192 ${AGENTS_HOME}`);
11730
11748
  const agentCount = applyDir(agentDiff);
11731
- log.ok(`${agentCount} agent file(s) updated.`);
11732
- log.info(`Updating skill \u2192 ${skillDest}`);
11749
+ if (!quiet)
11750
+ log.ok(`${agentCount} agent file(s) updated.`);
11751
+ totalSynced += agentCount;
11752
+ if (!quiet)
11753
+ log.info(`Updating skills \u2192 ${SKILLS_HOME}`);
11733
11754
  const skillCount = applyDir(skillDiff);
11734
- log.ok(`${skillCount} skill file(s) updated.`);
11755
+ if (!quiet)
11756
+ log.ok(`${skillCount} skill file(s) updated.`);
11757
+ totalSynced += skillCount;
11758
+ }
11759
+ if (quiet) {
11760
+ if (totalSynced > 0)
11761
+ log.ok(`cdd-kit: synced ${totalSynced} file(s) to ~/.claude/`);
11762
+ } else {
11763
+ log.blank();
11764
+ log.info("Project files (contracts/, specs/, tests/, ci/) were not changed.");
11765
+ log.ok("Update complete.");
11766
+ log.info(`Backup saved to: ${backupRoot}`);
11767
+ log.blank();
11735
11768
  }
11736
- log.blank();
11737
- log.info("Project files (contracts/, specs/, tests/, ci/) were not changed.");
11738
- log.ok("Update complete.");
11739
- log.info(`Backup saved to: ${backupRoot}`);
11740
- log.blank();
11741
11769
  }
11742
11770
 
11743
11771
  // src/commands/new-change.ts
@@ -12657,7 +12685,7 @@ program.command("init").description(
12657
12685
  provider: opts.provider
12658
12686
  })
12659
12687
  );
12660
- program.command("update").description("Update provider assets for the current project (does not overwrite project guidance files)").option("--yes", "Apply changes (default is dry-run)", false).option("--provider <provider>", "Provider adapter to update: auto, claude, codex, or both", "auto").action((opts) => update({ yes: opts.yes, provider: opts.provider }));
12688
+ program.command("update").description("Update provider assets for the current project (does not overwrite project guidance files)").option("--yes", "Apply changes (default is dry-run)", false).option("--provider <provider>", "Provider adapter to update: auto, claude, codex, or both", "auto").option("--postinstall", "Internal: invoked by npm postinstall; no-op if cdd has not been init-ed", false).action((opts) => update({ yes: opts.yes, provider: opts.provider, postinstall: opts.postinstall }));
12661
12689
  program.command("doctor").description("Inspect cdd-kit repo health, provider guidance, and context index freshness").option("--strict", "Treat warnings as errors", false).option("--json", "Print a machine-readable health report", false).option("--provider <provider>", "Provider adapter to inspect: auto, claude, codex, or both", "auto").option("--fix", "Auto-resolve safe warnings (stale context indexes, missing role bindings)", false).action(async (opts) => {
12662
12690
  const { doctor: doctor2 } = await Promise.resolve().then(() => (init_doctor(), doctor_exports));
12663
12691
  await doctor2({ strict: opts.strict, json: opts.json, provider: opts.provider, fix: opts.fix });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "contract-driven-delivery",
3
- "version": "2.0.1",
3
+ "version": "2.0.3",
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",
@@ -40,7 +40,8 @@
40
40
  "build": "node build.js",
41
41
  "test": "vitest run",
42
42
  "test:watch": "vitest",
43
- "prepublishOnly": "node build.js && vitest run"
43
+ "prepublishOnly": "node build.js && vitest run",
44
+ "postinstall": "node bin/postinstall.js"
44
45
  },
45
46
  "engines": {
46
47
  "node": ">=18.0.0"