opencode-harness-kit 0.2.0 → 0.3.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 (57) hide show
  1. package/CHANGELOG.md +301 -0
  2. package/GLOBAL.md +50 -13
  3. package/MIGRATION.md +140 -0
  4. package/README.md +276 -122
  5. package/bin/oc-harness.mjs +571 -135
  6. package/dist/index.js +453 -159
  7. package/docs/KNOWN-LIMITS.md +189 -4
  8. package/docs/RESEARCH.md +80 -5
  9. package/docs/ROUTING.md +254 -0
  10. package/lanes.json +136 -0
  11. package/lanes.schema.json +55 -0
  12. package/package.json +13 -11
  13. package/profiles/beam-free.json +51 -0
  14. package/profiles/big-pickle.json +45 -0
  15. package/profiles/deepseek-v4.1-flash-free.json +49 -0
  16. package/profiles/fleet-all.json +139 -0
  17. package/profiles/fleet-safe.json +139 -0
  18. package/profiles/glm-5.3-flash-free.json +49 -0
  19. package/profiles/index.json +120 -37
  20. package/profiles/longcat-2.5-preview-free.json +14 -10
  21. package/profiles/mimo-v2.6-flash-free.json +44 -0
  22. package/profiles/minimax-m3.1-flash-free.json +51 -0
  23. package/profiles/nemotron-3-ultra-free.json +44 -0
  24. package/profiles/nemotron-3.5-lightning-free.json +44 -0
  25. package/profiles/nemotron-3.5-super-vl-free.json +52 -0
  26. package/profiles/schema.json +30 -11
  27. package/{agents → prompts}/build.md +2 -35
  28. package/prompts/doc.md +42 -0
  29. package/{agents → prompts}/locate.md +1 -16
  30. package/prompts/longform.md +34 -0
  31. package/{agents → prompts}/plan.md +2 -25
  32. package/{agents → prompts}/reviewer.md +2 -22
  33. package/prompts/schema.md +30 -0
  34. package/prompts/sweep.md +29 -0
  35. package/{agents → prompts}/verify.md +6 -29
  36. package/prompts/vision.md +33 -0
  37. package/retention.yml +75 -57
  38. package/skills/space-build/SKILL.md +16 -0
  39. package/skills/space-finish/SKILL.md +0 -10
  40. package/skills/space-review/SKILL.md +8 -0
  41. package/src/config.js +94 -69
  42. package/src/driver/run.mjs +237 -0
  43. package/src/effort.js +59 -22
  44. package/src/index.ts +15 -2
  45. package/src/lane-usage.js +142 -0
  46. package/src/profile.js +615 -221
  47. package/src/router.ts +148 -39
  48. package/src/tasks.js +248 -0
  49. package/src/telemetry.ts +98 -0
  50. package/docs/demo/demo.gif +0 -0
  51. package/docs/demo/demo.mp4 +0 -0
  52. package/docs/demo/render.py +0 -146
  53. package/examples/muse-spark-1.3-contributor-free.json +0 -41
  54. package/profiles/deepseek-v4-flash-free.json +0 -41
  55. package/profiles/muse-split-free.json +0 -64
  56. package/profiles/space-bunny-free.json +0 -42
  57. package/profiles/x-preview-f-free.json +0 -41
@@ -1,146 +0,0 @@
1
- #!/usr/bin/env python3
2
- """Render the space-harness demo video from REAL CLI output.
3
-
4
- Nothing here is typed by hand: the script runs `init` and `verify` against a
5
- throwaway config dir, captures what the CLI actually prints, and draws that.
6
- The only cosmetic edit is replacing the random tmp path with
7
- ~/.config/opencode so the frames stay readable.
8
- """
9
- import os
10
- import re
11
- import subprocess
12
- import sys
13
- import tempfile
14
-
15
- from PIL import Image, ImageDraw, ImageFont
16
-
17
- REPO = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) # docs/demo/render.py -> repo root
18
- ROOT = REPO
19
- BIN = ["node", os.path.join(ROOT, "bin", "oc-harness.mjs")]
20
- W, H = 1280, 720
21
- FPS = 15
22
- FONT = ImageFont.truetype("/System/Library/Fonts/Menlo.ttc", 22)
23
- LINE_H = 34
24
- TOP = 40
25
- LEFT = 48
26
- BG = (13, 17, 23)
27
- WHITE = (230, 237, 243)
28
- GREEN = (63, 185, 80)
29
- DIM = (139, 148, 158)
30
- YELLOW = (210, 153, 34)
31
- RED = (248, 81, 73)
32
-
33
- ANSI = re.compile(r"\x1b\[(\d+)m")
34
-
35
-
36
- def run_clip(args, cfg):
37
- env = dict(os.environ, OPENCODE_CONFIG_DIR=cfg)
38
- p = subprocess.run(BIN + args, capture_output=True, text=True, env=env, cwd=ROOT)
39
- assert p.returncode == 0, p.stderr[-2000:]
40
- out = (p.stdout + p.stderr).replace(cfg, "~/.config/opencode")
41
- return [ln.rstrip() for ln in out.splitlines()]
42
-
43
-
44
- def spans(line):
45
- """Split an ANSI-coloured line into (text, colour) runs."""
46
- runs, cur, buf = [], WHITE, ""
47
- pos = 0
48
- for m in ANSI.finditer(line):
49
- if m.start() > pos:
50
- buf += line[pos : m.start()]
51
- code = m.group(1)
52
- if buf:
53
- runs.append((buf, cur))
54
- buf = ""
55
- cur = {"0": WHITE, "1": WHITE, "2": DIM, "31": RED, "32": GREEN, "33": YELLOW}.get(code, WHITE)
56
- pos = m.end()
57
- if pos < len(line):
58
- buf += line[pos:]
59
- if buf:
60
- runs.append((buf, cur))
61
- return runs or [("", WHITE)]
62
-
63
-
64
- def draw_terminal(draw, lines, prompt="$ "):
65
- draw.rectangle([0, 0, W, H], fill=BG)
66
- maxl = (H - TOP) // LINE_H
67
- if len(lines) > maxl:
68
- head = [lines[0]] if lines[0][0] == "cmd" else []
69
- lines = head + [ln for ln in lines if ln[0] != "cmd"][-(maxl - len(head)) :]
70
- y = TOP
71
- for kind, payload in lines:
72
- if y > H - LINE_H:
73
- break
74
- if kind == "cmd":
75
- draw.text((LEFT, y), prompt, font=FONT, fill=GREEN)
76
- x = LEFT + draw.textlength(prompt, font=FONT)
77
- for text, colour in spans(payload):
78
- draw.text((x, y), text, font=FONT, fill=colour)
79
- x += draw.textlength(text, font=FONT)
80
- elif kind == "card":
81
- text, colour = payload
82
- draw.text((LEFT, y), text, font=FONT, fill=colour)
83
- else:
84
- x = LEFT
85
- for text, colour in spans(payload):
86
- draw.text((x, y), text, font=FONT, fill=colour)
87
- x += draw.textlength(text, font=FONT)
88
- y += LINE_H
89
-
90
-
91
- def frames_for_command(cmd, output):
92
- frames = []
93
- typed = ""
94
- for ch in cmd:
95
- typed += ch
96
- frames.append([("cmd", typed)])
97
- for i in range(0, len(output), 2):
98
- frames.append([("cmd", cmd)] + [("out", ln) for ln in output[: i + 2]])
99
- full = [("cmd", cmd)] + [("out", ln) for ln in output]
100
- frames.extend([full] * (2 * FPS))
101
- return frames
102
-
103
-
104
- def card(lines, hold=2):
105
- return [[("card", ln) for ln in lines]] * (hold * FPS)
106
-
107
-
108
- def main(out_dir):
109
- cfg = tempfile.mkdtemp(prefix="demo-")
110
- init_out = run_clip(["init", "--profile", "space-bunny-free", "--yes"], cfg)
111
- verify_out = run_clip(["verify"], cfg)
112
- seq = []
113
- seq += card([(("space-harness", WHITE)), (("one command from zero to working", DIM))], hold=2)
114
- seq += frames_for_command("npx -y opencode-harness-kit init", [l for l in init_out if l.strip()])
115
- seq += frames_for_command("oc-harness verify", [l for l in verify_out if l.strip()])
116
- seq += card(
117
- [
118
- (("done. restart opencode, then ask it something", GREEN)),
119
- (("github.com/crytobot459/space-harness", WHITE)),
120
- (("free models. verified setup.", DIM)),
121
- ],
122
- hold=3,
123
- )
124
- os.makedirs(out_dir, exist_ok=True)
125
- frame_dir = tempfile.mkdtemp(prefix="frames-")
126
- for i, lines in enumerate(seq):
127
- flat = []
128
- for kind, payload in lines:
129
- flat.append((kind, payload))
130
- img = Image.new("RGB", (W, H), BG)
131
- draw_terminal(ImageDraw.Draw(img), flat)
132
- img.save(os.path.join(frame_dir, f"f{i:04d}.png"))
133
- mp4 = os.path.join(out_dir, "demo.mp4")
134
- subprocess.run(
135
- ["ffmpeg", "-y", "-v", "error", "-framerate", str(FPS), "-i", os.path.join(frame_dir, "f%04d.png"),
136
- "-c:v", "libx264", "-pix_fmt", "yuv420p", "-crf", "23", mp4], check=True)
137
- gif = os.path.join(out_dir, "demo.gif")
138
- subprocess.run(
139
- ["ffmpeg", "-y", "-v", "error", "-framerate", "10", "-i", os.path.join(frame_dir, "f%04d.png"),
140
- "-vf", "scale=640:-1:flags=lanczos,split[s0][s1];[s0]palettegen=max_colors=64[p];[s1][p]paletteuse",
141
- gif], check=True)
142
- print(f"wrote {mp4} ({os.path.getsize(mp4)//1024}KB) and {gif} ({os.path.getsize(gif)//1024}KB)")
143
-
144
-
145
- if __name__ == "__main__":
146
- main(sys.argv[1] if len(sys.argv) > 1 else os.path.join(ROOT, "docs", "demo"))
@@ -1,41 +0,0 @@
1
- {
2
- "$comment": "Contrarian example. Kept out of profiles/index.json on purpose: free, 1M context, and widely used, but its provider is permitted to train on your prompts and it has no `max` effort. `oc-harness list` shows it struck through with the reason. Install it with `oc-harness use muse-spark-1.3-contributor-free` and the warning is repeated on install.",
3
- "id": "muse-spark-1.3-contributor-free",
4
- "title": "Muse Spark 1.3 Contributor Free (OpenCode Zen)",
5
- "provider": "opencode",
6
- "model": "muse-spark-1.3-contributor-free",
7
- "family": "muse-free",
8
- "verified": "2026-10-07",
9
- "retention": "may-train",
10
- "notes": "Capability/privacy tradeoff, not a recommendation. Served over /v1/responses, a better cache dialect than chat/completions, and it supports temperature. But: 'Heavily discounted token pricing in exchange for permission to use your prompts and completions to train future Meta models', and its top effort is xhigh rather than max. Do not make this the default for private code.",
11
- "limits": {
12
- "context": 1048576,
13
- "output": 131072
14
- },
15
- "supports": {
16
- "reasoning": true,
17
- "temperature": true,
18
- "toolCall": true,
19
- "structuredOutput": true,
20
- "attachment": true
21
- },
22
- "effort": {
23
- "levels": ["minimal", "low", "medium", "high", "xhigh"],
24
- "default": "xhigh",
25
- "reserveShare": 0.12
26
- },
27
- "roles": {
28
- "plan": "max",
29
- "build": "high",
30
- "reviewer": "xhigh",
31
- "locate": "low",
32
- "verify": "low"
33
- },
34
- "prune": {
35
- "enabled": true,
36
- "keepRecentToolCalls": 6,
37
- "keepRecentTurns": 3,
38
- "keepUserMessages": true
39
- },
40
- "fallback": ["x-preview-f-free", "longcat-2.5-preview-free"]
41
- }
@@ -1,41 +0,0 @@
1
- {
2
- "$comment": "Fallback tier 3. The smallest context in the queue at 200k, which is why prune.keepRecentToolCalls drops: the same settings tuned for a 1M model overflow this one.",
3
- "id": "deepseek-v4-flash-free",
4
- "title": "DeepSeek V4 Flash Free (OpenCode Zen)",
5
- "provider": "opencode",
6
- "model": "deepseek-v4-flash-free",
7
- "family": "deepseek-flash",
8
- "verified": "2026-10-07",
9
- "retention": "zero-retention",
10
- "notes": "Text only, no attachment, 200k context. Included to show the compiler scaling pruning to the model: reserveShare and retention fall together, so a 1M-tuned profile cannot silently overflow a 200k model.",
11
- "limits": {
12
- "context": 200000,
13
- "output": 128000
14
- },
15
- "supports": {
16
- "reasoning": true,
17
- "temperature": true,
18
- "toolCall": true,
19
- "structuredOutput": true,
20
- "attachment": false
21
- },
22
- "effort": {
23
- "levels": ["low", "high", "max"],
24
- "default": "max",
25
- "reserveShare": 0.12
26
- },
27
- "roles": {
28
- "plan": "max",
29
- "build": "high",
30
- "reviewer": "max",
31
- "locate": "low",
32
- "verify": "low"
33
- },
34
- "prune": {
35
- "enabled": true,
36
- "keepRecentToolCalls": 4,
37
- "keepRecentTurns": 2,
38
- "keepUserMessages": true
39
- },
40
- "fallback": []
41
- }
@@ -1,64 +0,0 @@
1
- {
2
- "$comment": "Split profile. The heavy reasoning roles run on Muse Spark 1.3 because it is the strongest free model on Zen by a wide margin (Terminal-Bench 2.1 88.8% vs Nemotron 3 Ultra's 56.4%, DeepSWE v1.1 75.4%). The bulk-output roles stay on Space Bunny because its 524,288-token output cap is 4x Muse Spark's 131,072, and scaffolding or migrating many files is where that ceiling is actually felt. `retention` below describes the BASE model only; the config header prints the effective retention, which is may-train because three roles run on Muse Spark.",
3
- "id": "muse-split-free",
4
- "title": "Muse Spark 1.3 (heavy) + Space Bunny Free (bulk output)",
5
- "provider": "opencode",
6
- "model": "space-bunny-free",
7
- "family": null,
8
- "verified": "2026-10-07",
9
- "retention": "zero-retention",
10
- "notes": "Opt-in split, not a default. Muse Spark is a Meta contributor-tier free endpoint: its provider may train on your prompts and completions. Three of the five roles are therefore may-train even though the base model is zero-retention. Install it only for public or throwaway repositories. Effort requests below are resolved against each role's own model, so plan and reviewer ask for `max` and land on `xhigh`, which is the top Muse Spark offers.",
11
- "limits": {
12
- "context": 1048576,
13
- "input": 524288,
14
- "output": 524288
15
- },
16
- "supports": {
17
- "reasoning": true,
18
- "temperature": false,
19
- "toolCall": true,
20
- "structuredOutput": false,
21
- "attachment": true
22
- },
23
- "effort": {
24
- "levels": ["low", "medium", "high", "xhigh", "max"],
25
- "default": "max",
26
- "reserveShare": 0.12
27
- },
28
- "roles": {
29
- "plan": "max",
30
- "build": "high",
31
- "reviewer": "max",
32
- "locate": "low",
33
- "verify": "low"
34
- },
35
- "roleModels": {
36
- "plan": {
37
- "model": "muse-spark-1.3-contributor-free",
38
- "levels": ["minimal", "low", "medium", "high", "xhigh"]
39
- },
40
- "build": {
41
- "model": "muse-spark-1.3-contributor-free",
42
- "levels": ["minimal", "low", "medium", "high", "xhigh"]
43
- },
44
- "reviewer": {
45
- "model": "muse-spark-1.3-contributor-free",
46
- "levels": ["minimal", "low", "medium", "high", "xhigh"]
47
- },
48
- "locate": {
49
- "model": "space-bunny-free",
50
- "levels": ["low", "medium", "high", "xhigh", "max"]
51
- },
52
- "verify": {
53
- "model": "space-bunny-free",
54
- "levels": ["low", "medium", "high", "xhigh", "max"]
55
- }
56
- },
57
- "prune": {
58
- "enabled": true,
59
- "keepRecentToolCalls": 6,
60
- "keepRecentTurns": 3,
61
- "keepUserMessages": true
62
- },
63
- "fallback": ["x-preview-f-free", "longcat-2.5-preview-free", "deepseek-v4-flash-free"]
64
- }
@@ -1,42 +0,0 @@
1
- {
2
- "$comment": "Validated against models.dev on 2026-10-07. Regenerate with `oc-harness use --refresh`.",
3
- "id": "space-bunny-free",
4
- "title": "Space Bunny Free (OpenCode Zen)",
5
- "provider": "opencode",
6
- "model": "space-bunny-free",
7
- "family": null,
8
- "verified": "2026-10-07",
9
- "retention": "zero-retention",
10
- "notes": "Stealth preview reasoning model. Served over /v1/chat/completions (openai-compatible), which is the weaker cache dialect compared with models on /v1/responses. At the time of writing it is the only free model on Zen whose top reasoning effort is `max` and whose provider is zero-retention. Free for a limited time; expect the slot to rotate.",
11
- "limits": {
12
- "context": 1048576,
13
- "input": 524288,
14
- "output": 524288
15
- },
16
- "supports": {
17
- "reasoning": true,
18
- "temperature": false,
19
- "toolCall": true,
20
- "structuredOutput": false,
21
- "attachment": true
22
- },
23
- "effort": {
24
- "levels": ["low", "medium", "high", "xhigh", "max"],
25
- "default": "max",
26
- "reserveShare": 0.12
27
- },
28
- "roles": {
29
- "plan": "max",
30
- "build": "high",
31
- "reviewer": "max",
32
- "locate": "low",
33
- "verify": "low"
34
- },
35
- "prune": {
36
- "enabled": true,
37
- "keepRecentToolCalls": 6,
38
- "keepRecentTurns": 3,
39
- "keepUserMessages": true
40
- },
41
- "fallback": ["x-preview-f-free", "longcat-2.5-preview-free", "deepseek-v4-flash-free"]
42
- }
@@ -1,41 +0,0 @@
1
- {
2
- "$comment": "Fallback tier 1 for space-bunny-free. Zero-retention, 1M context, and a `max` effort, but only three levels: there is no medium or xhigh, so roles asking for those resolve down to high.",
3
- "id": "x-preview-f-free",
4
- "title": "x-preview-f Free (OpenCode Zen)",
5
- "provider": "opencode",
6
- "model": "x-preview-f-free",
7
- "family": null,
8
- "verified": "2026-10-07",
9
- "retention": "zero-retention",
10
- "notes": "Only three effort levels (low, high, max) and a 131k completion ceiling, so long single answers are not its strength. Kept as first fallback: same 1M context class and the same zero-retention terms as the default.",
11
- "limits": {
12
- "context": 1000000,
13
- "output": 131072
14
- },
15
- "supports": {
16
- "reasoning": true,
17
- "temperature": true,
18
- "toolCall": true,
19
- "structuredOutput": true,
20
- "attachment": true
21
- },
22
- "effort": {
23
- "levels": ["low", "high", "max"],
24
- "default": "max",
25
- "reserveShare": 0.12
26
- },
27
- "roles": {
28
- "plan": "max",
29
- "build": "high",
30
- "reviewer": "max",
31
- "locate": "low",
32
- "verify": "low"
33
- },
34
- "prune": {
35
- "enabled": true,
36
- "keepRecentToolCalls": 6,
37
- "keepRecentTurns": 3,
38
- "keepUserMessages": true
39
- },
40
- "fallback": ["longcat-2.5-preview-free", "deepseek-v4-flash-free"]
41
- }