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.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/.codebuddy-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +2 -2
- package/.kimi-plugin/plugin.json +1 -1
- package/CHANGELOG.md +41 -0
- package/INSTALL.md +34 -110
- package/INSTALL.zh-CN.md +17 -83
- package/README.md +49 -41
- package/README.zh-CN.md +43 -34
- package/adapters/claude/.claude-plugin/marketplace.json +1 -1
- package/adapters/claude/.claude-plugin/plugin.json +1 -1
- package/adapters/claude/bin/release-skill.bundle.mjs +4131 -3180
- package/adapters/claude/schemas/release-plan.schema.json +7 -0
- package/adapters/claude/schemas/release-run.schema.json +43 -0
- package/adapters/claude/skills/release-help/SKILL.md +10 -2
- package/adapters/claude/skills/release-prepare/SKILL.md +6 -15
- package/adapters/claude/skills/release-publish/SKILL.md +5 -6
- package/adapters/claude/skills/release-reconcile/SKILL.md +2 -2
- package/adapters/claude/skills/release-verify/SKILL.md +4 -6
- package/adapters/codex/.codex-plugin/plugin.json +2 -2
- package/adapters/codex/bin/release-skill.bundle.mjs +4131 -3180
- package/adapters/codex/schemas/release-plan.schema.json +7 -0
- package/adapters/codex/schemas/release-run.schema.json +43 -0
- package/adapters/codex/skills/release-help/SKILL.md +10 -2
- package/adapters/codex/skills/release-prepare/SKILL.md +6 -15
- package/adapters/codex/skills/release-publish/SKILL.md +5 -6
- package/adapters/codex/skills/release-reconcile/SKILL.md +2 -2
- package/adapters/codex/skills/release-verify/SKILL.md +4 -6
- package/adapters/kimi/.kimi-plugin/plugin.json +1 -1
- package/adapters/kimi/bin/release-skill.bundle.mjs +4131 -3180
- package/adapters/kimi/schemas/release-plan.schema.json +7 -0
- package/adapters/kimi/schemas/release-run.schema.json +43 -0
- package/adapters/kimi/skills/release-help/SKILL.md +10 -2
- package/adapters/kimi/skills/release-prepare/SKILL.md +6 -15
- package/adapters/kimi/skills/release-publish/SKILL.md +5 -6
- package/adapters/kimi/skills/release-reconcile/SKILL.md +2 -2
- package/adapters/kimi/skills/release-verify/SKILL.md +4 -6
- package/adapters/workbuddy/.codebuddy-plugin/plugin.json +1 -1
- package/adapters/workbuddy/bin/release-skill.bundle.mjs +4131 -3180
- package/adapters/workbuddy/schemas/release-plan.schema.json +7 -0
- package/adapters/workbuddy/schemas/release-run.schema.json +43 -0
- package/adapters/workbuddy/skills/release-help/SKILL.md +10 -2
- package/adapters/workbuddy/skills/release-prepare/SKILL.md +6 -15
- package/adapters/workbuddy/skills/release-publish/SKILL.md +5 -6
- package/adapters/workbuddy/skills/release-reconcile/SKILL.md +2 -2
- package/adapters/workbuddy/skills/release-verify/SKILL.md +4 -6
- package/bin/release-skill-cli.mjs +212 -43
- package/bin/release-skill.bundle.mjs +4131 -3180
- package/package.json +1 -1
- package/references/01-state-machine.md +1 -1
- package/references/06-adapter-contract.md +6 -5
- package/schemas/release-plan.schema.json +7 -0
- package/schemas/release-run.schema.json +43 -0
- package/skills/release-help/SKILL.md +10 -2
- package/skills/release-prepare/SKILL.md +6 -15
- package/skills/release-publish/SKILL.md +5 -6
- package/skills/release-reconcile/SKILL.md +2 -2
- package/skills/release-verify/SKILL.md +4 -6
- package/skills-src/release-help/SKILL.md +10 -2
- package/skills-src/release-prepare/SKILL.md +6 -15
- package/skills-src/release-publish/SKILL.md +5 -6
- package/skills-src/release-reconcile/SKILL.md +2 -2
- package/skills-src/release-verify/SKILL.md +4 -6
- package/src/adapters/plugin-marketplace.mjs +1 -1
- package/src/adapters/push-snapshot.mjs +3 -1
- package/src/commands/approve.mjs +8 -6
- package/src/commands/attest.mjs +195 -0
- package/src/commands/hooks.mjs +42 -0
- package/src/commands/prepare.mjs +13 -50
- package/src/commands/publish.mjs +0 -8
- package/src/commands/reconcile.mjs +3 -37
- package/src/commands/ship.mjs +334 -0
- package/src/commands/verify.mjs +195 -15
- package/src/core/git-transport.mjs +93 -0
- package/src/core/release-metadata.mjs +105 -0
- 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: '
|
|
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: '
|
|
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
|
|
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
|
|
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
|
|
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
|
-
--
|
|
244
|
-
--
|
|
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:
|
|
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
|
|
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: '
|
|
347
|
-
codebuddy: '
|
|
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;
|
|
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
|
|
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
|
|
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
|
-
|
|
398
|
-
|
|
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
|
-
//
|
|
546
|
-
//
|
|
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
|
-
|
|
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 || !
|
|
595
|
-
const msg = 'approve requires --plan <path
|
|
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) === `${
|
|
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
|
|
797
|
-
const msg = 'publish requires --plan <path
|
|
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) {
|