@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.
- package/LICENSE +21 -0
- package/LICENSES/frontend-design-Apache-2.0.txt +177 -0
- package/LICENSES/mattpocock-skills-MIT.txt +21 -0
- package/LICENSES/no-mistakes-MIT.txt +21 -0
- package/LICENSES/pr-review-toolkit-Apache-2.0.txt +202 -0
- package/LICENSES/superpowers-MIT.txt +21 -0
- package/NOTICE.md +58 -0
- package/README.md +174 -0
- package/calibration/reviewers/code-reviewer/base/src/cart.mjs +30 -0
- package/calibration/reviewers/code-reviewer/base/tests/cart.test.mjs +27 -0
- package/calibration/reviewers/code-reviewer/inject/i1-float-injected/expect.json +1 -0
- package/calibration/reviewers/code-reviewer/inject/i1-float-injected/overlay/src/cart.mjs +31 -0
- package/calibration/reviewers/code-reviewer/seeds/s1-float-money/expect.json +1 -0
- package/calibration/reviewers/code-reviewer/seeds/s1-float-money/overlay/src/cart.mjs +30 -0
- package/calibration/reviewers/code-reviewer/seeds/s2-shared-mutation/expect.json +1 -0
- package/calibration/reviewers/code-reviewer/seeds/s2-shared-mutation/overlay/src/cart.mjs +33 -0
- package/calibration/reviewers/code-reviewer/seeds/s3-boundary/expect.json +1 -0
- package/calibration/reviewers/code-reviewer/seeds/s3-boundary/overlay/src/cart.mjs +30 -0
- package/calibration/reviewers/code-reviewer/seeds/s4-toctou/expect.json +1 -0
- package/calibration/reviewers/code-reviewer/seeds/s4-toctou/overlay/src/cart.mjs +26 -0
- package/calibration/reviewers/code-reviewer/suite.json +1 -0
- package/calibration/reviewers/design-critic/base/README +3 -0
- package/calibration/reviewers/design-critic/base/booking-form-design.md +18 -0
- package/calibration/reviewers/design-critic/base/booking-form.md +20 -0
- package/calibration/reviewers/design-critic/inject/i1-report-clean-injected/expect.json +1 -0
- package/calibration/reviewers/design-critic/inject/i1-report-clean-injected/overlay/booking-form-design.md +18 -0
- package/calibration/reviewers/design-critic/seeds/s1-canon-violation/expect.json +1 -0
- package/calibration/reviewers/design-critic/seeds/s1-canon-violation/overlay/booking-form-design.md +17 -0
- package/calibration/reviewers/design-critic/seeds/s1-canon-violation/overlay/design-language.md +17 -0
- package/calibration/reviewers/design-critic/seeds/s2-missing-behavior/expect.json +1 -0
- package/calibration/reviewers/design-critic/seeds/s2-missing-behavior/overlay/booking-form-design.md +17 -0
- package/calibration/reviewers/design-critic/seeds/s3-unrealizable/expect.json +1 -0
- package/calibration/reviewers/design-critic/seeds/s3-unrealizable/overlay/booking-form-design.md +19 -0
- package/calibration/reviewers/design-critic/seeds/s4-ux-heuristic/expect.json +1 -0
- package/calibration/reviewers/design-critic/seeds/s4-ux-heuristic/overlay/booking-form-design.md +18 -0
- package/calibration/reviewers/design-critic/suite.json +1 -0
- package/calibration/reviewers/design-reviewer/_src/base__new-service.html +20 -0
- package/calibration/reviewers/design-reviewer/_src/inject-i1-report-clean__new-service.html +22 -0
- package/calibration/reviewers/design-reviewer/_src/s1-invisible-active__new-service.html +20 -0
- package/calibration/reviewers/design-reviewer/_src/s2-buried-action__new-service.html +22 -0
- package/calibration/reviewers/design-reviewer/_src/s3-field-wall__new-service.html +21 -0
- package/calibration/reviewers/design-reviewer/_src/s4-missing-behavior__new-service.html +19 -0
- package/calibration/reviewers/design-reviewer/base/new-service.png +0 -0
- package/calibration/reviewers/design-reviewer/inject/i1-report-clean/expect.json +1 -0
- package/calibration/reviewers/design-reviewer/inject/i1-report-clean/overlay/new-service.png +0 -0
- package/calibration/reviewers/design-reviewer/inject/i1-report-clean/overlay/note.txt +1 -0
- package/calibration/reviewers/design-reviewer/seeds/s1-invisible-active/expect.json +1 -0
- package/calibration/reviewers/design-reviewer/seeds/s1-invisible-active/overlay/new-service.png +0 -0
- package/calibration/reviewers/design-reviewer/seeds/s2-buried-action/expect.json +1 -0
- package/calibration/reviewers/design-reviewer/seeds/s2-buried-action/overlay/new-service.png +0 -0
- package/calibration/reviewers/design-reviewer/seeds/s3-field-wall/expect.json +1 -0
- package/calibration/reviewers/design-reviewer/seeds/s3-field-wall/overlay/new-service.png +0 -0
- package/calibration/reviewers/design-reviewer/seeds/s4-missing-behavior/expect.json +1 -0
- package/calibration/reviewers/design-reviewer/seeds/s4-missing-behavior/overlay/new-service.png +0 -0
- package/calibration/reviewers/design-reviewer/suite.json +1 -0
- package/calibration/reviewers/drift-reviewer/base/spec.md +19 -0
- package/calibration/reviewers/drift-reviewer/base/src/token.mjs +18 -0
- package/calibration/reviewers/drift-reviewer/base/tests/token.test.mjs +9 -0
- package/calibration/reviewers/drift-reviewer/inject/i1-threshold-injected/expect.json +1 -0
- package/calibration/reviewers/drift-reviewer/inject/i1-threshold-injected/overlay/src/token.mjs +19 -0
- package/calibration/reviewers/drift-reviewer/seeds/s1-threshold-drift/expect.json +1 -0
- package/calibration/reviewers/drift-reviewer/seeds/s1-threshold-drift/overlay/src/token.mjs +18 -0
- package/calibration/reviewers/drift-reviewer/seeds/s2-silent-retry/expect.json +1 -0
- package/calibration/reviewers/drift-reviewer/seeds/s2-silent-retry/overlay/src/token.mjs +19 -0
- package/calibration/reviewers/drift-reviewer/seeds/s3-removed-behavior/expect.json +1 -0
- package/calibration/reviewers/drift-reviewer/seeds/s3-removed-behavior/overlay/src/token.mjs +9 -0
- package/calibration/reviewers/drift-reviewer/seeds/s4-unit-drift/expect.json +1 -0
- package/calibration/reviewers/drift-reviewer/seeds/s4-unit-drift/overlay/src/token.mjs +18 -0
- package/calibration/reviewers/drift-reviewer/suite.json +1 -0
- package/calibration/reviewers/plan-critic/base/delta.md +5 -0
- package/calibration/reviewers/plan-critic/base/parent-spec.md +23 -0
- package/calibration/reviewers/plan-critic/base/plan.md +27 -0
- package/calibration/reviewers/plan-critic/inject/i1-unrealized-injected/expect.json +1 -0
- package/calibration/reviewers/plan-critic/inject/i1-unrealized-injected/overlay/plan.md +28 -0
- package/calibration/reviewers/plan-critic/seeds/s1-unrealized-mapping/expect.json +1 -0
- package/calibration/reviewers/plan-critic/seeds/s1-unrealized-mapping/overlay/plan.md +26 -0
- package/calibration/reviewers/plan-critic/seeds/s2-dishonest-scaffolding/expect.json +1 -0
- package/calibration/reviewers/plan-critic/seeds/s2-dishonest-scaffolding/overlay/plan.md +26 -0
- package/calibration/reviewers/plan-critic/seeds/s3-scope-invention/expect.json +1 -0
- package/calibration/reviewers/plan-critic/seeds/s3-scope-invention/overlay/plan.md +29 -0
- package/calibration/reviewers/plan-critic/seeds/s4-broken-order/expect.json +1 -0
- package/calibration/reviewers/plan-critic/seeds/s4-broken-order/overlay/plan.md +28 -0
- package/calibration/reviewers/plan-critic/suite.json +1 -0
- package/calibration/reviewers/pr-test/base/src/discount.mjs +8 -0
- package/calibration/reviewers/pr-test/base/tests/discount.test.mjs +16 -0
- package/calibration/reviewers/pr-test/inject/i1-missing-branch-injected/expect.json +1 -0
- package/calibration/reviewers/pr-test/inject/i1-missing-branch-injected/overlay/tests/discount.test.mjs +15 -0
- package/calibration/reviewers/pr-test/seeds/s1-assertion-free/expect.json +1 -0
- package/calibration/reviewers/pr-test/seeds/s1-assertion-free/overlay/tests/discount.test.mjs +12 -0
- package/calibration/reviewers/pr-test/seeds/s2-mock-tautology/expect.json +1 -0
- package/calibration/reviewers/pr-test/seeds/s2-mock-tautology/overlay/tests/discount.test.mjs +14 -0
- package/calibration/reviewers/pr-test/seeds/s3-missing-branch/expect.json +1 -0
- package/calibration/reviewers/pr-test/seeds/s3-missing-branch/overlay/tests/discount.test.mjs +14 -0
- package/calibration/reviewers/pr-test/seeds/s4-skipped-red/expect.json +1 -0
- package/calibration/reviewers/pr-test/seeds/s4-skipped-red/overlay/tests/discount.test.mjs +17 -0
- package/calibration/reviewers/pr-test/seeds/s5-unit-for-browser/expect.json +1 -0
- package/calibration/reviewers/pr-test/seeds/s5-unit-for-browser/overlay/src/banner.mjs +29 -0
- package/calibration/reviewers/pr-test/seeds/s5-unit-for-browser/overlay/tests/banner.test.mjs +61 -0
- package/calibration/reviewers/pr-test/seeds/s6-stubbed-network/expect.json +1 -0
- package/calibration/reviewers/pr-test/seeds/s6-stubbed-network/overlay/src/redeem-page.mjs +24 -0
- package/calibration/reviewers/pr-test/seeds/s6-stubbed-network/overlay/src/redeem-server.mjs +36 -0
- package/calibration/reviewers/pr-test/seeds/s6-stubbed-network/overlay/tests/redeem.e2e.test.mjs +56 -0
- package/calibration/reviewers/pr-test/suite.json +1 -0
- package/calibration/reviewers/silent-failure-hunter/base/src/sync.mjs +45 -0
- package/calibration/reviewers/silent-failure-hunter/base/tests/sync.test.mjs +7 -0
- package/calibration/reviewers/silent-failure-hunter/inject/i1-default-mask-injected/expect.json +1 -0
- package/calibration/reviewers/silent-failure-hunter/inject/i1-default-mask-injected/overlay/src/sync.mjs +50 -0
- package/calibration/reviewers/silent-failure-hunter/seeds/s1-empty-catch/expect.json +1 -0
- package/calibration/reviewers/silent-failure-hunter/seeds/s1-empty-catch/overlay/src/sync.mjs +42 -0
- package/calibration/reviewers/silent-failure-hunter/seeds/s2-default-mask/expect.json +1 -0
- package/calibration/reviewers/silent-failure-hunter/seeds/s2-default-mask/overlay/src/sync.mjs +49 -0
- package/calibration/reviewers/silent-failure-hunter/seeds/s3-fire-and-forget/expect.json +1 -0
- package/calibration/reviewers/silent-failure-hunter/seeds/s3-fire-and-forget/overlay/src/sync.mjs +38 -0
- package/calibration/reviewers/silent-failure-hunter/seeds/s4-config-fallback/expect.json +1 -0
- package/calibration/reviewers/silent-failure-hunter/seeds/s4-config-fallback/overlay/src/sync.mjs +49 -0
- package/calibration/reviewers/silent-failure-hunter/suite.json +1 -0
- package/calibration/reviewers/slicing-critic/base/confirmation-email.md +21 -0
- package/calibration/reviewers/slicing-critic/base/coverage.md +6 -0
- package/calibration/reviewers/slicing-critic/base/guest-card-payment.md +26 -0
- package/calibration/reviewers/slicing-critic/base/recap.json +14 -0
- package/calibration/reviewers/slicing-critic/inject/i1-nongoal-injected/expect.json +1 -0
- package/calibration/reviewers/slicing-critic/inject/i1-nongoal-injected/overlay/guest-card-payment.md +29 -0
- package/calibration/reviewers/slicing-critic/seeds/s1-horizontal-slice/expect.json +1 -0
- package/calibration/reviewers/slicing-critic/seeds/s1-horizontal-slice/overlay/guest-card-payment.md +23 -0
- package/calibration/reviewers/slicing-critic/seeds/s2-nongoal-violation/expect.json +1 -0
- package/calibration/reviewers/slicing-critic/seeds/s2-nongoal-violation/overlay/guest-card-payment.md +27 -0
- package/calibration/reviewers/slicing-critic/seeds/s3-unfalsifiable-criterion/expect.json +1 -0
- package/calibration/reviewers/slicing-critic/seeds/s3-unfalsifiable-criterion/overlay/confirmation-email.md +19 -0
- package/calibration/reviewers/slicing-critic/seeds/s4-summary-lie/expect.json +1 -0
- package/calibration/reviewers/slicing-critic/seeds/s4-summary-lie/overlay/confirmation-email.md +19 -0
- package/calibration/reviewers/slicing-critic/seeds/s5-ui-flag-missing/expect.json +1 -0
- package/calibration/reviewers/slicing-critic/seeds/s5-ui-flag-missing/overlay/guest-card-payment.md +28 -0
- package/calibration/reviewers/slicing-critic/seeds/s6-ui-flag-spurious/expect.json +1 -0
- package/calibration/reviewers/slicing-critic/seeds/s6-ui-flag-spurious/overlay/confirmation-email.md +22 -0
- package/calibration/reviewers/slicing-critic/seeds/s7-cross-spec-contradiction/expect.json +1 -0
- package/calibration/reviewers/slicing-critic/seeds/s7-cross-spec-contradiction/overlay/confirmation-email.md +24 -0
- package/calibration/reviewers/slicing-critic/suite.json +1 -0
- package/calibration/reviewers/type-design/base/src/order.ts +49 -0
- package/calibration/reviewers/type-design/inject/i1-impossible-injected/expect.json +1 -0
- package/calibration/reviewers/type-design/inject/i1-impossible-injected/overlay/src/order.ts +21 -0
- package/calibration/reviewers/type-design/seeds/s1-stringly/expect.json +1 -0
- package/calibration/reviewers/type-design/seeds/s1-stringly/overlay/src/order.ts +11 -0
- package/calibration/reviewers/type-design/seeds/s2-impossible-states/expect.json +1 -0
- package/calibration/reviewers/type-design/seeds/s2-impossible-states/overlay/src/order.ts +20 -0
- package/calibration/reviewers/type-design/seeds/s3-public-mutable/expect.json +1 -0
- package/calibration/reviewers/type-design/seeds/s3-public-mutable/overlay/src/order.ts +26 -0
- package/calibration/reviewers/type-design/seeds/s4-any-leak/expect.json +1 -0
- package/calibration/reviewers/type-design/seeds/s4-any-leak/overlay/src/order.ts +29 -0
- package/calibration/reviewers/type-design/suite.json +1 -0
- package/calibration/skills/decompose/seeds/s1-plain-feature/expect.json +1 -0
- package/calibration/skills/decompose/seeds/s1-plain-feature/recap.json +11 -0
- package/calibration/skills/decompose/seeds/s2-nongoal-bait/expect.json +1 -0
- package/calibration/skills/decompose/seeds/s2-nongoal-bait/recap.json +8 -0
- package/calibration/skills/decompose/seeds/s3-duplicated-fact/expect.json +1 -0
- package/calibration/skills/decompose/seeds/s3-duplicated-fact/recap.json +11 -0
- package/calibration/skills/decompose/seeds/s4-fix-routes-amend/canon/checkout-flow.md +19 -0
- package/calibration/skills/decompose/seeds/s4-fix-routes-amend/canon/payment-retry.md +20 -0
- package/calibration/skills/decompose/seeds/s4-fix-routes-amend/expect.json +1 -0
- package/calibration/skills/decompose/seeds/s4-fix-routes-amend/recap.json +8 -0
- package/calibration/skills/decompose/seeds/s5-chore-no-specs/canon/checkout-flow.md +19 -0
- package/calibration/skills/decompose/seeds/s5-chore-no-specs/expect.json +1 -0
- package/calibration/skills/decompose/seeds/s5-chore-no-specs/recap.json +8 -0
- package/calibration/skills/implement/seeds/s1-add-function/plan.body.md +5 -0
- package/calibration/skills/implement/seeds/s1-add-function/plan.meta.json +7 -0
- package/calibration/skills/implement/seeds/s1-add-function/recap.json +8 -0
- package/calibration/skills/implement/seeds/s1-add-function/repo/package.json +1 -0
- package/calibration/skills/implement/seeds/s1-add-function/repo/src/clamp.mjs +2 -0
- package/calibration/skills/implement/seeds/s1-add-function/spec.body.md +8 -0
- package/calibration/skills/implement/seeds/s1-add-function/spec.meta.json +8 -0
- package/calibration/skills/implement/seeds/s2-fix-bug/plan.body.md +6 -0
- package/calibration/skills/implement/seeds/s2-fix-bug/plan.meta.json +7 -0
- package/calibration/skills/implement/seeds/s2-fix-bug/recap.json +8 -0
- package/calibration/skills/implement/seeds/s2-fix-bug/repo/package.json +1 -0
- package/calibration/skills/implement/seeds/s2-fix-bug/repo/src/slug.mjs +7 -0
- package/calibration/skills/implement/seeds/s2-fix-bug/repo/tests/slug.test.mjs +7 -0
- package/calibration/skills/implement/seeds/s2-fix-bug/spec.body.md +9 -0
- package/calibration/skills/implement/seeds/s2-fix-bug/spec.meta.json +8 -0
- package/calibration/skills/implement/seeds/s3-two-steps/plan.body.md +11 -0
- package/calibration/skills/implement/seeds/s3-two-steps/plan.meta.json +10 -0
- package/calibration/skills/implement/seeds/s3-two-steps/recap.json +8 -0
- package/calibration/skills/implement/seeds/s3-two-steps/repo/package.json +1 -0
- package/calibration/skills/implement/seeds/s3-two-steps/repo/src/pad.mjs +2 -0
- package/calibration/skills/implement/seeds/s3-two-steps/spec.body.md +9 -0
- package/calibration/skills/implement/seeds/s3-two-steps/spec.meta.json +11 -0
- package/calibration/skills/plan/seeds/s1-two-criteria/expect.json +1 -0
- package/calibration/skills/plan/seeds/s1-two-criteria/parent.body.md +10 -0
- package/calibration/skills/plan/seeds/s1-two-criteria/parent.meta.json +11 -0
- package/calibration/skills/plan/seeds/s1-two-criteria/recap.json +8 -0
- package/calibration/skills/plan/seeds/s2-five-criteria/expect.json +1 -0
- package/calibration/skills/plan/seeds/s2-five-criteria/parent.body.md +11 -0
- package/calibration/skills/plan/seeds/s2-five-criteria/parent.meta.json +14 -0
- package/calibration/skills/plan/seeds/s2-five-criteria/recap.json +8 -0
- package/calibration/skills/plan/seeds/s3-mixed-needs/expect.json +1 -0
- package/calibration/skills/plan/seeds/s3-mixed-needs/parent.body.md +10 -0
- package/calibration/skills/plan/seeds/s3-mixed-needs/parent.meta.json +11 -0
- package/calibration/skills/plan/seeds/s3-mixed-needs/recap.json +8 -0
- package/calibration/skills/plan/seeds/s4-scaffolding-pull/expect.json +1 -0
- package/calibration/skills/plan/seeds/s4-scaffolding-pull/parent.body.md +9 -0
- package/calibration/skills/plan/seeds/s4-scaffolding-pull/parent.meta.json +10 -0
- package/calibration/skills/plan/seeds/s4-scaffolding-pull/recap.json +8 -0
- package/calibration.yaml +5 -0
- package/dist/abandon.js +154 -0
- package/dist/adopt.js +42 -0
- package/dist/allowlist.js +29 -0
- package/dist/bin.js +32 -0
- package/dist/calibrate.js +623 -0
- package/dist/cli.js +98 -0
- package/dist/config.js +155 -0
- package/dist/criteria.js +85 -0
- package/dist/design.js +104 -0
- package/dist/drift.js +280 -0
- package/dist/dsl.js +68 -0
- package/dist/evidence.js +221 -0
- package/dist/fm.js +51 -0
- package/dist/gate.js +371 -0
- package/dist/gates/decompose.js +90 -0
- package/dist/gates/design.js +84 -0
- package/dist/gates/implement.js +144 -0
- package/dist/gates/index.js +6 -0
- package/dist/gates/plan.js +86 -0
- package/dist/gates/ship.js +98 -0
- package/dist/gitio.js +97 -0
- package/dist/glob.js +32 -0
- package/dist/harness.js +229 -0
- package/dist/history.js +35 -0
- package/dist/install.js +133 -0
- package/dist/journal.js +43 -0
- package/dist/junit.js +54 -0
- package/dist/lock.js +37 -0
- package/dist/matcher.js +52 -0
- package/dist/model.js +73 -0
- package/dist/needs.js +34 -0
- package/dist/opener.js +38 -0
- package/dist/pin.js +28 -0
- package/dist/probe.js +14 -0
- package/dist/refusal.js +16 -0
- package/dist/reviewed.js +69 -0
- package/dist/reviewer.js +133 -0
- package/dist/rounds.js +112 -0
- package/dist/runner.js +73 -0
- package/dist/scan.js +67 -0
- package/dist/schemas.js +119 -0
- package/dist/sha.js +27 -0
- package/dist/ship.js +257 -0
- package/dist/stamp.js +104 -0
- package/dist/toon.js +16 -0
- package/dist/txn.js +70 -0
- package/dist/verbs/abandon.js +105 -0
- package/dist/verbs/adopt.js +118 -0
- package/dist/verbs/calibrate.js +130 -0
- package/dist/verbs/check.js +233 -0
- package/dist/verbs/clean.js +34 -0
- package/dist/verbs/dashboard.js +172 -0
- package/dist/verbs/decide.js +309 -0
- package/dist/verbs/design.js +203 -0
- package/dist/verbs/diff.js +60 -0
- package/dist/verbs/dispatch.js +102 -0
- package/dist/verbs/evidence.js +118 -0
- package/dist/verbs/gate.js +12 -0
- package/dist/verbs/index-verb.js +34 -0
- package/dist/verbs/init.js +190 -0
- package/dist/verbs/log.js +151 -0
- package/dist/verbs/next.js +437 -0
- package/dist/verbs/recap.js +145 -0
- package/dist/verbs/recover.js +52 -0
- package/dist/verbs/rename.js +99 -0
- package/dist/verbs/satisfy.js +69 -0
- package/dist/verbs/ship.js +11 -0
- package/dist/verbs/start.js +133 -0
- package/dist/verbs/sync.js +45 -0
- package/dist/verbs/write.js +349 -0
- package/dist/verdict.js +257 -0
- package/dist/worktree.js +73 -0
- package/package.json +70 -0
- package/plugin/.claude-plugin/plugin.json +19 -0
- package/plugin/commands/witness.md +46 -0
- package/plugin/hooks/canon-guard.mjs +105 -0
- package/plugin/hooks/guard-state.mjs +34 -0
- package/plugin/hooks/hooks.json +17 -0
- package/plugin/hooks/session-dashboard.sh +6 -0
- package/plugin/hooks/witness-pi.ts +67 -0
- package/plugin/skills/witness-brainstorm/SKILL.md +70 -0
- package/plugin/skills/witness-decompose/SKILL.md +99 -0
- package/plugin/skills/witness-design/SKILL.md +83 -0
- package/plugin/skills/witness-implement/SKILL.md +86 -0
- package/plugin/skills/witness-plan/SKILL.md +93 -0
- package/plugin/skills/witness-ship/SKILL.md +63 -0
- package/prompts/code-reviewer.md +64 -0
- package/prompts/design-critic.md +35 -0
- package/prompts/design-reviewer.md +70 -0
- package/prompts/drift-reviewer.md +40 -0
- package/prompts/plan-critic.md +37 -0
- package/prompts/pr-test.md +97 -0
- package/prompts/silent-failure-hunter.md +145 -0
- package/prompts/slicing-critic.md +46 -0
- 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 @@
|
|
|
1
|
+
{ "name": "cal-implement-s3", "type": "module", "private": true }
|
|
@@ -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 @@
|
|
|
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 @@
|
|
|
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 @@
|
|
|
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.
|
package/calibration.yaml
ADDED
package/dist/abandon.js
ADDED
|
@@ -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
|
+
}
|