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,289 +0,0 @@
1
- /**
2
- * Drift pattern catalog — known anti-patterns opensquid intercepts at
3
- * the Claude Code PreToolUse hook before the agent commits the action.
4
- *
5
- * Each pattern has:
6
- * - `id` — stable identifier for the rule
7
- * - `trigger` — matcher against tool call input
8
- * - `lesson` — short reference to the lesson that owns this rule
9
- * - `message` — what the agent sees in stderr when intercepted
10
- * - `severity` — "block" (exit 2 stops the call) or "warn" (stderr
11
- * only, call proceeds)
12
- *
13
- * Patterns are CONSERVATIVE on purpose: we'd rather miss a drift than
14
- * spam false positives. The catalog grows lesson-by-lesson as new
15
- * drifts are observed and the user endorses the rule.
16
- */
17
- // ---------------------------------------------------------------------
18
- // Catalog — start with drifts observed in actual build sessions
19
- // ---------------------------------------------------------------------
20
- export const DRIFT_PATTERNS = [
21
- // 1. git commit --amend — never amend (locked override, 2026-05-15)
22
- {
23
- id: "never-amend",
24
- tool: "Bash",
25
- trigger: { kind: "bash_regex", pattern: "git\\s+commit\\b[^\\n]*\\s--amend\\b" },
26
- lesson: "auto-commit",
27
- severity: "block",
28
- message: "BLOCKED: `git commit --amend` violates the never-amend rule (CLAUDE.md " +
29
- "claude-overrides:v1, feedback_auto_commit). Even on unpushed commits — " +
30
- "make a follow-up commit instead. Override only if the user explicitly " +
31
- "requested an amend in THIS turn.",
32
- },
33
- // 2. git push without explicit user request — block by default
34
- {
35
- id: "no-implicit-push",
36
- tool: "Bash",
37
- trigger: { kind: "bash_regex", pattern: "git\\s+push\\b" },
38
- lesson: "auto-commit",
39
- severity: "block",
40
- message: "BLOCKED: `git push` requires explicit user authorization. " +
41
- "Commits stay local until the user says push. If the user just said " +
42
- "to push (or has pre-authorized pushes per CLAUDE.md), bypass with " +
43
- "OPENSQUID_SKIP_DRIFT=1 for this command.",
44
- },
45
- // 3. Engine commit subject containing consumer-product strings
46
- // (substrate-purity rule). Heuristic: matches `git commit -m` in a
47
- // bash command whose body contains `codex`, `opensquid`, or
48
- // `MindCraftor` AND is running inside engine/.
49
- //
50
- // This one INTENTIONALLY peeks inside the -m "..." quoted message,
51
- // so it opts out of the default quote stripping.
52
- {
53
- id: "substrate-purity",
54
- tool: "Bash",
55
- trigger: {
56
- kind: "bash_regex",
57
- pattern: "loop/engine.*git\\s+commit[^\\n]*-m[^\\n]*(codex|opensquid|MindCraftor)",
58
- strip_quotes: false,
59
- },
60
- lesson: "code-quality",
61
- severity: "warn",
62
- message: "WARN: engine commit message appears to reference a consumer-product " +
63
- "concept (codex / opensquid / MindCraftor). Engine commit messages " +
64
- "must stay substrate-pure — engine speaks in engine types only. " +
65
- "Re-word using Pack provenance / lesson / authorship terminology.",
66
- },
67
- // 4. Force-push to main/master — extra protection on top of #2
68
- {
69
- id: "no-force-push-main",
70
- tool: "Bash",
71
- trigger: {
72
- kind: "bash_regex",
73
- pattern: "git\\s+push\\b[^\\n]*(--force|-f)\\b[^\\n]*\\b(main|master)\\b",
74
- },
75
- lesson: "auto-commit",
76
- severity: "block",
77
- message: "BLOCKED: force-push to main/master is destructive. Requires explicit " +
78
- "user request — and even then, prefer a regular push or a new branch.",
79
- },
80
- // 5. Telegram routing — task-completion reports sent to plugin:telegram
81
- // reply (DM) instead of opensquid chat_send (supergroup). 0.7.24 / D2.
82
- // Heuristic: the text starts with the agent's report marker `🦑 #<N>`.
83
- {
84
- id: "telegram-redirect-report",
85
- tool: "mcp__plugin_telegram_telegram__reply",
86
- trigger: { kind: "text_regex", pattern: "^\\s*🦑\\s+#\\d", field: "text" },
87
- lesson: "telegram-reports",
88
- severity: "warn",
89
- message: "WARN: this message looks like a task-completion report (starts with `🦑 #N`). " +
90
- "Per [[feedback_telegram_reports]], reports go via `mcp__opensquid__chat_send` " +
91
- "to the project's `report_channel` (supergroup + topic), not via " +
92
- "plugin:telegram reply (which is the user's DM). Re-route via chat_send if " +
93
- "this is meant to be a task report. Catches drift D2.",
94
- },
95
- // 6. Bundled multi-purpose commit — `git commit -m` message that
96
- // references 2+ distinct task numbers. The D4 incident was commit
97
- // bef7eff bundling "close #166 + defer #168 + section-header
98
- // rewrite" into one commit. The 2-task-ref heuristic is narrow
99
- // enough to avoid most legitimate single-purpose commits while
100
- // catching the typical bundle shape ("close #X and defer #Y").
101
- // 0.7.28 / D4. strip_quotes=false so the -m body is scanned.
102
- {
103
- id: "bundled-commit",
104
- tool: "Bash",
105
- trigger: {
106
- kind: "bash_regex",
107
- pattern: "git\\s+commit\\b[^\\n]*-m\\b[^\\n]*#\\d+[^\\n]*#\\d+",
108
- strip_quotes: false,
109
- },
110
- lesson: "auto-commit",
111
- severity: "warn",
112
- message: "WARN: commit message references 2+ task numbers (`#N`). Per the auto-commit " +
113
- "rule (CLAUDE.md), prefer multiple small logical commits over one large " +
114
- "catchall. If these task numbers are genuinely one logical unit (e.g. one " +
115
- "task closing two others as duplicates), proceed. Catches drift D4.",
116
- },
117
- ];
118
- /**
119
- * Run the catalog against a tool call. Returns every matching pattern;
120
- * caller decides block-vs-warn based on highest severity.
121
- */
122
- export function findDrifts(call) {
123
- const hits = [];
124
- for (const pattern of DRIFT_PATTERNS) {
125
- if (pattern.tool !== "*" && pattern.tool !== call.tool)
126
- continue;
127
- if (matches(pattern.trigger, call)) {
128
- hits.push({ pattern });
129
- }
130
- }
131
- return hits;
132
- }
133
- function matches(trigger, call) {
134
- switch (trigger.kind) {
135
- case "bash_contains": {
136
- const cmd = stringField(call.input, "command");
137
- if (cmd === null)
138
- return false;
139
- const haystack = trigger.strip_quotes === false ? cmd : stripQuotedStrings(cmd);
140
- return haystack.includes(trigger.needle);
141
- }
142
- case "bash_regex": {
143
- const cmd = stringField(call.input, "command");
144
- if (cmd === null)
145
- return false;
146
- const haystack = trigger.strip_quotes === false ? cmd : stripQuotedStrings(cmd);
147
- try {
148
- return new RegExp(trigger.pattern).test(haystack);
149
- }
150
- catch {
151
- return false;
152
- }
153
- }
154
- case "text_regex": {
155
- const text = stringField(call.input, trigger.field);
156
- if (text === null)
157
- return false;
158
- try {
159
- return new RegExp(trigger.pattern).test(text);
160
- }
161
- catch {
162
- return false;
163
- }
164
- }
165
- }
166
- }
167
- /**
168
- * Remove single- and double-quoted string contents PLUS HEREDOC bodies
169
- * from a shell command so drift patterns match REAL shell tokens, not
170
- * text that happens to appear inside `echo "..."`, `grep '...'`, or a
171
- * `git commit -m "$(cat <<'EOF' ... EOF)"` body.
172
- *
173
- * Approximate: doesn't handle backslash-escaped quotes or `$(...)`
174
- * nesting beyond the HEREDOC. Sufficient for the false-positive cases
175
- * observed in real Claude Code sessions; tighten if a real-world
176
- * counter-example surfaces.
177
- *
178
- * v0.6.5 (#136) — added HEREDOC body stripping. Previously, a
179
- * `git commit -m` with a HEREDOC message body containing the literal
180
- * string "git push" would false-fire the no-implicit-push drift block
181
- * because the entire HEREDOC body was part of the bash command string.
182
- * Now the body is stripped before pattern matching. Caught dogfooding
183
- * the v0.6.4 commit (commit message described regex patterns containing
184
- * the word "git push" and the drift-block fired against itself).
185
- *
186
- * Replacement is empty rather than a placeholder so adjacent tokens
187
- * still parse correctly (e.g. `cmd "literal" && more` → `cmd && more`).
188
- */
189
- function stripQuotedStrings(s) {
190
- return stripHeredocBodies(s)
191
- .replace(/'[^']*'/g, "")
192
- .replace(/"[^"]*"/g, "");
193
- }
194
- /**
195
- * Strip HEREDOC bodies (`<<DELIM ... DELIM` and variants) from a
196
- * shell command.
197
- *
198
- * Recognizes:
199
- * <<EOF ... \nEOF (unquoted delimiter, expansion-allowing)
200
- * <<'EOF' ... \nEOF (single-quoted, literal body)
201
- * <<"EOF" ... \nEOF (double-quoted, literal body)
202
- * <<-EOF ... \nEOF (tab-stripping mode)
203
- * <<-'EOF' ... \nEOF (combined)
204
- *
205
- * Delimiter is any word-char sequence (EOF, END, HERE, MARKER, etc.).
206
- * Lazy `[\s\S]*?` matches across newlines; `\b` after the backref
207
- * ensures `EOFX` doesn't close an `<<EOF` block.
208
- *
209
- * If a HEREDOC has no closing delimiter (truncated input), regex
210
- * doesn't match and the body stays intact — fail-open behavior.
211
- *
212
- * Exported for direct unit testing.
213
- */
214
- export function stripHeredocBodies(s) {
215
- // `\n[ \t]*\1\b` — allow leading whitespace before the closing
216
- // delimiter so the `<<-DELIM` (tab-stripping) variant matches its
217
- // indented closing line (`\t\tEOF`). The permissive whitespace
218
- // also covers the plain `<<DELIM` case where users sometimes
219
- // accidentally indent the closing line — no real harm.
220
- return s.replace(/<<-?\s*['"]?(\w+)['"]?[\s\S]*?\n[ \t]*\1\b/g, "");
221
- }
222
- function stringField(input, field) {
223
- const v = input[field];
224
- return typeof v === "string" ? v : null;
225
- }
226
- /**
227
- * Decide the final exit code + message from a list of hits.
228
- *
229
- * - Any "block" hit → exit 2 with all blocking messages
230
- * - Only "warn" hits → exit 0, print warnings to stderr
231
- * - No hits → exit 0 silently
232
- *
233
- * Emergency bypass: `OPENSQUID_SKIP_DRIFT=1` downgrades every block to
234
- * an audit-trail warning (exit 0, stderr explains the bypass). Two ways
235
- * to set it:
236
- *
237
- * 1. Parent process env — useful for whole-session bypass (e.g. set
238
- * before launching Claude Code).
239
- * 2. Inline command prefix — e.g. `OPENSQUID_SKIP_DRIFT=1 git push`.
240
- * The hook reads the COMMAND STRING from the Bash tool input and
241
- * sees the prefix even though the env var never reaches the hook's
242
- * own process.env (the hook is a sibling subprocess spawned by
243
- * Claude Code, not a child of the would-be Bash subprocess).
244
- *
245
- * Matches the shape of the version-gate (`OPENSQUID_SKIP_VERSION_GATE=1`)
246
- * and workflow-gate (`OPENSQUID_SKIP_WORKFLOW_GATE=1`) bypasses so the
247
- * operator only has one mental model — except those two only check
248
- * process.env (their hooks happen before any command runs); drift-
249
- * patterns additionally checks the command-string prefix so the bypass
250
- * can be requested per-command from within an existing session.
251
- */
252
- export function decide(hits, call) {
253
- if (hits.length === 0)
254
- return { exit: 0, stderr: "" };
255
- if (isDriftBypassed(call)) {
256
- const ids = hits.map((h) => h.pattern.id).join(", ");
257
- return {
258
- exit: 0,
259
- stderr: `🦑 [opensquid drift-patterns] BYPASSED via OPENSQUID_SKIP_DRIFT=1 (hits: ${ids})\n`,
260
- };
261
- }
262
- const blocks = hits.filter((h) => h.pattern.severity === "block");
263
- const warns = hits.filter((h) => h.pattern.severity === "warn");
264
- const lines = [];
265
- for (const h of blocks) {
266
- lines.push(`🦑 [opensquid drift-block] ${h.pattern.id}: ${h.pattern.message}`);
267
- }
268
- for (const h of warns) {
269
- lines.push(`🦑 [opensquid drift-warn] ${h.pattern.id}: ${h.pattern.message}`);
270
- }
271
- return {
272
- exit: blocks.length > 0 ? 2 : 0,
273
- stderr: lines.join("\n") + "\n",
274
- };
275
- }
276
- function isDriftBypassed(call) {
277
- if (process.env.OPENSQUID_SKIP_DRIFT === "1")
278
- return true;
279
- if (!call)
280
- return false;
281
- const cmd = stringField(call.input, "command");
282
- if (cmd === null)
283
- return false;
284
- // Inline prefix: zero or more env-var assignments may precede the
285
- // bypass var. Permissive on whitespace; strict on the value (must be
286
- // literally "1" to match the env-var semantics).
287
- return /(^|\s|;|&&)\s*OPENSQUID_SKIP_DRIFT=1(\s|$)/.test(cmd);
288
- }
289
- //# sourceMappingURL=drift-patterns.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"drift-patterns.js","sourceRoot":"","sources":["../../src.legacy/hooks/drift-patterns.ts"],"names":[],"mappings":"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"}
@@ -1,325 +0,0 @@
1
- import { afterEach, describe, expect, it } from "vitest";
2
- import { decide, findDrifts, stripHeredocBodies } from "./drift-patterns.js";
3
- function bash(command) {
4
- return { tool: "Bash", input: { command } };
5
- }
6
- function telegramReply(text) {
7
- return {
8
- tool: "mcp__plugin_telegram_telegram__reply",
9
- input: { chat_id: "8075471258", text },
10
- };
11
- }
12
- describe("drift catalog — never-amend", () => {
13
- it("blocks `git commit --amend`", () => {
14
- const hits = findDrifts(bash('git commit --amend -m "fix typo"'));
15
- expect(hits.map((h) => h.pattern.id)).toContain("never-amend");
16
- expect(decide(hits).exit).toBe(2);
17
- });
18
- it("blocks `git commit -a --amend`", () => {
19
- const hits = findDrifts(bash("git commit -a --amend"));
20
- expect(hits.map((h) => h.pattern.id)).toContain("never-amend");
21
- });
22
- it("doesn't fire on a normal `git commit -m`", () => {
23
- const hits = findDrifts(bash('git commit -m "regular commit"'));
24
- expect(hits.map((h) => h.pattern.id)).not.toContain("never-amend");
25
- });
26
- });
27
- describe("drift catalog — no-implicit-push", () => {
28
- it("blocks `git push`", () => {
29
- const hits = findDrifts(bash("git push origin main"));
30
- expect(hits.map((h) => h.pattern.id)).toContain("no-implicit-push");
31
- expect(decide(hits).exit).toBe(2);
32
- });
33
- it("blocks `git push -u origin feature`", () => {
34
- const hits = findDrifts(bash("git push -u origin feature/x"));
35
- expect(hits.map((h) => h.pattern.id)).toContain("no-implicit-push");
36
- });
37
- it("force-push to main is ALSO caught by both rules", () => {
38
- const hits = findDrifts(bash("git push --force origin main"));
39
- const ids = hits.map((h) => h.pattern.id);
40
- expect(ids).toContain("no-implicit-push");
41
- expect(ids).toContain("no-force-push-main");
42
- expect(decide(hits).exit).toBe(2);
43
- });
44
- it("doesn't fire on `git pull` or `git status`", () => {
45
- expect(findDrifts(bash("git pull")).length).toBe(0);
46
- expect(findDrifts(bash("git status")).length).toBe(0);
47
- });
48
- });
49
- describe("drift catalog — substrate-purity (engine commits)", () => {
50
- it("warns on engine commit message referencing 'codex'", () => {
51
- const hits = findDrifts(bash('cd /Users/slee/projects/loop/engine && git commit -m "v1.1: codex support"'));
52
- expect(hits.map((h) => h.pattern.id)).toContain("substrate-purity");
53
- // Severity = warn, so exit stays 0 (call proceeds).
54
- expect(decide(hits).exit).toBe(0);
55
- });
56
- it("warns on engine commit referencing 'opensquid'", () => {
57
- const hits = findDrifts(bash('cd ~/projects/loop/engine && git commit -m "support for opensquid pack"'));
58
- expect(hits.map((h) => h.pattern.id)).toContain("substrate-purity");
59
- });
60
- it("doesn't fire on substrate-pure engine commits", () => {
61
- const hits = findDrifts(bash('cd /Users/slee/projects/loop/engine && git commit -m "v1.1: Pack authorship"'));
62
- expect(hits.map((h) => h.pattern.id)).not.toContain("substrate-purity");
63
- });
64
- it("doesn't fire on opensquid commits mentioning codex (correct context)", () => {
65
- const hits = findDrifts(bash('cd /Users/slee/projects/opensquid && git commit -m "v0.4: codex CLI"'));
66
- expect(hits.map((h) => h.pattern.id)).not.toContain("substrate-purity");
67
- });
68
- });
69
- describe("decide — combining hits", () => {
70
- it("returns exit 0 + empty stderr on no hits", () => {
71
- const { exit, stderr } = decide([]);
72
- expect(exit).toBe(0);
73
- expect(stderr).toBe("");
74
- });
75
- it("returns exit 2 if any hit is severity=block", () => {
76
- const hits = findDrifts(bash("git push --force origin main"));
77
- const { exit, stderr } = decide(hits);
78
- expect(exit).toBe(2);
79
- expect(stderr).toContain("BLOCKED");
80
- });
81
- it("returns exit 0 + stderr on warn-only hits", () => {
82
- const hits = findDrifts(bash('cd /Users/slee/projects/loop/engine && git commit -m "codex stuff"'));
83
- const { exit, stderr } = decide(hits);
84
- expect(exit).toBe(0);
85
- expect(stderr).toContain("WARN");
86
- });
87
- });
88
- describe("non-bash tools are not matched by bash rules", () => {
89
- it("Edit calls bypass git rules", () => {
90
- const call = {
91
- tool: "Edit",
92
- input: { file_path: "/x/y", old_string: "git commit --amend", new_string: "" },
93
- };
94
- expect(findDrifts(call).length).toBe(0);
95
- });
96
- });
97
- describe("false-positive resistance — patterns inside quoted strings", () => {
98
- it("never-amend ignores --amend inside double-quoted string", () => {
99
- const hits = findDrifts(bash('echo "git commit --amend in a string literal"'));
100
- expect(hits.map((h) => h.pattern.id)).not.toContain("never-amend");
101
- });
102
- it("never-amend ignores --amend inside single-quoted string", () => {
103
- const hits = findDrifts(bash("grep 'git commit --amend' file.txt"));
104
- expect(hits.map((h) => h.pattern.id)).not.toContain("never-amend");
105
- });
106
- it("no-implicit-push ignores 'git push' in an echo literal", () => {
107
- const hits = findDrifts(bash('echo "to deploy run git push origin main"'));
108
- expect(hits.map((h) => h.pattern.id)).not.toContain("no-implicit-push");
109
- });
110
- it("never-amend STILL fires when amend is a real shell token", () => {
111
- const hits = findDrifts(bash('git commit --amend -m "fix typo"'));
112
- expect(hits.map((h) => h.pattern.id)).toContain("never-amend");
113
- });
114
- it("never-amend fires after shell continuation (&&, ;, |)", () => {
115
- expect(findDrifts(bash("cd /repo && git commit --amend")).map((h) => h.pattern.id)).toContain("never-amend");
116
- expect(findDrifts(bash("foo; git commit --amend")).map((h) => h.pattern.id)).toContain("never-amend");
117
- });
118
- });
119
- // =====================================================================
120
- // v0.6.5 (#136) — HEREDOC body stripping. Caught while dogfooding the
121
- // v0.6.4 commit: the no-implicit-push drift fired against a `git commit`
122
- // whose HEREDOC commit message body contained the literal string
123
- // describing a regex pattern (the words `git push` appeared in prose
124
- // describing a pattern). The hook scanned the entire bash command
125
- // string including HEREDOC bodies → false-positive block.
126
- //
127
- // Fix: stripHeredocBodies runs before stripQuotedStrings so the body
128
- // is removed before any drift regex sees it.
129
- // =====================================================================
130
- describe("stripHeredocBodies (v0.6.5)", () => {
131
- it("strips single-quoted-delimiter HEREDOC body", () => {
132
- const cmd = `git commit -m "$(cat <<'EOF'
133
- This body contains git push origin main
134
- EOF
135
- )"`;
136
- expect(stripHeredocBodies(cmd)).not.toContain("git push");
137
- });
138
- it("strips unquoted-delimiter HEREDOC body", () => {
139
- const cmd = `cat <<MARKER
140
- inner content with git push verbatim
141
- MARKER`;
142
- expect(stripHeredocBodies(cmd)).not.toContain("git push");
143
- });
144
- it("strips double-quoted-delimiter HEREDOC body", () => {
145
- const cmd = `cat <<"END"
146
- git push --force here
147
- END`;
148
- expect(stripHeredocBodies(cmd)).not.toContain("git push");
149
- });
150
- it("strips tab-stripping (<<-) variant", () => {
151
- const cmd = `cat <<-EOF
152
- \t\tgit push danger
153
- \tEOF`;
154
- expect(stripHeredocBodies(cmd)).not.toContain("git push");
155
- });
156
- it("strips multiple HEREDOCs in one command", () => {
157
- const cmd = `cat <<'A'
158
- contains git push
159
- A
160
- echo "between"
161
- cat <<'B'
162
- contains git commit --amend
163
- B`;
164
- const stripped = stripHeredocBodies(cmd);
165
- expect(stripped).not.toContain("git push");
166
- expect(stripped).not.toContain("git commit --amend");
167
- });
168
- it("leaves a truncated HEREDOC (no closing delimiter) intact (fail-open)", () => {
169
- const cmd = `cat <<EOF
170
- truncated body with git push but no EOF closing`;
171
- // No \nEOF\b on its own → regex doesn't match → fail-open
172
- expect(stripHeredocBodies(cmd)).toContain("git push");
173
- });
174
- });
175
- describe("drift catalog — HEREDOC false-positive resistance (v0.6.5 #136)", () => {
176
- it("no-implicit-push does NOT fire when 'git push' appears only in a HEREDOC commit message", () => {
177
- // This is the exact pattern that bit me during the v0.6.4 commit.
178
- const cmd = `git -c commit.gpgsign=false commit -m "$(cat <<'EOF'
179
- feat: blah
180
-
181
- - pushed (bash_regex git push) — this LITERAL string in the message
182
- body would have tripped the no-implicit-push drift block before
183
- the v0.6.5 fix.
184
- EOF
185
- )"`;
186
- const hits = findDrifts(bash(cmd));
187
- expect(hits.map((h) => h.pattern.id)).not.toContain("no-implicit-push");
188
- });
189
- it("never-amend does NOT fire on 'git commit --amend' in HEREDOC commit body", () => {
190
- const cmd = `git commit -m "$(cat <<'EOF'
191
- Mentioning git commit --amend in the message body for context.
192
- EOF
193
- )"`;
194
- const hits = findDrifts(bash(cmd));
195
- expect(hits.map((h) => h.pattern.id)).not.toContain("never-amend");
196
- });
197
- it("STILL fires when 'git push' is the actual command after a HEREDOC", () => {
198
- // The HEREDOC ends, then a real git push follows. Must still block.
199
- const cmd = `cat <<'EOF'
200
- some prose
201
- EOF
202
- git push origin main`;
203
- const hits = findDrifts(bash(cmd));
204
- expect(hits.map((h) => h.pattern.id)).toContain("no-implicit-push");
205
- });
206
- });
207
- // =====================================================================
208
- // v0.6.6 (#137) — OPENSQUID_SKIP_DRIFT emergency bypass. Mirrors
209
- // OPENSQUID_SKIP_VERSION_GATE / OPENSQUID_SKIP_WORKFLOW_GATE shape so
210
- // the operator only has one mental model for "this hook is wrong, get
211
- // out of my way". The documented uninstall-hooks workaround doesn't
212
- // actually work mid-session because Claude Code caches the settings.json
213
- // hook command at session start.
214
- // =====================================================================
215
- describe("OPENSQUID_SKIP_DRIFT bypass (v0.6.6)", () => {
216
- // Vitest runs tests serially within a file by default; we restore the
217
- // env var after each test so other tests aren't tainted.
218
- afterEach(() => {
219
- delete process.env.OPENSQUID_SKIP_DRIFT;
220
- });
221
- it("ALLOWS (exit 0) with bypass warning when OPENSQUID_SKIP_DRIFT=1 is in parent env and a block would fire", () => {
222
- process.env.OPENSQUID_SKIP_DRIFT = "1";
223
- const call = bash("git push origin main");
224
- const hits = findDrifts(call);
225
- expect(hits.length).toBeGreaterThan(0);
226
- const { exit, stderr } = decide(hits, call);
227
- expect(exit).toBe(0);
228
- expect(stderr).toContain("BYPASSED via OPENSQUID_SKIP_DRIFT=1");
229
- expect(stderr).toContain("no-implicit-push");
230
- });
231
- it("ALLOWS via INLINE prefix `OPENSQUID_SKIP_DRIFT=1 git push ...` even when parent env unset", () => {
232
- delete process.env.OPENSQUID_SKIP_DRIFT;
233
- const call = bash("OPENSQUID_SKIP_DRIFT=1 git push origin main");
234
- const hits = findDrifts(call);
235
- expect(hits.length).toBeGreaterThan(0);
236
- const { exit, stderr } = decide(hits, call);
237
- expect(exit).toBe(0);
238
- expect(stderr).toContain("BYPASSED via OPENSQUID_SKIP_DRIFT=1");
239
- });
240
- it("ALLOWS via inline prefix after `cd ... &&` chain", () => {
241
- const call = bash("cd /tmp && OPENSQUID_SKIP_DRIFT=1 git push origin main");
242
- const hits = findDrifts(call);
243
- const { exit } = decide(hits, call);
244
- expect(exit).toBe(0);
245
- });
246
- it("includes ALL hit ids in the bypass message (operator audit trail)", () => {
247
- process.env.OPENSQUID_SKIP_DRIFT = "1";
248
- const call = bash("git push --force origin main");
249
- const hits = findDrifts(call);
250
- const { stderr } = decide(hits, call);
251
- expect(stderr).toContain("no-implicit-push");
252
- expect(stderr).toContain("no-force-push-main");
253
- });
254
- it("does NOT bypass when env var is unset or != '1'", () => {
255
- process.env.OPENSQUID_SKIP_DRIFT = "true";
256
- const call = bash("git push origin main");
257
- const hits = findDrifts(call);
258
- const { exit } = decide(hits, call);
259
- expect(exit).toBe(2);
260
- });
261
- it("does NOT bypass when inline prefix value != '1'", () => {
262
- const call = bash("OPENSQUID_SKIP_DRIFT=true git push origin main");
263
- const hits = findDrifts(call);
264
- const { exit } = decide(hits, call);
265
- expect(exit).toBe(2);
266
- });
267
- it("does NOT bypass when var name appears only as substring", () => {
268
- // Defensive: `MY_OPENSQUID_SKIP_DRIFT=1` shouldn't match (leading word-boundary).
269
- const call = bash("MY_OPENSQUID_SKIP_DRIFT=1 git push origin main");
270
- const hits = findDrifts(call);
271
- const { exit } = decide(hits, call);
272
- expect(exit).toBe(2);
273
- });
274
- it("emits nothing on empty hits regardless of env var", () => {
275
- process.env.OPENSQUID_SKIP_DRIFT = "1";
276
- const { exit, stderr } = decide([]);
277
- expect(exit).toBe(0);
278
- expect(stderr).toBe("");
279
- });
280
- });
281
- describe("drift catalog — telegram-redirect-report (D2)", () => {
282
- it("warns when plugin:telegram reply starts with `🦑 #N` task-report marker", () => {
283
- const hits = findDrifts(telegramReply("🦑 #170 — engine-client startupAck fix\n\nshipped"));
284
- expect(hits.map((h) => h.pattern.id)).toContain("telegram-redirect-report");
285
- expect(decide(hits).exit).toBe(0); // warn-severity = non-blocking
286
- });
287
- it("warns with leading whitespace before the marker", () => {
288
- const hits = findDrifts(telegramReply(" 🦑 #4 — cleanup commit"));
289
- expect(hits.map((h) => h.pattern.id)).toContain("telegram-redirect-report");
290
- });
291
- it("does NOT fire on regular plugin:telegram replies", () => {
292
- const hits = findDrifts(telegramReply("ok, working on it now"));
293
- expect(hits.map((h) => h.pattern.id)).not.toContain("telegram-redirect-report");
294
- });
295
- it("does NOT fire on chat_send (the correct tool for reports)", () => {
296
- const hits = findDrifts({
297
- tool: "mcp__opensquid__chat_send",
298
- input: { text: "🦑 #170 — full 7-phase report goes here" },
299
- });
300
- expect(hits.map((h) => h.pattern.id)).not.toContain("telegram-redirect-report");
301
- });
302
- });
303
- describe("drift catalog — bundled-commit (D4)", () => {
304
- it("warns on a commit message that references 2+ task numbers", () => {
305
- const hits = findDrifts(bash('git commit -m "close #166 and defer #168"'));
306
- expect(hits.map((h) => h.pattern.id)).toContain("bundled-commit");
307
- expect(decide(hits).exit).toBe(0); // warn-severity = non-blocking
308
- });
309
- it('warns on inline `-m "..."` with 2 #N refs on one line', () => {
310
- const hits = findDrifts(bash('git commit -m "release: #166 + #168 + cleanup"'));
311
- expect(hits.map((h) => h.pattern.id)).toContain("bundled-commit");
312
- });
313
- // Known limitation: HEREDOC commit message bodies are stripped by
314
- // stripHeredocBodies BEFORE pattern matching, so refs inside the
315
- // HEREDOC body don't fire this pattern. Adding staged-content-aware
316
- // detection (via a dedicated bundled-commit-gate) is deferred.
317
- it("does NOT fire on a single-task commit", () => {
318
- const hits = findDrifts(bash('git commit -m "fix(workflow-gate): session_id mismatch (#166)"'));
319
- expect(hits.map((h) => h.pattern.id)).not.toContain("bundled-commit");
320
- });
321
- it("does NOT fire on commits with no task ref at all", () => {
322
- const hits = findDrifts(bash('git commit -m "refactor: drift-patterns.ts"'));
323
- expect(hits.map((h) => h.pattern.id)).not.toContain("bundled-commit");
324
- });
325
- });