release-skill 0.9.0 → 0.9.1
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 +32 -0
- package/INSTALL.md +22 -9
- package/INSTALL.zh-CN.md +15 -8
- package/README.md +37 -30
- package/README.zh-CN.md +32 -27
- package/adapters/claude/.claude-plugin/marketplace.json +1 -1
- package/adapters/claude/.claude-plugin/plugin.json +1 -1
- package/adapters/claude/bin/consumer-contract-vectors.json +110 -0
- package/adapters/claude/bin/error-codes.json +8 -1
- package/adapters/claude/bin/foundation-resource-binding.json +1 -1
- package/adapters/claude/bin/registry.json +1 -1
- package/adapters/claude/bin/release-skill.bundle.mjs +1719 -1291
- package/adapters/claude/bin/rules.json +1 -1
- package/adapters/claude/bin/schemas/consumer-contract-vector.schema.json +149 -0
- package/adapters/claude/skills/release-finish/SKILL.md +4 -4
- package/adapters/claude/skills/release-help/SKILL.md +3 -2
- package/adapters/claude/skills/release-prepare/SKILL.md +2 -2
- package/adapters/claude/skills/release-publish/SKILL.md +3 -1
- package/adapters/claude/src/schemas/migration-manifest.schema.json +271 -7
- package/adapters/claude/src/schemas/profile-adoption-declaration.schema.json +8 -0
- package/adapters/codex/.codex-plugin/plugin.json +2 -2
- package/adapters/codex/bin/consumer-contract-vectors.json +110 -0
- package/adapters/codex/bin/error-codes.json +8 -1
- package/adapters/codex/bin/foundation-resource-binding.json +1 -1
- package/adapters/codex/bin/registry.json +1 -1
- package/adapters/codex/bin/release-skill.bundle.mjs +1719 -1291
- package/adapters/codex/bin/rules.json +1 -1
- package/adapters/codex/bin/schemas/consumer-contract-vector.schema.json +149 -0
- package/adapters/codex/skills/release-finish/SKILL.md +4 -4
- package/adapters/codex/skills/release-help/SKILL.md +3 -2
- package/adapters/codex/skills/release-prepare/SKILL.md +2 -2
- package/adapters/codex/skills/release-publish/SKILL.md +3 -1
- package/adapters/codex/src/schemas/migration-manifest.schema.json +271 -7
- package/adapters/codex/src/schemas/profile-adoption-declaration.schema.json +8 -0
- package/adapters/kimi/.kimi-plugin/plugin.json +1 -1
- package/adapters/kimi/bin/consumer-contract-vectors.json +110 -0
- package/adapters/kimi/bin/error-codes.json +8 -1
- package/adapters/kimi/bin/foundation-resource-binding.json +1 -1
- package/adapters/kimi/bin/registry.json +1 -1
- package/adapters/kimi/bin/release-skill.bundle.mjs +1719 -1291
- package/adapters/kimi/bin/rules.json +1 -1
- package/adapters/kimi/bin/schemas/consumer-contract-vector.schema.json +149 -0
- package/adapters/kimi/skills/release-finish/SKILL.md +4 -4
- package/adapters/kimi/skills/release-help/SKILL.md +3 -2
- package/adapters/kimi/skills/release-prepare/SKILL.md +2 -2
- package/adapters/kimi/skills/release-publish/SKILL.md +3 -1
- package/adapters/kimi/src/schemas/migration-manifest.schema.json +271 -7
- package/adapters/kimi/src/schemas/profile-adoption-declaration.schema.json +8 -0
- package/adapters/workbuddy/.codebuddy-plugin/plugin.json +1 -1
- package/adapters/workbuddy/bin/consumer-contract-vectors.json +110 -0
- package/adapters/workbuddy/bin/error-codes.json +8 -1
- package/adapters/workbuddy/bin/foundation-resource-binding.json +1 -1
- package/adapters/workbuddy/bin/registry.json +1 -1
- package/adapters/workbuddy/bin/release-skill.bundle.mjs +1719 -1291
- package/adapters/workbuddy/bin/rules.json +1 -1
- package/adapters/workbuddy/bin/schemas/consumer-contract-vector.schema.json +149 -0
- package/adapters/workbuddy/skills/release-finish/SKILL.md +4 -4
- package/adapters/workbuddy/skills/release-help/SKILL.md +3 -2
- package/adapters/workbuddy/skills/release-prepare/SKILL.md +2 -2
- package/adapters/workbuddy/skills/release-publish/SKILL.md +3 -1
- package/adapters/workbuddy/src/schemas/migration-manifest.schema.json +271 -7
- package/adapters/workbuddy/src/schemas/profile-adoption-declaration.schema.json +8 -0
- package/bin/consumer-contract-vectors.json +110 -0
- package/bin/error-codes.json +8 -1
- package/bin/foundation-resource-binding.json +1 -1
- package/bin/registry.json +1 -1
- package/bin/release-skill-cli.mjs +40 -4
- package/bin/release-skill.bundle.mjs +1719 -1291
- package/bin/rules.json +1 -1
- package/bin/schemas/consumer-contract-vector.schema.json +149 -0
- package/package.json +4 -4
- package/platform-manifest.json +5 -5
- package/skills/release-finish/SKILL.md +4 -4
- package/skills/release-help/SKILL.md +3 -2
- package/skills/release-prepare/SKILL.md +2 -2
- package/skills/release-publish/SKILL.md +3 -1
- package/skills-src/release-finish/SKILL.md +4 -4
- package/skills-src/release-help/SKILL.md +3 -2
- package/skills-src/release-prepare/SKILL.md +2 -2
- package/skills-src/release-publish/SKILL.md +3 -1
- package/src/commands/post-release-local.mjs +249 -58
- package/src/commands/prepare.mjs +39 -4
- package/src/commands/ship.mjs +124 -16
- package/src/core/derived-artifact-gates.mjs +32 -5
- package/src/producers/foundation-resource-projection.mjs +16 -3
- package/src/schemas/migration-manifest.schema.json +271 -7
- package/src/schemas/profile-adoption-declaration.schema.json +8 -0
|
@@ -64,6 +64,9 @@ function actionTarget(plan, action, host) {
|
|
|
64
64
|
const tagAction = (plan.externalActions ?? []).find((candidate) => (
|
|
65
65
|
candidate.type === 'create-tag' && candidate.unitId === action.unitId
|
|
66
66
|
));
|
|
67
|
+
const pushAction = (plan.externalActions ?? []).find((candidate) => (
|
|
68
|
+
candidate.type === 'push-snapshot' && candidate.unitId === action.unitId
|
|
69
|
+
));
|
|
67
70
|
const pluginTag = tagAction?.parameters?.tag;
|
|
68
71
|
if (
|
|
69
72
|
pluginRepo !== unit.publicRepo
|
|
@@ -85,6 +88,28 @@ function actionTarget(plan, action, host) {
|
|
|
85
88
|
} else if (sourceDescriptor.form !== 'bundled-family' || sourceDescriptor.commit !== pluginCommit) {
|
|
86
89
|
throw new Error(`post-release action ${action.id} has no single frozen source commit`);
|
|
87
90
|
}
|
|
91
|
+
const codeBuddyFamily = host === 'codebuddy' || host === 'workbuddy';
|
|
92
|
+
if (
|
|
93
|
+
codeBuddyFamily
|
|
94
|
+
&& sourceDescriptor.form === 'bundled-family'
|
|
95
|
+
&& (
|
|
96
|
+
pushAction?.parameters?.repo !== pluginRepo
|
|
97
|
+
|| pushAction?.parameters?.commit !== pluginCommit
|
|
98
|
+
|| typeof pushAction?.parameters?.branch !== 'string'
|
|
99
|
+
|| pushAction.parameters.branch.length === 0
|
|
100
|
+
|| (
|
|
101
|
+
pushAction.parameters.branch.startsWith('refs/')
|
|
102
|
+
&& !pushAction.parameters.branch.startsWith('refs/heads/')
|
|
103
|
+
)
|
|
104
|
+
)
|
|
105
|
+
) {
|
|
106
|
+
throw new Error(`post-release action ${action.id} has no matching frozen mutable branch identity`);
|
|
107
|
+
}
|
|
108
|
+
const marketplaceRef = codeBuddyFamily && sourceDescriptor.form === 'bundled-family'
|
|
109
|
+
? (pushAction.parameters.branch.startsWith('refs/heads/')
|
|
110
|
+
? pushAction.parameters.branch
|
|
111
|
+
: `refs/heads/${pushAction.parameters.branch}`)
|
|
112
|
+
: parameters.ref;
|
|
88
113
|
return {
|
|
89
114
|
host,
|
|
90
115
|
actionId: action.id,
|
|
@@ -96,11 +121,13 @@ function actionTarget(plan, action, host) {
|
|
|
96
121
|
: parameters.marketplace ?? sourceDescriptor.marketplaceEntry ?? parameters.plugin,
|
|
97
122
|
version: parameters.version,
|
|
98
123
|
pluginRepo,
|
|
124
|
+
githubHost: tagAction?.parameters?.githubHost ?? pushAction?.parameters?.githubHost ?? 'github.com',
|
|
99
125
|
pluginTag,
|
|
100
126
|
pluginCommit,
|
|
101
127
|
marketplaceRepo,
|
|
102
|
-
marketplaceRef
|
|
128
|
+
marketplaceRef,
|
|
103
129
|
marketplaceCommit,
|
|
130
|
+
sourceForm: sourceDescriptor.form,
|
|
104
131
|
timeoutMs: parameters.timeoutMs ?? 300_000,
|
|
105
132
|
};
|
|
106
133
|
}
|
|
@@ -225,6 +252,13 @@ async function defaultRun(command, args, options = {}) {
|
|
|
225
252
|
`${command} exited ${envelope.exitStatus}${stderr.trim() ? `: ${stderr.trim()}` : ''}`,
|
|
226
253
|
);
|
|
227
254
|
error.exitStatus = envelope.exitStatus;
|
|
255
|
+
error.hostCommandUnavailable = envelope.processStatus === 'FAILED_TO_START';
|
|
256
|
+
error.details = {
|
|
257
|
+
processStatus: envelope.processStatus,
|
|
258
|
+
terminationReason: envelope.terminationReason,
|
|
259
|
+
watchdogReason: envelope.watchdogReason,
|
|
260
|
+
...(envelope.evidence?.spawnError ? { spawnError: envelope.evidence.spawnError } : {}),
|
|
261
|
+
};
|
|
228
262
|
throw error;
|
|
229
263
|
}
|
|
230
264
|
return { stdout, stderr };
|
|
@@ -235,8 +269,9 @@ async function commandAvailable(command, host, run) {
|
|
|
235
269
|
try {
|
|
236
270
|
await run(command, ['--version'], { timeout: 10_000, env: hostEnvironment(host) });
|
|
237
271
|
return true;
|
|
238
|
-
} catch {
|
|
239
|
-
return false;
|
|
272
|
+
} catch (error) {
|
|
273
|
+
if (error?.hostCommandUnavailable === true) return false;
|
|
274
|
+
throw error;
|
|
240
275
|
}
|
|
241
276
|
}
|
|
242
277
|
|
|
@@ -298,8 +333,10 @@ function exactPluginObservation(target, stdout) {
|
|
|
298
333
|
};
|
|
299
334
|
}
|
|
300
335
|
if (!Array.isArray(parsed)) throw new Error(`${target.host} plugin list did not return an array`);
|
|
301
|
-
const
|
|
302
|
-
if (
|
|
336
|
+
const matches = parsed.filter((entry) => entry?.id === selector);
|
|
337
|
+
if (matches.length === 0) return { installed: false };
|
|
338
|
+
if (matches.length !== 1) throw new Error(`${target.host} plugin list returned conflicting entries for ${selector}`);
|
|
339
|
+
const [found] = matches;
|
|
303
340
|
return {
|
|
304
341
|
installed: true,
|
|
305
342
|
exact: found.version === target.version && found.gitCommitSha === target.pluginCommit,
|
|
@@ -346,7 +383,7 @@ async function observeStructuredTarget(target, command, env, run) {
|
|
|
346
383
|
}
|
|
347
384
|
|
|
348
385
|
async function bindStructuredMarketplace(target, command, env, run, observed) {
|
|
349
|
-
if (observed.marketplace.installed && observed.marketplace.exact) return;
|
|
386
|
+
if (observed.marketplace.installed && observed.marketplace.exact) return false;
|
|
350
387
|
if (observed.marketplace.installed) {
|
|
351
388
|
await run(command, [
|
|
352
389
|
'plugin', 'marketplace', 'remove', target.marketplace,
|
|
@@ -360,6 +397,7 @@ async function bindStructuredMarketplace(target, command, env, run, observed) {
|
|
|
360
397
|
if (!rebound.installed || !rebound.exact) {
|
|
361
398
|
throw new Error(`${target.host} marketplace did not bind to frozen commit ${target.marketplaceCommit}`);
|
|
362
399
|
}
|
|
400
|
+
return true;
|
|
363
401
|
}
|
|
364
402
|
|
|
365
403
|
function actionParametersForTarget(plan, target) {
|
|
@@ -397,11 +435,50 @@ async function runStructuredUpdate(target, detected, run, {
|
|
|
397
435
|
const env = hostEnvironment(target.host);
|
|
398
436
|
const listed = await run(detected.command, ['plugin', 'list', '--json'], { env });
|
|
399
437
|
const observed = exactPluginObservation(target, listed.stdout);
|
|
438
|
+
if (
|
|
439
|
+
target.sourceForm !== 'bundled-family'
|
|
440
|
+
|| target.pluginRepo !== target.marketplaceRepo
|
|
441
|
+
|| target.pluginCommit !== target.marketplaceCommit
|
|
442
|
+
) {
|
|
443
|
+
return {
|
|
444
|
+
status: 'MANUAL_REQUIRED',
|
|
445
|
+
reason: `${target.host} source identity is not eligible for a frozen bundled-family update`,
|
|
446
|
+
};
|
|
447
|
+
}
|
|
400
448
|
if (observed.exact) return { status: 'ALREADY_CURRENT', version: target.version };
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
449
|
+
if (!observed.installed) {
|
|
450
|
+
return {
|
|
451
|
+
status: 'MANUAL_REQUIRED',
|
|
452
|
+
reason: `${target.host} target plugin is not installed; no initial installation was performed`,
|
|
453
|
+
};
|
|
454
|
+
}
|
|
455
|
+
let remote;
|
|
456
|
+
try {
|
|
457
|
+
const remoteObservation = await run('git', [
|
|
458
|
+
'ls-remote', '--exit-code', `https://${target.githubHost}/${target.pluginRepo}.git`,
|
|
459
|
+
target.pluginTag, `${target.pluginTag}^{}`, target.marketplaceRef,
|
|
460
|
+
], { env, timeout: 30_000 });
|
|
461
|
+
remote = parseCodeBuddyRemoteObservation(target, remoteObservation.stdout);
|
|
462
|
+
} catch (error) {
|
|
463
|
+
return {
|
|
464
|
+
status: 'MANUAL_REQUIRED',
|
|
465
|
+
reason: `${target.host} could not prove the frozen remote refs: ${error?.message ?? String(error)}`,
|
|
466
|
+
};
|
|
467
|
+
}
|
|
468
|
+
if (!remote.exact) {
|
|
469
|
+
return {
|
|
470
|
+
status: 'MANUAL_REQUIRED',
|
|
471
|
+
reason: `${target.host} frozen tag and mutable ref do not both resolve to ${target.pluginCommit}`,
|
|
472
|
+
};
|
|
473
|
+
}
|
|
474
|
+
await run(detected.command, ['plugin', 'marketplace', 'update', target.marketplace], { env });
|
|
475
|
+
await run(detected.command, [
|
|
476
|
+
'plugin', 'update', `${target.plugin}@${target.marketplace}`, '--scope', 'user',
|
|
477
|
+
], { env });
|
|
478
|
+
const afterList = await run(detected.command, ['plugin', 'list', '--json'], { env });
|
|
479
|
+
const after = exactPluginObservation(target, afterList.stdout);
|
|
480
|
+
if (!after.exact) throw new Error(`${target.host} did not update to the frozen plugin identity`);
|
|
481
|
+
return { status: 'UPDATED', version: target.version, restartRequired: true };
|
|
405
482
|
}
|
|
406
483
|
|
|
407
484
|
const command = detected.command;
|
|
@@ -418,12 +495,15 @@ async function runStructuredUpdate(target, detected, run, {
|
|
|
418
495
|
return { status: 'ALREADY_CURRENT', version: target.version };
|
|
419
496
|
}
|
|
420
497
|
|
|
421
|
-
await bindStructuredMarketplace(target, command, env, run, before);
|
|
498
|
+
const marketplaceRebound = await bindStructuredMarketplace(target, command, env, run, before);
|
|
499
|
+
const current = target.host === 'claude' && marketplaceRebound
|
|
500
|
+
? await observeStructuredTarget(target, command, env, run)
|
|
501
|
+
: before;
|
|
422
502
|
const platform = getPlatform(target.host);
|
|
423
503
|
const selector = `${target.plugin}@${target.marketplace}`;
|
|
424
504
|
let installPath;
|
|
425
505
|
if (target.host === 'claude') {
|
|
426
|
-
const installArgs =
|
|
506
|
+
const installArgs = current.plugin.installed
|
|
427
507
|
? ['plugin', 'update', selector, '--scope', 'user', '--yes']
|
|
428
508
|
: platform.cli.install(target.plugin, target.marketplace);
|
|
429
509
|
await run(command, installArgs, { env });
|
|
@@ -450,10 +530,77 @@ async function runStructuredUpdate(target, detected, run, {
|
|
|
450
530
|
return { status: 'UPDATED', version: target.version, restartRequired: true };
|
|
451
531
|
}
|
|
452
532
|
|
|
453
|
-
function
|
|
454
|
-
const
|
|
455
|
-
const
|
|
456
|
-
|
|
533
|
+
function parseCodeBuddyRemoteObservation(target, stdout) {
|
|
534
|
+
const refs = new Map();
|
|
535
|
+
const tagRef = target.pluginTag.startsWith('refs/')
|
|
536
|
+
? target.pluginTag
|
|
537
|
+
: `refs/tags/${target.pluginTag}`;
|
|
538
|
+
const allowedRefs = new Set([tagRef, `${tagRef}^{}`, target.marketplaceRef]);
|
|
539
|
+
for (const line of String(stdout ?? '').split(/\r?\n/u)) {
|
|
540
|
+
if (line.length === 0) continue;
|
|
541
|
+
const match = /^([a-f0-9]{40})\t([^\s]+)$/u.exec(line);
|
|
542
|
+
if (!match) throw new Error('git ls-remote returned an invalid line');
|
|
543
|
+
const [, commit, ref] = match;
|
|
544
|
+
if (!allowedRefs.has(ref)) throw new Error(`git ls-remote returned an unexpected ref ${ref}`);
|
|
545
|
+
const existing = refs.get(ref);
|
|
546
|
+
if (existing && existing !== commit) throw new Error(`git ls-remote returned conflicting values for ${ref}`);
|
|
547
|
+
refs.set(ref, commit);
|
|
548
|
+
}
|
|
549
|
+
const peeledTag = refs.get(`${tagRef}^{}`);
|
|
550
|
+
const tagCommit = peeledTag ?? refs.get(tagRef);
|
|
551
|
+
const mutableCommit = refs.get(target.marketplaceRef);
|
|
552
|
+
return {
|
|
553
|
+
exact: tagCommit === target.pluginCommit && mutableCommit === target.pluginCommit,
|
|
554
|
+
tagCommit: tagCommit ?? null,
|
|
555
|
+
mutableCommit: mutableCommit ?? null,
|
|
556
|
+
};
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
function kimiExpectProgram({ removePlugin } = {}) {
|
|
560
|
+
const removeCommand = removePlugin
|
|
561
|
+
? `send -- "/plugins remove $removePlugin\\r"
|
|
562
|
+
expect {
|
|
563
|
+
-nocase -re {(remove|delete|uninstall).*(confirm|sure)|(confirm|sure).*(remove|delete|uninstall)} {
|
|
564
|
+
expect {
|
|
565
|
+
-ex $removePlugin { send -- "\\033\\[B\\r" }
|
|
566
|
+
timeout { exit 95 }
|
|
567
|
+
eof { exit 96 }
|
|
568
|
+
}
|
|
569
|
+
expect -re {> $}
|
|
570
|
+
}
|
|
571
|
+
-re {> $} {}
|
|
572
|
+
timeout { exit 93 }
|
|
573
|
+
eof { exit 94 }
|
|
574
|
+
}
|
|
575
|
+
`
|
|
576
|
+
: '';
|
|
577
|
+
return `set timeout 180
|
|
578
|
+
foreach variable {RELEASE_SKILL_KIMI_COMMAND RELEASE_SKILL_KIMI_INSTALL_URL RELEASE_SKILL_KIMI_REMOVE_PLUGIN} {
|
|
579
|
+
if {![info exists env($variable)]} { exit 90 }
|
|
580
|
+
}
|
|
581
|
+
set kimiCommand $env(RELEASE_SKILL_KIMI_COMMAND)
|
|
582
|
+
set installUrl $env(RELEASE_SKILL_KIMI_INSTALL_URL)
|
|
583
|
+
set removePlugin $env(RELEASE_SKILL_KIMI_REMOVE_PLUGIN)
|
|
584
|
+
spawn $kimiCommand
|
|
585
|
+
expect -re {> $}
|
|
586
|
+
${removeCommand}send -- "/plugins install $installUrl\\r"
|
|
587
|
+
expect {
|
|
588
|
+
-nocase -re {trust and install} {
|
|
589
|
+
expect {
|
|
590
|
+
-ex $installUrl { send -- "\\033\\[B\\r" }
|
|
591
|
+
timeout { exit 97 }
|
|
592
|
+
eof { exit 98 }
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
timeout { exit 91 }
|
|
596
|
+
eof { exit 92 }
|
|
597
|
+
}
|
|
598
|
+
expect -re {> $}
|
|
599
|
+
send -- "/reload\\r"
|
|
600
|
+
expect -re {> $}
|
|
601
|
+
send -- "/exit\\r"
|
|
602
|
+
expect eof
|
|
603
|
+
`;
|
|
457
604
|
}
|
|
458
605
|
|
|
459
606
|
async function observeKimiTarget(target, kimiHome, run) {
|
|
@@ -474,6 +621,7 @@ async function observeKimiTarget(target, kimiHome, run) {
|
|
|
474
621
|
}
|
|
475
622
|
const entry = installed?.plugins?.find((candidate) => candidate?.id === target.plugin);
|
|
476
623
|
if (!entry) return { installed: false };
|
|
624
|
+
if (!entry.github) return { installed: true, exact: false, source: 'legacy', entry };
|
|
477
625
|
const managedRoot = join(pluginsRoot, 'managed');
|
|
478
626
|
const pluginRoot = await resolveContained(managedRoot, target.plugin);
|
|
479
627
|
const declaredRoot = await resolveContained(managedRoot, relative(managedRoot, entry.root));
|
|
@@ -482,35 +630,53 @@ async function observeKimiTarget(target, kimiHome, run) {
|
|
|
482
630
|
await readFileContained(pluginRoot, 'package.json', { encoding: 'utf8' }),
|
|
483
631
|
'Kimi installed package manifest',
|
|
484
632
|
);
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
);
|
|
495
|
-
|
|
496
|
-
|
|
633
|
+
const refName = entry.github.ref?.kind === 'tag' ? entry.github.ref.value : undefined;
|
|
634
|
+
const revision = entry.github.installedSha;
|
|
635
|
+
let gitHeadExact = true;
|
|
636
|
+
try {
|
|
637
|
+
await access(join(pluginRoot, '.git'));
|
|
638
|
+
const head = await run('git', ['-C', pluginRoot, 'rev-parse', 'HEAD'], {
|
|
639
|
+
env: hostEnvironment('kimi', { kimiHome }),
|
|
640
|
+
timeout: 30_000,
|
|
641
|
+
});
|
|
642
|
+
gitHeadExact = head.stdout.trim() === target.pluginCommit;
|
|
643
|
+
} catch (error) {
|
|
644
|
+
if (error?.code !== 'ENOENT') throw error;
|
|
497
645
|
}
|
|
498
|
-
const head = await run('git', ['-C', pluginRoot, 'rev-parse', 'HEAD'], {
|
|
499
|
-
env: hostEnvironment('kimi'),
|
|
500
|
-
timeout: 30_000,
|
|
501
|
-
});
|
|
502
646
|
const exact = packageJson.name === target.plugin
|
|
503
647
|
&& packageJson.version === target.version
|
|
504
648
|
&& refName === target.pluginTag
|
|
505
649
|
&& revision === target.pluginCommit
|
|
506
|
-
&&
|
|
507
|
-
return {
|
|
650
|
+
&& gitHeadExact;
|
|
651
|
+
return {
|
|
652
|
+
installed: true,
|
|
653
|
+
exact,
|
|
654
|
+
source: 'github',
|
|
655
|
+
entry,
|
|
656
|
+
packageJson,
|
|
657
|
+
refName,
|
|
658
|
+
revision,
|
|
659
|
+
pluginRoot,
|
|
660
|
+
};
|
|
508
661
|
}
|
|
509
662
|
|
|
510
|
-
async function runKimiUpdate(target, detected, run, kimiHome
|
|
663
|
+
async function runKimiUpdate(target, detected, run, kimiHome, {
|
|
664
|
+
plan,
|
|
665
|
+
root,
|
|
666
|
+
verifyInstalledPayload,
|
|
667
|
+
}) {
|
|
511
668
|
const env = hostEnvironment('kimi', { kimiHome });
|
|
512
669
|
const before = await observeKimiTarget(target, kimiHome, run);
|
|
513
|
-
if (before.exact)
|
|
670
|
+
if (before.exact) {
|
|
671
|
+
await verifyStructuredInstalledPayload({
|
|
672
|
+
plan,
|
|
673
|
+
root,
|
|
674
|
+
target,
|
|
675
|
+
installPath: before.pluginRoot,
|
|
676
|
+
verifyInstalledPayload,
|
|
677
|
+
});
|
|
678
|
+
return { status: 'ALREADY_CURRENT', version: target.version };
|
|
679
|
+
}
|
|
514
680
|
await withTemporaryWorkspace(async (workspace) => {
|
|
515
681
|
const checkout = join(workspace.root, 'plugin');
|
|
516
682
|
await run('git', [
|
|
@@ -525,13 +691,28 @@ async function runKimiUpdate(target, detected, run, kimiHome) {
|
|
|
525
691
|
throw new Error(`Kimi checkout commit does not match frozen release commit ${target.pluginCommit}`);
|
|
526
692
|
}
|
|
527
693
|
const installUrl = `https://github.com/${target.pluginRepo}/releases/tag/${target.pluginTag}`;
|
|
528
|
-
|
|
694
|
+
const removePlugin = before.source === 'legacy' ? target.plugin : '';
|
|
695
|
+
await run(detected.expectCommand, ['-c', kimiExpectProgram({
|
|
696
|
+
...(removePlugin ? { removePlugin } : {}),
|
|
697
|
+
})], {
|
|
529
698
|
timeout: 240_000,
|
|
530
|
-
env
|
|
699
|
+
env: {
|
|
700
|
+
...env,
|
|
701
|
+
RELEASE_SKILL_KIMI_COMMAND: detected.command,
|
|
702
|
+
RELEASE_SKILL_KIMI_INSTALL_URL: installUrl,
|
|
703
|
+
RELEASE_SKILL_KIMI_REMOVE_PLUGIN: removePlugin,
|
|
704
|
+
},
|
|
531
705
|
});
|
|
532
706
|
}, { prefix: 'release-skill-kimi-update-' });
|
|
533
707
|
const after = await observeKimiTarget(target, kimiHome, run);
|
|
534
708
|
if (!after.exact) throw new Error('Kimi did not report the frozen plugin identity after TUI installation');
|
|
709
|
+
await verifyStructuredInstalledPayload({
|
|
710
|
+
plan,
|
|
711
|
+
root,
|
|
712
|
+
target,
|
|
713
|
+
installPath: after.pluginRoot,
|
|
714
|
+
verifyInstalledPayload,
|
|
715
|
+
});
|
|
535
716
|
return { status: 'UPDATED', version: target.version, restartRequired: true };
|
|
536
717
|
}
|
|
537
718
|
|
|
@@ -547,6 +728,19 @@ function aggregateStatus(results) {
|
|
|
547
728
|
return 'NO_APPLICABLE_HOSTS';
|
|
548
729
|
}
|
|
549
730
|
|
|
731
|
+
function failedHostResult(target, error) {
|
|
732
|
+
const message = error?.message ?? String(error);
|
|
733
|
+
return {
|
|
734
|
+
host: target.host,
|
|
735
|
+
unitId: target.unitId,
|
|
736
|
+
status: 'FAILED',
|
|
737
|
+
error: message,
|
|
738
|
+
reason: typeof error?.reason === 'string' ? error.reason : message,
|
|
739
|
+
...(error?.code !== undefined ? { code: error.code } : {}),
|
|
740
|
+
...(error?.details !== undefined ? { details: error.details } : {}),
|
|
741
|
+
};
|
|
742
|
+
}
|
|
743
|
+
|
|
550
744
|
export async function updateLocalHostPlugins({
|
|
551
745
|
plan,
|
|
552
746
|
root = process.cwd(),
|
|
@@ -562,9 +756,7 @@ export async function updateLocalHostPlugins({
|
|
|
562
756
|
if (confirmPlanDigest !== plan.digest) {
|
|
563
757
|
throw new Error('plan digest confirmation does not match the frozen release plan');
|
|
564
758
|
}
|
|
565
|
-
const selected = selectedHosts
|
|
566
|
-
? new Set(selectedHosts)
|
|
567
|
-
: new Set(checklist.localHostUpdate.hosts);
|
|
759
|
+
const selected = new Set(Array.isArray(selectedHosts) ? selectedHosts : []);
|
|
568
760
|
const unknown = [...selected].filter((host) => !checklist.localHostUpdate.hosts.includes(host));
|
|
569
761
|
if (unknown.length > 0) throw new Error(`selected hosts are not declared by the plan: ${unknown.join(', ')}`);
|
|
570
762
|
const targets = checklist.localHostUpdate.targets.filter((item) => selected.has(item.host));
|
|
@@ -572,19 +764,23 @@ export async function updateLocalHostPlugins({
|
|
|
572
764
|
|
|
573
765
|
const results = [];
|
|
574
766
|
for (const target of targets) {
|
|
575
|
-
const detected = await detect(target.host);
|
|
576
|
-
if (!detected?.available) {
|
|
577
|
-
results.push({
|
|
578
|
-
host: target.host,
|
|
579
|
-
unitId: target.unitId,
|
|
580
|
-
status: 'SKIPPED_NOT_INSTALLED',
|
|
581
|
-
reason: detected?.reason ?? 'host unavailable',
|
|
582
|
-
});
|
|
583
|
-
continue;
|
|
584
|
-
}
|
|
585
767
|
try {
|
|
768
|
+
const detected = await detect(target.host);
|
|
769
|
+
if (!detected?.available) {
|
|
770
|
+
results.push({
|
|
771
|
+
host: target.host,
|
|
772
|
+
unitId: target.unitId,
|
|
773
|
+
status: 'SKIPPED_NOT_INSTALLED',
|
|
774
|
+
reason: detected?.reason ?? 'host unavailable',
|
|
775
|
+
});
|
|
776
|
+
continue;
|
|
777
|
+
}
|
|
586
778
|
const outcome = target.host === 'kimi'
|
|
587
|
-
? await runKimiUpdate(target, detected, run, effectiveKimiHome
|
|
779
|
+
? await runKimiUpdate(target, detected, run, effectiveKimiHome, {
|
|
780
|
+
plan,
|
|
781
|
+
root,
|
|
782
|
+
verifyInstalledPayload,
|
|
783
|
+
})
|
|
588
784
|
: await runStructuredUpdate(target, detected, run, {
|
|
589
785
|
plan,
|
|
590
786
|
root,
|
|
@@ -592,12 +788,7 @@ export async function updateLocalHostPlugins({
|
|
|
592
788
|
});
|
|
593
789
|
results.push({ host: target.host, unitId: target.unitId, ...outcome });
|
|
594
790
|
} catch (error) {
|
|
595
|
-
results.push(
|
|
596
|
-
host: target.host,
|
|
597
|
-
unitId: target.unitId,
|
|
598
|
-
status: 'FAILED',
|
|
599
|
-
error: error?.message ?? String(error),
|
|
600
|
-
});
|
|
791
|
+
results.push(failedHostResult(target, error));
|
|
601
792
|
}
|
|
602
793
|
}
|
|
603
794
|
return {
|
package/src/commands/prepare.mjs
CHANGED
|
@@ -57,7 +57,11 @@ import {
|
|
|
57
57
|
} from '../snapshot/frozen.mjs';
|
|
58
58
|
import { ReleaseError, GATE_FAILED, CONFIG_INVALID, CONFIG_MISSING, FORBIDDEN_CONTENT_DETECTED, RELEASE_DOCS_STALE, DIRTY_SOURCE_INPUT, BUNDLE_STALE } from '../core/errors.mjs';
|
|
59
59
|
import { assertBundleFreshness } from '../core/bundle-freshness.mjs';
|
|
60
|
-
import {
|
|
60
|
+
import {
|
|
61
|
+
assertAdapterFreshness,
|
|
62
|
+
assertPlatformManifestFreshness,
|
|
63
|
+
assertSelfBootstrapFacts,
|
|
64
|
+
} from '../core/derived-artifact-gates.mjs';
|
|
61
65
|
import { isFoundationPluginVerificationEligible } from '../core/foundation-plugin-verification.mjs';
|
|
62
66
|
import { PKG_ROOT } from '../core/pkg-root.mjs';
|
|
63
67
|
import { writeFrozenMarker, FROZEN_MARKER_FILENAME } from '../core/frozen-marker.mjs';
|
|
@@ -3025,8 +3029,9 @@ export async function prepareRelease(options) {
|
|
|
3025
3029
|
// --- Step 1b-fast: version-sensitive derived-artifact fast pre-gates (O1) ---
|
|
3026
3030
|
// 2026-08-18 investigation §3.2: adapter drift and stale self-bootstrap
|
|
3027
3031
|
// fact pins used to surface only deep inside the ~80s full test hook.
|
|
3028
|
-
// Promote the
|
|
3029
|
-
// release-docs-self-bootstrap
|
|
3032
|
+
// Promote the three exact canonical checks (build-adapters --check,
|
|
3033
|
+
// generate-platform-manifest --check, and the release-docs-self-bootstrap
|
|
3034
|
+
// single-file test) to prepare's earliest
|
|
3030
3035
|
// stage so the same drift fails closed in seconds, before any hook. Like
|
|
3031
3036
|
// the bundle freshness gate this is artifact-integrity class: workflow
|
|
3032
3037
|
// trimming never exempts it. Installed layouts record not-applicable.
|
|
@@ -3034,7 +3039,8 @@ export async function prepareRelease(options) {
|
|
|
3034
3039
|
// the CLI, plain function calls, and the node:test harness — no
|
|
3035
3040
|
// environment variable may exempt them (a test seam is never a
|
|
3036
3041
|
// production switch). Tests that need lightweight fixtures inject the
|
|
3037
|
-
// adapterFreshnessFn/selfBootstrapFactsFn
|
|
3042
|
+
// adapterFreshnessFn/platformManifestFreshnessFn/selfBootstrapFactsFn
|
|
3043
|
+
// seams explicitly in-process.
|
|
3038
3044
|
await evidence.append({ phase: 'adapter-freshness', status: 'started' });
|
|
3039
3045
|
const adapterFreshnessFn = options.adapterFreshnessFn ?? assertAdapterFreshness;
|
|
3040
3046
|
let adapterFreshness;
|
|
@@ -3063,6 +3069,35 @@ export async function prepareRelease(options) {
|
|
|
3063
3069
|
});
|
|
3064
3070
|
}
|
|
3065
3071
|
|
|
3072
|
+
await evidence.append({ phase: 'platform-manifest-freshness', status: 'started' });
|
|
3073
|
+
const platformManifestFreshnessFn = options.platformManifestFreshnessFn
|
|
3074
|
+
?? assertPlatformManifestFreshness;
|
|
3075
|
+
let platformManifestFreshness;
|
|
3076
|
+
try {
|
|
3077
|
+
platformManifestFreshness = await platformManifestFreshnessFn(PKG_ROOT);
|
|
3078
|
+
} catch (err) {
|
|
3079
|
+
await evidence.append({
|
|
3080
|
+
phase: 'platform-manifest-freshness',
|
|
3081
|
+
status: 'blocking',
|
|
3082
|
+
reason: err.details?.reason ?? null,
|
|
3083
|
+
error: { code: err.code, message: err.message },
|
|
3084
|
+
});
|
|
3085
|
+
throw err;
|
|
3086
|
+
}
|
|
3087
|
+
if (platformManifestFreshness?.applicable === false) {
|
|
3088
|
+
await evidence.append({
|
|
3089
|
+
phase: 'platform-manifest-freshness',
|
|
3090
|
+
status: 'not-applicable',
|
|
3091
|
+
reason: platformManifestFreshness.reason,
|
|
3092
|
+
});
|
|
3093
|
+
} else {
|
|
3094
|
+
await evidence.append({
|
|
3095
|
+
phase: 'platform-manifest-freshness',
|
|
3096
|
+
status: 'completed',
|
|
3097
|
+
durationMs: platformManifestFreshness?.durationMs ?? null,
|
|
3098
|
+
});
|
|
3099
|
+
}
|
|
3100
|
+
|
|
3066
3101
|
await evidence.append({ phase: 'self-bootstrap-facts', status: 'started' });
|
|
3067
3102
|
const selfBootstrapFactsFn = options.selfBootstrapFactsFn ?? assertSelfBootstrapFacts;
|
|
3068
3103
|
let selfBootstrapFacts;
|