opensquid 0.5.441 → 0.5.447

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 (380) hide show
  1. package/README.md +1 -0
  2. package/dist/functions/arm_scope.d.ts +27 -0
  3. package/dist/functions/arm_scope.d.ts.map +1 -0
  4. package/dist/functions/arm_scope.js +52 -0
  5. package/dist/functions/arm_scope.js.map +1 -0
  6. package/dist/functions/index.d.ts +1 -0
  7. package/dist/functions/index.d.ts.map +1 -1
  8. package/dist/functions/index.js +1 -0
  9. package/dist/functions/index.js.map +1 -1
  10. package/dist/runtime/bootstrap.d.ts.map +1 -1
  11. package/dist/runtime/bootstrap.js +2 -0
  12. package/dist/runtime/bootstrap.js.map +1 -1
  13. package/dist/runtime/handoff/render.d.ts +5 -4
  14. package/dist/runtime/handoff/render.d.ts.map +1 -1
  15. package/dist/runtime/handoff/render.js +7 -7
  16. package/dist/runtime/handoff/render.js.map +1 -1
  17. package/dist/runtime/hooks/active_task_mirror.js +0 -0
  18. package/dist/runtime/hooks/apply_patch.js +0 -0
  19. package/dist/runtime/hooks/dispatch.js +0 -0
  20. package/dist/runtime/hooks/hook_output.js +0 -0
  21. package/dist/runtime/hooks/memory_reconcile.js +0 -0
  22. package/dist/runtime/hooks/new_project_detect.js +0 -0
  23. package/dist/runtime/hooks/profession_resolver.js +0 -0
  24. package/dist/runtime/hooks/scope_intent.js +0 -0
  25. package/dist/runtime/hooks/session_id.js +0 -0
  26. package/dist/runtime/hooks/session_liveness.js +0 -0
  27. package/dist/runtime/hooks/stop_drive.js +0 -0
  28. package/dist/runtime/hooks/stop_stream.js +0 -0
  29. package/dist/runtime/hooks/subagent_guard.js +0 -0
  30. package/dist/runtime/hooks/transcript.js +0 -0
  31. package/dist/runtime/hooks/transcript_tasks.js +0 -0
  32. package/dist/runtime/ralph/orchestrator.d.ts.map +1 -1
  33. package/dist/runtime/ralph/orchestrator.js +2 -1
  34. package/dist/runtime/ralph/orchestrator.js.map +1 -1
  35. package/dist/setup/cli/limits_state.d.ts.map +1 -1
  36. package/dist/setup/cli/limits_state.js +6 -40
  37. package/dist/setup/cli/limits_state.js.map +1 -1
  38. package/dist/setup/cli/pack_walk.d.ts +32 -0
  39. package/dist/setup/cli/pack_walk.d.ts.map +1 -0
  40. package/dist/setup/cli/pack_walk.js +76 -0
  41. package/dist/setup/cli/pack_walk.js.map +1 -0
  42. package/dist/setup/cli/permissions_state.d.ts.map +1 -1
  43. package/dist/setup/cli/permissions_state.js +6 -37
  44. package/dist/setup/cli/permissions_state.js.map +1 -1
  45. package/dist/setup/cli/triggers_state.d.ts.map +1 -1
  46. package/dist/setup/cli/triggers_state.js +3 -29
  47. package/dist/setup/cli/triggers_state.js.map +1 -1
  48. package/dist/workgraph/events.d.ts.map +1 -1
  49. package/dist/workgraph/events.js +10 -0
  50. package/dist/workgraph/events.js.map +1 -1
  51. package/dist/workgraph/store.d.ts.map +1 -1
  52. package/dist/workgraph/store.js +5 -0
  53. package/dist/workgraph/store.js.map +1 -1
  54. package/dist/workgraph/types.d.ts +2 -1
  55. package/dist/workgraph/types.d.ts.map +1 -1
  56. package/docs/ARCHITECTURE.md +268 -0
  57. package/package.json +5 -3
  58. package/packs/builtin/coding-flow/skills/entry-and-handoffs/skill.yaml +13 -17
  59. package/dist/anti-drift/evaluator.d.ts +0 -88
  60. package/dist/anti-drift/evaluator.d.ts.map +0 -1
  61. package/dist/anti-drift/evaluator.js +0 -417
  62. package/dist/anti-drift/evaluator.js.map +0 -1
  63. package/dist/anti-drift/evaluator.test.js +0 -78
  64. package/dist/anti-drift/rules.d.ts +0 -80
  65. package/dist/anti-drift/rules.d.ts.map +0 -1
  66. package/dist/anti-drift/rules.js +0 -368
  67. package/dist/anti-drift/rules.js.map +0 -1
  68. package/dist/anti-drift/rules.test.js +0 -213
  69. package/dist/anti-drift/state.d.ts +0 -107
  70. package/dist/anti-drift/state.d.ts.map +0 -1
  71. package/dist/anti-drift/state.js +0 -177
  72. package/dist/anti-drift/state.js.map +0 -1
  73. package/dist/anti-drift/state.test.js +0 -120
  74. package/dist/chat/adapters/discord.d.ts +0 -41
  75. package/dist/chat/adapters/discord.d.ts.map +0 -1
  76. package/dist/chat/adapters/discord.js +0 -176
  77. package/dist/chat/adapters/discord.js.map +0 -1
  78. package/dist/chat/adapters/discord.test.js +0 -25
  79. package/dist/chat/adapters/slack.d.ts +0 -43
  80. package/dist/chat/adapters/slack.d.ts.map +0 -1
  81. package/dist/chat/adapters/slack.js +0 -172
  82. package/dist/chat/adapters/slack.js.map +0 -1
  83. package/dist/chat/adapters/slack.test.js +0 -30
  84. package/dist/chat/adapters/telegram.d.ts +0 -148
  85. package/dist/chat/adapters/telegram.d.ts.map +0 -1
  86. package/dist/chat/adapters/telegram.js +0 -498
  87. package/dist/chat/adapters/telegram.js.map +0 -1
  88. package/dist/chat/adapters/telegram.test.js +0 -94
  89. package/dist/chat/config.d.ts +0 -98
  90. package/dist/chat/config.d.ts.map +0 -1
  91. package/dist/chat/config.js +0 -185
  92. package/dist/chat/config.js.map +0 -1
  93. package/dist/chat/daemon/active-project.d.ts +0 -17
  94. package/dist/chat/daemon/active-project.d.ts.map +0 -1
  95. package/dist/chat/daemon/active-project.js +0 -23
  96. package/dist/chat/daemon/active-project.js.map +0 -1
  97. package/dist/chat/daemon/autospawn.d.ts +0 -40
  98. package/dist/chat/daemon/autospawn.d.ts.map +0 -1
  99. package/dist/chat/daemon/autospawn.js +0 -129
  100. package/dist/chat/daemon/autospawn.js.map +0 -1
  101. package/dist/chat/daemon/autospawn.test.js +0 -112
  102. package/dist/chat/daemon/cli.d.ts +0 -18
  103. package/dist/chat/daemon/cli.d.ts.map +0 -1
  104. package/dist/chat/daemon/cli.js +0 -71
  105. package/dist/chat/daemon/cli.js.map +0 -1
  106. package/dist/chat/daemon/collisions.js +0 -384
  107. package/dist/chat/daemon/health-check.d.ts +0 -69
  108. package/dist/chat/daemon/health-check.d.ts.map +0 -1
  109. package/dist/chat/daemon/health-check.js +0 -112
  110. package/dist/chat/daemon/health-check.js.map +0 -1
  111. package/dist/chat/daemon/inbox-read.d.ts +0 -35
  112. package/dist/chat/daemon/inbox-read.d.ts.map +0 -1
  113. package/dist/chat/daemon/inbox-read.js +0 -75
  114. package/dist/chat/daemon/inbox-read.js.map +0 -1
  115. package/dist/chat/daemon/inbox-read.test.js +0 -97
  116. package/dist/chat/daemon/inbox.d.ts +0 -63
  117. package/dist/chat/daemon/inbox.d.ts.map +0 -1
  118. package/dist/chat/daemon/inbox.js +0 -56
  119. package/dist/chat/daemon/inbox.js.map +0 -1
  120. package/dist/chat/daemon/inbox.test.js +0 -110
  121. package/dist/chat/daemon/lifecycle.d.ts +0 -71
  122. package/dist/chat/daemon/lifecycle.d.ts.map +0 -1
  123. package/dist/chat/daemon/lifecycle.js +0 -221
  124. package/dist/chat/daemon/lifecycle.js.map +0 -1
  125. package/dist/chat/daemon/lifecycle.test.js +0 -163
  126. package/dist/chat/daemon/protocol.d.ts +0 -107
  127. package/dist/chat/daemon/protocol.d.ts.map +0 -1
  128. package/dist/chat/daemon/protocol.js +0 -54
  129. package/dist/chat/daemon/protocol.js.map +0 -1
  130. package/dist/chat/daemon/routing.d.ts +0 -140
  131. package/dist/chat/daemon/routing.d.ts.map +0 -1
  132. package/dist/chat/daemon/routing.js +0 -198
  133. package/dist/chat/daemon/routing.js.map +0 -1
  134. package/dist/chat/daemon/routing.test.js +0 -259
  135. package/dist/chat/daemon/rpc-client.d.ts +0 -45
  136. package/dist/chat/daemon/rpc-client.d.ts.map +0 -1
  137. package/dist/chat/daemon/rpc-client.js +0 -133
  138. package/dist/chat/daemon/rpc-client.js.map +0 -1
  139. package/dist/chat/daemon/rpc-server.d.ts +0 -39
  140. package/dist/chat/daemon/rpc-server.d.ts.map +0 -1
  141. package/dist/chat/daemon/rpc-server.js +0 -385
  142. package/dist/chat/daemon/rpc-server.js.map +0 -1
  143. package/dist/chat/daemon/rpc.test.js +0 -177
  144. package/dist/chat/daemon/subscribers.js +0 -257
  145. package/dist/chat/daemon/worker.d.ts +0 -27
  146. package/dist/chat/daemon/worker.d.ts.map +0 -1
  147. package/dist/chat/daemon/worker.js +0 -313
  148. package/dist/chat/daemon/worker.js.map +0 -1
  149. package/dist/chat/daemon/workspace-topic.js +0 -324
  150. package/dist/chat/env-token.d.ts +0 -60
  151. package/dist/chat/env-token.d.ts.map +0 -1
  152. package/dist/chat/env-token.js +0 -137
  153. package/dist/chat/env-token.js.map +0 -1
  154. package/dist/chat/env-token.test.js +0 -160
  155. package/dist/chat/factory.d.ts +0 -30
  156. package/dist/chat/factory.d.ts.map +0 -1
  157. package/dist/chat/factory.js +0 -50
  158. package/dist/chat/factory.js.map +0 -1
  159. package/dist/chat/factory.test.js +0 -55
  160. package/dist/chat/gateway.d.ts +0 -176
  161. package/dist/chat/gateway.d.ts.map +0 -1
  162. package/dist/chat/gateway.js +0 -146
  163. package/dist/chat/gateway.js.map +0 -1
  164. package/dist/chat/gateway.test.js +0 -192
  165. package/dist/claude-md.d.ts +0 -39
  166. package/dist/claude-md.d.ts.map +0 -1
  167. package/dist/claude-md.js +0 -113
  168. package/dist/claude-md.js.map +0 -1
  169. package/dist/claude-md.test.js +0 -91
  170. package/dist/codex/activate.d.ts +0 -66
  171. package/dist/codex/activate.d.ts.map +0 -1
  172. package/dist/codex/activate.js +0 -329
  173. package/dist/codex/activate.js.map +0 -1
  174. package/dist/codex/activate.test.js +0 -229
  175. package/dist/codex/bundled-default/bundled-default.test.js +0 -161
  176. package/dist/codex/cli-publish.test.js +0 -133
  177. package/dist/codex/cli.d.ts +0 -35
  178. package/dist/codex/cli.d.ts.map +0 -1
  179. package/dist/codex/cli.js +0 -554
  180. package/dist/codex/cli.js.map +0 -1
  181. package/dist/codex/cli.test.js +0 -277
  182. package/dist/codex/import-skill-md.d.ts +0 -53
  183. package/dist/codex/import-skill-md.d.ts.map +0 -1
  184. package/dist/codex/import-skill-md.js +0 -236
  185. package/dist/codex/import-skill-md.js.map +0 -1
  186. package/dist/codex/import-skill-md.test.js +0 -225
  187. package/dist/codex/loader.d.ts +0 -27
  188. package/dist/codex/loader.d.ts.map +0 -1
  189. package/dist/codex/loader.js +0 -86
  190. package/dist/codex/loader.js.map +0 -1
  191. package/dist/codex/loader.test.js +0 -75
  192. package/dist/codex/parse.d.ts +0 -28
  193. package/dist/codex/parse.d.ts.map +0 -1
  194. package/dist/codex/parse.js +0 -309
  195. package/dist/codex/parse.js.map +0 -1
  196. package/dist/codex/parse.test.js +0 -241
  197. package/dist/codex/store.d.ts +0 -87
  198. package/dist/codex/store.d.ts.map +0 -1
  199. package/dist/codex/store.js +0 -205
  200. package/dist/codex/store.js.map +0 -1
  201. package/dist/codex/store.test.js +0 -242
  202. package/dist/codex/types.d.ts +0 -398
  203. package/dist/codex/types.d.ts.map +0 -1
  204. package/dist/codex/types.js +0 -21
  205. package/dist/codex/types.js.map +0 -1
  206. package/dist/config.d.ts +0 -53
  207. package/dist/config.d.ts.map +0 -1
  208. package/dist/config.js +0 -202
  209. package/dist/config.js.map +0 -1
  210. package/dist/config.test.js +0 -117
  211. package/dist/engine/cli.d.ts +0 -14
  212. package/dist/engine/cli.d.ts.map +0 -1
  213. package/dist/engine/cli.js +0 -171
  214. package/dist/engine/cli.js.map +0 -1
  215. package/dist/engine/client.d.ts +0 -219
  216. package/dist/engine/client.d.ts.map +0 -1
  217. package/dist/engine/client.js +0 -312
  218. package/dist/engine/client.js.map +0 -1
  219. package/dist/engine/config.d.ts +0 -62
  220. package/dist/engine/config.d.ts.map +0 -1
  221. package/dist/engine/config.js +0 -223
  222. package/dist/engine/config.js.map +0 -1
  223. package/dist/engine/index.d.ts +0 -17
  224. package/dist/engine/index.d.ts.map +0 -1
  225. package/dist/engine/index.js +0 -16
  226. package/dist/engine/index.js.map +0 -1
  227. package/dist/engine/resolver.d.ts +0 -62
  228. package/dist/engine/resolver.d.ts.map +0 -1
  229. package/dist/engine/resolver.js +0 -103
  230. package/dist/engine/resolver.js.map +0 -1
  231. package/dist/engine/singleton.d.ts +0 -95
  232. package/dist/engine/singleton.d.ts.map +0 -1
  233. package/dist/engine/singleton.js +0 -325
  234. package/dist/engine/singleton.js.map +0 -1
  235. package/dist/engine/types.d.ts +0 -402
  236. package/dist/engine/types.d.ts.map +0 -1
  237. package/dist/engine/types.js +0 -22
  238. package/dist/engine/types.js.map +0 -1
  239. package/dist/engine-binary-resolver.js +0 -110
  240. package/dist/engine-binary-resolver.test.js +0 -61
  241. package/dist/engine-cli.js +0 -60
  242. package/dist/engine-client.js +0 -301
  243. package/dist/engine-client.test.js +0 -118
  244. package/dist/functions/chain_state.d.ts +0 -51
  245. package/dist/functions/chain_state.d.ts.map +0 -1
  246. package/dist/functions/chain_state.js +0 -59
  247. package/dist/functions/chain_state.js.map +0 -1
  248. package/dist/hooks/drift-catalog.d.ts +0 -68
  249. package/dist/hooks/drift-catalog.d.ts.map +0 -1
  250. package/dist/hooks/drift-catalog.js +0 -184
  251. package/dist/hooks/drift-catalog.js.map +0 -1
  252. package/dist/hooks/drift-catalog.test.js +0 -154
  253. package/dist/hooks/drift-patterns.d.ts +0 -110
  254. package/dist/hooks/drift-patterns.d.ts.map +0 -1
  255. package/dist/hooks/drift-patterns.js +0 -289
  256. package/dist/hooks/drift-patterns.js.map +0 -1
  257. package/dist/hooks/drift-patterns.test.js +0 -325
  258. package/dist/hooks/engine-vocab-gate.d.ts +0 -108
  259. package/dist/hooks/engine-vocab-gate.d.ts.map +0 -1
  260. package/dist/hooks/engine-vocab-gate.js +0 -225
  261. package/dist/hooks/engine-vocab-gate.js.map +0 -1
  262. package/dist/hooks/engine-vocab-gate.test.js +0 -170
  263. package/dist/hooks/heartbeat.d.ts +0 -107
  264. package/dist/hooks/heartbeat.d.ts.map +0 -1
  265. package/dist/hooks/heartbeat.js +0 -316
  266. package/dist/hooks/heartbeat.js.map +0 -1
  267. package/dist/hooks/heartbeat.test.js +0 -393
  268. package/dist/hooks/honesty-ledger-session-scope.test.js +0 -100
  269. package/dist/hooks/honesty-ledger.d.ts +0 -123
  270. package/dist/hooks/honesty-ledger.d.ts.map +0 -1
  271. package/dist/hooks/honesty-ledger.js +0 -226
  272. package/dist/hooks/honesty-ledger.js.map +0 -1
  273. package/dist/hooks/honesty-ledger.test.js +0 -466
  274. package/dist/hooks/inline-report-check.d.ts +0 -63
  275. package/dist/hooks/inline-report-check.d.ts.map +0 -1
  276. package/dist/hooks/inline-report-check.js +0 -88
  277. package/dist/hooks/inline-report-check.js.map +0 -1
  278. package/dist/hooks/inline-report-check.test.js +0 -96
  279. package/dist/hooks/pre-tool-use.d.ts +0 -62
  280. package/dist/hooks/pre-tool-use.d.ts.map +0 -1
  281. package/dist/hooks/pre-tool-use.js +0 -342
  282. package/dist/hooks/pre-tool-use.js.map +0 -1
  283. package/dist/hooks/pre-tool-use.test.js +0 -134
  284. package/dist/hooks/session-end.d.ts +0 -15
  285. package/dist/hooks/session-end.d.ts.map +0 -1
  286. package/dist/hooks/session-end.js +0 -60
  287. package/dist/hooks/session-end.js.map +0 -1
  288. package/dist/hooks/session-end.test.js +0 -52
  289. package/dist/hooks/stop.d.ts +0 -35
  290. package/dist/hooks/stop.d.ts.map +0 -1
  291. package/dist/hooks/stop.js +0 -136
  292. package/dist/hooks/stop.js.map +0 -1
  293. package/dist/hooks/transcript-active-task.test.js +0 -342
  294. package/dist/hooks/transcript.d.ts +0 -26
  295. package/dist/hooks/transcript.d.ts.map +0 -1
  296. package/dist/hooks/transcript.js +0 -266
  297. package/dist/hooks/transcript.js.map +0 -1
  298. package/dist/hooks/transcript.test.js +0 -103
  299. package/dist/hooks/user-prompt-submit.d.ts +0 -74
  300. package/dist/hooks/user-prompt-submit.d.ts.map +0 -1
  301. package/dist/hooks/user-prompt-submit.js +0 -256
  302. package/dist/hooks/user-prompt-submit.js.map +0 -1
  303. package/dist/hooks/user-prompt-submit.test.js +0 -118
  304. package/dist/hooks/versioning-gate.d.ts +0 -101
  305. package/dist/hooks/versioning-gate.d.ts.map +0 -1
  306. package/dist/hooks/versioning-gate.js +0 -245
  307. package/dist/hooks/versioning-gate.js.map +0 -1
  308. package/dist/hooks/versioning-gate.test.js +0 -368
  309. package/dist/hooks/workflow-gate.d.ts +0 -64
  310. package/dist/hooks/workflow-gate.d.ts.map +0 -1
  311. package/dist/hooks/workflow-gate.js +0 -152
  312. package/dist/hooks/workflow-gate.js.map +0 -1
  313. package/dist/hooks/workflow-gate.test.js +0 -197
  314. package/dist/hooks-cli.d.ts +0 -25
  315. package/dist/hooks-cli.d.ts.map +0 -1
  316. package/dist/hooks-cli.js +0 -286
  317. package/dist/hooks-cli.js.map +0 -1
  318. package/dist/hooks-cli.test.js +0 -148
  319. package/dist/origin.d.ts +0 -16
  320. package/dist/origin.d.ts.map +0 -1
  321. package/dist/origin.js +0 -92
  322. package/dist/origin.js.map +0 -1
  323. package/dist/packs/seed_lessons_ingest.d.ts +0 -30
  324. package/dist/packs/seed_lessons_ingest.d.ts.map +0 -1
  325. package/dist/packs/seed_lessons_ingest.js +0 -107
  326. package/dist/packs/seed_lessons_ingest.js.map +0 -1
  327. package/dist/project-cli.d.ts +0 -7
  328. package/dist/project-cli.d.ts.map +0 -1
  329. package/dist/project-cli.js +0 -145
  330. package/dist/project-cli.js.map +0 -1
  331. package/dist/project.d.ts +0 -127
  332. package/dist/project.d.ts.map +0 -1
  333. package/dist/project.js +0 -281
  334. package/dist/project.js.map +0 -1
  335. package/dist/project.test.js +0 -287
  336. package/dist/rag/backends/loop_engine.d.ts +0 -61
  337. package/dist/rag/backends/loop_engine.d.ts.map +0 -1
  338. package/dist/rag/backends/loop_engine.js +0 -160
  339. package/dist/rag/backends/loop_engine.js.map +0 -1
  340. package/dist/recall.d.ts +0 -82
  341. package/dist/recall.d.ts.map +0 -1
  342. package/dist/recall.js +0 -81
  343. package/dist/recall.js.map +0 -1
  344. package/dist/runtime/agent_bridge/autospawn.d.ts +0 -131
  345. package/dist/runtime/agent_bridge/autospawn.d.ts.map +0 -1
  346. package/dist/runtime/agent_bridge/autospawn.js +0 -251
  347. package/dist/runtime/agent_bridge/autospawn.js.map +0 -1
  348. package/dist/runtime/chain_state.d.ts +0 -124
  349. package/dist/runtime/chain_state.d.ts.map +0 -1
  350. package/dist/runtime/chain_state.js +0 -189
  351. package/dist/runtime/chain_state.js.map +0 -1
  352. package/dist/runtime/hooks/permission_decision.d.ts +0 -34
  353. package/dist/runtime/hooks/permission_decision.d.ts.map +0 -1
  354. package/dist/runtime/hooks/permission_decision.js +0 -39
  355. package/dist/runtime/hooks/permission_decision.js.map +0 -1
  356. package/dist/runtime/workflow_fsm.d.ts +0 -21
  357. package/dist/runtime/workflow_fsm.d.ts.map +0 -1
  358. package/dist/runtime/workflow_fsm.js +0 -25
  359. package/dist/runtime/workflow_fsm.js.map +0 -1
  360. package/dist/runtime/workflow_map.d.ts +0 -26
  361. package/dist/runtime/workflow_map.d.ts.map +0 -1
  362. package/dist/runtime/workflow_map.js +0 -38
  363. package/dist/runtime/workflow_map.js.map +0 -1
  364. package/dist/scope.d.ts +0 -48
  365. package/dist/scope.d.ts.map +0 -1
  366. package/dist/scope.js +0 -111
  367. package/dist/scope.js.map +0 -1
  368. package/dist/setup/cli/topic_create_step.d.ts +0 -84
  369. package/dist/setup/cli/topic_create_step.d.ts.map +0 -1
  370. package/dist/setup/cli/topic_create_step.js +0 -213
  371. package/dist/setup/cli/topic_create_step.js.map +0 -1
  372. package/dist/system-export.d.ts +0 -65
  373. package/dist/system-export.d.ts.map +0 -1
  374. package/dist/system-export.js +0 -194
  375. package/dist/system-export.js.map +0 -1
  376. package/dist/utterance/classifier.d.ts +0 -53
  377. package/dist/utterance/classifier.d.ts.map +0 -1
  378. package/dist/utterance/classifier.js +0 -184
  379. package/dist/utterance/classifier.js.map +0 -1
  380. package/dist/utterance/classifier.test.js +0 -147
@@ -1,393 +0,0 @@
1
- /**
2
- * Tests for #124 — token-threshold heartbeat that replaces the auto-
3
- * classifier subprocess.
4
- */
5
- import * as crypto from "node:crypto";
6
- import { promises as fs } from "node:fs";
7
- import * as os from "node:os";
8
- import * as path from "node:path";
9
- import { afterEach, beforeEach, describe, expect, it } from "vitest";
10
- import { DEFAULT_HEARTBEAT_TOKENS, checkAndMaybeArm, consumePendingHeartbeat, estimateTokens, estimateTranscriptTokens, formatHeartbeatNudge, heartbeatSessionFiles, heartbeatThresholdTokens, readCheckpoint, writeCheckpoint, } from "./heartbeat.js";
11
- let tmpRoot;
12
- const SESSION = "heartbeat-test";
13
- beforeEach(async () => {
14
- tmpRoot = path.join(os.tmpdir(), `oscli-heartbeat-${crypto.randomUUID()}`);
15
- await fs.mkdir(tmpRoot, { recursive: true });
16
- // Ensure no env override leaks across tests.
17
- delete process.env.OPENSQUID_HEARTBEAT_TOKENS;
18
- });
19
- afterEach(async () => {
20
- await fs.rm(tmpRoot, { recursive: true, force: true });
21
- delete process.env.OPENSQUID_HEARTBEAT_TOKENS;
22
- });
23
- // ---------------------------------------------------------------------
24
- // estimateTokens / heartbeatThresholdTokens
25
- // ---------------------------------------------------------------------
26
- describe("estimateTokens", () => {
27
- it("returns 0 for empty / null-ish input", () => {
28
- expect(estimateTokens("")).toBe(0);
29
- });
30
- it("approximates chars/4", () => {
31
- expect(estimateTokens("aaaa")).toBe(1);
32
- expect(estimateTokens("a".repeat(80))).toBe(20);
33
- expect(estimateTokens("a".repeat(100))).toBe(25);
34
- });
35
- });
36
- describe("heartbeatThresholdTokens", () => {
37
- it("returns default when env unset", () => {
38
- expect(heartbeatThresholdTokens()).toBe(DEFAULT_HEARTBEAT_TOKENS);
39
- });
40
- it("honors OPENSQUID_HEARTBEAT_TOKENS positive integer", () => {
41
- process.env.OPENSQUID_HEARTBEAT_TOKENS = "5000";
42
- expect(heartbeatThresholdTokens()).toBe(5000);
43
- });
44
- it("falls back to default when env value is zero / negative / NaN", () => {
45
- for (const bad of ["0", "-1", "abc", ""]) {
46
- process.env.OPENSQUID_HEARTBEAT_TOKENS = bad;
47
- expect(heartbeatThresholdTokens()).toBe(DEFAULT_HEARTBEAT_TOKENS);
48
- }
49
- });
50
- });
51
- // ---------------------------------------------------------------------
52
- // estimateTranscriptTokens
53
- // ---------------------------------------------------------------------
54
- describe("estimateTranscriptTokens (0.7.7 #161)", () => {
55
- it("returns 0 when transcript file is missing", async () => {
56
- const r = await estimateTranscriptTokens(path.join(tmpRoot, "nope.jsonl"));
57
- expect(r).toBe(0);
58
- });
59
- it("returns 0 for an empty file", async () => {
60
- const p = path.join(tmpRoot, "empty.jsonl");
61
- await fs.writeFile(p, "");
62
- expect(await estimateTranscriptTokens(p)).toBe(0);
63
- });
64
- it("counts user.string content", async () => {
65
- const p = path.join(tmpRoot, "transcript.jsonl");
66
- const line = JSON.stringify({
67
- type: "user",
68
- message: { role: "user", content: "x".repeat(400) },
69
- });
70
- await fs.writeFile(p, line + "\n");
71
- // 400 chars / 4 = 100 tokens
72
- expect(await estimateTranscriptTokens(p)).toBe(100);
73
- });
74
- it("counts assistant text blocks", async () => {
75
- const p = path.join(tmpRoot, "transcript.jsonl");
76
- const line = JSON.stringify({
77
- type: "assistant",
78
- message: {
79
- role: "assistant",
80
- content: [
81
- { type: "text", text: "a".repeat(400) },
82
- { type: "text", text: "b".repeat(400) },
83
- ],
84
- },
85
- });
86
- await fs.writeFile(p, line + "\n");
87
- // 800 chars / 4 = 200 tokens
88
- expect(await estimateTranscriptTokens(p)).toBe(200);
89
- });
90
- it("SKIPS thinking blocks (agent internal CoT)", async () => {
91
- const p = path.join(tmpRoot, "transcript.jsonl");
92
- const line = JSON.stringify({
93
- type: "assistant",
94
- message: {
95
- content: [
96
- { type: "thinking", thinking: "x".repeat(10000), signature: "sig" },
97
- { type: "text", text: "hello" },
98
- ],
99
- },
100
- });
101
- await fs.writeFile(p, line + "\n");
102
- // Only "hello" (5 chars) counted → 2 tokens (ceiling)
103
- expect(await estimateTranscriptTokens(p)).toBe(2);
104
- });
105
- it("SKIPS tool_use blocks (compact + outbound work)", async () => {
106
- const p = path.join(tmpRoot, "transcript.jsonl");
107
- const line = JSON.stringify({
108
- type: "assistant",
109
- message: {
110
- content: [
111
- { type: "tool_use", id: "x", name: "Bash", input: { command: "ls" } },
112
- { type: "text", text: "after the tool" },
113
- ],
114
- },
115
- });
116
- await fs.writeFile(p, line + "\n");
117
- // Only the text block (14 chars) → 4 tokens
118
- expect(await estimateTranscriptTokens(p)).toBe(4);
119
- });
120
- it("CAPS tool_result content at 2000 chars (prevents tool-result inflation)", async () => {
121
- const p = path.join(tmpRoot, "transcript.jsonl");
122
- const line = JSON.stringify({
123
- type: "user",
124
- message: {
125
- role: "user",
126
- content: [
127
- {
128
- type: "tool_result",
129
- tool_use_id: "x",
130
- content: "z".repeat(50000), // huge file read
131
- },
132
- ],
133
- },
134
- });
135
- await fs.writeFile(p, line + "\n");
136
- // Capped at 2000 chars / 4 = 500 tokens (NOT 12,500)
137
- expect(await estimateTranscriptTokens(p)).toBe(500);
138
- });
139
- it("counts tool_result content array form (nested blocks)", async () => {
140
- const p = path.join(tmpRoot, "transcript.jsonl");
141
- const line = JSON.stringify({
142
- type: "user",
143
- message: {
144
- content: [
145
- {
146
- type: "tool_result",
147
- tool_use_id: "x",
148
- content: [
149
- { type: "text", text: "y".repeat(800) },
150
- { type: "text", text: "y".repeat(800) },
151
- ],
152
- },
153
- ],
154
- },
155
- });
156
- await fs.writeFile(p, line + "\n");
157
- // 1600 chars total (under 2000 cap) → 400 tokens
158
- expect(await estimateTranscriptTokens(p)).toBe(400);
159
- });
160
- it("SKIPS non-conversation line types (system / permission-mode / file-history-snapshot / etc)", async () => {
161
- const p = path.join(tmpRoot, "transcript.jsonl");
162
- const lines = [
163
- JSON.stringify({ type: "permission-mode", permissionMode: "default" }),
164
- JSON.stringify({ type: "system", text: "x".repeat(10000) }),
165
- JSON.stringify({ type: "file-history-snapshot", snapshot: {} }),
166
- JSON.stringify({ type: "attachment", message: { content: "x".repeat(10000) } }),
167
- JSON.stringify({ type: "ai-title", title: "Hello" }),
168
- JSON.stringify({ type: "last-prompt", prompt: "x".repeat(10000) }),
169
- ].join("\n");
170
- await fs.writeFile(p, lines);
171
- expect(await estimateTranscriptTokens(p)).toBe(0);
172
- });
173
- it("tolerates malformed JSON lines (skips them)", async () => {
174
- const p = path.join(tmpRoot, "transcript.jsonl");
175
- const lines = [
176
- "not json at all",
177
- JSON.stringify({ type: "user", message: { content: "hello" } }),
178
- "{partial json",
179
- ].join("\n");
180
- await fs.writeFile(p, lines);
181
- // Only "hello" counted (5 chars) → 2 tokens (ceiling)
182
- expect(await estimateTranscriptTokens(p)).toBe(2);
183
- });
184
- });
185
- // ---------------------------------------------------------------------
186
- // formatHeartbeatNudge
187
- // ---------------------------------------------------------------------
188
- describe("formatHeartbeatNudge", () => {
189
- it("includes the delta + threshold + the recall instruction", () => {
190
- const nudge = formatHeartbeatNudge(20000, 20000);
191
- expect(nudge).toContain("20,000");
192
- expect(nudge).toContain("recall");
193
- expect(nudge).toContain("memorize");
194
- expect(nudge).toContain("🦑");
195
- });
196
- });
197
- // ---------------------------------------------------------------------
198
- // Checkpoint IO
199
- // ---------------------------------------------------------------------
200
- describe("checkpoint IO", () => {
201
- it("returns null when no checkpoint file exists", async () => {
202
- expect(await readCheckpoint(SESSION, { dataRoot: tmpRoot })).toBeNull();
203
- });
204
- it("round-trips via writeCheckpoint", async () => {
205
- await writeCheckpoint(SESSION, { last_token_count: 12345, last_checkpoint_at: "2026-05-15T00:00:00Z" }, { dataRoot: tmpRoot });
206
- const back = await readCheckpoint(SESSION, { dataRoot: tmpRoot });
207
- expect(back?.last_token_count).toBe(12345);
208
- expect(back?.last_checkpoint_at).toBe("2026-05-15T00:00:00Z");
209
- });
210
- it("returns null on malformed JSON", async () => {
211
- const p = path.join(tmpRoot, "sessions", SESSION);
212
- await fs.mkdir(p, { recursive: true });
213
- await fs.writeFile(path.join(p, "heartbeat-checkpoint.json"), "not json");
214
- expect(await readCheckpoint(SESSION, { dataRoot: tmpRoot })).toBeNull();
215
- });
216
- });
217
- // ---------------------------------------------------------------------
218
- // checkAndMaybeArm — Stop hook entrypoint
219
- // ---------------------------------------------------------------------
220
- describe("checkAndMaybeArm", () => {
221
- // 0.7.7 (#161): estimator now counts only user/assistant message bodies
222
- // from valid JSONL lines, not raw file bytes. Helper writes a synthetic
223
- // user message whose content has the requested char-count so existing
224
- // crossing-math tests still work without reading a real transcript.
225
- async function writeTranscript(chars) {
226
- const p = path.join(tmpRoot, "transcript.jsonl");
227
- const line = JSON.stringify({
228
- type: "user",
229
- message: { role: "user", content: "x".repeat(chars) },
230
- });
231
- await fs.writeFile(p, line + "\n");
232
- return p;
233
- }
234
- it("returns null when transcript is missing / empty", async () => {
235
- const r = await checkAndMaybeArm(SESSION, path.join(tmpRoot, "missing.jsonl"), {
236
- dataRoot: tmpRoot,
237
- });
238
- expect(r).toBeNull();
239
- });
240
- it("arms a heartbeat on first crossing (no prior checkpoint)", async () => {
241
- // 80000 chars -> 20000 tokens -> exactly threshold
242
- const tpath = await writeTranscript(80000);
243
- const nudge = await checkAndMaybeArm(SESSION, tpath, {
244
- dataRoot: tmpRoot,
245
- thresholdTokens: 20000,
246
- });
247
- expect(nudge).not.toBeNull();
248
- expect(nudge).toContain("20,000");
249
- // Checkpoint bumped to the current count.
250
- const cp = await readCheckpoint(SESSION, { dataRoot: tmpRoot });
251
- expect(cp?.last_token_count).toBe(20000);
252
- });
253
- it("does NOT arm again until threshold crossed from the new checkpoint", async () => {
254
- // First crossing.
255
- let tpath = await writeTranscript(80000);
256
- expect(await checkAndMaybeArm(SESSION, tpath, { dataRoot: tmpRoot, thresholdTokens: 20000 })).not.toBeNull();
257
- // Drain the previous nudge so we can detect a fresh one (or its absence).
258
- await consumePendingHeartbeat(SESSION, { dataRoot: tmpRoot });
259
- // Transcript grows by less than threshold from the checkpoint.
260
- tpath = await writeTranscript(80000 + 4000); // +1000 tokens
261
- const second = await checkAndMaybeArm(SESSION, tpath, {
262
- dataRoot: tmpRoot,
263
- thresholdTokens: 20000,
264
- });
265
- expect(second).toBeNull();
266
- // Checkpoint stays at the first crossing.
267
- const cp = await readCheckpoint(SESSION, { dataRoot: tmpRoot });
268
- expect(cp?.last_token_count).toBe(20000);
269
- });
270
- it("resets stale baseline when checkpoint > 10x current (post-0.7.7 estimator migration)", async () => {
271
- // Simulate a checkpoint left by the old estimator: 31M tokens for a
272
- // 1.5M-token-real transcript. New estimator returns ~1.5M, baseline
273
- // says 31M → naive delta is negative → would never fire. Reset
274
- // logic must zero the baseline so the next crossing arms.
275
- await writeCheckpoint(SESSION, { last_token_count: 31_000_000, last_checkpoint_at: "2026-05-17T00:00:00Z" }, { dataRoot: tmpRoot });
276
- const tpath = await writeTranscript(80000); // 20K tokens
277
- const nudge = await checkAndMaybeArm(SESSION, tpath, {
278
- dataRoot: tmpRoot,
279
- thresholdTokens: 20000,
280
- });
281
- expect(nudge).not.toBeNull();
282
- const cp = await readCheckpoint(SESSION, { dataRoot: tmpRoot });
283
- expect(cp?.last_token_count).toBe(20000);
284
- });
285
- it("does NOT reset baseline when checkpoint is within reasonable range", async () => {
286
- // Baseline only 2x current — not stale, just slow growth (or
287
- // transcript shrunk via compaction). Don't reset.
288
- await writeCheckpoint(SESSION, { last_token_count: 40000, last_checkpoint_at: "2026-05-17T00:00:00Z" }, { dataRoot: tmpRoot });
289
- const tpath = await writeTranscript(80000); // 20K tokens, baseline 40K, delta = -20K
290
- const nudge = await checkAndMaybeArm(SESSION, tpath, {
291
- dataRoot: tmpRoot,
292
- thresholdTokens: 20000,
293
- });
294
- expect(nudge).toBeNull(); // negative delta, but no reset → no fire
295
- });
296
- it("arms again on each subsequent threshold crossing", async () => {
297
- // First crossing at 20K tokens.
298
- let tpath = await writeTranscript(80000);
299
- expect(await checkAndMaybeArm(SESSION, tpath, { dataRoot: tmpRoot, thresholdTokens: 20000 })).not.toBeNull();
300
- await consumePendingHeartbeat(SESSION, { dataRoot: tmpRoot });
301
- // Second crossing at 40K tokens.
302
- tpath = await writeTranscript(160000);
303
- expect(await checkAndMaybeArm(SESSION, tpath, { dataRoot: tmpRoot, thresholdTokens: 20000 })).not.toBeNull();
304
- const cp = await readCheckpoint(SESSION, { dataRoot: tmpRoot });
305
- expect(cp?.last_token_count).toBe(40000);
306
- });
307
- it("does not arm when below threshold and no prior checkpoint", async () => {
308
- const tpath = await writeTranscript(40000); // 10K tokens < 20K threshold
309
- const r = await checkAndMaybeArm(SESSION, tpath, {
310
- dataRoot: tmpRoot,
311
- thresholdTokens: 20000,
312
- });
313
- expect(r).toBeNull();
314
- // No checkpoint written when we didn't arm.
315
- expect(await readCheckpoint(SESSION, { dataRoot: tmpRoot })).toBeNull();
316
- });
317
- });
318
- // ---------------------------------------------------------------------
319
- // consumePendingHeartbeat — UserPromptSubmit hook entrypoint
320
- // ---------------------------------------------------------------------
321
- describe("consumePendingHeartbeat", () => {
322
- it("returns null when no pending marker", async () => {
323
- expect(await consumePendingHeartbeat(SESSION, { dataRoot: tmpRoot })).toBeNull();
324
- });
325
- it("returns the armed nudge and removes the marker (one-shot)", async () => {
326
- // Arm. 0.7.7 (#161): estimator now requires valid JSONL; wrap the
327
- // body content so the line parses as a user message.
328
- const tpath = path.join(tmpRoot, "transcript.jsonl");
329
- const line = JSON.stringify({ type: "user", message: { content: "x".repeat(80000) } });
330
- await fs.writeFile(tpath, line + "\n");
331
- await checkAndMaybeArm(SESSION, tpath, { dataRoot: tmpRoot, thresholdTokens: 20000 });
332
- const first = await consumePendingHeartbeat(SESSION, { dataRoot: tmpRoot });
333
- expect(first).not.toBeNull();
334
- expect(first).toContain("🦑");
335
- // Second consume returns null — marker was deleted.
336
- const second = await consumePendingHeartbeat(SESSION, { dataRoot: tmpRoot });
337
- expect(second).toBeNull();
338
- });
339
- });
340
- // ---------------------------------------------------------------------
341
- // SessionEnd cleanup hook surface
342
- // ---------------------------------------------------------------------
343
- describe("heartbeatSessionFiles", () => {
344
- it("returns the two paths SessionEnd should remove", () => {
345
- const files = heartbeatSessionFiles(SESSION, tmpRoot);
346
- expect(files.some((p) => p.endsWith("heartbeat-checkpoint.json"))).toBe(true);
347
- expect(files.some((p) => p.endsWith("heartbeat-pending.txt"))).toBe(true);
348
- });
349
- });
350
- // =====================================================================
351
- // 0.7.26 / D7 — recall-required flag (heartbeat → block until recall)
352
- // =====================================================================
353
- describe("recall-required flag (D7)", () => {
354
- let tmp;
355
- beforeEach(async () => {
356
- tmp = path.join(os.tmpdir(), `opensquid-recall-flag-${crypto.randomUUID()}`);
357
- await fs.mkdir(tmp, { recursive: true });
358
- });
359
- afterEach(async () => {
360
- await fs.rm(tmp, { recursive: true, force: true });
361
- });
362
- it("isRecallRequired returns false when flag was never set", async () => {
363
- const { isRecallRequired } = await import("./heartbeat.js");
364
- expect(await isRecallRequired("sess-1", { dataRoot: tmp })).toBe(false);
365
- });
366
- it("markRecallRequired creates the flag; isRecallRequired returns true", async () => {
367
- const { markRecallRequired, isRecallRequired } = await import("./heartbeat.js");
368
- await markRecallRequired("sess-2", { dataRoot: tmp });
369
- expect(await isRecallRequired("sess-2", { dataRoot: tmp })).toBe(true);
370
- });
371
- it("clearRecallRequired removes the flag", async () => {
372
- const { markRecallRequired, clearRecallRequired, isRecallRequired } = await import("./heartbeat.js");
373
- await markRecallRequired("sess-3", { dataRoot: tmp });
374
- expect(await isRecallRequired("sess-3", { dataRoot: tmp })).toBe(true);
375
- await clearRecallRequired("sess-3", { dataRoot: tmp });
376
- expect(await isRecallRequired("sess-3", { dataRoot: tmp })).toBe(false);
377
- });
378
- it("clearRecallRequired is idempotent (clear without prior mark is fine)", async () => {
379
- const { clearRecallRequired } = await import("./heartbeat.js");
380
- await expect(clearRecallRequired("sess-never", { dataRoot: tmp })).resolves.toBeUndefined();
381
- });
382
- it("flags are per-session — setting one session doesn't affect another", async () => {
383
- const { markRecallRequired, isRecallRequired } = await import("./heartbeat.js");
384
- await markRecallRequired("sess-A", { dataRoot: tmp });
385
- expect(await isRecallRequired("sess-A", { dataRoot: tmp })).toBe(true);
386
- expect(await isRecallRequired("sess-B", { dataRoot: tmp })).toBe(false);
387
- });
388
- it("heartbeatSessionFiles includes the recall-required flag path for SessionEnd cleanup", async () => {
389
- const { heartbeatSessionFiles } = await import("./heartbeat.js");
390
- const files = heartbeatSessionFiles("sess-1", tmp);
391
- expect(files.some((p) => p.endsWith("recall-required.flag"))).toBe(true);
392
- });
393
- });
@@ -1,100 +0,0 @@
1
- /**
2
- * Tests specifically for the session-scope fix (#114).
3
- *
4
- * Verifies that:
5
- * 1. The ledger accumulates across multiple turns within a session.
6
- * 2. A claim made in turn N is satisfied by evidence from turn 1.
7
- * 3. clearSession wipes both ledger + broken-promises.
8
- * 4. Stop hook's de-dupe behavior — re-running reconcile on the same
9
- * text doesn't double-record the broken promise.
10
- */
11
- import * as crypto from "node:crypto";
12
- import { promises as fs } from "node:fs";
13
- import * as os from "node:os";
14
- import * as path from "node:path";
15
- import { afterEach, beforeEach, describe, expect, it } from "vitest";
16
- import { clearSession, clearTurnLedger, reconcile, readBrokenPromises, readTurnLedger, recordBrokenPromise, recordToolCall, } from "./honesty-ledger.js";
17
- let tmpRoot;
18
- const SESSION = "scope-test-session";
19
- beforeEach(async () => {
20
- tmpRoot = path.join(os.tmpdir(), `oscli-honesty-scope-${crypto.randomUUID()}`);
21
- await fs.mkdir(tmpRoot, { recursive: true });
22
- });
23
- afterEach(async () => {
24
- await fs.rm(tmpRoot, { recursive: true, force: true });
25
- });
26
- describe("session-scoped ledger (#114 fix)", () => {
27
- it("accumulates tool calls across multiple turns", async () => {
28
- // Turn 1: ran npm test
29
- await recordToolCall(SESSION, "Bash", "npm test", { dataRoot: tmpRoot });
30
- // Turn 2: ran cargo check
31
- await recordToolCall(SESSION, "Bash", "cargo check", { dataRoot: tmpRoot });
32
- // Turn 3: read a file
33
- await recordToolCall(SESSION, "Read", "/x.ts", { dataRoot: tmpRoot });
34
- const ledger = await readTurnLedger(SESSION, { dataRoot: tmpRoot });
35
- expect(ledger).toHaveLength(3);
36
- expect(ledger.map((e) => e.tool)).toEqual(["Bash", "Bash", "Read"]);
37
- });
38
- it("recap text in turn N is satisfied by tool call from turn 1 (THE FIX)", async () => {
39
- // Turn 1: actually ran tests
40
- await recordToolCall(SESSION, "Bash", "npm test", { dataRoot: tmpRoot });
41
- // Turn 5 (much later): assistant says "tests pass" in recap text
42
- const recapText = "Here's a summary. Tests pass and build is green.";
43
- const ledger = await readTurnLedger(SESSION, { dataRoot: tmpRoot });
44
- const broken = reconcile(recapText, ledger);
45
- // Before the fix: would flag "tests pass" as a broken promise.
46
- // After the fix: ledger has the prior turn's npm test, so satisfied.
47
- expect(broken.map((b) => b.claim_id)).not.toContain("running-tests");
48
- });
49
- it("genuinely lying recap is still caught (no false-negative regression)", async () => {
50
- // Turn 1: read a file
51
- await recordToolCall(SESSION, "Read", "/foo.ts", { dataRoot: tmpRoot });
52
- // Turn 2: claim "tests pass" but NO test was ever run in this session
53
- const ledger = await readTurnLedger(SESSION, { dataRoot: tmpRoot });
54
- const broken = reconcile("Tests pass.", ledger);
55
- expect(broken.map((b) => b.claim_id)).toContain("running-tests");
56
- });
57
- it("clearTurnLedger removes only the ledger file (not broken-promises)", async () => {
58
- await recordToolCall(SESSION, "Bash", "ls", { dataRoot: tmpRoot });
59
- await recordBrokenPromise(SESSION, {
60
- ts: "t",
61
- claim_id: "fake",
62
- claim_label: "fake",
63
- matched_text: "fake",
64
- reason: "fake",
65
- }, { dataRoot: tmpRoot });
66
- await clearTurnLedger(SESSION, { dataRoot: tmpRoot });
67
- expect(await readTurnLedger(SESSION, { dataRoot: tmpRoot })).toEqual([]);
68
- // broken-promises survives a turn-ledger clear
69
- expect(await readBrokenPromises(SESSION, { dataRoot: tmpRoot })).toHaveLength(1);
70
- });
71
- it("clearSession wipes BOTH ledger and broken-promises", async () => {
72
- await recordToolCall(SESSION, "Bash", "ls", { dataRoot: tmpRoot });
73
- await recordBrokenPromise(SESSION, {
74
- ts: "t",
75
- claim_id: "fake",
76
- claim_label: "fake",
77
- matched_text: "fake",
78
- reason: "fake",
79
- }, { dataRoot: tmpRoot });
80
- await clearSession(SESSION, { dataRoot: tmpRoot });
81
- expect(await readTurnLedger(SESSION, { dataRoot: tmpRoot })).toEqual([]);
82
- expect(await readBrokenPromises(SESSION, { dataRoot: tmpRoot })).toEqual([]);
83
- });
84
- it("clearSession is idempotent (no throw on already-clean session)", async () => {
85
- await clearSession(SESSION, { dataRoot: tmpRoot });
86
- await clearSession(SESSION, { dataRoot: tmpRoot });
87
- // No throw.
88
- });
89
- });
90
- describe("stuck-broken-promise dedupe (#114 fix companion)", () => {
91
- it("reconcile on the same text twice returns the same set", async () => {
92
- // No tool calls. Claim text triggers two patterns.
93
- const broken1 = reconcile("Tests pass and committed.", []);
94
- const broken2 = reconcile("Tests pass and committed.", []);
95
- expect(broken1.map((b) => b.claim_id).sort()).toEqual(broken2.map((b) => b.claim_id).sort());
96
- // Both should flag running-tests + committed
97
- expect(broken1.map((b) => b.claim_id)).toContain("running-tests");
98
- expect(broken1.map((b) => b.claim_id)).toContain("committed");
99
- });
100
- });
@@ -1,123 +0,0 @@
1
- /**
2
- * Honesty ledger — catches claim-vs-action gaps in assistant turns.
3
- *
4
- * The agent makes claims like "running tests now" or "starting research"
5
- * or "committed" in its text output. These claims are checkable against
6
- * the tool calls that actually happened in the same turn. When the
7
- * claim has no matching tool call, that's a "broken promise" — opensquid
8
- * records it so the next turn surfaces it to the agent for correction.
9
- *
10
- * Storage:
11
- * <data-root>/sessions/<session-id>/turn-ledger.jsonl
12
- * One JSON line per tool call this turn, appended by the PreToolUse
13
- * hook. Cleared at turn-end after reconciliation.
14
- *
15
- * <data-root>/sessions/<session-id>/broken-promises.jsonl
16
- * Append-only ledger of claims that lacked matching evidence.
17
- * Surfaced to the agent on the NEXT turn via SessionStart/
18
- * UserPromptSubmit hook output.
19
- */
20
- export type ClaimEvidenceShape = {
21
- kind: "any_tool";
22
- } | {
23
- kind: "bash_contains";
24
- needle: string;
25
- } | {
26
- kind: "bash_regex";
27
- pattern: string;
28
- } | {
29
- kind: "tool_called";
30
- tool: string;
31
- } | {
32
- kind: "any_of";
33
- options: ClaimEvidenceShape[];
34
- } | {
35
- kind: "input_contains";
36
- tool: string;
37
- needle: string;
38
- };
39
- export interface ClaimPattern {
40
- /** Stable id (e.g. "research-start"). */
41
- id: string;
42
- /** Regex matched against assistant text. */
43
- text_regex: string;
44
- /** What proof of action satisfies this claim. */
45
- evidence: ClaimEvidenceShape;
46
- /** Short label surfaced to the agent when the promise is broken. */
47
- promise_label: string;
48
- }
49
- /**
50
- * Claim catalog — sourced from the bundled-default codex (0.7.17,
51
- * drift-as-codex chunk 3b). Previously a hand-maintained TS array;
52
- * now loaded once at module init from
53
- * `src/codex/bundled-default/codex.yaml` via the chunk-2 loader.
54
- *
55
- * Fail-open: if the codex is unloadable, the catalog is empty and
56
- * no claims fire. Better silent under-enforcement than a hook crash.
57
- */
58
- export declare const CLAIM_PATTERNS: ClaimPattern[];
59
- /**
60
- * One tool-call entry, persisted across ALL turns in the same Claude
61
- * Code session. Cleared only at session end (or explicit
62
- * `clearSessionLedger`).
63
- *
64
- * #114 (2026-05-15) — v0.4.C.1 fix: previously this was a PER-TURN
65
- * ledger that got cleared by the Stop hook, which caused recap text
66
- * describing prior-turn work to be flagged as broken promises. Now
67
- * the ledger accumulates across the whole session so claims like
68
- * "tests pass" satisfy against any `npm test` from any earlier turn
69
- * in the same session.
70
- */
71
- export interface TurnLedgerEntry {
72
- ts: string;
73
- tool: string;
74
- /** Subset of tool_input relevant to reconciliation. */
75
- input_summary: string;
76
- }
77
- /** Called by PreToolUse hook to record what the agent is about to do. */
78
- export declare function recordToolCall(sessionId: string, tool: string, inputSummary: string, options?: {
79
- dataRoot?: string;
80
- }): Promise<void>;
81
- /** Read the session-scoped ledger (every tool call so far this session). */
82
- export declare function readTurnLedger(sessionId: string, options?: {
83
- dataRoot?: string;
84
- }): Promise<TurnLedgerEntry[]>;
85
- /**
86
- * Clear the session ledger. ONLY called at session end (or by tests).
87
- * Stop hook does NOT call this anymore — the ledger persists across
88
- * turns to avoid recap-text false-positives.
89
- */
90
- export declare function clearTurnLedger(sessionId: string, options?: {
91
- dataRoot?: string;
92
- }): Promise<void>;
93
- /**
94
- * Explicit session-end clear: wipes everything opensquid wrote under
95
- * this session's directory — turn ledger, broken promises, plus the
96
- * heartbeat checkpoint and pending marker (#124). Files are removed
97
- * individually so unrelated files in the session dir survive (in case
98
- * a future hook drops something else there).
99
- */
100
- export declare function clearSession(sessionId: string, options?: {
101
- dataRoot?: string;
102
- }): Promise<void>;
103
- export interface BrokenPromise {
104
- ts: string;
105
- claim_id: string;
106
- claim_label: string;
107
- matched_text: string;
108
- reason: string;
109
- }
110
- /**
111
- * Scan assistant text for claim phrases, reconcile against the ledger,
112
- * return any unfulfilled promises.
113
- */
114
- export declare function reconcile(assistantText: string, ledger: TurnLedgerEntry[]): BrokenPromise[];
115
- /** Append a broken promise to the session's append-only ledger. */
116
- export declare function recordBrokenPromise(sessionId: string, promise: BrokenPromise, options?: {
117
- dataRoot?: string;
118
- }): Promise<void>;
119
- /** Read all broken promises for a session (used by next turn's hook). */
120
- export declare function readBrokenPromises(sessionId: string, options?: {
121
- dataRoot?: string;
122
- }): Promise<BrokenPromise[]>;
123
- //# sourceMappingURL=honesty-ledger.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"honesty-ledger.d.ts","sourceRoot":"","sources":["../../src.legacy/hooks/honesty-ledger.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAaH,MAAM,MAAM,kBAAkB,GAC1B;IAAE,IAAI,EAAE,UAAU,CAAA;CAAE,GACpB;IAAE,IAAI,EAAE,eAAe,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GACzC;IAAE,IAAI,EAAE,YAAY,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GACvC;IAAE,IAAI,EAAE,aAAa,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAKrC;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,OAAO,EAAE,kBAAkB,EAAE,CAAA;CAAE,GACjD;IAAE,IAAI,EAAE,gBAAgB,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAE7D,MAAM,WAAW,YAAY;IAC3B,yCAAyC;IACzC,EAAE,EAAE,MAAM,CAAC;IACX,4CAA4C;IAC5C,UAAU,EAAE,MAAM,CAAC;IACnB,iDAAiD;IACjD,QAAQ,EAAE,kBAAkB,CAAC;IAC7B,oEAAoE;IACpE,aAAa,EAAE,MAAM,CAAC;CACvB;AA4CD;;;;;;;;GAQG;AACH,eAAO,MAAM,cAAc,EAAE,YAAY,EAUrC,CAAC;AAML;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,uDAAuD;IACvD,aAAa,EAAE,MAAM,CAAC;CACvB;AAgBD,yEAAyE;AACzE,wBAAsB,cAAc,CAClC,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,MAAM,EACZ,YAAY,EAAE,MAAM,EACpB,OAAO,GAAE;IAAE,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAO,GAClC,OAAO,CAAC,IAAI,CAAC,CAaf;AAED,4EAA4E;AAC5E,wBAAsB,cAAc,CAClC,SAAS,EAAE,MAAM,EACjB,OAAO,GAAE;IAAE,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAO,GAClC,OAAO,CAAC,eAAe,EAAE,CAAC,CAU5B;AAED;;;;GAIG;AACH,wBAAsB,eAAe,CACnC,SAAS,EAAE,MAAM,EACjB,OAAO,GAAE;IAAE,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAO,GAClC,OAAO,CAAC,IAAI,CAAC,CAMf;AAED;;;;;;GAMG;AACH,wBAAsB,YAAY,CAChC,SAAS,EAAE,MAAM,EACjB,OAAO,GAAE;IAAE,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAO,GAClC,OAAO,CAAC,IAAI,CAAC,CAcf;AAMD,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;;GAGG;AACH,wBAAgB,SAAS,CAAC,aAAa,EAAE,MAAM,EAAE,MAAM,EAAE,eAAe,EAAE,GAAG,aAAa,EAAE,CAqB3F;AA0BD,mEAAmE;AACnE,wBAAsB,mBAAmB,CACvC,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,aAAa,EACtB,OAAO,GAAE;IAAE,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAO,GAClC,OAAO,CAAC,IAAI,CAAC,CAQf;AAED,yEAAyE;AACzE,wBAAsB,kBAAkB,CACtC,SAAS,EAAE,MAAM,EACjB,OAAO,GAAE;IAAE,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAO,GAClC,OAAO,CAAC,aAAa,EAAE,CAAC,CAU1B"}