release-skill 0.2.1 → 0.2.3
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 +4 -2
- package/.codex-plugin/plugin.json +2 -2
- package/.kimi-plugin/plugin.json +1 -1
- package/CHANGELOG.md +40 -0
- package/INSTALL.md +183 -21
- package/INSTALL.zh-CN.md +160 -16
- package/README.md +112 -13
- package/README.zh-CN.md +72 -11
- 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 +2240 -587
- package/adapters/claude/schemas/release-plan.schema.json +44 -2
- package/adapters/claude/schemas/release-project.schema.json +46 -4
- package/adapters/claude/schemas/release-run.schema.json +1 -0
- package/adapters/codex/.codex-plugin/plugin.json +2 -2
- package/adapters/codex/bin/release-skill.bundle.mjs +2240 -587
- package/adapters/codex/schemas/release-plan.schema.json +44 -2
- package/adapters/codex/schemas/release-project.schema.json +46 -4
- package/adapters/codex/schemas/release-run.schema.json +1 -0
- package/adapters/kimi/.kimi-plugin/plugin.json +1 -1
- package/adapters/kimi/bin/release-skill.bundle.mjs +2240 -587
- package/adapters/kimi/schemas/release-plan.schema.json +44 -2
- package/adapters/kimi/schemas/release-project.schema.json +46 -4
- package/adapters/kimi/schemas/release-run.schema.json +1 -0
- package/adapters/workbuddy/.codebuddy-plugin/plugin.json +4 -2
- package/adapters/workbuddy/bin/release-skill.bundle.mjs +2240 -587
- package/adapters/workbuddy/schemas/release-plan.schema.json +44 -2
- package/adapters/workbuddy/schemas/release-project.schema.json +46 -4
- package/adapters/workbuddy/schemas/release-run.schema.json +1 -0
- package/bin/release-skill-cli.mjs +46 -4
- package/bin/release-skill.bundle.mjs +2240 -587
- package/package.json +1 -1
- package/references/02-project-config.md +7 -0
- package/references/06-adapter-contract.md +21 -2
- package/schemas/release-plan.schema.json +44 -2
- package/schemas/release-project.schema.json +46 -4
- package/schemas/release-run.schema.json +1 -0
- package/scripts/sync-public-files.mjs +8 -4
- package/src/adapters/contract.mjs +1 -0
- package/src/adapters/plugin-marketplace.mjs +796 -41
- package/src/commands/assess.mjs +50 -1
- package/src/commands/prepare.mjs +342 -9
- package/src/commands/publish.mjs +1 -0
- package/src/commands/reconcile.mjs +1 -0
- package/src/commands/setup.mjs +7 -3
- package/src/commands/verify.mjs +13 -5
- package/src/core/baseline.mjs +13 -0
- package/src/core/checkpoints.mjs +7 -2
- package/src/core/plan.mjs +275 -4
- package/src/core/verification-gates.mjs +1 -1
- package/src/platforms/codebuddy.mjs +658 -0
- package/src/platforms/registry.mjs +258 -5
- package/src/producers/build-adapters.mjs +30 -15
|
@@ -0,0 +1,658 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CodeBuddy / WorkBuddy platform protocol (human attestation closed loop).
|
|
3
|
+
*
|
|
4
|
+
* CodeBuddy (desktop product WorkBuddy) ships a `codebuddy` CLI that is
|
|
5
|
+
* structurally identical to claude-code, BUT its plugin install protocol cannot
|
|
6
|
+
* pin a frozen ref: `plugin marketplace add <source>` and `plugin install
|
|
7
|
+
* <plugin[@marketplace]>` have NO ref option, and the install tracks the
|
|
8
|
+
* marketplace default branch / latest (verified against the real CLI and its
|
|
9
|
+
* on-disk state model). An automated install checkpoint therefore cannot
|
|
10
|
+
* guarantee the frozen artifact's identity, which this project's security model
|
|
11
|
+
* requires. The install state DOES have a stable on-disk layout that serves as
|
|
12
|
+
* verification evidence.
|
|
13
|
+
*
|
|
14
|
+
* So codebuddy-marketplace-install is modeled exactly like kimi's protocol
|
|
15
|
+
* capability gap (a human attestation closed loop), with two differences driven
|
|
16
|
+
* by the verified CodeBuddy state model:
|
|
17
|
+
*
|
|
18
|
+
* - Install is from a unified marketplace (`artifact-skill-set`,
|
|
19
|
+
* https://github.com/ifoohoo/artifact-skill-set), NOT a release-tag URL.
|
|
20
|
+
* - Two install channels are accepted, both evidenced on disk:
|
|
21
|
+
* desktop: the WorkBuddy desktop app installs into
|
|
22
|
+
* `~/.workbuddy/plugins/marketplaces/<marketplace>/plugins/<plugin>/`
|
|
23
|
+
* cli: the bundled `codebuddy` CLI, run with an isolated
|
|
24
|
+
* `HOME=<codebuddyHome>`, installs into
|
|
25
|
+
* `<codebuddyHome>/.codebuddy/plugins/marketplaces/<marketplace>/plugins/<plugin>/`
|
|
26
|
+
* The attestation names the channel (`installChannel`) and the marketplace;
|
|
27
|
+
* the install path is validated per channel and fails closed otherwise.
|
|
28
|
+
*
|
|
29
|
+
* - execute NEVER execs the codebuddy CLI. It emits an actionable manual
|
|
30
|
+
* install requirement bound to the frozen plan digest + identity.
|
|
31
|
+
* - observe/verify consume a structured human attestation plus read-only
|
|
32
|
+
* verification of the installed copy. Missing/expired/mismatched/escaping
|
|
33
|
+
* proof fails closed, so a codebuddy unit can never reach VERIFIED without
|
|
34
|
+
* it — the same fail-closed severity as kimi ("post-publish verification
|
|
35
|
+
* cannot be waived").
|
|
36
|
+
*
|
|
37
|
+
* This module is the codebuddy half of the platform registry's strategy table
|
|
38
|
+
* (registry.mjs references executeCodeBuddyManualRequirement /
|
|
39
|
+
* readCodeBuddyManifest); the plugin-marketplace adapter consumes the
|
|
40
|
+
* attestation path from here through a codebuddy-specific branch that mirrors
|
|
41
|
+
* the kimi branch. The shared adapter primitives (safe-id pattern,
|
|
42
|
+
* frozen-timeout validation, atomic evidence writes) live in
|
|
43
|
+
* adapters/contract.mjs.
|
|
44
|
+
*
|
|
45
|
+
* NOTE on intentional duplication: the 64-hex digest pattern and the
|
|
46
|
+
* lifecycle plan normalization below mirror kimi.mjs's pure helpers instead of
|
|
47
|
+
* importing them. Importing from ./kimi.mjs would add a codebuddy -> kimi edge
|
|
48
|
+
* that, combined with the kimi -> plan -> registry -> kimi cycle, deadlocks the
|
|
49
|
+
* esbuild-generated module initializers in the self-contained bundle (the
|
|
50
|
+
* `prepare` dynamic import never settles). Inlining these two tiny pure
|
|
51
|
+
* functions keeps codebuddy's only cycle edge codebuddy -> plan -> registry
|
|
52
|
+
* (the same shape kimi already ships with) and the bundle evaluating cleanly.
|
|
53
|
+
*
|
|
54
|
+
* @module platforms/codebuddy
|
|
55
|
+
*/
|
|
56
|
+
|
|
57
|
+
import { readFile, mkdir } from 'node:fs/promises';
|
|
58
|
+
import { join, resolve, relative, isAbsolute } from 'node:path';
|
|
59
|
+
|
|
60
|
+
import {
|
|
61
|
+
ActionType,
|
|
62
|
+
ActionStatus,
|
|
63
|
+
createResult,
|
|
64
|
+
resolveTimeoutMs,
|
|
65
|
+
SAFE_ID_RE,
|
|
66
|
+
writeEvidenceAtomic,
|
|
67
|
+
} from '../adapters/contract.mjs';
|
|
68
|
+
import { canonicalJson } from '../core/digest.mjs';
|
|
69
|
+
// NOTE: computePlanDigest (../core/plan.mjs) is imported LAZILY inside
|
|
70
|
+
// resolveCodeBuddyBoundPlanDigest, NOT statically here. A static import would
|
|
71
|
+
// add a registry -> codebuddy -> plan -> registry cycle edge; combined with the
|
|
72
|
+
// existing kimi -> plan -> registry -> kimi cycle, that deadlocks esbuild's
|
|
73
|
+
// generated module initializers in the self-contained bundle (the `prepare`
|
|
74
|
+
// dynamic import never settles). kimi.mjs predates this constraint; codebuddy
|
|
75
|
+
// keeps the bundle graph acyclic by loading plan.mjs at call time (by then
|
|
76
|
+
// every module is initialized, so the lazy import resolves immediately).
|
|
77
|
+
|
|
78
|
+
/** 64-char lowercase hex plan/payload digest pattern (mirrors kimi.mjs). */
|
|
79
|
+
const HEX_DIGEST_RE = /^[a-f0-9]{64}$/;
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Normalize a plan back to its frozen form for digest comparison (mirrors the
|
|
83
|
+
* pure helper in kimi.mjs; inlined here to avoid a codebuddy -> kimi import
|
|
84
|
+
* edge — see the module note). Only lifecycle status fields are reset: the
|
|
85
|
+
* top-level `status` returns to "PREPARED" and every `externalActions[].status`
|
|
86
|
+
* returns to "PENDING"; every other field is preserved verbatim.
|
|
87
|
+
*
|
|
88
|
+
* @param {object} plan
|
|
89
|
+
* @returns {object} the lifecycle-normalized plan
|
|
90
|
+
*/
|
|
91
|
+
function normalizePlanForDigest(plan) {
|
|
92
|
+
const normalized = { ...plan, status: 'PREPARED' };
|
|
93
|
+
if (Array.isArray(plan.externalActions)) {
|
|
94
|
+
normalized.externalActions = plan.externalActions.map((action) => (
|
|
95
|
+
action && typeof action === 'object' && !Array.isArray(action)
|
|
96
|
+
? { ...action, status: 'PENDING' }
|
|
97
|
+
: action
|
|
98
|
+
));
|
|
99
|
+
}
|
|
100
|
+
return normalized;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/** Structured manual-install requirement written by codebuddy execute. */
|
|
104
|
+
export const CODEBUDDY_REQUIREMENT_FILE = 'release-skill-codebuddy-manual-install.json';
|
|
105
|
+
/** Structured human attestation consumed by codebuddy observe/verify. */
|
|
106
|
+
export const CODEBUDDY_ATTESTATION_FILE = 'release-skill-codebuddy-attestation.json';
|
|
107
|
+
/** Maximum attestation validity window (mirrors the 24h approval expiry). */
|
|
108
|
+
export const CODEBUDDY_MAX_ATTESTATION_VALIDITY_MS = 24 * 60 * 60 * 1000;
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Unified marketplace the WorkBuddy desktop app and the codebuddy CLI install
|
|
112
|
+
* release-skill from (verified fact). The attestation `marketplace` field must
|
|
113
|
+
* equal this value; the install path is validated against this marketplace
|
|
114
|
+
* segment in both channels.
|
|
115
|
+
*/
|
|
116
|
+
export const CODEBUDDY_MARKETPLACE_NAME = 'artifact-skill-set';
|
|
117
|
+
/** Marketplace source URL used by the isolated-CLI install path. */
|
|
118
|
+
export const CODEBUDDY_MARKETPLACE_SOURCE = 'https://github.com/ifoohoo/artifact-skill-set';
|
|
119
|
+
|
|
120
|
+
/** Authoritative codebuddy plugin manifest (single candidate, no precedence). */
|
|
121
|
+
export const CODEBUDDY_PLUGIN_MANIFEST_RELATIVE = join('.codebuddy-plugin', 'plugin.json');
|
|
122
|
+
|
|
123
|
+
/** The only two install channels with verified on-disk evidence. */
|
|
124
|
+
const CODEBUDDY_INSTALL_CHANNELS = new Set(['desktop', 'cli']);
|
|
125
|
+
|
|
126
|
+
/** Well-known desktop install root (the WorkBuddy app's home dir name). */
|
|
127
|
+
const CODEBUDDY_DESKTOP_HOME_DIR = '.workbuddy';
|
|
128
|
+
/** CLI-side plugin state dir under the isolated HOME. */
|
|
129
|
+
const CODEBUDDY_CLI_STATE_DIR = '.codebuddy';
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Resolve and verify the genuine frozen plan digest from the adapter context,
|
|
133
|
+
* for the codebuddy closed loop.
|
|
134
|
+
*
|
|
135
|
+
* Mirrors kimi's resolveBoundPlanDigest byte-for-byte in LOGIC (it reuses the
|
|
136
|
+
* same pure `normalizePlanForDigest`), but speaks in codebuddy terms so the
|
|
137
|
+
* codebuddy requirement/attestation error messages are codebuddy-worded. The
|
|
138
|
+
* carried `context.plan.digest` is recomputed from the lifecycle-normalized
|
|
139
|
+
* plan and must match exactly; any non-lifecycle tamper fails closed.
|
|
140
|
+
*
|
|
141
|
+
* @param {object} context - adapter context (must carry the frozen `plan`).
|
|
142
|
+
* @returns {Promise<string>} the verified frozen plan digest.
|
|
143
|
+
* @throws {Error} when the plan is absent or the digest does not match.
|
|
144
|
+
*/
|
|
145
|
+
export async function resolveCodeBuddyBoundPlanDigest(context) {
|
|
146
|
+
const plan = context?.plan;
|
|
147
|
+
if (!plan || typeof plan !== 'object' || Array.isArray(plan)) {
|
|
148
|
+
throw new Error('context.plan is required to bind the codebuddy plan digest');
|
|
149
|
+
}
|
|
150
|
+
const carried = plan.digest;
|
|
151
|
+
if (typeof carried !== 'string' || !HEX_DIGEST_RE.test(carried)) {
|
|
152
|
+
throw new Error('context.plan.digest must be a 64-char lowercase hex frozen plan digest');
|
|
153
|
+
}
|
|
154
|
+
// Lazy import keeps the static bundle graph acyclic (see module note).
|
|
155
|
+
const { computePlanDigest } = await import('../core/plan.mjs');
|
|
156
|
+
const normalized = normalizePlanForDigest(plan);
|
|
157
|
+
if (computePlanDigest(normalized) !== carried) {
|
|
158
|
+
throw new Error('context.plan.digest does not match the normalized frozen plan (a non-lifecycle field was tampered)');
|
|
159
|
+
}
|
|
160
|
+
return carried;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Authoritative, cross-run attestation directory for a codebuddy install.
|
|
165
|
+
*
|
|
166
|
+
* Lives at a stable root-fixed location keyed by the verified frozen plan
|
|
167
|
+
* digest and plugin id:
|
|
168
|
+
* <root>/.release-skill/codebuddy-attestations/<planDigest>/<plugin>/
|
|
169
|
+
*
|
|
170
|
+
* This survives the publish -> manual install -> reconcile -> verify chain,
|
|
171
|
+
* where each command otherwise uses a fresh runDir. Both the requirement and
|
|
172
|
+
* the human attestation live here. Segments are pre-validated (planDigest is
|
|
173
|
+
* 64-hex, plugin matches SAFE_ID_RE) and the resolved path is contained within
|
|
174
|
+
* the authority base, so no path escape is possible. (Path-escape validation is
|
|
175
|
+
* copied verbatim from kimiAuthorityDir, with codebuddy wording.)
|
|
176
|
+
*
|
|
177
|
+
* @param {object} context - adapter context (needs `root`).
|
|
178
|
+
* @param {string} planDigest - verified frozen plan digest (64-hex).
|
|
179
|
+
* @param {string} plugin - plugin id (SAFE_ID_RE).
|
|
180
|
+
* @returns {string} absolute authority directory.
|
|
181
|
+
*/
|
|
182
|
+
export function codebuddyAuthorityDir(context, planDigest, plugin) {
|
|
183
|
+
if (!context?.root) {
|
|
184
|
+
throw new Error('context.root is required for the codebuddy attestation authority');
|
|
185
|
+
}
|
|
186
|
+
if (!HEX_DIGEST_RE.test(planDigest)) {
|
|
187
|
+
throw new Error('codebuddy attestation authority requires a 64-hex plan digest');
|
|
188
|
+
}
|
|
189
|
+
if (!SAFE_ID_RE.test(plugin)) {
|
|
190
|
+
throw new Error(`codebuddy attestation authority requires a safe plugin id: "${plugin}"`);
|
|
191
|
+
}
|
|
192
|
+
const base = resolve(context.root, '.release-skill', 'codebuddy-attestations');
|
|
193
|
+
const dir = resolve(base, planDigest, plugin);
|
|
194
|
+
const rel = relative(base, dir);
|
|
195
|
+
const sep = process.platform === 'win32' ? '\\' : '/';
|
|
196
|
+
if (
|
|
197
|
+
rel === '' || rel === '..' || isAbsolute(rel) || rel.startsWith(`..${sep}`)
|
|
198
|
+
|| rel.split(sep).some((segment) => segment === '..' || segment === '')
|
|
199
|
+
) {
|
|
200
|
+
throw new Error('codebuddy attestation authority path escapes its base');
|
|
201
|
+
}
|
|
202
|
+
return dir;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* The isolated CodeBuddy CLI HOME for the cli install channel. The operator
|
|
207
|
+
* runs the bundled `codebuddy` CLI with `HOME=<codebuddyHome>` so the managed
|
|
208
|
+
* marketplace clone lands inside the plan-digest-keyed authority rather than
|
|
209
|
+
* the user's real `~/.codebuddy`. Identical across publish/reconcile/verify
|
|
210
|
+
* run dirs because it derives from the authority dir.
|
|
211
|
+
*
|
|
212
|
+
* @param {string} attestationDir - plan-digest-keyed authority dir.
|
|
213
|
+
* @returns {string}
|
|
214
|
+
*/
|
|
215
|
+
export function codebuddyCliHome(attestationDir) {
|
|
216
|
+
return resolve(attestationDir, 'codebuddy-home');
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* The expected CLI-channel install root for a plugin under an isolated home.
|
|
221
|
+
* `<codebuddyHome>/.codebuddy/plugins/marketplaces/<marketplace>/plugins/<plugin>`
|
|
222
|
+
*
|
|
223
|
+
* @param {string} cliHome - isolated codebuddy HOME.
|
|
224
|
+
* @param {string} marketplace - marketplace name.
|
|
225
|
+
* @param {string} plugin - plugin id.
|
|
226
|
+
* @returns {string}
|
|
227
|
+
*/
|
|
228
|
+
export function codebuddyCliInstallRoot(cliHome, marketplace, plugin) {
|
|
229
|
+
return resolve(cliHome, CODEBUDDY_CLI_STATE_DIR, 'plugins', 'marketplaces', marketplace, 'plugins', plugin);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Segment-level tail an attested desktop installPath must end with:
|
|
234
|
+
* `/.workbuddy/plugins/marketplaces/<marketplace>/plugins/<plugin>`
|
|
235
|
+
* (verified WorkBuddy desktop layout).
|
|
236
|
+
*
|
|
237
|
+
* @param {string} marketplace
|
|
238
|
+
* @param {string} plugin
|
|
239
|
+
* @returns {string[]}
|
|
240
|
+
*/
|
|
241
|
+
function codebuddyDesktopTailSegments(marketplace, plugin) {
|
|
242
|
+
return [CODEBUDDY_DESKTOP_HOME_DIR, 'plugins', 'marketplaces', marketplace, 'plugins', plugin];
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Segment-level tail an attested cli installPath must end with:
|
|
247
|
+
* `/.codebuddy/plugins/marketplaces/<marketplace>/plugins/<plugin>`
|
|
248
|
+
* (verified CodeBuddy CLI layout).
|
|
249
|
+
*
|
|
250
|
+
* @param {string} marketplace
|
|
251
|
+
* @param {string} plugin
|
|
252
|
+
* @returns {string[]}
|
|
253
|
+
*/
|
|
254
|
+
function codebuddyCliTailSegments(marketplace, plugin) {
|
|
255
|
+
return [CODEBUDDY_CLI_STATE_DIR, 'plugins', 'marketplaces', marketplace, 'plugins', plugin];
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Segment-level check that a desktop installPath ends with the well-known
|
|
260
|
+
* WorkBuddy marketplace layout. Pure string check (no filesystem): the path's
|
|
261
|
+
* segments (split on `/` or `\`, dropping empty/`.` segments) must end with the
|
|
262
|
+
* desktop tail. Fails closed otherwise.
|
|
263
|
+
*
|
|
264
|
+
* @param {string} installPath
|
|
265
|
+
* @param {string} marketplace
|
|
266
|
+
* @param {string} plugin
|
|
267
|
+
* @returns {boolean}
|
|
268
|
+
*/
|
|
269
|
+
function matchesDesktopLayout(installPath, marketplace, plugin) {
|
|
270
|
+
const segments = installPath.split(/[\\/]+/).filter((s) => s !== '' && s !== '.');
|
|
271
|
+
const tail = codebuddyDesktopTailSegments(marketplace, plugin);
|
|
272
|
+
if (segments.length < tail.length) return false;
|
|
273
|
+
const offset = segments.length - tail.length;
|
|
274
|
+
return tail.every((segment, index) => segments[offset + index] === segment);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* Segment-level check that a cli installPath ends with the well-known
|
|
279
|
+
* CodeBuddy CLI marketplace layout. Pure string check (no filesystem): the
|
|
280
|
+
* path's segments (split on `/` or `\`, dropping empty/`.` segments) must end
|
|
281
|
+
* with the cli tail. Fails closed otherwise.
|
|
282
|
+
*
|
|
283
|
+
* @param {string} installPath
|
|
284
|
+
* @param {string} marketplace
|
|
285
|
+
* @param {string} plugin
|
|
286
|
+
* @returns {boolean}
|
|
287
|
+
*/
|
|
288
|
+
function matchesCliLayout(installPath, marketplace, plugin) {
|
|
289
|
+
const segments = installPath.split(/[\\/]+/).filter((s) => s !== '' && s !== '.');
|
|
290
|
+
const tail = codebuddyCliTailSegments(marketplace, plugin);
|
|
291
|
+
if (segments.length < tail.length) return false;
|
|
292
|
+
const offset = segments.length - tail.length;
|
|
293
|
+
return tail.every((segment, index) => segments[offset + index] === segment);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* Human-facing, actionable manual-install closed-loop instructions for
|
|
298
|
+
* CodeBuddy / WorkBuddy.
|
|
299
|
+
*
|
|
300
|
+
* @param {{plugin:string, version:string, ref:string, cliHome:string, attestationDir:string}} p
|
|
301
|
+
* @returns {string[]}
|
|
302
|
+
*/
|
|
303
|
+
function buildCodeBuddyManualInstructions({ plugin, version, ref, cliHome, attestationDir }) {
|
|
304
|
+
const cliInstallRoot = codebuddyCliInstallRoot(cliHome, CODEBUDDY_MARKETPLACE_NAME, plugin);
|
|
305
|
+
const desktopInstallRoot = `~/${CODEBUDDY_DESKTOP_HOME_DIR}/plugins/marketplaces/${CODEBUDDY_MARKETPLACE_NAME}/plugins/${plugin}`;
|
|
306
|
+
return [
|
|
307
|
+
`CodeBuddy/WorkBuddy plugin install cannot pin a frozen ref (the codebuddy CLI marketplace add/install have no ref option and track the default branch), so installation is a manual step proven by a human attestation.`,
|
|
308
|
+
`1) publish fails closed at this codebuddy checkpoint and leaves the run PARTIAL (the automated Git branch/tag, npm, and GitHub Release writes still complete first).`,
|
|
309
|
+
`2) PRIMARY PATH (WorkBuddy desktop): install release-skill from the unified marketplace "${CODEBUDDY_MARKETPLACE_NAME}" (${CODEBUDDY_MARKETPLACE_SOURCE}). Confirm "~/.workbuddy/settings.json" enabledPlugins contains "${plugin}@${CODEBUDDY_MARKETPLACE_NAME}": true. The plugin lands at "${desktopInstallRoot}/".`,
|
|
310
|
+
`3) ALTERNATE PATH (bundled codebuddy CLI, isolatable): run the CLI with the ISOLATED home from this requirement so the clone lands inside it: HOME="${cliHome}" <codebuddy binary> plugin marketplace add ${CODEBUDDY_MARKETPLACE_SOURCE} ; then HOME="${cliHome}" <codebuddy binary> plugin install ${plugin}@${CODEBUDDY_MARKETPLACE_NAME}. The CLI ships with WorkBuddy.app (macOS known path /Applications/WorkBuddy.app/Contents/Resources/app.asar.unpacked/cli/bin/codebuddy). The plugin lands at "${cliInstallRoot}/".`,
|
|
311
|
+
`4) REF LIMITATION WARNING: a codebuddy install tracks the marketplace default branch and CANNOT be pinned to frozen ref "${ref}". Before writing the attestation you MUST confirm the installed plugin manifest version equals the frozen version ${version}; otherwise do NOT issue an attestation.`,
|
|
312
|
+
`5) Write the attestation JSON to: ${attestationDir}/${CODEBUDDY_ATTESTATION_FILE}. planDigest MUST be the frozen plan digest; payloadDigest MUST be the frozen snapshot payload digest; installChannel MUST be "desktop" or "cli"; marketplace MUST be "${CODEBUDDY_MARKETPLACE_NAME}"; installPath MUST be the actual installed plugin directory for the chosen channel. attestedAt must not be in the future and expiresAt must be within 24 hours of attestedAt.`,
|
|
313
|
+
` Required fields: consumer="codebuddy", plugin, version, entrySkill, repo, ref, marketplace, installChannel, installPath, planDigest, payloadDigest, attestedBy, attestedAt, expiresAt.`,
|
|
314
|
+
`6) Re-run release-skill reconcile (promotes PARTIAL -> PUBLISHED) and then verify (-> VERIFIED). Both read the attestation from this same plan-digest-keyed authority directory, so a fresh run directory does not lose the proof.`,
|
|
315
|
+
];
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/**
|
|
319
|
+
* Read the authoritative CodeBuddy plugin manifest from a verified plugin root.
|
|
320
|
+
*
|
|
321
|
+
* Single candidate `.codebuddy-plugin/plugin.json` (no precedence chain, unlike
|
|
322
|
+
* kimi). Returns the parsed manifest and the root-relative manifest path.
|
|
323
|
+
* Throws when the manifest is absent, not valid JSON, or not an object.
|
|
324
|
+
*
|
|
325
|
+
* @param {string} pluginRootReal - realpath of the verified plugin root.
|
|
326
|
+
* @returns {Promise<{manifest:object, manifestRelative:string}>}
|
|
327
|
+
*/
|
|
328
|
+
export async function readCodeBuddyManifest(pluginRootReal) {
|
|
329
|
+
const manifestRelative = CODEBUDDY_PLUGIN_MANIFEST_RELATIVE;
|
|
330
|
+
const manifestPath = resolve(pluginRootReal, manifestRelative);
|
|
331
|
+
let content;
|
|
332
|
+
try {
|
|
333
|
+
content = await readFile(manifestPath, 'utf8');
|
|
334
|
+
} catch {
|
|
335
|
+
throw new Error(`no codebuddy plugin manifest found (expected ${manifestRelative})`);
|
|
336
|
+
}
|
|
337
|
+
let manifest;
|
|
338
|
+
try {
|
|
339
|
+
manifest = JSON.parse(content);
|
|
340
|
+
} catch {
|
|
341
|
+
throw new Error(`codebuddy plugin manifest ${manifestRelative} is not valid JSON`);
|
|
342
|
+
}
|
|
343
|
+
if (!manifest || typeof manifest !== 'object' || Array.isArray(manifest)) {
|
|
344
|
+
throw new Error(`codebuddy plugin manifest ${manifestRelative} is not an object`);
|
|
345
|
+
}
|
|
346
|
+
return { manifest, manifestRelative };
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
/**
|
|
350
|
+
* Validate a structured codebuddy manual-install attestation against the frozen
|
|
351
|
+
* action and the verified frozen plan digest.
|
|
352
|
+
*
|
|
353
|
+
* Bindings (fail closed on any mismatch):
|
|
354
|
+
* - `consumer` must be "codebuddy".
|
|
355
|
+
* - `installChannel` must be "desktop" or "cli".
|
|
356
|
+
* - `marketplace` must equal the requirement-declared unified marketplace
|
|
357
|
+
* (`artifact-skill-set`).
|
|
358
|
+
* - `planDigest` binds to the REAL frozen plan digest (`boundPlanDigest`, from
|
|
359
|
+
* `context.plan.digest`) — NOT to `action.manifestDigest`.
|
|
360
|
+
* - `payloadDigest` binds separately to `action.manifestDigest`.
|
|
361
|
+
* - plugin identity, version, entry skill, repo, and frozen ref must match.
|
|
362
|
+
* - Time bounds: `attestedAt` not in the future, window (`expiresAt -
|
|
363
|
+
* attestedAt`) <= 24h, and not expired relative to `isoNow`.
|
|
364
|
+
* - installPath per channel (lexical): desktop must end with the well-known
|
|
365
|
+
* `/.workbuddy/plugins/marketplaces/<marketplace>/plugins/<plugin>` segment
|
|
366
|
+
* tail. The cli channel's containment within the isolated home is enforced in
|
|
367
|
+
* the adapter observe branch (it needs the context-derived isolated home and
|
|
368
|
+
* realpath resolution, mirroring kimi's observe-side managed-root check).
|
|
369
|
+
*
|
|
370
|
+
* @param {object} attestation - parsed attestation JSON.
|
|
371
|
+
* @param {object} action - the expanded codebuddy action (top-level fields).
|
|
372
|
+
* @param {string} isoNow - current ISO timestamp.
|
|
373
|
+
* @param {string} boundPlanDigest - verified frozen plan digest.
|
|
374
|
+
* @returns {{valid:boolean, error:string|null}}
|
|
375
|
+
*/
|
|
376
|
+
export function validateCodeBuddyAttestation(attestation, action, isoNow, boundPlanDigest) {
|
|
377
|
+
if (!attestation || typeof attestation !== 'object' || Array.isArray(attestation)) {
|
|
378
|
+
return { valid: false, error: 'codebuddy attestation is not an object' };
|
|
379
|
+
}
|
|
380
|
+
const requiredStrings = ['plugin', 'version', 'entrySkill', 'repo', 'ref', 'installPath', 'payloadDigest', 'planDigest', 'attestedBy', 'attestedAt', 'expiresAt', 'marketplace', 'installChannel'];
|
|
381
|
+
for (const field of requiredStrings) {
|
|
382
|
+
if (typeof attestation[field] !== 'string' || attestation[field].length === 0) {
|
|
383
|
+
return { valid: false, error: `codebuddy attestation missing required field "${field}"` };
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
if (attestation.consumer !== 'codebuddy') {
|
|
387
|
+
return { valid: false, error: `codebuddy attestation consumer "${attestation.consumer}" must be "codebuddy"` };
|
|
388
|
+
}
|
|
389
|
+
if (!CODEBUDDY_INSTALL_CHANNELS.has(attestation.installChannel)) {
|
|
390
|
+
return { valid: false, error: `codebuddy attestation installChannel "${attestation.installChannel}" must be "desktop" or "cli"` };
|
|
391
|
+
}
|
|
392
|
+
if (attestation.marketplace !== CODEBUDDY_MARKETPLACE_NAME) {
|
|
393
|
+
return { valid: false, error: `codebuddy attestation marketplace "${attestation.marketplace}" must be "${CODEBUDDY_MARKETPLACE_NAME}"` };
|
|
394
|
+
}
|
|
395
|
+
if (!HEX_DIGEST_RE.test(attestation.planDigest)) {
|
|
396
|
+
return { valid: false, error: 'codebuddy attestation planDigest must be a 64-char lowercase hex digest' };
|
|
397
|
+
}
|
|
398
|
+
if (attestation.planDigest !== boundPlanDigest) {
|
|
399
|
+
return { valid: false, error: 'codebuddy attestation planDigest does not match the frozen plan digest' };
|
|
400
|
+
}
|
|
401
|
+
if (attestation.plugin !== action.plugin) {
|
|
402
|
+
return { valid: false, error: `codebuddy attestation plugin "${attestation.plugin}" does not match action plugin "${action.plugin}"` };
|
|
403
|
+
}
|
|
404
|
+
if (attestation.version !== action.version) {
|
|
405
|
+
return { valid: false, error: `codebuddy attestation version "${attestation.version}" does not match action version "${action.version}"` };
|
|
406
|
+
}
|
|
407
|
+
if (attestation.entrySkill !== action.entrySkill) {
|
|
408
|
+
return { valid: false, error: `codebuddy attestation entrySkill "${attestation.entrySkill}" does not match action entrySkill "${action.entrySkill}"` };
|
|
409
|
+
}
|
|
410
|
+
if (attestation.repo !== action.repo) {
|
|
411
|
+
return { valid: false, error: `codebuddy attestation repo "${attestation.repo}" does not match action repo "${action.repo}"` };
|
|
412
|
+
}
|
|
413
|
+
const expectedRef = action.ref ?? `v${action.version}`;
|
|
414
|
+
if (attestation.ref !== expectedRef) {
|
|
415
|
+
return { valid: false, error: `codebuddy attestation ref "${attestation.ref}" does not match frozen ref "${expectedRef}"` };
|
|
416
|
+
}
|
|
417
|
+
if (attestation.payloadDigest !== action.manifestDigest) {
|
|
418
|
+
return { valid: false, error: 'codebuddy attestation payloadDigest does not match the frozen payload digest' };
|
|
419
|
+
}
|
|
420
|
+
const attestedMs = Date.parse(attestation.attestedAt);
|
|
421
|
+
const expiresMs = Date.parse(attestation.expiresAt);
|
|
422
|
+
const nowMs = Date.parse(isoNow);
|
|
423
|
+
if (!Number.isFinite(attestedMs) || !Number.isFinite(expiresMs) || !Number.isFinite(nowMs)) {
|
|
424
|
+
return { valid: false, error: 'codebuddy attestation attestedAt/expiresAt must be valid ISO timestamps' };
|
|
425
|
+
}
|
|
426
|
+
if (attestedMs > nowMs) {
|
|
427
|
+
return { valid: false, error: 'codebuddy attestation attestedAt is in the future' };
|
|
428
|
+
}
|
|
429
|
+
if (expiresMs <= attestedMs) {
|
|
430
|
+
return { valid: false, error: 'codebuddy attestation expiresAt must be after attestedAt' };
|
|
431
|
+
}
|
|
432
|
+
if (expiresMs - attestedMs > CODEBUDDY_MAX_ATTESTATION_VALIDITY_MS) {
|
|
433
|
+
return { valid: false, error: 'codebuddy attestation validity must not exceed 24 hours' };
|
|
434
|
+
}
|
|
435
|
+
if (nowMs > expiresMs) {
|
|
436
|
+
return { valid: false, error: 'codebuddy attestation has expired' };
|
|
437
|
+
}
|
|
438
|
+
// Desktop installPath must end with the well-known WorkBuddy marketplace
|
|
439
|
+
// layout (segment-level). The cli channel's isolated-home containment is
|
|
440
|
+
// enforced in the adapter observe branch (needs the context-derived isolated
|
|
441
|
+
// home + realpath), mirroring kimi's observe-side managed-root check.
|
|
442
|
+
if (attestation.installChannel === 'desktop'
|
|
443
|
+
&& !matchesDesktopLayout(attestation.installPath, attestation.marketplace, attestation.plugin)) {
|
|
444
|
+
return { valid: false, error: `codebuddy attestation installPath does not match the WorkBuddy desktop marketplace layout (.workbuddy/plugins/marketplaces/${attestation.marketplace}/plugins/${attestation.plugin})` };
|
|
445
|
+
}
|
|
446
|
+
// CLI installPath must end with the well-known CodeBuddy CLI marketplace
|
|
447
|
+
// layout (segment-level). This is a lexical check; the full realpath
|
|
448
|
+
// containment within the isolated home is enforced in the adapter observe
|
|
449
|
+
// branch (needs the context-derived isolated home + realpath resolution).
|
|
450
|
+
if (attestation.installChannel === 'cli'
|
|
451
|
+
&& !matchesCliLayout(attestation.installPath, attestation.marketplace, attestation.plugin)) {
|
|
452
|
+
return { valid: false, error: `codebuddy attestation installPath does not match the CodeBuddy CLI marketplace layout (.codebuddy/plugins/marketplaces/${attestation.marketplace}/plugins/${attestation.plugin})` };
|
|
453
|
+
}
|
|
454
|
+
return { valid: true, error: null };
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
/**
|
|
458
|
+
* CodeBuddy protocol capability gap: the codebuddy CLI cannot pin a frozen ref,
|
|
459
|
+
* so there is NO trustworthy automated install checkpoint. execute NEVER execs a
|
|
460
|
+
* codebuddy command. Instead it emits an actionable manual-install requirement
|
|
461
|
+
* bound to the real frozen plan digest + identity (naming both install channels
|
|
462
|
+
* and the unified marketplace), and leaves success to observe/verify, which
|
|
463
|
+
* consume only a trusted human attestation plus read-only verification. Without
|
|
464
|
+
* that proof the checkpoint fails closed and can never reach VERIFIED.
|
|
465
|
+
*
|
|
466
|
+
* Isolation model: the cli channel's isolated home is a STABLE, plan-digest-keyed
|
|
467
|
+
* directory under the attestation authority (`<authorityDir>/codebuddy-home`).
|
|
468
|
+
* execute creates the isolated home (so the operator's CLI has a HOME to write
|
|
469
|
+
* into) but never the marketplace install subdir (the operator's CLI install
|
|
470
|
+
* creates that). The desktop channel uses the user's real `~/.workbuddy` and
|
|
471
|
+
* needs no isolated home. The requirement write is idempotent: an identical
|
|
472
|
+
* existing requirement is left untouched, a divergent one fails closed.
|
|
473
|
+
*
|
|
474
|
+
* Referenced from the registry as the codebuddy strategy.buildManualRequirement
|
|
475
|
+
* — the automatable=false manual-requirement path.
|
|
476
|
+
*
|
|
477
|
+
* @param {object} action - expanded codebuddy action (validated params already).
|
|
478
|
+
* @param {object} context - adapter context (root, runDir, plan).
|
|
479
|
+
* @returns {Promise<import('../adapters/contract.mjs').AdapterResult>}
|
|
480
|
+
*/
|
|
481
|
+
export async function executeCodeBuddyManualRequirement(action, context) {
|
|
482
|
+
const actionType = ActionType.CODEBUDDY_MARKETPLACE_INSTALL;
|
|
483
|
+
|
|
484
|
+
// (A) Bind to the REAL frozen plan digest via strict normalized recompute.
|
|
485
|
+
let planDigest;
|
|
486
|
+
try {
|
|
487
|
+
planDigest = await resolveCodeBuddyBoundPlanDigest(context);
|
|
488
|
+
} catch (planErr) {
|
|
489
|
+
return createResult({
|
|
490
|
+
actionType,
|
|
491
|
+
status: ActionStatus.EXECUTE_FAILED,
|
|
492
|
+
error: `cannot bind codebuddy requirement to the frozen plan: ${planErr.message}`,
|
|
493
|
+
});
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
// Validate the frozen timeout. CodeBuddy execs no CLI, but the frozen-timeout
|
|
497
|
+
// fail-closed invariant still holds for every marketplace action.
|
|
498
|
+
try {
|
|
499
|
+
resolveTimeoutMs(action);
|
|
500
|
+
} catch (timeoutErr) {
|
|
501
|
+
return createResult({
|
|
502
|
+
actionType,
|
|
503
|
+
status: ActionStatus.EXECUTE_FAILED,
|
|
504
|
+
error: timeoutErr.message,
|
|
505
|
+
});
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
const ref = action.ref ?? `v${action.version}`;
|
|
509
|
+
|
|
510
|
+
// (B) Stable, plan-digest-keyed authority dir, shared across
|
|
511
|
+
// publish/reconcile/verify run dirs.
|
|
512
|
+
let attestationDir;
|
|
513
|
+
try {
|
|
514
|
+
attestationDir = codebuddyAuthorityDir(context, planDigest, action.plugin);
|
|
515
|
+
} catch (dirErr) {
|
|
516
|
+
return createResult({
|
|
517
|
+
actionType,
|
|
518
|
+
status: ActionStatus.EXECUTE_FAILED,
|
|
519
|
+
error: dirErr.message,
|
|
520
|
+
});
|
|
521
|
+
}
|
|
522
|
+
const cliHome = codebuddyCliHome(attestationDir);
|
|
523
|
+
const cliInstallRoot = codebuddyCliInstallRoot(cliHome, CODEBUDDY_MARKETPLACE_NAME, action.plugin);
|
|
524
|
+
const desktopInstallRoot = `~/${CODEBUDDY_DESKTOP_HOME_DIR}/plugins/marketplaces/${CODEBUDDY_MARKETPLACE_NAME}/plugins/${action.plugin}`;
|
|
525
|
+
|
|
526
|
+
const instructions = buildCodeBuddyManualInstructions({
|
|
527
|
+
plugin: action.plugin,
|
|
528
|
+
version: action.version,
|
|
529
|
+
ref,
|
|
530
|
+
cliHome,
|
|
531
|
+
attestationDir,
|
|
532
|
+
});
|
|
533
|
+
|
|
534
|
+
const requirement = {
|
|
535
|
+
kind: 'codebuddy-manual-install-requirement',
|
|
536
|
+
consumer: 'codebuddy',
|
|
537
|
+
plugin: action.plugin,
|
|
538
|
+
version: action.version,
|
|
539
|
+
entrySkill: action.entrySkill,
|
|
540
|
+
repo: action.repo,
|
|
541
|
+
ref,
|
|
542
|
+
marketplace: CODEBUDDY_MARKETPLACE_NAME,
|
|
543
|
+
marketplaceSource: CODEBUDDY_MARKETPLACE_SOURCE,
|
|
544
|
+
installChannels: ['desktop', 'cli'],
|
|
545
|
+
// (A) planDigest binds to the real frozen plan digest;
|
|
546
|
+
// expectedPayloadDigest binds separately to the snapshot payload digest.
|
|
547
|
+
planDigest,
|
|
548
|
+
expectedPayloadDigest: action.manifestDigest,
|
|
549
|
+
isolatedHome: cliHome,
|
|
550
|
+
codebuddyHome: cliHome,
|
|
551
|
+
cliInstallRoot,
|
|
552
|
+
desktopInstallRoot,
|
|
553
|
+
attestationDir,
|
|
554
|
+
attestationFile: CODEBUDDY_ATTESTATION_FILE,
|
|
555
|
+
attestationTemplate: {
|
|
556
|
+
consumer: 'codebuddy',
|
|
557
|
+
plugin: action.plugin,
|
|
558
|
+
version: action.version,
|
|
559
|
+
entrySkill: action.entrySkill,
|
|
560
|
+
repo: action.repo,
|
|
561
|
+
ref,
|
|
562
|
+
marketplace: CODEBUDDY_MARKETPLACE_NAME,
|
|
563
|
+
installChannel: '<"desktop" or "cli">',
|
|
564
|
+
installPath: '<actual installed plugin directory for the chosen channel>',
|
|
565
|
+
planDigest,
|
|
566
|
+
payloadDigest: action.manifestDigest,
|
|
567
|
+
attestedBy: '<person responsible for the manual install>',
|
|
568
|
+
attestedAt: '<ISO 8601 now; must not be in the future>',
|
|
569
|
+
expiresAt: '<ISO 8601; within 24h of attestedAt>',
|
|
570
|
+
},
|
|
571
|
+
instructions,
|
|
572
|
+
};
|
|
573
|
+
|
|
574
|
+
// Create ONLY the isolated cli home (which transitively creates the authority
|
|
575
|
+
// dir); never pre-create the marketplace install subdir — the operator's CLI
|
|
576
|
+
// install (cli channel) or the WorkBuddy desktop app (desktop channel) owns
|
|
577
|
+
// the actual install directory.
|
|
578
|
+
try {
|
|
579
|
+
await mkdir(cliHome, { recursive: true, mode: 0o700 });
|
|
580
|
+
} catch (mkdirErr) {
|
|
581
|
+
return createResult({
|
|
582
|
+
actionType,
|
|
583
|
+
status: ActionStatus.EXECUTE_FAILED,
|
|
584
|
+
error: `cannot create codebuddy isolated home directory: ${mkdirErr.message}`,
|
|
585
|
+
});
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
// Idempotent requirement write: an identical existing requirement is left
|
|
589
|
+
// untouched; a divergent existing requirement fails closed (never silently
|
|
590
|
+
// overwritten). `createdAt` is volatile and excluded from the comparison.
|
|
591
|
+
const requirementPath = resolve(attestationDir, CODEBUDDY_REQUIREMENT_FILE);
|
|
592
|
+
let existing = null;
|
|
593
|
+
let requirementMissing = false;
|
|
594
|
+
try {
|
|
595
|
+
const existingRaw = await readFile(requirementPath, 'utf8');
|
|
596
|
+
try {
|
|
597
|
+
existing = JSON.parse(existingRaw);
|
|
598
|
+
} catch (parseErr) {
|
|
599
|
+
return createResult({
|
|
600
|
+
actionType,
|
|
601
|
+
status: ActionStatus.EXECUTE_FAILED,
|
|
602
|
+
error: `existing codebuddy manual-install requirement is invalid JSON; refusing to overwrite: ${parseErr.message}`,
|
|
603
|
+
});
|
|
604
|
+
}
|
|
605
|
+
} catch (readErr) {
|
|
606
|
+
if (readErr?.code === 'ENOENT') {
|
|
607
|
+
requirementMissing = true;
|
|
608
|
+
} else {
|
|
609
|
+
return createResult({
|
|
610
|
+
actionType,
|
|
611
|
+
status: ActionStatus.EXECUTE_FAILED,
|
|
612
|
+
error: `existing codebuddy manual-install requirement cannot be read; refusing to overwrite: ${readErr.message}`,
|
|
613
|
+
});
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
if (!requirementMissing) {
|
|
617
|
+
if (!existing || typeof existing !== 'object' || Array.isArray(existing)) {
|
|
618
|
+
return createResult({
|
|
619
|
+
actionType,
|
|
620
|
+
status: ActionStatus.EXECUTE_FAILED,
|
|
621
|
+
error: 'existing codebuddy manual-install requirement is not an object; refusing to overwrite',
|
|
622
|
+
});
|
|
623
|
+
}
|
|
624
|
+
const { createdAt: _existingCreatedAt, ...existingBody } = existing;
|
|
625
|
+
if (canonicalJson(existingBody) !== canonicalJson(requirement)) {
|
|
626
|
+
return createResult({
|
|
627
|
+
actionType,
|
|
628
|
+
status: ActionStatus.EXECUTE_FAILED,
|
|
629
|
+
error: 'existing codebuddy manual-install requirement conflicts with the current frozen action; refusing to overwrite',
|
|
630
|
+
});
|
|
631
|
+
}
|
|
632
|
+
} else {
|
|
633
|
+
await writeEvidenceAtomic(requirementPath, { ...requirement, createdAt: new Date().toISOString() });
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
return createResult({
|
|
637
|
+
actionType,
|
|
638
|
+
status: ActionStatus.EXECUTED,
|
|
639
|
+
observation: {
|
|
640
|
+
installed: false,
|
|
641
|
+
manualInstallRequired: true,
|
|
642
|
+
consumer: 'codebuddy',
|
|
643
|
+
plugin: action.plugin,
|
|
644
|
+
version: action.version,
|
|
645
|
+
entrySkill: action.entrySkill,
|
|
646
|
+
repo: action.repo,
|
|
647
|
+
ref,
|
|
648
|
+
marketplace: CODEBUDDY_MARKETPLACE_NAME,
|
|
649
|
+
installChannels: ['desktop', 'cli'],
|
|
650
|
+
planDigest,
|
|
651
|
+
attestationDir,
|
|
652
|
+
codebuddyHome: cliHome,
|
|
653
|
+
cliInstallRoot,
|
|
654
|
+
desktopInstallRoot,
|
|
655
|
+
instructions,
|
|
656
|
+
},
|
|
657
|
+
});
|
|
658
|
+
}
|