release-skill 0.8.0 → 0.9.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 +65 -7
- package/INSTALL.md +14 -2
- package/INSTALL.zh-CN.md +12 -2
- package/README.md +51 -31
- package/README.zh-CN.md +33 -20
- package/adapters/claude/.claude-plugin/marketplace.json +1 -1
- package/adapters/claude/.claude-plugin/plugin.json +1 -1
- package/adapters/claude/bin/error-codes.json +1 -1
- package/adapters/claude/bin/foundation-resource-binding.json +1 -1
- package/adapters/claude/bin/prebuild-manifest.json +8 -4
- package/adapters/claude/bin/prebuilds/darwin-arm64/bound_read.darwin-arm64.node +0 -0
- package/adapters/claude/bin/prebuilds/darwin-x64/bound_read.darwin-x64.node +0 -0
- package/adapters/claude/bin/prebuilds/linux-arm64-gnu/bound_read.linux-arm64-gnu.node +0 -0
- package/adapters/claude/bin/prebuilds/linux-x64-gnu/bound_read.linux-x64-gnu.node +0 -0
- package/adapters/claude/bin/registry.json +22 -1
- package/adapters/claude/bin/release-skill-local-finish.mjs +4 -0
- package/adapters/claude/bin/release-skill.bundle.mjs +7152 -4231
- package/adapters/claude/bin/rules.json +1 -1
- package/adapters/claude/schemas/release-plan.schema.json +43 -5
- package/adapters/claude/schemas/release-run.schema.json +35 -1
- package/adapters/claude/skills/release-finish/SKILL.md +65 -0
- package/adapters/claude/skills/release-verify/SKILL.md +6 -1
- package/adapters/claude/src/schemas/filesystem-tree-observation.schema.json +55 -0
- package/adapters/claude/src/schemas/host-verification-result.schema.json +2 -2
- package/adapters/claude/src/schemas/plugin-verification-request.schema.json +374 -0
- package/adapters/claude/src/schemas/plugin-verification-result.schema.json +799 -0
- package/adapters/claude/src/schemas/watchdog-termination-envelope.schema.json +16 -0
- package/adapters/codex/.codex-plugin/plugin.json +2 -2
- package/adapters/codex/bin/error-codes.json +1 -1
- package/adapters/codex/bin/foundation-resource-binding.json +1 -1
- package/adapters/codex/bin/prebuild-manifest.json +8 -4
- package/adapters/codex/bin/prebuilds/darwin-arm64/bound_read.darwin-arm64.node +0 -0
- package/adapters/codex/bin/prebuilds/darwin-x64/bound_read.darwin-x64.node +0 -0
- package/adapters/codex/bin/prebuilds/linux-arm64-gnu/bound_read.linux-arm64-gnu.node +0 -0
- package/adapters/codex/bin/prebuilds/linux-x64-gnu/bound_read.linux-x64-gnu.node +0 -0
- package/adapters/codex/bin/registry.json +22 -1
- package/adapters/codex/bin/release-skill-local-finish.mjs +4 -0
- package/adapters/codex/bin/release-skill.bundle.mjs +7152 -4231
- package/adapters/codex/bin/rules.json +1 -1
- package/adapters/codex/schemas/release-plan.schema.json +43 -5
- package/adapters/codex/schemas/release-run.schema.json +35 -1
- package/adapters/codex/skills/release-finish/SKILL.md +72 -0
- package/adapters/codex/skills/release-verify/SKILL.md +6 -1
- package/adapters/codex/src/schemas/filesystem-tree-observation.schema.json +55 -0
- package/adapters/codex/src/schemas/host-verification-result.schema.json +2 -2
- package/adapters/codex/src/schemas/plugin-verification-request.schema.json +374 -0
- package/adapters/codex/src/schemas/plugin-verification-result.schema.json +799 -0
- package/adapters/codex/src/schemas/watchdog-termination-envelope.schema.json +16 -0
- package/adapters/kimi/.kimi-plugin/plugin.json +1 -1
- package/adapters/kimi/bin/error-codes.json +1 -1
- package/adapters/kimi/bin/foundation-resource-binding.json +1 -1
- package/adapters/kimi/bin/prebuild-manifest.json +8 -4
- package/adapters/kimi/bin/prebuilds/darwin-arm64/bound_read.darwin-arm64.node +0 -0
- package/adapters/kimi/bin/prebuilds/darwin-x64/bound_read.darwin-x64.node +0 -0
- package/adapters/kimi/bin/prebuilds/linux-arm64-gnu/bound_read.linux-arm64-gnu.node +0 -0
- package/adapters/kimi/bin/prebuilds/linux-x64-gnu/bound_read.linux-x64-gnu.node +0 -0
- package/adapters/kimi/bin/registry.json +22 -1
- package/adapters/kimi/bin/release-skill-local-finish.mjs +4 -0
- package/adapters/kimi/bin/release-skill.bundle.mjs +7152 -4231
- package/adapters/kimi/bin/rules.json +1 -1
- package/adapters/kimi/schemas/release-plan.schema.json +43 -5
- package/adapters/kimi/schemas/release-run.schema.json +35 -1
- package/adapters/kimi/skills/release-finish/SKILL.md +72 -0
- package/adapters/kimi/skills/release-verify/SKILL.md +6 -1
- package/adapters/kimi/src/schemas/filesystem-tree-observation.schema.json +55 -0
- package/adapters/kimi/src/schemas/host-verification-result.schema.json +2 -2
- package/adapters/kimi/src/schemas/plugin-verification-request.schema.json +374 -0
- package/adapters/kimi/src/schemas/plugin-verification-result.schema.json +799 -0
- package/adapters/kimi/src/schemas/watchdog-termination-envelope.schema.json +16 -0
- package/adapters/workbuddy/.codebuddy-plugin/plugin.json +1 -1
- package/adapters/workbuddy/bin/error-codes.json +1 -1
- package/adapters/workbuddy/bin/foundation-resource-binding.json +1 -1
- package/adapters/workbuddy/bin/prebuild-manifest.json +8 -4
- package/adapters/workbuddy/bin/prebuilds/darwin-arm64/bound_read.darwin-arm64.node +0 -0
- package/adapters/workbuddy/bin/prebuilds/darwin-x64/bound_read.darwin-x64.node +0 -0
- package/adapters/workbuddy/bin/prebuilds/linux-arm64-gnu/bound_read.linux-arm64-gnu.node +0 -0
- package/adapters/workbuddy/bin/prebuilds/linux-x64-gnu/bound_read.linux-x64-gnu.node +0 -0
- package/adapters/workbuddy/bin/registry.json +22 -1
- package/adapters/workbuddy/bin/release-skill-local-finish.mjs +4 -0
- package/adapters/workbuddy/bin/release-skill.bundle.mjs +7152 -4231
- package/adapters/workbuddy/bin/rules.json +1 -1
- package/adapters/workbuddy/schemas/release-plan.schema.json +43 -5
- package/adapters/workbuddy/schemas/release-run.schema.json +35 -1
- package/adapters/workbuddy/skills/release-finish/SKILL.md +65 -0
- package/adapters/workbuddy/skills/release-verify/SKILL.md +6 -1
- package/adapters/workbuddy/src/schemas/filesystem-tree-observation.schema.json +55 -0
- package/adapters/workbuddy/src/schemas/host-verification-result.schema.json +2 -2
- package/adapters/workbuddy/src/schemas/plugin-verification-request.schema.json +374 -0
- package/adapters/workbuddy/src/schemas/plugin-verification-result.schema.json +799 -0
- package/adapters/workbuddy/src/schemas/watchdog-termination-envelope.schema.json +16 -0
- package/bin/error-codes.json +1 -1
- package/bin/foundation-resource-binding.json +1 -1
- package/bin/prebuild-manifest.json +8 -4
- package/bin/prebuilds/darwin-arm64/bound_read.darwin-arm64.node +0 -0
- package/bin/prebuilds/darwin-x64/bound_read.darwin-x64.node +0 -0
- package/bin/prebuilds/linux-arm64-gnu/bound_read.linux-arm64-gnu.node +0 -0
- package/bin/prebuilds/linux-x64-gnu/bound_read.linux-x64-gnu.node +0 -0
- package/bin/registry.json +22 -1
- package/bin/release-skill-cli.mjs +126 -1
- package/bin/release-skill-local-finish.mjs +4 -0
- package/bin/release-skill.bundle.mjs +7152 -4231
- package/bin/rules.json +1 -1
- package/package.json +4 -4
- package/platform-manifest.json +30 -4
- package/schemas/release-plan.schema.json +43 -5
- package/schemas/release-run.schema.json +35 -1
- package/skills/release-finish/SKILL.md +65 -0
- package/skills/release-verify/SKILL.md +6 -1
- package/skills-src/release-finish/SKILL.md +65 -0
- package/skills-src/release-verify/SKILL.md +6 -1
- package/src/commands/approve.mjs +14 -2
- package/src/commands/distribute.mjs +562 -321
- package/src/commands/post-release-local.mjs +611 -0
- package/src/commands/postverify.mjs +262 -155
- package/src/commands/prepare.mjs +83 -57
- package/src/commands/publish.mjs +11 -10
- package/src/commands/reconcile.mjs +11 -10
- package/src/commands/setup.mjs +26 -13
- package/src/commands/ship.mjs +45 -12
- package/src/commands/verify.mjs +81 -18
- package/src/core/adoption-assessment.mjs +0 -3
- package/src/core/approval.mjs +11 -8
- package/src/core/foundation-plugin-verification.mjs +247 -0
- package/src/core/hooks.mjs +0 -16
- package/src/core/plan.mjs +19 -11
- package/src/core/postpublish-approval.mjs +12 -2
- package/src/core/postpublish-bundle.mjs +51 -13
- package/src/core/postpublish.mjs +146 -6
- package/src/core/preset-executor.mjs +53 -0
- package/src/core/proposal-inbox.mjs +91 -27
- package/src/core/recovery.mjs +27 -12
- package/src/core/skill-resource-closure.mjs +8 -4
- package/src/producers/build-adapters.mjs +10 -1
- package/src/producers/foundation-resource-projection.mjs +3 -3
- package/src/schemas/filesystem-tree-observation.schema.json +55 -0
- package/src/schemas/host-verification-result.schema.json +2 -2
- package/src/schemas/plugin-verification-request.schema.json +374 -0
- package/src/schemas/plugin-verification-result.schema.json +799 -0
- package/src/schemas/watchdog-termination-envelope.schema.json +16 -0
|
@@ -244,28 +244,38 @@ function sameClosureResources(actual, expected) {
|
|
|
244
244
|
}
|
|
245
245
|
|
|
246
246
|
/**
|
|
247
|
-
*
|
|
248
|
-
*
|
|
247
|
+
* Verify the frozen execution bundle closure WITHOUT installing anything
|
|
248
|
+
* (rework R-01: the full-declaration preflight reuses this exact
|
|
249
|
+
* implementation before the first external write — a later declaration's
|
|
250
|
+
* missing/drifted bundle must fail the whole saga with zero writes).
|
|
249
251
|
*
|
|
250
|
-
* Steps:
|
|
252
|
+
* Steps (the first half of verifyAndInstallExecutionBundle, shared verbatim):
|
|
251
253
|
* 1. Strictly read every frozen resource from the digest-addressed store
|
|
252
254
|
* (`readFileStrict` with the frozen sha256 content guard); the file mode
|
|
253
255
|
* must still equal the publication mode — any deviation is tamper;
|
|
254
256
|
* 2. recompute the closure through Foundation in a disposable workspace and
|
|
255
|
-
* compare it (digest + resources) with the plan's frozen closure
|
|
256
|
-
* 3. exclusively publish the verified bytes into the worktree — an occupied
|
|
257
|
-
* target means the bundle would shadow a frozen tag file and fails closed.
|
|
257
|
+
* compare it (digest + resources) with the plan's frozen closure.
|
|
258
258
|
*
|
|
259
259
|
* @param {object} params
|
|
260
|
-
* @param {object} params.
|
|
260
|
+
* @param {object} params.postPublish - The CURRENT declaration being
|
|
261
|
+
* verified (REQUIRED, rework R-06: the v1/v2 normalized single item or the
|
|
262
|
+
* v3 loop item; an array fails closed).
|
|
261
263
|
* @param {string} params.planPath - Absolute plan authority path (mechanical
|
|
262
264
|
* bundle-root anchor).
|
|
263
|
-
* @
|
|
264
|
-
*
|
|
265
|
+
* @returns {Promise<{verified: boolean, bytesByPath: Map<string, Buffer>}>}
|
|
266
|
+
* `verified: false` with an empty map when the declaration carries no
|
|
267
|
+
* executionBundle (a legal no-bundle declaration).
|
|
265
268
|
*/
|
|
266
|
-
export async function
|
|
267
|
-
const
|
|
268
|
-
if (!
|
|
269
|
+
export async function verifyExecutionBundle({ planPath, postPublish } = {}) {
|
|
270
|
+
const declaration = postPublish;
|
|
271
|
+
if (!declaration || typeof declaration !== 'object') {
|
|
272
|
+
fail('verifyExecutionBundle requires the current postPublish declaration; pass the normalized single item or the v3 loop item explicitly');
|
|
273
|
+
}
|
|
274
|
+
if (Array.isArray(declaration)) {
|
|
275
|
+
fail('verifyExecutionBundle requires the current postPublish declaration, never the declaration array');
|
|
276
|
+
}
|
|
277
|
+
const bundle = declaration.executionBundle;
|
|
278
|
+
if (!bundle) return { verified: false, bytesByPath: new Map() };
|
|
269
279
|
const closure = bundle.closure;
|
|
270
280
|
if (!closure || !Array.isArray(closure.resources) || typeof closure.digest !== 'string') {
|
|
271
281
|
fail('plan carries a malformed executionBundle closure');
|
|
@@ -318,10 +328,38 @@ export async function verifyAndInstallExecutionBundle({ plan, planPath, worktree
|
|
|
318
328
|
fail(`bundle closure recomputation failed: ${cause?.message ?? cause}`, { kind: cause?.details?.kind });
|
|
319
329
|
}
|
|
320
330
|
|
|
331
|
+
return { verified: true, bytesByPath };
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/**
|
|
335
|
+
* Re-verify the frozen execution bundle (the SAME closure verification the
|
|
336
|
+
* full-declaration preflight runs) and install ONLY the verified bytes into
|
|
337
|
+
* the detached tag worktree. Runs BEFORE any hook or external write.
|
|
338
|
+
*
|
|
339
|
+
* Install step: exclusively publish the verified bytes into the worktree —
|
|
340
|
+
* an occupied target means the bundle would shadow a frozen tag file and
|
|
341
|
+
* fails closed.
|
|
342
|
+
*
|
|
343
|
+
* @param {object} params
|
|
344
|
+
* @param {object} params.postPublish - The CURRENT declaration being
|
|
345
|
+
* executed (v3 multi-unit contract §4.3). REQUIRED (rework R-06): callers
|
|
346
|
+
* must pass the declaration explicitly — v1/v2 callers pass the single item
|
|
347
|
+
* obtained from normalizePostPublishView(), v3 callers pass their loop
|
|
348
|
+
* item. Omitting it fails closed; an array never matches (fail-closed).
|
|
349
|
+
* @param {string} params.planPath - Absolute plan authority path (mechanical
|
|
350
|
+
* bundle-root anchor).
|
|
351
|
+
* @param {string} params.worktreePath - Detached tag worktree root.
|
|
352
|
+
* @returns {Promise<{installed: string[]}>} Installed relative paths.
|
|
353
|
+
*/
|
|
354
|
+
export async function verifyAndInstallExecutionBundle({ planPath, worktreePath, postPublish } = {}) {
|
|
355
|
+
const { bytesByPath } = await verifyExecutionBundle({ planPath, postPublish });
|
|
356
|
+
const declaration = postPublish;
|
|
357
|
+
const closure = declaration?.executionBundle?.closure;
|
|
358
|
+
|
|
321
359
|
// Install verified bytes into the tag worktree — exclusively: the bundle
|
|
322
360
|
// must never overwrite (shadow) a file that already belongs to the tag.
|
|
323
361
|
const installed = [];
|
|
324
|
-
for (const resource of closure
|
|
362
|
+
for (const resource of closure?.resources ?? []) {
|
|
325
363
|
try {
|
|
326
364
|
await publishFileExclusive(worktreePath, resource.path, bytesByPath.get(resource.path), {
|
|
327
365
|
mode: EXECUTION_BUNDLE_FILE_MODE,
|
package/src/core/postpublish.mjs
CHANGED
|
@@ -481,6 +481,131 @@ export function validatePostPublishDeclaration(postPublish, options = {}) {
|
|
|
481
481
|
return postPublish;
|
|
482
482
|
}
|
|
483
483
|
|
|
484
|
+
/**
|
|
485
|
+
* Version-aware read-only normalization of the frozen plan's postPublish
|
|
486
|
+
* field (multi-release-unit postPublish v3, design §4.2 归一表).
|
|
487
|
+
*
|
|
488
|
+
* This is the SINGLE normalization seam every reader (approve, distribute,
|
|
489
|
+
* postVerify, ship, verify, recovery, publish, reconcile, approval) must use
|
|
490
|
+
* to obtain the declaration array view. The accepted shape is derived from
|
|
491
|
+
* `plan.planVersion` ONLY — readers must never guess the shape from the
|
|
492
|
+
* field value (no shape sniffing), and no second normalizer or compatibility
|
|
493
|
+
* schema may exist. Pure and strictly read-only: never mutates the plan and
|
|
494
|
+
* never rewrites legacy plans, summaries, or evidence.
|
|
495
|
+
*
|
|
496
|
+
* | planVersion | input shape | result |
|
|
497
|
+
* |-------------|---------------|------------------|
|
|
498
|
+
* | 1/2 | absent | [] |
|
|
499
|
+
* | 1/2 | single object | [declaration] |
|
|
500
|
+
* | 1/2 | array | rejected |
|
|
501
|
+
* | 3 | array | the array itself |
|
|
502
|
+
* | 3 | object/absent | rejected |
|
|
503
|
+
*
|
|
504
|
+
* Values outside the table (null, primitives, unknown planVersion) fail
|
|
505
|
+
* closed with GATE_FAILED: a schema-valid plan can never produce them, and a
|
|
506
|
+
* reader must never paper over a shape it does not understand.
|
|
507
|
+
*
|
|
508
|
+
* @param {object} plan - Frozen plan (schema-valid by the caller's contract).
|
|
509
|
+
* @returns {object[]} The declaration array view (empty for legacy absent).
|
|
510
|
+
* @throws {ReleaseError} GATE_FAILED on any shape/version mismatch.
|
|
511
|
+
*/
|
|
512
|
+
export function normalizePostPublishView(plan) {
|
|
513
|
+
const version = plan?.planVersion;
|
|
514
|
+
const postPublish = plan?.postPublish;
|
|
515
|
+
if (version === 1 || version === 2) {
|
|
516
|
+
if (postPublish === undefined) return [];
|
|
517
|
+
if (Array.isArray(postPublish)) {
|
|
518
|
+
throw new ReleaseError(
|
|
519
|
+
GATE_FAILED,
|
|
520
|
+
`planVersion ${version} plans must not carry a postPublish array; only planVersion 3 accepts the array shape`,
|
|
521
|
+
{ planVersion: version, postPublishKind: 'array' },
|
|
522
|
+
);
|
|
523
|
+
}
|
|
524
|
+
if (postPublish === null || typeof postPublish !== 'object') {
|
|
525
|
+
throw new ReleaseError(
|
|
526
|
+
GATE_FAILED,
|
|
527
|
+
`planVersion ${version} plans must carry a single postPublish object or none; found ${postPublish === null ? 'null' : typeof postPublish}`,
|
|
528
|
+
{ planVersion: version },
|
|
529
|
+
);
|
|
530
|
+
}
|
|
531
|
+
return [postPublish];
|
|
532
|
+
}
|
|
533
|
+
if (version === 3) {
|
|
534
|
+
if (!Array.isArray(postPublish)) {
|
|
535
|
+
throw new ReleaseError(
|
|
536
|
+
GATE_FAILED,
|
|
537
|
+
`planVersion 3 plans must carry a postPublish array (possibly empty); found ${postPublish === undefined ? 'no postPublish field' : postPublish === null ? 'null' : 'a single object'}`,
|
|
538
|
+
{ planVersion: 3, postPublishKind: postPublish === undefined ? 'absent' : postPublish === null ? 'null' : 'object' },
|
|
539
|
+
);
|
|
540
|
+
}
|
|
541
|
+
return postPublish;
|
|
542
|
+
}
|
|
543
|
+
throw new ReleaseError(
|
|
544
|
+
GATE_FAILED,
|
|
545
|
+
`cannot normalize postPublish shape: unknown planVersion ${String(version)}`,
|
|
546
|
+
{ planVersion: version },
|
|
547
|
+
);
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
/**
|
|
551
|
+
* Array-level domain validation: every EXPLICIT hooks[].id must be unique
|
|
552
|
+
* across the whole declaration array (multi-release-unit postPublish v3,
|
|
553
|
+
* design §9.2 rule 3; rework R-02). The (planDigest, hookId) approval
|
|
554
|
+
* contract binds each explicit hook id plan-wide, so a duplicate across
|
|
555
|
+
* units must fail before any approval is minted or consumed and before any
|
|
556
|
+
* side effect — JSON Schema cannot express cross-array business uniqueness,
|
|
557
|
+
* so this function is the SINGLE runtime authority for it.
|
|
558
|
+
*
|
|
559
|
+
* Every entry only normalizes its input to the declaration array view and
|
|
560
|
+
* calls this function: prepare, setup/adoption assessment, approve, the
|
|
561
|
+
* postpublish approval validator, distribute, and postVerify. Target and
|
|
562
|
+
* internal probe local ids may repeat across units (checkpoints are
|
|
563
|
+
* unit-namespaced); explicit hook ids may not.
|
|
564
|
+
*
|
|
565
|
+
* @param {object[]} declarations - The declaration array view
|
|
566
|
+
* (`normalizePostPublishView()` output, or the per-unit postPublish blocks
|
|
567
|
+
* collected by prepare/setup).
|
|
568
|
+
* @throws {ReleaseError} GATE_FAILED with details { hookId, unitIds } on the
|
|
569
|
+
* first cross-unit duplicate.
|
|
570
|
+
*/
|
|
571
|
+
export function validatePostPublishHookIdUniqueness(declarations) {
|
|
572
|
+
const explicitHookIdOwner = new Map();
|
|
573
|
+
for (const declaration of declarations ?? []) {
|
|
574
|
+
for (const hook of declaration?.hooks ?? []) {
|
|
575
|
+
const owner = explicitHookIdOwner.get(hook.id);
|
|
576
|
+
if (owner !== undefined && owner !== declaration.unitId) {
|
|
577
|
+
throw new ReleaseError(
|
|
578
|
+
GATE_FAILED,
|
|
579
|
+
`postPublish hook id "${hook.id}" is declared by units "${owner}" and "${declaration.unitId}"; explicit hooks[].id must be unique across the whole project (target and probe local ids may repeat across units)`,
|
|
580
|
+
{ hookId: hook.id, unitIds: [owner, declaration.unitId] },
|
|
581
|
+
);
|
|
582
|
+
}
|
|
583
|
+
explicitHookIdOwner.set(hook.id, declaration.unitId);
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
/**
|
|
589
|
+
* Deterministically derive the checkpoint action id for one postPublish
|
|
590
|
+
* local action (target probe/mirror or hook) inside one release unit
|
|
591
|
+
* (§4.3 checkpoint binding). planVersion 3 namespaces every action id under
|
|
592
|
+
* the owning declaration's unitId (`unitId/localId`); legacy plans keep the
|
|
593
|
+
* bare local id so pre-existing checkpoint records and approvals stay valid.
|
|
594
|
+
* This is the SINGLE derivation used by every consumer (distribute,
|
|
595
|
+
* postVerify, recovery, verify), so a checkpoint id can never drift between
|
|
596
|
+
* the run records, the recovery mapping, and the approval binding.
|
|
597
|
+
*
|
|
598
|
+
* @param {object} params
|
|
599
|
+
* @param {number} params.planVersion - Frozen plan's planVersion.
|
|
600
|
+
* @param {string} params.unitId - Owning declaration's unitId.
|
|
601
|
+
* @param {string} params.localId - Local action id (`probe-<target.id>`,
|
|
602
|
+
* `<target.id>`, or the hook id).
|
|
603
|
+
* @returns {string} The checkpoint action id.
|
|
604
|
+
*/
|
|
605
|
+
export function postPublishActionId({ planVersion, unitId, localId }) {
|
|
606
|
+
return planVersion === 3 ? `${unitId}/${localId}` : localId;
|
|
607
|
+
}
|
|
608
|
+
|
|
484
609
|
/**
|
|
485
610
|
* Order targets so every target comes after its dependsOn target.
|
|
486
611
|
*
|
|
@@ -595,24 +720,39 @@ export function effectiveHookRequiresApproval(hook) {
|
|
|
595
720
|
*
|
|
596
721
|
* @param {object} args
|
|
597
722
|
* @param {object} args.plan - Frozen plan (digest + units + postPublish).
|
|
723
|
+
* @param {object} args.postPublish - THE current declaration the caller is
|
|
724
|
+
* executing. REQUIRED (rework R-06): callers must pass the declaration
|
|
725
|
+
* explicitly — the unit identity is never derived from a global
|
|
726
|
+
* single-object read (contract §4.3). v1/v2 callers pass the single item
|
|
727
|
+
* obtained from normalizePostPublishView(); v3 callers pass their loop
|
|
728
|
+
* item. Omitting it fails closed with GATE_FAILED; a declaration ARRAY
|
|
729
|
+
* (or anything non-object) fails closed too.
|
|
598
730
|
* @param {string} args.runId - Current distribute run id.
|
|
599
731
|
* @param {object} args.sourceRun - Sealed source run (finishedAt = publishedAt).
|
|
600
732
|
* @param {string} args.payloadDir - Materialized payload directory.
|
|
601
733
|
* @param {'distribute'|'postVerify'} args.phase
|
|
602
734
|
* @param {object} [args.verifyEvidence] - Verify evidence (postVerify phase).
|
|
603
735
|
* @returns {object} The context projection.
|
|
736
|
+
* @throws {ReleaseError} GATE_FAILED when no single declaration is in scope.
|
|
604
737
|
*/
|
|
605
|
-
export function buildPostPublishContext({ plan, runId, sourceRun, payloadDir, phase, verifyEvidence }) {
|
|
606
|
-
const
|
|
607
|
-
|
|
738
|
+
export function buildPostPublishContext({ plan, postPublish, runId, sourceRun, payloadDir, phase, verifyEvidence }) {
|
|
739
|
+
const declaration = postPublish;
|
|
740
|
+
if (!declaration || typeof declaration !== 'object' || Array.isArray(declaration)) {
|
|
741
|
+
throw new ReleaseError(
|
|
742
|
+
GATE_FAILED,
|
|
743
|
+
`buildPostPublishContext requires the current postPublish declaration; found ${declaration === undefined ? 'no declaration' : Array.isArray(declaration) ? 'a declaration array' : typeof declaration}`,
|
|
744
|
+
{ planVersion: plan?.planVersion, unitId: declaration?.unitId ?? null },
|
|
745
|
+
);
|
|
746
|
+
}
|
|
747
|
+
const unit = (plan.units ?? []).find((entry) => entry.id === declaration.unitId);
|
|
608
748
|
const frozenSnapshot = unit?.frozenSnapshot ?? {};
|
|
609
749
|
return {
|
|
610
750
|
planDigest: plan.digest,
|
|
611
751
|
runId,
|
|
612
|
-
unitId:
|
|
752
|
+
unitId: declaration.unitId,
|
|
613
753
|
version: unit?.targetVersion,
|
|
614
|
-
tag:
|
|
615
|
-
commit:
|
|
754
|
+
tag: declaration.tag,
|
|
755
|
+
commit: declaration.tagCommit,
|
|
616
756
|
...(frozenSnapshot.tree !== undefined ? { tree: frozenSnapshot.tree } : {}),
|
|
617
757
|
...(frozenSnapshot.manifestDigest !== undefined ? { manifestDigest: frozenSnapshot.manifestDigest } : {}),
|
|
618
758
|
publishedAt: sourceRun?.finishedAt,
|
|
@@ -22,12 +22,17 @@
|
|
|
22
22
|
* @module core/preset-executor
|
|
23
23
|
*/
|
|
24
24
|
|
|
25
|
+
import { rm } from 'node:fs/promises';
|
|
26
|
+
|
|
25
27
|
import { ReleaseError, POST_PUBLISH_VERIFY_FAILED } from './errors.mjs';
|
|
26
28
|
import { resolveProposalInboxTransport } from './presets.mjs';
|
|
27
29
|
import { executeNotifyHandoffHook } from './notify-handoff.mjs';
|
|
28
30
|
import {
|
|
31
|
+
buildProposalDocument,
|
|
29
32
|
executeProposalInboxGitPushHook,
|
|
30
33
|
executeProposalInboxLocalFileHook,
|
|
34
|
+
observeProposalInboxGitPush,
|
|
35
|
+
proposalFileName,
|
|
31
36
|
} from './proposal-inbox.mjs';
|
|
32
37
|
import { executeMarketplaceRegistryEntryHook } from './marketplace-registry-entry.mjs';
|
|
33
38
|
import { executeDocsRefreshHook } from './docs-refresh-preset.mjs';
|
|
@@ -173,3 +178,51 @@ export async function executePresetHook(params) {
|
|
|
173
178
|
}
|
|
174
179
|
}
|
|
175
180
|
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Narrow READ-ONLY preset preflight (rework R-01): deterministic conflicts a
|
|
184
|
+
* preset can already recognize from its existing observe-before-write
|
|
185
|
+
* observation, evaluated BEFORE the first external write of the phase. This
|
|
186
|
+
* is NOT a second dispatch table and NOT a lock — it only covers the presets
|
|
187
|
+
* whose observation implementation already exists (proposal-inbox git-push:
|
|
188
|
+
* the proposal path exists with different bytes -> the shared observation
|
|
189
|
+
* throws REMOTE_CONFLICT), and every preset hook is re-observed at execution
|
|
190
|
+
* time regardless.
|
|
191
|
+
*
|
|
192
|
+
* Returns `null` for presets/transports without a read-only deterministic
|
|
193
|
+
* conflict observation (proposal-inbox local-file, notify-handoff, ...) —
|
|
194
|
+
* their execution-phase observe-before-write stays the only gate.
|
|
195
|
+
*
|
|
196
|
+
* @param {object} params
|
|
197
|
+
* @param {object} params.hook - Declared preset hook entry.
|
|
198
|
+
* @param {object} params.contextProjection - The deterministic §2.3 context
|
|
199
|
+
* projection of the current run (the same bytes the execution-phase
|
|
200
|
+
* delivery serializes; lineage-stable runId).
|
|
201
|
+
* @param {Function} [params.exec] - Injectable git exec (tests).
|
|
202
|
+
* @returns {Promise<{verdict: string, proposalPath: string}|null>}
|
|
203
|
+
* @throws {ReleaseError} REMOTE_CONFLICT/REMOTE_UNAVAILABLE from the shared
|
|
204
|
+
* observation implementation.
|
|
205
|
+
*/
|
|
206
|
+
export async function preflightPresetHook(params) {
|
|
207
|
+
const { hook, contextProjection, exec } = params ?? {};
|
|
208
|
+
if (!hook || typeof hook.preset !== 'string') return null;
|
|
209
|
+
if (hook.preset !== 'proposal-inbox') return null;
|
|
210
|
+
const target = hook.config?.target;
|
|
211
|
+
if (!target) return null; // targetOptional degradation: nothing remote to observe.
|
|
212
|
+
const transport = resolveProposalInboxTransport(hook.config);
|
|
213
|
+
if (transport !== 'git-push') return null; // local-file: no deterministic remote conflict observation.
|
|
214
|
+
|
|
215
|
+
// The same observation implementation the execution-phase delivery uses:
|
|
216
|
+
// build the byte-deterministic proposal and observe-before-write.
|
|
217
|
+
const document = buildProposalDocument(contextProjection);
|
|
218
|
+
const proposalPath = proposalFileName(contextProjection.unitId, contextProjection.version);
|
|
219
|
+
const { cloneDir, verdict } = await observeProposalInboxGitPush({
|
|
220
|
+
remoteUrl: target.remoteUrl,
|
|
221
|
+
branch: target.branch,
|
|
222
|
+
proposalPath,
|
|
223
|
+
proposalDocument: document,
|
|
224
|
+
...(exec !== undefined ? { exec } : {}),
|
|
225
|
+
});
|
|
226
|
+
await rm(cloneDir, { recursive: true, force: true }).catch(() => {});
|
|
227
|
+
return { verdict, proposalPath };
|
|
228
|
+
}
|
|
@@ -288,40 +288,42 @@ export async function crossCheckPushedCommit(exec, remoteUrl, branch, pushedComm
|
|
|
288
288
|
}
|
|
289
289
|
|
|
290
290
|
/**
|
|
291
|
-
*
|
|
292
|
-
*
|
|
291
|
+
* Observe-before-write for the git-push transport (rework R-01): clone the
|
|
292
|
+
* downstream into a fresh disposable checkout and compare any existing
|
|
293
|
+
* proposal bytes with the serialized document. This is the SINGLE observation
|
|
294
|
+
* implementation shared by the execution-phase delivery (observe-before-write)
|
|
295
|
+
* and the full-declaration preflight (deterministic conflict detection BEFORE
|
|
296
|
+
* the first external write) — no second observe algorithm or dispatch table.
|
|
293
297
|
*
|
|
294
|
-
* - identical
|
|
295
|
-
* - different existing content -> REMOTE_CONFLICT (human decision)
|
|
296
|
-
*
|
|
298
|
+
* - existing identical content -> verdict 'IDENTICAL';
|
|
299
|
+
* - different existing content -> REMOTE_CONFLICT (human decision), thrown
|
|
300
|
+
* from here so preflight and delivery can never drift apart;
|
|
301
|
+
* - absent -> verdict 'ABSENT'.
|
|
297
302
|
*
|
|
298
|
-
*
|
|
299
|
-
*
|
|
300
|
-
*
|
|
301
|
-
*
|
|
303
|
+
* On success the CALLER owns `cloneDir` and must remove it (the delivery
|
|
304
|
+
* continues writing/committing/pushing from that checkout; the preflight
|
|
305
|
+
* discards it immediately). On any thrown failure the clone dir is already
|
|
306
|
+
* removed.
|
|
302
307
|
*
|
|
303
308
|
* @param {object} params
|
|
304
309
|
* @param {string} params.remoteUrl - Downstream repository URL.
|
|
305
310
|
* @param {string} params.branch - Downstream branch.
|
|
306
311
|
* @param {string} params.proposalPath - Repository-relative proposal path.
|
|
307
312
|
* @param {object} params.proposalDocument - The proposal document.
|
|
308
|
-
* @param {object} params.commitIdentity - Frozen { name, email }.
|
|
309
313
|
* @param {Function} [params.exec] - Injectable git exec (tests).
|
|
310
|
-
* @returns {Promise<{
|
|
314
|
+
* @returns {Promise<{cloneDir: string, previousHead: string|null, verdict: 'ABSENT'|'IDENTICAL'}>}
|
|
311
315
|
*/
|
|
312
|
-
export async function
|
|
316
|
+
export async function observeProposalInboxGitPush(params) {
|
|
313
317
|
const {
|
|
314
318
|
remoteUrl,
|
|
315
319
|
branch,
|
|
316
320
|
proposalPath,
|
|
317
321
|
proposalDocument,
|
|
318
|
-
commitIdentity,
|
|
319
322
|
exec: execOpt,
|
|
320
323
|
} = params ?? {};
|
|
321
324
|
assertSafeRemoteUrl(remoteUrl);
|
|
322
325
|
assertSafeBranch(branch);
|
|
323
326
|
assertSafeProposalPath(proposalPath);
|
|
324
|
-
assertCommitIdentity(commitIdentity);
|
|
325
327
|
if (!proposalDocument || typeof proposalDocument !== 'object' || Array.isArray(proposalDocument)) {
|
|
326
328
|
throw new ReleaseError(GATE_FAILED, 'proposal-inbox requires a proposal document');
|
|
327
329
|
}
|
|
@@ -365,7 +367,7 @@ export async function deliverProposalGitPush(params) {
|
|
|
365
367
|
await git(['checkout', '--quiet', '--orphan', branch]);
|
|
366
368
|
}
|
|
367
369
|
|
|
368
|
-
// Observe
|
|
370
|
+
// Observe: an existing proposal decides the outcome.
|
|
369
371
|
const targetPath = join(cloneDir, proposalPath);
|
|
370
372
|
let existing = null;
|
|
371
373
|
try {
|
|
@@ -373,20 +375,82 @@ export async function deliverProposalGitPush(params) {
|
|
|
373
375
|
} catch {
|
|
374
376
|
existing = null;
|
|
375
377
|
}
|
|
376
|
-
if (existing
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
};
|
|
382
|
-
}
|
|
383
|
-
throw new ReleaseError(
|
|
384
|
-
REMOTE_CONFLICT,
|
|
385
|
-
`proposal ${proposalPath} already exists in ${redactUrlCredentialsIfPresent(remoteUrl)} with different content; overwriting a downstream proposal requires a human decision`,
|
|
386
|
-
{ remoteUrl: redactUrlCredentialsIfPresent(remoteUrl), proposalPath },
|
|
387
|
-
);
|
|
378
|
+
if (existing === null) {
|
|
379
|
+
return { cloneDir, previousHead, verdict: 'ABSENT' };
|
|
380
|
+
}
|
|
381
|
+
if (existing === serialized) {
|
|
382
|
+
return { cloneDir, previousHead, verdict: 'IDENTICAL' };
|
|
388
383
|
}
|
|
384
|
+
throw new ReleaseError(
|
|
385
|
+
REMOTE_CONFLICT,
|
|
386
|
+
`proposal ${proposalPath} already exists in ${redactUrlCredentialsIfPresent(remoteUrl)} with different content; overwriting a downstream proposal requires a human decision`,
|
|
387
|
+
{ remoteUrl: redactUrlCredentialsIfPresent(remoteUrl), proposalPath },
|
|
388
|
+
);
|
|
389
|
+
} catch (cause) {
|
|
390
|
+
await rm(cloneDir, { recursive: true, force: true }).catch(() => {});
|
|
391
|
+
throw cause;
|
|
392
|
+
}
|
|
393
|
+
}
|
|
389
394
|
|
|
395
|
+
/**
|
|
396
|
+
* Deliver one proposal document through the git-push transport:
|
|
397
|
+
* observe -> write (absent only) -> commit (frozen identity) -> push.
|
|
398
|
+
*
|
|
399
|
+
* - identical existing content -> NO_CHANGE, nothing written;
|
|
400
|
+
* - different existing content -> REMOTE_CONFLICT (human decision);
|
|
401
|
+
* - NEVER --force.
|
|
402
|
+
*
|
|
403
|
+
* The observe-before-write step calls the SAME shared observation
|
|
404
|
+
* implementation the full-declaration preflight uses, so the execution phase
|
|
405
|
+
* always re-observes the remote (the preflight is never a lock).
|
|
406
|
+
*
|
|
407
|
+
* No preset-level dry-run (R4 review m-3): the command-level DRY_RUN skip
|
|
408
|
+
* contract skips preset hooks wholesale (SKIPPED/DRY_RUN checkpoints in
|
|
409
|
+
* distribute/postVerify) before this delivery is ever invoked, so a dryRun
|
|
410
|
+
* parameter here would be unreachable dead surface.
|
|
411
|
+
*
|
|
412
|
+
* @param {object} params
|
|
413
|
+
* @param {string} params.remoteUrl - Downstream repository URL.
|
|
414
|
+
* @param {string} params.branch - Downstream branch.
|
|
415
|
+
* @param {string} params.proposalPath - Repository-relative proposal path.
|
|
416
|
+
* @param {object} params.proposalDocument - The proposal document.
|
|
417
|
+
* @param {object} params.commitIdentity - Frozen { name, email }.
|
|
418
|
+
* @param {Function} [params.exec] - Injectable git exec (tests).
|
|
419
|
+
* @returns {Promise<{ status: 'EXECUTED'|'NO_CHANGE', observation: object }>}
|
|
420
|
+
*/
|
|
421
|
+
export async function deliverProposalGitPush(params) {
|
|
422
|
+
const {
|
|
423
|
+
remoteUrl,
|
|
424
|
+
branch,
|
|
425
|
+
proposalPath,
|
|
426
|
+
proposalDocument,
|
|
427
|
+
commitIdentity,
|
|
428
|
+
exec: execOpt,
|
|
429
|
+
} = params ?? {};
|
|
430
|
+
assertCommitIdentity(commitIdentity);
|
|
431
|
+
const exec = typeof execOpt === 'function' ? execOpt : defaultExec;
|
|
432
|
+
const serialized = `${JSON.stringify(proposalDocument, null, 2)}\n`;
|
|
433
|
+
|
|
434
|
+
// Shared observe-before-write: identical -> NO_CHANGE; different -> the
|
|
435
|
+
// shared observation throws REMOTE_CONFLICT (human decision).
|
|
436
|
+
const { cloneDir, previousHead, verdict } = await observeProposalInboxGitPush({
|
|
437
|
+
remoteUrl,
|
|
438
|
+
branch,
|
|
439
|
+
proposalPath,
|
|
440
|
+
proposalDocument,
|
|
441
|
+
...(execOpt !== undefined ? { exec: execOpt } : {}),
|
|
442
|
+
});
|
|
443
|
+
|
|
444
|
+
try {
|
|
445
|
+
if (verdict === 'IDENTICAL') {
|
|
446
|
+
return {
|
|
447
|
+
status: 'NO_CHANGE',
|
|
448
|
+
observation: { mode: 'no-change', previousHead, branchTip: previousHead },
|
|
449
|
+
};
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
const git = (args, options = {}) => exec('git', args, { cwd: cloneDir, shell: false, timeout: GIT_TIMEOUT_MS, ...options });
|
|
453
|
+
const targetPath = join(cloneDir, proposalPath);
|
|
390
454
|
await mkdir(dirname(targetPath), { recursive: true });
|
|
391
455
|
await writeFile(targetPath, serialized);
|
|
392
456
|
await git(['add', '-A']);
|
package/src/core/recovery.mjs
CHANGED
|
@@ -33,7 +33,7 @@ import { validatePlan, computePlanDigest, assertImmutablePlanAuthority } from '.
|
|
|
33
33
|
import { validateApproval, validateApprovalRecordSchema, assertImmutableApprovalAuthority, computeApprovalDigest } from './approval.mjs';
|
|
34
34
|
import { isMarketplaceAction } from './checkpoints.mjs';
|
|
35
35
|
import { ReleaseError, GATE_FAILED } from './errors.mjs';
|
|
36
|
-
import { effectiveHookRequiresApproval } from './postpublish.mjs';
|
|
36
|
+
import { effectiveHookRequiresApproval, normalizePostPublishView, postPublishActionId } from './postpublish.mjs';
|
|
37
37
|
import { assertPostPublishApprovalAuthority, validatePostPublishApproval } from './postpublish-approval.mjs';
|
|
38
38
|
|
|
39
39
|
/** Remote errors alone are insufficient to select a safe recovery phase. */
|
|
@@ -194,12 +194,18 @@ export async function readRunRecovery(runPath, options = {}) {
|
|
|
194
194
|
throw new ReleaseError(GATE_FAILED, 'distribute recovery requires its same-lineage PUBLISHED publication');
|
|
195
195
|
}
|
|
196
196
|
// Mapping only: the common 1:1 checkpoint validator remains authoritative.
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
.
|
|
202
|
-
|
|
197
|
+
// §4.3 unified normalization: per-declaration mapping through the shared
|
|
198
|
+
// action-id derivation so v3 checkpoint ids (unitId/localId) match the
|
|
199
|
+
// executed records exactly; legacy plans resolve to the same bare ids.
|
|
200
|
+
const actions = normalizePostPublishView(plan).flatMap((declaration) => [
|
|
201
|
+
...(declaration.targets ?? []).flatMap((target) => [
|
|
202
|
+
{ id: postPublishActionId({ planVersion: plan.planVersion, unitId: declaration.unitId, localId: `probe-${target.id}` }), type: 'distribute-probe' },
|
|
203
|
+
{ id: postPublishActionId({ planVersion: plan.planVersion, unitId: declaration.unitId, localId: target.id }), type: 'distribute-mirror' },
|
|
204
|
+
]),
|
|
205
|
+
...(declaration.hooks ?? [])
|
|
206
|
+
.filter((hook) => hook.phase !== 'postVerify')
|
|
207
|
+
.map((hook) => ({ id: postPublishActionId({ planVersion: plan.planVersion, unitId: declaration.unitId, localId: hook.id }), type: 'postpublish-hook' })),
|
|
208
|
+
]);
|
|
203
209
|
// A preflight BLOCKED record may precede checkpoint initialization.
|
|
204
210
|
if (!(run.status === 'BLOCKED' && run.checkpoints.length === 0)) {
|
|
205
211
|
validateRunCheckpointMapping(run, actions);
|
|
@@ -268,8 +274,11 @@ export async function readRunRecovery(runPath, options = {}) {
|
|
|
268
274
|
}
|
|
269
275
|
} else if (['publish', 'reconcile'].includes(command)) {
|
|
270
276
|
if (run.status === 'PUBLISHED') {
|
|
271
|
-
|
|
272
|
-
|
|
277
|
+
// §4.3 unified normalization: v3 empty arrays mean no distribute work
|
|
278
|
+
// at all; only declarations carrying targets or hooks require the
|
|
279
|
+
// distribution phase.
|
|
280
|
+
const needsDistribution = normalizePostPublishView(plan).some((declaration) =>
|
|
281
|
+
(declaration.targets?.length ?? 0) > 0 || (declaration.hooks?.length ?? 0) > 0);
|
|
273
282
|
code = needsDistribution ? 'DISTRIBUTE' : 'VERIFY';
|
|
274
283
|
} else if (run.status === 'PARTIAL') {
|
|
275
284
|
code = 'RECONCILE';
|
|
@@ -280,9 +289,15 @@ export async function readRunRecovery(runPath, options = {}) {
|
|
|
280
289
|
} else code = 'DIAGNOSE';
|
|
281
290
|
|
|
282
291
|
if (code === 'DISTRIBUTE') {
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
292
|
+
// §4.3 unified normalization: approval gating resolves declared hooks
|
|
293
|
+
// across all declarations; checkpoint match uses the shared action-id
|
|
294
|
+
// derivation so a v3 record (unitId/hookId) counts as completed.
|
|
295
|
+
const pendingApprovals = normalizePostPublishView(plan).flatMap((declaration) =>
|
|
296
|
+
(declaration.hooks ?? [])
|
|
297
|
+
.filter((hook) => hook.phase !== 'postVerify' && effectiveHookRequiresApproval(hook))
|
|
298
|
+
.filter((hook) => !(run.command === 'distribute' && run.checkpoints.some((cp) =>
|
|
299
|
+
cp.actionId === postPublishActionId({ planVersion: plan.planVersion, unitId: declaration.unitId, localId: hook.id })
|
|
300
|
+
&& completed(cp)))));
|
|
286
301
|
if (pendingApprovals.length > 0) {
|
|
287
302
|
try {
|
|
288
303
|
const approved = new Set();
|
|
@@ -376,10 +376,14 @@ export function evaluateConsumerSkillResourceClosureReceipts(plan, receipts) {
|
|
|
376
376
|
'kimi-plugin',
|
|
377
377
|
'codebuddy-plugin',
|
|
378
378
|
]);
|
|
379
|
-
//
|
|
380
|
-
//
|
|
381
|
-
//
|
|
382
|
-
|
|
379
|
+
// Kimi/CodeBuddy remain non-blocking manual marketplace/invocation tasks in
|
|
380
|
+
// both strategy generations. The newer strategy adds a Foundation frozen
|
|
381
|
+
// payload observation but still does not establish an installed host
|
|
382
|
+
// surface, so these distributions stay outside this receipt set.
|
|
383
|
+
const humanConsumerTypes = [
|
|
384
|
+
'manualFollowUps',
|
|
385
|
+
'foundationPayloadThenManualFollowUps',
|
|
386
|
+
].includes(plan.humanConsumersStrategy)
|
|
383
387
|
? new Set(['kimi-plugin', 'codebuddy-plugin'])
|
|
384
388
|
: new Set();
|
|
385
389
|
const expectedKeys = [];
|
|
@@ -371,9 +371,17 @@ function renderSkillForPlatform(content, platformName) {
|
|
|
371
371
|
const hostLabel = platformName === 'codex' ? 'Codex' : 'Kimi';
|
|
372
372
|
const preamble = platformName === 'codex' ? CODEX_PREAMBLE : KIMI_PREAMBLE;
|
|
373
373
|
let rendered = content.replaceAll(
|
|
374
|
+
'${CLAUDE_PLUGIN_ROOT}/bin/release-skill-local-finish.mjs',
|
|
375
|
+
'$RELEASE_SKILL_LOCAL_FINISH_ENTRY',
|
|
376
|
+
).replaceAll(
|
|
374
377
|
'${CLAUDE_PLUGIN_ROOT}/bin/release-skill.mjs',
|
|
375
378
|
'$RELEASE_SKILL_ENTRY',
|
|
376
379
|
);
|
|
380
|
+
const renderedPreamble = rendered.includes('$RELEASE_SKILL_LOCAL_FINISH_ENTRY')
|
|
381
|
+
? preamble
|
|
382
|
+
.replaceAll('RELEASE_SKILL_ENTRY', 'RELEASE_SKILL_LOCAL_FINISH_ENTRY')
|
|
383
|
+
.replaceAll('release-skill.mjs', 'release-skill-local-finish.mjs')
|
|
384
|
+
: preamble;
|
|
377
385
|
// A remaining bare host-root reference has no safe path-derived equivalent.
|
|
378
386
|
if (rendered.includes('${CLAUDE_PLUGIN_ROOT}')) {
|
|
379
387
|
throw new Error(`${hostLabel} rendering found an unsupported bare CLAUDE_PLUGIN_ROOT reference`);
|
|
@@ -385,7 +393,7 @@ function renderSkillForPlatform(content, platformName) {
|
|
|
385
393
|
throw new Error(`${hostLabel} rendering requires a valid leading YAML frontmatter block`);
|
|
386
394
|
}
|
|
387
395
|
const insertionPoint = frontmatterEnd + '\n---\n'.length;
|
|
388
|
-
return `${rendered.slice(0, insertionPoint)}\n${
|
|
396
|
+
return `${rendered.slice(0, insertionPoint)}\n${renderedPreamble}${rendered.slice(insertionPoint)}`;
|
|
389
397
|
}
|
|
390
398
|
|
|
391
399
|
return content;
|
|
@@ -527,6 +535,7 @@ async function generateAdapterFiles(platform, skills, templateJson, root, market
|
|
|
527
535
|
|
|
528
536
|
const HOST_BIN_FILES = new Set([
|
|
529
537
|
'release-skill.mjs',
|
|
538
|
+
'release-skill-local-finish.mjs',
|
|
530
539
|
'release-skill.bundle.mjs',
|
|
531
540
|
'release-skill-cli.mjs',
|
|
532
541
|
'foundation-resource-binding.json',
|
|
@@ -55,9 +55,9 @@ import { digestBytes, publishFileOrReplace, readFileStrict, withTemporaryWorkspa
|
|
|
55
55
|
import { canonicalJson } from '../core/digest.mjs';
|
|
56
56
|
|
|
57
57
|
export const FOUNDATION_PACKAGES = Object.freeze({
|
|
58
|
-
'skill-family-contracts': '0.
|
|
59
|
-
'skill-family-engineering-kit': '0.
|
|
60
|
-
'skill-family-harness-node': '0.
|
|
58
|
+
'skill-family-contracts': '0.13.0',
|
|
59
|
+
'skill-family-engineering-kit': '0.13.0',
|
|
60
|
+
'skill-family-harness-node': '0.13.0',
|
|
61
61
|
});
|
|
62
62
|
|
|
63
63
|
export const BINDING_RECORD_PATH = 'bin/foundation-resource-binding.json';
|