@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,11 @@
1
+ ## Step: s1
2
+
3
+ Failing test first in `tests/pad.test.mjs` — name carries `@spec:pad-strings`;
4
+ assert `padLeft("7", 3, "0")` equals `"007"` and `padLeft("777", 2, "0")`
5
+ equals `"777"` (unchanged). Then implement `padLeft` in `src/pad.mjs`; green.
6
+
7
+ ## Step: s2
8
+
9
+ Failing test — name carries `@spec:pad-strings`; assert `padRight("7", 3, "0")`
10
+ equals `"700"` and `padRight("777", 2, "0")` equals `"777"` (unchanged). Then
11
+ implement `padRight` in `src/pad.mjs`; green.
@@ -0,0 +1,10 @@
1
+ {
2
+ "type": "plan",
3
+ "parent": "pad-strings",
4
+ "depends": [],
5
+ "needs": [],
6
+ "steps": [
7
+ { "id": "s1", "title": "implement padLeft with TDD", "criteria": ["ac-left"] },
8
+ { "id": "s2", "title": "implement padRight with TDD", "criteria": ["ac-right"] }
9
+ ]
10
+ }
@@ -0,0 +1,8 @@
1
+ {
2
+ "effort": "cal-implement",
3
+ "class": "feature",
4
+ "goals": [{ "id": "g1", "text": "The promised utility behavior exists with tagged tests" }],
5
+ "non_goals": [],
6
+ "constraints": [],
7
+ "slices": []
8
+ }
@@ -0,0 +1 @@
1
+ { "name": "cal-implement-s3", "type": "module", "private": true }
@@ -0,0 +1,2 @@
1
+ // padLeft/padRight are promised by the pad-strings spec — not implemented yet.
2
+ export {};
@@ -0,0 +1,9 @@
1
+ ## Motivation
2
+
3
+ Fixed-width columns line up in monospace output (g1).
4
+
5
+ ## Behavior
6
+
7
+ `padLeft(str, width, ch)` pads `str` on the left with `ch` until it reaches
8
+ `width` characters, unchanged if already at or past `width` (ac-left).
9
+ `padRight(str, width, ch)` does the same on the right (ac-right).
@@ -0,0 +1,11 @@
1
+ {
2
+ "type": "spec",
3
+ "summary": "Strings pad to a fixed width on either side",
4
+ "depends": [],
5
+ "needs": [],
6
+ "criteria": [
7
+ { "id": "ac-left", "test": "@spec:pad-strings" },
8
+ { "id": "ac-right", "test": "@spec:pad-strings" }
9
+ ],
10
+ "covers": ["g1"]
11
+ }
@@ -0,0 +1 @@
1
+ { "defect": "none — two-criterion mapping; measures unaided step totality" }
@@ -0,0 +1,10 @@
1
+ ## Motivation
2
+
3
+ Slugs appear in every shared URL (g1).
4
+
5
+ ## Behavior
6
+
7
+ `slugify(title)` lowercases, replaces whitespace runs with single hyphens,
8
+ strips characters outside `[a-z0-9-]`, and collapses repeated hyphens. Slugs
9
+ are capped at 80 characters without cutting mid-word where possible (ac-len's
10
+ suite asserts the cap; the cap value lives here: 80).
@@ -0,0 +1,11 @@
1
+ {
2
+ "type": "spec",
3
+ "summary": "Titles convert to lowercase URL-safe slugs",
4
+ "depends": [],
5
+ "needs": [],
6
+ "criteria": [
7
+ { "id": "ac-fmt", "test": "@spec:slug-format" },
8
+ { "id": "ac-len", "cmd": "node --test --test-name-pattern \"@spec:slug-format\" tests/" }
9
+ ],
10
+ "covers": ["g1"]
11
+ }
@@ -0,0 +1,8 @@
1
+ {
2
+ "effort": "slugify",
3
+ "class": "feature",
4
+ "goals": [{ "id": "g1", "text": "Titles convert to URL-safe slugs" }],
5
+ "non_goals": [{ "id": "n1", "text": "No transliteration of non-Latin scripts" }],
6
+ "constraints": [],
7
+ "slices": ["slugify"]
8
+ }
@@ -0,0 +1 @@
1
+ { "defect": "none — five-way mapping stresses totality" }
@@ -0,0 +1,11 @@
1
+ ## Motivation
2
+
3
+ Weak passwords are the largest account-takeover vector (g1).
4
+
5
+ ## Behavior
6
+
7
+ A password is accepted only if it satisfies every rule: at least 12
8
+ characters (ac-min); at least one uppercase letter (ac-upper); at least one
9
+ digit (ac-digit); not present in the top-1000 common-passwords list shipped
10
+ at `data/common-passwords.txt` (ac-common). The full policy suite runs as one
11
+ filtered command (ac-suite).
@@ -0,0 +1,14 @@
1
+ {
2
+ "type": "spec",
3
+ "summary": "Passwords are validated against the published policy",
4
+ "depends": [],
5
+ "needs": [],
6
+ "criteria": [
7
+ { "id": "ac-min", "test": "@spec:password-policy" },
8
+ { "id": "ac-upper", "test": "@spec:password-policy" },
9
+ { "id": "ac-digit", "test": "@spec:password-policy" },
10
+ { "id": "ac-common", "test": "@spec:password-policy" },
11
+ { "id": "ac-suite", "cmd": "node --test --test-name-pattern \"@spec:password-policy\" tests/" }
12
+ ],
13
+ "covers": ["g1"]
14
+ }
@@ -0,0 +1,8 @@
1
+ {
2
+ "effort": "password-rules",
3
+ "class": "feature",
4
+ "goals": [{ "id": "g1", "text": "Passwords are validated against the published policy" }],
5
+ "non_goals": [],
6
+ "constraints": [],
7
+ "slices": ["password policy validation"]
8
+ }
@@ -0,0 +1 @@
1
+ { "defect": "none — needs entry must survive into the plan's world untouched" }
@@ -0,0 +1,10 @@
1
+ ## Motivation
2
+
3
+ Receivers need to trust that a webhook actually came from us (g1).
4
+
5
+ ## Behavior
6
+
7
+ Every outbound webhook carries an `X-Hook-Signature` header: hex-encoded
8
+ HMAC-SHA256 of the raw request body, keyed by `WEBHOOK_SIGNING_KEY` (ac-sign).
9
+ A receiver recomputing the same HMAC over the same body and key must match
10
+ byte-for-byte, and a tampered body must fail verification (ac-verify).
@@ -0,0 +1,11 @@
1
+ {
2
+ "type": "spec",
3
+ "summary": "Outbound webhooks are HMAC-signed and verifiable",
4
+ "depends": [],
5
+ "needs": [{ "env": "WEBHOOK_SIGNING_KEY" }],
6
+ "criteria": [
7
+ { "id": "ac-sign", "test": "@spec:webhook-signatures" },
8
+ { "id": "ac-verify", "test": "@spec:webhook-signatures" }
9
+ ],
10
+ "covers": ["g1"]
11
+ }
@@ -0,0 +1,8 @@
1
+ {
2
+ "effort": "webhook-signing",
3
+ "class": "feature",
4
+ "goals": [{ "id": "g1", "text": "Outbound webhooks are HMAC-signed and verifiable" }],
5
+ "non_goals": [],
6
+ "constraints": [],
7
+ "slices": ["webhook signing"]
8
+ }
@@ -0,0 +1 @@
1
+ { "defect": "tempts an unmapped rigging step — write refuses it unless honestly marked scaffolding" }
@@ -0,0 +1,9 @@
1
+ ## Motivation
2
+
3
+ Long-lived access tokens widen the theft window (g1).
4
+
5
+ ## Behavior
6
+
7
+ An access token issued at time T is rejected by every endpoint from T+15
8
+ minutes onward (ac-expiry). Testing this without real sleeps requires a
9
+ controllable clock the test suite can advance deterministically.
@@ -0,0 +1,10 @@
1
+ {
2
+ "type": "spec",
3
+ "summary": "Access tokens expire after 15 minutes",
4
+ "depends": [],
5
+ "needs": [],
6
+ "criteria": [
7
+ { "id": "ac-expiry", "test": "@spec:token-expiry" }
8
+ ],
9
+ "covers": ["g1"]
10
+ }
@@ -0,0 +1,8 @@
1
+ {
2
+ "effort": "expiry-windows",
3
+ "class": "feature",
4
+ "goals": [{ "id": "g1", "text": "Access tokens expire after 15 minutes" }],
5
+ "non_goals": [],
6
+ "constraints": [],
7
+ "slices": ["token expiry"]
8
+ }
@@ -0,0 +1,5 @@
1
+ # witness shipped calibration matrix.
2
+ # Populated by `witness calibrate <model>` at release time (Plan 4).
3
+ # Until then no model is calibrated: every gate-run stamps calibration: "none"
4
+ # and the dashboard warns below-floor — which is the truth.
5
+ models: []
@@ -0,0 +1,154 @@
1
+ import { unlinkSync, writeFileSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ import { splitDoc } from './fm.js';
4
+ import { canonicalSha } from './sha.js';
5
+ import { tryGit, stateCommit } from './gitio.js';
6
+ import { acquireLock } from './lock.js';
7
+ import { crashPoint, withTxn } from './txn.js';
8
+ import { appendEntry, entryLine, journalRel } from './journal.js';
9
+ import { findById } from './scan.js';
10
+ import { effortOf, effortWrites } from './reviewed.js';
11
+ import { ok, refuse, v } from './refusal.js';
12
+ import { prepareStamp, writeStamp } from './stamp.js';
13
+ import { removeWorktree } from './worktree.js';
14
+ export function pinCommit(root, rel, pin) {
15
+ const log = tryGit(root, 'log', '--format=%H', '--', rel);
16
+ if (!log.ok)
17
+ return undefined;
18
+ // Status-only touch-ups (approve, stampLive, a gate's writeStamp) commit the same
19
+ // canonical sha under a new commit, since status is a volatile field excluded from
20
+ // it. Walking newest-first and stopping at the first non-match — rather than
21
+ // returning the first match — lands on the OLDEST commit that established this
22
+ // content, so its parent is the true prior (different-content) version.
23
+ let found;
24
+ for (const sha of log.out.split('\n').filter(Boolean)) {
25
+ const show = tryGit(root, 'show', `${sha}:${rel}`);
26
+ if (!show.ok)
27
+ break;
28
+ const doc = splitDoc(show.out);
29
+ if (!doc.ok || canonicalSha(doc.value.meta, doc.value.body) !== pin)
30
+ break;
31
+ found = sha;
32
+ }
33
+ return found;
34
+ }
35
+ export function specRevertFor(root, canon, spec, pin, abandonSet) {
36
+ const current = canonicalSha(spec.meta, spec.body);
37
+ if (current !== pin) {
38
+ return refuse([v('spec', 'stacked-amendment', `${String(spec.meta.id)} is at ${current.slice(0, 7)}, pin is ${pin.slice(0, 7)}`, 'abandon newest-first: a later amendment touched this spec')]);
39
+ }
40
+ const commit = pinCommit(root, spec.rel, pin);
41
+ if (!commit) {
42
+ return refuse([v('spec', 'pin-unresolvable', pin.slice(0, 7), 'a committed version matching the pin')]);
43
+ }
44
+ const prior = tryGit(root, 'show', `${commit}^:${spec.rel}`);
45
+ if (!prior.ok) {
46
+ const dependents = canon.docs
47
+ .filter((d) => !abandonSet.has(String(d.meta.id)))
48
+ .filter((d) => (d.meta.depends ?? []).includes(String(spec.meta.id)))
49
+ .map((d) => String(d.meta.id));
50
+ if (dependents.length > 0) {
51
+ return refuse([v('spec', 'waiting-dependents', dependents.join(' '), 'no docs outside the abandon set may depend on a spec being deleted')]);
52
+ }
53
+ return ok({ doc: spec, action: 'delete' });
54
+ }
55
+ const parsed = splitDoc(prior.out);
56
+ const toStatus = parsed.ok ? String(parsed.value.meta.status) : 'draft';
57
+ return ok({ doc: spec, action: 'restore', content: prior.out, toStatus });
58
+ }
59
+ export function executeAbandon(root, ctx, items, effortEntry, subject) {
60
+ const files = new Set();
61
+ const journalMulti = [];
62
+ const stamps = [];
63
+ const specEntries = [];
64
+ for (const item of items) {
65
+ if (item.planStamp) {
66
+ stamps.push(item.planStamp);
67
+ files.add(item.planStamp.rel);
68
+ files.add(journalRel(item.planStamp.stream));
69
+ journalMulti.push({ stream: item.planStamp.stream, line: item.planStamp.line });
70
+ }
71
+ if (item.revert) {
72
+ const spec = item.revert.doc;
73
+ const id = String(spec.meta.id);
74
+ const entry = {
75
+ v: 1, t: 'status', artifact: id, from: String(spec.meta.status),
76
+ to: item.revert.action === 'restore' ? item.revert.toStatus : 'removed',
77
+ cause: 'abandon',
78
+ };
79
+ specEntries.push({ stream: id, entry });
80
+ files.add(spec.rel);
81
+ files.add(journalRel(id));
82
+ journalMulti.push({ stream: id, line: entryLine(entry) });
83
+ }
84
+ }
85
+ let effortLine;
86
+ if (effortEntry) {
87
+ const entry = {
88
+ v: 1, t: 'human-decision', gate: 'effort', artifact: effortEntry.effort,
89
+ round: 0, decision: 'abandon-effort',
90
+ };
91
+ effortLine = { stream: effortEntry.effort, line: entryLine(entry) };
92
+ files.add(journalRel(effortEntry.effort));
93
+ journalMulti.push(effortLine);
94
+ }
95
+ const lockR = acquireLock(root);
96
+ if (!lockR.ok)
97
+ return lockR;
98
+ try {
99
+ return withTxn(root, { op: 'abandon', files: [...files], journalMulti }, () => {
100
+ for (const item of items) {
101
+ if (item.revert) {
102
+ const abs = join(root, item.revert.doc.rel);
103
+ if (item.revert.action === 'restore')
104
+ writeFileSync(abs, item.revert.content);
105
+ else
106
+ unlinkSync(abs); // stateCommit's `git add` below correctly stages a tracked-but-missing file as a deletion
107
+ }
108
+ }
109
+ for (const s of stamps)
110
+ writeStamp(root, s);
111
+ for (const se of specEntries)
112
+ appendEntry(root, se.stream, se.entry);
113
+ if (effortLine) {
114
+ appendEntry(root, effortLine.stream, JSON.parse(effortLine.line));
115
+ }
116
+ crashPoint(ctx.env, 'abandon-commit');
117
+ const r = stateCommit(root, [...files], subject);
118
+ if (r.ok)
119
+ for (const item of items)
120
+ if (item.planId)
121
+ removeWorktree(root, item.planId);
122
+ return r;
123
+ });
124
+ }
125
+ finally {
126
+ lockR.value();
127
+ }
128
+ }
129
+ export function planItems(root, canon, plan, abandonSet, pairing) {
130
+ const planId = String(plan.meta.id);
131
+ const status = String(plan.meta.status);
132
+ if (status === 'done') {
133
+ return refuse([v('plan', 'already-done', planId, 'abandon targets unfinished plans — done plans are history')]);
134
+ }
135
+ const item = {
136
+ planStamp: prepareStamp(plan, 'abandoned', 'abandon'),
137
+ planId,
138
+ };
139
+ if (pairing === 'stamp-only')
140
+ return ok(item);
141
+ const effort = effortOf(root, planId);
142
+ const parentId = String(plan.meta.parent);
143
+ const paired = effort !== undefined && effortWrites(root, effort).has(parentId);
144
+ if (paired) {
145
+ const parent = findById(canon, parentId);
146
+ if (parent) {
147
+ const revertR = specRevertFor(root, canon, parent, String(plan.meta['derives-from']), abandonSet);
148
+ if (!revertR.ok)
149
+ return revertR;
150
+ item.revert = revertR.value;
151
+ }
152
+ }
153
+ return ok(item);
154
+ }
package/dist/adopt.js ADDED
@@ -0,0 +1,42 @@
1
+ import { existsSync, readdirSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ import { splitDoc } from './fm.js';
4
+ import { TRAILER, git, tryGit } from './gitio.js';
5
+ import { readStream } from './journal.js';
6
+ import { canonicalSha } from './sha.js';
7
+ const hasTrailer = (root, sha) => git(root, 'show', '-s', '--format=%B', sha).split('\n').some((l) => l.trim() === TRAILER);
8
+ const commitsTouching = (root, rel) => git(root, 'log', '--format=%H', '--', rel).split('\n').filter(Boolean);
9
+ export function lastWitnessed(root, rel) {
10
+ for (const sha of commitsTouching(root, rel)) {
11
+ if (!hasTrailer(root, sha))
12
+ continue;
13
+ const raw = tryGit(root, 'show', `${sha}:${rel}`);
14
+ if (!raw.ok)
15
+ continue;
16
+ const doc = splitDoc(raw.out);
17
+ if (!doc.ok)
18
+ continue;
19
+ return { sha: canonicalSha(doc.value.meta, doc.value.body), criteria: doc.value.meta.criteria ?? null };
20
+ }
21
+ return undefined;
22
+ }
23
+ export function untrailedCommitsFor(root, rel) {
24
+ return commitsTouching(root, rel).filter((sha) => !hasTrailer(root, sha));
25
+ }
26
+ export function adoptedCommits(root) {
27
+ const out = new Set();
28
+ const dir = join(root, '.witness', 'journal');
29
+ if (!existsSync(dir))
30
+ return out;
31
+ for (const file of readdirSync(dir)) {
32
+ if (!file.endsWith('.jsonl'))
33
+ continue;
34
+ for (const entry of readStream(root, file.slice(0, -'.jsonl'.length))) {
35
+ if (entry.t !== 'adopt' || !Array.isArray(entry.commits))
36
+ continue;
37
+ for (const sha of entry.commits)
38
+ out.add(sha);
39
+ }
40
+ }
41
+ return out;
42
+ }
@@ -0,0 +1,29 @@
1
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ const allowPath = (root) => join(root, '.witness', 'allow.json');
4
+ function load(root) {
5
+ if (!existsSync(allowPath(root)))
6
+ return [];
7
+ try {
8
+ const parsed = JSON.parse(readFileSync(allowPath(root), 'utf8'));
9
+ return Array.isArray(parsed.commands) ? parsed.commands : [];
10
+ }
11
+ catch {
12
+ return [];
13
+ }
14
+ }
15
+ export async function ensureTrusted(root, ctx, cmd) {
16
+ if (ctx.env.WITNESS_TRUST_CMDS === '1')
17
+ return 'trusted';
18
+ const commands = load(root);
19
+ if (commands.includes(cmd))
20
+ return 'trusted';
21
+ if (!ctx.isTTY)
22
+ return 'blocked';
23
+ const answer = await ctx.ask(`witness wants to run: ${cmd}\ntrust this command? saves to .witness/allow.json (gitignored) [y/N]`);
24
+ if (!answer.trim().toLowerCase().startsWith('y'))
25
+ return 'declined';
26
+ mkdirSync(join(root, '.witness'), { recursive: true });
27
+ writeFileSync(allowPath(root), JSON.stringify({ commands: [...commands, cmd] }, null, 2));
28
+ return 'trusted';
29
+ }
package/dist/bin.js ADDED
@@ -0,0 +1,32 @@
1
+ #!/usr/bin/env node
2
+ import { createInterface } from 'node:readline/promises';
3
+ import { EXIT, main } from './cli.js';
4
+ import { renderRefusal, v } from './refusal.js';
5
+ const ctx = {
6
+ cwd: process.cwd(),
7
+ env: process.env,
8
+ isTTY: Boolean(process.stdin.isTTY && process.stdout.isTTY),
9
+ out: (l) => process.stdout.write(l + '\n'),
10
+ err: (l) => process.stderr.write(l + '\n'),
11
+ ask: async (q) => {
12
+ const rl = createInterface({ input: process.stdin, output: process.stderr });
13
+ try {
14
+ return await rl.question(q + ' ');
15
+ }
16
+ finally {
17
+ rl.close();
18
+ }
19
+ },
20
+ };
21
+ // A verb that throws is a bug, but a stack trace is not a refusal: the engine file
22
+ // renders exit 2/3 as a refusal it can act on, and anything else reads as a crash the
23
+ // session has no vocabulary for. `init --agent` is the first path that touches paths
24
+ // the repo is entitled to refuse (git commit --only on an ignored pathspec throws out
25
+ // of gitio.ts:6); every other verb gets the same protection for free.
26
+ try {
27
+ process.exit(await main(ctx, process.argv.slice(2)));
28
+ }
29
+ catch (e) {
30
+ renderRefusal([v('witness', 'unexpected-error', String(e.message).slice(0, 200), 'a bug — re-run with the same arguments and report this line')]).forEach((l) => process.stderr.write(`${l}\n`));
31
+ process.exit(EXIT.REFUSED);
32
+ }