gentle-pi 3.7.0 → 4.0.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 (316) hide show
  1. package/README.md +37 -6
  2. package/assets/agents/gentle-ai-explore.md +4 -4
  3. package/assets/agents/gentle-ai-verify.md +6 -4
  4. package/assets/agents/gentle-ai-worker.md +7 -9
  5. package/assets/orchestrator-delegation.md +40 -41
  6. package/assets/orchestrator-memory.md +1 -22
  7. package/assets/orchestrator-skills.md +1 -1
  8. package/assets/orchestrator.md +12 -24
  9. package/assets/support/strict-tdd-verify.md +4 -266
  10. package/assets/support/strict-tdd.md +8 -360
  11. package/bin/gentle-shell.mjs +254 -29
  12. package/docs/delegated-verification.md +26 -1
  13. package/docs/gentle-agents-activity.md +24 -0
  14. package/docs/gentle-shell.md +92 -28
  15. package/docs/native-authority-architecture.md +2 -2
  16. package/docs/prompt-history.md +280 -0
  17. package/docs/readme-reference.md +112 -206
  18. package/docs/telemetry.md +1 -1
  19. package/docs/yolo-mode.md +86 -0
  20. package/extensions/child-context.ts +26 -0
  21. package/extensions/child-safety.ts +23 -0
  22. package/extensions/gentle-agents.ts +429 -262
  23. package/extensions/gentle-ai.ts +781 -683
  24. package/extensions/gentle-shell.ts +1375 -88
  25. package/extensions/gentle-stats.ts +101 -0
  26. package/extensions/gentle-todo.ts +18 -7
  27. package/extensions/history/atomic-write.ts +38 -0
  28. package/extensions/history/hide-prompts.ts +183 -0
  29. package/extensions/history/index.ts +1419 -0
  30. package/extensions/history/load-shared-history.ts +39 -0
  31. package/extensions/history/selector-helpers.ts +538 -0
  32. package/extensions/history/session-scan.ts +233 -0
  33. package/extensions/history/store.ts +1119 -0
  34. package/extensions/nan-provider.ts +6 -0
  35. package/extensions/quiet-tools.ts +179 -87
  36. package/extensions/resume-hint.ts +60 -0
  37. package/extensions/skill-registry.ts +16 -12
  38. package/extensions/startup-banner.ts +60 -31
  39. package/lib/agent-assets.ts +604 -0
  40. package/lib/agent-profile-pin.ts +12 -0
  41. package/lib/agents-message-delivery.ts +181 -0
  42. package/lib/agents-protocol.ts +60 -0
  43. package/lib/agents-runner.ts +96 -98
  44. package/lib/agents-view.ts +26 -3
  45. package/lib/agents-widget.ts +16 -9
  46. package/lib/append-system-prompt.ts +21 -0
  47. package/lib/bounded-writer-admission.ts +147 -0
  48. package/lib/card-style-policy.ts +60 -0
  49. package/lib/child-context-files.ts +166 -0
  50. package/lib/codemode-renderer.ts +185 -0
  51. package/lib/command-palette-catalog.ts +3 -9
  52. package/lib/command-palette.ts +25 -14
  53. package/lib/destructive-command-guard.ts +144 -0
  54. package/lib/gentle-ai-elapsed-store.ts +87 -0
  55. package/lib/gentle-ai-renderer.ts +196 -39
  56. package/lib/gentle-shell-launcher.ts +24 -13
  57. package/lib/gentle-shell-resume-hint.ts +176 -0
  58. package/lib/history-capture-policy.ts +95 -0
  59. package/lib/model-routing-authority.ts +5 -1
  60. package/lib/nan-provider.ts +227 -0
  61. package/lib/native-review-cli.ts +49 -108
  62. package/lib/odd-phase-inference.ts +231 -0
  63. package/lib/odd-phase.ts +141 -0
  64. package/lib/overlay-repaint.ts +26 -0
  65. package/lib/pi-tui-keys.ts +53 -0
  66. package/lib/review-candidate-view-owner.ts +67 -17
  67. package/lib/review-candidate-view.ts +112 -25
  68. package/lib/review-reminder-receipt.ts +48 -8
  69. package/lib/review-risk-assessment.ts +156 -11
  70. package/lib/review-sidebar-state.ts +223 -0
  71. package/lib/selection-engine.ts +515 -0
  72. package/lib/session-messaging-grants.ts +135 -0
  73. package/lib/session-worktree-registry.ts +14 -2
  74. package/lib/shell-bar.ts +179 -24
  75. package/lib/shell-card.ts +287 -18
  76. package/lib/shell-changes-view.ts +2 -1
  77. package/lib/shell-prompt.ts +102 -6
  78. package/lib/shell-sidebar-layout.ts +78 -14
  79. package/lib/shell-sidebar.ts +15 -1
  80. package/lib/shell-todo.ts +23 -13
  81. package/lib/shell-usage-view.ts +9 -4
  82. package/lib/shell-usage.ts +66 -10
  83. package/lib/stats-collector.ts +381 -0
  84. package/lib/stats-view.ts +431 -0
  85. package/lib/theme-customization.ts +52 -0
  86. package/lib/vim-editor-adapter.ts +379 -0
  87. package/lib/vim-normal-engine.ts +154 -0
  88. package/lib/vim-operator-engine.ts +416 -0
  89. package/lib/vim-policy.ts +49 -0
  90. package/lib/vim-visual-engine.ts +107 -0
  91. package/lib/visual-customization-policy.ts +108 -0
  92. package/lib/visual-customize-view.ts +330 -0
  93. package/lib/visual-profiles.ts +228 -0
  94. package/lib/yolo-session-policy.ts +240 -0
  95. package/package.json +20 -8
  96. package/runtime/gentle-shell-launcher.mjs +23 -12
  97. package/runtime/gentle-shell-resume-hint.mjs +177 -0
  98. package/runtime/native-review-cli.mjs +49 -108
  99. package/runtime/review-risk-assessment.mjs +154 -9
  100. package/scripts/build-runtime-modules.mjs +1 -0
  101. package/scripts/gentle-ai-installer.mjs +14 -13
  102. package/scripts/mirror-odd-routing.mjs +2 -2
  103. package/scripts/run-test-suite.mjs +76 -0
  104. package/scripts/test-packed-runner.mjs +31 -14
  105. package/scripts/verify-package-files.mjs +11 -22
  106. package/skills/branch-pr/SKILL.md +24 -52
  107. package/skills/chained-pr/SKILL.md +31 -15
  108. package/skills/chained-pr/references/chaining-details.md +31 -20
  109. package/skills/gentle-ai/SKILL.md +9 -15
  110. package/skills/issue-creation/SKILL.md +8 -2
  111. package/skills/issue-creation/references/delegated-workflow-actions.md +19 -0
  112. package/skills/work-unit-commits/SKILL.md +4 -3
  113. package/tests/agent-profiles.test.ts +18 -0
  114. package/tests/agents-fake-child.ts +2 -2
  115. package/tests/agents-message-delivery.test.ts +106 -0
  116. package/tests/agents-protocol.test.ts +40 -0
  117. package/tests/agents-runner.test.ts +378 -89
  118. package/tests/agents-view-thread-identity.test.ts +169 -0
  119. package/tests/agents-view.test.ts +8 -2
  120. package/tests/agents-widget.test.ts +154 -15
  121. package/tests/append-system-prompt-route.test.ts +160 -0
  122. package/tests/append-system-prompt.test.ts +46 -0
  123. package/tests/artifact-language.test.ts +19 -213
  124. package/tests/ask-user-question.test.ts +44 -1
  125. package/tests/asset-installation-runtime.test.ts +5 -16
  126. package/tests/autonomous-guard.test.ts +69 -1
  127. package/tests/bounded-writer-admission.test.ts +95 -0
  128. package/tests/branch-pr-skill.test.ts +43 -0
  129. package/tests/card-style-policy.test.ts +55 -0
  130. package/tests/chained-pr-skill.test.ts +124 -0
  131. package/tests/child-context-files.test.ts +255 -0
  132. package/tests/child-safety.test.ts +82 -0
  133. package/tests/codemode-rendering.test.ts +491 -0
  134. package/tests/command-palette.test.ts +39 -3
  135. package/tests/delegated-key-learnings-contract.test.ts +0 -76
  136. package/tests/destructive-command-guard.test.ts +84 -0
  137. package/tests/devbinary/native-review-parity.devtest.ts +170 -2
  138. package/tests/devbinary/non-git-subagent-bootstrap.devtest.ts +193 -0
  139. package/tests/fixtures/stats/sessions/--work-alpha--/2026-09-28T10-00-00-000Z_aaa.jsonl +7 -0
  140. package/tests/fixtures/stats/sessions/--work-alpha--/2026-09-29T23-00-00-000Z_bbb.jsonl +3 -0
  141. package/tests/fixtures/stats/sessions/--work-alpha--/2026-09-30T08-00-00-000Z_ddd.jsonl +2 -0
  142. package/tests/fixtures/stats/sessions/--work-alpha--/2026-09-30T09-00-00-000Z_eee.jsonl +3 -0
  143. package/tests/fixtures/stats/sessions/--work-alpha--/run-1/session.jsonl +2 -0
  144. package/tests/fixtures/stats/sessions/--work-beta--/2026-09-01T12-00-00-000Z_ccc.jsonl +2 -0
  145. package/tests/fixtures/stats/user-pi/sessions/--work-alpha--/2026-09-28T10-00-00-000Z_aaa.jsonl +2 -0
  146. package/tests/fixtures/stats/user-pi/sessions/--work-alpha--/2026-09-29T23-00-00-000Z_bbb.jsonl +4 -0
  147. package/tests/fixtures/stats/user-pi/sessions/--work-gamma--/2026-09-20T09-00-00-000Z_fff.jsonl +3 -0
  148. package/tests/generic-agent-tools.test.ts +54 -0
  149. package/tests/gentle-agents.test.ts +1047 -252
  150. package/tests/gentle-ai-binary.test.ts +3 -3
  151. package/tests/gentle-ai-elapsed-store.test.ts +68 -0
  152. package/tests/gentle-ai-installer.test.ts +68 -54
  153. package/tests/gentle-ai-renderer.test.ts +487 -8
  154. package/tests/gentle-ai.test.ts +288 -65
  155. package/tests/gentle-card-text.ts +2 -1
  156. package/tests/gentle-shell-bin.test.ts +651 -114
  157. package/tests/gentle-shell-launcher.test.ts +99 -52
  158. package/tests/gentle-shell-resume-hint.test.ts +270 -0
  159. package/tests/gentle-shell.test.ts +3839 -187
  160. package/tests/gentle-stats.test.ts +152 -0
  161. package/tests/gentle-theme.test.ts +4 -1
  162. package/tests/gentle-todo.test.ts +80 -8
  163. package/tests/history-atomic-write.test.ts +57 -0
  164. package/tests/history-capture-policy.test.ts +102 -0
  165. package/tests/history-command-registration.test.ts +164 -0
  166. package/tests/history-dedupe-entries.test.ts +123 -0
  167. package/tests/history-delete-backfill.test.ts +190 -0
  168. package/tests/history-delete-confirm.test.ts +460 -0
  169. package/tests/history-dispatch.test.ts +180 -0
  170. package/tests/history-drain-hidden.test.ts +110 -0
  171. package/tests/history-drain-order.test.ts +98 -0
  172. package/tests/history-expanded-globals.test.ts +62 -0
  173. package/tests/history-gc.test.ts +832 -0
  174. package/tests/history-header-layout.test.ts +265 -0
  175. package/tests/history-hide-prompts.test.ts +275 -0
  176. package/tests/history-lazy-windowing.test.ts +508 -0
  177. package/tests/history-legacy-migrate-v2.test.ts +297 -0
  178. package/tests/history-load-shared-history.test.ts +53 -0
  179. package/tests/history-max-results-cap.test.ts +76 -0
  180. package/tests/history-multi-reader.test.ts +203 -0
  181. package/tests/history-off-path.test.ts +170 -0
  182. package/tests/history-openflow-integration.test.ts +173 -0
  183. package/tests/history-overlay-margin.test.ts +326 -0
  184. package/tests/history-preview-layout.test.ts +93 -0
  185. package/tests/history-registry.test.ts +143 -0
  186. package/tests/history-scope-delete.test.ts +411 -0
  187. package/tests/history-search-caret-keys.test.ts +142 -0
  188. package/tests/history-seed-bootstrap.test.ts +170 -0
  189. package/tests/history-seed-regen.test.ts +129 -0
  190. package/tests/history-selector-windowing.test.ts +94 -0
  191. package/tests/history-session-scan-directory.test.ts +87 -0
  192. package/tests/history-session-scan-extract.test.ts +583 -0
  193. package/tests/history-session-writer.test.ts +351 -0
  194. package/tests/history-store-paths.test.ts +79 -0
  195. package/tests/history-tombstone-exact.test.ts +139 -0
  196. package/tests/history-wheel-mouse.test.ts +242 -0
  197. package/tests/inprocess-reviewer.test.ts +29 -19
  198. package/tests/issue-creation-skill.test.ts +61 -0
  199. package/tests/model-routing-authority.test.ts +16 -0
  200. package/tests/nan-provider.test.ts +471 -0
  201. package/tests/native-review-capability-contract.test.ts +7 -1
  202. package/tests/native-review-cli.test.ts +6 -120
  203. package/tests/native-review-parity-runtime.test.ts +100 -3
  204. package/tests/odd-integration.test.ts +67 -0
  205. package/tests/odd-phase-inference.test.ts +213 -0
  206. package/tests/odd-phase-loader.test.ts +253 -0
  207. package/tests/odd-phase.test.ts +307 -0
  208. package/tests/odd-routing-canonical-ratchet.test.ts +11 -6
  209. package/tests/odd-routing-contract.test.ts +86 -35
  210. package/tests/orchestrator-budget.test.ts +14 -39
  211. package/tests/orchestrator-rdd-ownership.test.ts +3 -3
  212. package/tests/overlay-repaint.test.ts +74 -0
  213. package/tests/package-manifest.test.ts +252 -115
  214. package/tests/packed-runner-owned-path.test.ts +46 -0
  215. package/tests/persona-single-channel.test.ts +6 -6
  216. package/tests/provider-defect-handoff.test.ts +3 -11
  217. package/tests/quiet-bash-runtime.test.ts +76 -0
  218. package/tests/quiet-tool-rendering.test.ts +409 -184
  219. package/tests/rdd-aware-verification-contract.test.ts +76 -1
  220. package/tests/rdd-status-line.test.ts +9 -4
  221. package/tests/resume-hint-extension.test.ts +122 -0
  222. package/tests/review-agent-end-preflight.test.ts +176 -12
  223. package/tests/review-candidate-owner-retry.test.ts +22 -1
  224. package/tests/review-candidate-view.test.ts +298 -0
  225. package/tests/review-contract-prompt.test.ts +108 -43
  226. package/tests/review-controller-lock-status.test.ts +0 -1
  227. package/tests/review-controller-native-routing.test.ts +611 -5
  228. package/tests/review-controller-workspace-root.test.ts +163 -4
  229. package/tests/review-host-relay-routing.test.ts +338 -2
  230. package/tests/review-integration-v2-forward.test.ts +200 -0
  231. package/tests/review-ledger-contract.test.ts +10 -34
  232. package/tests/review-reminder-receipt.test.ts +47 -1
  233. package/tests/review-risk-assessment.test.ts +498 -6
  234. package/tests/review-sidebar-state.test.ts +402 -0
  235. package/tests/run-test-suite.test.ts +124 -0
  236. package/tests/runtime-harness.mjs +145 -786
  237. package/tests/runtime-metrics-children.test.ts +16 -23
  238. package/tests/selection-engine.test.ts +421 -0
  239. package/tests/session-messaging-grants.test.ts +255 -0
  240. package/tests/session-worktree-registry.test.ts +77 -0
  241. package/tests/shell-bar.test.ts +382 -1
  242. package/tests/shell-card.test.ts +353 -1
  243. package/tests/shell-changes-view.test.ts +52 -0
  244. package/tests/shell-prompt.test.ts +94 -2
  245. package/tests/shell-sidebar-layout.test.ts +325 -21
  246. package/tests/shell-sidebar-scroll-benchmark.test.ts +255 -0
  247. package/tests/shell-todo.test.ts +87 -1
  248. package/tests/shell-usage-view.test.ts +27 -0
  249. package/tests/shell-usage.test.ts +73 -0
  250. package/tests/skill-registry.test.ts +50 -1
  251. package/tests/startup-banner.test.ts +130 -2
  252. package/tests/stats-collector.test.ts +195 -0
  253. package/tests/stats-view.test.ts +202 -0
  254. package/tests/telemetry-trigger.test.ts +81 -20
  255. package/tests/theme-customization.test.ts +72 -0
  256. package/tests/vim-editor-adapter-host-resolution.test.ts +37 -0
  257. package/tests/vim-editor-adapter.test.ts +804 -0
  258. package/tests/vim-normal-engine.test.ts +101 -0
  259. package/tests/vim-operator-engine.test.ts +215 -0
  260. package/tests/vim-policy.test.ts +19 -0
  261. package/tests/vim-visual-engine.test.ts +52 -0
  262. package/tests/visual-customization-policy.test.ts +110 -0
  263. package/tests/visual-customize-view.test.ts +418 -0
  264. package/tests/visual-profiles.test.ts +87 -0
  265. package/tests/yolo-customize.test.ts +256 -0
  266. package/tests/yolo-mode-runtime.test.ts +161 -0
  267. package/tests/yolo-mode.test.ts +261 -0
  268. package/tests/yolo-session-policy.test.ts +59 -0
  269. package/themes/Gentle.json +2 -1
  270. package/themes/Gentleman-Cute.json +2 -1
  271. package/themes/Gentleman-Sexy.json +2 -1
  272. package/assets/agents/sdd-apply.md +0 -159
  273. package/assets/agents/sdd-archive.md +0 -228
  274. package/assets/agents/sdd-design.md +0 -49
  275. package/assets/agents/sdd-explore.md +0 -48
  276. package/assets/agents/sdd-init.md +0 -56
  277. package/assets/agents/sdd-onboard.md +0 -52
  278. package/assets/agents/sdd-proposal.md +0 -64
  279. package/assets/agents/sdd-remediate.md +0 -37
  280. package/assets/agents/sdd-research.md +0 -49
  281. package/assets/agents/sdd-spec.md +0 -192
  282. package/assets/agents/sdd-status.md +0 -54
  283. package/assets/agents/sdd-tasks.md +0 -108
  284. package/assets/agents/sdd-verify.md +0 -124
  285. package/assets/chains/sdd-full.chain.md +0 -83
  286. package/assets/chains/sdd-plan.chain.md +0 -56
  287. package/assets/chains/sdd-verify.chain.md +0 -43
  288. package/assets/sdd-orchestrator-workflow.md +0 -319
  289. package/assets/support/sdd-status-contract.md +0 -77
  290. package/docs/assets/diagrams/sdd-cycle.svg +0 -14
  291. package/extensions/sdd-init.ts +0 -816
  292. package/lib/openspec-deltas.ts +0 -156
  293. package/lib/sdd-preflight.ts +0 -1066
  294. package/lib/sdd-research-capabilities.ts +0 -94
  295. package/lib/sdd-status.ts +0 -26
  296. package/tests/fixtures/legacy/sdd-research-v2.5.0.md +0 -54
  297. package/tests/fixtures/native-review-cli/v2.1.3/bind-sdd.json +0 -25
  298. package/tests/fixtures/v0.10.7/assets/agents/sdd-apply.md +0 -132
  299. package/tests/openspec-deltas.test.ts +0 -209
  300. package/tests/sdd-agent-tools.test.ts +0 -156
  301. package/tests/sdd-archive-replay.test.ts +0 -82
  302. package/tests/sdd-classical-continuation.test.ts +0 -74
  303. package/tests/sdd-execution-routing-contract.test.ts +0 -44
  304. package/tests/sdd-managed-runtime-settlement.test.ts +0 -155
  305. package/tests/sdd-native-managed-uptake.test.ts +0 -243
  306. package/tests/sdd-no-attempts-contract.test.ts +0 -15
  307. package/tests/sdd-odd-integration.test.ts +0 -33
  308. package/tests/sdd-optional-research.test.ts +0 -124
  309. package/tests/sdd-planning-routing-contract.test.ts +0 -45
  310. package/tests/sdd-preflight-rpc-input.test.ts +0 -125
  311. package/tests/sdd-preflight.test.ts +0 -541
  312. package/tests/sdd-research-capabilities.test.ts +0 -114
  313. package/tests/sdd-research-live.test.ts +0 -241
  314. package/tests/sdd-selection-transport.test.ts +0 -653
  315. package/tests/sdd-status.test.ts +0 -9
  316. package/tests/sdd-task-truth.test.ts +0 -43
@@ -0,0 +1,110 @@
1
+ import { test } from "node:test";
2
+ import assert from "node:assert/strict";
3
+ import fs from "node:fs";
4
+ import os from "node:os";
5
+ import path from "node:path";
6
+ import {
7
+ drainGlobal,
8
+ drainProject,
9
+ globalSeedPath,
10
+ projectHash,
11
+ type DrainResult,
12
+ } from "../extensions/history/store.ts";
13
+
14
+ // Portable project identity: a never-existing literal. projectHash falls
15
+ // back to hashing the raw string when realpath fails, so the identity is
16
+ // deterministic on every machine (no machine-specific absolute paths).
17
+
18
+ const CWD = "/pi-history-test/drain-hidden-project";
19
+
20
+ function write(file: string, texts: string[], ts = 100): void {
21
+ fs.mkdirSync(path.dirname(file), { recursive: true });
22
+ fs.writeFileSync(
23
+ file,
24
+ `${texts.map((t) => JSON.stringify({ v: 1, text: t, ts })).join("\n")}\n`,
25
+ "utf8",
26
+ );
27
+ }
28
+
29
+ // Unwrap the ok shape. Drains FAIL CLOSED: the blocked variant carries no
30
+ // prompts field at all (asserted in the blocked test below).
31
+ function okPrompts(result: DrainResult): string[] {
32
+ assert.equal(result.status, "ok");
33
+ if (result.status !== "ok") throw new Error("unreachable");
34
+ return result.prompts;
35
+ }
36
+
37
+ test("drains skip tombstoned prompts in seeds and session files", () => {
38
+ const base = fs.mkdtempSync(path.join(os.tmpdir(), "hid-"));
39
+ const root = path.join(base, "h");
40
+ const stateDir = path.join(base, "state");
41
+ fs.mkdirSync(stateDir, { recursive: true });
42
+ fs.writeFileSync(
43
+ path.join(stateDir, "hidden.json"),
44
+ JSON.stringify(["deleted from seed", "deleted from session"]),
45
+ "utf8",
46
+ );
47
+ const dir = path.join(root, "projects", projectHash(CWD));
48
+ write(path.join(dir, "seed.jsonl"), ["keep", "deleted from seed"], 100);
49
+ write(path.join(dir, "s1.jsonl"), ["also keep", "deleted from session"], 200);
50
+ write(globalSeedPath(root), ["deleted from seed", "legacy keep"], 50);
51
+
52
+ assert.deepEqual(okPrompts(drainProject(root, CWD, 1000, stateDir)), [
53
+ "also keep",
54
+ "keep",
55
+ ]);
56
+ assert.deepEqual(okPrompts(drainGlobal(root, 1000, stateDir)), [
57
+ "also keep",
58
+ "keep",
59
+ "legacy keep",
60
+ ]);
61
+ // Without a stateDir the filter is off (raw drain semantics).
62
+ assert.equal(
63
+ okPrompts(drainProject(root, CWD)).includes("deleted from seed"),
64
+ true,
65
+ );
66
+ });
67
+
68
+ // Fail-closed seam: an untrusted hidden.json BLOCKS both drains with the
69
+ // recovery message and no prompts field; a stateDir whose hidden.json is
70
+ // MISSING stays the safe empty-tombstones case (the full expected prompts).
71
+ test("corrupt hidden.json blocks both drains with no prompts field; a missing file drains normally", () => {
72
+ const base = fs.mkdtempSync(path.join(os.tmpdir(), "hid-blocked-"));
73
+ const root = path.join(base, "h");
74
+ const stateDir = path.join(base, "state");
75
+ fs.mkdirSync(stateDir, { recursive: true });
76
+ fs.writeFileSync(
77
+ path.join(stateDir, "hidden.json"),
78
+ "{corrupt bytes",
79
+ "utf8",
80
+ );
81
+ const dir = path.join(root, "projects", projectHash(CWD));
82
+ write(path.join(dir, "seed.jsonl"), ["secret prompt", "keeper"], 100);
83
+
84
+ for (const result of [
85
+ drainProject(root, CWD, 1000, stateDir),
86
+ drainGlobal(root, 1000, stateDir),
87
+ ]) {
88
+ assert.equal(result.status, "blocked");
89
+ if (result.status !== "blocked") throw new Error("unreachable");
90
+ assert.ok(result.message.includes("hidden.json"));
91
+ // The blocked shape carries no prompts to render.
92
+ assert.equal("prompts" in result, false);
93
+ }
94
+
95
+ // Missing file: safe empty tombstones — the full drain comes back.
96
+ const missingBase = fs.mkdtempSync(path.join(os.tmpdir(), "hid-missing-"));
97
+ const missingRoot = path.join(missingBase, "h");
98
+ const missingState = path.join(missingBase, "state");
99
+ fs.mkdirSync(missingState, { recursive: true });
100
+ const missingDir = path.join(missingRoot, "projects", projectHash(CWD));
101
+ write(path.join(missingDir, "seed.jsonl"), ["kept", "shown"], 100);
102
+ assert.deepEqual(
103
+ okPrompts(drainProject(missingRoot, CWD, 1000, missingState)),
104
+ ["shown", "kept"],
105
+ );
106
+ assert.deepEqual(okPrompts(drainGlobal(missingRoot, 1000, missingState)), [
107
+ "shown",
108
+ "kept",
109
+ ]);
110
+ });
@@ -0,0 +1,98 @@
1
+ import { test } from "node:test";
2
+ import assert from "node:assert/strict";
3
+ import fs from "node:fs";
4
+ import os from "node:os";
5
+ import path from "node:path";
6
+ import {
7
+ drainGlobal,
8
+ drainProject,
9
+ globalSeedPath,
10
+ projectHash,
11
+ type DrainResult,
12
+ } from "../extensions/history/store.ts";
13
+
14
+ // Portable project identity: a never-existing literal. projectHash falls
15
+ // back to hashing the raw string when realpath fails, so the identity is
16
+ // deterministic on every machine (no machine-specific absolute paths).
17
+
18
+ const CWD = "/pi-history-test/drain-order-project";
19
+
20
+ function writeTs(file: string, texts: string[], ts: number): void {
21
+ fs.mkdirSync(path.dirname(file), { recursive: true });
22
+ fs.writeFileSync(
23
+ file,
24
+ `${texts.map((t) => JSON.stringify({ v: 1, text: t, ts })).join("\n")}\n`,
25
+ "utf8",
26
+ );
27
+ }
28
+
29
+ // Mechanical unwrap of the ok shape (drains can also return blocked).
30
+ function okPrompts(result: DrainResult): string[] {
31
+ assert.equal(result.status, "ok");
32
+ if (result.status !== "ok") throw new Error("unreachable");
33
+ return result.prompts;
34
+ }
35
+
36
+ test("atomic rewrite (delete) does not reshuffle the drain order", () => {
37
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), "ord-"));
38
+ const dir = path.join(root, "projects", projectHash(CWD));
39
+ writeTs(path.join(dir, "old.jsonl"), ["a-old"], 100);
40
+ writeTs(path.join(dir, "new.jsonl"), ["z-new"], 200);
41
+ assert.deepEqual(okPrompts(drainProject(root, CWD)), ["z-new", "a-old"]);
42
+ // Slice 5 ports deleteFromProject; its observable effect on the drain is
43
+ // simulated directly here: an atomic rewrite of the affected file that
44
+ // empties it — the mtime jumps to NOW, and the drain order must not move.
45
+ fs.writeFileSync(path.join(dir, "old.jsonl"), "", "utf8");
46
+ fs.utimesSync(path.join(dir, "old.jsonl"), new Date(), new Date());
47
+ assert.deepEqual(okPrompts(drainProject(root, CWD)), ["z-new"]);
48
+ // Re-add with an OLD ts via direct write: still ordered by ts, not mtime.
49
+ writeTs(path.join(dir, "old2.jsonl"), ["b-old"], 150);
50
+ fs.utimesSync(
51
+ path.join(dir, "old2.jsonl"),
52
+ new Date(Date.now() + 99999),
53
+ new Date(Date.now() + 99999),
54
+ );
55
+ assert.deepEqual(okPrompts(drainProject(root, CWD)), ["z-new", "b-old"]);
56
+ });
57
+
58
+ test("global drain puts the legacy seed last regardless of its fresh mtime", () => {
59
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), "seed-"));
60
+ const dir = path.join(root, "projects", projectHash(CWD));
61
+ writeTs(path.join(dir, "s.jsonl"), ["fresh"], 200);
62
+ const seed = globalSeedPath(root);
63
+ writeTs(seed, ["legacy-1", "legacy-2"], 10);
64
+ fs.utimesSync(seed, new Date(Date.now() + 5000), new Date(Date.now() + 5000));
65
+ assert.deepEqual(okPrompts(drainGlobal(root)), [
66
+ "fresh",
67
+ "legacy-2",
68
+ "legacy-1",
69
+ ]);
70
+ });
71
+
72
+ test(
73
+ "an unreadable store file is skipped; the rest drain in the expected order",
74
+ { skip: process.getuid?.() === 0 },
75
+ () => {
76
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), "ord-sealed-"));
77
+ const dir = path.join(root, "projects", projectHash(CWD));
78
+ writeTs(path.join(dir, "old.jsonl"), ["a-old"], 100);
79
+ const sealed = path.join(dir, "sealed.jsonl");
80
+ writeTs(sealed, ["sealed-never"], 150);
81
+ writeTs(path.join(dir, "new.jsonl"), ["z-new"], 200);
82
+ // The sealed file's fresh mtime would sort it FIRST if it were readable —
83
+ // its absence from the drain is caused by the unreadable skip alone.
84
+ fs.utimesSync(
85
+ sealed,
86
+ new Date(Date.now() + 99999),
87
+ new Date(Date.now() + 99999),
88
+ );
89
+ fs.chmodSync(sealed, 0o000);
90
+ try {
91
+ // An unreadable file reads as zero entries and drops out of the drain;
92
+ // the readable files keep their ts order. No throw.
93
+ assert.deepEqual(okPrompts(drainProject(root, CWD)), ["z-new", "a-old"]);
94
+ } finally {
95
+ fs.chmodSync(sealed, 0o644); // restore before cleanup
96
+ }
97
+ },
98
+ );
@@ -0,0 +1,62 @@
1
+ import { test } from "node:test";
2
+ import assert from "node:assert/strict";
3
+ import {
4
+ type PiHistoryGlobals,
5
+ withExpandedHistoryGlobals,
6
+ } from "../extensions/history/selector-helpers.ts";
7
+
8
+ // withExpandedHistoryGlobals contract: optional hooks invoked via `?.` —
9
+ // expand exactly once BEFORE the run starts, trim exactly once in the
10
+ // finally (success and rejection alike), and the run's resolution passes
11
+ // through untouched. Fixtures track call order in one array so the
12
+ // before/after ordering and the call counts are pinned together.
13
+
14
+ function trackingGlobals(): { globals: PiHistoryGlobals; events: string[] } {
15
+ const events: string[] = [];
16
+ return {
17
+ events,
18
+ globals: {
19
+ __piHistoryExpand: () => {
20
+ events.push("expand");
21
+ },
22
+ __piHistoryTrim: () => {
23
+ events.push("trim");
24
+ },
25
+ },
26
+ };
27
+ }
28
+
29
+ test("expand runs once before the run; trim once after; the result passes through", async () => {
30
+ const { globals, events } = trackingGlobals();
31
+ let ran = 0;
32
+ const value = await withExpandedHistoryGlobals(globals, async () => {
33
+ // By the time the body executes, expand already ran — exactly once.
34
+ ran += 1;
35
+ assert.deepEqual(events, ["expand"]);
36
+ return 42;
37
+ });
38
+ assert.equal(value, 42);
39
+ assert.equal(ran, 1);
40
+ assert.deepEqual(events, ["expand", "trim"]);
41
+ });
42
+
43
+ test("a rejected run still trims (finally) and the rejection propagates unchanged", async () => {
44
+ const { globals, events } = trackingGlobals();
45
+ const boom = new Error("boom");
46
+ let caught: unknown;
47
+ try {
48
+ await withExpandedHistoryGlobals(globals, async () => {
49
+ throw boom;
50
+ });
51
+ } catch (error) {
52
+ caught = error;
53
+ }
54
+ assert.equal(caught, boom);
55
+ assert.deepEqual(events, ["expand", "trim"]);
56
+ });
57
+
58
+ test("absent hooks are tolerated: the run executes with no throw", async () => {
59
+ const empty: PiHistoryGlobals = {};
60
+ const value = await withExpandedHistoryGlobals(empty, async () => "ok");
61
+ assert.equal(value, "ok");
62
+ });