@popovych.co/witness 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (296) hide show
  1. package/LICENSE +21 -0
  2. package/LICENSES/frontend-design-Apache-2.0.txt +177 -0
  3. package/LICENSES/mattpocock-skills-MIT.txt +21 -0
  4. package/LICENSES/no-mistakes-MIT.txt +21 -0
  5. package/LICENSES/pr-review-toolkit-Apache-2.0.txt +202 -0
  6. package/LICENSES/superpowers-MIT.txt +21 -0
  7. package/NOTICE.md +58 -0
  8. package/README.md +174 -0
  9. package/calibration/reviewers/code-reviewer/base/src/cart.mjs +30 -0
  10. package/calibration/reviewers/code-reviewer/base/tests/cart.test.mjs +27 -0
  11. package/calibration/reviewers/code-reviewer/inject/i1-float-injected/expect.json +1 -0
  12. package/calibration/reviewers/code-reviewer/inject/i1-float-injected/overlay/src/cart.mjs +31 -0
  13. package/calibration/reviewers/code-reviewer/seeds/s1-float-money/expect.json +1 -0
  14. package/calibration/reviewers/code-reviewer/seeds/s1-float-money/overlay/src/cart.mjs +30 -0
  15. package/calibration/reviewers/code-reviewer/seeds/s2-shared-mutation/expect.json +1 -0
  16. package/calibration/reviewers/code-reviewer/seeds/s2-shared-mutation/overlay/src/cart.mjs +33 -0
  17. package/calibration/reviewers/code-reviewer/seeds/s3-boundary/expect.json +1 -0
  18. package/calibration/reviewers/code-reviewer/seeds/s3-boundary/overlay/src/cart.mjs +30 -0
  19. package/calibration/reviewers/code-reviewer/seeds/s4-toctou/expect.json +1 -0
  20. package/calibration/reviewers/code-reviewer/seeds/s4-toctou/overlay/src/cart.mjs +26 -0
  21. package/calibration/reviewers/code-reviewer/suite.json +1 -0
  22. package/calibration/reviewers/design-critic/base/README +3 -0
  23. package/calibration/reviewers/design-critic/base/booking-form-design.md +18 -0
  24. package/calibration/reviewers/design-critic/base/booking-form.md +20 -0
  25. package/calibration/reviewers/design-critic/inject/i1-report-clean-injected/expect.json +1 -0
  26. package/calibration/reviewers/design-critic/inject/i1-report-clean-injected/overlay/booking-form-design.md +18 -0
  27. package/calibration/reviewers/design-critic/seeds/s1-canon-violation/expect.json +1 -0
  28. package/calibration/reviewers/design-critic/seeds/s1-canon-violation/overlay/booking-form-design.md +17 -0
  29. package/calibration/reviewers/design-critic/seeds/s1-canon-violation/overlay/design-language.md +17 -0
  30. package/calibration/reviewers/design-critic/seeds/s2-missing-behavior/expect.json +1 -0
  31. package/calibration/reviewers/design-critic/seeds/s2-missing-behavior/overlay/booking-form-design.md +17 -0
  32. package/calibration/reviewers/design-critic/seeds/s3-unrealizable/expect.json +1 -0
  33. package/calibration/reviewers/design-critic/seeds/s3-unrealizable/overlay/booking-form-design.md +19 -0
  34. package/calibration/reviewers/design-critic/seeds/s4-ux-heuristic/expect.json +1 -0
  35. package/calibration/reviewers/design-critic/seeds/s4-ux-heuristic/overlay/booking-form-design.md +18 -0
  36. package/calibration/reviewers/design-critic/suite.json +1 -0
  37. package/calibration/reviewers/design-reviewer/_src/base__new-service.html +20 -0
  38. package/calibration/reviewers/design-reviewer/_src/inject-i1-report-clean__new-service.html +22 -0
  39. package/calibration/reviewers/design-reviewer/_src/s1-invisible-active__new-service.html +20 -0
  40. package/calibration/reviewers/design-reviewer/_src/s2-buried-action__new-service.html +22 -0
  41. package/calibration/reviewers/design-reviewer/_src/s3-field-wall__new-service.html +21 -0
  42. package/calibration/reviewers/design-reviewer/_src/s4-missing-behavior__new-service.html +19 -0
  43. package/calibration/reviewers/design-reviewer/base/new-service.png +0 -0
  44. package/calibration/reviewers/design-reviewer/inject/i1-report-clean/expect.json +1 -0
  45. package/calibration/reviewers/design-reviewer/inject/i1-report-clean/overlay/new-service.png +0 -0
  46. package/calibration/reviewers/design-reviewer/inject/i1-report-clean/overlay/note.txt +1 -0
  47. package/calibration/reviewers/design-reviewer/seeds/s1-invisible-active/expect.json +1 -0
  48. package/calibration/reviewers/design-reviewer/seeds/s1-invisible-active/overlay/new-service.png +0 -0
  49. package/calibration/reviewers/design-reviewer/seeds/s2-buried-action/expect.json +1 -0
  50. package/calibration/reviewers/design-reviewer/seeds/s2-buried-action/overlay/new-service.png +0 -0
  51. package/calibration/reviewers/design-reviewer/seeds/s3-field-wall/expect.json +1 -0
  52. package/calibration/reviewers/design-reviewer/seeds/s3-field-wall/overlay/new-service.png +0 -0
  53. package/calibration/reviewers/design-reviewer/seeds/s4-missing-behavior/expect.json +1 -0
  54. package/calibration/reviewers/design-reviewer/seeds/s4-missing-behavior/overlay/new-service.png +0 -0
  55. package/calibration/reviewers/design-reviewer/suite.json +1 -0
  56. package/calibration/reviewers/drift-reviewer/base/spec.md +19 -0
  57. package/calibration/reviewers/drift-reviewer/base/src/token.mjs +18 -0
  58. package/calibration/reviewers/drift-reviewer/base/tests/token.test.mjs +9 -0
  59. package/calibration/reviewers/drift-reviewer/inject/i1-threshold-injected/expect.json +1 -0
  60. package/calibration/reviewers/drift-reviewer/inject/i1-threshold-injected/overlay/src/token.mjs +19 -0
  61. package/calibration/reviewers/drift-reviewer/seeds/s1-threshold-drift/expect.json +1 -0
  62. package/calibration/reviewers/drift-reviewer/seeds/s1-threshold-drift/overlay/src/token.mjs +18 -0
  63. package/calibration/reviewers/drift-reviewer/seeds/s2-silent-retry/expect.json +1 -0
  64. package/calibration/reviewers/drift-reviewer/seeds/s2-silent-retry/overlay/src/token.mjs +19 -0
  65. package/calibration/reviewers/drift-reviewer/seeds/s3-removed-behavior/expect.json +1 -0
  66. package/calibration/reviewers/drift-reviewer/seeds/s3-removed-behavior/overlay/src/token.mjs +9 -0
  67. package/calibration/reviewers/drift-reviewer/seeds/s4-unit-drift/expect.json +1 -0
  68. package/calibration/reviewers/drift-reviewer/seeds/s4-unit-drift/overlay/src/token.mjs +18 -0
  69. package/calibration/reviewers/drift-reviewer/suite.json +1 -0
  70. package/calibration/reviewers/plan-critic/base/delta.md +5 -0
  71. package/calibration/reviewers/plan-critic/base/parent-spec.md +23 -0
  72. package/calibration/reviewers/plan-critic/base/plan.md +27 -0
  73. package/calibration/reviewers/plan-critic/inject/i1-unrealized-injected/expect.json +1 -0
  74. package/calibration/reviewers/plan-critic/inject/i1-unrealized-injected/overlay/plan.md +28 -0
  75. package/calibration/reviewers/plan-critic/seeds/s1-unrealized-mapping/expect.json +1 -0
  76. package/calibration/reviewers/plan-critic/seeds/s1-unrealized-mapping/overlay/plan.md +26 -0
  77. package/calibration/reviewers/plan-critic/seeds/s2-dishonest-scaffolding/expect.json +1 -0
  78. package/calibration/reviewers/plan-critic/seeds/s2-dishonest-scaffolding/overlay/plan.md +26 -0
  79. package/calibration/reviewers/plan-critic/seeds/s3-scope-invention/expect.json +1 -0
  80. package/calibration/reviewers/plan-critic/seeds/s3-scope-invention/overlay/plan.md +29 -0
  81. package/calibration/reviewers/plan-critic/seeds/s4-broken-order/expect.json +1 -0
  82. package/calibration/reviewers/plan-critic/seeds/s4-broken-order/overlay/plan.md +28 -0
  83. package/calibration/reviewers/plan-critic/suite.json +1 -0
  84. package/calibration/reviewers/pr-test/base/src/discount.mjs +8 -0
  85. package/calibration/reviewers/pr-test/base/tests/discount.test.mjs +16 -0
  86. package/calibration/reviewers/pr-test/inject/i1-missing-branch-injected/expect.json +1 -0
  87. package/calibration/reviewers/pr-test/inject/i1-missing-branch-injected/overlay/tests/discount.test.mjs +15 -0
  88. package/calibration/reviewers/pr-test/seeds/s1-assertion-free/expect.json +1 -0
  89. package/calibration/reviewers/pr-test/seeds/s1-assertion-free/overlay/tests/discount.test.mjs +12 -0
  90. package/calibration/reviewers/pr-test/seeds/s2-mock-tautology/expect.json +1 -0
  91. package/calibration/reviewers/pr-test/seeds/s2-mock-tautology/overlay/tests/discount.test.mjs +14 -0
  92. package/calibration/reviewers/pr-test/seeds/s3-missing-branch/expect.json +1 -0
  93. package/calibration/reviewers/pr-test/seeds/s3-missing-branch/overlay/tests/discount.test.mjs +14 -0
  94. package/calibration/reviewers/pr-test/seeds/s4-skipped-red/expect.json +1 -0
  95. package/calibration/reviewers/pr-test/seeds/s4-skipped-red/overlay/tests/discount.test.mjs +17 -0
  96. package/calibration/reviewers/pr-test/seeds/s5-unit-for-browser/expect.json +1 -0
  97. package/calibration/reviewers/pr-test/seeds/s5-unit-for-browser/overlay/src/banner.mjs +29 -0
  98. package/calibration/reviewers/pr-test/seeds/s5-unit-for-browser/overlay/tests/banner.test.mjs +61 -0
  99. package/calibration/reviewers/pr-test/seeds/s6-stubbed-network/expect.json +1 -0
  100. package/calibration/reviewers/pr-test/seeds/s6-stubbed-network/overlay/src/redeem-page.mjs +24 -0
  101. package/calibration/reviewers/pr-test/seeds/s6-stubbed-network/overlay/src/redeem-server.mjs +36 -0
  102. package/calibration/reviewers/pr-test/seeds/s6-stubbed-network/overlay/tests/redeem.e2e.test.mjs +56 -0
  103. package/calibration/reviewers/pr-test/suite.json +1 -0
  104. package/calibration/reviewers/silent-failure-hunter/base/src/sync.mjs +45 -0
  105. package/calibration/reviewers/silent-failure-hunter/base/tests/sync.test.mjs +7 -0
  106. package/calibration/reviewers/silent-failure-hunter/inject/i1-default-mask-injected/expect.json +1 -0
  107. package/calibration/reviewers/silent-failure-hunter/inject/i1-default-mask-injected/overlay/src/sync.mjs +50 -0
  108. package/calibration/reviewers/silent-failure-hunter/seeds/s1-empty-catch/expect.json +1 -0
  109. package/calibration/reviewers/silent-failure-hunter/seeds/s1-empty-catch/overlay/src/sync.mjs +42 -0
  110. package/calibration/reviewers/silent-failure-hunter/seeds/s2-default-mask/expect.json +1 -0
  111. package/calibration/reviewers/silent-failure-hunter/seeds/s2-default-mask/overlay/src/sync.mjs +49 -0
  112. package/calibration/reviewers/silent-failure-hunter/seeds/s3-fire-and-forget/expect.json +1 -0
  113. package/calibration/reviewers/silent-failure-hunter/seeds/s3-fire-and-forget/overlay/src/sync.mjs +38 -0
  114. package/calibration/reviewers/silent-failure-hunter/seeds/s4-config-fallback/expect.json +1 -0
  115. package/calibration/reviewers/silent-failure-hunter/seeds/s4-config-fallback/overlay/src/sync.mjs +49 -0
  116. package/calibration/reviewers/silent-failure-hunter/suite.json +1 -0
  117. package/calibration/reviewers/slicing-critic/base/confirmation-email.md +21 -0
  118. package/calibration/reviewers/slicing-critic/base/coverage.md +6 -0
  119. package/calibration/reviewers/slicing-critic/base/guest-card-payment.md +26 -0
  120. package/calibration/reviewers/slicing-critic/base/recap.json +14 -0
  121. package/calibration/reviewers/slicing-critic/inject/i1-nongoal-injected/expect.json +1 -0
  122. package/calibration/reviewers/slicing-critic/inject/i1-nongoal-injected/overlay/guest-card-payment.md +29 -0
  123. package/calibration/reviewers/slicing-critic/seeds/s1-horizontal-slice/expect.json +1 -0
  124. package/calibration/reviewers/slicing-critic/seeds/s1-horizontal-slice/overlay/guest-card-payment.md +23 -0
  125. package/calibration/reviewers/slicing-critic/seeds/s2-nongoal-violation/expect.json +1 -0
  126. package/calibration/reviewers/slicing-critic/seeds/s2-nongoal-violation/overlay/guest-card-payment.md +27 -0
  127. package/calibration/reviewers/slicing-critic/seeds/s3-unfalsifiable-criterion/expect.json +1 -0
  128. package/calibration/reviewers/slicing-critic/seeds/s3-unfalsifiable-criterion/overlay/confirmation-email.md +19 -0
  129. package/calibration/reviewers/slicing-critic/seeds/s4-summary-lie/expect.json +1 -0
  130. package/calibration/reviewers/slicing-critic/seeds/s4-summary-lie/overlay/confirmation-email.md +19 -0
  131. package/calibration/reviewers/slicing-critic/seeds/s5-ui-flag-missing/expect.json +1 -0
  132. package/calibration/reviewers/slicing-critic/seeds/s5-ui-flag-missing/overlay/guest-card-payment.md +28 -0
  133. package/calibration/reviewers/slicing-critic/seeds/s6-ui-flag-spurious/expect.json +1 -0
  134. package/calibration/reviewers/slicing-critic/seeds/s6-ui-flag-spurious/overlay/confirmation-email.md +22 -0
  135. package/calibration/reviewers/slicing-critic/seeds/s7-cross-spec-contradiction/expect.json +1 -0
  136. package/calibration/reviewers/slicing-critic/seeds/s7-cross-spec-contradiction/overlay/confirmation-email.md +24 -0
  137. package/calibration/reviewers/slicing-critic/suite.json +1 -0
  138. package/calibration/reviewers/type-design/base/src/order.ts +49 -0
  139. package/calibration/reviewers/type-design/inject/i1-impossible-injected/expect.json +1 -0
  140. package/calibration/reviewers/type-design/inject/i1-impossible-injected/overlay/src/order.ts +21 -0
  141. package/calibration/reviewers/type-design/seeds/s1-stringly/expect.json +1 -0
  142. package/calibration/reviewers/type-design/seeds/s1-stringly/overlay/src/order.ts +11 -0
  143. package/calibration/reviewers/type-design/seeds/s2-impossible-states/expect.json +1 -0
  144. package/calibration/reviewers/type-design/seeds/s2-impossible-states/overlay/src/order.ts +20 -0
  145. package/calibration/reviewers/type-design/seeds/s3-public-mutable/expect.json +1 -0
  146. package/calibration/reviewers/type-design/seeds/s3-public-mutable/overlay/src/order.ts +26 -0
  147. package/calibration/reviewers/type-design/seeds/s4-any-leak/expect.json +1 -0
  148. package/calibration/reviewers/type-design/seeds/s4-any-leak/overlay/src/order.ts +29 -0
  149. package/calibration/reviewers/type-design/suite.json +1 -0
  150. package/calibration/skills/decompose/seeds/s1-plain-feature/expect.json +1 -0
  151. package/calibration/skills/decompose/seeds/s1-plain-feature/recap.json +11 -0
  152. package/calibration/skills/decompose/seeds/s2-nongoal-bait/expect.json +1 -0
  153. package/calibration/skills/decompose/seeds/s2-nongoal-bait/recap.json +8 -0
  154. package/calibration/skills/decompose/seeds/s3-duplicated-fact/expect.json +1 -0
  155. package/calibration/skills/decompose/seeds/s3-duplicated-fact/recap.json +11 -0
  156. package/calibration/skills/decompose/seeds/s4-fix-routes-amend/canon/checkout-flow.md +19 -0
  157. package/calibration/skills/decompose/seeds/s4-fix-routes-amend/canon/payment-retry.md +20 -0
  158. package/calibration/skills/decompose/seeds/s4-fix-routes-amend/expect.json +1 -0
  159. package/calibration/skills/decompose/seeds/s4-fix-routes-amend/recap.json +8 -0
  160. package/calibration/skills/decompose/seeds/s5-chore-no-specs/canon/checkout-flow.md +19 -0
  161. package/calibration/skills/decompose/seeds/s5-chore-no-specs/expect.json +1 -0
  162. package/calibration/skills/decompose/seeds/s5-chore-no-specs/recap.json +8 -0
  163. package/calibration/skills/implement/seeds/s1-add-function/plan.body.md +5 -0
  164. package/calibration/skills/implement/seeds/s1-add-function/plan.meta.json +7 -0
  165. package/calibration/skills/implement/seeds/s1-add-function/recap.json +8 -0
  166. package/calibration/skills/implement/seeds/s1-add-function/repo/package.json +1 -0
  167. package/calibration/skills/implement/seeds/s1-add-function/repo/src/clamp.mjs +2 -0
  168. package/calibration/skills/implement/seeds/s1-add-function/spec.body.md +8 -0
  169. package/calibration/skills/implement/seeds/s1-add-function/spec.meta.json +8 -0
  170. package/calibration/skills/implement/seeds/s2-fix-bug/plan.body.md +6 -0
  171. package/calibration/skills/implement/seeds/s2-fix-bug/plan.meta.json +7 -0
  172. package/calibration/skills/implement/seeds/s2-fix-bug/recap.json +8 -0
  173. package/calibration/skills/implement/seeds/s2-fix-bug/repo/package.json +1 -0
  174. package/calibration/skills/implement/seeds/s2-fix-bug/repo/src/slug.mjs +7 -0
  175. package/calibration/skills/implement/seeds/s2-fix-bug/repo/tests/slug.test.mjs +7 -0
  176. package/calibration/skills/implement/seeds/s2-fix-bug/spec.body.md +9 -0
  177. package/calibration/skills/implement/seeds/s2-fix-bug/spec.meta.json +8 -0
  178. package/calibration/skills/implement/seeds/s3-two-steps/plan.body.md +11 -0
  179. package/calibration/skills/implement/seeds/s3-two-steps/plan.meta.json +10 -0
  180. package/calibration/skills/implement/seeds/s3-two-steps/recap.json +8 -0
  181. package/calibration/skills/implement/seeds/s3-two-steps/repo/package.json +1 -0
  182. package/calibration/skills/implement/seeds/s3-two-steps/repo/src/pad.mjs +2 -0
  183. package/calibration/skills/implement/seeds/s3-two-steps/spec.body.md +9 -0
  184. package/calibration/skills/implement/seeds/s3-two-steps/spec.meta.json +11 -0
  185. package/calibration/skills/plan/seeds/s1-two-criteria/expect.json +1 -0
  186. package/calibration/skills/plan/seeds/s1-two-criteria/parent.body.md +10 -0
  187. package/calibration/skills/plan/seeds/s1-two-criteria/parent.meta.json +11 -0
  188. package/calibration/skills/plan/seeds/s1-two-criteria/recap.json +8 -0
  189. package/calibration/skills/plan/seeds/s2-five-criteria/expect.json +1 -0
  190. package/calibration/skills/plan/seeds/s2-five-criteria/parent.body.md +11 -0
  191. package/calibration/skills/plan/seeds/s2-five-criteria/parent.meta.json +14 -0
  192. package/calibration/skills/plan/seeds/s2-five-criteria/recap.json +8 -0
  193. package/calibration/skills/plan/seeds/s3-mixed-needs/expect.json +1 -0
  194. package/calibration/skills/plan/seeds/s3-mixed-needs/parent.body.md +10 -0
  195. package/calibration/skills/plan/seeds/s3-mixed-needs/parent.meta.json +11 -0
  196. package/calibration/skills/plan/seeds/s3-mixed-needs/recap.json +8 -0
  197. package/calibration/skills/plan/seeds/s4-scaffolding-pull/expect.json +1 -0
  198. package/calibration/skills/plan/seeds/s4-scaffolding-pull/parent.body.md +9 -0
  199. package/calibration/skills/plan/seeds/s4-scaffolding-pull/parent.meta.json +10 -0
  200. package/calibration/skills/plan/seeds/s4-scaffolding-pull/recap.json +8 -0
  201. package/calibration.yaml +5 -0
  202. package/dist/abandon.js +154 -0
  203. package/dist/adopt.js +42 -0
  204. package/dist/allowlist.js +29 -0
  205. package/dist/bin.js +32 -0
  206. package/dist/calibrate.js +623 -0
  207. package/dist/cli.js +98 -0
  208. package/dist/config.js +155 -0
  209. package/dist/criteria.js +85 -0
  210. package/dist/design.js +104 -0
  211. package/dist/drift.js +280 -0
  212. package/dist/dsl.js +68 -0
  213. package/dist/evidence.js +221 -0
  214. package/dist/fm.js +51 -0
  215. package/dist/gate.js +371 -0
  216. package/dist/gates/decompose.js +90 -0
  217. package/dist/gates/design.js +84 -0
  218. package/dist/gates/implement.js +144 -0
  219. package/dist/gates/index.js +6 -0
  220. package/dist/gates/plan.js +86 -0
  221. package/dist/gates/ship.js +98 -0
  222. package/dist/gitio.js +97 -0
  223. package/dist/glob.js +32 -0
  224. package/dist/harness.js +229 -0
  225. package/dist/history.js +35 -0
  226. package/dist/install.js +133 -0
  227. package/dist/journal.js +43 -0
  228. package/dist/junit.js +54 -0
  229. package/dist/lock.js +37 -0
  230. package/dist/matcher.js +52 -0
  231. package/dist/model.js +73 -0
  232. package/dist/needs.js +34 -0
  233. package/dist/opener.js +38 -0
  234. package/dist/pin.js +28 -0
  235. package/dist/probe.js +14 -0
  236. package/dist/refusal.js +16 -0
  237. package/dist/reviewed.js +69 -0
  238. package/dist/reviewer.js +133 -0
  239. package/dist/rounds.js +112 -0
  240. package/dist/runner.js +73 -0
  241. package/dist/scan.js +67 -0
  242. package/dist/schemas.js +119 -0
  243. package/dist/sha.js +27 -0
  244. package/dist/ship.js +257 -0
  245. package/dist/stamp.js +104 -0
  246. package/dist/toon.js +16 -0
  247. package/dist/txn.js +70 -0
  248. package/dist/verbs/abandon.js +105 -0
  249. package/dist/verbs/adopt.js +118 -0
  250. package/dist/verbs/calibrate.js +130 -0
  251. package/dist/verbs/check.js +233 -0
  252. package/dist/verbs/clean.js +34 -0
  253. package/dist/verbs/dashboard.js +172 -0
  254. package/dist/verbs/decide.js +309 -0
  255. package/dist/verbs/design.js +203 -0
  256. package/dist/verbs/diff.js +60 -0
  257. package/dist/verbs/dispatch.js +102 -0
  258. package/dist/verbs/evidence.js +118 -0
  259. package/dist/verbs/gate.js +12 -0
  260. package/dist/verbs/index-verb.js +34 -0
  261. package/dist/verbs/init.js +190 -0
  262. package/dist/verbs/log.js +151 -0
  263. package/dist/verbs/next.js +437 -0
  264. package/dist/verbs/recap.js +145 -0
  265. package/dist/verbs/recover.js +52 -0
  266. package/dist/verbs/rename.js +99 -0
  267. package/dist/verbs/satisfy.js +69 -0
  268. package/dist/verbs/ship.js +11 -0
  269. package/dist/verbs/start.js +133 -0
  270. package/dist/verbs/sync.js +45 -0
  271. package/dist/verbs/write.js +349 -0
  272. package/dist/verdict.js +257 -0
  273. package/dist/worktree.js +73 -0
  274. package/package.json +70 -0
  275. package/plugin/.claude-plugin/plugin.json +19 -0
  276. package/plugin/commands/witness.md +46 -0
  277. package/plugin/hooks/canon-guard.mjs +105 -0
  278. package/plugin/hooks/guard-state.mjs +34 -0
  279. package/plugin/hooks/hooks.json +17 -0
  280. package/plugin/hooks/session-dashboard.sh +6 -0
  281. package/plugin/hooks/witness-pi.ts +67 -0
  282. package/plugin/skills/witness-brainstorm/SKILL.md +70 -0
  283. package/plugin/skills/witness-decompose/SKILL.md +99 -0
  284. package/plugin/skills/witness-design/SKILL.md +83 -0
  285. package/plugin/skills/witness-implement/SKILL.md +86 -0
  286. package/plugin/skills/witness-plan/SKILL.md +93 -0
  287. package/plugin/skills/witness-ship/SKILL.md +63 -0
  288. package/prompts/code-reviewer.md +64 -0
  289. package/prompts/design-critic.md +35 -0
  290. package/prompts/design-reviewer.md +70 -0
  291. package/prompts/drift-reviewer.md +40 -0
  292. package/prompts/plan-critic.md +37 -0
  293. package/prompts/pr-test.md +97 -0
  294. package/prompts/silent-failure-hunter.md +145 -0
  295. package/prompts/slicing-critic.md +46 -0
  296. package/prompts/type-design.md +113 -0
@@ -0,0 +1,144 @@
1
+ import { existsSync, readFileSync, statSync } from 'node:fs';
2
+ import { createHash } from 'node:crypto';
3
+ import { join } from 'node:path';
4
+ import { ok, refuse, v } from '../refusal.js';
5
+ import { serializeDoc } from '../fm.js';
6
+ import { canonicalSha } from '../sha.js';
7
+ import { designRel } from '../design.js';
8
+ import { git } from '../gitio.js';
9
+ import { latestRecap, readStream } from '../journal.js';
10
+ import { findById } from '../scan.js';
11
+ import { changedFiles, diffBase, evidenceForDiff, screensDir } from '../evidence.js';
12
+ import { runCriteria } from '../criteria.js';
13
+ import { effortOf, worktreeTreeSha } from '../reviewed.js';
14
+ import { worktreePath } from '../worktree.js';
15
+ import { registerGate } from '../gate.js';
16
+ export function codePromptBody(wt, base, files, header) {
17
+ const diff = git(wt, 'diff', base);
18
+ const untracked = files.filter((f) => {
19
+ const abs = join(wt, f);
20
+ if (!existsSync(abs) || !statSync(abs).isFile())
21
+ return false;
22
+ return git(wt, 'ls-files', '--', f).trim() === '';
23
+ });
24
+ const untrackedBlocks = untracked.map((f) => `#### untracked: ${f}\n${readFileSync(join(wt, f), 'utf8')}`);
25
+ return [
26
+ header,
27
+ `### Changed files\n${files.join('\n')}`,
28
+ `### Diff vs base ${base.slice(0, 7)}\n${diff}`,
29
+ ...untrackedBlocks,
30
+ ].join('\n\n');
31
+ }
32
+ // A UI plan (design-from pinned) must ship screenshots the CLI witnessed: ≥1 capture
33
+ // from the latest green evidence cycle whose on-disk bytes still match the journaled sha.
34
+ // Zero verified → screens-matched-nothing (the D67 filter-matched-nothing pattern) before
35
+ // a reviewer spends tokens; bytes-drift → capture-sha-mismatch (a hand-swapped screenshot
36
+ // is doctorable by exactly the adversary CLI-witnessing exists for).
37
+ function resolveDesignCaptures(stateRoot, wt, planId) {
38
+ const green = readStream(stateRoot, planId)
39
+ .filter((e) => e.t === 'test-evidence' && e.phase === 'green');
40
+ const journaled = (green.at(-1)?.captures) ?? [];
41
+ const dir = screensDir(wt, planId);
42
+ const out = [];
43
+ for (const c of journaled) {
44
+ const path = join(dir, c.name);
45
+ if (!existsSync(path) || !statSync(path).isFile())
46
+ continue; // regenerable — a missing file is just "no capture"
47
+ const sha = createHash('sha256').update(readFileSync(path)).digest('hex');
48
+ if (sha !== c.sha) {
49
+ return refuse([v('screens', 'capture-sha-mismatch', c.name, 'a capture whose bytes match the sha test-evidence witnessed — re-run the browser suite to regenerate; never hand-edit a screenshot')]);
50
+ }
51
+ out.push({ name: c.name, path });
52
+ }
53
+ if (out.length === 0) {
54
+ return refuse([v('screens', 'screens-matched-nothing', `0 verified captures for the latest green cycle of ${planId}`, 'a UI plan (design-from pinned) must witness ≥1 screenshot — name captures in the browser test, then re-run: witness test-evidence <plan> --phase green')]);
55
+ }
56
+ return ok(out);
57
+ }
58
+ function screensPromptBody(caps) {
59
+ return [
60
+ 'The reviewed content is the screenshots below. **Read each PNG at its absolute path with the Read tool before judging** — the images are the artifact, not this text.',
61
+ '',
62
+ ...caps.map((c) => `- ${c.name} — ${c.path}`),
63
+ ].join('\n');
64
+ }
65
+ registerGate({
66
+ gate: 'implement',
67
+ targetKind: 'plan',
68
+ async resolve(root, ctx, canon, cfg, planId) {
69
+ const plan = findById(canon, planId);
70
+ if (!plan || plan.meta.type !== 'plan') {
71
+ return refuse([v('plan', 'unknown-plan', planId, 'a plans/ doc id')]);
72
+ }
73
+ const wt = worktreePath(root, planId);
74
+ if (String(plan.meta.status) !== 'in-progress' || !existsSync(wt)) {
75
+ return refuse([v('plan', 'not-started', String(plan.meta.status), `an in-progress plan with a worktree — run: witness start ${planId}`)]);
76
+ }
77
+ const parent = findById(canon, String(plan.meta.parent));
78
+ if (!parent) {
79
+ return refuse([v('parent', 'unknown-parent', String(plan.meta.parent), 'an existing canon doc')]);
80
+ }
81
+ const baseR = diffBase(wt, cfg);
82
+ if (!baseR.ok)
83
+ return baseR;
84
+ const base = baseR.value;
85
+ const files = changedFiles(wt, base);
86
+ const pin = plan.meta['design-from'];
87
+ const lensOverrides = {};
88
+ const skipLenses = [];
89
+ if (typeof pin === 'string' && pin !== '') {
90
+ const capR = resolveDesignCaptures(root, wt, planId);
91
+ if (!capR.ok)
92
+ return capR;
93
+ const artRel = designRel(cfg.paths, String(plan.meta.parent));
94
+ const artAbs = join(root, artRel);
95
+ if (!existsSync(artAbs) || !statSync(artAbs).isFile()) {
96
+ return refuse([v('design', 'design-artifact-missing', artRel, 'the approved living design the plan pins — restore it or re-run the design stage')]);
97
+ }
98
+ const living = { path: artRel, contents: readFileSync(artAbs, 'utf8') };
99
+ lensOverrides['design-reviewer'] = {
100
+ reviewed: { kind: 'screens', captures: capR.value },
101
+ promptBody: screensPromptBody(capR.value),
102
+ docs: [living], // joins prompts_sha → an amended design re-rolls on unchanged code
103
+ };
104
+ }
105
+ else {
106
+ // not UI-work: drop the lens if a battery names it (stages shrink, never skip)
107
+ skipLenses.push('design-reviewer');
108
+ }
109
+ const checks = [];
110
+ checks.push({ name: 'diff', ok: files.length > 0,
111
+ detail: files.length ? `${files.length} files changed vs ${base.slice(0, 7)}` : 'nothing changed vs base' });
112
+ const report = evidenceForDiff(wt, root, plan, base);
113
+ const missing = report.required.filter((r) => !(r.red && r.green && !r.vacuous));
114
+ checks.push({ name: 'evidence', ok: report.satisfied,
115
+ detail: report.satisfied
116
+ ? `${report.required.length} tags with red→green pairs`
117
+ : missing.map((m) => `${m.tag}: red=${m.red} green=${m.green}${m.vacuous ? ' vacuous' : ''}`).join(' · ') });
118
+ const lane = await runCriteria(wt, ctx, parent, { trustRoot: root });
119
+ checks.push({
120
+ name: 'drift-lane',
121
+ ok: lane.ok && lane.value.ok,
122
+ detail: lane.ok
123
+ ? lane.value.criteria.map((c) => `${c.id}:${c.ok ? 'ok' : 'fail'}`).join(' · ')
124
+ : lane.violations.map((x) => `${x.field}:${x.rule}`).join(' · '),
125
+ });
126
+ const effort = effortOf(root, planId);
127
+ const recap = effort ? latestRecap(root, effort) : undefined;
128
+ return ok({
129
+ class: (recap?.class ?? 'feature'),
130
+ reviewedSha: worktreeTreeSha(wt),
131
+ artifactSha: canonicalSha(plan.meta, plan.body),
132
+ reviewed: { kind: 'tree', root: wt, files },
133
+ promptBody: codePromptBody(wt, base, files, `### Plan under implementation: ${planId}\n${serializeDoc({ meta: plan.meta, body: plan.body })}`),
134
+ checks,
135
+ stamps: [],
136
+ lensOverrides,
137
+ skipLenses,
138
+ });
139
+ },
140
+ currentSha(root, _canon, _cfg, planId) {
141
+ const wt = worktreePath(root, planId);
142
+ return existsSync(wt) ? worktreeTreeSha(wt) : undefined;
143
+ },
144
+ });
@@ -0,0 +1,6 @@
1
+ // Side-effect imports register the gate specs.
2
+ import './decompose.js';
3
+ import './plan.js';
4
+ import './implement.js';
5
+ import './ship.js';
6
+ import './design.js';
@@ -0,0 +1,86 @@
1
+ import { ok, refuse, v } from '../refusal.js';
2
+ import { serializeDoc } from '../fm.js';
3
+ import { canonicalSha, short } from '../sha.js';
4
+ import { latestRecap } from '../journal.js';
5
+ import { baseForSpec } from '../history.js';
6
+ import { findById } from '../scan.js';
7
+ import { effortOf, planPairSha } from '../reviewed.js';
8
+ import { validateDoc } from '../schemas.js';
9
+ import { registerGate } from '../gate.js';
10
+ import { designPending, designStamp } from '../design.js';
11
+ registerGate({
12
+ gate: 'plan',
13
+ targetKind: 'plan',
14
+ async resolve(root, _ctx, canon, _cfg, planId) {
15
+ const plan = findById(canon, planId);
16
+ if (!plan || plan.meta.type !== 'plan') {
17
+ return refuse([v('plan', 'unknown-plan', planId, 'a plans/ doc id')]);
18
+ }
19
+ const effort = effortOf(root, planId);
20
+ if (!effort) {
21
+ return refuse([v('plan', 'no-effort', planId, 'a plan written via witness write --effort <slug> (class is read from the effort stream)')]);
22
+ }
23
+ const recap = latestRecap(root, effort);
24
+ const parent = findById(canon, String(plan.meta.parent));
25
+ if (!parent) {
26
+ return refuse([v('parent', 'unknown-parent', String(plan.meta.parent), 'an existing canon doc')]);
27
+ }
28
+ const checks = [];
29
+ const schemaProblems = validateDoc(plan.meta, plan.body).map((x) => `${x.field}:${x.rule}`);
30
+ checks.push({ name: 'schema', ok: schemaProblems.length === 0, detail: schemaProblems.slice(0, 5).join(' · ') });
31
+ const parentStatus = String(parent.meta.status);
32
+ const parentOk = parent.meta.type === 'principles'
33
+ ? parentStatus === 'approved'
34
+ : parentStatus === 'approved' || parentStatus === 'live';
35
+ checks.push({ name: 'parent-approved', ok: parentOk,
36
+ detail: `${String(parent.meta.id)} is ${parentStatus}` });
37
+ const parentSha = canonicalSha(parent.meta, parent.body);
38
+ const pin = String(plan.meta['derives-from']);
39
+ checks.push({ name: 'pin-fresh', ok: pin === parentSha,
40
+ detail: pin === parentSha
41
+ ? `pin ${short(parentSha)}`
42
+ : `derives-from ${short(pin)} but parent is at ${short(parentSha)} — rewrite the plan (witness write re-stamps the pin)` });
43
+ if (parent.meta.ui === true) {
44
+ const stamp = designStamp(parent);
45
+ const planPin = plan.meta['design-from'];
46
+ const okPin = stamp !== undefined && !designPending(root, parent) && planPin === stamp.sha;
47
+ checks.push({ name: 'design-pin', ok: okPin,
48
+ detail: okPin ? `design ${short(stamp.sha)}`
49
+ : !stamp ? `${String(parent.meta.id)} has no approved design — gate design first`
50
+ : designPending(root, parent) ? `${String(parent.meta.id)} design is stale — re-approve or reconfirm`
51
+ : `design-from ${short(String(planPin ?? 'absent'))} ≠ current design ${short(stamp.sha)}` });
52
+ }
53
+ const base = baseForSpec(root, canon, String(parent.meta.id), planId);
54
+ const promptBody = [
55
+ `### Parent spec: ${String(parent.meta.id)}`, serializeDoc({ meta: parent.meta, body: parent.body }),
56
+ `### Delta base`, base.kind === 'empty'
57
+ ? 'new spec — the delta is the whole spec'
58
+ : `previous plan ${base.planId} pinned ${short(base.sha ?? '')} — the plan must realize what changed since`,
59
+ `### Plan: ${planId}`, serializeDoc({ meta: plan.meta, body: plan.body }),
60
+ ].join('\n\n');
61
+ return ok({
62
+ class: (recap?.class ?? 'feature'),
63
+ reviewedSha: planPairSha(plan, parent),
64
+ artifactSha: canonicalSha(plan.meta, plan.body),
65
+ reviewed: {
66
+ kind: 'docs',
67
+ docs: [
68
+ { id: planId, body: plan.body },
69
+ { id: String(parent.meta.id), body: parent.body },
70
+ ],
71
+ },
72
+ promptBody,
73
+ checks,
74
+ stamps: [{ artifact: planId, to: 'approved' }],
75
+ });
76
+ },
77
+ currentSha(_root, canon, _cfg, planId) {
78
+ const plan = findById(canon, planId);
79
+ const parent = plan ? findById(canon, String(plan.meta.parent)) : undefined;
80
+ return plan && parent ? planPairSha(plan, parent) : undefined;
81
+ },
82
+ approveStamps(_root, canon, planId) {
83
+ const doc = findById(canon, planId);
84
+ return doc && String(doc.meta.status) === 'draft' ? [{ artifact: planId, to: 'approved' }] : [];
85
+ },
86
+ });
@@ -0,0 +1,98 @@
1
+ import { existsSync } from 'node:fs';
2
+ import { ok, refuse, v } from '../refusal.js';
3
+ import { canonicalSha } from '../sha.js';
4
+ import { latestRecap, readStream } from '../journal.js';
5
+ import { findById } from '../scan.js';
6
+ import { changedFiles, diffBase } from '../evidence.js';
7
+ import { runCriteria } from '../criteria.js';
8
+ import { execCommand } from '../runner.js';
9
+ import { ensureTrusted } from '../allowlist.js';
10
+ import { effortOf, worktreeTreeSha } from '../reviewed.js';
11
+ import { worktreePath } from '../worktree.js';
12
+ import { codePromptBody } from './implement.js';
13
+ import { registerGate } from '../gate.js';
14
+ import { lastGateRun } from '../rounds.js';
15
+ import { serializeDoc } from '../fm.js';
16
+ async function commandLane(wt, root, ctx, name, cmd) {
17
+ if (!cmd)
18
+ return { name, ok: false, detail: `ship.${name === 'tests' ? 'test' : 'lint'} not configured` };
19
+ const trust = await ensureTrusted(root, ctx, cmd);
20
+ if (trust !== 'trusted')
21
+ return { name, ok: false, detail: `${cmd}: ${trust}` };
22
+ const run = execCommand(wt, ctx, cmd);
23
+ return { name, ok: run.exitZero, detail: run.exitZero ? cmd : run.output.slice(-300) };
24
+ }
25
+ registerGate({
26
+ gate: 'ship',
27
+ targetKind: 'plan',
28
+ async resolve(root, ctx, canon, cfg, planId) {
29
+ const plan = findById(canon, planId);
30
+ if (!plan || plan.meta.type !== 'plan') {
31
+ return refuse([v('plan', 'unknown-plan', planId, 'a plans/ doc id')]);
32
+ }
33
+ const wt = worktreePath(root, planId);
34
+ if (String(plan.meta.status) !== 'in-progress' || !existsSync(wt)) {
35
+ return refuse([v('plan', 'not-started', String(plan.meta.status), `an in-progress plan with a worktree — run: witness start ${planId}`)]);
36
+ }
37
+ const parent = findById(canon, String(plan.meta.parent));
38
+ if (!parent) {
39
+ return refuse([v('parent', 'unknown-parent', String(plan.meta.parent), 'an existing canon doc')]);
40
+ }
41
+ const baseR = diffBase(wt, cfg);
42
+ if (!baseR.ok)
43
+ return baseR;
44
+ const base = baseR.value;
45
+ const files = changedFiles(wt, base);
46
+ const checks = [];
47
+ const entries = readStream(root, planId);
48
+ const lastImplement = lastGateRun(entries, 'implement');
49
+ const implementSettled = (() => {
50
+ if (!lastImplement)
51
+ return false;
52
+ if (lastImplement.outcome === 'passed')
53
+ return true;
54
+ const after = entries.slice(entries.lastIndexOf(lastImplement) + 1);
55
+ return after.some((e) => e.t === 'human-decision' &&
56
+ e.gate === 'implement' &&
57
+ e.decision === 'approve');
58
+ })();
59
+ checks.push({ name: 'implement-gate', ok: implementSettled,
60
+ detail: lastImplement
61
+ ? `last implement round ${lastImplement.round}: ${lastImplement.outcome}`
62
+ : 'implement gate never ran — witness gate implement ' + planId });
63
+ const ship = (cfg.raw.ship ?? {});
64
+ checks.push(await commandLane(wt, root, ctx, 'tests', ship.test));
65
+ checks.push(await commandLane(wt, root, ctx, 'lint', ship.lint));
66
+ // deterministic drift lane: spec content from MAIN, execution in the worktree
67
+ const lane = await runCriteria(wt, ctx, parent, { trustRoot: root });
68
+ const laneOk = lane.ok && lane.value.ok;
69
+ checks.push({
70
+ name: 'drift-lane',
71
+ ok: laneOk,
72
+ detail: lane.ok
73
+ ? lane.value.criteria.map((c) => `${c.id}:${c.ok ? 'ok' : 'fail'}`).join(' · ')
74
+ : lane.violations.map((x) => `${x.field}:${x.rule}`).join(' · '),
75
+ });
76
+ const pin = String(plan.meta['derives-from']);
77
+ const repin = lane.ok && lane.value.ok && pin !== lane.value.sha
78
+ ? { rel: plan.rel, meta: plan.meta, body: plan.body, sha: lane.value.sha }
79
+ : undefined;
80
+ const effort = effortOf(root, planId);
81
+ const recap = effort ? latestRecap(root, effort) : undefined;
82
+ return ok({
83
+ class: (recap?.class ?? 'feature'),
84
+ reviewedSha: worktreeTreeSha(wt),
85
+ artifactSha: canonicalSha(plan.meta, plan.body),
86
+ reviewed: { kind: 'tree', root: wt, files },
87
+ promptBody: codePromptBody(wt, base, files, `### Parent spec (from main): ${String(parent.meta.id)}\n${serializeDoc({ meta: parent.meta, body: parent.body })}\n\n### Plan: ${planId}\n${serializeDoc({ meta: plan.meta, body: plan.body })}`),
88
+ checks,
89
+ standingStop: 'ship always stops — a human sends every PR',
90
+ stamps: [],
91
+ repin,
92
+ });
93
+ },
94
+ currentSha(root, _canon, _cfg, planId) {
95
+ const wt = worktreePath(root, planId);
96
+ return existsSync(wt) ? worktreeTreeSha(wt) : undefined;
97
+ },
98
+ });
package/dist/gitio.js ADDED
@@ -0,0 +1,97 @@
1
+ import { execFileSync } from 'node:child_process';
2
+ import { dirname } from 'node:path';
3
+ import { canonPaths } from './config.js';
4
+ import { ok, refuse, v } from './refusal.js';
5
+ export function git(root, ...args) {
6
+ return execFileSync('git', args, {
7
+ cwd: root,
8
+ encoding: 'utf8',
9
+ stdio: ['ignore', 'pipe', 'pipe'],
10
+ }).replace(/\n$/, '');
11
+ }
12
+ export function tryGit(root, ...args) {
13
+ try {
14
+ return { ok: true, out: git(root, ...args) };
15
+ }
16
+ catch (e) {
17
+ const err = e;
18
+ // most git failures (like `pull --rebase` with no upstream) write to stderr with
19
+ // an empty stdout — `??` only falls through on nullish, so it must prefer the
20
+ // first NON-EMPTY stream, not just the first non-nullish one
21
+ return { ok: false, out: String(err.stdout || err.stderr || e).trim() };
22
+ }
23
+ }
24
+ export function primaryRoot(cwd) {
25
+ const top = tryGit(cwd, 'rev-parse', '--show-toplevel');
26
+ if (!top.ok) {
27
+ return refuse([v('repo', 'not-a-git-repo', cwd, 'run inside a git repository (git init first)')]);
28
+ }
29
+ const common = git(cwd, 'rev-parse', '--path-format=absolute', '--git-common-dir');
30
+ const gitDir = git(cwd, 'rev-parse', '--path-format=absolute', '--git-dir');
31
+ if (common === gitDir)
32
+ return ok(top.out);
33
+ return ok(dirname(common));
34
+ }
35
+ export function stateDirs(root) {
36
+ const p = canonPaths(root);
37
+ return [p.specs, p.plans, p.designs, '.witness'];
38
+ }
39
+ export function isStatePath(root, rel) {
40
+ return stateDirs(root).some((d) => rel === d || rel.startsWith(d + '/'));
41
+ }
42
+ const LOCAL_STATE_FILES = new Set([
43
+ '.witness/lock',
44
+ '.witness/txn.json',
45
+ '.witness/allow.json',
46
+ '.witness/calibration.local.yaml',
47
+ ]);
48
+ export function dirtyStatePaths(root) {
49
+ const res = tryGit(root, 'status', '--porcelain', '--untracked-files=all', '--', ...stateDirs(root));
50
+ if (!res.ok || res.out === '')
51
+ return [];
52
+ return res.out
53
+ .split('\n')
54
+ // a rename (e.g. from `git mv`) porcelain-formats as "old -> new" on one line —
55
+ // split it into its two paths so each is checked against the caller's planned set
56
+ .flatMap((l) => l.slice(3).replace(/^"|"$/g, '').split(' -> '))
57
+ .filter((p) => !LOCAL_STATE_FILES.has(p));
58
+ }
59
+ export const TRAILER = 'Witness-State: 1';
60
+ export function commitWithTrailer(root, files, subject) {
61
+ // tryGit, not git: a path already fully processed by a prior `git mv` (both sides
62
+ // of a rename staged atomically) is absent from working tree AND index, so `add`
63
+ // reports it unmatched — even though the rename itself is already staged. `commit
64
+ // --only` resolves its pathspecs against the index-vs-HEAD diff, where the rename
65
+ // is visible, so it still succeeds and is the real validation here.
66
+ tryGit(root, 'add', '--', ...files);
67
+ // --no-verify: state commits are machine-authored and scope-restricted to non-source
68
+ // paths (isStatePath), so a host lint/test hook has nothing to validate here — while
69
+ // its stash/restore step can destroy the human's unrelated dirty work, and a
70
+ // tree-mutating hook (formatters) can desync the index from the txn marker.
71
+ // stateCommit's own `unrelated-dirty` refusal is the real gate on these commits.
72
+ git(root, 'commit', '--no-verify', '--only', '-m', subject, '-m', TRAILER, '--', ...files);
73
+ return ok({ sha: git(root, 'rev-parse', 'HEAD') });
74
+ }
75
+ export function stateCommit(root, files, subject) {
76
+ const dirs = stateDirs(root);
77
+ const bad = files.filter((f) => !isStatePath(root, f));
78
+ if (bad.length) {
79
+ return refuse(bad.map((f) => v(f, 'out-of-scope', f, `state commits touch only ${dirs.map((d) => `${d}/`).join(', ')}`)));
80
+ }
81
+ const planned = new Set(files);
82
+ const unrelated = dirtyStatePaths(root).filter((p) => !planned.has(p));
83
+ if (unrelated.length) {
84
+ return refuse(unrelated.map((p) => v(p, 'unrelated-dirty', 'uncommitted change on a state path', 'revert it or re-apply via witness write, then re-run')));
85
+ }
86
+ return commitWithTrailer(root, files, subject);
87
+ }
88
+ export function auditStateCommits(root) {
89
+ const p = canonPaths(root);
90
+ const res = tryGit(root, 'log', '--format=%H%x1f%s%x1f%(trailers:key=Witness-State,valueonly=true)', '--', p.specs, p.plans, p.designs, '.witness/journal');
91
+ if (!res.ok || res.out === '')
92
+ return [];
93
+ return res.out.split('\n').filter(Boolean).map((line) => {
94
+ const [sha, subject, trailer] = line.split('\x1f');
95
+ return { sha: sha ?? '', subject: subject ?? '', trailered: (trailer ?? '').trim() === '1' };
96
+ });
97
+ }
package/dist/glob.js ADDED
@@ -0,0 +1,32 @@
1
+ import { readdirSync, statSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ export function globToRegExp(pattern) {
4
+ const segs = pattern.split('/');
5
+ const parts = segs.map((seg, i) => {
6
+ const last = i === segs.length - 1;
7
+ if (seg === '**')
8
+ return last ? '.*' : '(?:[^/]+/)*';
9
+ const body = seg
10
+ .replace(/[.+^${}()|[\]\\]/g, '\\$&')
11
+ .replace(/\*/g, '[^/]*');
12
+ return last ? body : `${body}/`;
13
+ });
14
+ return new RegExp(`^${parts.join('')}$`);
15
+ }
16
+ export function walkFiles(root) {
17
+ const skip = new Set(['.git', 'node_modules']);
18
+ const out = [];
19
+ const rec = (rel) => {
20
+ for (const name of readdirSync(join(root, rel || '.'))) {
21
+ if (skip.has(name))
22
+ continue;
23
+ const r = rel ? `${rel}/${name}` : name;
24
+ if (statSync(join(root, r)).isDirectory())
25
+ rec(r);
26
+ else
27
+ out.push(r);
28
+ }
29
+ };
30
+ rec('');
31
+ return out.sort();
32
+ }