@blackbelt-technology/pi-agent-dashboard 0.2.9 → 0.4.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 (238) hide show
  1. package/AGENTS.md +64 -8
  2. package/README.md +308 -101
  3. package/docs/architecture.md +515 -16
  4. package/package.json +14 -7
  5. package/packages/extension/package.json +11 -3
  6. package/packages/extension/src/__tests__/ask-user-tool.test.ts +300 -3
  7. package/packages/extension/src/__tests__/enrich-model-metadata.test.ts +201 -0
  8. package/packages/extension/src/__tests__/fork-entryid-timing.test.ts +100 -0
  9. package/packages/extension/src/__tests__/git-info.test.ts +67 -55
  10. package/packages/extension/src/__tests__/openspec-poller.test.ts +101 -96
  11. package/packages/extension/src/__tests__/process-scanner-kill.test.ts +61 -0
  12. package/packages/extension/src/__tests__/provider-register-reload.test.ts +394 -0
  13. package/packages/extension/src/__tests__/server-auto-start.test.ts +95 -4
  14. package/packages/extension/src/__tests__/server-launcher.test.ts +16 -0
  15. package/packages/extension/src/ask-user-tool.ts +289 -20
  16. package/packages/extension/src/bridge.ts +107 -6
  17. package/packages/extension/src/command-handler.ts +34 -39
  18. package/packages/extension/src/dev-build.ts +1 -1
  19. package/packages/extension/src/git-info.ts +9 -19
  20. package/packages/extension/src/pi-env.d.ts +1 -0
  21. package/packages/extension/src/process-scanner.ts +72 -38
  22. package/packages/extension/src/prompt-expander.ts +25 -4
  23. package/packages/extension/src/provider-register.ts +304 -16
  24. package/packages/extension/src/server-auto-start.ts +27 -1
  25. package/packages/extension/src/server-launcher.ts +71 -27
  26. package/packages/server/package.json +17 -2
  27. package/packages/server/src/__tests__/auto-attach.test.ts +10 -1
  28. package/packages/server/src/__tests__/auto-shutdown.test.ts +8 -2
  29. package/packages/server/src/__tests__/bootstrap-queue.test.ts +120 -0
  30. package/packages/server/src/__tests__/bootstrap-routes.test.ts +125 -0
  31. package/packages/server/src/__tests__/bootstrap-state.test.ts +119 -0
  32. package/packages/server/src/__tests__/browse-endpoint.test.ts +246 -10
  33. package/packages/server/src/__tests__/browser-gateway-handler-errors.test.ts +129 -0
  34. package/packages/server/src/__tests__/cli-parse.test.ts +11 -0
  35. package/packages/server/src/__tests__/concurrent-launch.test.ts +110 -0
  36. package/packages/server/src/__tests__/config-api.test.ts +68 -0
  37. package/packages/server/src/__tests__/cors.test.ts +34 -2
  38. package/packages/server/src/__tests__/crash-recovery.test.ts +88 -0
  39. package/packages/server/src/__tests__/directory-service.test.ts +234 -8
  40. package/packages/server/src/__tests__/editor-manager-pid-registry.test.ts +168 -0
  41. package/packages/server/src/__tests__/editor-manager.test.ts +33 -0
  42. package/packages/server/src/__tests__/editor-pid-registry.test.ts +191 -0
  43. package/packages/server/src/__tests__/editor-registry.test.ts +29 -15
  44. package/packages/server/src/__tests__/extension-register-appimage.test.ts +5 -1
  45. package/packages/server/src/__tests__/extension-register.test.ts +3 -1
  46. package/packages/server/src/__tests__/find-port-holders.test.ts +94 -0
  47. package/packages/server/src/__tests__/fix-pty-permissions.test.ts +59 -0
  48. package/packages/server/src/__tests__/force-kill-handler.test.ts +57 -8
  49. package/packages/server/src/__tests__/git-operations.test.ts +9 -7
  50. package/packages/server/src/__tests__/health-endpoint.test.ts +11 -13
  51. package/packages/server/src/__tests__/home-lock-escape-hatch.test.ts +60 -0
  52. package/packages/server/src/__tests__/home-lock-release.test.ts +85 -0
  53. package/packages/server/src/__tests__/home-lock.test.ts +308 -0
  54. package/packages/server/src/__tests__/is-pi-process.test.ts +36 -0
  55. package/packages/server/src/__tests__/node-guard.test.ts +85 -0
  56. package/packages/server/src/__tests__/openspec-tasks-parser.test.ts +178 -0
  57. package/packages/server/src/__tests__/openspec-tasks-routes.test.ts +180 -0
  58. package/packages/server/src/__tests__/package-manager-wrapper-resolve.test.ts +126 -0
  59. package/packages/server/src/__tests__/package-manager-wrapper.test.ts +45 -10
  60. package/packages/server/src/__tests__/pi-core-checker.test.ts +195 -0
  61. package/packages/server/src/__tests__/pi-core-routes.test.ts +184 -0
  62. package/packages/server/src/__tests__/pi-core-updater.test.ts +214 -0
  63. package/packages/server/src/__tests__/pi-version-skew.test.ts +165 -0
  64. package/packages/server/src/__tests__/preferences-store.test.ts +73 -4
  65. package/packages/server/src/__tests__/process-manager.test.ts +45 -18
  66. package/packages/server/src/__tests__/provider-auth-routes.test.ts +13 -3
  67. package/packages/server/src/__tests__/provider-probe.test.ts +287 -0
  68. package/packages/server/src/__tests__/provider-test-route.test.ts +149 -0
  69. package/packages/server/src/__tests__/recommended-routes.test.ts +389 -0
  70. package/packages/server/src/__tests__/restart-helper.test.ts +83 -0
  71. package/packages/server/src/__tests__/session-action-handler-headless-reload.test.ts +467 -0
  72. package/packages/server/src/__tests__/session-action-handler-reload-predicate.test.ts +73 -0
  73. package/packages/server/src/__tests__/session-action-handler-spawn-error.test.ts +74 -0
  74. package/packages/server/src/__tests__/session-file-dedup.test.ts +10 -10
  75. package/packages/server/src/__tests__/session-lifecycle-logging.test.ts +8 -2
  76. package/packages/server/src/__tests__/sleep-aware-heartbeat.test.ts +3 -1
  77. package/packages/server/src/__tests__/smoke-integration.test.ts +10 -10
  78. package/packages/server/src/__tests__/terminal-manager.test.ts +41 -1
  79. package/packages/server/src/__tests__/test-server-canary.test.ts +31 -0
  80. package/packages/server/src/__tests__/tool-routes.test.ts +277 -0
  81. package/packages/server/src/__tests__/trusted-networks-config.test.ts +19 -0
  82. package/packages/server/src/__tests__/trusted-networks-no-oauth-roundtrip.test.ts +126 -0
  83. package/packages/server/src/__tests__/tunnel-cleanup.test.ts +90 -0
  84. package/packages/server/src/__tests__/tunnel.test.ts +103 -6
  85. package/packages/server/src/__tests__/ws-ping-pong.test.ts +10 -2
  86. package/packages/server/src/__tests__/wsl-tmux-probe-cache.test.ts +44 -0
  87. package/packages/server/src/bootstrap-queue.ts +130 -0
  88. package/packages/server/src/bootstrap-state.ts +131 -0
  89. package/packages/server/src/browse.ts +108 -9
  90. package/packages/server/src/browser-gateway.ts +16 -3
  91. package/packages/server/src/browser-handlers/directory-handler.ts +23 -8
  92. package/packages/server/src/browser-handlers/session-action-handler.ts +213 -79
  93. package/packages/server/src/browser-handlers/session-action-helpers.ts +36 -0
  94. package/packages/server/src/cli.ts +256 -32
  95. package/packages/server/src/config-api.ts +16 -0
  96. package/packages/server/src/directory-service.ts +270 -39
  97. package/packages/server/src/editor-detection.ts +12 -9
  98. package/packages/server/src/editor-manager.ts +39 -5
  99. package/packages/server/src/editor-pid-registry.ts +199 -0
  100. package/packages/server/src/editor-registry.ts +22 -25
  101. package/packages/server/src/fix-pty-permissions.ts +44 -0
  102. package/packages/server/src/git-operations.ts +1 -1
  103. package/packages/server/src/headless-pid-registry.ts +16 -20
  104. package/packages/server/src/home-lock-release.ts +72 -0
  105. package/packages/server/src/home-lock.ts +389 -0
  106. package/packages/server/src/node-guard.ts +52 -0
  107. package/packages/server/src/npm-search-proxy.ts +71 -0
  108. package/packages/server/src/openspec-tasks.ts +158 -0
  109. package/packages/server/src/package-manager-wrapper.ts +225 -34
  110. package/packages/server/src/pi-core-checker.ts +290 -0
  111. package/packages/server/src/pi-core-updater.ts +172 -0
  112. package/packages/server/src/pi-gateway.ts +7 -0
  113. package/packages/server/src/pi-resource-scanner.ts +5 -8
  114. package/packages/server/src/pi-version-skew.ts +196 -0
  115. package/packages/server/src/preferences-store.ts +17 -3
  116. package/packages/server/src/process-manager.ts +403 -222
  117. package/packages/server/src/provider-probe.ts +234 -0
  118. package/packages/server/src/restart-helper.ts +130 -0
  119. package/packages/server/src/routes/bootstrap-routes.ts +88 -0
  120. package/packages/server/src/routes/file-routes.ts +30 -3
  121. package/packages/server/src/routes/openspec-routes.ts +107 -1
  122. package/packages/server/src/routes/pi-core-routes.ts +140 -0
  123. package/packages/server/src/routes/provider-auth-routes.ts +12 -10
  124. package/packages/server/src/routes/provider-routes.ts +55 -2
  125. package/packages/server/src/routes/recommended-routes.ts +225 -0
  126. package/packages/server/src/routes/system-routes.ts +30 -34
  127. package/packages/server/src/routes/tool-routes.ts +153 -0
  128. package/packages/server/src/server-pid.ts +5 -9
  129. package/packages/server/src/server.ts +363 -26
  130. package/packages/server/src/session-api.ts +77 -8
  131. package/packages/server/src/session-bootstrap.ts +17 -3
  132. package/packages/server/src/session-diff.ts +21 -21
  133. package/packages/server/src/terminal-manager.ts +65 -20
  134. package/packages/server/src/test-env-guard.ts +26 -0
  135. package/packages/server/src/test-support/test-server.ts +63 -0
  136. package/packages/server/src/tunnel.ts +172 -34
  137. package/packages/shared/package.json +10 -3
  138. package/packages/shared/src/__tests__/{tool-resolver.test.ts → binary-lookup.test.ts} +32 -12
  139. package/packages/shared/src/__tests__/bootstrap/README.md +133 -0
  140. package/packages/shared/src/__tests__/bootstrap/__snapshots__/cube.test.ts.snap +370 -0
  141. package/packages/shared/src/__tests__/bootstrap/assertions.ts +136 -0
  142. package/packages/shared/src/__tests__/bootstrap/cube.test.ts +47 -0
  143. package/packages/shared/src/__tests__/bootstrap/cube.ts +66 -0
  144. package/packages/shared/src/__tests__/bootstrap/families/__snapshots__/a-electron.test.ts.snap +83 -0
  145. package/packages/shared/src/__tests__/bootstrap/families/__snapshots__/b-npm-global.test.ts.snap +89 -0
  146. package/packages/shared/src/__tests__/bootstrap/families/__snapshots__/c-dev-monorepo.test.ts.snap +33 -0
  147. package/packages/shared/src/__tests__/bootstrap/families/__snapshots__/d-overrides.test.ts.snap +20 -0
  148. package/packages/shared/src/__tests__/bootstrap/families/__snapshots__/e-stale-partial.test.ts.snap +61 -0
  149. package/packages/shared/src/__tests__/bootstrap/families/__snapshots__/f-cwd-variants.test.ts.snap +33 -0
  150. package/packages/shared/src/__tests__/bootstrap/families/__snapshots__/g-windows-specifics.test.ts.snap +46 -0
  151. package/packages/shared/src/__tests__/bootstrap/families/__snapshots__/j-path-gui-minimal.test.ts.snap +12 -0
  152. package/packages/shared/src/__tests__/bootstrap/families/a-electron.test.ts +156 -0
  153. package/packages/shared/src/__tests__/bootstrap/families/b-npm-global.test.ts +157 -0
  154. package/packages/shared/src/__tests__/bootstrap/families/c-dev-monorepo.test.ts +102 -0
  155. package/packages/shared/src/__tests__/bootstrap/families/d-overrides.test.ts +76 -0
  156. package/packages/shared/src/__tests__/bootstrap/families/e-stale-partial.test.ts +94 -0
  157. package/packages/shared/src/__tests__/bootstrap/families/f-cwd-variants.test.ts +87 -0
  158. package/packages/shared/src/__tests__/bootstrap/families/g-windows-specifics.test.ts +143 -0
  159. package/packages/shared/src/__tests__/bootstrap/families/h-home-drift.test.ts +64 -0
  160. package/packages/shared/src/__tests__/bootstrap/families/i-malformed-settings.test.ts +77 -0
  161. package/packages/shared/src/__tests__/bootstrap/families/index.ts +19 -0
  162. package/packages/shared/src/__tests__/bootstrap/families/j-path-gui-minimal.test.ts +61 -0
  163. package/packages/shared/src/__tests__/bootstrap/families/k-dashboard-absent.test.ts +50 -0
  164. package/packages/shared/src/__tests__/bootstrap/families/l-instance-coordination.test.ts +272 -0
  165. package/packages/shared/src/__tests__/bootstrap/fixtures/dev-monorepo.ts +58 -0
  166. package/packages/shared/src/__tests__/bootstrap/fixtures/electron-layout.ts +84 -0
  167. package/packages/shared/src/__tests__/bootstrap/fixtures/index.ts +9 -0
  168. package/packages/shared/src/__tests__/bootstrap/fixtures/managed-install.ts +85 -0
  169. package/packages/shared/src/__tests__/bootstrap/fixtures/npm-global-layout.ts +122 -0
  170. package/packages/shared/src/__tests__/bootstrap/fixtures/pi-versions.ts +36 -0
  171. package/packages/shared/src/__tests__/bootstrap/fixtures/settings-json.ts +39 -0
  172. package/packages/shared/src/__tests__/bootstrap/harness.smoke.test.ts +220 -0
  173. package/packages/shared/src/__tests__/bootstrap/harness.ts +413 -0
  174. package/packages/shared/src/__tests__/bootstrap/scenarios-skipped.ts +125 -0
  175. package/packages/shared/src/__tests__/bootstrap/scenarios.ts +132 -0
  176. package/packages/shared/src/__tests__/bridge-register.test.ts +29 -6
  177. package/packages/shared/src/__tests__/config-openspec.test.ts +106 -0
  178. package/packages/shared/src/__tests__/config.test.ts +59 -3
  179. package/packages/shared/src/__tests__/detached-spawn.test.ts +243 -0
  180. package/packages/shared/src/__tests__/managed-paths.test.ts +60 -0
  181. package/packages/shared/src/__tests__/no-direct-child-process.test.ts +112 -0
  182. package/packages/shared/src/__tests__/no-direct-platform-branch.test.ts +174 -0
  183. package/packages/shared/src/__tests__/no-direct-process-kill.test.ts +105 -0
  184. package/packages/shared/src/__tests__/openspec-poller.test.ts +44 -0
  185. package/packages/shared/src/__tests__/platform-commands.test.ts +108 -0
  186. package/packages/shared/src/__tests__/platform-exec.test.ts +103 -0
  187. package/packages/shared/src/__tests__/platform-git.test.ts +194 -0
  188. package/packages/shared/src/__tests__/platform-npm.test.ts +137 -0
  189. package/packages/shared/src/__tests__/platform-openspec.test.ts +92 -0
  190. package/packages/shared/src/__tests__/platform-paths.test.ts +284 -0
  191. package/packages/shared/src/__tests__/platform-process-scan.test.ts +55 -0
  192. package/packages/shared/src/__tests__/platform-process.test.ts +160 -0
  193. package/packages/shared/src/__tests__/platform-runner.test.ts +173 -0
  194. package/packages/shared/src/__tests__/platform-shell.test.ts +74 -0
  195. package/packages/shared/src/__tests__/process-identify.test.ts +113 -0
  196. package/packages/shared/src/__tests__/recommended-extensions.test.ts +156 -0
  197. package/packages/shared/src/__tests__/resolve-jiti.test.ts +43 -7
  198. package/packages/shared/src/__tests__/semaphore.test.ts +119 -0
  199. package/packages/shared/src/__tests__/source-matching.test.ts +143 -0
  200. package/packages/shared/src/__tests__/spawn-mechanism.test.ts +131 -0
  201. package/packages/shared/src/__tests__/tool-registry-definitions.test.ts +239 -0
  202. package/packages/shared/src/__tests__/tool-registry-overrides.test.ts +137 -0
  203. package/packages/shared/src/__tests__/tool-registry-registry.test.ts +343 -0
  204. package/packages/shared/src/bootstrap-install.ts +212 -0
  205. package/packages/shared/src/bridge-register.ts +87 -20
  206. package/packages/shared/src/browser-protocol.ts +93 -1
  207. package/packages/shared/src/config.ts +87 -15
  208. package/packages/shared/src/managed-paths.ts +31 -4
  209. package/packages/shared/src/openspec-poller.ts +71 -49
  210. package/packages/shared/src/{tool-resolver.ts → platform/binary-lookup.ts} +125 -25
  211. package/packages/shared/src/platform/commands.ts +100 -0
  212. package/packages/shared/src/platform/detached-spawn.ts +305 -0
  213. package/packages/shared/src/platform/exec.ts +220 -0
  214. package/packages/shared/src/platform/git.ts +155 -0
  215. package/packages/shared/src/platform/index.ts +15 -0
  216. package/packages/shared/src/platform/npm.ts +162 -0
  217. package/packages/shared/src/platform/openspec.ts +91 -0
  218. package/packages/shared/src/platform/paths.ts +276 -0
  219. package/packages/shared/src/platform/process-identify.ts +126 -0
  220. package/packages/shared/src/platform/process-scan.ts +94 -0
  221. package/packages/shared/src/platform/process.ts +168 -0
  222. package/packages/shared/src/platform/runner.ts +369 -0
  223. package/packages/shared/src/platform/shell.ts +44 -0
  224. package/packages/shared/src/platform/spawn-mechanism.ts +124 -0
  225. package/packages/shared/src/platform/subprocess-adapter.ts +124 -0
  226. package/packages/shared/src/recommended-extensions.ts +196 -0
  227. package/packages/shared/src/resolve-jiti.ts +62 -3
  228. package/packages/shared/src/rest-api.ts +97 -0
  229. package/packages/shared/src/semaphore.ts +83 -0
  230. package/packages/shared/src/source-matching.ts +126 -0
  231. package/packages/shared/src/test-support/setup-home.ts +74 -0
  232. package/packages/shared/src/tool-registry/definitions.ts +342 -0
  233. package/packages/shared/src/tool-registry/index.ts +56 -0
  234. package/packages/shared/src/tool-registry/overrides.ts +118 -0
  235. package/packages/shared/src/tool-registry/registry.ts +262 -0
  236. package/packages/shared/src/tool-registry/strategies.ts +198 -0
  237. package/packages/shared/src/tool-registry/types.ts +180 -0
  238. package/packages/shared/src/types.ts +7 -0
@@ -0,0 +1,143 @@
1
+ /**
2
+ * Family G — Windows specifics.
3
+ *
4
+ * G1: win-cmd-shim — pi.cmd found; `toArgv` MUST prepend node.exe.
5
+ * G2: win-appdata-roaming — npm-g installed at %APPDATA%\Roaming\npm.
6
+ * G3: win-programfiles-cwd — cwd under "C:\Program Files (x86)\..."
7
+ * (covered in F1-win; add a G-variant with
8
+ * pi resolution via npm-g).
9
+ * G4: win-programfiles-node — node.exe at "C:\Program Files\nodejs".
10
+ */
11
+ import { describe, expect, it } from "vitest";
12
+ import { withFakeEnv, layer } from "../harness.js";
13
+ import { registerDefaultTools } from "../../../tool-registry/definitions.js";
14
+ import * as fixtures from "../fixtures/index.js";
15
+ import { snapshotTrail } from "../assertions.js";
16
+ import { register, SKIPPED_SCENARIOS, cellKey } from "../scenarios.js";
17
+
18
+ // All Family G cells are win32-only.
19
+ const G = [
20
+ // G1 is already covered by B2 (npm-g on win32); this family focuses
21
+ // on specific layout variants.
22
+ { platform: "win32", dash: "managed", pi: "present-valid", settings: "valid", env: "normal" },
23
+ { platform: "win32", dash: "npm-g", pi: "present-valid", settings: "valid", env: "normal" },
24
+ ] as const;
25
+ for (const cell of G) {
26
+ register(cell, "families/g-windows-specifics.test.ts");
27
+ SKIPPED_SCENARIOS.delete(cellKey(cell));
28
+ }
29
+
30
+ describe("Family G — Windows specifics", () => {
31
+ it("G1 — pi.cmd resolved + toArgv prepends node.exe (no-cmd-flash)", async () => {
32
+ // Managed install with pi-coding-agent at a real module path, NOT
33
+ // the .bin/pi.cmd shim. This forces resolution through
34
+ // managedModuleStrategy (matches `@mariozechner/pi-coding-agent/
35
+ // dist/cli.js`), which is a Node script — `toArgv` prepends
36
+ // node.exe. That's the no-cmd-flash invariant.
37
+ const homedir = "C:\\Users\\R";
38
+ await withFakeEnv(
39
+ {
40
+ platform: "win32",
41
+ homedir,
42
+ env: { PATH: "C:\\Program Files\\nodejs" },
43
+ fs: layer(
44
+ fixtures.managedInstall({ homedir, platform: "win32" }),
45
+ {
46
+ // node.exe must be resolvable for toArgv to prepend it.
47
+ "C:\\Program Files\\nodejs\\node.exe": "\x7fELF",
48
+ },
49
+ ),
50
+ },
51
+ (ctx) => {
52
+ const registry = ctx.createRegistry();
53
+ registerDefaultTools(registry, ctx.createStrategyDeps());
54
+ const executor = registry.resolveExecutor("pi");
55
+ expect(executor.ok).toBe(true);
56
+ expect(executor.path?.endsWith("cli.js")).toBe(true);
57
+ // No-cmd-flash invariant: argv[0] MUST be node.exe, NOT the
58
+ // .cmd shim or cmd.exe. The snapshot locks this in.
59
+ expect(executor.argv[0]).toBe("C:\\Program Files\\nodejs\\node.exe");
60
+ expect(executor.argv).toHaveLength(2);
61
+ expect(snapshotTrail(executor, ctx)).toMatchSnapshot();
62
+ },
63
+ );
64
+ });
65
+
66
+ it("G2 — npm-g at %APPDATA%\\Roaming\\npm (argv prepends node.exe)", async () => {
67
+ const homedir = "C:\\Users\\R";
68
+ await withFakeEnv(
69
+ {
70
+ platform: "win32",
71
+ homedir,
72
+ env: {
73
+ PATH: "C:\\Users\\R\\AppData\\Roaming\\npm;C:\\Program Files\\nodejs",
74
+ APPDATA: "C:\\Users\\R\\AppData\\Roaming",
75
+ },
76
+ fs: layer(
77
+ fixtures.npmGlobalWindowsAppData(homedir, { dashboard: false }),
78
+ { "C:\\Program Files\\nodejs\\node.exe": "\x7fELF" },
79
+ ),
80
+ },
81
+ (ctx) => {
82
+ const registry = ctx.createRegistry();
83
+ registerDefaultTools(registry, ctx.createStrategyDeps());
84
+ const executor = registry.resolveExecutor("pi");
85
+ expect(executor.ok).toBe(true);
86
+ expect(executor.source).toBe("npm-global");
87
+ // Same no-cmd-flash invariant: even for npm-g, argv routes
88
+ // through node.exe.
89
+ expect(executor.argv[0]).toBe("C:\\Program Files\\nodejs\\node.exe");
90
+ expect(snapshotTrail(executor, ctx)).toMatchSnapshot();
91
+ },
92
+ );
93
+ });
94
+
95
+ it("G3 — cwd under Program Files (x86) does not affect resolution", async () => {
96
+ // Spec requires this cell tested; covered structurally by F1-win,
97
+ // but a dedicated block documents the invariant alongside the
98
+ // other G cells.
99
+ const homedir = "C:\\Users\\R";
100
+ await withFakeEnv(
101
+ {
102
+ platform: "win32",
103
+ homedir,
104
+ cwd: "C:\\Program Files (x86)\\Pi Dashboard",
105
+ fs: fixtures.managedInstall({ homedir, platform: "win32" }),
106
+ },
107
+ (ctx) => {
108
+ const registry = ctx.createRegistry();
109
+ registerDefaultTools(registry, ctx.createStrategyDeps());
110
+ const res = registry.resolve("pi");
111
+ expect(res.ok).toBe(true);
112
+ expect(res.source).toBe("managed");
113
+ },
114
+ );
115
+ });
116
+
117
+ it("G4 — node.exe at C:\\Program Files\\nodejs\\node.exe", async () => {
118
+ const homedir = "C:\\Users\\R";
119
+ await withFakeEnv(
120
+ {
121
+ platform: "win32",
122
+ homedir,
123
+ env: { PATH: "C:\\Program Files\\nodejs" },
124
+ fs: layer(
125
+ fixtures.managedInstall({ homedir, platform: "win32" }),
126
+ {
127
+ "C:\\Program Files\\nodejs\\node.exe": "\x7fELF",
128
+ },
129
+ ),
130
+ },
131
+ (ctx) => {
132
+ const registry = ctx.createRegistry();
133
+ registerDefaultTools(registry, ctx.createStrategyDeps());
134
+ const nodeRes = registry.resolveExecutor("node");
135
+ expect(nodeRes.ok).toBe(true);
136
+ expect(nodeRes.path).toBe("C:\\Program Files\\nodejs\\node.exe");
137
+ // Binary-kind tool: argv = [path] (no interpreter prepended).
138
+ expect(nodeRes.argv).toEqual(["C:\\Program Files\\nodejs\\node.exe"]);
139
+ expect(snapshotTrail(nodeRes, ctx)).toMatchSnapshot();
140
+ },
141
+ );
142
+ });
143
+ });
@@ -0,0 +1,64 @@
1
+ /**
2
+ * Family H — HOME drift scenarios.
3
+ *
4
+ * H1: home-drift-git-bash — Windows: $HOME=/c/Users/R set by Git Bash,
5
+ * USERPROFILE=C:\Users\R, os.homedir()=C:\Users\R. All paths must
6
+ * canonicalize to the same settings.json location.
7
+ * H2: home-symlink — posix: homedir is a symlink (common with
8
+ * filevault / dotfile managers). Today the harness does not
9
+ * simulate symlinks (memfs limitation), so H2 is documented and
10
+ * covered by scenarios-skipped.ts. This file adds a placeholder
11
+ * test documenting the invariant.
12
+ *
13
+ * These scenarios exercise `registerBridgeExtension`'s homedir
14
+ * resolution. They don't touch ToolRegistry (bridge registration is
15
+ * an independent resolution problem per design §2).
16
+ */
17
+ import { describe, expect, it } from "vitest";
18
+ import { withFakeEnv } from "../harness.js";
19
+ import * as fixtures from "../fixtures/index.js";
20
+ import { register, SKIPPED_SCENARIOS, cellKey } from "../scenarios.js";
21
+
22
+ const H = [
23
+ { platform: "win32", dash: "managed", pi: "present-valid", settings: "valid", env: "home-drift" },
24
+ ] as const;
25
+ for (const cell of H) {
26
+ register(cell, "families/h-home-drift.test.ts");
27
+ SKIPPED_SCENARIOS.delete(cellKey(cell));
28
+ }
29
+
30
+ describe("Family H — HOME drift", () => {
31
+ it("H1 — Git Bash \\$HOME vs USERPROFILE both reach same canonical homedir", async () => {
32
+ // This test documents the EXPECTED behavior. Full enforcement
33
+ // lives in `single-dashboard-per-home` (Layer 0 canonicalization).
34
+ // Here we only verify that `registerBridgeExtension` accepts an
35
+ // explicit homedir and uses it over env vars.
36
+ const canonicalHome = "C:\\Users\\R";
37
+ await withFakeEnv(
38
+ {
39
+ platform: "win32",
40
+ homedir: canonicalHome,
41
+ env: {
42
+ // Simulated drift: $HOME disagrees with USERPROFILE. The
43
+ // explicit `{ homedir }` argument SHOULD win over env vars.
44
+ HOME: "/c/Users/R",
45
+ USERPROFILE: canonicalHome,
46
+ },
47
+ fs: fixtures.settingsJson({
48
+ homedir: canonicalHome,
49
+ platform: "win32",
50
+ packages: [],
51
+ }),
52
+ },
53
+ (ctx) => {
54
+ // Harness provides the fake homedir to registerBridgeExtension
55
+ // via the new opts arg. We don't actually call it here (it
56
+ // uses real fs) — instead we assert the settings.json path
57
+ // the harness reports matches the canonical homedir.
58
+ const settings = ctx.readSettings();
59
+ expect(settings).not.toBeNull();
60
+ expect(settings).toEqual({ packages: [] });
61
+ },
62
+ );
63
+ });
64
+ });
@@ -0,0 +1,77 @@
1
+ /**
2
+ * Family I — settings.json shape variants.
3
+ *
4
+ * I1: malformed-settings — broken JSON in settings.json.
5
+ * I2: settings-other-packages — settings contains unrelated extensions
6
+ * that MUST be preserved across bridge registration.
7
+ *
8
+ * Bridge-registration semantics live in `registerBridgeExtension`,
9
+ * which uses node:fs directly. The harness can only assert input
10
+ * (fake settings.json shape visible via readSettings) — full round-trip
11
+ * asserting preservation lands when bridge-register is refactored
12
+ * to accept an injectable fs (future task, cross-proposal).
13
+ */
14
+ import { describe, expect, it } from "vitest";
15
+ import { withFakeEnv } from "../harness.js";
16
+ import * as fixtures from "../fixtures/index.js";
17
+ import { register, SKIPPED_SCENARIOS, cellKey } from "../scenarios.js";
18
+
19
+ const I = [
20
+ { platform: "linux", dash: "managed", pi: "present-valid", settings: "malformed", env: "normal" },
21
+ ] as const;
22
+ for (const cell of I) {
23
+ register(cell, "families/i-malformed-settings.test.ts");
24
+ SKIPPED_SCENARIOS.delete(cellKey(cell));
25
+ }
26
+
27
+ describe("Family I — settings.json variants", () => {
28
+ it("I1 — malformed JSON surfaces as null from readSettings", async () => {
29
+ const homedir = "/home/r";
30
+ await withFakeEnv(
31
+ {
32
+ platform: "linux",
33
+ homedir,
34
+ fs: fixtures.settingsJson({
35
+ homedir,
36
+ platform: "linux",
37
+ malformed: true,
38
+ }),
39
+ },
40
+ (ctx) => {
41
+ // readSettings returns null for malformed JSON — tolerant
42
+ // fallback behavior. Consumers (registerBridgeExtension)
43
+ // treat null as "start fresh".
44
+ expect(ctx.readSettings()).toBeNull();
45
+ },
46
+ );
47
+ });
48
+
49
+ it("I2 — settings with unrelated packages is preserved in fixture", async () => {
50
+ const homedir = "/home/r";
51
+ await withFakeEnv(
52
+ {
53
+ platform: "linux",
54
+ homedir,
55
+ fs: fixtures.settingsJson({
56
+ homedir,
57
+ platform: "linux",
58
+ packages: [
59
+ "/home/r/.pi/extensions/custom-pkg",
60
+ "/home/r/.pi/extensions/another-pkg",
61
+ ],
62
+ }),
63
+ },
64
+ (ctx) => {
65
+ const settings = ctx.readSettings();
66
+ expect(settings).toEqual({
67
+ packages: [
68
+ "/home/r/.pi/extensions/custom-pkg",
69
+ "/home/r/.pi/extensions/another-pkg",
70
+ ],
71
+ });
72
+ // Full round-trip preservation test: pending bridge-register
73
+ // fs injection. Asserted at input side only for now.
74
+ },
75
+ );
76
+ });
77
+ });
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Family-test barrel — imports every family file for its registration
3
+ * side-effects so `cube.test.ts` can sweep a fully-populated
4
+ * REGISTERED_SCENARIOS map.
5
+ *
6
+ * Each family file registers at module top-level via `register(cell, tag)`.
7
+ * When a new family file is added, import it here.
8
+ */
9
+ import "./a-electron.test.js";
10
+ import "./b-npm-global.test.js";
11
+ import "./c-dev-monorepo.test.js";
12
+ import "./d-overrides.test.js";
13
+ import "./e-stale-partial.test.js";
14
+ import "./f-cwd-variants.test.js";
15
+ import "./g-windows-specifics.test.js";
16
+ import "./h-home-drift.test.js";
17
+ import "./i-malformed-settings.test.js";
18
+ import "./j-path-gui-minimal.test.js";
19
+ import "./k-dashboard-absent.test.js";
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Family J — minimal PATH (GUI-launched processes).
3
+ *
4
+ * J1: path-gui-minimal — PATH contains only /usr/bin (no /usr/local/bin,
5
+ * no ~/.npm). Typical of GUI-launched processes on macOS/Linux.
6
+ * Resolution should still succeed via npm-global strategy, which
7
+ * uses `npm root -g` not PATH.
8
+ */
9
+ import { describe, expect, it } from "vitest";
10
+ import { withFakeEnv } from "../harness.js";
11
+ import { registerDefaultTools } from "../../../tool-registry/definitions.js";
12
+ import * as fixtures from "../fixtures/index.js";
13
+ import { snapshotTrail } from "../assertions.js";
14
+ import { register, SKIPPED_SCENARIOS, cellKey } from "../scenarios.js";
15
+
16
+ const J = [
17
+ // Use "absent" dash axis — the dashboard itself isn't relevant; we
18
+ // only care about pi resolution.
19
+ { platform: "linux", dash: "absent", pi: "present-valid", settings: "empty", env: "normal" },
20
+ ] as const;
21
+ for (const cell of J) {
22
+ register(cell, "families/j-path-gui-minimal.test.ts");
23
+ SKIPPED_SCENARIOS.delete(cellKey(cell));
24
+ }
25
+
26
+ describe("Family J — minimal PATH", () => {
27
+ it("J1 — GUI-launched minimal PATH: pi does NOT resolve on posix (limitation)", async () => {
28
+ const homedir = "/home/r";
29
+ await withFakeEnv(
30
+ {
31
+ platform: "linux",
32
+ homedir,
33
+ // The minimal PATH a macOS GUI-launched app sees by default.
34
+ env: { PATH: "/usr/bin" },
35
+ // pi + openspec live in /usr/local/bin, NOT in PATH.
36
+ fs: fixtures.npmGlobalUnix({
37
+ root: "/usr/lib/node_modules",
38
+ binDir: "/usr/local/bin",
39
+ }),
40
+ npmRootGlobal: "/usr/lib/node_modules",
41
+ },
42
+ (ctx) => {
43
+ const registry = ctx.createRegistry();
44
+ registerDefaultTools(registry, ctx.createStrategyDeps());
45
+ const res = registry.resolve("pi");
46
+ // On Unix, the pi chain is override → managed-bin → where.
47
+ // npm-g strategy is NOT in the Unix pi chain; with PATH missing
48
+ // `/usr/local/bin`, `where` can't find pi either. This is a
49
+ // real limitation worth locking in via snapshot — if a future
50
+ // change adds npm-g to the Unix pi chain, this test surfaces
51
+ // it loudly.
52
+ expect(res.ok).toBe(false);
53
+ expect(snapshotTrail(res, ctx)).toMatchSnapshot();
54
+
55
+ // Same limitation for openspec on Unix.
56
+ const os = registry.resolve("openspec");
57
+ expect(os.ok).toBe(false);
58
+ },
59
+ );
60
+ });
61
+ });
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Family K — dashboard absent.
3
+ *
4
+ * K1: no dashboard anywhere; pi present. Registry behaves normally for
5
+ * pi. The dashboard itself isn't registered in ToolRegistry (it's
6
+ * the package this code is part of), so "dashboard absent" is
7
+ * observable only at the dependency-detector level — out of scope
8
+ * for this family.
9
+ *
10
+ * Kept as a minimal registration + assertion so the cell appears in
11
+ * the cube.
12
+ */
13
+ import { describe, expect, it } from "vitest";
14
+ import { withFakeEnv } from "../harness.js";
15
+ import { registerDefaultTools } from "../../../tool-registry/definitions.js";
16
+ import * as fixtures from "../fixtures/index.js";
17
+ import { register, SKIPPED_SCENARIOS, cellKey } from "../scenarios.js";
18
+
19
+ const K = [
20
+ { platform: "linux", dash: "absent", pi: "present-valid", settings: "valid", env: "normal" },
21
+ ] as const;
22
+ for (const cell of K) {
23
+ register(cell, "families/k-dashboard-absent.test.ts");
24
+ SKIPPED_SCENARIOS.delete(cellKey(cell));
25
+ }
26
+
27
+ describe("Family K — dashboard absent", () => {
28
+ it("pi still resolves when no dashboard binary is installed", async () => {
29
+ const homedir = "/home/r";
30
+ await withFakeEnv(
31
+ {
32
+ platform: "linux",
33
+ homedir,
34
+ fs: fixtures.managedInstall({ homedir, platform: "linux" }),
35
+ },
36
+ (ctx) => {
37
+ const registry = ctx.createRegistry();
38
+ registerDefaultTools(registry, ctx.createStrategyDeps());
39
+ const res = registry.resolve("pi");
40
+ expect(res.ok).toBe(true);
41
+ expect(res.source).toBe("managed");
42
+ // "Dashboard absence" is not observable at the registry level —
43
+ // the dashboard isn't a registered tool. The observation
44
+ // happens at `dependency-detector.ts:detectPiDashboardCli()`
45
+ // via `which pi-dashboard`. Covered by dependency-detector
46
+ // unit tests, not this harness.
47
+ },
48
+ );
49
+ });
50
+ });