@jaybeeuu/agent-cortex 1.42.0 → 1.43.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 (62) hide show
  1. package/.agents/skills/style-versioning/SKILL.md +73 -2
  2. package/.github/workflows/ci.yml +47 -27
  3. package/AGENTS.md +23 -2
  4. package/CHANGELOG.md +166 -0
  5. package/README.md +125 -73
  6. package/agents/README.md +17 -4
  7. package/bin/agent-cortex.mjs +48 -30
  8. package/bin/installers/pi.mjs +885 -237
  9. package/docs/claude-hooks.md +5 -4
  10. package/docs/commit-signing.md +231 -0
  11. package/docs/ideas/enable-signed-commit-enforcement-changesets-bot-commits-must-be-signed.md +10 -1
  12. package/docs/ideas/extension-manifests.md +5 -0
  13. package/docs/research/disable-model-invocation.md +103 -0
  14. package/docs/research/skill-design-research.md +2 -2
  15. package/docs/skills/skill-anatomy.md +5 -1
  16. package/docs/technical-direction/harness-config.md +25 -1
  17. package/docs/technical-direction/migration-execution-labels.md +17 -9
  18. package/extensions/agent-modes/discover.test.ts +19 -0
  19. package/extensions/agent-modes/discover.ts +25 -3
  20. package/extensions/session-start/README.md +38 -0
  21. package/extensions/session-start/index.test.ts +122 -0
  22. package/extensions/session-start/index.ts +27 -0
  23. package/extensions/session-start/package.json +19 -0
  24. package/extensions/session-start/tsconfig.json +12 -0
  25. package/extensions/skill-stats/pnpm-lock.yaml +329 -0
  26. package/extensions/subagent/README.md +1 -1
  27. package/extensions/subagent/agents.test.ts +176 -0
  28. package/extensions/subagent/agents.ts +102 -11
  29. package/extensions/subagent/index.ts +2 -1
  30. package/extensions/subagent/package.json +1 -1
  31. package/lib/cli.mjs +35 -9
  32. package/package.json +6 -5
  33. package/pi/settings.json +1 -7
  34. package/plugin.json +1 -1
  35. package/pnpm-workspace.yaml +6 -0
  36. package/scripts/check-changeset.mjs +42 -0
  37. package/scripts/lib/release-gates.mjs +61 -0
  38. package/scripts/lib/verify-worker-tree.mjs +107 -0
  39. package/scripts/resolve-publish-command.mjs +17 -0
  40. package/skills/planning/create-task/scripts/pnpm-lock.yaml +342 -0
  41. package/skills/productivity/validate-agent-dir/scripts/pnpm-lock.yaml +329 -0
  42. package/skills/workflow/git-workflow/SKILL.md +35 -1
  43. package/skills/workflow/ralph/REFERENCE.md +17 -1
  44. package/skills/workflow/ralph/SKILL.md +4 -1
  45. package/skills/workflow/run-pipeline-stage/SKILL.md +11 -4
  46. package/skills/workflow/run-pipeline-stage/playbooks/code.md +2 -0
  47. package/skills/workflow/run-pipeline-stage/playbooks/document.md +3 -0
  48. package/skills/workflow/run-pipeline-stage/playbooks/fix.md +2 -0
  49. package/skills/workflow/run-pipeline-stage/prompts/stage-runner.md +11 -0
  50. package/skills/workflow/run-pipeline-stage/scripts/pnpm-lock.yaml +342 -0
  51. package/test/cli.test.mjs +26 -29
  52. package/test/helpers/fake-pi.mjs +132 -0
  53. package/test/pi-installer.test.mjs +345 -286
  54. package/test/pi-register.test.mjs +342 -0
  55. package/test/pi-sweep.test.mjs +619 -0
  56. package/test/release-gates.test.mjs +190 -0
  57. package/test/verify-worker-tree.test.mjs +120 -0
  58. package/token-map.README.md +7 -6
  59. package/token-map.json +6 -5
  60. package/extensions/session-start.ts +0 -44
  61. package/lib/pi-packages.mjs +0 -169
  62. package/test/pi-packages.test.mjs +0 -295
@@ -0,0 +1,342 @@
1
+ lockfileVersion: '9.0'
2
+
3
+ settings:
4
+ autoInstallPeers: true
5
+ excludeLinksFromLockfile: false
6
+
7
+ importers:
8
+
9
+ .:
10
+ devDependencies:
11
+ '@types/node':
12
+ specifier: ^22.0.0
13
+ version: 22.19.17
14
+ tsx:
15
+ specifier: ^4.0.0
16
+ version: 4.21.0
17
+ typescript:
18
+ specifier: ^5.0.0
19
+ version: 5.9.3
20
+
21
+ packages:
22
+
23
+ '@esbuild/aix-ppc64@0.27.7':
24
+ resolution: {integrity: sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==}
25
+ engines: {node: '>=18'}
26
+ cpu: [ppc64]
27
+ os: [aix]
28
+
29
+ '@esbuild/android-arm64@0.27.7':
30
+ resolution: {integrity: sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==}
31
+ engines: {node: '>=18'}
32
+ cpu: [arm64]
33
+ os: [android]
34
+
35
+ '@esbuild/android-arm@0.27.7':
36
+ resolution: {integrity: sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==}
37
+ engines: {node: '>=18'}
38
+ cpu: [arm]
39
+ os: [android]
40
+
41
+ '@esbuild/android-x64@0.27.7':
42
+ resolution: {integrity: sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==}
43
+ engines: {node: '>=18'}
44
+ cpu: [x64]
45
+ os: [android]
46
+
47
+ '@esbuild/darwin-arm64@0.27.7':
48
+ resolution: {integrity: sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==}
49
+ engines: {node: '>=18'}
50
+ cpu: [arm64]
51
+ os: [darwin]
52
+
53
+ '@esbuild/darwin-x64@0.27.7':
54
+ resolution: {integrity: sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==}
55
+ engines: {node: '>=18'}
56
+ cpu: [x64]
57
+ os: [darwin]
58
+
59
+ '@esbuild/freebsd-arm64@0.27.7':
60
+ resolution: {integrity: sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==}
61
+ engines: {node: '>=18'}
62
+ cpu: [arm64]
63
+ os: [freebsd]
64
+
65
+ '@esbuild/freebsd-x64@0.27.7':
66
+ resolution: {integrity: sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==}
67
+ engines: {node: '>=18'}
68
+ cpu: [x64]
69
+ os: [freebsd]
70
+
71
+ '@esbuild/linux-arm64@0.27.7':
72
+ resolution: {integrity: sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==}
73
+ engines: {node: '>=18'}
74
+ cpu: [arm64]
75
+ os: [linux]
76
+
77
+ '@esbuild/linux-arm@0.27.7':
78
+ resolution: {integrity: sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==}
79
+ engines: {node: '>=18'}
80
+ cpu: [arm]
81
+ os: [linux]
82
+
83
+ '@esbuild/linux-ia32@0.27.7':
84
+ resolution: {integrity: sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==}
85
+ engines: {node: '>=18'}
86
+ cpu: [ia32]
87
+ os: [linux]
88
+
89
+ '@esbuild/linux-loong64@0.27.7':
90
+ resolution: {integrity: sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==}
91
+ engines: {node: '>=18'}
92
+ cpu: [loong64]
93
+ os: [linux]
94
+
95
+ '@esbuild/linux-mips64el@0.27.7':
96
+ resolution: {integrity: sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==}
97
+ engines: {node: '>=18'}
98
+ cpu: [mips64el]
99
+ os: [linux]
100
+
101
+ '@esbuild/linux-ppc64@0.27.7':
102
+ resolution: {integrity: sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==}
103
+ engines: {node: '>=18'}
104
+ cpu: [ppc64]
105
+ os: [linux]
106
+
107
+ '@esbuild/linux-riscv64@0.27.7':
108
+ resolution: {integrity: sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==}
109
+ engines: {node: '>=18'}
110
+ cpu: [riscv64]
111
+ os: [linux]
112
+
113
+ '@esbuild/linux-s390x@0.27.7':
114
+ resolution: {integrity: sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==}
115
+ engines: {node: '>=18'}
116
+ cpu: [s390x]
117
+ os: [linux]
118
+
119
+ '@esbuild/linux-x64@0.27.7':
120
+ resolution: {integrity: sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==}
121
+ engines: {node: '>=18'}
122
+ cpu: [x64]
123
+ os: [linux]
124
+
125
+ '@esbuild/netbsd-arm64@0.27.7':
126
+ resolution: {integrity: sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==}
127
+ engines: {node: '>=18'}
128
+ cpu: [arm64]
129
+ os: [netbsd]
130
+
131
+ '@esbuild/netbsd-x64@0.27.7':
132
+ resolution: {integrity: sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==}
133
+ engines: {node: '>=18'}
134
+ cpu: [x64]
135
+ os: [netbsd]
136
+
137
+ '@esbuild/openbsd-arm64@0.27.7':
138
+ resolution: {integrity: sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==}
139
+ engines: {node: '>=18'}
140
+ cpu: [arm64]
141
+ os: [openbsd]
142
+
143
+ '@esbuild/openbsd-x64@0.27.7':
144
+ resolution: {integrity: sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==}
145
+ engines: {node: '>=18'}
146
+ cpu: [x64]
147
+ os: [openbsd]
148
+
149
+ '@esbuild/openharmony-arm64@0.27.7':
150
+ resolution: {integrity: sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==}
151
+ engines: {node: '>=18'}
152
+ cpu: [arm64]
153
+ os: [openharmony]
154
+
155
+ '@esbuild/sunos-x64@0.27.7':
156
+ resolution: {integrity: sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==}
157
+ engines: {node: '>=18'}
158
+ cpu: [x64]
159
+ os: [sunos]
160
+
161
+ '@esbuild/win32-arm64@0.27.7':
162
+ resolution: {integrity: sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==}
163
+ engines: {node: '>=18'}
164
+ cpu: [arm64]
165
+ os: [win32]
166
+
167
+ '@esbuild/win32-ia32@0.27.7':
168
+ resolution: {integrity: sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==}
169
+ engines: {node: '>=18'}
170
+ cpu: [ia32]
171
+ os: [win32]
172
+
173
+ '@esbuild/win32-x64@0.27.7':
174
+ resolution: {integrity: sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==}
175
+ engines: {node: '>=18'}
176
+ cpu: [x64]
177
+ os: [win32]
178
+
179
+ '@types/node@22.19.17':
180
+ resolution: {integrity: sha512-wGdMcf+vPYM6jikpS/qhg6WiqSV/OhG+jeeHT/KlVqxYfD40iYJf9/AE1uQxVWFvU7MipKRkRv8NSHiCGgPr8Q==}
181
+
182
+ esbuild@0.27.7:
183
+ resolution: {integrity: sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==}
184
+ engines: {node: '>=18'}
185
+ hasBin: true
186
+
187
+ fsevents@2.3.3:
188
+ resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
189
+ engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
190
+ os: [darwin]
191
+
192
+ get-tsconfig@4.14.0:
193
+ resolution: {integrity: sha512-yTb+8DXzDREzgvYmh6s9vHsSVCHeC0G3PI5bEXNBHtmshPnO+S5O7qgLEOn0I5QvMy6kpZN8K1NKGyilLb93wA==}
194
+
195
+ resolve-pkg-maps@1.0.0:
196
+ resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==}
197
+
198
+ tsx@4.21.0:
199
+ resolution: {integrity: sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==}
200
+ engines: {node: '>=18.0.0'}
201
+ hasBin: true
202
+
203
+ typescript@5.9.3:
204
+ resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==}
205
+ engines: {node: '>=14.17'}
206
+ hasBin: true
207
+
208
+ undici-types@6.21.0:
209
+ resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==}
210
+
211
+ snapshots:
212
+
213
+ '@esbuild/aix-ppc64@0.27.7':
214
+ optional: true
215
+
216
+ '@esbuild/android-arm64@0.27.7':
217
+ optional: true
218
+
219
+ '@esbuild/android-arm@0.27.7':
220
+ optional: true
221
+
222
+ '@esbuild/android-x64@0.27.7':
223
+ optional: true
224
+
225
+ '@esbuild/darwin-arm64@0.27.7':
226
+ optional: true
227
+
228
+ '@esbuild/darwin-x64@0.27.7':
229
+ optional: true
230
+
231
+ '@esbuild/freebsd-arm64@0.27.7':
232
+ optional: true
233
+
234
+ '@esbuild/freebsd-x64@0.27.7':
235
+ optional: true
236
+
237
+ '@esbuild/linux-arm64@0.27.7':
238
+ optional: true
239
+
240
+ '@esbuild/linux-arm@0.27.7':
241
+ optional: true
242
+
243
+ '@esbuild/linux-ia32@0.27.7':
244
+ optional: true
245
+
246
+ '@esbuild/linux-loong64@0.27.7':
247
+ optional: true
248
+
249
+ '@esbuild/linux-mips64el@0.27.7':
250
+ optional: true
251
+
252
+ '@esbuild/linux-ppc64@0.27.7':
253
+ optional: true
254
+
255
+ '@esbuild/linux-riscv64@0.27.7':
256
+ optional: true
257
+
258
+ '@esbuild/linux-s390x@0.27.7':
259
+ optional: true
260
+
261
+ '@esbuild/linux-x64@0.27.7':
262
+ optional: true
263
+
264
+ '@esbuild/netbsd-arm64@0.27.7':
265
+ optional: true
266
+
267
+ '@esbuild/netbsd-x64@0.27.7':
268
+ optional: true
269
+
270
+ '@esbuild/openbsd-arm64@0.27.7':
271
+ optional: true
272
+
273
+ '@esbuild/openbsd-x64@0.27.7':
274
+ optional: true
275
+
276
+ '@esbuild/openharmony-arm64@0.27.7':
277
+ optional: true
278
+
279
+ '@esbuild/sunos-x64@0.27.7':
280
+ optional: true
281
+
282
+ '@esbuild/win32-arm64@0.27.7':
283
+ optional: true
284
+
285
+ '@esbuild/win32-ia32@0.27.7':
286
+ optional: true
287
+
288
+ '@esbuild/win32-x64@0.27.7':
289
+ optional: true
290
+
291
+ '@types/node@22.19.17':
292
+ dependencies:
293
+ undici-types: 6.21.0
294
+
295
+ esbuild@0.27.7:
296
+ optionalDependencies:
297
+ '@esbuild/aix-ppc64': 0.27.7
298
+ '@esbuild/android-arm': 0.27.7
299
+ '@esbuild/android-arm64': 0.27.7
300
+ '@esbuild/android-x64': 0.27.7
301
+ '@esbuild/darwin-arm64': 0.27.7
302
+ '@esbuild/darwin-x64': 0.27.7
303
+ '@esbuild/freebsd-arm64': 0.27.7
304
+ '@esbuild/freebsd-x64': 0.27.7
305
+ '@esbuild/linux-arm': 0.27.7
306
+ '@esbuild/linux-arm64': 0.27.7
307
+ '@esbuild/linux-ia32': 0.27.7
308
+ '@esbuild/linux-loong64': 0.27.7
309
+ '@esbuild/linux-mips64el': 0.27.7
310
+ '@esbuild/linux-ppc64': 0.27.7
311
+ '@esbuild/linux-riscv64': 0.27.7
312
+ '@esbuild/linux-s390x': 0.27.7
313
+ '@esbuild/linux-x64': 0.27.7
314
+ '@esbuild/netbsd-arm64': 0.27.7
315
+ '@esbuild/netbsd-x64': 0.27.7
316
+ '@esbuild/openbsd-arm64': 0.27.7
317
+ '@esbuild/openbsd-x64': 0.27.7
318
+ '@esbuild/openharmony-arm64': 0.27.7
319
+ '@esbuild/sunos-x64': 0.27.7
320
+ '@esbuild/win32-arm64': 0.27.7
321
+ '@esbuild/win32-ia32': 0.27.7
322
+ '@esbuild/win32-x64': 0.27.7
323
+
324
+ fsevents@2.3.3:
325
+ optional: true
326
+
327
+ get-tsconfig@4.14.0:
328
+ dependencies:
329
+ resolve-pkg-maps: 1.0.0
330
+
331
+ resolve-pkg-maps@1.0.0: {}
332
+
333
+ tsx@4.21.0:
334
+ dependencies:
335
+ esbuild: 0.27.7
336
+ get-tsconfig: 4.14.0
337
+ optionalDependencies:
338
+ fsevents: 2.3.3
339
+
340
+ typescript@5.9.3: {}
341
+
342
+ undici-types@6.21.0: {}
package/test/cli.test.mjs CHANGED
@@ -123,11 +123,6 @@ describe("parseArgs", () => {
123
123
  assert.deepStrictEqual(result, { command: "install", harness: "pi", pluginRoot: "/tmp/plugin" });
124
124
  });
125
125
 
126
- it("parses --no-provision for install", async () => {
127
- const result = parseArgs(["install", "pi", "--no-provision"]);
128
- assert.deepStrictEqual(result, { command: "install", harness: "pi", noProvision: true });
129
- });
130
-
131
126
  it("combines multiple install options", async () => {
132
127
  const result = parseArgs(["install", "pi", "--dry-run", "--output", "/tmp/out", "--plugin-root", "/tmp/plugin"]);
133
128
  assert.deepStrictEqual(result, {
@@ -236,14 +231,6 @@ describe("CLI integration", () => {
236
231
  }
237
232
  });
238
233
 
239
- it("exits 0 for install pi --no-provision", async () => {
240
- // --no-provision keeps this hermetic: without it the CLI would install the
241
- // third-party packages declared in the package manifest.
242
- const { exitCode, stdout } = await runCli(["install", "pi", "--no-provision"]);
243
- assert.equal(exitCode, 0);
244
- assert.ok(stdout.includes("pi"));
245
- });
246
-
247
234
  it("exits 1 and prints error for install with no harness", async () => {
248
235
  const { exitCode, stderr } = await runCli(["install"]);
249
236
  assert.equal(exitCode, 1);
@@ -256,6 +243,12 @@ describe("CLI integration", () => {
256
243
  assert.ok(stderr.includes("--bogus"));
257
244
  });
258
245
 
246
+ it("exits 1 for the removed --no-provision flag", async () => {
247
+ const { exitCode, stderr } = await runCli(["install", "pi", "--no-provision"]);
248
+ assert.equal(exitCode, 1);
249
+ assert.ok(stderr.includes("--no-provision"), "the removed flag is named in the error");
250
+ });
251
+
259
252
  it("exits 1 and prints error for install with a missing option value", async () => {
260
253
  const { exitCode, stderr } = await runCli(["install", "pi", "--output"]);
261
254
  assert.equal(exitCode, 1);
@@ -263,30 +256,33 @@ describe("CLI integration", () => {
263
256
  });
264
257
 
265
258
  it("exits 0 for install pi --dry-run and writes nothing", async () => {
266
- const { exitCode, stdout } = await runCli(["install", "pi", "--dry-run"]);
267
- assert.equal(exitCode, 0);
268
- assert.ok(stdout.includes("ralph.agent.md"));
269
- assert.ok(stdout.includes("dry-run"));
259
+ // A throwaway HOME keeps the registration plan (which reads pi's settings)
260
+ // off the developer's real ~/.pi/agent.
261
+ const fakeHome = await mkdtemp(join(tmpdir(), "cli-pi-dry-run-home-"));
262
+ try {
263
+ const { exitCode, stdout } = await runCli(["install", "pi", "--dry-run"], { HOME: fakeHome });
264
+ assert.equal(exitCode, 0);
265
+ assert.ok(stdout.includes("ralph.agent.md"));
266
+ assert.ok(stdout.includes("dry-run"));
267
+ } finally {
268
+ await rm(fakeHome, { recursive: true, force: true });
269
+ }
270
270
  });
271
271
 
272
- it("install pi --dry-run lists the pi packages it would install", async () => {
273
- // Fake HOME keeps the pi user scope hermetic: both manifest packages are
274
- // missing, so the plan contains exactly the declared sources.
272
+ it("install pi --dry-run reports the bundled pi packages it would stage", async () => {
275
273
  const fakeHome = await mkdtemp(join(tmpdir(), "cli-pi-home-"));
276
274
  try {
277
275
  const { exitCode, stdout } = await runCli(["install", "pi", "--dry-run"], { HOME: fakeHome });
278
276
  assert.equal(exitCode, 0);
279
- assert.ok(stdout.includes("Would install pi package npm:pi-questions"), "planned pi-questions shown");
280
- assert.ok(
281
- stdout.includes("Would install pi package npm:pi-web-access@0.10.7"),
282
- "planned pi-web-access shown",
283
- );
277
+ assert.ok(stdout.includes("Bundled pi package pi-questions"), "pi-questions staged");
278
+ assert.ok(stdout.includes("Bundled pi package pi-web-access"), "pi-web-access staged");
279
+ assert.ok(stdout.includes(join(fakeHome, ".agent-cortex", "pi")), "default output is the home staging dir");
284
280
  } finally {
285
281
  await rm(fakeHome, { recursive: true, force: true });
286
282
  }
287
283
  });
288
284
 
289
- it("exits 0 for install pi --output <tmp> and writes agent and config files", async () => {
285
+ it("exits 0 for install pi --output <tmp> and stages a loadable pi package", async () => {
290
286
  const outDir = await mkdtemp(join(tmpdir(), "cli-pi-install-"));
291
287
  try {
292
288
  const { exitCode, stdout } = await runCli(["install", "pi", "--output", outDir]);
@@ -294,9 +290,10 @@ describe("CLI integration", () => {
294
290
  assert.ok(stdout.includes("ralph.agent.md"));
295
291
  const ralph = await readFile(join(outDir, "agents", "ralph.agent.md"), "utf-8");
296
292
  assert.ok(ralph.includes("agent-cortex:ralph"));
297
- const settings = JSON.parse(await readFile(join(outDir, "settings.json"), "utf-8"));
298
- assert.ok(Array.isArray(settings.packages));
299
- assert.ok(await readFile(join(outDir, "keybindings.json"), "utf-8"));
293
+ const manifest = JSON.parse(await readFile(join(outDir, "package.json"), "utf-8"));
294
+ assert.deepEqual(manifest.keywords, ["pi-package"]);
295
+ assert.ok(manifest.pi.extensions.includes("./extensions"));
296
+ assert.ok(manifest.pi.skills.includes("./skills"));
300
297
  } finally {
301
298
  await rm(outDir, { recursive: true, force: true });
302
299
  }
@@ -0,0 +1,132 @@
1
+ // Shared test helper: a stateful fake `pi` binary for pi's package commands.
2
+ // The real CLI is a genuine external system (and its user settings must never
3
+ // be touched by tests), so tests drive this fake instead. It records every
4
+ // invocation to `calls.log` (argv, space-joined, one line each) and simulates
5
+ // just enough real behaviour to assert registration state:
6
+ //
7
+ // --version → exit 0 (availability probe)
8
+ // list → configured package sources
9
+ // install <source> → adds <source> to settings.json `packages`, persisted
10
+ // relative to the settings file's directory (what real
11
+ // pi does) unless an entry already resolves to it
12
+ // remove <source> → drops the matching entry; exit 1 when none matches
13
+ //
14
+ // Local sources are resolved the way pi resolves them: relative to the
15
+ // directory holding settings.json. `failWhen` makes any invocation whose argv
16
+ // contains the substring exit 1 (command failures).
17
+ //
18
+ // Written async throughout (the fake is a child process and the helper's own
19
+ // I/O is test code) — the repo's check-no-sync lint scans test/ for *Sync.
20
+
21
+ import { chmod, writeFile } from "node:fs/promises";
22
+ import { join } from "node:path";
23
+
24
+ const FAKE_PI_SCRIPT = [
25
+ "#!/usr/bin/env node",
26
+ "// Stateful fake for the `pi` package CLI (test/helpers/fake-pi.mjs).",
27
+ "const fs = require('node:fs/promises');",
28
+ "const path = require('node:path');",
29
+ "const os = require('node:os');",
30
+ "",
31
+ "const dir = __dirname;",
32
+ "const settingsFile = path.join(dir, 'settings.json');",
33
+ "const logFile = path.join(dir, 'calls.log');",
34
+ "const failWhenFile = path.join(dir, 'failWhen.txt');",
35
+ "",
36
+ "const expand = (p) => (p === '~' ? os.homedir() : p.startsWith('~/') ? path.join(os.homedir(), p.slice(2)) : p);",
37
+ "const sourceOf = (entry) => (typeof entry === 'string' ? entry : entry && entry.source);",
38
+ "// pi resolves a configured local source against the settings file's directory.",
39
+ "const resolvedSource = (entry) => path.resolve(dir, expand(sourceOf(entry)));",
40
+ "const resolvedArgument = (arg) => path.resolve(dir, expand(arg));",
41
+ "",
42
+ "(async () => {",
43
+ " const argv = process.argv.slice(2);",
44
+ " const joined = argv.join(' ');",
45
+ " await fs.appendFile(logFile, joined + '\\n');",
46
+ "",
47
+ " const failWhen = await fs.readFile(failWhenFile, 'utf8').catch(() => null);",
48
+ " if (failWhen && joined.includes(failWhen)) process.exit(1);",
49
+ "",
50
+ " if (argv[0] === '--version' || argv[0] === '-v') {",
51
+ " console.log('1.4.0');",
52
+ " process.exit(0);",
53
+ " }",
54
+ "",
55
+ " const readSettings = async () => {",
56
+ " try {",
57
+ " return JSON.parse(await fs.readFile(settingsFile, 'utf8'));",
58
+ " } catch {",
59
+ " return {};",
60
+ " }",
61
+ " };",
62
+ " const writePackages = async (settings, packages) =>",
63
+ " fs.writeFile(settingsFile, JSON.stringify({ ...settings, packages }, null, 2) + '\\n');",
64
+ "",
65
+ " const [command, argument] = argv;",
66
+ " const settings = await readSettings();",
67
+ " const packages = settings.packages ?? [];",
68
+ "",
69
+ " if (command === 'list') {",
70
+ " if (packages.length === 0) {",
71
+ " console.log('No packages installed.');",
72
+ " process.exit(0);",
73
+ " }",
74
+ " console.log('User packages:');",
75
+ " for (const entry of packages) console.log(' ' + sourceOf(entry));",
76
+ " process.exit(0);",
77
+ " }",
78
+ "",
79
+ " if (command === 'install') {",
80
+ " const target = resolvedArgument(argument);",
81
+ " const persisted = path.relative(dir, target) || '.';",
82
+ " const index = packages.findIndex((entry) => resolvedSource(entry) === target);",
83
+ " if (index === -1) {",
84
+ " packages.push(persisted);",
85
+ " } else if (typeof packages[index] === 'string') {",
86
+ " packages[index] = persisted;",
87
+ " } else {",
88
+ " packages[index] = { ...packages[index], source: persisted };",
89
+ " }",
90
+ " await writePackages(settings, packages);",
91
+ " console.log('Installed ' + argument);",
92
+ " process.exit(0);",
93
+ " }",
94
+ "",
95
+ " if (command === 'remove') {",
96
+ " const target = resolvedArgument(argument);",
97
+ " const next = packages.filter((entry) => resolvedSource(entry) !== target);",
98
+ " if (next.length === packages.length) {",
99
+ " console.error('No matching package found for ' + argument);",
100
+ " process.exit(1);",
101
+ " }",
102
+ " await writePackages(settings, next);",
103
+ " console.log('Removed ' + argument);",
104
+ " process.exit(0);",
105
+ " }",
106
+ "",
107
+ " process.exit(0);",
108
+ "})().catch((err) => {",
109
+ " console.error(err);",
110
+ " process.exit(1);",
111
+ "});",
112
+ ].join("\n");
113
+
114
+ /** Create a stateful fake `pi` binary in `dir`.
115
+ *
116
+ * The binary's own directory doubles as pi's settings directory, so a local
117
+ * package source is persisted relative to it exactly as real pi does.
118
+ * @param {string} dir Directory to hold bin, calls.log, settings.json
119
+ * @param {{ failWhen?: string|null, packages?: unknown[] }} [options]
120
+ * failWhen: invocations whose argv contains the substring exit 1;
121
+ * packages: initial `packages` array for settings.json
122
+ * @returns {Promise<{ bin: string, log: string, settingsPath: string }>} */
123
+ export async function makeFakePi(dir, { failWhen = null, packages = [] } = {}) {
124
+ const bin = join(dir, "pi");
125
+ const log = join(dir, "calls.log");
126
+ const settingsPath = join(dir, "settings.json");
127
+ await writeFile(bin, FAKE_PI_SCRIPT + "\n");
128
+ await chmod(bin, 0o755);
129
+ await writeFile(settingsPath, `${JSON.stringify({ packages }, null, 2)}\n`);
130
+ if (failWhen) await writeFile(join(dir, "failWhen.txt"), failWhen);
131
+ return { bin, log, settingsPath };
132
+ }