release-skill 0.9.7 → 0.9.9
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/.agents/plugins/marketplace.json +9 -0
- package/.claude-plugin/marketplace.json +11 -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 +52 -0
- package/INSTALL.md +17 -9
- package/INSTALL.zh-CN.md +15 -9
- package/README.md +31 -28
- package/README.zh-CN.md +26 -26
- 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 +298 -45
- package/adapters/claude/skills/release-finish/SKILL.md +12 -4
- package/adapters/codex/.codex-plugin/plugin.json +2 -2
- package/adapters/codex/bin/release-skill.bundle.mjs +298 -45
- package/adapters/codex/skills/release-finish/SKILL.md +12 -4
- package/adapters/kimi/.kimi-plugin/plugin.json +1 -1
- package/adapters/kimi/bin/release-skill.bundle.mjs +298 -45
- package/adapters/kimi/skills/release-finish/SKILL.md +12 -4
- package/adapters/workbuddy/.codebuddy-plugin/plugin.json +1 -1
- package/adapters/workbuddy/bin/release-skill.bundle.mjs +298 -45
- package/adapters/workbuddy/skills/release-finish/SKILL.md +12 -4
- package/bin/release-skill.bundle.mjs +298 -45
- package/package.json +1 -1
- package/platform-manifest.json +4 -4
- package/skills/release-finish/SKILL.md +12 -4
- package/skills-src/release-finish/SKILL.md +12 -4
- package/src/commands/post-release-local.mjs +292 -39
- package/src/commands/ship.mjs +4 -6
- package/src/commands/verify.mjs +8 -8
- package/src/core/postpublish.mjs +24 -0
- package/src/core/recovery.mjs +8 -6
|
@@ -35,7 +35,7 @@ const SAFE_ENV_KEYS = Object.freeze([
|
|
|
35
35
|
'PATH', 'HOME', 'USER', 'LANG', 'LC_ALL', 'LC_CTYPE', 'TERM', 'TMPDIR',
|
|
36
36
|
'HTTP_PROXY', 'HTTPS_PROXY', 'NO_PROXY', 'http_proxy', 'https_proxy', 'no_proxy',
|
|
37
37
|
'SSL_CERT_FILE', 'SSL_CERT_DIR', 'NODE_EXTRA_CA_CERTS',
|
|
38
|
-
'CLAUDE_CONFIG_DIR', 'CODEX_HOME', '
|
|
38
|
+
'CLAUDE_CONFIG_DIR', 'CODEX_HOME', 'KIMI_CODE_HOME',
|
|
39
39
|
'CODEBUDDY_CONFIG_DIR', 'WORKBUDDY_CONFIG_DIR',
|
|
40
40
|
]);
|
|
41
41
|
const CODEBUDDY_PLUGIN_LIST_ARGS = Object.freeze(['plugin', 'list', '--json']);
|
|
@@ -422,7 +422,7 @@ function hostEnvironment(host, { kimiHome } = {}) {
|
|
|
422
422
|
env.CODEBUDDY_CONFIG_DIR = join(env.HOME, '.workbuddy');
|
|
423
423
|
env.WORKBUDDY_CONFIG_DIR = join(env.HOME, '.workbuddy');
|
|
424
424
|
}
|
|
425
|
-
if (host === 'kimi' && kimiHome) env.
|
|
425
|
+
if (host === 'kimi' && kimiHome) env.KIMI_CODE_HOME = kimiHome;
|
|
426
426
|
return env;
|
|
427
427
|
}
|
|
428
428
|
|
|
@@ -647,6 +647,67 @@ function normalizeGitSource(source) {
|
|
|
647
647
|
.replace(/\/$/u, '');
|
|
648
648
|
}
|
|
649
649
|
|
|
650
|
+
function parseFrozenRemoteRef(target, stdout) {
|
|
651
|
+
const directRefs = new Map();
|
|
652
|
+
const peeledRefs = new Map();
|
|
653
|
+
const expected = target.marketplaceRef;
|
|
654
|
+
const allowedDirect = expected.startsWith('refs/')
|
|
655
|
+
? new Set([expected])
|
|
656
|
+
: new Set([`refs/heads/${expected}`, `refs/tags/${expected}`]);
|
|
657
|
+
for (const line of String(stdout ?? '').split(/\r?\n/u)) {
|
|
658
|
+
if (line.length === 0) continue;
|
|
659
|
+
const match = /^([a-f0-9]{40})\t([^\s]+)$/u.exec(line);
|
|
660
|
+
if (!match) throw new Error('git ls-remote returned an invalid line');
|
|
661
|
+
const [, commit, remoteRef] = match;
|
|
662
|
+
const peeled = remoteRef.endsWith('^{}');
|
|
663
|
+
const direct = peeled ? remoteRef.slice(0, -3) : remoteRef;
|
|
664
|
+
if (!allowedDirect.has(direct)) {
|
|
665
|
+
throw new Error(`git ls-remote returned an unexpected ref ${remoteRef}`);
|
|
666
|
+
}
|
|
667
|
+
const destination = peeled ? peeledRefs : directRefs;
|
|
668
|
+
const previous = destination.get(direct);
|
|
669
|
+
if (previous && previous !== commit) {
|
|
670
|
+
throw new Error(`git ls-remote returned conflicting values for ${remoteRef}`);
|
|
671
|
+
}
|
|
672
|
+
destination.set(direct, commit);
|
|
673
|
+
}
|
|
674
|
+
const resolved = [...allowedDirect]
|
|
675
|
+
.filter((remoteRef) => directRefs.has(remoteRef) || peeledRefs.has(remoteRef))
|
|
676
|
+
.map((remoteRef) => peeledRefs.get(remoteRef) ?? directRefs.get(remoteRef));
|
|
677
|
+
return {
|
|
678
|
+
found: resolved.length > 0,
|
|
679
|
+
exact: resolved.length > 0 && resolved.every((commit) => commit === target.marketplaceCommit),
|
|
680
|
+
};
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
async function preflightStructuredMarketplace(target, env, run) {
|
|
684
|
+
try {
|
|
685
|
+
const observed = await run('git', [
|
|
686
|
+
'ls-remote', '--exit-code', `https://${target.githubHost}/${target.marketplaceRepo}.git`,
|
|
687
|
+
target.marketplaceRef, `${target.marketplaceRef}^{}`,
|
|
688
|
+
], { env, timeout: 30_000 });
|
|
689
|
+
const remote = parseFrozenRemoteRef(target, observed.stdout);
|
|
690
|
+
if (!remote.found) {
|
|
691
|
+
return {
|
|
692
|
+
ok: false,
|
|
693
|
+
reason: `${target.host} frozen marketplace ref ${target.marketplaceRef} is missing`,
|
|
694
|
+
};
|
|
695
|
+
}
|
|
696
|
+
if (!remote.exact) {
|
|
697
|
+
return {
|
|
698
|
+
ok: false,
|
|
699
|
+
reason: `${target.host} frozen marketplace ref ${target.marketplaceRef} does not resolve to ${target.marketplaceCommit}`,
|
|
700
|
+
};
|
|
701
|
+
}
|
|
702
|
+
return { ok: true };
|
|
703
|
+
} catch (error) {
|
|
704
|
+
return {
|
|
705
|
+
ok: false,
|
|
706
|
+
reason: `${target.host} could not prove the frozen marketplace ref: ${error?.message ?? String(error)}`,
|
|
707
|
+
};
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
|
|
650
711
|
async function observeMarketplace(target, command, env, run) {
|
|
651
712
|
const listed = await run(command, ['plugin', 'marketplace', 'list', '--json'], { env });
|
|
652
713
|
const parsed = parseJson(listed.stdout, `${target.host} marketplace list`);
|
|
@@ -835,6 +896,11 @@ async function runStructuredUpdate(target, detected, run, {
|
|
|
835
896
|
return { status: 'ALREADY_CURRENT', version: target.version };
|
|
836
897
|
}
|
|
837
898
|
|
|
899
|
+
if (!before.marketplace.exact) {
|
|
900
|
+
const preflight = await preflightStructuredMarketplace(target, env, run);
|
|
901
|
+
if (!preflight.ok) return { status: 'MANUAL_REQUIRED', reason: preflight.reason };
|
|
902
|
+
}
|
|
903
|
+
|
|
838
904
|
const marketplaceRebound = await bindStructuredMarketplace(target, command, env, run, before);
|
|
839
905
|
const current = target.host === 'claude' && marketplaceRebound
|
|
840
906
|
? await observeStructuredTarget(target, command, env, run)
|
|
@@ -898,49 +964,223 @@ function parseCodeBuddyRemoteObservation(target, stdout) {
|
|
|
898
964
|
|
|
899
965
|
function kimiExpectProgram({ removePlugin } = {}) {
|
|
900
966
|
const removeCommand = removePlugin
|
|
901
|
-
? `
|
|
967
|
+
? `submitCommand "/plugins remove $removePlugin"
|
|
902
968
|
expect {
|
|
903
969
|
-nocase -re {(remove|delete|uninstall).*(confirm|sure)|(confirm|sure).*(remove|delete|uninstall)} {
|
|
904
970
|
expect {
|
|
905
|
-
-ex $removePlugin {
|
|
906
|
-
|
|
907
|
-
|
|
971
|
+
-ex $removePlugin {
|
|
972
|
+
send -- "\\033\\[B"
|
|
973
|
+
send -- "\\033\\[13u"
|
|
974
|
+
}
|
|
975
|
+
timeout { failTimeout remove-confirmation 125 127 }
|
|
976
|
+
eof { failEof remove-confirmation 126 }
|
|
977
|
+
}
|
|
978
|
+
expect {
|
|
979
|
+
-nocase -re {Trust this folder\\?} { directoryTrust }
|
|
980
|
+
-re $promptPattern {}
|
|
981
|
+
timeout { failTimeout remove-prompt 128 130 }
|
|
982
|
+
eof { failEof remove-prompt 129 }
|
|
908
983
|
}
|
|
909
|
-
expect -re $promptPattern
|
|
910
984
|
}
|
|
911
985
|
-re $promptPattern {}
|
|
912
|
-
|
|
913
|
-
|
|
986
|
+
-nocase -re {Trust this folder\\?} { directoryTrust }
|
|
987
|
+
timeout { failTimeout remove-dialog 122 124 }
|
|
988
|
+
eof { failEof remove-dialog 123 }
|
|
914
989
|
}
|
|
915
990
|
`
|
|
916
991
|
: '';
|
|
917
|
-
return `set timeout
|
|
918
|
-
foreach variable {RELEASE_SKILL_KIMI_COMMAND RELEASE_SKILL_KIMI_INSTALL_URL RELEASE_SKILL_KIMI_REMOVE_PLUGIN} {
|
|
992
|
+
return `set timeout 240
|
|
993
|
+
foreach variable {RELEASE_SKILL_KIMI_COMMAND RELEASE_SKILL_KIMI_INSTALL_URL RELEASE_SKILL_KIMI_REMOVE_PLUGIN RELEASE_SKILL_KIMI_EXPECTED_REPO RELEASE_SKILL_KIMI_EXPECTED_TAG} {
|
|
919
994
|
if {![info exists env($variable)]} { exit 90 }
|
|
920
995
|
}
|
|
921
996
|
set kimiCommand $env(RELEASE_SKILL_KIMI_COMMAND)
|
|
922
997
|
set installUrl $env(RELEASE_SKILL_KIMI_INSTALL_URL)
|
|
923
998
|
set removePlugin $env(RELEASE_SKILL_KIMI_REMOVE_PLUGIN)
|
|
924
|
-
set
|
|
999
|
+
set expectedRepo $env(RELEASE_SKILL_KIMI_EXPECTED_REPO)
|
|
1000
|
+
set expectedTag $env(RELEASE_SKILL_KIMI_EXPECTED_TAG)
|
|
1001
|
+
set promptPattern {(?:(?:^|\\r|\\n)> (?:\\r*\\n|$)|(?:^|\\r|\\n)(?:(?:\\033\\[[0-9;?]*[ -/]*[@-~])|\\033\\][^\\x07]*\\x07|[ \\t])*│[^\\r\\n]*>[^\\r\\n]*│(?:(?:\\033\\[[0-9;?]*[ -/]*[@-~])|\\033\\][^\\x07]*\\x07|[ \\t])*(?:\\r*\\n|$))}
|
|
1002
|
+
|
|
1003
|
+
proc cleanScreen {value} {
|
|
1004
|
+
regsub -all {\\033\\[[0-9;?]*[ -/]*[@-~]} $value {} value
|
|
1005
|
+
regsub -all {\\033\\][^\\x07]*\\x07} $value {} value
|
|
1006
|
+
regsub -all {\\r} $value {} value
|
|
1007
|
+
return $value
|
|
1008
|
+
}
|
|
1009
|
+
|
|
1010
|
+
proc compactScreen {value} {
|
|
1011
|
+
set value [cleanScreen $value]
|
|
1012
|
+
regsub -all {[[:space:]]+} $value {} value
|
|
1013
|
+
return $value
|
|
1014
|
+
}
|
|
1015
|
+
|
|
1016
|
+
proc extractPluginTrustDialog {value} {
|
|
1017
|
+
set cleaned [cleanScreen $value]
|
|
1018
|
+
set lowered [string tolower $cleaned]
|
|
1019
|
+
set dialogStart -1
|
|
1020
|
+
foreach marker {"install third-party plugin " "trust and install from "} {
|
|
1021
|
+
set markerStart [string last $marker $lowered]
|
|
1022
|
+
if {$markerStart > $dialogStart} { set dialogStart $markerStart }
|
|
1023
|
+
}
|
|
1024
|
+
if {$dialogStart < 0} { return "" }
|
|
1025
|
+
return [string range $cleaned $dialogStart end]
|
|
1026
|
+
}
|
|
1027
|
+
|
|
1028
|
+
proc extractPluginTrustIdentity {value} {
|
|
1029
|
+
set cleaned [cleanScreen $value]
|
|
1030
|
+
set lowered [string tolower $cleaned]
|
|
1031
|
+
set identityStart -1
|
|
1032
|
+
foreach marker {"install third-party plugin " "trust and install from "} {
|
|
1033
|
+
set markerStart [string last $marker $lowered]
|
|
1034
|
+
if {$markerStart >= 0 && $markerStart + [string length $marker] > $identityStart} {
|
|
1035
|
+
set identityStart [expr {$markerStart + [string length $marker]}]
|
|
1036
|
+
}
|
|
1037
|
+
}
|
|
1038
|
+
if {$identityStart < 0} { return "" }
|
|
1039
|
+
set identityEnd [string first "?" $cleaned $identityStart]
|
|
1040
|
+
if {$identityEnd < 0} { return "" }
|
|
1041
|
+
return [string range $cleaned $identityStart [expr {$identityEnd - 1}]]
|
|
1042
|
+
}
|
|
1043
|
+
|
|
1044
|
+
proc failTimeout {state timeoutCode unknownCode} {
|
|
1045
|
+
global expect_out
|
|
1046
|
+
set buffer ""
|
|
1047
|
+
if {[info exists expect_out(buffer)]} { set buffer [string trim [cleanScreen $expect_out(buffer)]] }
|
|
1048
|
+
if {$buffer ne ""} {
|
|
1049
|
+
puts stderr "KIMI_TUI_STATE:$state:unknown"
|
|
1050
|
+
exit $unknownCode
|
|
1051
|
+
}
|
|
1052
|
+
puts stderr "KIMI_TUI_STATE:$state:timeout"
|
|
1053
|
+
exit $timeoutCode
|
|
1054
|
+
}
|
|
1055
|
+
|
|
1056
|
+
proc failEof {state code} {
|
|
1057
|
+
puts stderr "KIMI_TUI_STATE:$state:eof"
|
|
1058
|
+
exit $code
|
|
1059
|
+
}
|
|
1060
|
+
|
|
1061
|
+
proc directoryTrust {} {
|
|
1062
|
+
puts stderr "KIMI_TUI_STATE:directory-trust:manual-required"
|
|
1063
|
+
exit 80
|
|
1064
|
+
}
|
|
1065
|
+
|
|
1066
|
+
proc submitCommand {command} {
|
|
1067
|
+
send -- "\\033\\[200~"
|
|
1068
|
+
send -- $command
|
|
1069
|
+
send -- "\\033\\[201~"
|
|
1070
|
+
send -- "\\033\\[13u"
|
|
1071
|
+
}
|
|
1072
|
+
|
|
925
1073
|
spawn $kimiCommand
|
|
926
|
-
|
|
927
|
-
|
|
1074
|
+
if {[catch {exec stty columns 240 rows 60 < $spawn_out(slave,name)} resizeError]} {
|
|
1075
|
+
puts stderr "KIMI_TUI_STATE:terminal-size:failed"
|
|
1076
|
+
exit 131
|
|
1077
|
+
}
|
|
928
1078
|
expect {
|
|
929
|
-
-nocase -re {
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
1079
|
+
-nocase -re {Trust this folder\\?} { directoryTrust }
|
|
1080
|
+
-re $promptPattern {}
|
|
1081
|
+
timeout { failTimeout initial-prompt 101 103 }
|
|
1082
|
+
eof { failEof initial-prompt 102 }
|
|
1083
|
+
}
|
|
1084
|
+
${removeCommand}submitCommand "/plugins install $installUrl"
|
|
1085
|
+
set dialogBuffer ""
|
|
1086
|
+
expect {
|
|
1087
|
+
-nocase -re {Trust this folder\\?} { directoryTrust }
|
|
1088
|
+
-nocase -re {(?:Install third-party plugin|Trust and install from)[ \\t]} {
|
|
1089
|
+
append dialogBuffer $expect_out(buffer)
|
|
1090
|
+
}
|
|
1091
|
+
timeout { failTimeout plugin-trust-anchor 104 106 }
|
|
1092
|
+
eof { failEof plugin-trust-anchor 105 }
|
|
1093
|
+
}
|
|
1094
|
+
expect {
|
|
1095
|
+
-nocase -re {Trust this folder\\?} { directoryTrust }
|
|
1096
|
+
-nocase -re {❯[^\\r\\n]*(?:Exit|Cancel|Trust and install)} {
|
|
1097
|
+
append dialogBuffer $expect_out(buffer)
|
|
1098
|
+
}
|
|
1099
|
+
-re {❯[^\\r\\n]*\\r*\\n} {
|
|
1100
|
+
puts stderr "KIMI_TUI_STATE:plugin-trust-selected-row:unknown"
|
|
1101
|
+
exit 134
|
|
1102
|
+
}
|
|
1103
|
+
timeout {
|
|
1104
|
+
puts stderr "KIMI_TUI_STATE:plugin-trust-selected-row:timeout"
|
|
1105
|
+
exit 132
|
|
1106
|
+
}
|
|
1107
|
+
eof { failEof plugin-trust-selected-row 133 }
|
|
1108
|
+
}
|
|
1109
|
+
|
|
1110
|
+
set dialog [extractPluginTrustDialog $dialogBuffer]
|
|
1111
|
+
if {$dialog eq ""} {
|
|
1112
|
+
puts stderr "KIMI_TUI_STATE:plugin-trust:dialog-unknown"
|
|
1113
|
+
exit 113
|
|
1114
|
+
}
|
|
1115
|
+
set identity [extractPluginTrustIdentity $dialog]
|
|
1116
|
+
set compactIdentity [compactScreen $identity]
|
|
1117
|
+
if {$identity eq "" || $compactIdentity ne [compactScreen $installUrl]} {
|
|
1118
|
+
set expectedRepoPrefix "[compactScreen $expectedRepo]/releases/tag/"
|
|
1119
|
+
if {[string first $expectedRepoPrefix $compactIdentity] != 0} {
|
|
1120
|
+
puts stderr "KIMI_TUI_STATE:plugin-trust:repo-mismatch"
|
|
1121
|
+
exit 111
|
|
1122
|
+
}
|
|
1123
|
+
puts stderr "KIMI_TUI_STATE:plugin-trust:tag-mismatch"
|
|
1124
|
+
exit 112
|
|
1125
|
+
}
|
|
1126
|
+
if {[regexp -nocase {(^|\\n)[^\\n]*❯[^\\n]*trust and install} $dialog]} {
|
|
1127
|
+
send -- "\\033\\[13u"
|
|
1128
|
+
} elseif {[regexp -nocase {(^|\\n)[^\\n]*❯[^\\n]*(cancel|exit)} $dialog]} {
|
|
1129
|
+
send -- "\\033\\[B"
|
|
1130
|
+
expect {
|
|
1131
|
+
-nocase -re {Trust this folder\\?} { directoryTrust }
|
|
1132
|
+
-nocase -re {❯[^\\r\\n]*Trust and install} {}
|
|
1133
|
+
-re {❯[^\\r\\n]*\\r*\\n} {
|
|
1134
|
+
puts stderr "KIMI_TUI_STATE:plugin-trust-confirm-selection:unknown"
|
|
1135
|
+
exit 109
|
|
934
1136
|
}
|
|
1137
|
+
timeout { failTimeout plugin-trust-confirm-selection 107 109 }
|
|
1138
|
+
eof { failEof plugin-trust-confirm-selection 108 }
|
|
1139
|
+
}
|
|
1140
|
+
send -- "\\033\\[13u"
|
|
1141
|
+
} else {
|
|
1142
|
+
puts stderr "KIMI_TUI_STATE:plugin-trust:selection-unknown"
|
|
1143
|
+
exit 113
|
|
1144
|
+
}
|
|
1145
|
+
|
|
1146
|
+
expect {
|
|
1147
|
+
-nocase -re {Trust this folder\\?} { directoryTrust }
|
|
1148
|
+
-nocase -re {Install finished[^\\r\\n]*see details below\\.} {}
|
|
1149
|
+
-nocase -re {Installing plugin from[^\\r\\n]*(?:\\r|\\n)} {
|
|
1150
|
+
exp_continue -continue_timer
|
|
1151
|
+
}
|
|
1152
|
+
-nocase -re {Install failed:[^\\r\\n]*} {
|
|
1153
|
+
puts stderr "KIMI_TUI_STATE:install-result:failed"
|
|
1154
|
+
exit 135
|
|
935
1155
|
}
|
|
936
|
-
|
|
937
|
-
|
|
1156
|
+
-nocase -re {(^|\\r|\\n)Install[^\\r\\n]*\\r*\\n} {
|
|
1157
|
+
puts stderr "KIMI_TUI_STATE:install-result:unknown"
|
|
1158
|
+
exit 138
|
|
1159
|
+
}
|
|
1160
|
+
timeout {
|
|
1161
|
+
puts stderr "KIMI_TUI_STATE:install-result:timeout"
|
|
1162
|
+
exit 136
|
|
1163
|
+
}
|
|
1164
|
+
eof { failEof install-result 137 }
|
|
1165
|
+
}
|
|
1166
|
+
expect {
|
|
1167
|
+
-nocase -re {Trust this folder\\?} { directoryTrust }
|
|
1168
|
+
-re $promptPattern {}
|
|
1169
|
+
timeout { failTimeout post-install-prompt 114 116 }
|
|
1170
|
+
eof { failEof post-install-prompt 115 }
|
|
1171
|
+
}
|
|
1172
|
+
submitCommand "/reload"
|
|
1173
|
+
expect {
|
|
1174
|
+
-nocase -re {Trust this folder\\?} { directoryTrust }
|
|
1175
|
+
-re $promptPattern {}
|
|
1176
|
+
timeout { failTimeout reload-prompt 117 119 }
|
|
1177
|
+
eof { failEof reload-prompt 118 }
|
|
1178
|
+
}
|
|
1179
|
+
submitCommand "/exit"
|
|
1180
|
+
expect {
|
|
1181
|
+
eof { puts stderr "KIMI_TUI_STATE:exit-eof:eof" }
|
|
1182
|
+
timeout { failTimeout exit-eof 120 121 }
|
|
938
1183
|
}
|
|
939
|
-
expect -re $promptPattern
|
|
940
|
-
send -- "/reload\\r"
|
|
941
|
-
expect -re $promptPattern
|
|
942
|
-
send -- "/exit\\r"
|
|
943
|
-
expect eof
|
|
944
1184
|
`;
|
|
945
1185
|
}
|
|
946
1186
|
|
|
@@ -1018,7 +1258,7 @@ async function runKimiUpdate(target, detected, run, kimiHome, {
|
|
|
1018
1258
|
});
|
|
1019
1259
|
return { status: 'ALREADY_CURRENT', version: target.version };
|
|
1020
1260
|
}
|
|
1021
|
-
await withTemporaryWorkspace(async (workspace) => {
|
|
1261
|
+
const tuiOutcome = await withTemporaryWorkspace(async (workspace) => {
|
|
1022
1262
|
const checkout = join(workspace.root, 'plugin');
|
|
1023
1263
|
await run('git', [
|
|
1024
1264
|
'clone', '--depth', '1', '--branch', target.pluginTag, '--single-branch',
|
|
@@ -1033,18 +1273,31 @@ async function runKimiUpdate(target, detected, run, kimiHome, {
|
|
|
1033
1273
|
}
|
|
1034
1274
|
const installUrl = `https://github.com/${target.pluginRepo}/releases/tag/${target.pluginTag}`;
|
|
1035
1275
|
const removePlugin = before.source === 'legacy' ? target.plugin : '';
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1276
|
+
try {
|
|
1277
|
+
await run(detected.expectCommand, ['-c', kimiExpectProgram({
|
|
1278
|
+
...(removePlugin ? { removePlugin } : {}),
|
|
1279
|
+
})], {
|
|
1280
|
+
timeout: Math.max(300_000, target.timeoutMs),
|
|
1281
|
+
env: {
|
|
1282
|
+
...env,
|
|
1283
|
+
RELEASE_SKILL_KIMI_COMMAND: detected.command,
|
|
1284
|
+
RELEASE_SKILL_KIMI_INSTALL_URL: installUrl,
|
|
1285
|
+
RELEASE_SKILL_KIMI_REMOVE_PLUGIN: removePlugin,
|
|
1286
|
+
RELEASE_SKILL_KIMI_EXPECTED_REPO: `https://github.com/${target.pluginRepo}`,
|
|
1287
|
+
RELEASE_SKILL_KIMI_EXPECTED_TAG: target.pluginTag,
|
|
1288
|
+
},
|
|
1289
|
+
});
|
|
1290
|
+
} catch (error) {
|
|
1291
|
+
if (error?.exitStatus === 80) {
|
|
1292
|
+
return {
|
|
1293
|
+
status: 'MANUAL_REQUIRED',
|
|
1294
|
+
reason: 'Kimi requires folder trust; release-finish did not confirm the folder or continue installation',
|
|
1295
|
+
};
|
|
1296
|
+
}
|
|
1297
|
+
throw error;
|
|
1298
|
+
}
|
|
1047
1299
|
}, { prefix: 'release-skill-kimi-update-' });
|
|
1300
|
+
if (tuiOutcome) return tuiOutcome;
|
|
1048
1301
|
const after = await observeKimiTarget(target, kimiHome, run);
|
|
1049
1302
|
if (!after.exact) throw new Error('Kimi did not report the frozen plugin identity after TUI installation');
|
|
1050
1303
|
await verifyStructuredInstalledPayload({
|
|
@@ -1098,7 +1351,7 @@ async function updateLocalHostPluginsInternal({
|
|
|
1098
1351
|
kimiHome,
|
|
1099
1352
|
verifyInstalledPayload = verifyInstalledMarketplacePayload,
|
|
1100
1353
|
} = {}) {
|
|
1101
|
-
const effectiveKimiHome = kimiHome ?? process.env.
|
|
1354
|
+
const effectiveKimiHome = kimiHome ?? process.env.KIMI_CODE_HOME ?? join(homedir(), '.kimi-code');
|
|
1102
1355
|
const checklist = derivePostReleaseChecklist(plan, { postVerifyComplete: true });
|
|
1103
1356
|
if (confirmPlanDigest !== plan.digest) {
|
|
1104
1357
|
throw new Error('plan digest confirmation does not match the frozen release plan');
|
package/src/commands/ship.mjs
CHANGED
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
effectiveHookRequiresApproval,
|
|
9
9
|
normalizePostPublishView,
|
|
10
10
|
postPublishActionId,
|
|
11
|
+
requiresPostPublishDistribution,
|
|
11
12
|
} from '../core/postpublish.mjs';
|
|
12
13
|
import {
|
|
13
14
|
derivePostReleaseChecklist,
|
|
@@ -652,13 +653,10 @@ export async function advanceShip(options = {}, injected = {}) {
|
|
|
652
653
|
|
|
653
654
|
if (state.status === 'PUBLISHED' || state.status === 'NEEDS_MANUAL_ATTESTATIONS') {
|
|
654
655
|
// Step 1: Check if postPublish requires distribution.
|
|
655
|
-
//
|
|
656
|
-
//
|
|
657
|
-
// legacy absent postPublish resolves to the same empty view.
|
|
656
|
+
// Only targets and distribute-phase hooks route through distribute;
|
|
657
|
+
// phase:postVerify hooks belong to their independent post-VERIFIED run.
|
|
658
658
|
const plan = JSON.parse(await readFile(state.planPath, 'utf8'));
|
|
659
|
-
const
|
|
660
|
-
(declaration.targets?.length ?? 0) > 0 || (declaration.hooks?.length ?? 0) > 0);
|
|
661
|
-
const needsDistribution = hasDistributeWork;
|
|
659
|
+
const needsDistribution = requiresPostPublishDistribution(plan);
|
|
662
660
|
if (needsDistribution && deps.distributeRelease) {
|
|
663
661
|
state.status = 'DISTRIBUTING';
|
|
664
662
|
await writeJsonAtomic(statePath, state);
|
package/src/commands/verify.mjs
CHANGED
|
@@ -23,7 +23,11 @@ import { resolveContained } from 'skill-family-harness-node';
|
|
|
23
23
|
const execFile = promisify(execFileCb);
|
|
24
24
|
|
|
25
25
|
import { validatePlan, computePlanDigest, validatePlanActionCompleteness } from '../core/plan.mjs';
|
|
26
|
-
import {
|
|
26
|
+
import {
|
|
27
|
+
normalizePostPublishView,
|
|
28
|
+
postPublishActionId,
|
|
29
|
+
requiresPostPublishDistribution,
|
|
30
|
+
} from '../core/postpublish.mjs';
|
|
27
31
|
import { createEvidenceWriter } from '../core/evidence.mjs';
|
|
28
32
|
import { readRunRecovery } from '../core/recovery.mjs';
|
|
29
33
|
import {
|
|
@@ -1230,15 +1234,11 @@ export async function verifyRelease(options) {
|
|
|
1230
1234
|
|
|
1231
1235
|
// =======================================================================
|
|
1232
1236
|
// Step 2b: Check for postPublish distribution requirement.
|
|
1233
|
-
//
|
|
1234
|
-
// PARTIAL distribute run passes only through the blocksVerified:false
|
|
1237
|
+
// The gate triggers only when targets or distribute-phase hooks exist.
|
|
1238
|
+
// A PARTIAL distribute run passes only through the blocksVerified:false
|
|
1235
1239
|
// exemption path (evaluateDistributeGateRun) — warned, never silent.
|
|
1236
1240
|
// =======================================================================
|
|
1237
|
-
|
|
1238
|
-
// requirement; legacy absent postPublish resolves to the same empty view.
|
|
1239
|
-
const postPublishDeclarations = normalizePostPublishView(plan);
|
|
1240
|
-
const requiresDistribution = postPublishDeclarations.some((declaration) =>
|
|
1241
|
-
(declaration.targets?.length ?? 0) > 0 || (declaration.hooks?.length ?? 0) > 0);
|
|
1241
|
+
const requiresDistribution = requiresPostPublishDistribution(plan);
|
|
1242
1242
|
if (requiresDistribution) {
|
|
1243
1243
|
await evidence.append({ phase: 'verify', step: 'distribute-run-discovery', status: 'started' });
|
|
1244
1244
|
|
package/src/core/postpublish.mjs
CHANGED
|
@@ -547,6 +547,30 @@ export function normalizePostPublishView(plan) {
|
|
|
547
547
|
);
|
|
548
548
|
}
|
|
549
549
|
|
|
550
|
+
/**
|
|
551
|
+
* Decide whether a frozen plan has work for the distribute phase.
|
|
552
|
+
*
|
|
553
|
+
* A declaration requires distribute when it contains at least one legacy
|
|
554
|
+
* target, an explicit phase:distribute hook, or a hook whose omitted phase
|
|
555
|
+
* keeps the existing distribute default. phase:postVerify hooks belong only
|
|
556
|
+
* to the independent postVerify run and must not allocate an empty
|
|
557
|
+
* distribute predecessor.
|
|
558
|
+
*
|
|
559
|
+
* This is the single release-domain authority used by ship, verify, and
|
|
560
|
+
* recovery. Shape compatibility remains owned by normalizePostPublishView.
|
|
561
|
+
*
|
|
562
|
+
* @param {object} plan - Frozen release plan.
|
|
563
|
+
* @returns {boolean} Whether distribute must precede verify.
|
|
564
|
+
*/
|
|
565
|
+
export function requiresPostPublishDistribution(plan) {
|
|
566
|
+
return normalizePostPublishView(plan).some((declaration) => (
|
|
567
|
+
(declaration.targets?.length ?? 0) > 0
|
|
568
|
+
|| (declaration.hooks ?? []).some((hook) => (
|
|
569
|
+
hook.phase === undefined || hook.phase === 'distribute'
|
|
570
|
+
))
|
|
571
|
+
));
|
|
572
|
+
}
|
|
573
|
+
|
|
550
574
|
/**
|
|
551
575
|
* Array-level domain validation: every EXPLICIT hooks[].id must be unique
|
|
552
576
|
* across the whole declaration array (multi-release-unit postPublish v3,
|
package/src/core/recovery.mjs
CHANGED
|
@@ -33,7 +33,12 @@ 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 {
|
|
36
|
+
import {
|
|
37
|
+
effectiveHookRequiresApproval,
|
|
38
|
+
normalizePostPublishView,
|
|
39
|
+
postPublishActionId,
|
|
40
|
+
requiresPostPublishDistribution,
|
|
41
|
+
} from './postpublish.mjs';
|
|
37
42
|
import { assertPostPublishApprovalAuthority, validatePostPublishApproval } from './postpublish-approval.mjs';
|
|
38
43
|
|
|
39
44
|
/** Remote errors alone are insufficient to select a safe recovery phase. */
|
|
@@ -274,11 +279,8 @@ export async function readRunRecovery(runPath, options = {}) {
|
|
|
274
279
|
}
|
|
275
280
|
} else if (['publish', 'reconcile'].includes(command)) {
|
|
276
281
|
if (run.status === 'PUBLISHED') {
|
|
277
|
-
//
|
|
278
|
-
|
|
279
|
-
// distribution phase.
|
|
280
|
-
const needsDistribution = normalizePostPublishView(plan).some((declaration) =>
|
|
281
|
-
(declaration.targets?.length ?? 0) > 0 || (declaration.hooks?.length ?? 0) > 0);
|
|
282
|
+
// phase:postVerify hooks do not create an empty distribute predecessor.
|
|
283
|
+
const needsDistribution = requiresPostPublishDistribution(plan);
|
|
282
284
|
code = needsDistribution ? 'DISTRIBUTE' : 'VERIFY';
|
|
283
285
|
} else if (run.status === 'PARTIAL') {
|
|
284
286
|
code = 'RECONCILE';
|