gentle-pi 4.2.0 → 4.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 (334) hide show
  1. package/README.md +340 -138
  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/design-canvas/canvas.css +648 -0
  6. package/assets/design-canvas/canvas.js +1497 -0
  7. package/assets/design-canvas/index.html +88 -0
  8. package/assets/design-canvas/inspector.js +290 -0
  9. package/assets/orchestrator-delegation-reference.md +163 -0
  10. package/assets/orchestrator-delegation.md +25 -176
  11. package/assets/orchestrator-tracking.md +1 -1
  12. package/assets/orchestrator-verification.md +14 -8
  13. package/assets/orchestrator-writer.md +5 -1
  14. package/assets/orchestrator.md +11 -11
  15. package/bin/engram-guard +1 -1
  16. package/bin/gentle-shell.mjs +237 -139
  17. package/contracts/review-provider-contract-mirror/provider-contract.lock.json +8 -7
  18. package/contracts/review-provider-contract-mirror/{v1.3.0 → v1.4.0}/bundle/README.md +7 -0
  19. package/contracts/review-provider-contract-mirror/{v1.3.0 → v1.4.0}/bundle/manifest.json +11 -2
  20. package/contracts/review-provider-contract-mirror/v1.4.0/bundle/routing/pi.md +92 -0
  21. package/contracts/review-provider-contract-mirror/{v1.3.0 → v1.4.0}/generated/provider-capabilities.baseline.json +8 -1
  22. package/contracts/review-provider-contract-mirror/{v1.3.0 → v1.4.0}/generated/provider-roles.baseline.json +1 -1
  23. package/docs/assets/diagrams/agent-orchestration.svg +54 -15
  24. package/docs/assets/diagrams/gentleman-workflow.svg +69 -11
  25. package/docs/assets/diagrams/measured-routing.svg +49 -0
  26. package/docs/assets/diagrams/native-review.svg +52 -12
  27. package/docs/assets/diagrams/odd-workflow.svg +128 -66
  28. package/docs/assets/diagrams/permission-modes.svg +51 -0
  29. package/docs/assets/features/audio-notifications.png +0 -0
  30. package/docs/assets/features/command-palette.png +0 -0
  31. package/docs/bundled-install.md +65 -30
  32. package/docs/delegated-verification.md +16 -8
  33. package/docs/gentle-agents-activity.md +16 -6
  34. package/docs/gentle-music.md +61 -0
  35. package/docs/gentle-shell-uninstall.md +8 -1
  36. package/docs/gentle-shell.md +80 -9
  37. package/docs/install-wizard.md +37 -6
  38. package/docs/permission-modes.md +8 -3
  39. package/docs/readme-reference.md +120 -28
  40. package/docs/session-profile-format.md +26 -0
  41. package/docs/voice.md +259 -0
  42. package/docs/yolo-mode.md +38 -10
  43. package/extensions/ask-user-choice.ts +4 -1
  44. package/extensions/codegraph-tools.ts +68 -6
  45. package/extensions/gentle-agents.ts +270 -48
  46. package/extensions/gentle-ai.ts +490 -113
  47. package/extensions/gentle-design.ts +158 -0
  48. package/extensions/gentle-herdr-activity.ts +2 -3
  49. package/extensions/gentle-music.ts +289 -0
  50. package/extensions/gentle-shell.ts +359 -85
  51. package/extensions/gentle-todo.ts +43 -9
  52. package/extensions/gentle-voice.ts +11 -0
  53. package/extensions/quiet-tools.ts +1 -1
  54. package/extensions/session-scratch.ts +384 -0
  55. package/extensions/skill-registry.ts +63 -8
  56. package/extensions/startup-banner.ts +2 -3
  57. package/lib/agent-home.ts +8 -3
  58. package/lib/agent-profiles.ts +26 -0
  59. package/lib/agents-config.ts +35 -0
  60. package/lib/agents-protocol.ts +60 -1
  61. package/lib/agents-runner.ts +107 -9
  62. package/lib/agents-session-transport.ts +101 -6
  63. package/lib/agents-view.ts +3 -1
  64. package/lib/agents-widget.ts +14 -1
  65. package/lib/auto-permission-memory.ts +48 -18
  66. package/lib/auto-permission-runtime.ts +21 -6
  67. package/lib/bounded-writer-admission.ts +51 -0
  68. package/lib/clipboard-image.ts +147 -0
  69. package/lib/command-palette-catalog.ts +2 -4
  70. package/lib/commit-slice-signal.ts +100 -0
  71. package/lib/context-backstop.ts +120 -0
  72. package/lib/delivered-test-lint.ts +80 -0
  73. package/lib/design-canvas-server.ts +947 -0
  74. package/lib/design-comments.ts +437 -0
  75. package/lib/design-handoffs.ts +313 -0
  76. package/lib/design-open-browser.ts +58 -0
  77. package/lib/design-sources.ts +120 -0
  78. package/lib/destructive-command-guard.ts +120 -9
  79. package/lib/external-editor.ts +31 -0
  80. package/lib/gauge-theme.ts +146 -0
  81. package/lib/gentle-ai-binary.ts +28 -7
  82. package/lib/gentle-design.ts +513 -0
  83. package/lib/gentle-shell-bundled.ts +146 -36
  84. package/lib/gentle-shell-import-pi.ts +233 -0
  85. package/lib/gentle-shell-launcher.ts +154 -15
  86. package/lib/gentle-shell-startup-update.ts +61 -9
  87. package/lib/gentle-shell-uninstall.ts +51 -2
  88. package/lib/herdr-activity.ts +24 -0
  89. package/lib/image-attachments.ts +150 -0
  90. package/lib/inprocess-reviewer.ts +33 -2
  91. package/lib/nan-provider.ts +3 -0
  92. package/lib/native-review-cli.ts +20 -5
  93. package/lib/notification-audio-native.ts +149 -13
  94. package/lib/notification-audio.ts +4 -0
  95. package/lib/notification-pulse-worker.ts +82 -18
  96. package/lib/notification-service.ts +5 -1
  97. package/lib/orchestrator-discovery.ts +5 -1
  98. package/lib/orchestrator-presence.ts +87 -8
  99. package/lib/orchestrator-scope.ts +31 -3
  100. package/lib/orchestrator-state.ts +11 -3
  101. package/lib/permission-mode-policy.ts +2 -2
  102. package/lib/provider-contract-bundle.ts +99 -53
  103. package/lib/review-candidate-view-owner.ts +29 -19
  104. package/lib/review-candidate-view.ts +36 -2
  105. package/lib/review-host-relay.ts +11 -2
  106. package/lib/review-reminder-receipt.ts +24 -1
  107. package/lib/review-risk-assessment.ts +43 -38
  108. package/lib/review-session-standing-permission.ts +3 -1
  109. package/lib/session-profile-append-controller.ts +713 -0
  110. package/lib/session-profile-disk-reader.ts +182 -0
  111. package/lib/session-profile-freeze.ts +30 -1
  112. package/lib/session-profile-persistence.ts +15 -7
  113. package/lib/session-scratch.ts +523 -0
  114. package/lib/session-worktree-registry.ts +7 -0
  115. package/lib/shell-bar.ts +29 -28
  116. package/lib/shell-changes-view.ts +80 -13
  117. package/lib/shell-changes.ts +81 -5
  118. package/lib/shell-gauge.ts +49 -6
  119. package/lib/shell-prompt.ts +4 -4
  120. package/lib/shell-sidebar-layout.ts +26 -6
  121. package/lib/shell-todo.ts +24 -5
  122. package/lib/shell-usage-view.ts +5 -1
  123. package/lib/shell-usage.ts +7 -7
  124. package/lib/verifier-admission.ts +30 -0
  125. package/lib/vim-editor-adapter.ts +11 -8
  126. package/lib/voice-artifacts.ts +188 -0
  127. package/lib/voice-command.ts +576 -0
  128. package/lib/voice-install.ts +455 -0
  129. package/lib/voice-platform.ts +99 -0
  130. package/lib/voice-push-to-talk.ts +262 -0
  131. package/lib/voice-session.ts +460 -0
  132. package/lib/voice-settings.ts +50 -0
  133. package/lib/voice-ui.ts +158 -0
  134. package/lib/yolo-session-policy.ts +58 -22
  135. package/lib/yolo-temp-scratch.ts +69 -0
  136. package/package.json +1 -2
  137. package/runtime/gentle-ai-binary.mjs +28 -7
  138. package/runtime/gentle-shell-bundled.mjs +145 -35
  139. package/runtime/gentle-shell-import-pi.mjs +234 -0
  140. package/runtime/gentle-shell-launcher.mjs +152 -13
  141. package/runtime/gentle-shell-startup-update.mjs +61 -9
  142. package/runtime/gentle-shell-uninstall.mjs +51 -2
  143. package/runtime/native-review-cli.mjs +19 -4
  144. package/runtime/review-risk-assessment.mjs +43 -38
  145. package/scripts/build-runtime-modules.mjs +1 -0
  146. package/scripts/bundled-engram-migration.mjs +119 -0
  147. package/scripts/gentle-ai-installer.mjs +10 -10
  148. package/scripts/installer-downloads.mjs +7 -7
  149. package/scripts/installer-preflight.mjs +5 -3
  150. package/scripts/installer-server.mjs +14 -4
  151. package/scripts/installer-windows.mjs +37 -7
  152. package/scripts/main-channel.mjs +94 -19
  153. package/scripts/pi-update.mjs +34 -6
  154. package/scripts/run-test-suite.mjs +5 -0
  155. package/scripts/verify-package-files.mjs +43 -13
  156. package/scripts/voice-worker.cjs +476 -0
  157. package/skills/design/SKILL.md +110 -0
  158. package/skills/gentle-ai/SKILL.md +3 -3
  159. package/skills/work-unit-commits/SKILL.md +5 -3
  160. package/tests/agent-profiles.test.ts +24 -0
  161. package/tests/agent-risk-escalation.test.ts +37 -10
  162. package/tests/agents-config.test.ts +61 -0
  163. package/tests/agents-fake-child.ts +8 -1
  164. package/tests/agents-protocol.test.ts +32 -0
  165. package/tests/agents-runner.test.ts +485 -7
  166. package/tests/agents-session-transport.test.ts +53 -3
  167. package/tests/ask-user-choice.test.ts +30 -0
  168. package/tests/auto-mode.test.ts +17 -0
  169. package/tests/auto-permission-memory-diagnostics.test.ts +56 -0
  170. package/tests/auto-permission-runtime.test.ts +85 -1
  171. package/tests/autonomous-guard.test.ts +142 -0
  172. package/tests/bounded-writer-admission.test.ts +11 -3
  173. package/tests/bundled-install.test.ts +3 -3
  174. package/tests/card-content-policy.test.ts +19 -9
  175. package/tests/child-safety.test.ts +39 -0
  176. package/tests/clipboard-image.test.ts +114 -0
  177. package/tests/codegraph-tools.test.ts +123 -2
  178. package/tests/command-palette.test.ts +15 -5
  179. package/tests/commit-slice-signal.test.ts +162 -0
  180. package/tests/context-backstop.test.ts +225 -0
  181. package/tests/delegate-for-reason-contract.test.ts +92 -24
  182. package/tests/delivered-test-lint.test.ts +105 -0
  183. package/tests/design-canvas-assets.test.ts +710 -0
  184. package/tests/design-canvas-server.test.ts +1096 -0
  185. package/tests/design-comments.test.ts +201 -0
  186. package/tests/design-handoffs.test.ts +282 -0
  187. package/tests/design-open-browser.test.ts +70 -0
  188. package/tests/design-sources.test.ts +132 -0
  189. package/tests/destructive-command-guard.test.ts +85 -1
  190. package/tests/engram-safety.test.ts +132 -57
  191. package/tests/external-editor.test.ts +84 -0
  192. package/tests/fixtures/primary-session-prompt/gentleman.golden.md +143 -0
  193. package/tests/fixtures/provider-contract-bundle/v1.4.0/README.md +29 -0
  194. package/tests/fixtures/provider-contract-bundle/v1.4.0/manifest.json +83 -0
  195. package/tests/fixtures/provider-contract-bundle/v1.4.0/orchestration/pi.md +70 -0
  196. package/tests/fixtures/provider-contract-bundle/v1.4.0/routing/pi.md +92 -0
  197. package/tests/fixtures/provider-contract-bundle/v1.4.0/schemas/lens.schema.json +16 -0
  198. package/tests/fixtures/provider-contract-bundle/v1.4.0/schemas/refuter.schema.json +1 -0
  199. package/tests/fixtures/provider-contract-bundle/v1.4.0/schemas/targeted-validator.schema.json +1 -0
  200. package/tests/fixtures/provider-contract-bundle/v1.4.0/vectors/lens.json +1 -0
  201. package/tests/fixtures/provider-contract-bundle/v1.4.0/vectors/refuter.json +1 -0
  202. package/tests/fixtures/provider-contract-bundle/v1.4.0/vectors/targeted-validator.json +1 -0
  203. package/tests/gauge-theme.test.ts +120 -0
  204. package/tests/gentle-agents.test.ts +645 -21
  205. package/tests/gentle-ai-binary.test.ts +1 -1
  206. package/tests/gentle-ai-child-guards.test.ts +61 -0
  207. package/tests/gentle-ai-doctor-herdr.test.ts +94 -0
  208. package/tests/gentle-ai-installer.test.ts +51 -51
  209. package/tests/gentle-ai.test.ts +292 -13
  210. package/tests/gentle-design.test.ts +1313 -0
  211. package/tests/gentle-music.test.ts +335 -0
  212. package/tests/gentle-shell-bin.test.ts +163 -0
  213. package/tests/gentle-shell-bundled.test.ts +392 -176
  214. package/tests/gentle-shell-import-pi.test.ts +262 -0
  215. package/tests/gentle-shell-launcher.test.ts +107 -2
  216. package/tests/gentle-shell-startup-update.test.ts +77 -1
  217. package/tests/gentle-shell-uninstall.test.ts +89 -0
  218. package/tests/gentle-shell.test.ts +225 -17
  219. package/tests/gentle-todo.test.ts +190 -0
  220. package/tests/headless-delivery-question.test.ts +58 -0
  221. package/tests/herdr-activity.test.ts +34 -1
  222. package/tests/image-attachments.test.ts +119 -0
  223. package/tests/image-paste-chips.test.ts +236 -0
  224. package/tests/inprocess-reviewer.test.ts +55 -2
  225. package/tests/installer-bundles.test.ts +12 -6
  226. package/tests/installer-posix-bootstrap.test.ts +8 -8
  227. package/tests/installer-preflight.test.ts +16 -0
  228. package/tests/installer-server.test.ts +10 -2
  229. package/tests/installer-windows-bootstrap.test.ts +94 -7
  230. package/tests/installer-windows-diagnostics.test.ts +111 -0
  231. package/tests/main-channel-upgrade.test.ts +232 -2
  232. package/tests/main-channel.test.ts +140 -21
  233. package/tests/nan-provider.test.ts +36 -0
  234. package/tests/native-review-capability-contract.test.ts +17 -4
  235. package/tests/native-review-parity.test.ts +23 -2
  236. package/tests/no-self-pause-contract.test.ts +5 -2
  237. package/tests/notification-audio-native.test.ts +128 -9
  238. package/tests/notification-customize.test.ts +17 -5
  239. package/tests/odd-integration.test.ts +5 -1
  240. package/tests/odd-routing-canonical-ratchet.test.ts +128 -162
  241. package/tests/odd-routing-contract.test.ts +115 -30
  242. package/tests/orchestrator-budget.test.ts +22 -8
  243. package/tests/orchestrator-discovery.test.ts +29 -0
  244. package/tests/orchestrator-presence.test.ts +100 -2
  245. package/tests/orchestrator-rdd-ownership.test.ts +3 -1
  246. package/tests/orchestrator-scope.test.ts +80 -0
  247. package/tests/orchestrator-state.test.ts +29 -0
  248. package/tests/orchestrator-status-publication-contract.test.ts +12 -8
  249. package/tests/package-files.test.ts +34 -0
  250. package/tests/package-manifest.test.ts +24 -20
  251. package/tests/permission-mode-policy.test.ts +10 -1
  252. package/tests/pi-update-launcher.test.ts +21 -2
  253. package/tests/pi-update.test.ts +99 -4
  254. package/tests/primary-session-prompt.test.ts +101 -0
  255. package/tests/provider-contract-bundle.test.ts +118 -0
  256. package/tests/provider-contract-mirror.test.ts +24 -0
  257. package/tests/quiet-tool-rendering.test.ts +23 -0
  258. package/tests/rdd-aware-verification-contract.test.ts +29 -11
  259. package/tests/rdd-status-line.test.ts +79 -0
  260. package/tests/review-agent-end-preflight.test.ts +6 -0
  261. package/tests/review-candidate-view.test.ts +49 -0
  262. package/tests/review-contract-prompt.test.ts +5 -2
  263. package/tests/review-controller-native-routing.test.ts +14 -1
  264. package/tests/review-controller.test.ts +1 -1
  265. package/tests/review-host-relay-routing.test.ts +147 -3
  266. package/tests/review-host-relay.test.ts +9 -2
  267. package/tests/review-json-arguments.test.ts +20 -3
  268. package/tests/review-loadout-rdd-off-sdk.test.ts +258 -0
  269. package/tests/review-owner-identity.test.ts +132 -0
  270. package/tests/review-relay-transport-agent.test.ts +26 -0
  271. package/tests/review-reminder-receipt.test.ts +31 -1
  272. package/tests/review-risk-assessment.test.ts +163 -76
  273. package/tests/review-session-standing-permission-controller.test.ts +14 -1
  274. package/tests/review-session-standing-permission.test.ts +27 -1
  275. package/tests/run-test-suite.test.ts +15 -0
  276. package/tests/runtime-harness.mjs +16 -6
  277. package/tests/session-changes-shell.test.ts +1 -1
  278. package/tests/session-changes.test.ts +2 -1
  279. package/tests/session-profile-append-controller-fork.test.ts +458 -0
  280. package/tests/session-profile-append-controller-pi.test.ts +290 -0
  281. package/tests/session-profile-append-controller.test.ts +731 -0
  282. package/tests/session-profile-disk-reader-pi.test.ts +183 -0
  283. package/tests/session-profile-disk-reader.test.ts +311 -0
  284. package/tests/session-profile-freeze.test.ts +23 -0
  285. package/tests/session-scratch.test.ts +1011 -0
  286. package/tests/session-subject-routing-contract.test.ts +2 -1
  287. package/tests/session-worktree-registry.test.ts +13 -1
  288. package/tests/shell-bar.test.ts +21 -2
  289. package/tests/shell-changes-view.test.ts +137 -1
  290. package/tests/shell-changes.test.ts +148 -1
  291. package/tests/shell-gauge.test.ts +42 -0
  292. package/tests/shell-prompt.test.ts +3 -3
  293. package/tests/shell-render-cache.test.ts +134 -1
  294. package/tests/shell-sidebar-layout.test.ts +50 -0
  295. package/tests/short-check-routing-contract.test.ts +1 -1
  296. package/tests/skill-collision-prefixes.test.ts +3 -1
  297. package/tests/skill-registry-watcher.test.ts +17 -5
  298. package/tests/skill-registry.test.ts +47 -3
  299. package/tests/small-task-inline-contract.test.ts +37 -0
  300. package/tests/standalone-config-home.test.ts +324 -0
  301. package/tests/startup-banner.test.ts +5 -4
  302. package/tests/startup-home-repo.test.ts +34 -0
  303. package/tests/support/orchestrator-modules.ts +1 -0
  304. package/tests/task-size-routing-contract.test.ts +70 -10
  305. package/tests/test-scratch-contract.test.ts +102 -0
  306. package/tests/test-strength-contract.test.ts +36 -1
  307. package/tests/tool-batching-contract.test.ts +64 -0
  308. package/tests/verification-proportionality.test.ts +46 -0
  309. package/tests/verifier-admission.test.ts +154 -0
  310. package/tests/verify-agent-contract.test.ts +8 -8
  311. package/tests/voice-command.test.ts +773 -0
  312. package/tests/voice-install.test.ts +323 -0
  313. package/tests/voice-platform.test.ts +176 -0
  314. package/tests/voice-push-to-talk.test.ts +444 -0
  315. package/tests/voice-session.test.ts +434 -0
  316. package/tests/voice-settings.test.ts +49 -0
  317. package/tests/voice-ui.test.ts +97 -0
  318. package/tests/voice-worker.test.ts +445 -0
  319. package/tests/weak-model-economy-prompt.test.ts +32 -0
  320. package/tests/windows-hidden-processes.test.ts +30 -2
  321. package/tests/writer-edit-surface-scope.test.ts +3 -0
  322. package/tests/writer-feature-document-admission.test.ts +156 -0
  323. package/tests/yolo-customize.test.ts +5 -1
  324. package/tests/yolo-mode.test.ts +19 -1
  325. package/tests/yolo-session-policy-capture.test.ts +104 -0
  326. package/tests/yolo-temp-scratch.test.ts +71 -0
  327. package/scripts/mirror-odd-routing.mjs +0 -242
  328. /package/contracts/review-provider-contract-mirror/{v1.3.0 → v1.4.0}/bundle/orchestration/pi.md +0 -0
  329. /package/contracts/review-provider-contract-mirror/{v1.3.0 → v1.4.0}/bundle/schemas/lens.schema.json +0 -0
  330. /package/contracts/review-provider-contract-mirror/{v1.3.0 → v1.4.0}/bundle/schemas/refuter.schema.json +0 -0
  331. /package/contracts/review-provider-contract-mirror/{v1.3.0 → v1.4.0}/bundle/schemas/targeted-validator.schema.json +0 -0
  332. /package/contracts/review-provider-contract-mirror/{v1.3.0 → v1.4.0}/bundle/vectors/lens.json +0 -0
  333. /package/contracts/review-provider-contract-mirror/{v1.3.0 → v1.4.0}/bundle/vectors/refuter.json +0 -0
  334. /package/contracts/review-provider-contract-mirror/{v1.3.0 → v1.4.0}/bundle/vectors/targeted-validator.json +0 -0
@@ -0,0 +1,290 @@
1
+ // Public SessionManager fixtures only. Retain isolated data for parent evidence.
2
+ import assert from "node:assert/strict";
3
+ import {
4
+ existsSync,
5
+ mkdirSync,
6
+ mkdtempSync,
7
+ readFileSync,
8
+ renameSync,
9
+ } from "node:fs";
10
+ import { tmpdir } from "node:os";
11
+ import { dirname, join } from "node:path";
12
+ import { test } from "node:test";
13
+ import { fileURLToPath } from "node:url";
14
+ import { SessionManager } from "@earendil-works/pi-coding-agent";
15
+ import {
16
+ createSessionProfileBind,
17
+ SESSION_PROFILE_CUSTOM_TYPE,
18
+ } from "../lib/session-profile-persistence.ts";
19
+ import {
20
+ createSessionProfileAppendController,
21
+ type SessionProfileAppendOutcome,
22
+ } from "../lib/session-profile-append-controller.ts";
23
+
24
+ const metadata = join(
25
+ dirname(fileURLToPath(import.meta.resolve("@earendil-works/pi-coding-agent"))),
26
+ "..",
27
+ "package.json",
28
+ );
29
+ const version: string = JSON.parse(readFileSync(metadata, "utf8")).version;
30
+ assert.match(version, /^\d+\.\d+\.\d+$/);
31
+ assert.ok(
32
+ Number(version.split(".")[0]) >= 1,
33
+ `Public fixtures require Pi >=1.0.0, installed ${version}`,
34
+ );
35
+ console.info(
36
+ `Public append controller fixture API: ${version}; minimum tested baseline 1.0.0`,
37
+ );
38
+ function controller(manager: SessionManager, errors: unknown[] = []) {
39
+ // Extension appendEntry is void; the actual public manager's ID is ignored.
40
+ return createSessionProfileAppendController(manager, (type, data): void => {
41
+ try {
42
+ manager.appendCustomEntry(type, data);
43
+ } catch (error) {
44
+ errors.push(error);
45
+ throw error;
46
+ }
47
+ });
48
+ }
49
+ function fixture() {
50
+ const root = mkdtempSync(join(tmpdir(), "profile-append-pi-"));
51
+ const sessions = join(root, "sessions");
52
+ const manager = SessionManager.create(root, sessions);
53
+ const errors: unknown[] = [];
54
+ return { manager, sessions, errors, adapter: controller(manager, errors) };
55
+ }
56
+ function flush(manager: SessionManager) {
57
+ // Ordinary test-owned conversation fixture, never a production workaround.
58
+ manager.appendMessage({
59
+ role: "user",
60
+ content: "isolated append controller fixture",
61
+ timestamp: Date.now(),
62
+ });
63
+ }
64
+ function bound(result: SessionProfileAppendOutcome, name: string) {
65
+ assert.equal(result.state.status, "bound");
66
+ if (result.state.status !== "bound")
67
+ throw new Error("Expected bound authority");
68
+ assert.equal(result.state.binding.name, name);
69
+ return result.state.binding;
70
+ }
71
+ for (const operation of ["bind", "clear"] as const) {
72
+ test(`public explicit custom-only ${operation} reports preflush, normal fixture flush upgrades exact record`, () => {
73
+ const { manager, adapter } = fixture();
74
+ // isPersisted() is configuration, not a first-flush witness.
75
+ assert.equal(manager.isPersisted(), true);
76
+ const first =
77
+ operation === "bind" ? adapter.bind("initial", {}) : adapter.clear();
78
+ assert.equal(first.status, "not-persisted");
79
+ assert.equal(first.state.status, operation === "bind" ? "bound" : "cleared");
80
+ const file = manager.getSessionFile();
81
+ assert.ok(file);
82
+ assert.equal(existsSync(file), false);
83
+ assert.equal(adapter.refresh().status, "not-persisted");
84
+ assert.equal(controller(manager).refresh().status, "indeterminate");
85
+ const selected = manager.getBranch().at(-1);
86
+ assert.ok(selected);
87
+ flush(manager);
88
+ const persisted = adapter.refresh();
89
+ assert.equal(persisted.status, "persisted");
90
+ assert.equal(
91
+ persisted.state.status,
92
+ operation === "bind" ? "bound" : "cleared",
93
+ );
94
+ const disk = readFileSync(file, "utf8")
95
+ .trim()
96
+ .split("\n")
97
+ .map((line) => JSON.parse(line));
98
+ assert.deepEqual(
99
+ disk.find((row) => row.id === selected.id),
100
+ JSON.parse(JSON.stringify(selected)),
101
+ );
102
+ });
103
+ }
104
+ for (const prior of ["bound", "invalid", "future"] as const) {
105
+ for (const operation of ["bind", "clear"] as const) {
106
+ test(`public NEW ${operation} over unwritten ${prior} is not restoration or first-flush proof`, () => {
107
+ const { manager } = fixture();
108
+ manager.appendCustomEntry(
109
+ prior === "future"
110
+ ? "gentle-pi.session-profile/v2"
111
+ : SESSION_PROFILE_CUSTOM_TYPE,
112
+ prior === "bound" ? createSessionProfileBind("unwritten", {}) : {},
113
+ );
114
+ const file = manager.getSessionFile();
115
+ assert.ok(file);
116
+ assert.equal(existsSync(file), false);
117
+ const adapter = controller(manager);
118
+ assert.equal(adapter.refresh().status, "indeterminate");
119
+ const result =
120
+ operation === "bind" ? adapter.bind("NEW", {}) : adapter.clear();
121
+ assert.equal(result.status, "not-persisted");
122
+ assert.equal(
123
+ result.state.status,
124
+ operation === "bind" ? "bound" : "cleared",
125
+ );
126
+ if (operation === "bind") bound(result, "NEW");
127
+ assert.equal(adapter.refresh().status, "not-persisted");
128
+ assert.equal(controller(manager).refresh().status, "indeterminate");
129
+ const selected = manager.getBranch().at(-1);
130
+ assert.ok(selected);
131
+ flush(manager);
132
+ assert.equal(adapter.refresh().status, "persisted");
133
+ assert.equal(
134
+ controller(manager).refresh().state.status,
135
+ operation === "bind" ? "bound" : "cleared",
136
+ );
137
+ const disk = readFileSync(file, "utf8")
138
+ .trim()
139
+ .split("\n")
140
+ .map((line) => JSON.parse(line));
141
+ assert.deepEqual(
142
+ disk.find((row) => row.id === selected.id),
143
+ JSON.parse(JSON.stringify(selected)),
144
+ );
145
+ });
146
+ }
147
+ }
148
+ for (const failure of ["bind", "clear"] as const) {
149
+ for (const recovery of ["bind", "clear"] as const) {
150
+ test(`public EISDIR failed ${failure} never adopted; new ${recovery} survives orphan-parent reopen`, () => {
151
+ const { manager, sessions, adapter, errors } = fixture();
152
+ assert.equal(
153
+ adapter.bind("good", {
154
+ worker: { model: "offline/good" },
155
+ orchestrator: { model: "offline/preserved" },
156
+ }).status,
157
+ "not-persisted",
158
+ );
159
+ flush(manager);
160
+ bound(adapter.refresh(), "good");
161
+ const file = manager.getSessionFile();
162
+ assert.ok(file);
163
+ const saved = `${file}.saved`;
164
+ renameSync(file, saved);
165
+ mkdirSync(file);
166
+ const failed =
167
+ failure === "bind" ? adapter.bind("failed", {}) : adapter.clear();
168
+ assert.equal(failed.status, "indeterminate");
169
+ assert.equal(errors.length, 1);
170
+ assert.equal((errors[0] as NodeJS.ErrnoException).code, "EISDIR");
171
+ const ghost = manager.getBranch().at(-1);
172
+ assert.ok(ghost);
173
+ assert.equal(ghost.type, "custom");
174
+ // Recover fixture filesystem by moves, not history edits or private APIs.
175
+ renameSync(file, `${file}.fault-directory`);
176
+ renameSync(saved, file);
177
+ const preserved = adapter.refresh();
178
+ assert.equal(preserved.status, "persisted");
179
+ bound(preserved, "good");
180
+ const branch = manager.getBranch();
181
+ const good = branch.find(
182
+ (entry) => entry.type === "custom" && entry.id !== ghost.id,
183
+ );
184
+ assert.ok(good);
185
+ manager.branch(good.id);
186
+ bound(adapter.refresh(), "good");
187
+ manager.branch(ghost.id);
188
+ bound(adapter.refresh(), "good");
189
+ const recovered =
190
+ recovery === "bind" ? adapter.bind("recovered", {}) : adapter.clear();
191
+ assert.equal(recovered.status, "persisted");
192
+ assert.equal(
193
+ recovered.state.status,
194
+ recovery === "bind" ? "bound" : "cleared",
195
+ );
196
+ const latest = manager.getBranch().at(-1);
197
+ assert.ok(latest);
198
+ assert.equal(latest.parentId, ghost.id);
199
+ const disk = readFileSync(file, "utf8")
200
+ .trim()
201
+ .split("\n")
202
+ .map((line) => JSON.parse(line));
203
+ assert.equal(
204
+ disk.some((row) => row.id === ghost.id),
205
+ false,
206
+ );
207
+ assert.deepEqual(
208
+ disk.find((row) => row.id === latest.id),
209
+ JSON.parse(JSON.stringify(latest)),
210
+ );
211
+ const reopened = SessionManager.open(file, sessions);
212
+ const restored = controller(reopened).refresh();
213
+ assert.equal(restored.status, "persisted");
214
+ assert.equal(
215
+ restored.state.status,
216
+ recovery === "bind" ? "bound" : "cleared",
217
+ );
218
+ // Pi truncates the resumed branch at the missing failed parent. The new
219
+ // selected record is valid authority without history repair/full ancestry.
220
+ assert.equal(reopened.getBranch().length, 1);
221
+ assert.equal(
222
+ reopened.getBranch().some((entry) => entry.id === ghost.id),
223
+ false,
224
+ );
225
+ assert.equal(reopened.getBranch().at(-1)?.id, latest.id);
226
+ manager.branch(ghost.id);
227
+ bound(adapter.refresh(), "good");
228
+ });
229
+ }
230
+ }
231
+ test("public sibling disk records never override the selected active branch", () => {
232
+ const { manager, adapter } = fixture();
233
+ adapter.bind("base", {});
234
+ flush(manager);
235
+ const base = manager.getLeafId();
236
+ assert.ok(base);
237
+ assert.equal(adapter.bind("left", {}).status, "persisted");
238
+ const left = manager.getLeafId();
239
+ assert.ok(left);
240
+ manager.branch(base);
241
+ assert.equal(adapter.bind("right", {}).status, "persisted");
242
+ bound(adapter.refresh(), "right");
243
+ manager.branch(left);
244
+ bound(adapter.refresh(), "left");
245
+ const other = fixture();
246
+ assert.equal(other.adapter.refresh().status, "indeterminate");
247
+ });
248
+
249
+ for (const failedOperation of ["bind", "clear"] as const) {
250
+ test(`public EISDIR ${failedOperation} quarantine survives detach and fresh append capability`, () => {
251
+ const f = fixture();
252
+ f.adapter.bind("trusted", {});
253
+ flush(f.manager);
254
+ const file = f.manager.getSessionFile();
255
+ assert.ok(file);
256
+ const original = readFileSync(file, "utf8");
257
+ renameSync(file, `${file}.saved`);
258
+ mkdirSync(file);
259
+ const failed =
260
+ failedOperation === "bind"
261
+ ? f.adapter.bind("FAILED", {})
262
+ : f.adapter.clear();
263
+ assert.equal(failed.status, "indeterminate");
264
+ assert.equal(readFileSync(`${file}.saved`, "utf8"), original);
265
+ const ghost = f.manager.getLeafId();
266
+ assert.ok(ghost);
267
+ renameSync(file, `${file}.fault-directory`);
268
+ renameSync(`${file}.saved`, file);
269
+ f.adapter.detach();
270
+ let calls = 0;
271
+ const current = f.adapter.attach(
272
+ f.manager,
273
+ (t, d) => {
274
+ calls++;
275
+ f.manager.appendCustomEntry(t, d);
276
+ },
277
+ { reason: "reload" },
278
+ );
279
+ bound(current.refresh(), "trusted");
280
+ const length = f.manager.getBranch().length;
281
+ assert.equal(f.adapter.bind("stale", {}).status, "indeterminate");
282
+ assert.equal(f.manager.getBranch().length, length);
283
+ assert.equal(calls, 0);
284
+ assert.equal(current.clear().status, "persisted");
285
+ assert.equal(calls, 1);
286
+ f.manager.branch(ghost);
287
+ bound(current.refresh(), "trusted");
288
+ assert.equal(f.errors.length, 1);
289
+ });
290
+ }