@accidentally-awesome-labs/opencode-bestest 0.4.3

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 (45) hide show
  1. package/README.md +36 -0
  2. package/bin/init.ts +126 -0
  3. package/bin/opencode-bestest +3 -0
  4. package/opencode-assets/agents/plan-checker.md +27 -0
  5. package/opencode-assets/agents/researcher.md +20 -0
  6. package/opencode-assets/agents/reviewer.md +22 -0
  7. package/opencode-assets/commands/bestest-go.md +5 -0
  8. package/opencode-assets/commands/bestest-status.md +5 -0
  9. package/package.json +30 -0
  10. package/payload/VERSION +1 -0
  11. package/payload/agents/plan-checker.md +25 -0
  12. package/payload/agents/researcher.md +17 -0
  13. package/payload/agents/reviewer.md +21 -0
  14. package/payload/hooks/guard.sh +111 -0
  15. package/payload/hooks/lock-guard.sh +118 -0
  16. package/payload/hooks/session-start.sh +63 -0
  17. package/payload/hooks/syntax-check.sh +96 -0
  18. package/payload/scripts/trace.sh +98 -0
  19. package/payload/skills/bestest-go/SKILL.md +37 -0
  20. package/payload/skills/bestest-status/SKILL.md +16 -0
  21. package/payload/skills/build/SKILL.md +29 -0
  22. package/payload/skills/debug/SKILL.md +20 -0
  23. package/payload/skills/interrogate/SKILL.md +48 -0
  24. package/payload/skills/interrogate/references/challenge-tactics.md +21 -0
  25. package/payload/skills/interrogate/references/question-protocol.md +21 -0
  26. package/payload/skills/plan/SKILL.md +25 -0
  27. package/payload/skills/plan/references/assets.md +15 -0
  28. package/payload/skills/research/SKILL.md +23 -0
  29. package/payload/skills/ship/SKILL.md +28 -0
  30. package/payload/skills/using-bestest/SKILL.md +20 -0
  31. package/payload/skills/using-bestest/references/claude-code.md +16 -0
  32. package/payload/skills/using-bestest/references/codex.md +17 -0
  33. package/payload/skills/using-bestest/references/opencode.md +18 -0
  34. package/payload/templates/DEBUG.md +17 -0
  35. package/payload/templates/DECISIONS.md +11 -0
  36. package/payload/templates/RUNBOOK.md +24 -0
  37. package/payload/templates/SPEC.md +35 -0
  38. package/payload/templates/STATE.md +20 -0
  39. package/payload/templates/UAT.md +10 -0
  40. package/payload/templates/report.md +15 -0
  41. package/payload/templates/task-card.md +24 -0
  42. package/src/guards.ts +80 -0
  43. package/src/index.ts +34 -0
  44. package/src/resolve.ts +22 -0
  45. package/src/session.ts +57 -0
package/README.md ADDED
@@ -0,0 +1,36 @@
1
+ # opencode-bestest
2
+
3
+ The OpenCode head of [the bestest plugin](https://github.com/accidentally-awesome-labs/the-bestest-agent) — one compact lifecycle (P0 interrogate → P5 ship) with *physically enforced* gates: spec freeze, TDD test locks, destructive-command guard, and drift detection.
4
+
5
+ ## Install (per project)
6
+
7
+ ```bash
8
+ cd your-project
9
+ bunx @accidentally-awesome-labs/opencode-bestest init # or npx
10
+ ```
11
+
12
+ `init` is idempotent (re-run it to update) and supports `--dry-run` and `--global` (skills/agents/commands into `~/.config/opencode/`). It:
13
+
14
+ - copies the lifecycle skills into `.opencode/skills/` (prefixed `bestest-*`)
15
+ - installs the plan-checker / researcher / reviewer agents and `/bestest-go` + `/bestest-status` commands
16
+ - copies the runtime payload (guard scripts, `trace.sh`, templates) to `.opencode/bestest/`
17
+ - wires `opencode.json`: the `@accidentally-awesome-labs/opencode-bestest` plugin (enforcement) and an `instructions` entry that passively injects `.bestest/.context.md` — the plugin-maintained digest of STATE + LOCKS + active card + DRIFT notice
18
+
19
+ Then open opencode and run `/bestest-go`.
20
+
21
+ ## What the plugin enforces
22
+
23
+ | Tool call | Script | Outcome |
24
+ |---|---|---|
25
+ | `bash` | `guard.sh` | destructive commands (rm -rf /, git reset --hard, force-push to main, …) → blocked |
26
+ | `write` / `edit` / `apply_patch` | `lock-guard.sh` | writes to the frozen SPEC or locked test files → blocked with the amendment path |
27
+ | same, after | `syntax-check.sh` | syntax errors fed straight back (advisory: never blocks when checkers are absent) |
28
+ | `session.idle` | `session-start.sh stop` | drift snapshot; next session's digest carries a DRIFT notice if the tree changed out-of-band |
29
+
30
+ The bash scripts are byte-identical to the ones the Claude Code and Codex heads run — one guard implementation, three harnesses. Guards **fail closed**: no bash (install Git Bash on Windows) means guarded tools are refused, not waved through.
31
+
32
+ Dev/source install (no npm): clone the repo, then in your project's `.opencode/plugins/bestest-dev.ts`:
33
+
34
+ ```ts
35
+ export { BestestPlugin } from "/path/to/the-bestest-agent/adapters/opencode/src/index"
36
+ ```
package/bin/init.ts ADDED
@@ -0,0 +1,126 @@
1
+ #!/usr/bin/env bun
2
+ // opencode-bestest installer: copies skills/agents/commands + runtime payload
3
+ // into a project (or --global), wires opencode.json. Idempotent; --dry-run.
4
+ import { cpSync, existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from "node:fs"
5
+ import { homedir } from "node:os"
6
+ import { dirname, join } from "node:path"
7
+ import { fileURLToPath } from "node:url"
8
+ import { sourceRoot } from "../src/resolve"
9
+
10
+ const args = new Set(process.argv.slice(2))
11
+ const DRY = args.has("--dry-run")
12
+ const GLOBAL = args.has("--global")
13
+ const cwd = process.cwd()
14
+ const base = GLOBAL ? join(homedir(), ".config", "opencode") : join(cwd, ".opencode")
15
+ const src = sourceRoot()
16
+ // opencode-assets sits next to src/ in the package (and in the repo clone)
17
+ const pkgRoot = join(dirname(fileURLToPath(import.meta.url)), "..")
18
+ const assetDir = join(pkgRoot, "opencode-assets")
19
+
20
+ const actions: string[] = []
21
+ const doCopy = (from: string, to: string) => {
22
+ actions.push(`copy ${from} -> ${to}`)
23
+ if (!DRY) {
24
+ mkdirSync(dirname(to), { recursive: true })
25
+ cpSync(from, to, { recursive: true })
26
+ }
27
+ }
28
+
29
+ // 1. Skills — renamed with a bestest- prefix (un-namespaced skill dirs collide:
30
+ // 'plan'/'build'/'debug' are common). Cross-references in prose keep the bare
31
+ // names; the opencode harness map explains the prefix.
32
+ const RENAME: Record<string, string> = {
33
+ "using-bestest": "using-bestest",
34
+ interrogate: "bestest-interrogate",
35
+ research: "bestest-research",
36
+ plan: "bestest-plan",
37
+ build: "bestest-build",
38
+ ship: "bestest-ship",
39
+ debug: "bestest-debug",
40
+ "bestest-go": "bestest-go",
41
+ "bestest-status": "bestest-status",
42
+ }
43
+ for (const dir of readdirSync(join(src, "skills"))) {
44
+ const target = RENAME[dir]
45
+ if (!target) continue
46
+ doCopy(join(src, "skills", dir), join(base, "skills", target))
47
+ if (!DRY && target !== dir) {
48
+ const f = join(base, "skills", target, "SKILL.md")
49
+ writeFileSync(f, readFileSync(f, "utf8").replace(/^name:\s*.*$/m, `name: ${target}`))
50
+ }
51
+ }
52
+
53
+ // 2/3. Agents + command stubs (hand-maintained conversions; bodies byte-identical
54
+ // to the canonical agents/*.md — static-checks enforce parity).
55
+ for (const kind of ["agents", "commands"]) {
56
+ for (const f of readdirSync(join(assetDir, kind))) {
57
+ doCopy(join(assetDir, kind, f), join(base, kind, f))
58
+ }
59
+ }
60
+
61
+ // 4. Runtime payload for project sessions: hooks (shim targets), trace.sh, templates.
62
+ if (!GLOBAL) {
63
+ for (const part of ["hooks", "scripts", "templates"]) {
64
+ doCopy(join(src, part), join(cwd, ".opencode", "bestest", part))
65
+ }
66
+ const version = existsSync(join(src, "VERSION")) ? readFileSync(join(src, "VERSION"), "utf8").trim() : "dev"
67
+ actions.push(`write .opencode/bestest/VERSION = ${version}`)
68
+ if (!DRY) writeFileSync(join(cwd, ".opencode", "bestest", "VERSION"), version + "\n")
69
+ }
70
+
71
+ // 5. opencode.json merge (strict JSON only; JSONC users get manual instructions).
72
+ const cfgPath = GLOBAL ? join(base, "opencode.json") : join(cwd, "opencode.json")
73
+ const PLUGIN = "@accidentally-awesome-labs/opencode-bestest"
74
+ const INSTR = ".bestest/.context.md"
75
+ let cfg: Record<string, unknown> = { $schema: "https://opencode.ai/config.json" }
76
+ let cfgOk = true
77
+ if (existsSync(cfgPath)) {
78
+ try {
79
+ cfg = JSON.parse(readFileSync(cfgPath, "utf8"))
80
+ } catch {
81
+ cfgOk = false
82
+ }
83
+ }
84
+ if (cfgOk) {
85
+ const plugins = new Set([...((cfg.plugin as string[]) ?? [])])
86
+ plugins.add(PLUGIN)
87
+ cfg.plugin = [...plugins]
88
+ const instr = new Set([...((cfg.instructions as string[]) ?? [])])
89
+ instr.add(INSTR)
90
+ cfg.instructions = [...instr]
91
+ actions.push(`merge ${cfgPath}: plugin += ${PLUGIN}, instructions += ${INSTR}`)
92
+ if (!DRY) writeFileSync(cfgPath, JSON.stringify(cfg, null, 2) + "\n")
93
+ } else {
94
+ actions.push(`SKIPPED ${cfgPath} (not strict JSON) — add manually: "plugin": ["${PLUGIN}"], "instructions": ["${INSTR}"]`)
95
+ }
96
+
97
+ // 6. gitignore the digest + snapshot (project installs only).
98
+ if (!GLOBAL) {
99
+ const gi = join(cwd, ".gitignore")
100
+ const lines = existsSync(gi) ? readFileSync(gi, "utf8") : ""
101
+ const missing = [".bestest/.context.md", ".bestest/.session-snapshot"].filter((l) => !lines.includes(l))
102
+ if (missing.length) {
103
+ actions.push(`append .gitignore: ${missing.join(", ")}`)
104
+ if (!DRY) writeFileSync(gi, lines + (lines.endsWith("\n") || lines === "" ? "" : "\n") + missing.join("\n") + "\n")
105
+ }
106
+ }
107
+
108
+ // 7. Existing .bestest project: build the first digest now (no chicken-and-egg).
109
+ if (!GLOBAL && existsSync(join(cwd, ".bestest")) && Bun.which("bash")) {
110
+ actions.push("generate initial .bestest/.context.md")
111
+ if (!DRY) {
112
+ const proc = Bun.spawn(["bash", join(src, "hooks", "session-start.sh"), "start"], {
113
+ cwd,
114
+ env: { ...process.env, CLAUDE_PROJECT_DIR: cwd },
115
+ stdout: "pipe",
116
+ stderr: "ignore",
117
+ })
118
+ const out = await new Response(proc.stdout).text()
119
+ await proc.exited
120
+ if (out.trim()) writeFileSync(join(cwd, ".bestest", ".context.md"), out)
121
+ }
122
+ }
123
+
124
+ console.log(`opencode-bestest init ${DRY ? "(dry-run)" : ""}${GLOBAL ? " (global)" : ""}`)
125
+ for (const a of actions) console.log(" " + a)
126
+ console.log(cfgOk ? "Done. Open opencode and run /bestest-go." : "Done with manual steps above. Then open opencode and run /bestest-go.")
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env bun
2
+ // npm rejects .ts bin targets; this shebang launcher keeps `bunx … init` working.
3
+ import "./init.ts"
@@ -0,0 +1,27 @@
1
+ ---
2
+ description: Adversarial checker for bestest specs (P2 rubric mode) and plans (P3 card mode) — read-only, verdict plus numbered blockers.
3
+ mode: subagent
4
+ permission:
5
+ edit: deny
6
+ bash: deny
7
+ webfetch: deny
8
+ ---
9
+
10
+ You are the adversarial checker for a bestest project. You are read-only; you report, you never fix. Your job is to be the reviewer who finds the hole *before* it costs a build day. A clean PASS you didn't earn is a failure of yours, not a success of theirs.
11
+
12
+ **Spec mode** (given SPEC.md): grade completeness, clarity, consistency, measurability, coverage, traceability. Every finding typed and cited:
13
+ - `[Gap]` — something the build will need that no section provides (asset, actor, failure path, data lifecycle).
14
+ - `[Ambiguity]` — a sentence two reasonable builders would implement differently. Quote it; say the two readings.
15
+ - `[Conflict]` — two statements that cannot both hold. Cite both R-IDs/sections.
16
+ - `[Assumption]` — a load-bearing `[assumed]` tag or an untagged claim with no `[user]`/`[cited:]` evidence.
17
+ Check every R-N.M is real EARS (trigger + observable behavior, one sentence, testable), anti-goals ≥3, out_of_scope non-empty, doneness function falsifiable, autonomy contract present. Gaps and Conflicts are freeze-blockers.
18
+
19
+ **Plan mode** (given PLAN.md + cards + SPEC): hunt, in order:
20
+ 1. R-IDs with no covering card; cards citing phantom R-IDs; orphan cards serving no R-ID.
21
+ 2. **Recompute complexity** per card from its own fields (`1 + 2/extra file + 2/verify cmd + 3 if title needs "and" + 2/new interface`); declared < recomputed, or >6 unsplit → blocker.
22
+ 3. Zero-context violations: placeholders, "as discussed", missing signatures/paths, a card a fresh stranger couldn't execute.
23
+ 4. Assets consumed but never produced (check `blocked_by:` ordering too).
24
+ 5. Complexity Justification table: any structure beyond the simplest satisfying design without a row → blocker.
25
+ 6. TDD pairing: implementer cards with `may_edit_tests: true`, or no failing-test card ahead of them.
26
+
27
+ Verdict format, both modes: `PASS` or `FAIL` + numbered blockers, each with file/R-ID cite and the one-line fix you'd accept. Nits go in a separate short list — never pad blockers.
@@ -0,0 +1,20 @@
1
+ ---
2
+ description: Fetches and verifies external facts for a bestest research question, returning cited findings — never answers from memory.
3
+ mode: subagent
4
+ permission:
5
+ edit:
6
+ "*": deny
7
+ ".bestest/research/*": allow
8
+ bash: deny
9
+ ---
10
+
11
+ You research exactly one question (RQ) for a bestest project. Your training data is presumed stale: every claim you return must come from a page you fetched **this session**.
12
+
13
+ Method:
14
+ 1. Read the RQ file you were given; note the answer shape it needs and which decision it feeds.
15
+ 2. Search broadly, then fetch primary sources — official docs, changelogs, pricing pages, GitHub issues/releases — over blogs and search snippets. For version- or price-sensitive facts, find the date on the page.
16
+ For version-sensitive TOOL behavior, docs may describe an unreleased major: prefer running the pinned tool itself (`--help`, a scratch project) over reading about it, and cite the run.
17
+ 3. Adversarially check each load-bearing claim: one honest attempt to refute it via a second independent source. Disagreement is a finding — report both sides, don't pick silently.
18
+ 4. For dependency candidates, run SLOPCHECK: last release, issue trend, bus factor, install weight, license, "does the platform already do this?".
19
+
20
+ Write findings into the RQ file: each claim on its own line, tagged `[cited:URL]` (the URL you fetched, not the search result), or `[assumed]` if you genuinely could not verify — with what you tried. Finish with a ≤5-line summary: the answer, confidence, and what would change it. Your final message is just the RQ file path and that summary.
@@ -0,0 +1,22 @@
1
+ ---
2
+ description: Risk-scaled adversarial code review of a completed bestest task card — read-only on product code, evidence-based verdict.
3
+ mode: subagent
4
+ permission:
5
+ edit: deny
6
+ webfetch: deny
7
+ ---
8
+
9
+ You review one completed task card for a bestest project. Read-only: you may run read-only commands (tests, linters, `git diff`) but change nothing. You are reviewing the *diff against the card*, not the codebase's life story.
10
+
11
+ Inputs: the card, the diff range, the report, SPEC.md, relevant D-IDs.
12
+
13
+ **Depth is risk-scaled by card `weight:`**
14
+ - **weight ≥ 4 — full adversarial:** actively try to break it. Trace the R-IDs' EARS triggers through the code by hand; probe edge/failure paths the tests skip; check the claim→evidence table by re-running what's cheap; hunt silent contract drift (behavior the SPEC forbids, error paths swallowed, security footguns in the diff).
15
+ - **weight < 4 — blockers-only:** correctness of the R-ID behavior, verify commands genuinely proving it, diff staying inside `files:`. No style opinions.
16
+
17
+ Always check, any weight:
18
+ 1. Tests: do they *assert the criterion* or just execute the code? A test the implementation can't fail is a blocker at weight≥4, a flag below.
19
+ 2. Report honesty: any claim whose evidence doesn't hold up = blocker, regardless of code quality.
20
+ 3. `Bestest-Req:` trailer present and citing the card's R-IDs.
21
+
22
+ Verdict: `APPROVED` or numbered findings, each: severity (blocker/flag), file:line, what breaks, the smallest fix you'd accept. A finding you raised closes only when you re-confirm the fix — say explicitly what you'd need to see. Do not approve out of fatigue; do not block on taste.
@@ -0,0 +1,5 @@
1
+ ---
2
+ description: Route to the current bestest lifecycle phase (or start a new project at P0)
3
+ ---
4
+
5
+ Load the **bestest-go** skill and follow it exactly. Plugin runtime root for its bootstrap step (record as the `plugin:` line in STATE.md): `.opencode/bestest`. Note: on this harness the lifecycle skills are installed with a `bestest-` prefix (bestest-interrogate, bestest-plan, …).
@@ -0,0 +1,5 @@
1
+ ---
2
+ description: Read-only dashboard of the bestest project state — never mutates anything
3
+ ---
4
+
5
+ Load the **bestest-status** skill and follow it exactly (strictly read-only). If STATE.md lacks a `plugin:` line, trace.sh lives under `.opencode/bestest`.
package/package.json ADDED
@@ -0,0 +1,30 @@
1
+ {
2
+ "name": "@accidentally-awesome-labs/opencode-bestest",
3
+ "version": "0.4.3",
4
+ "description": "OpenCode head for the bestest plugin: guard/lock/syntax enforcement shelling into the shared bash hooks, passive state injection, and an installer for skills/agents/commands.",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "exports": "./src/index.ts",
8
+ "bin": {
9
+ "opencode-bestest": "bin/opencode-bestest"
10
+ },
11
+ "files": [
12
+ "src",
13
+ "bin",
14
+ "payload",
15
+ "opencode-assets"
16
+ ],
17
+ "scripts": {
18
+ "prepack": "bash prepack.sh",
19
+ "check": "bun x tsc --noEmit",
20
+ "test": "bun test"
21
+ },
22
+ "devDependencies": {
23
+ "@opencode-ai/plugin": "1.17.15",
24
+ "bun-types": "^1.3.14",
25
+ "typescript": "^5.9.0"
26
+ },
27
+ "publishConfig": {
28
+ "access": "public"
29
+ }
30
+ }
@@ -0,0 +1 @@
1
+ 0.4.3
@@ -0,0 +1,25 @@
1
+ ---
2
+ name: plan-checker
3
+ description: Adversarial checker for bestest specs (P2 rubric mode) and plans (P3 card mode) — read-only, verdict plus numbered blockers.
4
+ tools: Read, Grep, Glob
5
+ model: sonnet
6
+ ---
7
+
8
+ You are the adversarial checker for a bestest project. You are read-only; you report, you never fix. Your job is to be the reviewer who finds the hole *before* it costs a build day. A clean PASS you didn't earn is a failure of yours, not a success of theirs.
9
+
10
+ **Spec mode** (given SPEC.md): grade completeness, clarity, consistency, measurability, coverage, traceability. Every finding typed and cited:
11
+ - `[Gap]` — something the build will need that no section provides (asset, actor, failure path, data lifecycle).
12
+ - `[Ambiguity]` — a sentence two reasonable builders would implement differently. Quote it; say the two readings.
13
+ - `[Conflict]` — two statements that cannot both hold. Cite both R-IDs/sections.
14
+ - `[Assumption]` — a load-bearing `[assumed]` tag or an untagged claim with no `[user]`/`[cited:]` evidence.
15
+ Check every R-N.M is real EARS (trigger + observable behavior, one sentence, testable), anti-goals ≥3, out_of_scope non-empty, doneness function falsifiable, autonomy contract present. Gaps and Conflicts are freeze-blockers.
16
+
17
+ **Plan mode** (given PLAN.md + cards + SPEC): hunt, in order:
18
+ 1. R-IDs with no covering card; cards citing phantom R-IDs; orphan cards serving no R-ID.
19
+ 2. **Recompute complexity** per card from its own fields (`1 + 2/extra file + 2/verify cmd + 3 if title needs "and" + 2/new interface`); declared < recomputed, or >6 unsplit → blocker.
20
+ 3. Zero-context violations: placeholders, "as discussed", missing signatures/paths, a card a fresh stranger couldn't execute.
21
+ 4. Assets consumed but never produced (check `blocked_by:` ordering too).
22
+ 5. Complexity Justification table: any structure beyond the simplest satisfying design without a row → blocker.
23
+ 6. TDD pairing: implementer cards with `may_edit_tests: true`, or no failing-test card ahead of them.
24
+
25
+ Verdict format, both modes: `PASS` or `FAIL` + numbered blockers, each with file/R-ID cite and the one-line fix you'd accept. Nits go in a separate short list — never pad blockers.
@@ -0,0 +1,17 @@
1
+ ---
2
+ name: researcher
3
+ description: Fetches and verifies external facts for a bestest research question, returning cited findings — never answers from memory.
4
+ tools: WebSearch, WebFetch, Read, Grep, Glob, Write
5
+ model: sonnet
6
+ ---
7
+
8
+ You research exactly one question (RQ) for a bestest project. Your training data is presumed stale: every claim you return must come from a page you fetched **this session**.
9
+
10
+ Method:
11
+ 1. Read the RQ file you were given; note the answer shape it needs and which decision it feeds.
12
+ 2. Search broadly, then fetch primary sources — official docs, changelogs, pricing pages, GitHub issues/releases — over blogs and search snippets. For version- or price-sensitive facts, find the date on the page.
13
+ For version-sensitive TOOL behavior, docs may describe an unreleased major: prefer running the pinned tool itself (`--help`, a scratch project) over reading about it, and cite the run.
14
+ 3. Adversarially check each load-bearing claim: one honest attempt to refute it via a second independent source. Disagreement is a finding — report both sides, don't pick silently.
15
+ 4. For dependency candidates, run SLOPCHECK: last release, issue trend, bus factor, install weight, license, "does the platform already do this?".
16
+
17
+ Write findings into the RQ file: each claim on its own line, tagged `[cited:URL]` (the URL you fetched, not the search result), or `[assumed]` if you genuinely could not verify — with what you tried. Finish with a ≤5-line summary: the answer, confidence, and what would change it. Your final message is just the RQ file path and that summary.
@@ -0,0 +1,21 @@
1
+ ---
2
+ name: reviewer
3
+ description: Risk-scaled adversarial code review of a completed bestest task card — read-only on product code, evidence-based verdict.
4
+ tools: Read, Grep, Glob, Bash
5
+ model: sonnet
6
+ ---
7
+
8
+ You review one completed task card for a bestest project. Read-only: you may run read-only commands (tests, linters, `git diff`) but change nothing. You are reviewing the *diff against the card*, not the codebase's life story.
9
+
10
+ Inputs: the card, the diff range, the report, SPEC.md, relevant D-IDs.
11
+
12
+ **Depth is risk-scaled by card `weight:`**
13
+ - **weight ≥ 4 — full adversarial:** actively try to break it. Trace the R-IDs' EARS triggers through the code by hand; probe edge/failure paths the tests skip; check the claim→evidence table by re-running what's cheap; hunt silent contract drift (behavior the SPEC forbids, error paths swallowed, security footguns in the diff).
14
+ - **weight < 4 — blockers-only:** correctness of the R-ID behavior, verify commands genuinely proving it, diff staying inside `files:`. No style opinions.
15
+
16
+ Always check, any weight:
17
+ 1. Tests: do they *assert the criterion* or just execute the code? A test the implementation can't fail is a blocker at weight≥4, a flag below.
18
+ 2. Report honesty: any claim whose evidence doesn't hold up = blocker, regardless of code quality.
19
+ 3. `Bestest-Req:` trailer present and citing the card's R-IDs.
20
+
21
+ Verdict: `APPROVED` or numbered findings, each: severity (blocker/flag), file:line, what breaks, the smallest fix you'd accept. A finding you raised closes only when you re-confirm the fix — say explicitly what you'd need to see. Do not approve out of fatigue; do not block on taste.
@@ -0,0 +1,111 @@
1
+ #!/usr/bin/env bash
2
+ # bestest: PreToolUse[Bash] guard. Blocks destructive commands (exit 2 = block,
3
+ # stderr is shown to Claude). Everything else passes through silently.
4
+ # Compound commands are split on ;, &&, ||, | and newlines — every segment is
5
+ # analyzed independently, so `safe ; rm -rf /` cannot hide behind the last segment.
6
+ set -u
7
+
8
+ input="$(cat)"
9
+ root="${CLAUDE_PROJECT_DIR:-$(pwd)}"
10
+
11
+ get_command() {
12
+ if command -v jq >/dev/null 2>&1; then
13
+ printf '%s' "$input" | jq -r '.tool_input.command // empty' 2>/dev/null
14
+ elif command -v python3 >/dev/null 2>&1; then
15
+ printf '%s' "$input" | python3 -c 'import json,sys
16
+ try: print(json.load(sys.stdin).get("tool_input",{}).get("command",""))
17
+ except Exception: pass' 2>/dev/null
18
+ else
19
+ # Last resort (no jq, no python3): sed extraction so the guard cannot fail
20
+ # open. Crude JSON decoding, but the pattern checks below only need the text.
21
+ printf '%s' "$input" \
22
+ | sed -nE 's/.*"command"[[:space:]]*:[[:space:]]*"((\\.|[^"\\])*)".*/\1/p' \
23
+ | sed -e 's/\\n/;/g' -e 's/\\t/ /g' -e 's/\\"/"/g' -e 's/\\\\/\\/g'
24
+ fi
25
+ }
26
+
27
+ cmd="$(get_command)"
28
+ [ -n "$cmd" ] || exit 0
29
+
30
+ block() {
31
+ echo "bestest guard: blocked — $1. If genuinely intended, this is a GATED action: ask the user to run it themselves or to approve an exception." >&2
32
+ exit 2
33
+ }
34
+
35
+ case "$cmd" in
36
+ *':(){ :|:& };:'*|*':(){:|:&};:'*) block "fork bomb" ;;
37
+ esac
38
+
39
+ # --- whole-command checks (substring anywhere is already damning) -------------
40
+ if printf '%s' "$cmd" | grep -Eq 'git[[:space:]]+reset[[:space:]]+--hard'; then
41
+ block "git reset --hard discards uncommitted work; stash or commit first"
42
+ fi
43
+
44
+ if printf '%s' "$cmd" | grep -Eq 'git[[:space:]]+clean[[:space:]]+-[a-zA-Z]*f'; then
45
+ block "git clean -f deletes untracked files irreversibly"
46
+ fi
47
+
48
+ if printf '%s' "$cmd" | grep -Eiq '(psql|mysql|mariadb|sqlite3|mongosh?|sqlcmd|clickhouse|duckdb|dbshell)' \
49
+ && printf '%s' "$cmd" | grep -Eiq "drop[[:space:]]+(database|schema)[[:space:]]"; then
50
+ block "DROP DATABASE/SCHEMA is a GATED destructive action"
51
+ fi
52
+
53
+ if printf '%s' "$cmd" | grep -Eq 'chmod[[:space:]]+(-R[[:space:]]+)?0?777'; then
54
+ block "chmod 777 is never the fix"
55
+ fi
56
+
57
+ if printf '%s' "$cmd" | grep -Eq '(^|[;&|[:space:]])(mkfs|fdisk)([[:space:].]|$)|dd[[:space:]]+[^;|&]*of=/dev/(sd|nvme|disk)|>[[:space:]]*/dev/(sd|nvme|disk)'; then
58
+ block "raw disk write"
59
+ fi
60
+
61
+ # --- per-segment checks --------------------------------------------------------
62
+ check_rm_segment() {
63
+ seg="$1"
64
+ printf '%s' "$seg" | grep -Eq '(^|[[:space:]])rm([[:space:]]|$)' || return 0
65
+ args="$(printf '%s' "$seg" | sed -E 's/.*(^|[[:space:]])rm[[:space:]]+//')"
66
+ rec=0; force=0; targets=""
67
+ for w in $args; do
68
+ case "$w" in
69
+ --recursive) rec=1 ;;
70
+ --force) force=1 ;;
71
+ --*) ;;
72
+ -*)
73
+ case "$w" in *[rR]*) rec=1 ;; esac
74
+ case "$w" in *f*) force=1 ;; esac ;;
75
+ *) targets="$targets $w" ;;
76
+ esac
77
+ done
78
+ [ "$rec" = 1 ] && [ "$force" = 1 ] || return 0
79
+ for w in $targets; do
80
+ w="${w%\"}"; w="${w#\"}"; w="${w%\'}"; w="${w#\'}"
81
+ case "$w" in
82
+ /|/\*|'~'|'~/'|'~/*'|\$HOME|\$HOME/|'$HOME/*')
83
+ block "recursive force-delete of / or home" ;;
84
+ /tmp/*|/private/tmp/*|/var/tmp/*) continue ;;
85
+ $root/*) continue ;;
86
+ /*|'~/'*|\$HOME/*)
87
+ block "recursive force-delete outside the project ($w) — delete inside the repo or temp dirs only" ;;
88
+ esac
89
+ done
90
+ }
91
+
92
+ check_push_segment() {
93
+ seg="$1"
94
+ printf '%s' "$seg" | grep -Eq 'git[[:space:]]+push' || return 0
95
+ if printf '%s' "$seg" | grep -Eq '[[:space:]](--force|-f)([[:space:]]|$)' \
96
+ && ! printf '%s' "$seg" | grep -q 'force-with-lease' \
97
+ && printf '%s' "$seg" | grep -Eq '[[:space:]](main|master)([[:space:]]|$|:)'; then
98
+ block "force-push to main/master (use --force-with-lease on a feature branch, or get user approval)"
99
+ fi
100
+ }
101
+
102
+ while IFS= read -r seg; do
103
+ [ -n "${seg// /}" ] || continue
104
+ check_rm_segment "$seg"
105
+ check_push_segment "$seg"
106
+ done <<EOF
107
+ $(printf '%s' "$cmd" | tr '\n' ';' | sed -E 's/&&|\|\||[;|]/\
108
+ /g')
109
+ EOF
110
+
111
+ exit 0
@@ -0,0 +1,118 @@
1
+ #!/usr/bin/env bash
2
+ # bestest: PreToolUse[Write|Edit|MultiEdit|apply_patch] lock guard.
3
+ # Blocks writes to paths frozen in .bestest/LOCKS:
4
+ # hash <sha256> <path> -> path is frozen (e.g. SPEC.md after P2)
5
+ # glob <pattern> -> pattern is frozen (test immutability); exempt only
6
+ # when the active card has 'may_edit_tests: true'
7
+ # Two envelope shapes, one contract (exit 2 = block):
8
+ # Claude Write/Edit -> .tool_input.file_path
9
+ # Codex/OpenCode apply_patch -> .tool_input.command holds the patch text;
10
+ # paths come from '*** Add/Update/Delete File:' headers
11
+ # Deliberate escape hatch: gate steps mutate LOCKS/SPEC via explicit shell
12
+ # commands (visible in transcript), never via file-edit tools.
13
+ set -u
14
+
15
+ input="$(cat)"
16
+ root="${CLAUDE_PROJECT_DIR:-$(pwd)}"
17
+ locks="$root/.bestest/LOCKS"
18
+
19
+ get_file_path() {
20
+ if command -v jq >/dev/null 2>&1; then
21
+ printf '%s' "$input" | jq -r '.tool_input.file_path // empty' 2>/dev/null
22
+ elif command -v python3 >/dev/null 2>&1; then
23
+ printf '%s' "$input" | python3 -c 'import json,sys
24
+ try: print(json.load(sys.stdin).get("tool_input",{}).get("file_path",""))
25
+ except Exception: pass' 2>/dev/null
26
+ else
27
+ # Last resort (no jq, no python3): sed extraction so the lock cannot fail
28
+ # open. Crude JSON decoding, but path comparison below only needs the text.
29
+ printf '%s' "$input" \
30
+ | sed -nE 's/.*"file_path"[[:space:]]*:[[:space:]]*"((\\.|[^"\\])*)".*/\1/p' \
31
+ | sed -e 's/\\"/"/g' -e 's/\\\\/\\/g'
32
+ fi
33
+ }
34
+
35
+ get_command_text() {
36
+ if command -v jq >/dev/null 2>&1; then
37
+ printf '%s' "$input" | jq -r '.tool_input.command // empty' 2>/dev/null
38
+ elif command -v python3 >/dev/null 2>&1; then
39
+ printf '%s' "$input" | python3 -c 'import json,sys
40
+ try: print(json.load(sys.stdin).get("tool_input",{}).get("command",""))
41
+ except Exception: pass' 2>/dev/null
42
+ else
43
+ printf '%s' "$input" \
44
+ | sed -nE 's/.*"command"[[:space:]]*:[[:space:]]*"((\\.|[^"\\])*)".*/\1/p' \
45
+ | awk '{gsub(/\\n/,"\n"); gsub(/\\t/," "); gsub(/\\"/,"\""); gsub(/\\\\/,"\\"); print}'
46
+ fi
47
+ }
48
+
49
+ # apply_patch envelopes: one project-relative path per header line.
50
+ patch_paths() {
51
+ cmd="$(get_command_text)"
52
+ case "$cmd" in
53
+ *'*** Begin Patch'*) ;;
54
+ *) return 0 ;;
55
+ esac
56
+ printf '%s\n' "$cmd" \
57
+ | sed -nE -e 's/^\*\*\* (Add|Update|Delete) File: //p' -e 's/^\*\*\* Move to: //p'
58
+ }
59
+
60
+ paths="$(get_file_path)"
61
+ [ -n "$paths" ] || paths="$(patch_paths)"
62
+ [ -n "$paths" ] || exit 0
63
+ [ -f "$locks" ] || exit 0
64
+
65
+ block() {
66
+ echo "bestest lock-guard: '$rel' is frozen ($1). $2" >&2
67
+ exit 2
68
+ }
69
+
70
+ # Known limitation: STATE.md's card: pointer and the card files are writable (the
71
+ # orchestrator updates them constantly). A builder that edits them to self-grant
72
+ # may_edit_tests is caught downstream by the diff-scope gate: cards/STATE are never
73
+ # in a card's own files: list, so the tampering diff fails the per-task gate.
74
+ may_edit_tests="false"
75
+ card="$(grep -m1 -E '^card:' "$root/.bestest/STATE.md" 2>/dev/null | sed -E 's/^card:[[:space:]]*//; s/[[:space:]]*#.*$//; s/[[:space:]]+$//')"
76
+ if [ -n "${card:-}" ] && [ -f "$root/$card" ]; then
77
+ # frontmatter only — a mention of may_edit_tests in the card body must not count
78
+ if awk '/^---$/{n++; next} n==1' "$root/$card" | grep -m1 -Eq '^may_edit_tests:[[:space:]]*true'; then
79
+ may_edit_tests="true"
80
+ fi
81
+ fi
82
+
83
+ check_one_path() {
84
+ # Normalize to a project-relative path.
85
+ rel="${1#"$root"/}"
86
+
87
+ # LOCKS itself is never edited via file-edit tools.
88
+ [ "$rel" = ".bestest/LOCKS" ] && block "the lock registry" "Gates append to it via explicit shell steps only."
89
+
90
+ while IFS= read -r line; do
91
+ kind="${line%% *}"
92
+ case "$kind" in
93
+ hash)
94
+ path="$(printf '%s' "$line" | awk '{print $3}')"
95
+ [ "$rel" = "$path" ] && block "hash-locked at spec freeze" \
96
+ "To amend: record a D-ID in .bestest/DECISIONS.md, apply the amendment via an explicit shell gate step, then update the hash line in LOCKS."
97
+ ;;
98
+ glob)
99
+ pat="$(printf '%s' "$line" | cut -d' ' -f2-)"
100
+ case "$rel" in
101
+ $pat)
102
+ [ "$may_edit_tests" = "true" ] || block "test file (TDD lock)" \
103
+ "Only a card with 'may_edit_tests: true' may touch tests. Implementer cards make tests pass, not change."
104
+ ;;
105
+ esac
106
+ ;;
107
+ esac
108
+ done < "$locks"
109
+ }
110
+
111
+ while IFS= read -r fp; do
112
+ [ -n "$fp" ] || continue
113
+ check_one_path "$fp"
114
+ done <<EOF
115
+ $paths
116
+ EOF
117
+
118
+ exit 0
@@ -0,0 +1,63 @@
1
+ #!/usr/bin/env bash
2
+ # bestest: SessionStart + Stop hook (dispatch on argv[1]).
3
+ # start -> inject .bestest/ state digest into context; detect drift.
4
+ # stop -> snapshot HEAD + dirty-tree hash for the next drift check.
5
+ set -u
6
+
7
+ mode="${1:-start}"
8
+ root="${CLAUDE_PROJECT_DIR:-$(pwd)}"
9
+ b="$root/.bestest"
10
+ snap="$b/.session-snapshot"
11
+
12
+ hash_stdin() {
13
+ if command -v sha256sum >/dev/null 2>&1; then sha256sum | cut -d' ' -f1
14
+ else shasum -a 256 | cut -d' ' -f1; fi
15
+ }
16
+
17
+ # Not a bestest project (or not a repo yet): stay silent.
18
+ [ -d "$b" ] || exit 0
19
+
20
+ head="$(git -C "$root" rev-parse HEAD 2>/dev/null || echo none)"
21
+ # Hash content, not just the porcelain listing — an edit that keeps the same
22
+ # file list would otherwise slip past the drift check. Exclude our own snapshot
23
+ # file, whose creation at stop-time would otherwise fabricate drift.
24
+ dirty="$( { git -C "$root" status --porcelain | grep -v '\.bestest/\.session-snapshot'; git -C "$root" diff; git -C "$root" diff --cached; } 2>/dev/null | hash_stdin)"
25
+
26
+ if [ "$mode" = "stop" ]; then
27
+ printf 'HEAD=%s\nDIRTY=%s\n' "$head" "$dirty" > "$snap" 2>/dev/null
28
+ exit 0
29
+ fi
30
+
31
+ [ -f "$b/STATE.md" ] || exit 0
32
+
33
+ echo "## bestest state (auto-injected — durable source of truth: .bestest/)"
34
+ sed -n '1,60p' "$b/STATE.md"
35
+
36
+ if [ -f "$b/LOCKS" ] && [ -s "$b/LOCKS" ]; then
37
+ echo
38
+ echo "### LOCKS (frozen paths — writes blocked by hook)"
39
+ cat "$b/LOCKS"
40
+ fi
41
+
42
+ card="$(grep -m1 -E '^card:' "$b/STATE.md" 2>/dev/null | sed -E 's/^card:[[:space:]]*//; s/[[:space:]]*#.*$//; s/[[:space:]]+$//')"
43
+ if [ -n "${card:-}" ] && [ "$card" != "none" ] && [ -f "$root/$card" ]; then
44
+ echo
45
+ echo "### active task card ($card)"
46
+ cat "$root/$card"
47
+ fi
48
+
49
+ if [ -f "$b/SPEC.md" ]; then
50
+ echo
51
+ echo "Autonomy contract in force: SPEC.md section 9 (PROCEED / INTERRUPT / GATED)."
52
+ fi
53
+
54
+ if [ -f "$snap" ]; then
55
+ old_head="$(grep -m1 '^HEAD=' "$snap" | cut -d= -f2-)"
56
+ old_dirty="$(grep -m1 '^DIRTY=' "$snap" | cut -d= -f2-)"
57
+ if [ "$old_head" != "$head" ] || [ "$old_dirty" != "$dirty" ]; then
58
+ echo
59
+ echo "DRIFT: the working tree changed since the last session ended. Before continuing, reconcile code against SPEC.md, classify each delta (in-spec / out-of-spec / needs D-ID), and append R-ID-traced tasks to .bestest/PLAN.md."
60
+ fi
61
+ fi
62
+
63
+ exit 0