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,245 +0,0 @@
1
- /**
2
- * Versioning gate — pre-commit check enforcing per-commit patch bumps
3
- * (v0.6.3). Wired into the PreToolUse hook for `git commit` commands.
4
- *
5
- * Problem this fixes: I keep batching multiple fixes into one commit
6
- * and bumping the minor (or no bump at all) instead of one patch per
7
- * fix. The discipline rule was memorized (`mem-d2cc0e78`) but rules
8
- * I can ignore aren't structural protection. This gate makes the
9
- * discipline mechanical — if your commit touches source code AND
10
- * doesn't include a manifest version bump in the same commit, it
11
- * gets rejected before `git commit` runs.
12
- *
13
- * Detection:
14
- * 1. `git diff --cached --name-only` → list of staged files
15
- * 2. If no `src/**` files staged → allow (docs/CI/config commits
16
- * don't need version bumps)
17
- * 3. If `src/**` files staged → require a manifest (Cargo.toml or
18
- * package.json) to also be staged with a `version` line diff
19
- * 4. Otherwise → block with actionable message
20
- *
21
- * Fail-open invariant: any error running git or parsing output →
22
- * allow with a stderr warning (per the honesty-ledger + workflow-gate
23
- * precedent — never block on opensquid's own bug).
24
- *
25
- * Emergency override: `OPENSQUID_SKIP_VERSION_GATE=1` bypasses with a
26
- * loud stderr warning. For genuine emergencies (revert commits,
27
- * generated-code-only diffs, etc.) where the discipline doesn't
28
- * apply.
29
- */
30
- import { exec as execCb } from "node:child_process";
31
- import { promisify } from "node:util";
32
- const exec = promisify(execCb);
33
- export async function evaluateVersioningGate(input = {}) {
34
- if (checkOverrideEnv()) {
35
- return {
36
- block: false,
37
- stderr: "🦑 [opensquid versioning-gate] BYPASSED via OPENSQUID_SKIP_VERSION_GATE=1\n",
38
- };
39
- }
40
- const cwd = input.cwd ?? process.cwd();
41
- // List staged files. `--no-renames` keeps the output simple (renames
42
- // appear as both old + new path rather than `R100\told\tnew`).
43
- let stagedFiles;
44
- try {
45
- const { stdout } = await exec("git diff --cached --name-only --no-renames", {
46
- cwd,
47
- maxBuffer: 1024 * 1024,
48
- });
49
- stagedFiles = stdout
50
- .split("\n")
51
- .map((l) => l.trim())
52
- .filter(Boolean);
53
- }
54
- catch (err) {
55
- return {
56
- block: false,
57
- stderr: `[opensquid versioning-gate] git diff failed (proceeding): ${err instanceof Error ? err.message : err}\n`,
58
- };
59
- }
60
- if (stagedFiles.length === 0) {
61
- // Nothing staged — git commit will fail on its own with no need
62
- // for this gate to intervene.
63
- return { block: false, stderr: "" };
64
- }
65
- const sourceFiles = stagedFiles.filter(isSourceFile);
66
- if (sourceFiles.length === 0) {
67
- // Docs / CI / config / fixtures / etc. — no source change, no bump needed.
68
- return { block: false, stderr: "" };
69
- }
70
- const manifests = stagedFiles.filter(isManifestFile);
71
- if (manifests.length === 0) {
72
- return {
73
- block: true,
74
- stderr: buildBlockMessage(sourceFiles, []),
75
- };
76
- }
77
- // At least one manifest is staged — check that at least one has a
78
- // version-line diff (just touching the manifest without bumping
79
- // version doesn't count).
80
- let bumpedManifest = null;
81
- for (const m of manifests) {
82
- const jump = await readManifestVersionBump(cwd, m);
83
- if (jump) {
84
- bumpedManifest = { path: m, jump };
85
- break;
86
- }
87
- }
88
- if (!bumpedManifest) {
89
- return {
90
- block: true,
91
- stderr: buildBlockMessage(sourceFiles, manifests),
92
- };
93
- }
94
- // 0.7.23 / D5 — catch-up bump detection. The PATCH-ONLY rule
95
- // ([[feedback_pre1_versioning]] v4) says every src commit = exactly
96
- // one patch bump. A jump like 0.7.10 → 0.7.14 in a single commit
97
- // means previous src commits skipped their bumps. Don't BLOCK
98
- // (legitimate explicit catch-ups exist), but surface a loud warning
99
- // so the skip is visible.
100
- const { jump } = bumpedManifest;
101
- if (jump && isMultiPatchJump(jump)) {
102
- return {
103
- block: false,
104
- stderr: `🦑 [opensquid versioning-gate] WARN: catch-up bump detected (${jump.from} → ${jump.to})\n` +
105
- ` PATCH-ONLY rule says one patch per commit. A multi-patch jump in one\n` +
106
- ` commit usually means earlier src commits shipped without bumps. Drift D5.\n`,
107
- };
108
- }
109
- return { block: false, stderr: "" };
110
- }
111
- /**
112
- * Is this a source file that should trigger version-bump enforcement?
113
- * Generous definition: anything under `src/` for any language we support.
114
- */
115
- export function isSourceFile(p) {
116
- // src/ at any depth (top-level or nested workspace member)
117
- return /(^|\/)src\//.test(p);
118
- }
119
- /** Is this the repo's version manifest? */
120
- export function isManifestFile(p) {
121
- const base = p.split("/").pop() ?? "";
122
- return base === "Cargo.toml" || base === "package.json";
123
- }
124
- /**
125
- * Look at the staged diff of a manifest and return the version jump
126
- * (from → to). Returns null when the diff doesn't touch a `version`
127
- * line at all.
128
- *
129
- * Exported for direct testing.
130
- */
131
- export async function readManifestVersionBump(cwd, manifestPath) {
132
- let diff;
133
- try {
134
- const { stdout } = await exec(`git diff --cached --no-color -U0 -- ${quoteShell(manifestPath)}`, { cwd, maxBuffer: 1024 * 1024 });
135
- diff = stdout;
136
- }
137
- catch {
138
- return null;
139
- }
140
- return parseVersionJumpFromDiff(diff);
141
- }
142
- /**
143
- * Parse `+`/`-` lines from a manifest diff and extract the version
144
- * jump. Cargo: `version = "..."`. package.json: `"version": "..."`.
145
- *
146
- * Anchor discipline:
147
- * - Cargo (TOML, line-oriented) → anchor `^version` so we don't
148
- * match a dep with `version = "..."` in `[dependencies.foo]`.
149
- * - package.json (JSON, can be MINIFIED single-line) → do NOT
150
- * anchor; `"version"` can appear mid-line in minified JSON.
151
- *
152
- * Exported for direct testing.
153
- */
154
- export function parseVersionJumpFromDiff(diff) {
155
- let oldVersion = null;
156
- let newVersion = null;
157
- for (const line of diff.split("\n")) {
158
- if (line.startsWith("+++") || line.startsWith("---"))
159
- continue;
160
- const sign = line[0];
161
- if (sign !== "+" && sign !== "-")
162
- continue;
163
- const body = line.slice(1).trim();
164
- let v = null;
165
- const cargoMatch = body.match(/^version\s*=\s*"([^"]+)"/);
166
- if (cargoMatch)
167
- v = cargoMatch[1];
168
- if (v === null) {
169
- const npmMatch = body.match(/"version"\s*:\s*"([^"]+)"/);
170
- if (npmMatch)
171
- v = npmMatch[1];
172
- }
173
- if (v === null)
174
- continue;
175
- if (sign === "+")
176
- newVersion = v;
177
- else
178
- oldVersion = v;
179
- }
180
- if (!newVersion)
181
- return null;
182
- // New-version only (e.g. brand-new manifest with no prior `version`
183
- // line) is still a valid "version bump" — treat oldVersion as empty.
184
- return { from: oldVersion ?? "", to: newVersion };
185
- }
186
- /**
187
- * Detect a multi-patch jump: same major.minor, but patch advances by
188
- * more than 1 (catch-up bump).
189
- *
190
- * Returns false for:
191
- * - First-time bumps (from === "")
192
- * - Same-patch (no actual jump, shouldn't happen with proper diff)
193
- * - Minor/major bumps (those are user-authorized; PATCH-ONLY rule
194
- * forbids the agent from naming them but doesn't make them "drift")
195
- * - Non-SemVer version strings (best-effort parse)
196
- *
197
- * Exported for direct testing.
198
- */
199
- export function isMultiPatchJump(jump) {
200
- const oldParts = parseSemver(jump.from);
201
- const newParts = parseSemver(jump.to);
202
- if (!oldParts || !newParts)
203
- return false;
204
- // Only flag same-major.minor with patch jump > 1.
205
- if (oldParts.major !== newParts.major)
206
- return false;
207
- if (oldParts.minor !== newParts.minor)
208
- return false;
209
- return newParts.patch > oldParts.patch + 1;
210
- }
211
- function parseSemver(v) {
212
- const m = v.match(/^(\d+)\.(\d+)\.(\d+)/);
213
- if (!m)
214
- return null;
215
- return { major: Number(m[1]), minor: Number(m[2]), patch: Number(m[3]) };
216
- }
217
- function quoteShell(s) {
218
- // Defensive single-quote shell escape. Manifest paths are usually
219
- // boring but we don't trust them blindly.
220
- return `'${s.replace(/'/g, "'\\''")}'`;
221
- }
222
- function buildBlockMessage(sourceFiles, manifests) {
223
- const srcSample = sourceFiles.slice(0, 5).join(", ") + (sourceFiles.length > 5 ? ", ..." : "");
224
- const lines = [
225
- `🦑 [opensquid versioning-gate] commit blocked — source changes without a version bump`,
226
- ` source files staged (${sourceFiles.length}): ${srcSample}`,
227
- ];
228
- if (manifests.length === 0) {
229
- lines.push(` No Cargo.toml or package.json staged.`, ` Bump the patch version (per mem-d2cc0e78 — fix per commit, not batched), \`git add\` the manifest, then re-commit.`);
230
- }
231
- else {
232
- lines.push(` Manifest(s) staged but no version-line diff: ${manifests.join(", ")}`, ` Bump the version field (Cargo.toml: \`version = "x.y.z"\`, package.json: \`"version": "x.y.z"\`), re-stage, then re-commit.`);
233
- }
234
- lines.push(` Override (genuine emergency): set OPENSQUID_SKIP_VERSION_GATE=1 for this command.`);
235
- return lines.join("\n") + "\n";
236
- }
237
- /**
238
- * Emergency-override env var. Loud stderr warning on bypass so it
239
- * always shows up in scrollback / CI logs. Exported for the test
240
- * suite.
241
- */
242
- export function checkOverrideEnv() {
243
- return process.env.OPENSQUID_SKIP_VERSION_GATE === "1";
244
- }
245
- //# sourceMappingURL=versioning-gate.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"versioning-gate.js","sourceRoot":"","sources":["../../src.legacy/hooks/versioning-gate.ts"],"names":[],"mappings":"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"}
@@ -1,368 +0,0 @@
1
- /**
2
- * Versioning-gate tests — exercise against real git repos in tmpdirs.
3
- * The gate's logic depends on actual `git diff --cached` output shape,
4
- * so synthesized state wouldn't catch the same class of bug that
5
- * v0.6.1 transcript-walker missed against real Claude Code shapes
6
- * (per the v0.6.2 lesson). Each test inits a fresh tmp git repo,
7
- * sets up the staged state we want, runs the gate, asserts.
8
- */
9
- import { afterEach, beforeEach, describe, expect, it } from "vitest";
10
- import { execSync } from "node:child_process";
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 { checkOverrideEnv, evaluateVersioningGate, isManifestFile, isMultiPatchJump, isSourceFile, parseVersionJumpFromDiff, } from "./versioning-gate.js";
16
- // ---------------------------------------------------------------------
17
- // Helpers
18
- // ---------------------------------------------------------------------
19
- let repoDir;
20
- beforeEach(async () => {
21
- repoDir = path.join(os.tmpdir(), `opensquid-ver-${crypto.randomUUID()}`);
22
- await fs.mkdir(repoDir, { recursive: true });
23
- // Minimal git init that doesn't depend on the user's global config.
24
- execSync("git init -q", { cwd: repoDir });
25
- execSync("git config user.email test@example.com", { cwd: repoDir });
26
- execSync("git config user.name 'Test'", { cwd: repoDir });
27
- });
28
- afterEach(async () => {
29
- await fs.rm(repoDir, { recursive: true, force: true });
30
- delete process.env.OPENSQUID_SKIP_VERSION_GATE;
31
- });
32
- async function writeFile(rel, content) {
33
- const full = path.join(repoDir, rel);
34
- await fs.mkdir(path.dirname(full), { recursive: true });
35
- await fs.writeFile(full, content, "utf8");
36
- }
37
- function stage(rel) {
38
- execSync(`git add ${rel}`, { cwd: repoDir });
39
- }
40
- function commit(message) {
41
- execSync(`git commit -q -m '${message}'`, { cwd: repoDir });
42
- }
43
- /** Initial commit so subsequent `git diff --cached` against HEAD works. */
44
- async function seedInitial() {
45
- await writeFile("README.md", "# seed\n");
46
- stage("README.md");
47
- commit("initial");
48
- }
49
- // ---------------------------------------------------------------------
50
- // Pure helper tests (no git involvement)
51
- // ---------------------------------------------------------------------
52
- describe("isSourceFile", () => {
53
- it("matches src/ at repo root", () => {
54
- expect(isSourceFile("src/index.ts")).toBe(true);
55
- expect(isSourceFile("src/hooks/foo.ts")).toBe(true);
56
- });
57
- it("matches nested src/ in workspace member", () => {
58
- expect(isSourceFile("crates/engine/src/lib.rs")).toBe(true);
59
- expect(isSourceFile("packages/x/src/foo.js")).toBe(true);
60
- });
61
- it("does not match top-level files outside src/", () => {
62
- expect(isSourceFile("README.md")).toBe(false);
63
- expect(isSourceFile("Cargo.toml")).toBe(false);
64
- expect(isSourceFile("package.json")).toBe(false);
65
- expect(isSourceFile("CHANGELOG.md")).toBe(false);
66
- expect(isSourceFile(".github/workflows/ci.yml")).toBe(false);
67
- expect(isSourceFile("tests/foo.test.ts")).toBe(false);
68
- });
69
- it("does not match 'srcfoo/' (false-prefix)", () => {
70
- expect(isSourceFile("srcfoo/bar.ts")).toBe(false);
71
- });
72
- });
73
- describe("isManifestFile", () => {
74
- it("matches root-level manifests", () => {
75
- expect(isManifestFile("Cargo.toml")).toBe(true);
76
- expect(isManifestFile("package.json")).toBe(true);
77
- });
78
- it("matches nested manifests (workspace members)", () => {
79
- expect(isManifestFile("crates/engine/Cargo.toml")).toBe(true);
80
- expect(isManifestFile("packages/x/package.json")).toBe(true);
81
- });
82
- it("does not match other tomls / jsons", () => {
83
- expect(isManifestFile("tsconfig.json")).toBe(false);
84
- expect(isManifestFile("rustfmt.toml")).toBe(false);
85
- expect(isManifestFile("clippy.toml")).toBe(false);
86
- });
87
- });
88
- // ---------------------------------------------------------------------
89
- // Allow paths — gate should NOT block
90
- // ---------------------------------------------------------------------
91
- describe("evaluateVersioningGate — allow paths", () => {
92
- it("allows when nothing is staged", async () => {
93
- await seedInitial();
94
- const r = await evaluateVersioningGate({ cwd: repoDir });
95
- expect(r.block).toBe(false);
96
- expect(r.stderr).toBe("");
97
- });
98
- it("allows docs-only commit (no src/ staged)", async () => {
99
- await seedInitial();
100
- await writeFile("README.md", "# updated\n");
101
- await writeFile("CHANGELOG.md", "## changes\n");
102
- stage("README.md");
103
- stage("CHANGELOG.md");
104
- const r = await evaluateVersioningGate({ cwd: repoDir });
105
- expect(r.block).toBe(false);
106
- });
107
- it("allows CI-only commit", async () => {
108
- await seedInitial();
109
- await writeFile(".github/workflows/ci.yml", "name: CI\n");
110
- stage(".github/workflows/ci.yml");
111
- const r = await evaluateVersioningGate({ cwd: repoDir });
112
- expect(r.block).toBe(false);
113
- });
114
- it("allows src + Cargo.toml version bump in same commit", async () => {
115
- await seedInitial();
116
- await writeFile("Cargo.toml", 'version = "0.1.0"\n');
117
- await writeFile("src/lib.rs", "// initial\n");
118
- stage("Cargo.toml");
119
- stage("src/lib.rs");
120
- commit("initial src + manifest");
121
- // Now bump:
122
- await writeFile("Cargo.toml", 'version = "0.1.1"\n');
123
- await writeFile("src/lib.rs", "// patched\n");
124
- stage("Cargo.toml");
125
- stage("src/lib.rs");
126
- const r = await evaluateVersioningGate({ cwd: repoDir });
127
- expect(r.block).toBe(false);
128
- });
129
- // Audit HIGH fix (v0.6.3): minified package.json was originally a
130
- // false-positive block because the regex anchored `^"version"` and
131
- // minified JSON has `"version"` mid-line. Drop the anchor.
132
- it("allows src + MINIFIED package.json version bump (audit HIGH regression)", async () => {
133
- await seedInitial();
134
- await writeFile("package.json", '{"name":"x","version":"0.1.0"}\n');
135
- await writeFile("src/index.ts", "// initial\n");
136
- stage("package.json");
137
- stage("src/index.ts");
138
- commit("initial");
139
- await writeFile("package.json", '{"name":"x","version":"0.1.1"}\n');
140
- await writeFile("src/index.ts", "// patched\n");
141
- stage("package.json");
142
- stage("src/index.ts");
143
- const r = await evaluateVersioningGate({ cwd: repoDir });
144
- expect(r.block).toBe(false);
145
- });
146
- it("allows src + package.json version bump in same commit (realistic multi-line JSON)", async () => {
147
- await seedInitial();
148
- // Real package.json is pretty-printed multi-line so the diff
149
- // surfaces a `"version": "..."` change on its own line.
150
- const pkgV1 = JSON.stringify({ name: "x", version: "0.1.0" }, null, 2) + "\n";
151
- const pkgV2 = JSON.stringify({ name: "x", version: "0.1.1" }, null, 2) + "\n";
152
- await writeFile("package.json", pkgV1);
153
- await writeFile("src/index.ts", "// initial\n");
154
- stage("package.json");
155
- stage("src/index.ts");
156
- commit("initial");
157
- await writeFile("package.json", pkgV2);
158
- await writeFile("src/index.ts", "// patched\n");
159
- stage("package.json");
160
- stage("src/index.ts");
161
- const r = await evaluateVersioningGate({ cwd: repoDir });
162
- expect(r.block).toBe(false);
163
- });
164
- });
165
- // ---------------------------------------------------------------------
166
- // Block paths — gate SHOULD block
167
- // ---------------------------------------------------------------------
168
- describe("evaluateVersioningGate — block paths", () => {
169
- it("blocks src/ change with no manifest staged", async () => {
170
- await seedInitial();
171
- await writeFile("Cargo.toml", 'version = "0.1.0"\n');
172
- await writeFile("src/lib.rs", "// initial\n");
173
- stage("Cargo.toml");
174
- stage("src/lib.rs");
175
- commit("initial src + manifest");
176
- // Now ONLY src changes, no manifest bump:
177
- await writeFile("src/lib.rs", "// edited\n");
178
- stage("src/lib.rs");
179
- const r = await evaluateVersioningGate({ cwd: repoDir });
180
- expect(r.block).toBe(true);
181
- expect(r.stderr).toContain("commit blocked");
182
- expect(r.stderr).toContain("src/lib.rs");
183
- expect(r.stderr).toContain("No Cargo.toml or package.json staged");
184
- });
185
- it("blocks src/ change with manifest staged but no version-line diff", async () => {
186
- await seedInitial();
187
- await writeFile("Cargo.toml", 'version = "0.1.0"\n[dependencies]\nfoo = "1"\n');
188
- await writeFile("src/lib.rs", "// initial\n");
189
- stage("Cargo.toml");
190
- stage("src/lib.rs");
191
- commit("initial");
192
- // Touch manifest (add dep) but DON'T bump version, also touch src:
193
- await writeFile("Cargo.toml", 'version = "0.1.0"\n[dependencies]\nfoo = "1"\nbar = "2"\n');
194
- await writeFile("src/lib.rs", "// edited\n");
195
- stage("Cargo.toml");
196
- stage("src/lib.rs");
197
- const r = await evaluateVersioningGate({ cwd: repoDir });
198
- expect(r.block).toBe(true);
199
- expect(r.stderr).toContain("no version-line diff");
200
- expect(r.stderr).toContain("Cargo.toml");
201
- });
202
- });
203
- // ---------------------------------------------------------------------
204
- // Multi-manifest / workspace
205
- // ---------------------------------------------------------------------
206
- describe("evaluateVersioningGate — workspace / multi-manifest", () => {
207
- it("allows when ANY staged manifest has a version bump", async () => {
208
- await seedInitial();
209
- await writeFile("crates/a/Cargo.toml", 'version = "0.1.0"\n');
210
- await writeFile("crates/b/Cargo.toml", 'version = "0.1.0"\n');
211
- await writeFile("crates/a/src/lib.rs", "// a\n");
212
- await writeFile("crates/b/src/lib.rs", "// b\n");
213
- stage("crates/a/Cargo.toml");
214
- stage("crates/b/Cargo.toml");
215
- stage("crates/a/src/lib.rs");
216
- stage("crates/b/src/lib.rs");
217
- commit("initial");
218
- // Bump ONLY crate a; edit src in BOTH:
219
- await writeFile("crates/a/Cargo.toml", 'version = "0.1.1"\n');
220
- await writeFile("crates/a/src/lib.rs", "// a patched\n");
221
- await writeFile("crates/b/src/lib.rs", "// b edited\n");
222
- stage("crates/a/Cargo.toml");
223
- stage("crates/a/src/lib.rs");
224
- stage("crates/b/src/lib.rs");
225
- const r = await evaluateVersioningGate({ cwd: repoDir });
226
- // Liberal policy: ANY manifest bump is enough. Workspace-wide
227
- // discipline is a v0.6.4+ refinement if needed.
228
- expect(r.block).toBe(false);
229
- });
230
- });
231
- // ---------------------------------------------------------------------
232
- // Emergency override
233
- // ---------------------------------------------------------------------
234
- describe("evaluateVersioningGate — emergency override", () => {
235
- it("ALLOWS with bypass warning when OPENSQUID_SKIP_VERSION_GATE=1", async () => {
236
- process.env.OPENSQUID_SKIP_VERSION_GATE = "1";
237
- expect(checkOverrideEnv()).toBe(true);
238
- await seedInitial();
239
- await writeFile("Cargo.toml", 'version = "0.1.0"\n');
240
- await writeFile("src/lib.rs", "// initial\n");
241
- stage("Cargo.toml");
242
- stage("src/lib.rs");
243
- commit("initial");
244
- await writeFile("src/lib.rs", "// edited\n");
245
- stage("src/lib.rs");
246
- const r = await evaluateVersioningGate({ cwd: repoDir });
247
- expect(r.block).toBe(false);
248
- expect(r.stderr).toContain("BYPASSED");
249
- });
250
- it("respects the env var only when EXACTLY '1'", async () => {
251
- process.env.OPENSQUID_SKIP_VERSION_GATE = "true";
252
- expect(checkOverrideEnv()).toBe(false);
253
- process.env.OPENSQUID_SKIP_VERSION_GATE = "1";
254
- expect(checkOverrideEnv()).toBe(true);
255
- });
256
- });
257
- // ---------------------------------------------------------------------
258
- // 0.7.23 / D5 — multi-patch catch-up jump detection
259
- // ---------------------------------------------------------------------
260
- describe("parseVersionJumpFromDiff", () => {
261
- it("parses a Cargo.toml patch bump", () => {
262
- const diff = `--- a/Cargo.toml
263
- +++ b/Cargo.toml
264
- @@ -1 +1 @@
265
- -version = "0.7.10"
266
- +version = "0.7.11"
267
- `;
268
- expect(parseVersionJumpFromDiff(diff)).toEqual({ from: "0.7.10", to: "0.7.11" });
269
- });
270
- it("parses a package.json patch bump", () => {
271
- const diff = `--- a/package.json
272
- +++ b/package.json
273
- @@ -2 +2 @@
274
- - "version": "0.7.10",
275
- + "version": "0.7.11",
276
- `;
277
- expect(parseVersionJumpFromDiff(diff)).toEqual({ from: "0.7.10", to: "0.7.11" });
278
- });
279
- it("parses a multi-patch jump (D5 incident shape)", () => {
280
- const diff = `--- a/package.json
281
- +++ b/package.json
282
- - "version": "0.7.10",
283
- + "version": "0.7.14",
284
- `;
285
- expect(parseVersionJumpFromDiff(diff)).toEqual({ from: "0.7.10", to: "0.7.14" });
286
- });
287
- it("returns null when no version line is touched", () => {
288
- const diff = `--- a/package.json
289
- +++ b/package.json
290
- + "description": "updated text",
291
- - "description": "old text",
292
- `;
293
- expect(parseVersionJumpFromDiff(diff)).toBeNull();
294
- });
295
- });
296
- describe("isMultiPatchJump", () => {
297
- it("flags 0.7.10 → 0.7.14 (D5 catch-up)", () => {
298
- expect(isMultiPatchJump({ from: "0.7.10", to: "0.7.14" })).toBe(true);
299
- });
300
- it("flags 0.7.10 → 0.7.12 (2-step jump)", () => {
301
- expect(isMultiPatchJump({ from: "0.7.10", to: "0.7.12" })).toBe(true);
302
- });
303
- it("does NOT flag a normal 0.7.10 → 0.7.11 patch bump", () => {
304
- expect(isMultiPatchJump({ from: "0.7.10", to: "0.7.11" })).toBe(false);
305
- });
306
- it("does NOT flag a minor bump (0.7.x → 0.8.0)", () => {
307
- // Minor/major bumps are user-authorized; not the agent's drift to warn about.
308
- expect(isMultiPatchJump({ from: "0.7.20", to: "0.8.0" })).toBe(false);
309
- });
310
- it("does NOT flag a first-time version (from empty)", () => {
311
- expect(isMultiPatchJump({ from: "", to: "0.1.0" })).toBe(false);
312
- });
313
- it("does NOT flag non-SemVer strings", () => {
314
- expect(isMultiPatchJump({ from: "wip", to: "wip-2" })).toBe(false);
315
- });
316
- });
317
- describe("evaluateVersioningGate — multi-patch warning end-to-end (D5)", () => {
318
- it("ALLOWS but WARNS when commit bumps version by >1 patch", async () => {
319
- await seedInitial();
320
- await writeFile("Cargo.toml", 'version = "0.7.10"\n');
321
- await writeFile("src/lib.rs", "// initial\n");
322
- stage("Cargo.toml");
323
- stage("src/lib.rs");
324
- commit("initial");
325
- // Now jump 0.7.10 → 0.7.14 in one commit alongside a src change
326
- await writeFile("Cargo.toml", 'version = "0.7.14"\n');
327
- await writeFile("src/lib.rs", "// changed\n");
328
- stage("Cargo.toml");
329
- stage("src/lib.rs");
330
- const r = await evaluateVersioningGate({ cwd: repoDir });
331
- expect(r.block).toBe(false);
332
- expect(r.stderr).toContain("catch-up bump detected");
333
- expect(r.stderr).toContain("0.7.10 → 0.7.14");
334
- expect(r.stderr).toContain("Drift D5");
335
- });
336
- it("does NOT warn on a clean +1 patch bump", async () => {
337
- await seedInitial();
338
- await writeFile("Cargo.toml", 'version = "0.7.10"\n');
339
- await writeFile("src/lib.rs", "// initial\n");
340
- stage("Cargo.toml");
341
- stage("src/lib.rs");
342
- commit("initial");
343
- await writeFile("Cargo.toml", 'version = "0.7.11"\n');
344
- await writeFile("src/lib.rs", "// changed\n");
345
- stage("Cargo.toml");
346
- stage("src/lib.rs");
347
- const r = await evaluateVersioningGate({ cwd: repoDir });
348
- expect(r.block).toBe(false);
349
- expect(r.stderr).toBe("");
350
- });
351
- });
352
- // ---------------------------------------------------------------------
353
- // Fail-open invariant
354
- // ---------------------------------------------------------------------
355
- describe("evaluateVersioningGate — fail-open invariant", () => {
356
- it("ALLOWS with stderr warning when git is not available (non-repo cwd)", async () => {
357
- const notARepo = path.join(os.tmpdir(), `not-a-repo-${crypto.randomUUID()}`);
358
- await fs.mkdir(notARepo, { recursive: true });
359
- try {
360
- const r = await evaluateVersioningGate({ cwd: notARepo });
361
- expect(r.block).toBe(false);
362
- expect(r.stderr).toContain("git diff failed");
363
- }
364
- finally {
365
- await fs.rm(notARepo, { recursive: true, force: true });
366
- }
367
- });
368
- });