@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
@@ -0,0 +1,24 @@
1
+ # RUNBOOK — <project name>
2
+ <!-- Stack-agnostic checklist: fill every section for the stack SPEC §5 derived.
3
+ 'n/a' requires a one-line reason. Rehearsed means actually executed once. -->
4
+
5
+ ## Deploy
6
+ <!-- exact commands/pipeline; config + secrets source; migration order -->
7
+
8
+ ## Rollback (REHEARSED — paste the rehearsal transcript, not the theory)
9
+ <!-- steps + how long it took + what state is lost/kept -->
10
+
11
+ ## Dependency timeouts
12
+ <!-- | dependency | timeout | retry policy | behavior when down | — audit every external call -->
13
+
14
+ ## Observability floor
15
+ <!-- named logs (where), metrics (which), alerts (threshold + who is paged) -->
16
+
17
+ ## Security smoke (negative paths)
18
+ <!-- auth-required route without auth; malformed input; injection probe on entry points; secrets not in logs -->
19
+
20
+ ## Restore drill
21
+ <!-- backup source -> restore command -> verified-by; last drilled date -->
22
+
23
+ ## Spend
24
+ <!-- ceiling per month | alert threshold | where the alert lands -->
@@ -0,0 +1,35 @@
1
+ # SPEC — <project name>
2
+ <!-- Frozen at P2: sha256 recorded in .bestest/LOCKS; the lock-guard hook then
3
+ blocks edits. Amendments: D-ID in DECISIONS.md -> Bash gate step -> re-hash. -->
4
+
5
+ ## 1. Premise Verdict
6
+ <!-- one paragraph: the real problem, for whom, why now, why not an existing tool.
7
+ Every factual claim tagged [user] / [cited:URL] / [assumed]. -->
8
+
9
+ ## 2. Requirements — EARS acceptance criteria
10
+ <!-- one sentence each, numbered R-N.M, observable behavior only:
11
+ R-1.1 WHEN <trigger> [WHILE <state>] THE SYSTEM SHALL <observable behavior>. -->
12
+
13
+ ## 3. Anti-goals (≥3)
14
+ <!-- things this project deliberately will NOT do, each with the failure it prevents -->
15
+
16
+ ## 4. Out of scope
17
+ <!-- non-empty. Each line: item — reason — Include/Defer/Cut/Hold. Deferred items = parking lot. -->
18
+
19
+ ## 5. Stack (derived, cited)
20
+ <!-- | layer | choice | why | evidence | — every row [cited:URL] from P1 research -->
21
+
22
+ ## 6. Assets
23
+ <!-- data, images, fixtures, credentials the build needs; who/what produces each -->
24
+
25
+ ## 7. Risks
26
+ <!-- from pre-mortem + vague-answer probes; each with likelihood, blast, tripwire -->
27
+
28
+ ## 8. Doneness function
29
+ <!-- the falsifiable test of "this project succeeded" — used verbatim at P5 go/no-go -->
30
+
31
+ ## 9. Autonomy Contract
32
+ - **PROCEED silently:** weight<4 decisions; fixes inside the active card's `files:` blast radius.
33
+ - **INTERRUPT the user:** weight≥4; spec contradiction; unachievable criterion; docs-vs-reality mismatch; security concern; out-of-scope diff.
34
+ - **GATED (never without explicit user approval):** deploy, spend money, destructive ops, credentials, publishing.
35
+ - Out-of-blast-radius discoveries → DISCOVERED.md. Never fixed silently, never dropped.
@@ -0,0 +1,20 @@
1
+ # STATE — <project name>
2
+ <!-- Overwritten at every gate/task boundary. Hard cap 60 lines: the SessionStart
3
+ hook injects lines 1-60 verbatim. Machine-read fields below must keep their
4
+ exact key names. -->
5
+
6
+ phase: P0 # P0 interrogate | P1 research | P2 spec | P3 plan | P4 build | P5 ship | done
7
+ milestone: v1 # archived to .bestest/milestones/<milestone>/ at rollover
8
+ stage: 0 # interrogation stage 0-6, or task id T-## during P4
9
+ spec_hash: none # sha256 recorded in .bestest/LOCKS at P2 freeze
10
+ card: none # project-relative path to the active task card (lock-guard reads this)
11
+
12
+ ## Now
13
+ - next action: <one imperative sentence — what the very next tool call should do>
14
+ - blocked on: none
15
+
16
+ ## Gates passed
17
+ <!-- append one line per gate: date · gate · evidence pointer -->
18
+
19
+ ## Open interrupts / escalations
20
+ <!-- anything awaiting the user; empty means full autonomy within the contract -->
@@ -0,0 +1,10 @@
1
+ # UAT — one question per acceptance criterion
2
+ <!-- Filled at P5. One row per R-N.M from SPEC §2. The user answers the question;
3
+ 'pass' requires their yes plus the evidence pointer. -->
4
+
5
+ | R-ID | question to user (restates the EARS criterion as a check) | evidence | result |
6
+ |------|-----------------------------------------------------------|----------|--------|
7
+ | R-1.1 | | | pending |
8
+
9
+ ## Doneness function verdict
10
+ <!-- SPEC §8 evaluated verbatim: PASS / FAIL + why -->
@@ -0,0 +1,15 @@
1
+ # Report — T-00
2
+ <!-- Written by the builder subagent; the orchestrator trusts nothing in it
3
+ until verify commands are re-run and the diff-scope gate passes. -->
4
+
5
+ ## Claim → evidence
6
+ <!-- | claim | evidence (command + output excerpt, diff hunk, or [cited:URL]) | -->
7
+
8
+ ## Verify output
9
+ <!-- paste actual output of each card verify: command -->
10
+
11
+ ## FRICTION
12
+ <!-- REQUIRED. Failed commands, backtracking, wrong card assumptions, environment surprises — or exactly 'None'. The orchestrator patches remaining cards + LESSONS.md from this before the next dispatch. -->
13
+
14
+ ## Discovered
15
+ <!-- out-of-blast-radius items appended to DISCOVERED.md this task, or 'None' -->
@@ -0,0 +1,24 @@
1
+ ---
2
+ id: T-00
3
+ title: <imperative, one clause — needing "and" means split it>
4
+ status: todo # todo | doing | done
5
+ files: src/example.py # comma-separated blast radius; diffs outside = revert or INTERRUPT
6
+ requirements: R-1.1 # comma-separated R-IDs this card advances
7
+ weight: 3 # irreversibility x scope; >=4 -> full adversarial review
8
+ complexity: 3 # 1 + 2/extra file + 2/verify cmd + 3 if title needs "and" + 2/new interface; >6 split
9
+ blocked_by: none # comma-separated T-IDs
10
+ may_edit_tests: false # true only on test-author cards (lock-guard enforces)
11
+ verify:
12
+ - <command that proves this card done — orchestrator re-runs it>
13
+ ---
14
+
15
+ ## Context (zero-context contract)
16
+ <!-- Everything a fresh subagent needs: exact paths, function signatures, data shapes,
17
+ relevant D-IDs, LESSONS to read first. No placeholders, no "see above". -->
18
+
19
+ ## Steps
20
+ <!-- numbered, concrete; TDD: failing test exists before implementation -->
21
+
22
+ ## Done means
23
+ <!-- observable end state, restated from the R-IDs, plus: report written to
24
+ .bestest/reports/T-00.md; commit carries trailer 'Bestest-Req: R-1.1' -->
package/src/guards.ts ADDED
@@ -0,0 +1,80 @@
1
+ // tool.execute.before/after -> the shared bash hook scripts.
2
+ // Contract (same one tests/bench.sh proves): stdin {"tool_input":{...}},
3
+ // exit 0 = pass, exit 2 = block with the model-facing reason on stderr.
4
+ // guard/lock CANNOT fail open: any failure to run them throws (blocks).
5
+ // syntax-check is advisory by design: only a real exit-2 throws.
6
+ import { hookScript } from "./resolve"
7
+
8
+ export const WRITE_TOOLS = ["write", "edit", "apply_patch"] as const
9
+
10
+ type Envelope = { tool_input: Record<string, string> }
11
+
12
+ function envelopeFor(tool: string, args: Record<string, unknown>): Envelope | null {
13
+ if (tool === "bash" && typeof args.command === "string") {
14
+ return { tool_input: { command: args.command } }
15
+ }
16
+ if (tool === "apply_patch" && typeof args.patchText === "string") {
17
+ // lock-guard/syntax-check parse '*** Add/Update/Delete File:' headers from .command
18
+ return { tool_input: { command: args.patchText } }
19
+ }
20
+ if ((tool === "write" || tool === "edit") && typeof args.filePath === "string") {
21
+ return { tool_input: { file_path: args.filePath } }
22
+ }
23
+ return null
24
+ }
25
+
26
+ async function runHook(script: string, envelope: Envelope, projectRoot: string) {
27
+ const proc = Bun.spawn(["bash", hookScript(script)], {
28
+ stdin: new TextEncoder().encode(JSON.stringify(envelope)),
29
+ cwd: projectRoot,
30
+ env: { ...process.env, CLAUDE_PROJECT_DIR: projectRoot },
31
+ stdout: "ignore",
32
+ stderr: "pipe",
33
+ })
34
+ const stderr = await new Response(proc.stderr).text()
35
+ const code = await proc.exited
36
+ return { code, stderr }
37
+ }
38
+
39
+ export function makeGuards(projectRoot: string, bashOk: boolean) {
40
+ const enforce = async (script: string, envelope: Envelope) => {
41
+ if (!bashOk) {
42
+ throw new Error(
43
+ `bestest ${script}: bash not found — guards cannot run and will not fail open. Install bash (Git Bash on Windows) or remove opencode-bestest from opencode.json.`,
44
+ )
45
+ }
46
+ let res
47
+ try {
48
+ res = await runHook(script, envelope, projectRoot)
49
+ } catch (e) {
50
+ throw new Error(`bestest ${script}: failed to run (${e}) — refusing to fail open.`)
51
+ }
52
+ if (res.code === 2) throw new Error(res.stderr.trim() || `bestest ${script}: blocked`)
53
+ if (res.code !== 0) throw new Error(`bestest ${script}: exited ${res.code} — refusing to fail open.`)
54
+ }
55
+
56
+ return {
57
+ before: async (tool: string, args: Record<string, unknown>) => {
58
+ if (tool === "bash") {
59
+ const env = envelopeFor(tool, args)
60
+ if (env) await enforce("guard.sh", env)
61
+ } else if ((WRITE_TOOLS as readonly string[]).includes(tool)) {
62
+ const env = envelopeFor(tool, args)
63
+ if (env) await enforce("lock-guard.sh", env)
64
+ }
65
+ },
66
+ after: async (tool: string, args: Record<string, unknown>) => {
67
+ if (!(WRITE_TOOLS as readonly string[]).includes(tool) || !bashOk) return
68
+ const env = envelopeFor(tool, args)
69
+ if (!env) return
70
+ let res
71
+ try {
72
+ res = await runHook("syntax-check.sh", env, projectRoot)
73
+ } catch {
74
+ return // advisory gate: a failure to RUN the checker never blocks
75
+ }
76
+ // exit 2 = genuine syntax failure -> feed back to the model (write already persisted)
77
+ if (res.code === 2) throw new Error(res.stderr.trim() || "bestest syntax-check: failed")
78
+ },
79
+ }
80
+ }
package/src/index.ts ADDED
@@ -0,0 +1,34 @@
1
+ import type { Plugin } from "@opencode-ai/plugin"
2
+ import { makeGuards } from "./guards"
3
+ import { makeSession } from "./session"
4
+
5
+ export const BestestPlugin: Plugin = async ({ directory, worktree }) => {
6
+ const projectRoot = worktree ?? directory
7
+ const bashOk = Bun.which("bash") !== null
8
+ if (!bashOk) {
9
+ console.error(
10
+ "opencode-bestest: bash not found — guard/lock/drift enforcement is DISABLED-CLOSED (guarded tools will be refused). Install bash (Git Bash on Windows).",
11
+ )
12
+ }
13
+ const guards = makeGuards(projectRoot, bashOk)
14
+ const session = makeSession(projectRoot, bashOk)
15
+ await session.regen() // server start == session boundary for the digest
16
+
17
+ return {
18
+ "tool.execute.before": async (input, output) => {
19
+ await guards.before(input.tool, output.args ?? {})
20
+ },
21
+ "tool.execute.after": async (input, _output) => {
22
+ await guards.after(input.tool, (input as { args?: Record<string, unknown> }).args ?? {})
23
+ },
24
+ event: async ({ event }) => {
25
+ await session.onEvent(event.type, event)
26
+ },
27
+ // Layer 2: re-inject state across compaction (experimental API, feature-detected
28
+ // by opencode itself — unknown hook keys are simply never called).
29
+ "experimental.session.compacting": async (_input, output) => {
30
+ const d = await session.digest()
31
+ if (d) output.context.push(d)
32
+ },
33
+ }
34
+ }
package/src/resolve.ts ADDED
@@ -0,0 +1,22 @@
1
+ // Locates the shared bash sources. Two tiers:
2
+ // 1. payload/ next to src/ — the published npm tarball (prepack.sh snapshot)
3
+ // 2. the repo root two levels up — running from a clone (dev, gauntlet)
4
+ import { existsSync } from "node:fs"
5
+ import { dirname, join } from "node:path"
6
+ import { fileURLToPath } from "node:url"
7
+
8
+ const srcDir = dirname(fileURLToPath(import.meta.url))
9
+
10
+ export function sourceRoot(): string {
11
+ const payload = join(srcDir, "..", "payload")
12
+ if (existsSync(join(payload, "hooks", "guard.sh"))) return payload
13
+ const repo = join(srcDir, "..", "..", "..")
14
+ if (existsSync(join(repo, "hooks", "guard.sh"))) return repo
15
+ throw new Error(
16
+ "opencode-bestest: cannot locate bundled hook scripts (payload/ missing and not in a repo clone) — reinstall the package",
17
+ )
18
+ }
19
+
20
+ export function hookScript(name: string): string {
21
+ return join(sourceRoot(), "hooks", name)
22
+ }
package/src/session.ts ADDED
@@ -0,0 +1,57 @@
1
+ // Session-state machinery. OpenCode has no SessionStart stdout injection, so:
2
+ // digest file .bestest/.context.md <- session-start.sh start (regenerated
3
+ // at plugin init, session.created, session.idle, .bestest edits)
4
+ // opencode.json instructions: [".bestest/.context.md"] <- passive injection
5
+ // session.idle -> session-start.sh stop (drift snapshot), then regen digest
6
+ // experimental.session.compacting -> push the digest (feature-detected layer 2)
7
+ import { existsSync } from "node:fs"
8
+ import { join } from "node:path"
9
+ import { hookScript } from "./resolve"
10
+
11
+ async function runSessionScript(mode: "start" | "stop", projectRoot: string): Promise<string> {
12
+ const proc = Bun.spawn(["bash", hookScript("session-start.sh"), mode], {
13
+ cwd: projectRoot,
14
+ env: { ...process.env, CLAUDE_PROJECT_DIR: projectRoot },
15
+ stdout: "pipe",
16
+ stderr: "ignore",
17
+ })
18
+ const out = await new Response(proc.stdout).text()
19
+ await proc.exited
20
+ return out
21
+ }
22
+
23
+ export function makeSession(projectRoot: string, bashOk: boolean) {
24
+ const digestPath = join(projectRoot, ".bestest", ".context.md")
25
+ const isBestest = () => existsSync(join(projectRoot, ".bestest"))
26
+
27
+ const regen = async () => {
28
+ if (!bashOk || !isBestest()) return
29
+ const out = await runSessionScript("start", projectRoot)
30
+ if (out.trim().length > 0) await Bun.write(digestPath, out)
31
+ }
32
+
33
+ const snapshot = async () => {
34
+ if (!bashOk || !isBestest()) return
35
+ await runSessionScript("stop", projectRoot)
36
+ await regen() // snapshot changes drift state; keep the digest current
37
+ }
38
+
39
+ const digest = async (): Promise<string | null> => {
40
+ try {
41
+ return existsSync(digestPath) ? await Bun.file(digestPath).text() : null
42
+ } catch {
43
+ return null
44
+ }
45
+ }
46
+
47
+ const onEvent = async (type: string, raw: unknown) => {
48
+ if (type === "session.created") await regen()
49
+ else if (type === "session.idle") await snapshot()
50
+ else if (type === "file.edited") {
51
+ const s = JSON.stringify(raw ?? "")
52
+ if (s.includes(".bestest/STATE.md") || s.includes(".bestest/LOCKS")) await regen()
53
+ }
54
+ }
55
+
56
+ return { regen, snapshot, digest, onEvent }
57
+ }