@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.
- package/README.md +36 -0
- package/bin/init.ts +126 -0
- package/bin/opencode-bestest +3 -0
- package/opencode-assets/agents/plan-checker.md +27 -0
- package/opencode-assets/agents/researcher.md +20 -0
- package/opencode-assets/agents/reviewer.md +22 -0
- package/opencode-assets/commands/bestest-go.md +5 -0
- package/opencode-assets/commands/bestest-status.md +5 -0
- package/package.json +30 -0
- package/payload/VERSION +1 -0
- package/payload/agents/plan-checker.md +25 -0
- package/payload/agents/researcher.md +17 -0
- package/payload/agents/reviewer.md +21 -0
- package/payload/hooks/guard.sh +111 -0
- package/payload/hooks/lock-guard.sh +118 -0
- package/payload/hooks/session-start.sh +63 -0
- package/payload/hooks/syntax-check.sh +96 -0
- package/payload/scripts/trace.sh +98 -0
- package/payload/skills/bestest-go/SKILL.md +37 -0
- package/payload/skills/bestest-status/SKILL.md +16 -0
- package/payload/skills/build/SKILL.md +29 -0
- package/payload/skills/debug/SKILL.md +20 -0
- package/payload/skills/interrogate/SKILL.md +48 -0
- package/payload/skills/interrogate/references/challenge-tactics.md +21 -0
- package/payload/skills/interrogate/references/question-protocol.md +21 -0
- package/payload/skills/plan/SKILL.md +25 -0
- package/payload/skills/plan/references/assets.md +15 -0
- package/payload/skills/research/SKILL.md +23 -0
- package/payload/skills/ship/SKILL.md +28 -0
- package/payload/skills/using-bestest/SKILL.md +20 -0
- package/payload/skills/using-bestest/references/claude-code.md +16 -0
- package/payload/skills/using-bestest/references/codex.md +17 -0
- package/payload/skills/using-bestest/references/opencode.md +18 -0
- package/payload/templates/DEBUG.md +17 -0
- package/payload/templates/DECISIONS.md +11 -0
- package/payload/templates/RUNBOOK.md +24 -0
- package/payload/templates/SPEC.md +35 -0
- package/payload/templates/STATE.md +20 -0
- package/payload/templates/UAT.md +10 -0
- package/payload/templates/report.md +15 -0
- package/payload/templates/task-card.md +24 -0
- package/src/guards.ts +80 -0
- package/src/index.ts +34 -0
- package/src/resolve.ts +22 -0
- 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
|
+
}
|