@stigmer/cli 3.19.0 → 3.20.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (106) hide show
  1. package/commands/apply.d.ts.map +1 -1
  2. package/commands/apply.js +42 -100
  3. package/commands/apply.js.map +1 -1
  4. package/commands/validate.js +3 -4
  5. package/commands/validate.js.map +1 -1
  6. package/local/bootstrap.d.ts +1 -2
  7. package/local/bootstrap.d.ts.map +1 -1
  8. package/local/bootstrap.js +13 -41
  9. package/local/bootstrap.js.map +1 -1
  10. package/local/daemon/launch.d.ts +2 -2
  11. package/local/daemon/launch.d.ts.map +1 -1
  12. package/local/daemon/launch.js +2 -2
  13. package/local/daemon/launch.js.map +1 -1
  14. package/local/plugins/defaults.js +2 -2
  15. package/local/plugins/defaults.js.map +1 -1
  16. package/package.json +7 -7
  17. package/registry/index.d.ts +2 -2
  18. package/registry/index.d.ts.map +1 -1
  19. package/registry/index.js +2 -2
  20. package/registry/index.js.map +1 -1
  21. package/registry/metadata.d.ts +9 -0
  22. package/registry/metadata.d.ts.map +1 -1
  23. package/registry/metadata.js +14 -5
  24. package/registry/metadata.js.map +1 -1
  25. package/registry/registry.d.ts +9 -0
  26. package/registry/registry.d.ts.map +1 -1
  27. package/registry/registry.js +16 -1
  28. package/registry/registry.js.map +1 -1
  29. package/registry/verb-support.d.ts.map +1 -1
  30. package/registry/verb-support.js +0 -13
  31. package/registry/verb-support.js.map +1 -1
  32. package/resources/apply/apply.d.ts +11 -12
  33. package/resources/apply/apply.d.ts.map +1 -1
  34. package/resources/apply/apply.js +12 -21
  35. package/resources/apply/apply.js.map +1 -1
  36. package/resources/apply/handlers.js +2 -2
  37. package/resources/apply/handlers.js.map +1 -1
  38. package/resources/delete.d.ts.map +1 -1
  39. package/resources/delete.js +0 -1
  40. package/resources/delete.js.map +1 -1
  41. package/resources/get-bindings.d.ts.map +1 -1
  42. package/resources/get-bindings.js +0 -5
  43. package/resources/get-bindings.js.map +1 -1
  44. package/resources/list.d.ts.map +1 -1
  45. package/resources/list.js +0 -1
  46. package/resources/list.js.map +1 -1
  47. package/resources/validate.d.ts.map +1 -1
  48. package/resources/validate.js +0 -2
  49. package/resources/validate.js.map +1 -1
  50. package/src/commands/apply.ts +35 -150
  51. package/src/commands/validate.ts +2 -4
  52. package/src/local/bootstrap.test.ts +15 -59
  53. package/src/local/bootstrap.ts +12 -53
  54. package/src/local/daemon/launch.ts +4 -4
  55. package/src/local/plugins/defaults.ts +2 -2
  56. package/src/registry/index.ts +7 -1
  57. package/src/registry/metadata.ts +15 -5
  58. package/src/registry/registry.test.ts +1 -7
  59. package/src/registry/registry.ts +17 -1
  60. package/src/registry/verb-support.ts +0 -13
  61. package/src/resources/apply/apply.integration.test.ts +15 -3
  62. package/src/resources/apply/apply.ts +15 -26
  63. package/src/resources/apply/handlers.ts +2 -2
  64. package/src/resources/delete.ts +0 -1
  65. package/src/resources/get-bindings.ts +0 -5
  66. package/src/resources/list.ts +0 -1
  67. package/src/resources/validate.test.ts +0 -1
  68. package/src/resources/validate.ts +0 -2
  69. package/local/seedpack-retire.d.ts +0 -63
  70. package/local/seedpack-retire.d.ts.map +0 -1
  71. package/local/seedpack-retire.js +0 -361
  72. package/local/seedpack-retire.js.map +0 -1
  73. package/resources/apply/declarative.d.ts +0 -106
  74. package/resources/apply/declarative.d.ts.map +0 -1
  75. package/resources/apply/declarative.js +0 -397
  76. package/resources/apply/declarative.js.map +0 -1
  77. package/resources/apply/synth/project-track.d.ts +0 -21
  78. package/resources/apply/synth/project-track.d.ts.map +0 -1
  79. package/resources/apply/synth/project-track.js +0 -102
  80. package/resources/apply/synth/project-track.js.map +0 -1
  81. package/resources/apply/synth/reader.d.ts +0 -18
  82. package/resources/apply/synth/reader.d.ts.map +0 -1
  83. package/resources/apply/synth/reader.js +0 -55
  84. package/resources/apply/synth/reader.js.map +0 -1
  85. package/resources/apply/synth/runtime.d.ts +0 -8
  86. package/resources/apply/synth/runtime.d.ts.map +0 -1
  87. package/resources/apply/synth/runtime.js +0 -32
  88. package/resources/apply/synth/runtime.js.map +0 -1
  89. package/resources/apply/synth/synthesize.d.ts +0 -43
  90. package/resources/apply/synth/synthesize.d.ts.map +0 -1
  91. package/resources/apply/synth/synthesize.js +0 -115
  92. package/resources/apply/synth/synthesize.js.map +0 -1
  93. package/src/local/seedpack-retire.test.ts +0 -499
  94. package/src/local/seedpack-retire.ts +0 -474
  95. package/src/resources/apply/declarative.integration.test.ts +0 -204
  96. package/src/resources/apply/declarative.test.ts +0 -132
  97. package/src/resources/apply/declarative.ts +0 -491
  98. package/src/resources/apply/synth/capstone.integration.test.ts +0 -134
  99. package/src/resources/apply/synth/project-track.integration.test.ts +0 -194
  100. package/src/resources/apply/synth/project-track.ts +0 -126
  101. package/src/resources/apply/synth/reader.test.ts +0 -90
  102. package/src/resources/apply/synth/reader.ts +0 -68
  103. package/src/resources/apply/synth/runtime.test.ts +0 -29
  104. package/src/resources/apply/synth/runtime.ts +0 -39
  105. package/src/resources/apply/synth/synthesize.test.ts +0 -109
  106. package/src/resources/apply/synth/synthesize.ts +0 -161
@@ -1,499 +0,0 @@
1
- // Pins the seedpack retire over a real Connect backend: a full seedpack
2
- // Project is removed children-first (workflows, agents, skills, MCP servers)
3
- // through the kinds' own delete RPCs, then the Project row, then the marker;
4
- // a member some user agent references, in any org the identity sees, is kept
5
- // and the report names the agent; a member a default plugin adopted (its row
6
- // carries `stigmer.ai/plugin`) is reported as the plugin's and never deleted,
7
- // the plugin's slug read once and its id standing in when the plugin row is
8
- // gone; a seedpack agent referencing a seedpack server keeps nothing; a
9
- // delete the server refuses is kept with the server's sentence; a member
10
- // already gone is not an error; no Project means one read and a stale
11
- // marker's removal; a second run is a no-op.
12
-
13
- import { existsSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
14
- import {
15
- createServer as createHttp2Server,
16
- type Http2Server,
17
- type ServerHttp2Session,
18
- } from "node:http2";
19
- import type { AddressInfo } from "node:net";
20
- import { tmpdir } from "node:os";
21
- import { join } from "node:path";
22
- import { create } from "@bufbuild/protobuf";
23
- import type { DescMessage, MessageShape } from "@bufbuild/protobuf";
24
- import { Code, ConnectError, type ConnectRouter } from "@connectrpc/connect";
25
- import { connectNodeAdapter } from "@connectrpc/connect-node";
26
- import { AgentSchema } from "@stigmer/protos/ai/stigmer/agentic/agent/v1/api_pb";
27
- import { AgentCommandController } from "@stigmer/protos/ai/stigmer/agentic/agent/v1/command_pb";
28
- import { AgentQueryController } from "@stigmer/protos/ai/stigmer/agentic/agent/v1/query_pb";
29
- import { McpServerSchema } from "@stigmer/protos/ai/stigmer/agentic/mcpserver/v1/api_pb";
30
- import { McpServerCommandController } from "@stigmer/protos/ai/stigmer/agentic/mcpserver/v1/command_pb";
31
- import { McpServerQueryController } from "@stigmer/protos/ai/stigmer/agentic/mcpserver/v1/query_pb";
32
- import { PluginSchema } from "@stigmer/protos/ai/stigmer/agentic/plugin/v1/api_pb";
33
- import { PluginQueryController } from "@stigmer/protos/ai/stigmer/agentic/plugin/v1/query_pb";
34
- import { SkillSchema } from "@stigmer/protos/ai/stigmer/agentic/skill/v1/api_pb";
35
- import { SkillCommandController } from "@stigmer/protos/ai/stigmer/agentic/skill/v1/command_pb";
36
- import { SkillQueryController } from "@stigmer/protos/ai/stigmer/agentic/skill/v1/query_pb";
37
- import { WorkflowSchema } from "@stigmer/protos/ai/stigmer/agentic/workflow/v1/api_pb";
38
- import { WorkflowCommandController } from "@stigmer/protos/ai/stigmer/agentic/workflow/v1/command_pb";
39
- import { WorkflowQueryController } from "@stigmer/protos/ai/stigmer/agentic/workflow/v1/query_pb";
40
- import { ApiResourceKind } from "@stigmer/protos/ai/stigmer/commons/apiresource/apiresourcekind/api_resource_kind_pb";
41
- import type { ApiResourceReference } from "@stigmer/protos/ai/stigmer/commons/apiresource/io_pb";
42
- import { SearchResponseSchema } from "@stigmer/protos/ai/stigmer/search/v1/io_pb";
43
- import { SearchService } from "@stigmer/protos/ai/stigmer/search/v1/query_pb";
44
- import { OrganizationSchema } from "@stigmer/protos/ai/stigmer/tenancy/organization/v1/api_pb";
45
- import { OrganizationsSchema } from "@stigmer/protos/ai/stigmer/tenancy/organization/v1/io_pb";
46
- import { OrganizationQueryController } from "@stigmer/protos/ai/stigmer/tenancy/organization/v1/query_pb";
47
- import { ProjectSchema } from "@stigmer/protos/ai/stigmer/tenancy/project/v1/api_pb";
48
- import { ProjectCommandController } from "@stigmer/protos/ai/stigmer/tenancy/project/v1/command_pb";
49
- import { ProjectQueryController } from "@stigmer/protos/ai/stigmer/tenancy/project/v1/query_pb";
50
- import { createNodeClient, normalizeEndpoint } from "@stigmer/sdk/node";
51
- import type { Stigmer } from "@stigmer/sdk";
52
- import {
53
- afterAll,
54
- afterEach,
55
- beforeAll,
56
- beforeEach,
57
- describe,
58
- expect,
59
- it,
60
- } from "vitest";
61
- import {
62
- SEEDPACK_MARKER_FILE,
63
- SEEDPACK_PROJECT_SLUG,
64
- type RetireResult,
65
- renderRetireReport,
66
- retireSeedpack,
67
- } from "./seedpack-retire.js";
68
-
69
- let backend: Http2Server;
70
- let stigmer: Stigmer;
71
- const openSessions = new Set<ServerHttp2Session>();
72
- let markerDir: string;
73
-
74
- // --- The backend's state: rows by "kind:org/slug", deletes in order ---------
75
-
76
- interface Row {
77
- readonly kind: ApiResourceKind;
78
- readonly org: string;
79
- readonly slug: string;
80
- readonly message: MessageShape<DescMessage>;
81
- }
82
-
83
- let rows: Map<string, Row>;
84
- let deletes: string[];
85
- /** Rows whose delete the backend refuses, with the sentence it answers. */
86
- let refusals: Map<string, string>;
87
- let orgs: string[];
88
- let projectReads: number;
89
- let pluginReads: number;
90
-
91
- function key(kind: ApiResourceKind, org: string, slug: string): string {
92
- return `${kind}:${org}/${slug}`;
93
- }
94
-
95
- function idOf(kind: ApiResourceKind, slug: string): string {
96
- return `${ApiResourceKind[kind]}_${slug}`;
97
- }
98
-
99
- function ref(
100
- kind: ApiResourceKind,
101
- slug: string,
102
- org = "stigmer",
103
- ): Pick<ApiResourceReference, "kind" | "org" | "slug"> {
104
- return { kind, org, slug };
105
- }
106
-
107
- function put(
108
- kind: ApiResourceKind,
109
- schema: DescMessage,
110
- org: string,
111
- slug: string,
112
- spec: Record<string, unknown> = {},
113
- labels: Record<string, string> = {},
114
- ): void {
115
- const message = create(schema, {
116
- metadata: { id: idOf(kind, slug), slug, org, labels },
117
- spec,
118
- } as never);
119
- rows.set(key(kind, org, slug), { kind, org, slug, message });
120
- }
121
-
122
- function seedProject(members: ReadonlyArray<ReturnType<typeof ref>>): void {
123
- rows.set(key(ApiResourceKind.project, "stigmer", SEEDPACK_PROJECT_SLUG), {
124
- kind: ApiResourceKind.project,
125
- org: "stigmer",
126
- slug: SEEDPACK_PROJECT_SLUG,
127
- message: create(ProjectSchema, {
128
- metadata: {
129
- id: idOf(ApiResourceKind.project, SEEDPACK_PROJECT_SLUG),
130
- slug: SEEDPACK_PROJECT_SLUG,
131
- org: "stigmer",
132
- },
133
- spec: { members: members.map((m) => ({ ...m })) },
134
- }),
135
- });
136
- }
137
-
138
- /** The full seedpack: one of each kind, the agent referencing the skill and the server. */
139
- function seedFullSeedpack(): void {
140
- put(ApiResourceKind.mcp_server, McpServerSchema, "stigmer", "github");
141
- put(ApiResourceKind.skill, SkillSchema, "stigmer", "code-reviewer");
142
- put(ApiResourceKind.agent, AgentSchema, "stigmer", "code-review-agent", {
143
- mcpServerUsages: [
144
- { mcpServerRef: ref(ApiResourceKind.mcp_server, "github") },
145
- ],
146
- skillRefs: [ref(ApiResourceKind.skill, "code-reviewer")],
147
- });
148
- put(ApiResourceKind.workflow, WorkflowSchema, "stigmer", "content-review");
149
- seedProject([
150
- ref(ApiResourceKind.mcp_server, "github"),
151
- ref(ApiResourceKind.skill, "code-reviewer"),
152
- ref(ApiResourceKind.agent, "code-review-agent"),
153
- ref(ApiResourceKind.workflow, "content-review"),
154
- ]);
155
- }
156
-
157
- function byReference(kind: ApiResourceKind) {
158
- return (r: ApiResourceReference) => {
159
- const row = rows.get(key(kind, r.org, r.slug));
160
- if (row === undefined) {
161
- throw new ConnectError(`${r.org}/${r.slug} not found`, Code.NotFound);
162
- }
163
- return row.message as never;
164
- };
165
- }
166
-
167
- function byId(kind: ApiResourceKind) {
168
- return (id: { value: string }) => {
169
- const row = [...rows.values()].find(
170
- (r) => r.kind === kind && idOf(kind, r.slug) === id.value,
171
- );
172
- if (row === undefined) {
173
- throw new ConnectError(`${id.value} not found`, Code.NotFound);
174
- }
175
- return row.message as never;
176
- };
177
- }
178
-
179
- function deleteById(kind: ApiResourceKind) {
180
- return (id: { value: string } | { resourceId: string }) => {
181
- const value = "value" in id ? id.value : id.resourceId;
182
- const row = [...rows.values()].find(
183
- (r) => r.kind === kind && idOf(kind, r.slug) === value,
184
- );
185
- if (row === undefined) {
186
- throw new ConnectError(`${value} not found`, Code.NotFound);
187
- }
188
- const k = key(kind, row.org, row.slug);
189
- const refusal = refusals.get(k);
190
- if (refusal !== undefined) {
191
- throw new ConnectError(refusal, Code.FailedPrecondition);
192
- }
193
- rows.delete(k);
194
- deletes.push(k);
195
- return row.message as never;
196
- };
197
- }
198
-
199
- beforeAll(async () => {
200
- const routes = (router: ConnectRouter) => {
201
- router.service(OrganizationQueryController, {
202
- findMyOrganizations: () =>
203
- create(OrganizationsSchema, {
204
- entries: orgs.map((slug) =>
205
- create(OrganizationSchema, { metadata: { slug, org: slug } }),
206
- ),
207
- }),
208
- });
209
- router.service(ProjectQueryController, {
210
- getByReference: (r) => {
211
- projectReads += 1;
212
- return byReference(ApiResourceKind.project)(r);
213
- },
214
- });
215
- router.service(ProjectCommandController, {
216
- delete: deleteById(ApiResourceKind.project),
217
- });
218
- router.service(SearchService, {
219
- search: (req) => {
220
- const entries = [...rows.values()].filter(
221
- (r) => r.kind === ApiResourceKind.agent && r.org === req.org,
222
- );
223
- return create(SearchResponseSchema, {
224
- entries: entries.map((r) => ({
225
- kind: r.kind,
226
- id: idOf(r.kind, r.slug),
227
- slug: r.slug,
228
- org: r.org,
229
- })),
230
- totalCount: entries.length,
231
- totalPages: 1,
232
- });
233
- },
234
- });
235
- router.service(AgentQueryController, {
236
- get: byId(ApiResourceKind.agent),
237
- getByReference: byReference(ApiResourceKind.agent),
238
- });
239
- router.service(AgentCommandController, {
240
- delete: deleteById(ApiResourceKind.agent),
241
- });
242
- router.service(SkillQueryController, {
243
- getByReference: byReference(ApiResourceKind.skill),
244
- });
245
- router.service(SkillCommandController, {
246
- delete: deleteById(ApiResourceKind.skill),
247
- });
248
- router.service(McpServerQueryController, {
249
- getByReference: byReference(ApiResourceKind.mcp_server),
250
- });
251
- router.service(McpServerCommandController, {
252
- delete: deleteById(ApiResourceKind.mcp_server),
253
- });
254
- router.service(WorkflowQueryController, {
255
- getByReference: byReference(ApiResourceKind.workflow),
256
- });
257
- router.service(WorkflowCommandController, {
258
- delete: deleteById(ApiResourceKind.workflow),
259
- });
260
- router.service(PluginQueryController, {
261
- get: (id) => {
262
- pluginReads += 1;
263
- return byId(ApiResourceKind.plugin)(id);
264
- },
265
- });
266
- };
267
- backend = createHttp2Server(connectNodeAdapter({ routes }));
268
- backend.on("session", (session) => {
269
- openSessions.add(session);
270
- session.on("close", () => openSessions.delete(session));
271
- });
272
- await new Promise<void>((resolve) => backend.listen(0, "127.0.0.1", resolve));
273
- const port = (backend.address() as AddressInfo).port;
274
- stigmer = createNodeClient({
275
- baseUrl: normalizeEndpoint(`127.0.0.1:${port}`),
276
- });
277
- });
278
-
279
- afterAll(async () => {
280
- for (const session of openSessions) session.destroy();
281
- await new Promise<void>((resolve) => backend.close(() => resolve()));
282
- });
283
-
284
- beforeEach(() => {
285
- rows = new Map();
286
- deletes = [];
287
- refusals = new Map();
288
- orgs = ["stigmer", "acme"];
289
- projectReads = 0;
290
- pluginReads = 0;
291
- markerDir = mkdtempSync(join(tmpdir(), "stigmer-retire-"));
292
- writeFileSync(join(markerDir, SEEDPACK_MARKER_FILE), "sha256:abc\n");
293
- });
294
-
295
- afterEach(() => {
296
- rmSync(markerDir, { recursive: true, force: true });
297
- });
298
-
299
- function run(): Promise<RetireResult> {
300
- return retireSeedpack(stigmer, { markerDir });
301
- }
302
-
303
- function report(result: RetireResult): string[] {
304
- const lines: string[] = [];
305
- renderRetireReport(result, (line) => lines.push(line));
306
- return lines;
307
- }
308
-
309
- const projectKey = key(ApiResourceKind.project, "stigmer", SEEDPACK_PROJECT_SLUG);
310
-
311
- describe("retireSeedpack", () => {
312
- it("removes a full seedpack children-first, then the Project row, then the marker", async () => {
313
- seedFullSeedpack();
314
- const result = await run();
315
- expect(result.present).toBe(true);
316
- if (!result.present) return;
317
- expect(result.outcomes.map((o) => o.action)).toEqual([
318
- "removed",
319
- "removed",
320
- "removed",
321
- "removed",
322
- ]);
323
- expect(deletes).toEqual([
324
- key(ApiResourceKind.workflow, "stigmer", "content-review"),
325
- key(ApiResourceKind.agent, "stigmer", "code-review-agent"),
326
- key(ApiResourceKind.skill, "stigmer", "code-reviewer"),
327
- key(ApiResourceKind.mcp_server, "stigmer", "github"),
328
- projectKey,
329
- ]);
330
- expect(result.projectRemoved).toBe(true);
331
- expect(existsSync(join(markerDir, SEEDPACK_MARKER_FILE))).toBe(false);
332
- expect(report(result)).toEqual([
333
- "Retired the system content an older release installed: removed 4 resources.",
334
- ]);
335
- });
336
-
337
- it("keeps a member a user's agent in another org references, and names the agent", async () => {
338
- seedFullSeedpack();
339
- put(ApiResourceKind.agent, AgentSchema, "acme", "my-reviewer", {
340
- mcpServerUsages: [
341
- { mcpServerRef: ref(ApiResourceKind.mcp_server, "github") },
342
- ],
343
- });
344
- const result = await run();
345
- if (!result.present) throw new Error("expected a project");
346
- const github = result.outcomes.find(
347
- (o) => o.member.slug === "github",
348
- );
349
- expect(github?.action).toBe("kept-referenced");
350
- expect(github?.action === "kept-referenced" && github.by).toEqual([
351
- "agent 'acme/my-reviewer'",
352
- ]);
353
- expect(deletes).not.toContain(
354
- key(ApiResourceKind.mcp_server, "stigmer", "github"),
355
- );
356
- // The seedpack's own agent referenced the same server and the skill; it kept neither.
357
- expect(deletes).toContain(key(ApiResourceKind.skill, "stigmer", "code-reviewer"));
358
- expect(deletes).toContain(projectKey);
359
- // The user's agent is not a seedpack member and is never touched.
360
- expect(rows.has(key(ApiResourceKind.agent, "acme", "my-reviewer"))).toBe(true);
361
- expect(report(result)).toEqual([
362
- "Retired the system content an older release installed: removed 3 resources, kept 1 resource.",
363
- " kept mcpserver 'stigmer/github': referenced by agent 'acme/my-reviewer'",
364
- " A kept resource's icon no longer resolves. Once its referrer is edited, remove it with 'stigmer delete <kind> <org>/<slug>'.",
365
- ]);
366
- });
367
-
368
- it("reports a member a default plugin adopted as the plugin's, never deletes it, and reads the plugin once", async () => {
369
- seedFullSeedpack();
370
- put(ApiResourceKind.plugin, PluginSchema, "stigmer", "assistant");
371
- const pluginId = idOf(ApiResourceKind.plugin, "assistant");
372
- // The bootstrap ran first and its push adopted two rows in place.
373
- put(
374
- ApiResourceKind.agent,
375
- AgentSchema,
376
- "stigmer",
377
- "code-review-agent",
378
- {},
379
- { "stigmer.ai/plugin": pluginId, "stigmer.ai/system": "true" },
380
- );
381
- put(
382
- ApiResourceKind.mcp_server,
383
- McpServerSchema,
384
- "stigmer",
385
- "github",
386
- {},
387
- { "stigmer.ai/plugin": pluginId },
388
- );
389
- const result = await run();
390
- if (!result.present) throw new Error("expected a project");
391
- expect(result.outcomes.map((o) => [o.member.slug, o.action])).toEqual([
392
- ["content-review", "removed"],
393
- ["code-review-agent", "adopted"],
394
- ["code-reviewer", "removed"],
395
- ["github", "adopted"],
396
- ]);
397
- expect(
398
- result.outcomes
399
- .filter((o) => o.action === "adopted")
400
- .map((o) => o.action === "adopted" && o.byPlugin),
401
- ).toEqual(["assistant", "assistant"]);
402
- expect(pluginReads).toBe(1);
403
- expect(deletes).toEqual([
404
- key(ApiResourceKind.workflow, "stigmer", "content-review"),
405
- key(ApiResourceKind.skill, "stigmer", "code-reviewer"),
406
- projectKey,
407
- ]);
408
- expect(rows.has(key(ApiResourceKind.agent, "stigmer", "code-review-agent"))).toBe(true);
409
- expect(report(result)).toEqual([
410
- "Retired the system content an older release installed: removed 2 resources, 2 resources now managed by a plugin.",
411
- " agent 'stigmer/code-review-agent' is now managed by plugin 'assistant'; its instances and conversations continue",
412
- " mcpserver 'stigmer/github' is now managed by plugin 'assistant'; its instances and conversations continue",
413
- ]);
414
- });
415
-
416
- it("names the adopting plugin by id when its row is gone", async () => {
417
- seedFullSeedpack();
418
- put(
419
- ApiResourceKind.agent,
420
- AgentSchema,
421
- "stigmer",
422
- "code-review-agent",
423
- {},
424
- { "stigmer.ai/plugin": "plg_vanished" },
425
- );
426
- const result = await run();
427
- if (!result.present) throw new Error("expected a project");
428
- const agent = result.outcomes.find((o) => o.member.slug === "code-review-agent");
429
- expect(agent).toMatchObject({ action: "adopted", byPlugin: "plg_vanished" });
430
- expect(deletes).not.toContain(
431
- key(ApiResourceKind.agent, "stigmer", "code-review-agent"),
432
- );
433
- });
434
-
435
- it("keeps a member whose delete the server refuses, with the server's sentence", async () => {
436
- seedFullSeedpack();
437
- refusals.set(
438
- key(ApiResourceKind.skill, "stigmer", "code-reviewer"),
439
- "skill 'code-reviewer' is managed by plugin 'reviewer'",
440
- );
441
- const result = await run();
442
- if (!result.present) throw new Error("expected a project");
443
- const skill = result.outcomes.find((o) => o.member.slug === "code-reviewer");
444
- expect(skill?.action).toBe("kept-refused");
445
- expect(skill?.action === "kept-refused" && skill.reason).toMatch(
446
- /managed by plugin 'reviewer'/,
447
- );
448
- expect(deletes).toContain(key(ApiResourceKind.mcp_server, "stigmer", "github"));
449
- expect(report(result)[1]).toMatch(
450
- /^ {2}kept skill 'stigmer\/code-reviewer': .*managed by plugin 'reviewer'/,
451
- );
452
- });
453
-
454
- it("counts a member that is already gone without an error", async () => {
455
- seedFullSeedpack();
456
- rows.delete(key(ApiResourceKind.workflow, "stigmer", "content-review"));
457
- const result = await run();
458
- if (!result.present) throw new Error("expected a project");
459
- expect(result.outcomes[0]).toMatchObject({
460
- action: "already-gone",
461
- member: { slug: "content-review" },
462
- });
463
- expect(result.outcomes.slice(1).map((o) => o.action)).toEqual([
464
- "removed",
465
- "removed",
466
- "removed",
467
- ]);
468
- });
469
-
470
- it("does nothing but one read and the marker's removal when there is no Project", async () => {
471
- const result = await run();
472
- expect(result).toEqual({ present: false });
473
- expect(projectReads).toBe(1);
474
- expect(deletes).toEqual([]);
475
- expect(existsSync(join(markerDir, SEEDPACK_MARKER_FILE))).toBe(false);
476
- expect(report(result)).toEqual([]);
477
- });
478
-
479
- it("is a no-op the second time", async () => {
480
- seedFullSeedpack();
481
- await run();
482
- deletes = [];
483
- const again = await run();
484
- expect(again).toEqual({ present: false });
485
- expect(deletes).toEqual([]);
486
- });
487
-
488
- it("reports a Project row the server would not delete and keeps the marker's removal", async () => {
489
- seedFullSeedpack();
490
- refusals.set(projectKey, "project is locked");
491
- const result = await run();
492
- if (!result.present) throw new Error("expected a project");
493
- expect(result.projectRemoved).toBe(false);
494
- expect(result.projectRefusal).toMatch(/project is locked/);
495
- expect(report(result).at(-1)).toMatch(
496
- /project row could not be removed: .*project is locked.* Run 'stigmer up' again to retry\./,
497
- );
498
- });
499
- });