@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,190 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { parseArgs } from 'node:util';
|
|
4
|
+
import { EXIT } from '../cli.js';
|
|
5
|
+
import { configPath } from '../config.js';
|
|
6
|
+
import { writeDoc } from '../fm.js';
|
|
7
|
+
import { commitWithTrailer, primaryRoot, tryGit } from '../gitio.js';
|
|
8
|
+
import { resolveHarness } from '../harness.js';
|
|
9
|
+
import { installPayload, mergeSettings, preflightPayload, readIfExists, recordHarness, writeSettings, } from '../install.js';
|
|
10
|
+
import { probe } from '../probe.js';
|
|
11
|
+
import { acquireLock } from '../lock.js';
|
|
12
|
+
import { renderRefusal, v } from '../refusal.js';
|
|
13
|
+
import { kv } from '../toon.js';
|
|
14
|
+
const DEFAULT_CONFIG = `# witness.config.yaml — the whole surface
|
|
15
|
+
schema: 1
|
|
16
|
+
# paths: { specs: docs/specs, plans: docs/plans } # optional canon roots (defaults: specs, plans); git mv existing docs when changing
|
|
17
|
+
# docs: # repo docs registry — enumerated keys, unknown keys refused
|
|
18
|
+
# conventions: [docs/code/architecture.md] # → injected into code-reviewer at implement + ship (cache-keyed)
|
|
19
|
+
gates:
|
|
20
|
+
model: claude-fable-5 # reviewer model pin — exact id, aliases refused
|
|
21
|
+
# any gate block may pin its own model (wins over the global pin), e.g.
|
|
22
|
+
# decompose: { reviewers: [slicing-critic], model: claude-opus-4-8 }
|
|
23
|
+
decompose: { reviewers: [slicing-critic] }
|
|
24
|
+
plan: { reviewers: [plan-critic] }
|
|
25
|
+
implement:
|
|
26
|
+
reviewers: # per change class; a plain list = all classes
|
|
27
|
+
feature: [code-reviewer, silent-failure-hunter, type-design, pr-test, design-reviewer]
|
|
28
|
+
fix: [code-reviewer, silent-failure-hunter, design-reviewer]
|
|
29
|
+
chore: [code-reviewer]
|
|
30
|
+
ship: { reviewers: [drift-reviewer, code-reviewer] }
|
|
31
|
+
criteria:
|
|
32
|
+
runner: 'npm test -- -t "@spec:{id}"' # or: runner: full-suite + report: junit:**/reports/junit.xml
|
|
33
|
+
ship: { test: "npm test", lint: "npm run lint", branch: main }
|
|
34
|
+
`;
|
|
35
|
+
const GITIGNORE_BLOCK = `# witness local (never committed)
|
|
36
|
+
.witness/lock
|
|
37
|
+
.witness/txn.json
|
|
38
|
+
.witness/allow.json
|
|
39
|
+
.witness/calibration.local.yaml
|
|
40
|
+
.witness/screens/
|
|
41
|
+
`;
|
|
42
|
+
const PRINCIPLES_BODY = `# Principles
|
|
43
|
+
|
|
44
|
+
Repo-wide rules and trade-offs. Amend via \`witness write\`; chores may name
|
|
45
|
+
this doc as their plan's parent.
|
|
46
|
+
`;
|
|
47
|
+
const USAGE = 'usage: witness init [--agent claude-code|pi|auto]';
|
|
48
|
+
// `--agent` is what makes a second run legal. A bare `witness init` keeps its tested
|
|
49
|
+
// contract — it refuses `already-initialized` — because scaffolding twice is a mistake.
|
|
50
|
+
// Installing a payload set twice is not: it is how a Claude Code repo gains Pi support,
|
|
51
|
+
// and how a half-finished install is completed.
|
|
52
|
+
export async function run(ctx, argv = []) {
|
|
53
|
+
let agent;
|
|
54
|
+
try {
|
|
55
|
+
agent = parseArgs({ args: argv, options: { agent: { type: 'string' } }, allowPositionals: false })
|
|
56
|
+
.values.agent;
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
ctx.err(USAGE);
|
|
60
|
+
return EXIT.REFUSED;
|
|
61
|
+
}
|
|
62
|
+
const rootRes = primaryRoot(ctx.cwd);
|
|
63
|
+
if (!rootRes.ok) {
|
|
64
|
+
renderRefusal(rootRes.violations).forEach(ctx.err);
|
|
65
|
+
return EXIT.REFUSED;
|
|
66
|
+
}
|
|
67
|
+
const root = rootRes.value;
|
|
68
|
+
const scaffolded = existsSync(configPath(root));
|
|
69
|
+
if (scaffolded && agent === undefined) {
|
|
70
|
+
renderRefusal([v('witness.config.yaml', 'already-initialized', 'config exists', 'witness is already set up here — witness init --agent <name> installs an agent payload set')]).forEach(ctx.err);
|
|
71
|
+
return EXIT.REFUSED;
|
|
72
|
+
}
|
|
73
|
+
let harness;
|
|
74
|
+
if (agent !== undefined) {
|
|
75
|
+
// `auto` is the one value that consults the detection rungs; a named agent is a
|
|
76
|
+
// claim and refuses when false, listing the harnesses that exist.
|
|
77
|
+
const hxR = agent === 'auto'
|
|
78
|
+
? resolveHarness(ctx.env, {})
|
|
79
|
+
: resolveHarness({ WITNESS_HARNESS: agent }, {});
|
|
80
|
+
if (!hxR.ok) {
|
|
81
|
+
renderRefusal(hxR.violations.map((x) => ({ ...x, field: '--agent' }))).forEach(ctx.err);
|
|
82
|
+
return EXIT.REFUSED;
|
|
83
|
+
}
|
|
84
|
+
harness = hxR.value.harness;
|
|
85
|
+
// Revision 6: pre-flight before the lock and before any scaffold write, so a
|
|
86
|
+
// refusal here leaves the repo exactly as it was.
|
|
87
|
+
const pre = preflightPayload(root, harness);
|
|
88
|
+
if (!pre.ok) {
|
|
89
|
+
renderRefusal(pre.violations).forEach(ctx.err);
|
|
90
|
+
return EXIT.REFUSED;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
const lock = acquireLock(root);
|
|
94
|
+
if (!lock.ok) {
|
|
95
|
+
renderRefusal(lock.violations).forEach(ctx.err);
|
|
96
|
+
return EXIT.BLOCKED;
|
|
97
|
+
}
|
|
98
|
+
try {
|
|
99
|
+
const files = [];
|
|
100
|
+
if (!scaffolded) {
|
|
101
|
+
writeFileSync(configPath(root), DEFAULT_CONFIG);
|
|
102
|
+
mkdirSync(join(root, 'plans'), { recursive: true });
|
|
103
|
+
mkdirSync(join(root, '.witness', 'journal'), { recursive: true });
|
|
104
|
+
writeFileSync(join(root, 'plans', '.gitkeep'), '');
|
|
105
|
+
writeFileSync(join(root, '.witness', 'journal', '.gitkeep'), '');
|
|
106
|
+
const gi = join(root, '.gitignore');
|
|
107
|
+
const existing = existsSync(gi) ? readFileSync(gi, 'utf8') : '';
|
|
108
|
+
if (!existing.includes('.witness/lock')) {
|
|
109
|
+
writeFileSync(gi, existing + (existing && !existing.endsWith('\n') ? '\n' : '') + GITIGNORE_BLOCK);
|
|
110
|
+
}
|
|
111
|
+
writeDoc(join(root, 'specs', 'principles.md'), {
|
|
112
|
+
meta: { id: 'principles', type: 'principles', status: 'draft', depends: [], needs: [] },
|
|
113
|
+
body: PRINCIPLES_BODY,
|
|
114
|
+
});
|
|
115
|
+
files.push('witness.config.yaml', '.gitignore', 'specs/principles.md', 'plans/.gitkeep', '.witness/journal/.gitkeep');
|
|
116
|
+
}
|
|
117
|
+
let synced;
|
|
118
|
+
if (harness !== undefined) {
|
|
119
|
+
const payload = installPayload(root, harness);
|
|
120
|
+
if (!payload.ok) {
|
|
121
|
+
renderRefusal(payload.violations).forEach(ctx.err);
|
|
122
|
+
return EXIT.REFUSED;
|
|
123
|
+
}
|
|
124
|
+
synced = payload.value;
|
|
125
|
+
files.push(...synced.written, ...synced.restamped);
|
|
126
|
+
if (harness.settings !== undefined) {
|
|
127
|
+
const merged = mergeSettings(readIfExists(root, harness.settings));
|
|
128
|
+
if (merged.changed) {
|
|
129
|
+
writeSettings(root, harness.settings, merged.text);
|
|
130
|
+
files.push(harness.settings);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
const recorded = recordHarness(readFileSync(configPath(root), 'utf8'), harness.name);
|
|
134
|
+
if (recorded.changed) {
|
|
135
|
+
writeFileSync(configPath(root), recorded.text);
|
|
136
|
+
if (!files.includes('witness.config.yaml'))
|
|
137
|
+
files.push('witness.config.yaml');
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
// Nothing to COMMIT is the same legal silent success as nothing to write. A
|
|
141
|
+
// restamp can restore a file to exactly its HEAD content (an uncommitted pin edit),
|
|
142
|
+
// and `git commit --only` over a pathspec with no diff exits non-zero — which threw
|
|
143
|
+
// out of gitio until Task 9's manual pass caught it. Ask git what actually changed
|
|
144
|
+
// rather than assuming every file we touched is dirty.
|
|
145
|
+
const dirty = files.length > 0
|
|
146
|
+
? new Set(tryGit(root, 'status', '--porcelain', '--', ...files).out
|
|
147
|
+
.split('\n').filter((l) => l !== '').map((l) => l.slice(3).trim()))
|
|
148
|
+
: new Set();
|
|
149
|
+
const staged = files.filter((rel) => dirty.has(rel));
|
|
150
|
+
if (staged.length > 0) {
|
|
151
|
+
const subject = scaffolded
|
|
152
|
+
? `init(${harness?.name ?? '?'}): agent payloads`
|
|
153
|
+
: 'init: witness scaffold';
|
|
154
|
+
const commit = commitWithTrailer(root, staged, subject);
|
|
155
|
+
if (!commit.ok) {
|
|
156
|
+
renderRefusal(commit.violations).forEach(ctx.err);
|
|
157
|
+
return EXIT.REFUSED;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
if (!scaffolded) {
|
|
161
|
+
ctx.out(kv('initialized', root));
|
|
162
|
+
ctx.out(kv('canon', 'specs/principles.md (draft)'));
|
|
163
|
+
}
|
|
164
|
+
if (harness !== undefined) {
|
|
165
|
+
ctx.out(kv('agent', harness.name));
|
|
166
|
+
ctx.out(kv('payload', files.length > 0 ? files.join(' · ') : 'already installed'));
|
|
167
|
+
// Never silent: a file we chose not to touch is a file that may now be a version
|
|
168
|
+
// behind the CLI running this command.
|
|
169
|
+
if (synced && synced.modified.length > 0) {
|
|
170
|
+
ctx.out(kv('payload-modified', `${synced.modified.join(' · ')} — locally edited, left alone`));
|
|
171
|
+
}
|
|
172
|
+
// Revision 4: surface the prerequisite at the one moment the human can act on it
|
|
173
|
+
// cheaply. A note, not a refusal — a machine may legitimately author without ever
|
|
174
|
+
// running gates, and blocking init there would be wrong.
|
|
175
|
+
if (!probe('claude', ['--version'], ctx.env)) {
|
|
176
|
+
ctx.out('note: the claude CLI is required for gates on every harness — install and authenticate it');
|
|
177
|
+
}
|
|
178
|
+
// Revision 6: silent-guard prevention. Prompts are self-revealing when trust is
|
|
179
|
+
// declined (/witness simply is not there); the guard is not.
|
|
180
|
+
if (harness.name === 'pi') {
|
|
181
|
+
ctx.out('note: pi loads .pi/prompts and .pi/extensions only after the project is trusted — decline it and both /witness and the canon guard are absent');
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
ctx.out(scaffolded ? 'next: witness next' : 'next: witness recap --file <recap.json>');
|
|
185
|
+
return EXIT.OK;
|
|
186
|
+
}
|
|
187
|
+
finally {
|
|
188
|
+
lock.value();
|
|
189
|
+
}
|
|
190
|
+
}
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import { existsSync, readdirSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { parseArgs } from 'node:util';
|
|
4
|
+
import { EXIT } from '../cli.js';
|
|
5
|
+
import { git, primaryRoot, tryGit } from '../gitio.js';
|
|
6
|
+
import { journalRel, readStream, streamExists } from '../journal.js';
|
|
7
|
+
import { renderRefusal, v } from '../refusal.js';
|
|
8
|
+
import { short } from '../sha.js';
|
|
9
|
+
import { rows } from '../toon.js';
|
|
10
|
+
function detail(e) {
|
|
11
|
+
switch (e.t) {
|
|
12
|
+
case 'recap': {
|
|
13
|
+
const size = (k) => (Array.isArray(e[k]) ? e[k].length : 0);
|
|
14
|
+
return `class=${String(e.class)} goals=${size('goals')} non_goals=${size('non_goals')} slices=${size('slices')}`;
|
|
15
|
+
}
|
|
16
|
+
case 'write': {
|
|
17
|
+
const covers = Array.isArray(e.covers) && e.covers.length ? ` covers=${e.covers.join('+')}` : '';
|
|
18
|
+
return `artifact=${String(e.artifact)} sha=${short(String(e.sha))}${covers}`;
|
|
19
|
+
}
|
|
20
|
+
case 'write-refused': {
|
|
21
|
+
const pairs = Array.isArray(e.rules)
|
|
22
|
+
? e.rules.map((r) => `${r.field}:${r.rule}`).join('+')
|
|
23
|
+
: '';
|
|
24
|
+
return `artifact=${String(e.artifact)} rules=${pairs}`;
|
|
25
|
+
}
|
|
26
|
+
case 'gate-run': {
|
|
27
|
+
const g = e;
|
|
28
|
+
return `gate ${g.gate} r${g.round} ${g.outcome} @${g.reviewed_sha.slice(0, 7)}`;
|
|
29
|
+
}
|
|
30
|
+
case 'human-decision': {
|
|
31
|
+
const d = e;
|
|
32
|
+
return `${d.gate}: ${d.decision}${d.note ? ` — ${d.note.slice(0, 60)}` : ''}`;
|
|
33
|
+
}
|
|
34
|
+
case 'status': {
|
|
35
|
+
const s = e;
|
|
36
|
+
return `${s.from} → ${s.to} (${s.cause})`;
|
|
37
|
+
}
|
|
38
|
+
default: {
|
|
39
|
+
const { v: _v, t: _t, ...rest } = e;
|
|
40
|
+
return JSON.stringify(rest).slice(0, 100);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
const streamIds = (root) => {
|
|
45
|
+
const dir = join(root, '.witness', 'journal');
|
|
46
|
+
if (!existsSync(dir))
|
|
47
|
+
return [];
|
|
48
|
+
return readdirSync(dir)
|
|
49
|
+
.filter((f) => f.endsWith('.jsonl'))
|
|
50
|
+
.map((f) => f.slice(0, -'.jsonl'.length))
|
|
51
|
+
.sort();
|
|
52
|
+
};
|
|
53
|
+
function successorOf(root, id) {
|
|
54
|
+
const terminal = readStream(root, id).find((e) => e.t === 'status' && e.to === 'superseded');
|
|
55
|
+
return terminal?.by;
|
|
56
|
+
}
|
|
57
|
+
function predecessorsOf(root, id) {
|
|
58
|
+
return streamIds(root).filter((s) => successorOf(root, s) === id);
|
|
59
|
+
}
|
|
60
|
+
export function lineageChain(root, id) {
|
|
61
|
+
const chain = [id];
|
|
62
|
+
for (let cur = id;;) {
|
|
63
|
+
const prev = predecessorsOf(root, cur)[0];
|
|
64
|
+
if (!prev || chain.includes(prev))
|
|
65
|
+
break;
|
|
66
|
+
chain.unshift(prev);
|
|
67
|
+
cur = prev;
|
|
68
|
+
}
|
|
69
|
+
for (let cur = id;;) {
|
|
70
|
+
const next = successorOf(root, cur);
|
|
71
|
+
if (!next || chain.includes(next))
|
|
72
|
+
break;
|
|
73
|
+
chain.push(next);
|
|
74
|
+
cur = next;
|
|
75
|
+
}
|
|
76
|
+
return chain;
|
|
77
|
+
}
|
|
78
|
+
function commitIndex(root) {
|
|
79
|
+
const map = new Map();
|
|
80
|
+
git(root, 'rev-list', '--reverse', 'HEAD').split('\n').filter(Boolean)
|
|
81
|
+
.forEach((sha, i) => map.set(sha, i));
|
|
82
|
+
return map;
|
|
83
|
+
}
|
|
84
|
+
function lineOrigins(root, rel) {
|
|
85
|
+
const blame = tryGit(root, 'blame', '--porcelain', '--', rel);
|
|
86
|
+
if (!blame.ok)
|
|
87
|
+
return [];
|
|
88
|
+
const shas = [];
|
|
89
|
+
for (const line of blame.out.split('\n')) {
|
|
90
|
+
if (/^[0-9a-f]{40} \d+ \d+/.test(line))
|
|
91
|
+
shas.push(line.slice(0, 40));
|
|
92
|
+
}
|
|
93
|
+
return shas;
|
|
94
|
+
}
|
|
95
|
+
export function mergedRows(root) {
|
|
96
|
+
const order = commitIndex(root);
|
|
97
|
+
const rowsOut = [];
|
|
98
|
+
for (const stream of streamIds(root)) {
|
|
99
|
+
const entries = readStream(root, stream);
|
|
100
|
+
const origins = lineOrigins(root, journalRel(stream));
|
|
101
|
+
entries.forEach((entry, lineNo) => {
|
|
102
|
+
const sha = origins[lineNo];
|
|
103
|
+
rowsOut.push({ stream, entry, lineNo, idx: sha !== undefined ? (order.get(sha) ?? Number.MAX_SAFE_INTEGER) : Number.MAX_SAFE_INTEGER });
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
rowsOut.sort((a, b) => a.idx - b.idx || a.stream.localeCompare(b.stream) || a.lineNo - b.lineNo);
|
|
107
|
+
return rowsOut.map(({ stream, entry }) => ({ stream, entry }));
|
|
108
|
+
}
|
|
109
|
+
export async function run(ctx, argv) {
|
|
110
|
+
const { values, positionals } = parseArgs({
|
|
111
|
+
args: argv,
|
|
112
|
+
options: { all: { type: 'boolean' }, lineage: { type: 'boolean' } },
|
|
113
|
+
allowPositionals: true,
|
|
114
|
+
});
|
|
115
|
+
const id = positionals[0];
|
|
116
|
+
if (!values.all && !id) {
|
|
117
|
+
renderRefusal([v('id', 'required', 'absent', 'witness log <id> [--lineage] | witness log --all')]).forEach(ctx.err);
|
|
118
|
+
return EXIT.REFUSED;
|
|
119
|
+
}
|
|
120
|
+
const rootRes = primaryRoot(ctx.cwd);
|
|
121
|
+
if (!rootRes.ok) {
|
|
122
|
+
renderRefusal(rootRes.violations).forEach(ctx.err);
|
|
123
|
+
return EXIT.REFUSED;
|
|
124
|
+
}
|
|
125
|
+
const root = rootRes.value;
|
|
126
|
+
if (values.all) {
|
|
127
|
+
const merged = mergedRows(root);
|
|
128
|
+
rows('all', ['n', 'stream', 't', 'detail'], merged.map((r, i) => ({ n: i + 1, stream: r.stream, t: r.entry.t, detail: detail(r.entry) })))
|
|
129
|
+
.forEach(ctx.out);
|
|
130
|
+
return EXIT.OK;
|
|
131
|
+
}
|
|
132
|
+
if (values.lineage) {
|
|
133
|
+
// a lineage member (e.g. a re-slice successor) may have no gate-run/status
|
|
134
|
+
// activity of its own yet — walk the chain by journal existence on the *other*
|
|
135
|
+
// streams rather than requiring id's own stream to exist.
|
|
136
|
+
const chain = lineageChain(root, id);
|
|
137
|
+
ctx.out(`lineage: ${chain.join(' → ')}`);
|
|
138
|
+
const merged = chain.flatMap((sid) => readStream(root, sid).map((entry) => ({ stream: sid, entry })));
|
|
139
|
+
rows('lineage', ['n', 'stream', 't', 'detail'], merged.map((r, i) => ({ n: i + 1, stream: r.stream, t: r.entry.t, detail: detail(r.entry) })))
|
|
140
|
+
.forEach(ctx.out);
|
|
141
|
+
return EXIT.OK;
|
|
142
|
+
}
|
|
143
|
+
if (!streamExists(root, id)) {
|
|
144
|
+
renderRefusal([v('id', 'unknown-stream', id, 'an artifact or effort with a journal stream')]).forEach(ctx.err);
|
|
145
|
+
return EXIT.REFUSED;
|
|
146
|
+
}
|
|
147
|
+
const entries = readStream(root, id);
|
|
148
|
+
rows('entries', ['n', 't', 'detail'], entries.map((e, i) => ({ n: i + 1, t: e.t, detail: detail(e) })))
|
|
149
|
+
.forEach(ctx.out);
|
|
150
|
+
return EXIT.OK;
|
|
151
|
+
}
|