gentle-pi 4.1.0 → 4.3.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 (249) hide show
  1. package/README.md +352 -128
  2. package/assets/agents/gentle-ai-explore.md +1 -0
  3. package/assets/agents/gentle-ai-verify.md +6 -4
  4. package/assets/agents/gentle-ai-worker.md +7 -1
  5. package/assets/orchestrator-delegation-reference.md +163 -0
  6. package/assets/orchestrator-delegation.md +25 -176
  7. package/assets/orchestrator-tracking.md +1 -1
  8. package/assets/orchestrator-verification.md +14 -8
  9. package/assets/orchestrator-writer.md +5 -1
  10. package/assets/orchestrator.md +9 -9
  11. package/bin/engram-guard +1 -1
  12. package/bin/gentle-shell.mjs +188 -35
  13. package/contracts/review-provider-contract-mirror/provider-contract.lock.json +8 -7
  14. package/contracts/review-provider-contract-mirror/{v1.3.0 → v1.4.0}/bundle/README.md +7 -0
  15. package/contracts/review-provider-contract-mirror/{v1.3.0 → v1.4.0}/bundle/manifest.json +11 -2
  16. package/contracts/review-provider-contract-mirror/v1.4.0/bundle/routing/pi.md +92 -0
  17. package/contracts/review-provider-contract-mirror/{v1.3.0 → v1.4.0}/generated/provider-capabilities.baseline.json +8 -1
  18. package/contracts/review-provider-contract-mirror/{v1.3.0 → v1.4.0}/generated/provider-roles.baseline.json +1 -1
  19. package/docs/assets/diagrams/agent-orchestration.svg +54 -15
  20. package/docs/assets/diagrams/gentleman-workflow.svg +69 -11
  21. package/docs/assets/diagrams/measured-routing.svg +49 -0
  22. package/docs/assets/diagrams/native-review.svg +52 -12
  23. package/docs/assets/diagrams/odd-workflow.svg +128 -66
  24. package/docs/assets/diagrams/permission-modes.svg +51 -0
  25. package/docs/assets/features/audio-notifications.png +0 -0
  26. package/docs/assets/features/command-palette.png +0 -0
  27. package/docs/auto-permission-answers.md +25 -0
  28. package/docs/auto-permission-evidence.md +39 -0
  29. package/docs/bundled-install.md +40 -8
  30. package/docs/delegated-verification.md +10 -8
  31. package/docs/gentle-agents-activity.md +11 -5
  32. package/docs/gentle-shell-uninstall.md +8 -1
  33. package/docs/gentle-shell.md +20 -12
  34. package/docs/install-wizard.md +17 -10
  35. package/docs/permission-modes.md +51 -6
  36. package/docs/readme-reference.md +155 -31
  37. package/docs/session-profile-format.md +39 -0
  38. package/docs/yolo-mode.md +21 -7
  39. package/extensions/ask-user-choice.ts +4 -0
  40. package/extensions/ask-user-question.ts +6 -1
  41. package/extensions/codegraph-tools.ts +68 -6
  42. package/extensions/gentle-agents.ts +146 -28
  43. package/extensions/gentle-ai.ts +380 -68
  44. package/extensions/gentle-herdr-activity.ts +2 -3
  45. package/extensions/gentle-shell.ts +53 -43
  46. package/extensions/gentle-todo.ts +62 -18
  47. package/extensions/quiet-tools.ts +1 -1
  48. package/extensions/session-scratch.ts +365 -0
  49. package/extensions/startup-banner.ts +2 -3
  50. package/lib/agent-home.ts +8 -3
  51. package/lib/agents-protocol.ts +3 -0
  52. package/lib/agents-runner.ts +55 -6
  53. package/lib/agents-view.ts +3 -1
  54. package/lib/auto-permission-answers.ts +133 -0
  55. package/lib/auto-permission-classifier.ts +215 -36
  56. package/lib/auto-permission-evidence.ts +88 -0
  57. package/lib/auto-permission-memory.ts +69 -0
  58. package/lib/auto-permission-runtime.ts +251 -35
  59. package/lib/auto-permission-tool-context.ts +61 -0
  60. package/lib/bounded-writer-admission.ts +51 -0
  61. package/lib/command-palette-catalog.ts +0 -4
  62. package/lib/commit-slice-signal.ts +100 -0
  63. package/lib/context-backstop.ts +84 -0
  64. package/lib/delivered-test-lint.ts +80 -0
  65. package/lib/destructive-command-guard.ts +215 -28
  66. package/lib/gauge-theme.ts +146 -0
  67. package/lib/gentle-ai-binary.ts +28 -7
  68. package/lib/gentle-shell-bundled.ts +62 -8
  69. package/lib/gentle-shell-import-pi.ts +233 -0
  70. package/lib/gentle-shell-launcher.ts +146 -12
  71. package/lib/gentle-shell-uninstall.ts +18 -1
  72. package/lib/herdr-activity.ts +24 -0
  73. package/lib/model-routing-authority.ts +6 -1
  74. package/lib/native-review-cli.ts +9 -1
  75. package/lib/orchestrator-discovery.ts +5 -1
  76. package/lib/orchestrator-presence.ts +9 -0
  77. package/lib/orchestrator-scope.ts +21 -3
  78. package/lib/provider-contract-bundle.ts +99 -53
  79. package/lib/review-candidate-view-owner.ts +29 -19
  80. package/lib/review-candidate-view.ts +36 -2
  81. package/lib/review-risk-assessment.ts +43 -38
  82. package/lib/review-session-standing-permission.ts +3 -1
  83. package/lib/selection-engine.ts +4 -1
  84. package/lib/session-profile-freeze.ts +40 -16
  85. package/lib/session-profile-persistence.ts +187 -0
  86. package/lib/session-scratch.ts +474 -0
  87. package/lib/shell-bar.ts +25 -25
  88. package/lib/shell-changes-view.ts +80 -13
  89. package/lib/shell-changes.ts +2 -0
  90. package/lib/shell-gauge.ts +49 -6
  91. package/lib/shell-prompt.ts +6 -4
  92. package/lib/shell-todo.ts +39 -8
  93. package/lib/shell-usage-view.ts +5 -1
  94. package/lib/shell-usage.ts +7 -7
  95. package/lib/verifier-admission.ts +29 -0
  96. package/lib/vim-editor-adapter.ts +22 -4
  97. package/lib/yolo-temp-scratch.ts +69 -0
  98. package/package.json +5 -6
  99. package/runtime/gentle-ai-binary.mjs +28 -7
  100. package/runtime/gentle-shell-bundled.mjs +61 -7
  101. package/runtime/gentle-shell-import-pi.mjs +234 -0
  102. package/runtime/gentle-shell-launcher.mjs +144 -10
  103. package/runtime/gentle-shell-uninstall.mjs +18 -1
  104. package/runtime/native-review-cli.mjs +8 -0
  105. package/runtime/review-risk-assessment.mjs +43 -38
  106. package/scripts/build-runtime-modules.mjs +1 -0
  107. package/scripts/bundled-engram-migration.mjs +125 -0
  108. package/scripts/gentle-ai-installer.mjs +10 -10
  109. package/scripts/install-wizard-preview.mjs +2 -2
  110. package/scripts/installer-downloads.mjs +7 -7
  111. package/scripts/installer-preflight.mjs +1 -1
  112. package/scripts/installer-server.mjs +14 -4
  113. package/scripts/main-channel.mjs +3 -2
  114. package/scripts/pi-update.mjs +223 -0
  115. package/scripts/run-test-suite.mjs +5 -0
  116. package/scripts/verify-package-files.mjs +19 -13
  117. package/skills/gentle-ai/SKILL.md +3 -3
  118. package/skills/work-unit-commits/SKILL.md +5 -3
  119. package/tests/agent-risk-escalation.test.ts +37 -10
  120. package/tests/agents-runner.test.ts +260 -3
  121. package/tests/append-system-prompt-route.test.ts +18 -1
  122. package/tests/auto-mode-human-answers.test.ts +68 -0
  123. package/tests/auto-mode-invariants.test.ts +23 -5
  124. package/tests/auto-mode-sdk.test.ts +29 -2
  125. package/tests/auto-permission-answers.test.ts +170 -0
  126. package/tests/auto-permission-classifier.test.ts +353 -2
  127. package/tests/auto-permission-compound.test.ts +24 -0
  128. package/tests/auto-permission-evidence.test.ts +88 -0
  129. package/tests/auto-permission-memory.test.ts +46 -0
  130. package/tests/auto-permission-public-regressions.test.ts +111 -0
  131. package/tests/auto-permission-runtime.test.ts +270 -12
  132. package/tests/autonomous-guard.test.ts +208 -16
  133. package/tests/bounded-writer-admission.test.ts +11 -3
  134. package/tests/bundled-install.test.ts +3 -3
  135. package/tests/card-content-policy.test.ts +19 -9
  136. package/tests/child-safety.test.ts +39 -0
  137. package/tests/codegraph-tools.test.ts +123 -2
  138. package/tests/command-palette.test.ts +14 -4
  139. package/tests/commit-slice-signal.test.ts +162 -0
  140. package/tests/context-backstop.test.ts +159 -0
  141. package/tests/delegate-for-reason-contract.test.ts +91 -24
  142. package/tests/delivered-test-lint.test.ts +105 -0
  143. package/tests/destructive-command-guard.test.ts +85 -1
  144. package/tests/engram-safety.test.ts +35 -7
  145. package/tests/fixtures/primary-session-prompt/gentleman.golden.md +143 -0
  146. package/tests/fixtures/provider-contract-bundle/v1.4.0/README.md +29 -0
  147. package/tests/fixtures/provider-contract-bundle/v1.4.0/manifest.json +83 -0
  148. package/tests/fixtures/provider-contract-bundle/v1.4.0/orchestration/pi.md +70 -0
  149. package/tests/fixtures/provider-contract-bundle/v1.4.0/routing/pi.md +92 -0
  150. package/tests/fixtures/provider-contract-bundle/v1.4.0/schemas/lens.schema.json +16 -0
  151. package/tests/fixtures/provider-contract-bundle/v1.4.0/schemas/refuter.schema.json +1 -0
  152. package/tests/fixtures/provider-contract-bundle/v1.4.0/schemas/targeted-validator.schema.json +1 -0
  153. package/tests/fixtures/provider-contract-bundle/v1.4.0/vectors/lens.json +1 -0
  154. package/tests/fixtures/provider-contract-bundle/v1.4.0/vectors/refuter.json +1 -0
  155. package/tests/fixtures/provider-contract-bundle/v1.4.0/vectors/targeted-validator.json +1 -0
  156. package/tests/gauge-theme.test.ts +120 -0
  157. package/tests/gentle-agents.test.ts +443 -8
  158. package/tests/gentle-ai-binary.test.ts +1 -1
  159. package/tests/gentle-ai-child-guards.test.ts +61 -0
  160. package/tests/gentle-ai-doctor-herdr.test.ts +94 -0
  161. package/tests/gentle-ai-installer.test.ts +51 -51
  162. package/tests/gentle-shell-bin.test.ts +180 -17
  163. package/tests/gentle-shell-bundled.test.ts +235 -38
  164. package/tests/gentle-shell-import-pi.test.ts +262 -0
  165. package/tests/gentle-shell-launcher.test.ts +97 -13
  166. package/tests/gentle-shell-uninstall.test.ts +30 -0
  167. package/tests/gentle-shell.test.ts +113 -14
  168. package/tests/gentle-todo.test.ts +258 -27
  169. package/tests/headless-delivery-question.test.ts +58 -0
  170. package/tests/herdr-activity.test.ts +34 -1
  171. package/tests/install-wizard.test.ts +2 -2
  172. package/tests/installer-posix-bootstrap.test.ts +8 -8
  173. package/tests/installer-preflight.test.ts +3 -3
  174. package/tests/installer-probes.test.ts +8 -8
  175. package/tests/installer-runner.test.ts +14 -13
  176. package/tests/installer-server.test.ts +13 -5
  177. package/tests/installer-windows-bootstrap.test.ts +33 -4
  178. package/tests/main-channel-upgrade.test.ts +7 -1
  179. package/tests/native-review-capability-contract.test.ts +7 -1
  180. package/tests/native-review-parity.test.ts +22 -1
  181. package/tests/no-self-pause-contract.test.ts +5 -2
  182. package/tests/odd-integration.test.ts +5 -1
  183. package/tests/odd-routing-canonical-ratchet.test.ts +127 -162
  184. package/tests/odd-routing-contract.test.ts +115 -30
  185. package/tests/odd-runtime-delegation-gate.test.ts +4 -1
  186. package/tests/orchestrator-budget.test.ts +19 -8
  187. package/tests/orchestrator-discovery.test.ts +29 -0
  188. package/tests/orchestrator-rdd-ownership.test.ts +3 -1
  189. package/tests/orchestrator-scope.test.ts +66 -0
  190. package/tests/orchestrator-status-publication-contract.test.ts +12 -8
  191. package/tests/package-manifest.test.ts +28 -24
  192. package/tests/pi-update-launcher.test.ts +106 -0
  193. package/tests/pi-update.test.ts +186 -0
  194. package/tests/primary-session-prompt.test.ts +101 -0
  195. package/tests/provider-contract-bundle.test.ts +118 -0
  196. package/tests/provider-contract-mirror.test.ts +24 -0
  197. package/tests/quiet-tool-rendering.test.ts +23 -0
  198. package/tests/rdd-aware-verification-contract.test.ts +29 -11
  199. package/tests/rdd-status-line.test.ts +131 -0
  200. package/tests/review-agent-end-preflight.test.ts +6 -0
  201. package/tests/review-candidate-view.test.ts +49 -0
  202. package/tests/review-contract-prompt.test.ts +5 -2
  203. package/tests/review-controller-native-routing.test.ts +14 -1
  204. package/tests/review-controller.test.ts +1 -1
  205. package/tests/review-host-relay-routing.test.ts +59 -2
  206. package/tests/review-loadout-rdd-off-sdk.test.ts +237 -0
  207. package/tests/review-owner-identity.test.ts +132 -0
  208. package/tests/review-risk-assessment.test.ts +163 -76
  209. package/tests/review-session-standing-permission-controller.test.ts +14 -1
  210. package/tests/review-session-standing-permission.test.ts +27 -1
  211. package/tests/run-test-suite.test.ts +15 -0
  212. package/tests/runtime-harness.mjs +16 -6
  213. package/tests/selection-engine.test.ts +35 -5
  214. package/tests/session-changes-shell.test.ts +1 -1
  215. package/tests/session-profile-freeze.test.ts +37 -1
  216. package/tests/session-profile-persistence.test.ts +334 -0
  217. package/tests/session-scratch.test.ts +945 -0
  218. package/tests/session-subject-routing-contract.test.ts +2 -1
  219. package/tests/shell-bar.test.ts +7 -0
  220. package/tests/shell-changes-view.test.ts +137 -1
  221. package/tests/shell-changes.test.ts +18 -0
  222. package/tests/shell-gauge.test.ts +42 -0
  223. package/tests/shell-todo.test.ts +15 -12
  224. package/tests/small-task-inline-contract.test.ts +37 -0
  225. package/tests/standalone-config-home.test.ts +324 -0
  226. package/tests/startup-banner.test.ts +5 -4
  227. package/tests/startup-home-repo.test.ts +34 -0
  228. package/tests/support/orchestrator-modules.ts +1 -0
  229. package/tests/task-size-routing-contract.test.ts +69 -9
  230. package/tests/test-scratch-contract.test.ts +102 -0
  231. package/tests/test-strength-contract.test.ts +36 -1
  232. package/tests/tool-batching-contract.test.ts +64 -0
  233. package/tests/verification-proportionality.test.ts +46 -0
  234. package/tests/verifier-admission.test.ts +137 -0
  235. package/tests/verify-agent-contract.test.ts +8 -8
  236. package/tests/vim-editor-adapter.test.ts +41 -15
  237. package/tests/windows-hidden-processes.test.ts +5 -0
  238. package/tests/writer-edit-surface-scope.test.ts +3 -0
  239. package/tests/writer-feature-document-admission.test.ts +156 -0
  240. package/tests/yolo-mode.test.ts +24 -1
  241. package/tests/yolo-temp-scratch.test.ts +71 -0
  242. package/scripts/mirror-odd-routing.mjs +0 -242
  243. /package/contracts/review-provider-contract-mirror/{v1.3.0 → v1.4.0}/bundle/orchestration/pi.md +0 -0
  244. /package/contracts/review-provider-contract-mirror/{v1.3.0 → v1.4.0}/bundle/schemas/lens.schema.json +0 -0
  245. /package/contracts/review-provider-contract-mirror/{v1.3.0 → v1.4.0}/bundle/schemas/refuter.schema.json +0 -0
  246. /package/contracts/review-provider-contract-mirror/{v1.3.0 → v1.4.0}/bundle/schemas/targeted-validator.schema.json +0 -0
  247. /package/contracts/review-provider-contract-mirror/{v1.3.0 → v1.4.0}/bundle/vectors/lens.json +0 -0
  248. /package/contracts/review-provider-contract-mirror/{v1.3.0 → v1.4.0}/bundle/vectors/refuter.json +0 -0
  249. /package/contracts/review-provider-contract-mirror/{v1.3.0 → v1.4.0}/bundle/vectors/targeted-validator.json +0 -0
@@ -0,0 +1,94 @@
1
+ import assert from "node:assert/strict";
2
+ import { mkdtemp } from "node:fs/promises";
3
+ import { createServer, type Server } from "node:net";
4
+ import { tmpdir } from "node:os";
5
+ import { join } from "node:path";
6
+ import test, { afterEach, beforeEach } from "node:test";
7
+ import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
8
+ import { createGentleAiExtension } from "../extensions/gentle-ai.ts";
9
+ import { setGentleAiDevBinaryEnvironmentForTesting } from "../lib/gentle-ai-binary.ts";
10
+
11
+ // /gentle:doctor surfaces the Herdr sidebar setup only inside Herdr. It is
12
+ // read-only guidance: Herdr config is never written.
13
+
14
+ const ISOLATED = ["GENTLE_PI_CONFIG_HOME", "GENTLE_PI_AGENT_HOME", "GENTLE_PI_AGENTS_CHILD", "HERDR_ENV", "HERDR_PANE_ID", "HERDR_SOCKET_PATH"];
15
+ let saved: Record<string, string | undefined> = {};
16
+ beforeEach(async () => {
17
+ saved = Object.fromEntries(ISOLATED.map((name) => [name, process.env[name]]));
18
+ for (const name of ISOLATED) delete process.env[name];
19
+ process.env.GENTLE_PI_CONFIG_HOME = await mkdtemp(join(tmpdir(), "gentle-pi-doctor-config-"));
20
+ process.env.GENTLE_PI_AGENT_HOME = await mkdtemp(join(tmpdir(), "gentle-pi-doctor-agent-"));
21
+ setGentleAiDevBinaryEnvironmentForTesting({ env: {}, home: await mkdtemp(join(tmpdir(), "gentle-pi-doctor-home-")) });
22
+ });
23
+ afterEach(() => {
24
+ setGentleAiDevBinaryEnvironmentForTesting(undefined);
25
+ for (const name of ISOLATED) {
26
+ if (saved[name] === undefined) delete process.env[name];
27
+ else process.env[name] = saved[name];
28
+ }
29
+ });
30
+
31
+ async function runDoctor(): Promise<Array<{ message: string; severity: string }>> {
32
+ const commands = new Map<string, { handler: (args: string, ctx: ExtensionContext) => Promise<void> }>();
33
+ const pi = {
34
+ on() {},
35
+ registerCommand(name: string, registration: { handler: (args: string, ctx: ExtensionContext) => Promise<void> }) {
36
+ commands.set(name, registration);
37
+ },
38
+ registerTool() {},
39
+ } as unknown as ExtensionAPI;
40
+ createGentleAiExtension({ nativeReviewCli: null })(pi);
41
+ const notifications: Array<{ message: string; severity: string }> = [];
42
+ const ctx = {
43
+ cwd: await mkdtemp(join(tmpdir(), "gentle-pi-doctor-cwd-")),
44
+ mode: "tui",
45
+ hasUI: true,
46
+ ui: { notify(message: string, severity: string) { notifications.push({ message, severity }); } },
47
+ } as unknown as ExtensionContext;
48
+ await commands.get("gentle:doctor")!.handler("", ctx);
49
+ return notifications;
50
+ }
51
+
52
+ async function listen(path: string): Promise<Server> {
53
+ const server = createServer();
54
+ await new Promise<void>((resolve) => server.listen(path, resolve));
55
+ return server;
56
+ }
57
+
58
+ test("outside Herdr, /gentle:doctor stays silent about Herdr", async () => {
59
+ const notifications = await runDoctor();
60
+ assert.equal(notifications.length, 1);
61
+ assert.match(notifications[0]!.message, /^el Gentleman doctor\n/);
62
+ assert.doesNotMatch(notifications[0]!.message, /herdr|\$summary/i);
63
+ });
64
+
65
+ test("inside Herdr, /gentle:doctor appends the read-only sidebar setup hint", async () => {
66
+ const baseline = await runDoctor();
67
+ const socket = join(await mkdtemp(join(tmpdir(), "gd-")), "h.sock");
68
+ const server = await listen(socket);
69
+ try {
70
+ Object.assign(process.env, { HERDR_ENV: "1", HERDR_PANE_ID: "pane-1", HERDR_SOCKET_PATH: socket });
71
+ const notifications = await runDoctor();
72
+ assert.equal(notifications.length, 1);
73
+ assert.equal(notifications[0]!.severity, baseline[0]!.severity, "the hint does not change doctor severity");
74
+ assert.ok(notifications[0]!.message.endsWith([
75
+ "Herdr",
76
+ "pass: Herdr task-summary publishing active",
77
+ "info: Herdr's default sidebar does not show task titles; add these two rows to the Agents sidebar rows in your Herdr config: ['$summary'], ['$summary2']",
78
+ "info: Sidebar rows belong to the config of the Herdr client that displays the session, including when Pi runs over SSH",
79
+ "info: After editing, reload or restart Herdr; Gentle Shell never changes Herdr config",
80
+ "info: Details: https://github.com/Gentleman-Programming/gentle-shell/blob/main/docs/readme-reference.md#herdr-active-work-summary",
81
+ ].join("\n")), notifications[0]!.message);
82
+ assert.ok(notifications[0]!.message.startsWith(baseline[0]!.message), "existing doctor lines are unchanged");
83
+ } finally {
84
+ await new Promise((resolve) => server.close(resolve));
85
+ }
86
+ });
87
+
88
+ test("inside Herdr without a live socket, /gentle:doctor reports publishing inactive", async () => {
89
+ Object.assign(process.env, { HERDR_ENV: "1", HERDR_PANE_ID: "pane-1", HERDR_SOCKET_PATH: join(tmpdir(), "missing-herdr.sock") });
90
+ const notifications = await runDoctor();
91
+ assert.equal(notifications.length, 1);
92
+ assert.match(notifications[0]!.message, /\nwarn: Herdr task-summary publishing inactive: HERDR_SOCKET_PATH is not a Unix socket\n/);
93
+ assert.match(notifications[0]!.message, /\['\$summary'\], \['\$summary2'\]/);
94
+ });
@@ -24,7 +24,7 @@ import {
24
24
  trustedSystemExtractor,
25
25
  } from "../scripts/gentle-ai-installer.mjs";
26
26
 
27
- // v4.1.1 archive digests independently match the minisign-signed release
27
+ // v4.2.0 archive digests independently match the minisign-signed release
28
28
  // checksums; binary digests were computed from the extracted executables.
29
29
  function releaseAssets(): Record<string, { name: string; sha256: string; binarySha256: string; url: string }> {
30
30
  const assets: Record<string, { name: string; sha256: string; binarySha256: string; url: string }> = {};
@@ -40,17 +40,17 @@ function releaseAssets(): Record<string, { name: string; sha256: string; binaryS
40
40
  }
41
41
 
42
42
  const EXPECTED_ASSETS = {
43
- "darwin/amd64": { name: "gentle-ai_4.1.1_darwin_amd64.tar.gz", sha256: "f2e9aebfba3056e88a8d7c7193e4e8c04282dc126d5eaa2216bc807deadecffd", binarySha256: "82351dd86bf7323c6a5b9e9e68b3cc2893a65a4df04fbdf1ef1801cabf5d2338" },
44
- "darwin/arm64": { name: "gentle-ai_4.1.1_darwin_arm64.tar.gz", sha256: "8093dced37130cb26534d81663013f81d9039342e33f00b8c85aa5319aa7eea9", binarySha256: "6362e3641b87ae2b41a8dd7695dad4cf47457ee86c9f63792e474476bfdb6b6a" },
45
- "linux/amd64": { name: "gentle-ai_4.1.1_linux_amd64.tar.gz", sha256: "57869ebcdba5ba9942493ab413bbc15d146e2451c4dca3eaafcf163fcbd646b4", binarySha256: "7c638c8467efc7036e0ec6b8a1d9080a5e9a64f7ae6cbc6074fce577f789b7fe" },
46
- "linux/arm64": { name: "gentle-ai_4.1.1_linux_arm64.tar.gz", sha256: "5f8fa38573eea0f2110902f0996aa0b008aac58dc0f9a6abbdf301dcbc93f4eb", binarySha256: "76a36a73d6905a16221496ffe9a193bd79e17951463d711cd03abf370349ef55" },
43
+ "darwin/amd64": { name: "gentle-ai_4.2.0_darwin_amd64.tar.gz", sha256: "8f6a91e0f99cac3aea138705d7d222ea342f53a47602d48cb01488952a1c12f8", binarySha256: "784855e99f1bc45579f01420c923b7dc1dc6d63492cdc0174a9a32e611849e57" },
44
+ "darwin/arm64": { name: "gentle-ai_4.2.0_darwin_arm64.tar.gz", sha256: "d5e9a6521de5f08a393635f083f64dc7afc1b8e97884f7aab5ae5eb1f17c7f37", binarySha256: "8ea490287ef796848b0d305cf2b26265e360d3f400efc016972844498fb87097" },
45
+ "linux/amd64": { name: "gentle-ai_4.2.0_linux_amd64.tar.gz", sha256: "552c1642fd10f5d96cfe9bb1cc3b66b30eb47d3074cc7ca0559b598eb086defc", binarySha256: "9212c7b16ea9abfe93e3a16b29d8390e7762b5c07730cf05df1f3e7892ff96a4" },
46
+ "linux/arm64": { name: "gentle-ai_4.2.0_linux_arm64.tar.gz", sha256: "2bebe91b8808ce48d86c4d3700d098e37cb6b0658f196c666d9deeeb7c7c1a7a", binarySha256: "e1b88bd3dcdc8a15ddbcaf8731fd44561f05b06fbcb59d9385bbe82c4b96b5c4" },
47
47
  } as const;
48
48
 
49
- test("published Gentle AI v4.1.1 is the installer pin", () => {
50
- assert.equal(INSTALLER_VERSION, "4.1.1");
49
+ test("published Gentle AI v4.2.0 is the installer pin", () => {
50
+ assert.equal(INSTALLER_VERSION, "4.2.0");
51
51
  // v4.0.0 moved the Go module path to the /v4 major-version suffix.
52
52
  assert.equal(GENTLE_AI_WINDOWS_SOURCE_MODULE, "github.com/gentleman-programming/gentle-ai/v4");
53
- assert.equal(GENTLE_AI_WINDOWS_SOURCE_MODULE_CHECKSUM, "h1:+WmZ2IvIOiJ8qtHa7wsjnYKNasem+PByeOZ6HIjoAL8=");
53
+ assert.equal(GENTLE_AI_WINDOWS_SOURCE_MODULE_CHECKSUM, "h1:J8EluS7QrfM+m3vmQlF26B/shZRjD6rBfsKtrABL5mc=");
54
54
  });
55
55
 
56
56
  test("default installer package root is the package containing scripts, not its parent", () => {
@@ -61,15 +61,15 @@ test("default installer package root is the package containing scripts, not its
61
61
  assert.notEqual(resolveGentleAiInstallerPackageRoot(), dirname(expectedPackageRoot));
62
62
  });
63
63
 
64
- test("release mapping selects only the supported official v4.1.1 assets and pinned digests", () => {
64
+ test("release mapping selects only the supported official v4.2.0 assets and pinned digests", () => {
65
65
  assert.deepEqual(
66
66
  Object.fromEntries(Object.entries(releaseAssets()).map(([key, asset]) => [key, { name: asset.name, sha256: asset.sha256, binarySha256: asset.binarySha256 }])),
67
67
  EXPECTED_ASSETS,
68
68
  );
69
- assert.equal(resolveGentleAiReleaseAsset("linux", "x64").name, "gentle-ai_4.1.1_linux_amd64.tar.gz");
70
- assert.equal(resolveGentleAiReleaseAsset("darwin", "arm64").name, "gentle-ai_4.1.1_darwin_arm64.tar.gz");
69
+ assert.equal(resolveGentleAiReleaseAsset("linux", "x64").name, "gentle-ai_4.2.0_linux_amd64.tar.gz");
70
+ assert.equal(resolveGentleAiReleaseAsset("darwin", "arm64").name, "gentle-ai_4.2.0_darwin_arm64.tar.gz");
71
71
  for (const asset of Object.values(releaseAssets())) {
72
- assert.match(asset.url, /^https:\/\/github\.com\/Gentleman-Programming\/gentle-ai\/releases\/download\/v4\.1\.1\//);
72
+ assert.match(asset.url, /^https:\/\/github\.com\/Gentleman-Programming\/gentle-ai\/releases\/download\/v4\.2\.0\//);
73
73
  }
74
74
  });
75
75
 
@@ -79,7 +79,7 @@ test("raw release assets are admitted only under a prerelease pin", () => {
79
79
  assert.equal(gentleAiAssetForm("gentle-ai_2.5.0-rc.3_windows_amd64.exe", "2.5.0-rc.3"), "raw-binary");
80
80
  // A raw binary under a stable pin means the pin itself is wrong: stable
81
81
  // releases publish signed archives only, so this fails closed pre-download.
82
- assert.throws(() => gentleAiAssetForm("gentle-ai_4.1.1_linux_amd64", "4.1.1"), /only admitted for a prerelease pin/);
82
+ assert.throws(() => gentleAiAssetForm("gentle-ai_4.2.0_linux_amd64", "4.2.0"), /only admitted for a prerelease pin/);
83
83
  assert.throws(() => gentleAiAssetForm("gentle-ai.dmg", "2.5.0-rc.3"), /unsupported Gentle AI release asset form/);
84
84
  // The current stable pin admits every pinned asset row through the same
85
85
  // gate the installer uses at download time (default installerVersion
@@ -106,7 +106,7 @@ test("release digests are all-or-none and install fails closed while any digest
106
106
  }),
107
107
  /checksum mismatch/,
108
108
  );
109
- assert.equal(existsSync(join(packageRoot, ".gentle-ai", "v4.1.1", "gentle-ai")), false);
109
+ assert.equal(existsSync(join(packageRoot, ".gentle-ai", "v4.2.0", "gentle-ai")), false);
110
110
  }
111
111
  });
112
112
 
@@ -136,7 +136,7 @@ function windowsGoFixture(fixtureOptions: WindowsGoFixtureOptions = {}) {
136
136
  const metadata = [
137
137
  "gentle-ai.exe: go1.25.10",
138
138
  "\tpath\tgithub.com/gentleman-programming/gentle-ai/v4/cmd/gentle-ai",
139
- `\tmod\tgithub.com/gentleman-programming/gentle-ai/v4\tv4.1.1\t${GENTLE_AI_WINDOWS_SOURCE_MODULE_CHECKSUM}`,
139
+ `\tmod\tgithub.com/gentleman-programming/gentle-ai/v4\tv4.2.0\t${GENTLE_AI_WINDOWS_SOURCE_MODULE_CHECKSUM}`,
140
140
  "\tbuild\t-buildmode=exe", "\tbuild\t-compiler=gc", "\tbuild\tCGO_ENABLED=0", `\tbuild\tGOARCH=${fixtureOptions.goArchitecture ?? "amd64"}`, "\tbuild\tGOOS=windows",
141
141
  ].join("\n");
142
142
  const run = async (file: string, arguments_: string[], options: WindowsGoCall["options"]) => {
@@ -154,7 +154,7 @@ function windowsGoFixture(fixtureOptions: WindowsGoFixtureOptions = {}) {
154
154
  return { stdout: "", stderr: "" };
155
155
  }
156
156
  if (file === goExecutable && arguments_[0] === "version" && arguments_[1] === "-m") return { stdout: metadata, stderr: "" };
157
- if (arguments_.length === 1 && arguments_[0] === "version") return { stdout: fixtureOptions.reportedVersion ?? "gentle-ai 4.1.1\n", stderr: "" };
157
+ if (arguments_.length === 1 && arguments_[0] === "version") return { stdout: fixtureOptions.reportedVersion ?? "gentle-ai 4.2.0\n", stderr: "" };
158
158
  throw new Error(`unexpected command: ${file} ${arguments_.join(" ")}`);
159
159
  };
160
160
  return { calls, run, setGoExecutable: (path: string) => { goExecutable = path; } };
@@ -170,7 +170,7 @@ test("win32 x64 and arm64 install the exact Go SumDB source tag without archive
170
170
  const result = await installGentleAi({ packageRoot, platform: "win32", arch, execFile: fixture.run, resolveGoExecutable: async () => goPath });
171
171
  assert.equal(result.installed, true);
172
172
  assert.deepEqual(fixture.calls.filter((call) => call.file === goPath).map((call) => call.arguments_.slice(0, 2)), [
173
- ["version"], ["install", "github.com/gentleman-programming/gentle-ai/v4/cmd/gentle-ai@v4.1.1"], ["version", "-m"],
173
+ ["version"], ["install", "github.com/gentleman-programming/gentle-ai/v4/cmd/gentle-ai@v4.2.0"], ["version", "-m"],
174
174
  ]);
175
175
  }
176
176
  });
@@ -189,7 +189,7 @@ test("Windows source install reports missing or too-old Go without publishing a
189
189
  () => installGentleAi({ packageRoot, platform: "win32", arch: "x64", execFile: fixture.run, resolveGoExecutable: async () => goPath }),
190
190
  (error: unknown) => error instanceof Error && "code" in error && error.code === fixtureOptions.expectedCode,
191
191
  );
192
- assert.equal(existsSync(join(packageRoot, ".gentle-ai", "v4.1.1", "gentle-ai.exe")), false);
192
+ assert.equal(existsSync(join(packageRoot, ".gentle-ai", "v4.2.0", "gentle-ai.exe")), false);
193
193
  assert.deepEqual((await readdir(packageRoot)).filter((entry) => entry.includes("install-")), []);
194
194
  }
195
195
  });
@@ -235,7 +235,7 @@ test("Windows source install builds in a short private directory under the temp
235
235
  }
236
236
  for (const call of fixture.calls.filter((entry) => entry.file === goPath)) assert.ok(call.options.cwd?.startsWith(buildDirectory), call.arguments_.join(" "));
237
237
  assert.deepEqual(await readdir(temporaryDirectory), [], "the build directory is removed");
238
- if (!installError) assert.equal(existsSync(join(packageRoot, ".gentle-ai", "v4.1.1", "gentle-ai.exe")), true, "the verified binary is published into the package");
238
+ if (!installError) assert.equal(existsSync(join(packageRoot, ".gentle-ai", "v4.2.0", "gentle-ai.exe")), true, "the verified binary is published into the package");
239
239
  } finally {
240
240
  await rm(packageRoot, { recursive: true, force: true });
241
241
  await rm(temporaryDirectory, { recursive: true, force: true });
@@ -257,7 +257,7 @@ test("Windows source install cleans staging after Go failure or wrong built vers
257
257
  () => installGentleAi({ packageRoot, platform: "win32", arch: "x64", execFile: fixture.run, resolveGoExecutable: async () => goPath }),
258
258
  (error: unknown) => error instanceof Error && "code" in error && error.code === fixtureOptions.expectedCode,
259
259
  );
260
- const runtimeDirectory = join(packageRoot, ".gentle-ai", "v4.1.1");
260
+ const runtimeDirectory = join(packageRoot, ".gentle-ai", "v4.2.0");
261
261
  assert.ok(fixture.calls.some((call) => call.arguments_[0] === "install"));
262
262
  assert.equal(existsSync(join(runtimeDirectory, "gentle-ai.exe")), false);
263
263
  assert.equal(existsSync(runtimeDirectory) && (await readdir(runtimeDirectory)).some((entry) => entry.startsWith(".go-install-") || entry.endsWith(".tmp")), false);
@@ -278,7 +278,7 @@ test("Windows source installs reuse only a fully verified package-local binary",
278
278
  assert.equal(reused.installed, false);
279
279
  assert.ok(reuse.calls.every((call) => call.file !== "gentle-ai"), "the installer must never fall back to ambient gentle-ai on PATH");
280
280
 
281
- await writeFile(join(packageRoot, ".gentle-ai", "v4.1.1", "integrity.json"), "{}\n");
281
+ await writeFile(join(packageRoot, ".gentle-ai", "v4.2.0", "integrity.json"), "{}\n");
282
282
  const repaired = windowsGoFixture();
283
283
  repaired.setGoExecutable(goPath);
284
284
  assert.equal((await installGentleAi({ packageRoot, platform: "win32", arch: "x64", execFile: repaired.run, resolveGoExecutable: async () => goPath })).installed, true);
@@ -320,7 +320,7 @@ async function hardenedWindowsGoFixture(packageRoot: string, fixtureOptions: Har
320
320
  const metadata = fixtureOptions.metadataOverride ?? [
321
321
  "gentle-ai.exe: go1.25.10",
322
322
  "\tpath\tgithub.com/gentleman-programming/gentle-ai/v4/cmd/gentle-ai",
323
- `\tmod\tgithub.com/gentleman-programming/gentle-ai/v4\tv4.1.1\t${GENTLE_AI_WINDOWS_SOURCE_MODULE_CHECKSUM}`,
323
+ `\tmod\tgithub.com/gentleman-programming/gentle-ai/v4\tv4.2.0\t${GENTLE_AI_WINDOWS_SOURCE_MODULE_CHECKSUM}`,
324
324
  "\tbuild\t-buildmode=exe",
325
325
  "\tbuild\t-compiler=gc",
326
326
  "\tbuild\tCGO_ENABLED=0",
@@ -340,7 +340,7 @@ async function hardenedWindowsGoFixture(packageRoot: string, fixtureOptions: Har
340
340
  return { stdout: "", stderr: "" };
341
341
  }
342
342
  if (file === goPath && arguments_[0] === "version" && arguments_[1] === "-m") return { stdout: metadata, stderr: "" };
343
- if (arguments_.length === 1 && arguments_[0] === "version") return { stdout: "gentle-ai 4.1.1\n", stderr: "" };
343
+ if (arguments_.length === 1 && arguments_[0] === "version") return { stdout: "gentle-ai 4.2.0\n", stderr: "" };
344
344
  throw new Error(`unexpected command: ${file} ${arguments_.join(" ")}`);
345
345
  };
346
346
  return {
@@ -382,7 +382,7 @@ test("Windows source installation resolves one validated Go executable and seals
382
382
  const goCalls = fixture.calls.filter((call) => call.file === fixture.goPath);
383
383
  assert.deepEqual(goCalls.map((call) => call.arguments_.slice(0, 2)), [
384
384
  ["version"],
385
- ["install", "github.com/gentleman-programming/gentle-ai/v4/cmd/gentle-ai@v4.1.1"],
385
+ ["install", "github.com/gentleman-programming/gentle-ai/v4/cmd/gentle-ai@v4.2.0"],
386
386
  ["version", "-m"],
387
387
  ]);
388
388
  for (const call of goCalls) {
@@ -408,14 +408,14 @@ test("Windows source manifest binds verified Go metadata and architecture", asyn
408
408
  const packageRoot = await mkdtemp(join(tmpdir(), "gentle-pi-installer-provenance-"));
409
409
  const fixture = await hardenedWindowsGoFixture(packageRoot, { architecture: "arm64" });
410
410
  const result = await installGentleAi({ packageRoot, platform: "win32", arch: "arm64", execFile: fixture.run, resolveGoExecutable: fixture.resolveGoExecutable });
411
- const manifest = JSON.parse(await readFile(join(packageRoot, ".gentle-ai", "v4.1.1", "integrity.json"), "utf8")) as Record<string, string>;
411
+ const manifest = JSON.parse(await readFile(join(packageRoot, ".gentle-ai", "v4.2.0", "integrity.json"), "utf8")) as Record<string, string>;
412
412
  assert.equal(result.installed, true);
413
413
  assert.deepEqual(manifest, {
414
- version: "4.1.1",
414
+ version: "4.2.0",
415
415
  method: "go-sumdb-source-build",
416
416
  package: "github.com/gentleman-programming/gentle-ai/v4/cmd/gentle-ai",
417
417
  module: "github.com/gentleman-programming/gentle-ai/v4",
418
- tag: "v4.1.1",
418
+ tag: "v4.2.0",
419
419
  architecture: "arm64",
420
420
  binarySha256: createHash("sha256").update("trusted Windows source build").digest("hex"),
421
421
  moduleChecksum: GENTLE_AI_WINDOWS_SOURCE_MODULE_CHECKSUM,
@@ -435,12 +435,12 @@ test("Windows source installation rejects Go metadata for a different architectu
435
435
  () => installGentleAi({ packageRoot, platform: "win32", arch: "x64", execFile: fixture.run, resolveGoExecutable: fixture.resolveGoExecutable }),
436
436
  (error: unknown) => error instanceof Error && "code" in error && error.code === "GENTLE_AI_GO_INSTALL_FAILED",
437
437
  );
438
- assert.equal(existsSync(join(packageRoot, ".gentle-ai", "v4.1.1", "gentle-ai.exe")), false);
438
+ assert.equal(existsSync(join(packageRoot, ".gentle-ai", "v4.2.0", "gentle-ai.exe")), false);
439
439
  });
440
440
 
441
441
  test("Windows source installation treats a fresh ownerless lock as active", async () => {
442
442
  const packageRoot = await mkdtemp(join(tmpdir(), "gentle-pi-installer-ownerless-lock-"));
443
- const lockPath = join(packageRoot, ".gentle-ai", ".v4.1.1.install.lock");
443
+ const lockPath = join(packageRoot, ".gentle-ai", ".v4.2.0.install.lock");
444
444
  await mkdir(lockPath, { recursive: true });
445
445
  const fixture = await hardenedWindowsGoFixture(packageRoot);
446
446
  await assertManualLockRecoveryRequired(packageRoot, fixture);
@@ -448,7 +448,7 @@ test("Windows source installation treats a fresh ownerless lock as active", asyn
448
448
 
449
449
  test("Windows source installation preserves a lock whose owner nonce changed before release", async () => {
450
450
  const packageRoot = await mkdtemp(join(tmpdir(), "gentle-pi-installer-owner-lock-"));
451
- const lockOwnerPath = join(packageRoot, ".gentle-ai", ".v4.1.1.install.lock", "owner.json");
451
+ const lockOwnerPath = join(packageRoot, ".gentle-ai", ".v4.2.0.install.lock", "owner.json");
452
452
  const fixture = await hardenedWindowsGoFixture(packageRoot);
453
453
  let replacedOwner = false;
454
454
  const run = async (...arguments_: Parameters<typeof fixture.run>) => {
@@ -465,7 +465,7 @@ test("Windows source installation preserves a lock whose owner nonce changed bef
465
465
  });
466
466
 
467
467
  async function assertManualLockRecoveryRequired(packageRoot: string, fixture: Awaited<ReturnType<typeof hardenedWindowsGoFixture>>, options: Record<string, unknown> = {}) {
468
- const lockPath = join(packageRoot, ".gentle-ai", ".v4.1.1.install.lock");
468
+ const lockPath = join(packageRoot, ".gentle-ai", ".v4.2.0.install.lock");
469
469
  await assert.rejects(
470
470
  () => installGentleAi({ packageRoot, platform: "win32", arch: "x64", execFile: fixture.run, resolveGoExecutable: fixture.resolveGoExecutable, ...options }),
471
471
  (error: unknown) => error instanceof Error && error.message.includes(lockPath) && /confirm no installer is active.*remove.*manually/i.test(error.message),
@@ -475,18 +475,18 @@ async function assertManualLockRecoveryRequired(packageRoot: string, fixture: Aw
475
475
  }
476
476
 
477
477
  function tombstonePath(packageRoot: string, nonce: string): string {
478
- return join(packageRoot, ".gentle-ai", `.v4.1.1.install.tombstone-${nonce}`);
478
+ return join(packageRoot, ".gentle-ai", `.v4.2.0.install.tombstone-${nonce}`);
479
479
  }
480
480
 
481
481
  async function tombstones(packageRoot: string): Promise<string[]> {
482
482
  const runtimeRoot = join(packageRoot, ".gentle-ai");
483
483
  return existsSync(runtimeRoot)
484
- ? (await readdir(runtimeRoot)).filter((entry) => entry.startsWith(".v4.1.1.install.tombstone-"))
484
+ ? (await readdir(runtimeRoot)).filter((entry) => entry.startsWith(".v4.2.0.install.tombstone-"))
485
485
  : [];
486
486
  }
487
487
 
488
488
  function backupBundlePath(packageRoot: string, nonce: string): string {
489
- return join(packageRoot, ".gentle-ai", `.v4.1.1.backup-${nonce}`);
489
+ return join(packageRoot, ".gentle-ai", `.v4.2.0.backup-${nonce}`);
490
490
  }
491
491
 
492
492
  async function copyWindowsBundle(source: string, destination: string): Promise<void> {
@@ -497,7 +497,7 @@ async function copyWindowsBundle(source: string, destination: string): Promise<v
497
497
 
498
498
  test("Windows source installation fails closed for an ownerless lock even after the stale threshold", async () => {
499
499
  const packageRoot = await mkdtemp(join(tmpdir(), "gentle-pi-installer-ownerless-stale-lock-"));
500
- const lockPath = join(packageRoot, ".gentle-ai", ".v4.1.1.install.lock");
500
+ const lockPath = join(packageRoot, ".gentle-ai", ".v4.2.0.install.lock");
501
501
  await mkdir(lockPath, { recursive: true });
502
502
  const fixture = await hardenedWindowsGoFixture(packageRoot);
503
503
  await assertManualLockRecoveryRequired(packageRoot, fixture, { now: () => Date.now() + 10 * 60_000 });
@@ -505,7 +505,7 @@ test("Windows source installation fails closed for an ownerless lock even after
505
505
 
506
506
  test("Windows source installation fails closed for a stale owner lock", async () => {
507
507
  const packageRoot = await mkdtemp(join(tmpdir(), "gentle-pi-installer-stale-lock-"));
508
- const lockPath = join(packageRoot, ".gentle-ai", ".v4.1.1.install.lock");
508
+ const lockPath = join(packageRoot, ".gentle-ai", ".v4.2.0.install.lock");
509
509
  await mkdir(lockPath, { recursive: true });
510
510
  await writeFile(join(lockPath, "owner.json"), `${JSON.stringify({ createdAt: 0, nonce: "0".repeat(64) })}\n`);
511
511
  const fixture = await hardenedWindowsGoFixture(packageRoot);
@@ -557,7 +557,7 @@ test("Windows source acquisition fails closed when a tombstone appears after its
557
557
  (error: unknown) => error instanceof Error && error.message.includes(foreignTombstone),
558
558
  );
559
559
  assert.equal(existsSync(foreignTombstone), true);
560
- assert.equal(existsSync(join(packageRoot, ".gentle-ai", ".v4.1.1.install.lock")), false);
560
+ assert.equal(existsSync(join(packageRoot, ".gentle-ai", ".v4.2.0.install.lock")), false);
561
561
  assert.equal(fixture.calls.some((call) => call.arguments_[0] === "install"), false);
562
562
  });
563
563
 
@@ -565,7 +565,7 @@ test("Windows source release deletes its matching tombstone and allows reuse", a
565
565
  const packageRoot = await mkdtemp(join(tmpdir(), "gentle-pi-installer-owned-lock-release-"));
566
566
  const fixture = await hardenedWindowsGoFixture(packageRoot);
567
567
  await installGentleAi({ packageRoot, platform: "win32", arch: "x64", execFile: fixture.run, resolveGoExecutable: fixture.resolveGoExecutable });
568
- assert.equal(existsSync(join(packageRoot, ".gentle-ai", ".v4.1.1.install.lock")), false);
568
+ assert.equal(existsSync(join(packageRoot, ".gentle-ai", ".v4.2.0.install.lock")), false);
569
569
  assert.deepEqual(await tombstones(packageRoot), []);
570
570
  const reuse = await hardenedWindowsGoFixture(packageRoot);
571
571
  assert.equal((await installGentleAi({ packageRoot, platform: "win32", arch: "x64", execFile: reuse.run, resolveGoExecutable: reuse.resolveGoExecutable })).installed, false);
@@ -575,7 +575,7 @@ test("Windows source recovers a valid backup after a crash between publication r
575
575
  const packageRoot = await mkdtemp(join(tmpdir(), "gentle-pi-installer-backup-crash-"));
576
576
  const initial = await hardenedWindowsGoFixture(packageRoot);
577
577
  await installGentleAi({ packageRoot, platform: "win32", arch: "x64", execFile: initial.run, resolveGoExecutable: initial.resolveGoExecutable });
578
- const live = join(packageRoot, ".gentle-ai", "v4.1.1");
578
+ const live = join(packageRoot, ".gentle-ai", "v4.2.0");
579
579
  const backup = backupBundlePath(packageRoot, "crash");
580
580
  await rename(live, backup);
581
581
  const recovery = await hardenedWindowsGoFixture(packageRoot);
@@ -611,7 +611,7 @@ test("Windows source cleans one validated backup only when the live bundle is va
611
611
  const packageRoot = await mkdtemp(join(tmpdir(), "gentle-pi-installer-backup-valid-live-"));
612
612
  const initial = await hardenedWindowsGoFixture(packageRoot);
613
613
  await installGentleAi({ packageRoot, platform: "win32", arch: "x64", execFile: initial.run, resolveGoExecutable: initial.resolveGoExecutable });
614
- const live = join(packageRoot, ".gentle-ai", "v4.1.1");
614
+ const live = join(packageRoot, ".gentle-ai", "v4.2.0");
615
615
  const backup = backupBundlePath(packageRoot, "valid");
616
616
  await copyWindowsBundle(live, backup);
617
617
  const reuse = await hardenedWindowsGoFixture(packageRoot);
@@ -624,7 +624,7 @@ test("Windows source preserves a valid backup when the live bundle is invalid",
624
624
  const packageRoot = await mkdtemp(join(tmpdir(), "gentle-pi-installer-backup-invalid-live-"));
625
625
  const initial = await hardenedWindowsGoFixture(packageRoot);
626
626
  await installGentleAi({ packageRoot, platform: "win32", arch: "x64", execFile: initial.run, resolveGoExecutable: initial.resolveGoExecutable });
627
- const live = join(packageRoot, ".gentle-ai", "v4.1.1");
627
+ const live = join(packageRoot, ".gentle-ai", "v4.2.0");
628
628
  const backup = backupBundlePath(packageRoot, "valid");
629
629
  await copyWindowsBundle(live, backup);
630
630
  await writeFile(join(live, "integrity.json"), "{}\n");
@@ -642,7 +642,7 @@ test("Windows concurrent installs fail closed until normal release, then reuse t
642
642
  const fixture = await hardenedWindowsGoFixture(packageRoot, { blockInstall: true });
643
643
  const first = installGentleAi({ packageRoot, platform: "win32", arch: "x64", execFile: fixture.run, resolveGoExecutable: fixture.resolveGoExecutable });
644
644
  await fixture.waitForInstall();
645
- const lockPath = join(packageRoot, ".gentle-ai", ".v4.1.1.install.lock");
645
+ const lockPath = join(packageRoot, ".gentle-ai", ".v4.2.0.install.lock");
646
646
  await assert.rejects(
647
647
  () => installGentleAi({ packageRoot, platform: "win32", arch: "x64", execFile: fixture.run, resolveGoExecutable: fixture.resolveGoExecutable }),
648
648
  (error: unknown) => error instanceof Error && error.message.includes(lockPath),
@@ -656,7 +656,7 @@ test("Windows concurrent installs fail closed until normal release, then reuse t
656
656
 
657
657
  test("Windows source publication rolls back a prior bundle when final directory swap fails", async () => {
658
658
  const packageRoot = await mkdtemp(join(tmpdir(), "gentle-pi-installer-rollback-"));
659
- const versionDirectory = join(packageRoot, ".gentle-ai", "v4.1.1");
659
+ const versionDirectory = join(packageRoot, ".gentle-ai", "v4.2.0");
660
660
  await mkdir(versionDirectory, { recursive: true });
661
661
  await writeFile(join(versionDirectory, "old.txt"), "previous bundle");
662
662
  const fixture = await hardenedWindowsGoFixture(packageRoot);
@@ -1092,7 +1092,7 @@ test("Darwin/Linux signed bundles retain their four-field manifest and reusable
1092
1092
  },
1093
1093
  };
1094
1094
  await installGentleAi(options);
1095
- const manifestPath = join(packageRoot, ".gentle-ai", "v4.1.1", "integrity.json");
1095
+ const manifestPath = join(packageRoot, ".gentle-ai", "v4.2.0", "integrity.json");
1096
1096
  const manifest = JSON.parse(await readFile(manifestPath, "utf8")) as Record<string, string>;
1097
1097
  assert.deepEqual(Object.keys(manifest), ["version", "asset", "assetSha256", "binarySha256"]);
1098
1098
  assert.equal((await installGentleAi({ ...options, download: async () => { throw new Error("signed bundle must be reused"); } })).installed, false);
@@ -1105,7 +1105,7 @@ test("a pinned asset falls back to its gentle-pi mirror only on download failure
1105
1105
  // under the current stable pin, whose form gate refuses raw binaries.
1106
1106
  const payload = Buffer.from("signed archive fixture");
1107
1107
  const binary = "mirrored binary";
1108
- const baseAsset = { name: "gentle-ai_4.1.1_linux_amd64.tar.gz", sha256: createHash("sha256").update(payload).digest("hex"), binarySha256: createHash("sha256").update(binary).digest("hex"), url: "https://example.invalid/upstream", mirrorUrl: "https://example.invalid/mirror", executable: "gentle-ai" };
1108
+ const baseAsset = { name: "gentle-ai_4.2.0_linux_amd64.tar.gz", sha256: createHash("sha256").update(payload).digest("hex"), binarySha256: createHash("sha256").update(binary).digest("hex"), url: "https://example.invalid/upstream", mirrorUrl: "https://example.invalid/mirror", executable: "gentle-ai" };
1109
1109
  const extractArchive = async (_archive: string, destination: string) => {
1110
1110
  await mkdir(destination, { recursive: true });
1111
1111
  await writeFile(join(destination, "gentle-ai"), binary);
@@ -1249,7 +1249,7 @@ test("checksum mismatch cleans temporary state without promoting a binary", asyn
1249
1249
  }),
1250
1250
  /checksum mismatch/,
1251
1251
  );
1252
- assert.equal(existsSync(join(packageRoot, ".gentle-ai", "v4.1.1", "gentle-ai")), false);
1252
+ assert.equal(existsSync(join(packageRoot, ".gentle-ai", "v4.2.0", "gentle-ai")), false);
1253
1253
  assert.deepEqual((await readdir(packageRoot)).filter((entry) => entry.startsWith(".gentle-ai-install-")), []);
1254
1254
  });
1255
1255
 
@@ -1271,7 +1271,7 @@ test("installer promotes only the expected regular executable with executable PO
1271
1271
  await chmod(extracted, 0o700);
1272
1272
  },
1273
1273
  });
1274
- const binary = join(packageRoot, ".gentle-ai", "v4.1.1", "gentle-ai");
1274
+ const binary = join(packageRoot, ".gentle-ai", "v4.2.0", "gentle-ai");
1275
1275
  assert.equal(existsSync(binary), true);
1276
1276
  assert.equal(await readFile(binary, "utf8"), "native executable");
1277
1277
  assert.ok(((await stat(binary)).mode & 0o111) !== 0);
@@ -1302,7 +1302,7 @@ test("installer rejects an extracted binary that differs from its pinned digest"
1302
1302
  }),
1303
1303
  /binary checksum mismatch/,
1304
1304
  );
1305
- assert.equal(existsSync(join(packageRoot, ".gentle-ai", "v4.1.1", "gentle-ai")), false);
1305
+ assert.equal(existsSync(join(packageRoot, ".gentle-ai", "v4.2.0", "gentle-ai")), false);
1306
1306
  });
1307
1307
 
1308
1308
  test("installer repairs a valid non-executable POSIX binary instead of reusing it", async (t) => {
@@ -1325,7 +1325,7 @@ test("installer repairs a valid non-executable POSIX binary instead of reusing i
1325
1325
  },
1326
1326
  };
1327
1327
  await installGentleAi(options);
1328
- const binary = join(packageRoot, ".gentle-ai", "v4.1.1", "gentle-ai");
1328
+ const binary = join(packageRoot, ".gentle-ai", "v4.2.0", "gentle-ai");
1329
1329
  await chmod(binary, 0o600);
1330
1330
  const repaired = await installGentleAi(options);
1331
1331
  assert.equal(repaired.installed, true);
@@ -1362,7 +1362,7 @@ test("installer rejects archives with multiple expected executable entries", asy
1362
1362
  }),
1363
1363
  /exactly one regular gentle-ai/,
1364
1364
  );
1365
- assert.equal(existsSync(join(packageRoot, ".gentle-ai", "v4.1.1", "gentle-ai")), false);
1365
+ assert.equal(existsSync(join(packageRoot, ".gentle-ai", "v4.2.0", "gentle-ai")), false);
1366
1366
  });
1367
1367
 
1368
1368
  test("installer rejects an archive without the expected regular executable", async () => {
@@ -1380,7 +1380,7 @@ test("installer rejects an archive without the expected regular executable", asy
1380
1380
  }),
1381
1381
  /non-regular gentle-ai/,
1382
1382
  );
1383
- assert.equal(existsSync(join(packageRoot, ".gentle-ai", "v4.1.1", "gentle-ai")), false);
1383
+ assert.equal(existsSync(join(packageRoot, ".gentle-ai", "v4.2.0", "gentle-ai")), false);
1384
1384
  });
1385
1385
 
1386
1386
  test("safeRemoveDirectory passes Windows-safe retry options to recursive rm cleanup", async () => {