@vellumai/assistant 0.8.11 → 0.8.12-staging.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ARCHITECTURE.md +15 -17
- package/README.md +0 -6
- package/bun.lock +6 -122
- package/node_modules/@vellumai/gateway-client/bun.lock +1 -0
- package/node_modules/@vellumai/gateway-client/package.json +3 -1
- package/node_modules/@vellumai/gateway-client/src/__tests__/gateway-client.test.ts +1 -1
- package/node_modules/@vellumai/gateway-client/src/gateway-ipc-contracts.ts +87 -0
- package/node_modules/@vellumai/gateway-client/src/index.ts +3 -5
- package/openapi.yaml +126 -4
- package/package.json +1 -3
- package/src/__tests__/adaptive-thinking-repair.test.ts +185 -0
- package/src/__tests__/agent-loop-compaction-events.test.ts +7 -6
- package/src/__tests__/anthropic-provider.test.ts +129 -0
- package/src/__tests__/background-workers-disk-pressure.test.ts +4 -1
- package/src/__tests__/btw-routes.test.ts +7 -34
- package/src/__tests__/checker.test.ts +6 -12
- package/src/__tests__/config-loader-backfill.test.ts +4 -2
- package/src/__tests__/config-loader-quarantine-notice.test.ts +167 -0
- package/src/__tests__/config-watcher.test.ts +2 -2
- package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +1 -1
- package/src/__tests__/conversation-error.test.ts +2 -6
- package/src/__tests__/conversation-history-web-search.test.ts +8 -0
- package/src/__tests__/conversation-title-service.test.ts +2 -1
- package/src/__tests__/credential-security-invariants.test.ts +1 -1
- package/src/__tests__/disk-pressure-tools.test.ts +1 -1
- package/src/__tests__/exploration-drift-hook.test.ts +692 -0
- package/src/__tests__/filing-service.test.ts +8 -3
- package/src/__tests__/guardian-action-store.test.ts +0 -167
- package/src/__tests__/handlers-skills-memory-v2-reseed.test.ts +1 -1
- package/src/__tests__/heartbeat-disk-pressure.test.ts +4 -1
- package/src/__tests__/heartbeat-service.test.ts +5 -2
- package/src/__tests__/identity-intro-cache.test.ts +12 -5
- package/src/__tests__/identity-routes.test.ts +16 -57
- package/src/__tests__/injector-chain.test.ts +8 -3
- package/src/__tests__/injector-config-quarantine-notice.test.ts +115 -0
- package/src/__tests__/llm-usage-store.test.ts +11 -0
- package/src/__tests__/memory-v2-static-injector.test.ts +22 -0
- package/src/__tests__/model-intents.test.ts +1 -1
- package/src/__tests__/oauth-cli.test.ts +19 -8
- package/src/__tests__/openai-provider.test.ts +34 -0
- package/src/__tests__/prechat-onboarding-contract.test.ts +0 -1
- package/src/__tests__/recurrence-engine.test.ts +45 -0
- package/src/__tests__/schedule-routes.test.ts +34 -0
- package/src/__tests__/scheduler-disk-pressure.test.ts +1 -1
- package/src/__tests__/script-proxy-conversation-manager.test.ts +10 -5
- package/src/__tests__/skill-tool-factory.test.ts +49 -0
- package/src/__tests__/subagent-role-registry.test.ts +24 -1
- package/src/__tests__/subagent-tools.test.ts +1 -0
- package/src/__tests__/system-prompt.test.ts +109 -11
- package/src/__tests__/tool-error-hook.test.ts +1 -0
- package/src/__tests__/tool-result-spool.test.ts +337 -0
- package/src/__tests__/tool-result-truncate-hook.test.ts +1 -0
- package/src/__tests__/validate-input.test.ts +95 -1
- package/src/__tests__/workspace-migration-098-remove-stale-updates-bulletin-file.test.ts +65 -0
- package/src/__tests__/workspace-migration-099-disable-cache-one-shot-callsites.test.ts +139 -0
- package/src/__tests__/workspace-release-notes-feature-flag-guard.test.ts +45 -95
- package/src/agent/loop.ts +81 -24
- package/src/api/events/usage-progress.ts +28 -0
- package/src/api/index.ts +6 -0
- package/src/background-wake/wake-intent-hooks.test.ts +2 -0
- package/src/bundler/app-bundler.ts +25 -42
- package/src/calls/call-controller.ts +1 -1
- package/src/cli/commands/plugins.ts +248 -15
- package/src/cli/lib/__tests__/inspect-plugin.test.ts +318 -0
- package/src/cli/lib/__tests__/install-from-github.test.ts +16 -9
- package/src/cli/lib/__tests__/plugin-artifact.test.ts +183 -0
- package/src/cli/lib/__tests__/plugin-details.test.ts +158 -0
- package/src/cli/lib/__tests__/plugin-fingerprint.test.ts +245 -0
- package/src/cli/lib/__tests__/upgrade-plugin.test.ts +301 -0
- package/src/cli/lib/inspect-plugin.ts +252 -0
- package/src/cli/lib/install-from-github.ts +214 -21
- package/src/cli/lib/list-installed-plugins.ts +17 -6
- package/src/cli/lib/plugin-artifact.ts +103 -0
- package/src/cli/lib/plugin-details.ts +18 -1
- package/src/cli/lib/plugin-fingerprint.ts +197 -0
- package/src/cli/lib/upgrade-plugin.ts +219 -0
- package/src/config/bundled-skills/subagent/SKILL.md +2 -0
- package/src/config/bundled-skills/subagent/TOOLS.json +8 -2
- package/src/config/call-site-defaults.ts +13 -2
- package/src/config/feature-flag-registry.json +8 -16
- package/src/config/loader.ts +52 -59
- package/src/config/schema.ts +0 -2
- package/src/config/schemas/__tests__/memory-v2.test.ts +1 -0
- package/src/config/schemas/__tests__/memory-v3.test.ts +10 -0
- package/src/config/schemas/llm.ts +10 -0
- package/src/config/schemas/memory-v2.ts +13 -0
- package/src/config/schemas/memory-v3.ts +92 -0
- package/src/context/post-turn-tool-result-truncation.ts +32 -18
- package/src/context/tool-result-spool.ts +104 -0
- package/src/credential-execution/feature-gates.ts +0 -1
- package/src/daemon/conversation-agent-loop-handlers.ts +41 -16
- package/src/daemon/conversation-error.ts +6 -15
- package/src/daemon/conversation.ts +9 -0
- package/src/daemon/disk-pressure-policy.ts +0 -1
- package/src/daemon/lifecycle.ts +1 -20
- package/src/daemon/message-types/conversations.ts +2 -15
- package/src/daemon/trust-context.ts +1 -1
- package/src/heartbeat/__tests__/heartbeat-service.test.ts +1 -1
- package/src/home/__tests__/home-content-refresh.test.ts +114 -0
- package/src/home/__tests__/suggested-prompts.test.ts +86 -5
- package/src/home/home-content-refresh.ts +43 -31
- package/src/home/home-greeting-cache.ts +8 -1
- package/src/home/home-greeting.ts +13 -9
- package/src/home/suggested-prompts.ts +77 -24
- package/src/ipc/routes/trust-rules.test.ts +66 -72
- package/src/media/image-credentials.ts +2 -2
- package/src/memory/__tests__/compaction-log-store-clickhouse.test.ts +432 -0
- package/src/memory/{compaction-log-writer-clickhouse.ts → compaction-log-store-clickhouse.ts} +264 -55
- package/src/memory/conversation-attention-store.ts +1 -0
- package/src/memory/conversation-bootstrap.ts +18 -9
- package/src/memory/conversation-crud.ts +12 -2
- package/src/memory/conversation-title-service.ts +53 -9
- package/src/memory/delivery-channels.ts +0 -69
- package/src/memory/graph/extraction-job.ts +0 -15
- package/src/memory/guardian-action-store.ts +1 -376
- package/src/memory/llm-usage-store.ts +5 -1
- package/src/memory/migrations/181-rename-thread-starters-checkpoints.ts +2 -2
- package/src/memory/v2/__tests__/consolidation-job.test.ts +183 -2
- package/src/memory/v2/__tests__/injection.test.ts +70 -0
- package/src/memory/v2/__tests__/static-context.test.ts +12 -0
- package/src/memory/v2/consolidation-job.ts +93 -9
- package/src/memory/v2/injection.ts +53 -0
- package/src/memory/v2/prompts/consolidation.ts +1 -0
- package/src/memory/v2/static-context.ts +13 -1
- package/src/memory/v2/sweep-job.ts +1 -1
- package/src/memory/v2/types.ts +5 -0
- package/src/plugin-api/types.ts +7 -0
- package/src/plugins/defaults/exploration-drift/hooks/post-tool-use.ts +300 -0
- package/src/plugins/defaults/exploration-drift/package.json +15 -0
- package/src/plugins/defaults/index.ts +25 -0
- package/src/plugins/defaults/memory-retrieval/injectors.ts +132 -4
- package/src/plugins/defaults/memory-v3-shadow/__tests__/card.test.ts +92 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/carry-integration.test.ts +2 -1
- package/src/plugins/defaults/memory-v3-shadow/__tests__/fresh-set.test.ts +52 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/injection.test.ts +1 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/live-integration.test.ts +2 -1
- package/src/plugins/defaults/memory-v3-shadow/__tests__/orchestrate.test.ts +136 -5
- package/src/plugins/defaults/memory-v3-shadow/__tests__/pool-select.test.ts +17 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/selection-log-store.test.ts +6 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-integration.test.ts +5 -1
- package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-plugin.test.ts +68 -4
- package/src/plugins/defaults/memory-v3-shadow/card.ts +49 -5
- package/src/plugins/defaults/memory-v3-shadow/fresh-set.ts +59 -0
- package/src/plugins/defaults/memory-v3-shadow/injector.ts +4 -2
- package/src/plugins/defaults/memory-v3-shadow/learned-edges.test.ts +169 -0
- package/src/plugins/defaults/memory-v3-shadow/learned-edges.ts +178 -0
- package/src/plugins/defaults/memory-v3-shadow/orchestrate.ts +115 -26
- package/src/plugins/defaults/memory-v3-shadow/pool-select.ts +13 -9
- package/src/plugins/defaults/memory-v3-shadow/shadow-plugin.ts +144 -22
- package/src/plugins/defaults/memory-v3-shadow/types.ts +24 -6
- package/src/plugins/defaults/title-generate/hooks/stop.ts +13 -0
- package/src/plugins/defaults/title-generate/hooks/user-prompt-submit.ts +16 -0
- package/src/prompts/cache-boundary.ts +17 -0
- package/src/prompts/sections.ts +50 -17
- package/src/prompts/system-prompt.ts +12 -4
- package/src/prompts/templates/system-sections.ts +22 -0
- package/src/providers/anthropic/client.ts +74 -28
- package/src/providers/gemini/client.ts +5 -1
- package/src/providers/minimax/client.ts +9 -0
- package/src/providers/model-intents.ts +2 -2
- package/src/providers/openai/chat-completions-provider.ts +2 -1
- package/src/providers/openai/responses-provider.ts +5 -1
- package/src/providers/retry.ts +8 -0
- package/src/providers/types.ts +11 -0
- package/src/runtime/AGENTS.md +6 -0
- package/src/runtime/__tests__/agent-wake.test.ts +2 -2
- package/src/runtime/agent-wake.ts +5 -5
- package/src/runtime/background-job-runner.ts +2 -2
- package/src/runtime/migrations/__tests__/vbundle-legacy-user-md.test.ts +150 -3
- package/src/runtime/migrations/vbundle-import-analyzer.ts +29 -6
- package/src/runtime/migrations/vbundle-import-policy.ts +23 -0
- package/src/runtime/migrations/vbundle-importer.ts +9 -4
- package/src/runtime/migrations/vbundle-streaming-importer.ts +8 -3
- package/src/runtime/pre-first-message-gate.ts +1 -1
- package/src/runtime/routes/__tests__/conversation-compaction-routes.test.ts +241 -0
- package/src/runtime/routes/__tests__/gateway-log-routes.test.ts +97 -185
- package/src/runtime/routes/__tests__/home-feed-routes.test.ts +17 -0
- package/src/runtime/routes/__tests__/plugins-routes.test.ts +1 -0
- package/src/runtime/routes/__tests__/task-routes.test.ts +3 -3
- package/src/runtime/routes/btw-routes.ts +0 -14
- package/src/runtime/routes/conversation-compaction-routes.ts +86 -19
- package/src/runtime/routes/conversation-list-routes.ts +77 -5
- package/src/runtime/routes/conversation-management-routes.ts +54 -0
- package/src/runtime/routes/gateway-log-routes.ts +14 -64
- package/src/runtime/routes/home-feed-routes.ts +10 -0
- package/src/runtime/routes/identity-intro-cache.ts +1 -1
- package/src/runtime/routes/identity-routes.ts +76 -20
- package/src/runtime/routes/inbound-message-handler.ts +0 -36
- package/src/runtime/routes/plugins-routes.ts +21 -0
- package/src/runtime/routes/schedule-routes.ts +19 -2
- package/src/runtime/routes/trust-rules-routes.ts +14 -67
- package/src/schedule/recurrence-engine.ts +34 -0
- package/src/schedule/scheduler.ts +1 -0
- package/src/skills/validate-input.ts +41 -1
- package/src/subagent/types.ts +26 -1
- package/src/telemetry/types.ts +15 -1
- package/src/telemetry/usage-telemetry-reporter.test.ts +6 -1
- package/src/telemetry/usage-telemetry-reporter.ts +1 -0
- package/src/tools/apps/executors.ts +1 -1
- package/src/tools/skills/skill-tool-factory.ts +19 -8
- package/src/usage/types.ts +8 -1
- package/src/util/platform.ts +16 -0
- package/src/watcher/engine.ts +1 -0
- package/src/workspace/adaptive-thinking-repair.ts +113 -0
- package/src/workspace/migrations/097-enable-adaptive-thinking-managed-profiles.ts +70 -67
- package/src/workspace/migrations/098-remove-stale-updates-bulletin-file.ts +31 -0
- package/src/workspace/migrations/099-disable-cache-one-shot-callsites.ts +81 -0
- package/src/workspace/migrations/registry.ts +4 -0
- package/src/__tests__/config-loader-quarantine-bulletin.test.ts +0 -202
- package/src/__tests__/conversation-starters-cadence.test.ts +0 -161
- package/src/__tests__/guardian-action-followup-executor.test.ts +0 -322
- package/src/__tests__/guardian-action-followup-store.test.ts +0 -373
- package/src/__tests__/guardian-action-late-reply.test.ts +0 -1083
- package/src/__tests__/update-bulletin-job.test.ts +0 -292
- package/src/config/schemas/updates.ts +0 -14
- package/src/memory/__tests__/compaction-log-writer-clickhouse.test.ts +0 -227
- package/src/memory/conversation-starters-cadence.ts +0 -78
- package/src/prompts/update-bulletin-job.ts +0 -180
- package/src/runtime/guardian-action-followup-executor.ts +0 -306
|
@@ -10,6 +10,11 @@
|
|
|
10
10
|
import type { Command } from "commander";
|
|
11
11
|
|
|
12
12
|
import { confirmPrompt } from "../lib/confirm-prompt.js";
|
|
13
|
+
import {
|
|
14
|
+
inspectPlugin,
|
|
15
|
+
type PluginInspection,
|
|
16
|
+
PluginInspectNotFoundError,
|
|
17
|
+
} from "../lib/inspect-plugin.js";
|
|
13
18
|
import {
|
|
14
19
|
DEFAULT_PLUGIN_REF,
|
|
15
20
|
installPlugin,
|
|
@@ -18,6 +23,7 @@ import {
|
|
|
18
23
|
PluginNotFoundError,
|
|
19
24
|
} from "../lib/install-from-github.js";
|
|
20
25
|
import { listInstalledPlugins } from "../lib/list-installed-plugins.js";
|
|
26
|
+
import type { FingerprintComparison } from "../lib/plugin-fingerprint.js";
|
|
21
27
|
import { registerCommand } from "../lib/register-command.js";
|
|
22
28
|
import {
|
|
23
29
|
InvalidSearchPatternError,
|
|
@@ -27,6 +33,11 @@ import {
|
|
|
27
33
|
PluginNotInstalledError,
|
|
28
34
|
uninstallPlugin,
|
|
29
35
|
} from "../lib/uninstall-plugin.js";
|
|
36
|
+
import {
|
|
37
|
+
PluginNotUpgradableError,
|
|
38
|
+
type PluginUpgradeResult,
|
|
39
|
+
upgradePlugin,
|
|
40
|
+
} from "../lib/upgrade-plugin.js";
|
|
30
41
|
import { getCliLogger } from "../logger.js";
|
|
31
42
|
|
|
32
43
|
const log = getCliLogger("plugins");
|
|
@@ -41,15 +52,19 @@ export function registerPluginsCommand(program: Command): void {
|
|
|
41
52
|
"after",
|
|
42
53
|
`
|
|
43
54
|
Examples:
|
|
44
|
-
$ assistant plugins install
|
|
45
|
-
$ assistant plugins install
|
|
46
|
-
$ assistant plugins install
|
|
55
|
+
$ assistant plugins install example
|
|
56
|
+
$ assistant plugins install example --force
|
|
57
|
+
$ assistant plugins install example --ref my-feature-branch
|
|
47
58
|
$ assistant plugins list
|
|
48
59
|
$ assistant plugins list --json
|
|
49
|
-
$ assistant plugins
|
|
50
|
-
$ assistant plugins
|
|
51
|
-
$ assistant plugins
|
|
52
|
-
$ assistant plugins
|
|
60
|
+
$ assistant plugins inspect example
|
|
61
|
+
$ assistant plugins inspect example --json
|
|
62
|
+
$ assistant plugins upgrade example
|
|
63
|
+
$ assistant plugins upgrade example --dry-run
|
|
64
|
+
$ assistant plugins search example
|
|
65
|
+
$ assistant plugins search "^example"
|
|
66
|
+
$ assistant plugins search example --json
|
|
67
|
+
$ assistant plugins uninstall example`,
|
|
53
68
|
);
|
|
54
69
|
|
|
55
70
|
plugins
|
|
@@ -147,6 +162,55 @@ Examples:
|
|
|
147
162
|
);
|
|
148
163
|
});
|
|
149
164
|
|
|
165
|
+
plugins
|
|
166
|
+
.command("inspect <name>")
|
|
167
|
+
.description(
|
|
168
|
+
"Show a plugin's local install metadata and the marketplace pin, and whether an update is available",
|
|
169
|
+
)
|
|
170
|
+
.option("--json", "Emit machine-readable JSON instead of a summary")
|
|
171
|
+
.action(async (name: string, opts: { json?: boolean }) => {
|
|
172
|
+
try {
|
|
173
|
+
const inspection = await inspectPlugin(
|
|
174
|
+
{ name },
|
|
175
|
+
{ fetch: globalThis.fetch.bind(globalThis) },
|
|
176
|
+
);
|
|
177
|
+
|
|
178
|
+
if (opts.json) {
|
|
179
|
+
process.stdout.write(JSON.stringify(inspection, null, 2) + "\n");
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Logged after the JSON early-return: the CLI logger writes
|
|
184
|
+
// info to stdout, which would otherwise corrupt --json output.
|
|
185
|
+
log.info(
|
|
186
|
+
{
|
|
187
|
+
name: inspection.name,
|
|
188
|
+
installed: inspection.installed,
|
|
189
|
+
status: inspection.status,
|
|
190
|
+
},
|
|
191
|
+
"plugin inspect",
|
|
192
|
+
);
|
|
193
|
+
|
|
194
|
+
for (const line of formatInspection(inspection)) {
|
|
195
|
+
console.log(line);
|
|
196
|
+
}
|
|
197
|
+
} catch (err) {
|
|
198
|
+
if (err instanceof PluginInspectNotFoundError) {
|
|
199
|
+
console.error(err.message);
|
|
200
|
+
process.exitCode = 1;
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
if (err instanceof InvalidPluginNameError) {
|
|
204
|
+
console.error(err.message);
|
|
205
|
+
process.exitCode = 1;
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
209
|
+
console.error(`Plugin inspect failed: ${message}`);
|
|
210
|
+
process.exitCode = 1;
|
|
211
|
+
}
|
|
212
|
+
});
|
|
213
|
+
|
|
150
214
|
plugins
|
|
151
215
|
.command("search <query>")
|
|
152
216
|
.description(
|
|
@@ -160,9 +224,13 @@ Examples:
|
|
|
160
224
|
{ fetch: globalThis.fetch.bind(globalThis) },
|
|
161
225
|
);
|
|
162
226
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
227
|
+
if (opts.json) {
|
|
228
|
+
process.stdout.write(JSON.stringify(result, null, 2) + "\n");
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// Logged after the JSON early-return: the CLI logger writes info
|
|
233
|
+
// to stdout, which would otherwise corrupt --json output.
|
|
166
234
|
log.info(
|
|
167
235
|
{
|
|
168
236
|
query: result.query,
|
|
@@ -172,11 +240,6 @@ Examples:
|
|
|
172
240
|
"external plugin search",
|
|
173
241
|
);
|
|
174
242
|
|
|
175
|
-
if (opts.json) {
|
|
176
|
-
process.stdout.write(JSON.stringify(result, null, 2) + "\n");
|
|
177
|
-
return;
|
|
178
|
-
}
|
|
179
|
-
|
|
180
243
|
if (result.matches.length === 0) {
|
|
181
244
|
console.log(`No plugins matched "${result.query}".`);
|
|
182
245
|
return;
|
|
@@ -253,6 +316,176 @@ Examples:
|
|
|
253
316
|
process.exitCode = 1;
|
|
254
317
|
}
|
|
255
318
|
});
|
|
319
|
+
|
|
320
|
+
plugins
|
|
321
|
+
.command("upgrade <name>")
|
|
322
|
+
.description(
|
|
323
|
+
"Upgrade an installed plugin to the marketplace's current pin",
|
|
324
|
+
)
|
|
325
|
+
.option(
|
|
326
|
+
"--dry-run",
|
|
327
|
+
"Show what would change without modifying the install",
|
|
328
|
+
)
|
|
329
|
+
.option("--json", "Emit machine-readable JSON instead of a summary")
|
|
330
|
+
.action(
|
|
331
|
+
async (name: string, opts: { dryRun?: boolean; json?: boolean }) => {
|
|
332
|
+
try {
|
|
333
|
+
const result = await upgradePlugin(
|
|
334
|
+
{ name, dryRun: opts.dryRun },
|
|
335
|
+
{ fetch: globalThis.fetch.bind(globalThis) },
|
|
336
|
+
);
|
|
337
|
+
|
|
338
|
+
if (opts.json) {
|
|
339
|
+
process.stdout.write(JSON.stringify(result, null, 2) + "\n");
|
|
340
|
+
return;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
// Logged after the JSON early-return: the CLI logger writes
|
|
344
|
+
// info to stdout, which would otherwise corrupt --json output.
|
|
345
|
+
log.info(
|
|
346
|
+
{
|
|
347
|
+
name: result.name,
|
|
348
|
+
outcome: result.outcome,
|
|
349
|
+
from: result.fromCommit,
|
|
350
|
+
to: result.toCommit,
|
|
351
|
+
},
|
|
352
|
+
"plugin upgrade",
|
|
353
|
+
);
|
|
354
|
+
|
|
355
|
+
for (const line of formatUpgrade(result)) {
|
|
356
|
+
console.log(line);
|
|
357
|
+
}
|
|
358
|
+
} catch (err) {
|
|
359
|
+
if (
|
|
360
|
+
err instanceof PluginNotInstalledError ||
|
|
361
|
+
err instanceof PluginNotUpgradableError ||
|
|
362
|
+
err instanceof InvalidPluginNameError
|
|
363
|
+
) {
|
|
364
|
+
console.error(err.message);
|
|
365
|
+
process.exitCode = 1;
|
|
366
|
+
return;
|
|
367
|
+
}
|
|
368
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
369
|
+
console.error(`Plugin upgrade failed: ${message}`);
|
|
370
|
+
process.exitCode = 1;
|
|
371
|
+
}
|
|
372
|
+
},
|
|
373
|
+
);
|
|
256
374
|
},
|
|
257
375
|
});
|
|
258
376
|
}
|
|
377
|
+
|
|
378
|
+
/** Abbreviate a commit SHA for display; passes through non-SHA / null values. */
|
|
379
|
+
function shortSha(sha: string | null): string {
|
|
380
|
+
return sha ? sha.slice(0, 7) : "—";
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
/** Human-readable status line for an inspection result. */
|
|
384
|
+
function statusLine(inspection: PluginInspection): string {
|
|
385
|
+
const { status, local, remote } = inspection;
|
|
386
|
+
switch (status) {
|
|
387
|
+
case "up-to-date":
|
|
388
|
+
return "up to date";
|
|
389
|
+
case "update-available":
|
|
390
|
+
return `update available (${shortSha(local?.commit ?? null)} → ${shortSha(
|
|
391
|
+
remote?.commit ?? null,
|
|
392
|
+
)})`;
|
|
393
|
+
case "not-installed":
|
|
394
|
+
return "not installed";
|
|
395
|
+
case "not-in-marketplace":
|
|
396
|
+
return "installed (not in marketplace)";
|
|
397
|
+
case "unknown-provenance":
|
|
398
|
+
return "unknown — reinstall to record provenance";
|
|
399
|
+
case "remote-unavailable":
|
|
400
|
+
return "installed (marketplace unavailable)";
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
/**
|
|
405
|
+
* Summarize local edits relative to the install-time fingerprint. Reports
|
|
406
|
+
* "unknown" when no baseline was recorded (an older or manually-copied
|
|
407
|
+
* install), "none" when the on-disk tree matches, or a per-category count.
|
|
408
|
+
*/
|
|
409
|
+
function localEditsLine(changes: FingerprintComparison | null): string {
|
|
410
|
+
if (!changes)
|
|
411
|
+
return "unknown (no recorded baseline; reinstall to record one)";
|
|
412
|
+
if (changes.clean) return "none";
|
|
413
|
+
const parts = [
|
|
414
|
+
`${changes.modified.length} modified`,
|
|
415
|
+
`${changes.added.length} added`,
|
|
416
|
+
`${changes.removed.length} removed`,
|
|
417
|
+
];
|
|
418
|
+
return parts.join(", ");
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
/** Render an inspection as aligned, human-readable summary lines. */
|
|
422
|
+
function formatInspection(inspection: PluginInspection): string[] {
|
|
423
|
+
const { name, local, remote, remoteError } = inspection;
|
|
424
|
+
const lines: string[] = [`${name} plugin`];
|
|
425
|
+
const row = (label: string, value: string) =>
|
|
426
|
+
lines.push(` ${label.padEnd(11)} ${value}`);
|
|
427
|
+
|
|
428
|
+
row("status", statusLine(inspection));
|
|
429
|
+
|
|
430
|
+
if (local) {
|
|
431
|
+
const installedAt = local.installedAt
|
|
432
|
+
? ` (${local.installedAt.slice(0, 10)})`
|
|
433
|
+
: "";
|
|
434
|
+
row("installed", `${shortSha(local.commit)}${installedAt}`);
|
|
435
|
+
row("local edits", localEditsLine(local.localChanges));
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
if (remote) {
|
|
439
|
+
const where = remote.path ? `${remote.repo}/${remote.path}` : remote.repo;
|
|
440
|
+
row(
|
|
441
|
+
"remote pin",
|
|
442
|
+
`${shortSha(remote.commit)} (${where}, ${remote.marketplaceRef})`,
|
|
443
|
+
);
|
|
444
|
+
} else if (remoteError) {
|
|
445
|
+
row("remote", `unavailable — ${remoteError}`);
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
const version = local?.version ?? null;
|
|
449
|
+
if (version) row("version", version);
|
|
450
|
+
|
|
451
|
+
const license = remote?.license ?? null;
|
|
452
|
+
const homepage = remote?.homepage ?? null;
|
|
453
|
+
if (license && homepage) row("license", `${license} homepage ${homepage}`);
|
|
454
|
+
else if (license) row("license", license);
|
|
455
|
+
else if (homepage) row("homepage", homepage);
|
|
456
|
+
|
|
457
|
+
const description = remote?.description ?? local?.description ?? null;
|
|
458
|
+
if (description) row("description", description);
|
|
459
|
+
|
|
460
|
+
for (const issue of local?.issues ?? []) row("issue", issue);
|
|
461
|
+
|
|
462
|
+
return lines;
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
/** Render an upgrade result as human-readable summary lines. */
|
|
466
|
+
function formatUpgrade(result: PluginUpgradeResult): string[] {
|
|
467
|
+
const { name, fromCommit, toCommit } = result;
|
|
468
|
+
const move = `${shortSha(fromCommit)} → ${shortSha(toCommit)}`;
|
|
469
|
+
const provenanceNote = result.provenanceWasUnknown
|
|
470
|
+
? " (previous install had no recorded provenance)"
|
|
471
|
+
: "";
|
|
472
|
+
|
|
473
|
+
switch (result.outcome) {
|
|
474
|
+
case "already-up-to-date":
|
|
475
|
+
return [`"${name}" is already up to date (${shortSha(toCommit)}).`];
|
|
476
|
+
case "would-upgrade":
|
|
477
|
+
return [
|
|
478
|
+
`"${name}" would upgrade ${move}${provenanceNote} (dry run; no changes made).`,
|
|
479
|
+
];
|
|
480
|
+
case "upgraded": {
|
|
481
|
+
const count =
|
|
482
|
+
result.fileCount === null
|
|
483
|
+
? ""
|
|
484
|
+
: ` (${result.fileCount} file${result.fileCount === 1 ? "" : "s"})`;
|
|
485
|
+
return [
|
|
486
|
+
`Upgraded "${name}" ${move}${count}${provenanceNote} → ${result.target}`,
|
|
487
|
+
"Restart the assistant to pick up the upgrade.",
|
|
488
|
+
];
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
}
|
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for {@link inspectPlugin}.
|
|
3
|
+
*
|
|
4
|
+
* The marketplace fetch is replaced with an in-memory fixture passed via the
|
|
5
|
+
* `fetch` dependency, and the installed-copy path is exercised against a real
|
|
6
|
+
* temp directory passed via `workspacePluginsDir` — no globals are monkeypatched.
|
|
7
|
+
* Drift is the exact comparison of the provenance sidecar's commit against the
|
|
8
|
+
* marketplace pin, so the fixtures vary those two SHAs.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
12
|
+
import { tmpdir } from "node:os";
|
|
13
|
+
import { join } from "node:path";
|
|
14
|
+
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
|
|
15
|
+
|
|
16
|
+
import {
|
|
17
|
+
inspectPlugin,
|
|
18
|
+
PluginInspectNotFoundError,
|
|
19
|
+
} from "../inspect-plugin.js";
|
|
20
|
+
import type { FetchLike } from "../install-from-github.js";
|
|
21
|
+
import { computeFingerprint } from "../plugin-fingerprint.js";
|
|
22
|
+
|
|
23
|
+
const SHA_A = "a".repeat(40);
|
|
24
|
+
const SHA_B = "b".repeat(40);
|
|
25
|
+
|
|
26
|
+
/** A marketplace manifest object with a single entry for `name`. */
|
|
27
|
+
function manifestWith(name: string, ref: string): unknown {
|
|
28
|
+
return {
|
|
29
|
+
name: "vellum",
|
|
30
|
+
plugins: [
|
|
31
|
+
{
|
|
32
|
+
name,
|
|
33
|
+
source: { source: "github", repo: `example-org/${name}`, ref },
|
|
34
|
+
description: "A test plugin.",
|
|
35
|
+
category: "developer",
|
|
36
|
+
homepage: `https://github.com/example-org/${name}`,
|
|
37
|
+
license: "MIT",
|
|
38
|
+
},
|
|
39
|
+
],
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Build a `fetch` that serves the marketplace manifest. `marketplace:
|
|
45
|
+
* undefined` answers 404 (empty catalog); `fail: true` rejects with a network
|
|
46
|
+
* error. Anything else surfaces a 500 so test bugs are loud.
|
|
47
|
+
*/
|
|
48
|
+
function makeFetch(opts: { marketplace?: unknown; fail?: boolean }): FetchLike {
|
|
49
|
+
return (async (input: RequestInfo | URL) => {
|
|
50
|
+
const url = typeof input === "string" ? input : input.toString();
|
|
51
|
+
if (url.includes("marketplace.json")) {
|
|
52
|
+
if (opts.fail) throw new Error("network down");
|
|
53
|
+
if (opts.marketplace === undefined) {
|
|
54
|
+
return new Response("not found", { status: 404 });
|
|
55
|
+
}
|
|
56
|
+
return new Response(JSON.stringify(opts.marketplace), { status: 200 });
|
|
57
|
+
}
|
|
58
|
+
return new Response("unexpected url: " + url, { status: 500 });
|
|
59
|
+
}) as FetchLike;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** Materialize an installed plugin copy with an optional provenance sidecar. */
|
|
63
|
+
function installPlugin(
|
|
64
|
+
workspace: string,
|
|
65
|
+
name: string,
|
|
66
|
+
opts: {
|
|
67
|
+
version?: string;
|
|
68
|
+
sidecar?: { commit?: string | null; ref?: string } | null;
|
|
69
|
+
/** Embed a content fingerprint of the materialized tree in the sidecar. */
|
|
70
|
+
fingerprint?: boolean;
|
|
71
|
+
} = {},
|
|
72
|
+
): void {
|
|
73
|
+
const dir = join(workspace, name);
|
|
74
|
+
mkdirSync(dir, { recursive: true });
|
|
75
|
+
writeFileSync(
|
|
76
|
+
join(dir, "package.json"),
|
|
77
|
+
JSON.stringify({
|
|
78
|
+
name,
|
|
79
|
+
version: opts.version ?? "0.1.0",
|
|
80
|
+
description: "Installed copy.",
|
|
81
|
+
}),
|
|
82
|
+
);
|
|
83
|
+
if (opts.sidecar !== null) {
|
|
84
|
+
const sidecar = opts.sidecar ?? { commit: SHA_A };
|
|
85
|
+
// Mirror install: fingerprint the tree before the sidecar is written so it
|
|
86
|
+
// is not part of its own baseline.
|
|
87
|
+
const fingerprint = opts.fingerprint
|
|
88
|
+
? computeFingerprint(dir, ["install-meta.json"])
|
|
89
|
+
: undefined;
|
|
90
|
+
writeFileSync(
|
|
91
|
+
join(dir, "install-meta.json"),
|
|
92
|
+
JSON.stringify({
|
|
93
|
+
origin: "vellum",
|
|
94
|
+
name,
|
|
95
|
+
source: {
|
|
96
|
+
kind: "github",
|
|
97
|
+
owner: "example-org",
|
|
98
|
+
repo: name,
|
|
99
|
+
ref: sidecar.ref ?? sidecar.commit ?? SHA_A,
|
|
100
|
+
},
|
|
101
|
+
commit: sidecar.commit ?? undefined,
|
|
102
|
+
installedAt: "2026-06-10T12:00:00.000Z",
|
|
103
|
+
fingerprint,
|
|
104
|
+
}),
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
let workspace: string;
|
|
110
|
+
|
|
111
|
+
beforeEach(() => {
|
|
112
|
+
workspace = mkdtempSync(join(tmpdir(), "inspect-plugin-"));
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
afterEach(() => {
|
|
116
|
+
rmSync(workspace, { recursive: true, force: true });
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
describe("inspectPlugin", () => {
|
|
120
|
+
test("reports up-to-date when the installed commit equals the marketplace pin", async () => {
|
|
121
|
+
// GIVEN an installed plugin pinned to the same commit the marketplace pins
|
|
122
|
+
installPlugin(workspace, "level-up", { sidecar: { commit: SHA_A } });
|
|
123
|
+
const fetch = makeFetch({ marketplace: manifestWith("level-up", SHA_A) });
|
|
124
|
+
|
|
125
|
+
// WHEN it is inspected
|
|
126
|
+
const result = await inspectPlugin(
|
|
127
|
+
{ name: "level-up" },
|
|
128
|
+
{ fetch, workspacePluginsDir: workspace },
|
|
129
|
+
);
|
|
130
|
+
|
|
131
|
+
// THEN the status is up-to-date and both sides resolve the same commit
|
|
132
|
+
expect(result.status).toBe("up-to-date");
|
|
133
|
+
expect(result.installed).toBe(true);
|
|
134
|
+
expect(result.local?.commit).toBe(SHA_A);
|
|
135
|
+
expect(result.remote?.commit).toBe(SHA_A);
|
|
136
|
+
// AND local + remote metadata are surfaced
|
|
137
|
+
expect(result.local?.version).toBe("0.1.0");
|
|
138
|
+
expect(result.remote?.repo).toBe("example-org/level-up");
|
|
139
|
+
expect(result.remote?.license).toBe("MIT");
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
test("reports update-available when the marketplace pin has advanced", async () => {
|
|
143
|
+
// GIVEN an installed plugin whose commit differs from the marketplace pin
|
|
144
|
+
installPlugin(workspace, "level-up", { sidecar: { commit: SHA_A } });
|
|
145
|
+
const fetch = makeFetch({ marketplace: manifestWith("level-up", SHA_B) });
|
|
146
|
+
|
|
147
|
+
// WHEN it is inspected
|
|
148
|
+
const result = await inspectPlugin(
|
|
149
|
+
{ name: "level-up" },
|
|
150
|
+
{ fetch, workspacePluginsDir: workspace },
|
|
151
|
+
);
|
|
152
|
+
|
|
153
|
+
// THEN drift is reported with both commits
|
|
154
|
+
expect(result.status).toBe("update-available");
|
|
155
|
+
expect(result.local?.commit).toBe(SHA_A);
|
|
156
|
+
expect(result.remote?.commit).toBe(SHA_B);
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
test("falls back to the sidecar ref when no commit was recorded", async () => {
|
|
160
|
+
// GIVEN a sidecar with no commit but a full-SHA ref equal to the pin
|
|
161
|
+
installPlugin(workspace, "level-up", {
|
|
162
|
+
sidecar: { commit: null, ref: SHA_A },
|
|
163
|
+
});
|
|
164
|
+
const fetch = makeFetch({ marketplace: manifestWith("level-up", SHA_A) });
|
|
165
|
+
|
|
166
|
+
// WHEN it is inspected
|
|
167
|
+
const result = await inspectPlugin(
|
|
168
|
+
{ name: "level-up" },
|
|
169
|
+
{ fetch, workspacePluginsDir: workspace },
|
|
170
|
+
);
|
|
171
|
+
|
|
172
|
+
// THEN the ref stands in for the commit and the status is up-to-date
|
|
173
|
+
expect(result.status).toBe("up-to-date");
|
|
174
|
+
expect(result.local?.commit).toBe(SHA_A);
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
test("reports unknown-provenance when installed without a resolvable commit", async () => {
|
|
178
|
+
// GIVEN an installed copy with no provenance sidecar at all
|
|
179
|
+
installPlugin(workspace, "level-up", { sidecar: null });
|
|
180
|
+
const fetch = makeFetch({ marketplace: manifestWith("level-up", SHA_A) });
|
|
181
|
+
|
|
182
|
+
// WHEN it is inspected
|
|
183
|
+
const result = await inspectPlugin(
|
|
184
|
+
{ name: "level-up" },
|
|
185
|
+
{ fetch, workspacePluginsDir: workspace },
|
|
186
|
+
);
|
|
187
|
+
|
|
188
|
+
// THEN the commit cannot be compared
|
|
189
|
+
expect(result.status).toBe("unknown-provenance");
|
|
190
|
+
expect(result.local?.commit).toBeNull();
|
|
191
|
+
expect(result.remote?.commit).toBe(SHA_A);
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
test("reports not-in-marketplace when installed but no entry claims the name", async () => {
|
|
195
|
+
// GIVEN an installed plugin and an empty catalog
|
|
196
|
+
installPlugin(workspace, "level-up", { sidecar: { commit: SHA_A } });
|
|
197
|
+
const fetch = makeFetch({ marketplace: undefined });
|
|
198
|
+
|
|
199
|
+
// WHEN it is inspected
|
|
200
|
+
const result = await inspectPlugin(
|
|
201
|
+
{ name: "level-up" },
|
|
202
|
+
{ fetch, workspacePluginsDir: workspace },
|
|
203
|
+
);
|
|
204
|
+
|
|
205
|
+
// THEN there is no remote to compare against
|
|
206
|
+
expect(result.status).toBe("not-in-marketplace");
|
|
207
|
+
expect(result.installed).toBe(true);
|
|
208
|
+
expect(result.remote).toBeNull();
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
test("reports not-installed but previews the marketplace entry", async () => {
|
|
212
|
+
// GIVEN a marketplace entry with no local copy installed
|
|
213
|
+
const fetch = makeFetch({ marketplace: manifestWith("level-up", SHA_B) });
|
|
214
|
+
|
|
215
|
+
// WHEN it is inspected
|
|
216
|
+
const result = await inspectPlugin(
|
|
217
|
+
{ name: "level-up" },
|
|
218
|
+
{ fetch, workspacePluginsDir: workspace },
|
|
219
|
+
);
|
|
220
|
+
|
|
221
|
+
// THEN the remote metadata previews what would be installed
|
|
222
|
+
expect(result.status).toBe("not-installed");
|
|
223
|
+
expect(result.installed).toBe(false);
|
|
224
|
+
expect(result.local).toBeNull();
|
|
225
|
+
expect(result.remote?.commit).toBe(SHA_B);
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
test("reports remote-unavailable when the marketplace cannot be reached", async () => {
|
|
229
|
+
// GIVEN an installed plugin and a failing marketplace fetch
|
|
230
|
+
installPlugin(workspace, "level-up", { sidecar: { commit: SHA_A } });
|
|
231
|
+
const fetch = makeFetch({ fail: true });
|
|
232
|
+
|
|
233
|
+
// WHEN it is inspected
|
|
234
|
+
const result = await inspectPlugin(
|
|
235
|
+
{ name: "level-up" },
|
|
236
|
+
{ fetch, workspacePluginsDir: workspace },
|
|
237
|
+
);
|
|
238
|
+
|
|
239
|
+
// THEN local info is still reported alongside the fetch error
|
|
240
|
+
expect(result.status).toBe("remote-unavailable");
|
|
241
|
+
expect(result.local?.commit).toBe(SHA_A);
|
|
242
|
+
expect(result.remote).toBeNull();
|
|
243
|
+
expect(result.remoteError).toContain("network down");
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
test("reports no local changes when the on-disk tree matches the fingerprint", async () => {
|
|
247
|
+
// GIVEN an installed plugin with a recorded content fingerprint, untouched
|
|
248
|
+
installPlugin(workspace, "level-up", {
|
|
249
|
+
sidecar: { commit: SHA_A },
|
|
250
|
+
fingerprint: true,
|
|
251
|
+
});
|
|
252
|
+
const fetch = makeFetch({ marketplace: manifestWith("level-up", SHA_A) });
|
|
253
|
+
|
|
254
|
+
// WHEN it is inspected
|
|
255
|
+
const result = await inspectPlugin(
|
|
256
|
+
{ name: "level-up" },
|
|
257
|
+
{ fetch, workspacePluginsDir: workspace },
|
|
258
|
+
);
|
|
259
|
+
|
|
260
|
+
// THEN the local copy is reported clean against its baseline
|
|
261
|
+
expect(result.local?.localChanges?.clean).toBe(true);
|
|
262
|
+
expect(result.local?.localChanges?.modified).toEqual([]);
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
test("detects a locally modified file against the fingerprint", async () => {
|
|
266
|
+
// GIVEN an installed plugin with a recorded fingerprint
|
|
267
|
+
installPlugin(workspace, "level-up", {
|
|
268
|
+
sidecar: { commit: SHA_A },
|
|
269
|
+
fingerprint: true,
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
// AND a tracked file is edited after install
|
|
273
|
+
writeFileSync(
|
|
274
|
+
join(workspace, "level-up", "package.json"),
|
|
275
|
+
JSON.stringify({ name: "level-up", version: "9.9.9" }),
|
|
276
|
+
);
|
|
277
|
+
const fetch = makeFetch({ marketplace: manifestWith("level-up", SHA_A) });
|
|
278
|
+
|
|
279
|
+
// WHEN it is inspected
|
|
280
|
+
const result = await inspectPlugin(
|
|
281
|
+
{ name: "level-up" },
|
|
282
|
+
{ fetch, workspacePluginsDir: workspace },
|
|
283
|
+
);
|
|
284
|
+
|
|
285
|
+
// THEN the edited file surfaces as a local modification
|
|
286
|
+
expect(result.local?.localChanges?.clean).toBe(false);
|
|
287
|
+
expect(result.local?.localChanges?.modified).toEqual(["package.json"]);
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
test("reports unknown local changes when no fingerprint was recorded", async () => {
|
|
291
|
+
// GIVEN an install whose sidecar predates fingerprinting
|
|
292
|
+
installPlugin(workspace, "level-up", { sidecar: { commit: SHA_A } });
|
|
293
|
+
const fetch = makeFetch({ marketplace: manifestWith("level-up", SHA_A) });
|
|
294
|
+
|
|
295
|
+
// WHEN it is inspected
|
|
296
|
+
const result = await inspectPlugin(
|
|
297
|
+
{ name: "level-up" },
|
|
298
|
+
{ fetch, workspacePluginsDir: workspace },
|
|
299
|
+
);
|
|
300
|
+
|
|
301
|
+
// THEN modification cannot be determined
|
|
302
|
+
expect(result.local).not.toBeNull();
|
|
303
|
+
expect(result.local?.localChanges).toBeNull();
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
test("throws when the plugin is neither installed nor in the marketplace", async () => {
|
|
307
|
+
// GIVEN no local copy and an empty catalog
|
|
308
|
+
const fetch = makeFetch({ marketplace: undefined });
|
|
309
|
+
|
|
310
|
+
// WHEN/THEN inspecting a missing plugin rejects with a not-found error
|
|
311
|
+
await expect(
|
|
312
|
+
inspectPlugin(
|
|
313
|
+
{ name: "ghost" },
|
|
314
|
+
{ fetch, workspacePluginsDir: workspace },
|
|
315
|
+
),
|
|
316
|
+
).rejects.toBeInstanceOf(PluginInspectNotFoundError);
|
|
317
|
+
});
|
|
318
|
+
});
|
|
@@ -371,16 +371,23 @@ describe("installPlugin — marketplace resolution", () => {
|
|
|
371
371
|
"https://github.com/JuliusBrussee/caveman.git",
|
|
372
372
|
);
|
|
373
373
|
|
|
374
|
-
// AND
|
|
375
|
-
const
|
|
376
|
-
readFileSync(join(target, "
|
|
374
|
+
// AND an install-meta sidecar records origin + github source + commit.
|
|
375
|
+
const meta = JSON.parse(
|
|
376
|
+
readFileSync(join(target, "install-meta.json"), "utf-8"),
|
|
377
377
|
);
|
|
378
|
-
expect(
|
|
379
|
-
expect(
|
|
380
|
-
expect(
|
|
381
|
-
|
|
382
|
-
);
|
|
383
|
-
expect(
|
|
378
|
+
expect(meta.origin).toBe("vellum");
|
|
379
|
+
expect(meta.sourceRepo).toBe("JuliusBrussee/caveman");
|
|
380
|
+
expect(meta.source.kind).toBe("github");
|
|
381
|
+
expect(meta.source.owner).toBe("JuliusBrussee");
|
|
382
|
+
expect(meta.source.ref).toBe("63a91ecadbf4c4719a4602a5abb00883f9966034");
|
|
383
|
+
expect(meta.commit).toBe("63a91ecadbf4c4719a4602a5abb00883f9966034");
|
|
384
|
+
|
|
385
|
+
// AND both content digests baseline the materialized tree (excluding the
|
|
386
|
+
// sidecar) so later local edits are detectable.
|
|
387
|
+
expect(meta.fingerprint.algorithm).toBe("sha256");
|
|
388
|
+
expect(meta.fingerprint.files["package.json"]).toMatch(/^[0-9a-f]{64}$/);
|
|
389
|
+
expect(meta.fingerprint.files["install-meta.json"]).toBeUndefined();
|
|
390
|
+
expect(meta.contentHash).toMatch(/^v2:[0-9a-f]{64}$/);
|
|
384
391
|
});
|
|
385
392
|
|
|
386
393
|
test("refuses to install when the checked-out commit differs from the pinned SHA", async () => {
|