release-skill 0.2.9 → 0.4.0

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.
Files changed (77) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/.codebuddy-plugin/plugin.json +1 -1
  4. package/.codex-plugin/plugin.json +2 -2
  5. package/.kimi-plugin/plugin.json +1 -1
  6. package/CHANGELOG.md +41 -0
  7. package/INSTALL.md +34 -110
  8. package/INSTALL.zh-CN.md +17 -83
  9. package/README.md +49 -41
  10. package/README.zh-CN.md +43 -34
  11. package/adapters/claude/.claude-plugin/marketplace.json +1 -1
  12. package/adapters/claude/.claude-plugin/plugin.json +1 -1
  13. package/adapters/claude/bin/release-skill.bundle.mjs +4131 -3180
  14. package/adapters/claude/schemas/release-plan.schema.json +7 -0
  15. package/adapters/claude/schemas/release-run.schema.json +43 -0
  16. package/adapters/claude/skills/release-help/SKILL.md +10 -2
  17. package/adapters/claude/skills/release-prepare/SKILL.md +6 -15
  18. package/adapters/claude/skills/release-publish/SKILL.md +5 -6
  19. package/adapters/claude/skills/release-reconcile/SKILL.md +2 -2
  20. package/adapters/claude/skills/release-verify/SKILL.md +4 -6
  21. package/adapters/codex/.codex-plugin/plugin.json +2 -2
  22. package/adapters/codex/bin/release-skill.bundle.mjs +4131 -3180
  23. package/adapters/codex/schemas/release-plan.schema.json +7 -0
  24. package/adapters/codex/schemas/release-run.schema.json +43 -0
  25. package/adapters/codex/skills/release-help/SKILL.md +10 -2
  26. package/adapters/codex/skills/release-prepare/SKILL.md +6 -15
  27. package/adapters/codex/skills/release-publish/SKILL.md +5 -6
  28. package/adapters/codex/skills/release-reconcile/SKILL.md +2 -2
  29. package/adapters/codex/skills/release-verify/SKILL.md +4 -6
  30. package/adapters/kimi/.kimi-plugin/plugin.json +1 -1
  31. package/adapters/kimi/bin/release-skill.bundle.mjs +4131 -3180
  32. package/adapters/kimi/schemas/release-plan.schema.json +7 -0
  33. package/adapters/kimi/schemas/release-run.schema.json +43 -0
  34. package/adapters/kimi/skills/release-help/SKILL.md +10 -2
  35. package/adapters/kimi/skills/release-prepare/SKILL.md +6 -15
  36. package/adapters/kimi/skills/release-publish/SKILL.md +5 -6
  37. package/adapters/kimi/skills/release-reconcile/SKILL.md +2 -2
  38. package/adapters/kimi/skills/release-verify/SKILL.md +4 -6
  39. package/adapters/workbuddy/.codebuddy-plugin/plugin.json +1 -1
  40. package/adapters/workbuddy/bin/release-skill.bundle.mjs +4131 -3180
  41. package/adapters/workbuddy/schemas/release-plan.schema.json +7 -0
  42. package/adapters/workbuddy/schemas/release-run.schema.json +43 -0
  43. package/adapters/workbuddy/skills/release-help/SKILL.md +10 -2
  44. package/adapters/workbuddy/skills/release-prepare/SKILL.md +6 -15
  45. package/adapters/workbuddy/skills/release-publish/SKILL.md +5 -6
  46. package/adapters/workbuddy/skills/release-reconcile/SKILL.md +2 -2
  47. package/adapters/workbuddy/skills/release-verify/SKILL.md +4 -6
  48. package/bin/release-skill-cli.mjs +212 -43
  49. package/bin/release-skill.bundle.mjs +4131 -3180
  50. package/package.json +1 -1
  51. package/references/01-state-machine.md +1 -1
  52. package/references/06-adapter-contract.md +6 -5
  53. package/schemas/release-plan.schema.json +7 -0
  54. package/schemas/release-run.schema.json +43 -0
  55. package/skills/release-help/SKILL.md +10 -2
  56. package/skills/release-prepare/SKILL.md +6 -15
  57. package/skills/release-publish/SKILL.md +5 -6
  58. package/skills/release-reconcile/SKILL.md +2 -2
  59. package/skills/release-verify/SKILL.md +4 -6
  60. package/skills-src/release-help/SKILL.md +10 -2
  61. package/skills-src/release-prepare/SKILL.md +6 -15
  62. package/skills-src/release-publish/SKILL.md +5 -6
  63. package/skills-src/release-reconcile/SKILL.md +2 -2
  64. package/skills-src/release-verify/SKILL.md +4 -6
  65. package/src/adapters/plugin-marketplace.mjs +1 -1
  66. package/src/adapters/push-snapshot.mjs +3 -1
  67. package/src/commands/approve.mjs +8 -6
  68. package/src/commands/attest.mjs +195 -0
  69. package/src/commands/hooks.mjs +42 -0
  70. package/src/commands/prepare.mjs +13 -50
  71. package/src/commands/publish.mjs +0 -8
  72. package/src/commands/reconcile.mjs +3 -37
  73. package/src/commands/ship.mjs +334 -0
  74. package/src/commands/verify.mjs +195 -15
  75. package/src/core/git-transport.mjs +93 -0
  76. package/src/core/release-metadata.mjs +105 -0
  77. package/src/core/skill-resource-closure.mjs +7 -1
@@ -19,7 +19,7 @@ registerPathRedactor(redactSensitivePaths);
19
19
 
20
20
  const execFile = promisify(execFileCb);
21
21
 
22
- const COMMANDS = new Set(['help', 'setup', 'assess', 'prepare', 'approve', 'publish', 'reconcile', 'verify', 'artifacts', 'docs']);
22
+ const COMMANDS = new Set(['help', 'setup', 'assess', 'prepare', 'approve', 'publish', 'reconcile', 'verify', 'ship', 'attest', 'hooks', 'artifacts', 'docs']);
23
23
 
24
24
  /**
25
25
  * Check if a command is available and get its version.
@@ -149,14 +149,14 @@ async function performEnvironmentChecks() {
149
149
  checks.kimi = {
150
150
  ...kimiCheck,
151
151
  required: false,
152
- usage: '仅当计划声明 kimi-plugin distribution 时用于消费者安装验证',
152
+ usage: '仅供发布后的人工安装使用;release-skill 不核验 Kimi 安装',
153
153
  };
154
154
 
155
155
  const codebuddyCheck = await checkCodeBuddyDependency();
156
156
  checks.codebuddy = {
157
157
  ...codebuddyCheck,
158
158
  required: false,
159
- usage: '仅当计划声明 codebuddy-plugin distribution 时用于消费者安装验证(人工 attestation 闭环;CLI 无法钉死冻结 ref,安装由人工完成并出具 attestation)',
159
+ usage: '仅供发布后的人工安装使用;release-skill 不核验 CodeBuddy/WorkBuddy 安装',
160
160
  };
161
161
 
162
162
  return checks;
@@ -182,7 +182,7 @@ function getCapabilityMaturity() {
182
182
  prepare: {
183
183
  available: true,
184
184
  mode: 'offline local writes',
185
- description: 'Freeze a release plan with snapshots and gates',
185
+ description: 'Freeze a release plan with snapshots and gates; the command invocation authorizes hook and gate execution',
186
186
  },
187
187
  docs: {
188
188
  available: true,
@@ -192,7 +192,7 @@ function getCapabilityMaturity() {
192
192
  publish: {
193
193
  available: true,
194
194
  mode: 'controlled production (protocol-tested; no OS/network sandbox)',
195
- description: 'Publishes frozen GitHub/npm artifacts and runs configured Claude/Codex/Kimi/CodeBuddy consumer checkpoints (CodeBuddy via human attestation closed loop) with approval and exact digest confirmation',
195
+ description: 'Publishes frozen GitHub/npm artifacts after one readable-plan approval, runs automated Claude/Codex checkpoints, and emits non-blocking Kimi/CodeBuddy manual follow-ups',
196
196
  },
197
197
  reconcile: {
198
198
  available: true,
@@ -202,7 +202,7 @@ function getCapabilityMaturity() {
202
202
  verify: {
203
203
  available: true,
204
204
  mode: 'fresh consumer verification (protocol-tested; no OS/network sandbox)',
205
- description: 'Recheck remote state, exact npm installation, CLI help, and configured Claude/Codex/Kimi/CodeBuddy installs (CodeBuddy via human attestation) before VERIFIED',
205
+ description: 'Recheck remote state, exact npm installation, CLI help, and automated Claude/Codex installs before VERIFIED; Kimi/CodeBuddy remain unverified manual follow-ups',
206
206
  },
207
207
  };
208
208
  }
@@ -219,9 +219,12 @@ Commands:
219
219
  assess Read-only assessment of project release readiness
220
220
  prepare Freeze a release plan (release-skill output to .release-skill/; hooks may do remote ops)
221
221
  approve Record local approval for a frozen release plan
222
- publish Publish frozen GitHub/npm artifacts after approval and digest confirmation
222
+ publish Publish frozen GitHub/npm artifacts after approval
223
223
  reconcile Resume PARTIAL state from evidence; conflicts require a human
224
224
  verify Fresh remote and consumer verification; only this reaches VERIFIED
225
+ ship Resume one durable prepare -> approve -> publish -> verify flow
226
+ attest Legacy only: record a Kimi/CodeBuddy result for an old frozen plan
227
+ hooks Run declared development hooks and populate reusable receipts
225
228
  artifacts Artifact status, inspect, update/apply, resolution, and diagnostics
226
229
  docs Refresh declared release documents (read-only dry-run by default)
227
230
 
@@ -231,7 +234,6 @@ Options:
231
234
  --run <path> Path to the release run file (required for reconcile/verify)
232
235
  --approval <path> Path to the approval record
233
236
  --production Prepare immutable Git/npm production artifacts
234
- --confirm-production <digest> Confirm the exact production plan digest
235
237
  --output <path> Override prepare/approve output path (non-production only)
236
238
  --run-dir <path> Override prepare run directory; production requires one direct child of .release-skill/runs
237
239
  --answers <path> Human-reviewed setup answers JSON
@@ -240,8 +242,14 @@ Options:
240
242
  --unit <id> Release unit whose declared release documents are refreshed (docs refresh)
241
243
  --confirm-refresh <sha256:...> Confirm the exact dry-run refreshDigest before any document write
242
244
  --ack-local-document-write Acknowledge the explicit local release-document write (docs refresh --write)
243
- --acknowledge-hook-side-effects Acknowledge unsandboxed legacy hook execution
244
- --acknowledge-gate-side-effects Acknowledge unsandboxed local verification gate execution
245
+ --platform <id> Legacy attestation platform: kimi or codebuddy
246
+ --plugin <id> Plugin id for a generated manual requirement
247
+ --actor <name> Person confirming an approval or manual result
248
+ --result <value> Manual result: passed or failed
249
+ --install-path <path> Actual managed plugin path when closure verification requires it
250
+ --install-channel <desktop|cli> CodeBuddy installation channel when required
251
+ --approve Approve the ship plan (boolean; plan digest is auto-resolved)
252
+ --state <path> Override the durable ship state file
245
253
  --no-hook-cache Force every prepare hook to run in full; neither read nor write the hook cache
246
254
  --json Output results as JSON
247
255
  --version Show version and exit
@@ -249,14 +257,16 @@ Options:
249
257
 
250
258
  Safety:
251
259
  Safe default: help -> setup (when config is absent) -> assess -> prepare --offline -> human review.
252
- Production happy end: prepare --production -> approve -> publish -> verify.
260
+ Production happy end: ship --target-version <ver> -> ship --approve --actor <name>.
261
+ The ship command runs configured hooks and gates automatically; the only human gate is plan approval.
262
+ Kimi/CodeBuddy installations are non-blocking manual follow-up tasks (not verified by system).
253
263
  prepare copies current public files into a local snapshot; it does not rewrite source files.
254
264
  - Default mode is offline (release-skill pipeline does no remote writes)
255
265
  - prepare output goes to .release-skill/ directory only
256
266
  - User-configured hooks may write anywhere and perform remote operations
257
267
  - To ensure zero remote writes, disable hooks or audit them separately
258
268
  - docs refresh --write rewrites only declared README managed regions and the current CHANGELOG entry after exact refreshDigest confirmation; it never commits, pushes, tags, publishes, or installs.
259
- - publish requires explicit approval and an exact plan-digest confirmation
269
+ - publish requires explicit approval; plan digest is auto-read from the plan file
260
270
  - publish consumes frozen Git/npm artifacts, never the live workspace
261
271
  - existing remote objects and uncertain checks stop for human intervention
262
272
  - production-equivalent protocol sandbox is verified; a real remote canary is not
@@ -343,8 +353,8 @@ if (!command || command === 'help') {
343
353
  conditionalConsumers: {
344
354
  claude: '声明 claude-plugin distribution 时必须可用',
345
355
  codex: '声明 codex-plugin distribution 时必须可用',
346
- kimi: '声明 kimi-plugin distribution 时必须可用',
347
- codebuddy: '声明 codebuddy-plugin distribution 时用于人工 attestation 闭环(CLI 无法钉死冻结 ref,安装由人工完成并出具 attestation)',
356
+ kimi: '发布后人工安装待办;不影响生产发布就绪度,系统不核验',
357
+ codebuddy: '发布后人工安装待办;不影响生产发布就绪度,系统不核验',
348
358
  },
349
359
  },
350
360
  },
@@ -353,12 +363,12 @@ if (!command || command === 'help') {
353
363
  maturity: {
354
364
  setup: 'read-only by default; create-once requires answers plus exact setupDigest confirmation',
355
365
  assess: 'read-only (default); --output writes local report',
356
- prepare: 'offline local writes; configured hooks/gates require their explicit side-effect acknowledgements',
366
+ prepare: 'offline local writes; the command invocation authorizes configured hook and gate execution',
357
367
  docs: 'read-only dry-run by default; write requires --write, exact --confirm-refresh, and --ack-local-document-write; never commits, pushes, or publishes',
358
368
  onlinePrepare: 'previous-public-baseline observation available; production mode freezes publish artifacts and fails closed on drift or unknown state',
359
- publish: 'GitHub/npm plus configured Claude/Codex/Kimi/CodeBuddy consumer checkpoints are protocol-tested without an OS/network sandbox (CodeBuddy via human attestation closed loop); approval and exact digest confirmation required',
369
+ publish: 'GitHub/npm plus automated Claude/Codex consumer checkpoints are protocol-tested without an OS/network sandbox; one approval is required and the internal plan digest is checked automatically',
360
370
  reconcile: 'PARTIAL recovery is protocol-tested without an OS/network sandbox; remote conflicts require human intervention',
361
- verify: 'fresh exact npm and Claude/Codex/Kimi/CodeBuddy consumer installation checks are protocol-tested without an OS/network sandbox (CodeBuddy via human attestation); configured consumer processes require explicit acknowledgement; success reaches VERIFIED',
371
+ verify: 'fresh exact npm and Claude/Codex consumer installation checks are protocol-tested without an OS/network sandbox; Kimi/CodeBuddy are unverified manual follow-ups; command invocation authorizes configured gates',
362
372
  },
363
373
  recommendations: [],
364
374
  };
@@ -394,13 +404,8 @@ if (!command || command === 'help') {
394
404
  output.recommendations.push('Install Codex CLI before releasing a configured codex-plugin distribution');
395
405
  }
396
406
 
397
- if (!checks.kimi.available) {
398
- output.recommendations.push('Install Kimi Code CLI before releasing a configured kimi-plugin distribution');
399
- }
400
-
401
- if (!checks.codebuddy.available) {
402
- output.recommendations.push('Install WorkBuddy (bundled codebuddy CLI) before releasing a configured codebuddy-plugin distribution; the install is a manual attestation closed loop because the CLI cannot pin a frozen ref');
403
- }
407
+ // Kimi/CodeBuddy are post-release manual follow-ups. Their local CLI
408
+ // availability is informational and never blocks release readiness.
404
409
 
405
410
  console.log(JSON.stringify(output, null, 2));
406
411
  process.exit(readiness.status === 'READY' ? 0 : 1);
@@ -500,6 +505,155 @@ if (command === 'assess') {
500
505
  }
501
506
  }
502
507
 
508
+ // --- Reusable hook receipt routing ---
509
+ if (command === 'hooks') {
510
+ const subcommand = positional[1];
511
+ const rootIdx = args.indexOf('--root');
512
+ const root = resolve(rootIdx !== -1 && args[rootIdx + 1] ? args[rootIdx + 1] : process.cwd());
513
+ if (subcommand !== 'validate') {
514
+ const message = 'hooks requires subcommand: hooks validate';
515
+ if (hasJson) console.log(JSON.stringify({ error: 'MISSING_PARAMETERS', message }));
516
+ else console.error(`Error: ${message}`);
517
+ process.exit(1);
518
+ }
519
+ try {
520
+ const { validateDeclaredHooks } = await import('../src/commands/hooks.mjs');
521
+ const result = await validateDeclaredHooks({
522
+ root,
523
+ // --acknowledge-hook-side-effects is accepted as a no-effect compatibility input
524
+ hooksAuthorized: args.includes('--acknowledge-hook-side-effects'),
525
+ hookCache: !args.includes('--no-hook-cache'),
526
+ });
527
+ if (hasJson) console.log(JSON.stringify(result, null, 2));
528
+ else {
529
+ console.log(`Declared hooks: ${result.status}`);
530
+ console.log(`Reusable receipt evidence: ${result.evidenceDir}`);
531
+ }
532
+ process.exit(0);
533
+ } catch (err) {
534
+ if (hasJson) {
535
+ console.log(JSON.stringify({
536
+ error: err.code ?? 'UNKNOWN_ERROR',
537
+ message: err.message,
538
+ details: err.details ?? {},
539
+ exitCode: err.exitCode ?? 1,
540
+ }));
541
+ } else console.error(`Error: ${err.message}`);
542
+ process.exit(err.exitCode ?? 1);
543
+ }
544
+ }
545
+
546
+ // --- Durable end-to-end ship routing ---
547
+ if (command === 'ship') {
548
+ const value = (flag) => {
549
+ const idx = args.indexOf(flag);
550
+ return idx !== -1 && args[idx + 1] ? args[idx + 1] : undefined;
551
+ };
552
+ const root = resolve(value('--root') ?? process.cwd());
553
+ try {
554
+ const [
555
+ { advanceShip },
556
+ { createGitGithubAdapter },
557
+ { createNpmAdapter },
558
+ { createPluginMarketplaceAdapter },
559
+ { createPushSnapshotAdapter },
560
+ { createAdapterRegistry },
561
+ ] = await Promise.all([
562
+ import('../src/commands/ship.mjs'),
563
+ import('../src/adapters/git-github.mjs'),
564
+ import('../src/adapters/npm.mjs'),
565
+ import('../src/adapters/plugin-marketplace.mjs'),
566
+ import('../src/adapters/push-snapshot.mjs'),
567
+ import('../src/adapters/contract.mjs'),
568
+ ]);
569
+ const adapterRegistry = createAdapterRegistry([
570
+ createGitGithubAdapter(),
571
+ createNpmAdapter(),
572
+ createPluginMarketplaceAdapter(),
573
+ createPushSnapshotAdapter(),
574
+ ]);
575
+ const result = await advanceShip({
576
+ root,
577
+ statePath: value('--state'),
578
+ targetVersion: value('--target-version') ?? value('--version'),
579
+ approve: args.includes('--approve'),
580
+ planApprovalDigest: value('--approve-plan'),
581
+ actor: value('--actor'),
582
+ adapterRegistry,
583
+ });
584
+ if (hasJson) {
585
+ console.log(JSON.stringify(result, null, 2));
586
+ } else {
587
+ console.log(`Ship status: ${result.status}`);
588
+ console.log(`State: ${result.statePath}`);
589
+ if (result.planDigest && result.status === 'NEEDS_PLAN_APPROVAL') {
590
+ console.log(`Approve plan: ship --approve --actor <person>`);
591
+ }
592
+ for (const followUp of result.manualFollowUps ?? []) {
593
+ console.log(`Manual follow-up [${followUp.platform}] ${followUp.plugin}: not verified by system`);
594
+ }
595
+ for (const requirement of result.requirements ?? []) {
596
+ console.log(`Manual requirement [${requirement.platform}]: ${requirement.requirementPath}`);
597
+ }
598
+ }
599
+ process.exit(0);
600
+ } catch (err) {
601
+ if (hasJson) {
602
+ console.log(JSON.stringify({
603
+ error: err.code ?? 'UNKNOWN_ERROR',
604
+ message: err.message,
605
+ details: err.details ?? {},
606
+ exitCode: err.exitCode ?? 1,
607
+ }));
608
+ } else {
609
+ console.error(`Error: ${err.message}`);
610
+ }
611
+ process.exit(err.exitCode ?? 1);
612
+ }
613
+ }
614
+
615
+ // --- Manual attestation command routing ---
616
+ if (command === 'attest') {
617
+ const value = (flag) => {
618
+ const idx = args.indexOf(flag);
619
+ return idx !== -1 && args[idx + 1] ? args[idx + 1] : undefined;
620
+ };
621
+ const root = resolve(value('--root') ?? process.cwd());
622
+ try {
623
+ const { recordManualAttestation } = await import('../src/commands/attest.mjs');
624
+ const result = await recordManualAttestation({
625
+ root,
626
+ platform: value('--platform'),
627
+ plugin: value('--plugin'),
628
+ actor: value('--actor'),
629
+ result: value('--result'),
630
+ installPath: value('--install-path'),
631
+ installChannel: value('--install-channel'),
632
+ note: value('--note'),
633
+ });
634
+ if (hasJson) {
635
+ console.log(JSON.stringify(result, null, 2));
636
+ } else {
637
+ console.log(`Manual attestation recorded: ${result.platform}/${result.plugin} ${result.version}`);
638
+ console.log(`Bound plan digest: ${result.planDigest}`);
639
+ console.log(`Receipt: ${result.attestationPath}`);
640
+ }
641
+ process.exit(0);
642
+ } catch (err) {
643
+ if (hasJson) {
644
+ console.log(JSON.stringify({
645
+ error: err.code ?? 'UNKNOWN_ERROR',
646
+ message: err.message,
647
+ details: err.details ?? {},
648
+ exitCode: err.exitCode ?? 1,
649
+ }));
650
+ } else {
651
+ console.error(`Error: ${err.message}`);
652
+ }
653
+ process.exit(err.exitCode ?? 1);
654
+ }
655
+ }
656
+
503
657
  // --- Prepare command routing ---
504
658
  if (command === 'prepare') {
505
659
  const rootIdx = args.indexOf('--root');
@@ -542,15 +696,27 @@ if (command === 'prepare') {
542
696
  });
543
697
 
544
698
  if (hasJson) {
545
- // Output the full plan object plus metadata so consumers
546
- // can inspect status, units, externalActions, planDigest, etc.
699
+ // Keep stdout compact and stable. The immutable plan remains the single
700
+ // authority at planPath; callers should not have to carry its full
701
+ // source closure, manifests and action payloads through chat/logs.
547
702
  const planContent = await readFileFs(result.planPath, 'utf8');
548
703
  const plan = JSON.parse(planContent);
549
704
  console.log(JSON.stringify({
550
- ...plan,
705
+ command: 'prepare',
706
+ status: plan.status,
551
707
  planPath: result.planPath,
552
708
  planDigest: result.planDigest,
553
709
  evidenceDir: result.evidenceDir,
710
+ units: (plan.units ?? []).map((unit) => ({
711
+ id: unit.id,
712
+ targetVersion: unit.targetVersion ?? unit.version,
713
+ })),
714
+ actionCount: (plan.externalActions ?? []).length,
715
+ actions: (plan.externalActions ?? []).map((action) => ({
716
+ id: action.id,
717
+ type: action.type,
718
+ unitId: action.unitId,
719
+ })),
554
720
  warnings: result.warnings,
555
721
  }, null, 2));
556
722
  } else {
@@ -591,8 +757,8 @@ if (command === 'approve') {
591
757
  const outputIdx = args.indexOf('--output');
592
758
  const outputPath = outputIdx !== -1 && args[outputIdx + 1] ? resolve(args[outputIdx + 1]) : undefined;
593
759
 
594
- if (!planPath || !expectedDigest || !actor) {
595
- const msg = 'approve requires --plan <path>, --digest <sha256>, and --actor <name>';
760
+ if (!planPath || !actor) {
761
+ const msg = 'approve requires --plan <path> and --actor <name>';
596
762
  if (hasJson) {
597
763
  console.log(JSON.stringify({ error: 'MISSING_PARAMETERS', message: msg, exitCode: 1 }));
598
764
  } else {
@@ -603,13 +769,24 @@ if (command === 'approve') {
603
769
 
604
770
  try {
605
771
  const { approvePlan } = await import('../src/commands/approve.mjs');
772
+ const { computePlanDigest } = await import('../src/core/plan.mjs');
773
+ const { readFile: readFileFs } = await import('node:fs/promises');
774
+
775
+ // Auto-read digest from plan when --digest is omitted
776
+ let resolvedDigest = expectedDigest;
777
+ if (!resolvedDigest) {
778
+ const planRaw = await readFileFs(resolve(planPath), 'utf8');
779
+ const planObj = JSON.parse(planRaw);
780
+ resolvedDigest = computePlanDigest(planObj);
781
+ }
782
+
606
783
  const resolvedPlanPath = resolve(planPath);
607
784
  const planDir = dirname(resolvedPlanPath);
608
- const releaseDir = basename(planDir) === 'plans' && basename(resolvedPlanPath) === `${expectedDigest}.json`
785
+ const releaseDir = basename(planDir) === 'plans' && basename(resolvedPlanPath) === `${resolvedDigest}.json`
609
786
  ? dirname(planDir)
610
787
  : planDir;
611
788
  const approvalPath = outputPath ?? join(releaseDir, 'approval-record.json');
612
- const record = await approvePlan({ planPath, expectedDigest, actor, outputPath: approvalPath });
789
+ const record = await approvePlan({ planPath, expectedDigest: resolvedDigest, actor, outputPath: approvalPath });
613
790
 
614
791
  if (hasJson) {
615
792
  console.log(JSON.stringify(record, null, 2));
@@ -647,10 +824,6 @@ if (command === 'reconcile') {
647
824
  const runPath = runIdx !== -1 && args[runIdx + 1] ? resolve(args[runIdx + 1]) : undefined;
648
825
  const approvalIdx = args.indexOf('--approval');
649
826
  const approvalPath = approvalIdx !== -1 && args[approvalIdx + 1] ? resolve(args[approvalIdx + 1]) : undefined;
650
- const confirmationIdx = args.indexOf('--confirm-production');
651
- const productionConfirmation = confirmationIdx !== -1 && args[confirmationIdx + 1]
652
- ? args[confirmationIdx + 1]
653
- : undefined;
654
827
 
655
828
  if (!planPath || !runPath) {
656
829
  const msg = 'reconcile requires --plan <path> and --run <path>';
@@ -669,6 +842,7 @@ if (command === 'reconcile') {
669
842
  const { createPluginMarketplaceAdapter } = await import('../src/adapters/plugin-marketplace.mjs');
670
843
  const { createPushSnapshotAdapter } = await import('../src/adapters/push-snapshot.mjs');
671
844
  const { createAdapterRegistry } = await import('../src/adapters/contract.mjs');
845
+
672
846
  const registry = createAdapterRegistry([
673
847
  createGitGithubAdapter(),
674
848
  createNpmAdapter(),
@@ -682,7 +856,6 @@ if (command === 'reconcile') {
682
856
  approvalPath,
683
857
  adapterRegistry: registry,
684
858
  root,
685
- productionConfirmation,
686
859
  });
687
860
 
688
861
  if (hasJson) {
@@ -788,13 +961,9 @@ if (command === 'publish') {
788
961
  const planPath = planIdx !== -1 && args[planIdx + 1] ? resolve(args[planIdx + 1]) : undefined;
789
962
  const approvalIdx = args.indexOf('--approval');
790
963
  const approvalPath = approvalIdx !== -1 && args[approvalIdx + 1] ? resolve(args[approvalIdx + 1]) : undefined;
791
- const confirmationIdx = args.indexOf('--confirm-production');
792
- const productionConfirmation = confirmationIdx !== -1 && args[confirmationIdx + 1]
793
- ? args[confirmationIdx + 1]
794
- : undefined;
795
964
 
796
- if (!planPath || !approvalPath || !productionConfirmation) {
797
- const msg = 'publish requires --plan <path>, --approval <path>, and --confirm-production <plan-digest>';
965
+ if (!planPath || !approvalPath) {
966
+ const msg = 'publish requires --plan <path> and --approval <path>';
798
967
  if (hasJson) {
799
968
  console.log(JSON.stringify({ error: 'MISSING_PARAMETERS', message: msg, exitCode: 1 }));
800
969
  } else {
@@ -810,6 +979,7 @@ if (command === 'publish') {
810
979
  const { createPluginMarketplaceAdapter } = await import('../src/adapters/plugin-marketplace.mjs');
811
980
  const { createPushSnapshotAdapter } = await import('../src/adapters/push-snapshot.mjs');
812
981
  const { createAdapterRegistry } = await import('../src/adapters/contract.mjs');
982
+
813
983
  const registry = createAdapterRegistry([
814
984
  createGitGithubAdapter(),
815
985
  createNpmAdapter(),
@@ -823,7 +993,6 @@ if (command === 'publish') {
823
993
  adapterRegistry: registry,
824
994
  root,
825
995
  productionMode: true,
826
- productionConfirmation,
827
996
  });
828
997
 
829
998
  if (hasJson) {