feat: add OpenCode as a fourth supported host (#53)#54
Merged
Conversation
OpenCode (sst/opencode at opencode.ai) joins Claude Code, Codex CLI, and Gemini CLI. Spike confirmed both architectural risks resolved positively: OpenCode reads ~/.claude/skills/ natively as a Claude-compat directory (skill discoverable without re-install), and the built-in `task` tool has the same shape as Claude Code's Task tool (sub-agent dispatch translates 1:1). - bin/resumasher-telemetry-log: sniff OPENCODE / OPENCODE_VERSION env vars; add ~/.opencode/skills/ to user-scope detection. - scripts/orchestration.py: add .opencode to DEFAULT_IGNORE_DIRS so project-scope OpenCode installs don't get the skill source mined. - SKILL.md: add ~/.opencode/skills/resumasher to path resolution prologue (both copies); document `task` / `webfetch` / `websearch` / `question` tool names alongside the existing three hosts; add HOST enum value `opencode_cli` everywhere. - README, PRIVACY: add OpenCode install + update blocks; default to the Claude-compat path with native ~/.opencode/skills/ documented as the alternative. - Tests: OPENCODE_VERSION env-var sniff assertion, install-scope auto-detection for ~/.opencode/skills/, .opencode parametrize on the self-mining-risk regression. All 435 deterministic tests pass. Skipped on purpose: install.sh stays document-only (Claude-compat path is the recommended default, no branching); inline-prompt sub-agent dispatch stays the model (Codex pattern, no .opencode/agents/ files shipped). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The first real /resumasher run under OpenCode used qwen3.6-35b-a3b (local Ollama-class model). The PDF rendered correctly but the transcript showed three shell errors and one parser gap that strong models silently paper over. All four are host-agnostic and reproduce the bug class behind #44/#45/#46/#50: SKILL.md prescribed a shell idiom that didn't survive contact with real sub-agent text. 1. SKILL.md Phase 0: prescribe `mkdir -p $STUDENT_CWD/.resumasher` before the config.json heredoc. Fresh student folders have no .resumasher/ dir; without the mkdir the redirect fails silently and the next phase runs against an empty config. 2. orchestration.py read-resume: error hard at the CLI boundary when called with empty/whitespace-only path. Pre-fix `Path("")` resolved to "." and produced IsADirectoryError deep in the stack while the orchestrator continued with a 0-byte resume.txt. Now exits 2 with a FAILURE message naming the likely cause ($RESUME_PATH not re-derived in fresh Bash call). 3. SKILL.md Phase 2: prescribe `cat > file << 'HEREDOC'` for saving sub-agent text outputs (quoted-delimiter heredoc is byte-literal and immune to apostrophes/dollar-signs/backticks). Explicitly forbid the broken `var='...'; echo "$var" > file` pattern with the apostrophe failure mode called out in prose. 4. orchestration.py extractor regexes: tolerate markdown-bold variants around sentinel keys (`**ROLE:**`, `**KEY**:`, `KEY: **value**`, `**KEY:** **value**`). Switched from greedy `\*{0,2}` groups (which mis-parsed `**KEY:** **value**`) to a `[\s*]*` character class around each token — robust to all four bold variants plus plain. Tests: 12 parametrized markdown-bold cases per extractor; new test_skill_md_weak_model_guards.py pins the SKILL.md prescriptions so future edits don't regress; CLI-level read-resume empty-path test asserts exit 2 + the named-cause stderr message. All 450 deterministic tests green (was 435 + 15 new). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Second qwen3.6-35b OpenCode run (samples-issue42/session-ses_235c.md) completed the full nine-phase pipeline end-to-end but surfaced four more weak-model behaviors that the SKILL.md prescriptions were too soft to prevent. 1. commands/resumasher.md slash-command shim. OpenCode's `/resumasher jd.md` was silently dropping the argument because we shipped no command file — OpenCode falls back to skill auto-discovery and pastes SKILL.md as a user message. Six-line shim + install.sh detection (gates on `command -v opencode`, honors XDG_CONFIG_HOME) + README + 5 deterministic tests. 2. Sub-agent dispatch section reordered. Qwen tried Claude Code's `subagent_type="general-purpose"` first and got rejected on OpenCode. SKILL.md now leads the dispatch bullet with an explicit "use the entry that matches the CLI you're actually running in" callout, names the rejection error verbatim, and inlines the `NOT "general-purpose"` clarification on the OpenCode entry. 3. Phase 3 forbids manual fit-field writes. Qwen bypassed `extract-fit-fields` and wrote each field via individual `echo` commands, shipping `Entry/Junior` (not in our enum) and a 200-char recommendation prose to telemetry. SKILL.md Phase 3 now explicitly forbids manual writes with the rationale and the run reference inline. 4. Sub-agent prompt section forbids prompt rewriting. Qwen rewrote the cover-letter prompt (only one of five), inverting "Start with a greeting H1" to "End with" and producing a PDF where the salutation rendered as a giant H1 at the bottom. SKILL.md now has a load-bearing "Pass $PROMPT AS-IS — do not paraphrase, summarize, shorten, or rewrite" instruction. Filed #55 (v0.5 epic) for templated per-host SKILL.md generation — that would address Finding (2) by removing cross-host enumeration ambiguity, but doesn't touch the other three findings, so it's the right move for v0.5+ rather than this PR. All 455 deterministic tests pass (was 450 + 5 new shim tests). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…#53) A third qwen3.6-35b OpenCode run (samples-issue42/session-ses_2359.md) exposed that OpenCode caps tool output at 51,200 bytes by default (packages/opencode/src/tool/truncate.ts: MAX_BYTES = 50 * 1024). Our SKILL.md is ~82KB. When the cap is too low: - OpenCode truncates the skill output at ~38% (preserves the first 50,726 bytes — exactly the 50KB cap minus a partial trailing line) - The rest is replaced with a "Use the Task tool to read the rest" meta-instruction, which weak models routinely ignore - Phases 7-9 prescriptions never reach the model - qwen produces wrong PDF filenames (`tailored-resume.pdf` instead of `resume.pdf`), only renders 2 of 3 PDFs (no interview-prep.pdf), and ships skeletal Phase 9 telemetry with the wrong flag name (`--recommendation` instead of `--fit-recommendation`) Strong cloud models (Claude, GPT-5) usually recover by inferring the missing phases. The truncation is configurable: `tool_output.max_bytes` in `~/.config/opencode/opencode.json` (or XDG-equivalent). 100KB is double the default and fits SKILL.md plus growth headroom. install.sh now READS the user's opencode config (never writes to it) via the venv Python and prints a heads-up if the cap is below SKILL.md's size, with the exact JSON snippet to add. Read-only contract is pinned by a test that asserts no write redirect exists in the OpenCode detection block. README OpenCode section adds the same prescription with the rationale and a link to the OpenCode source. Issue #55 (templated per-host SKILL.md) downgrades from "urgent" to "optimization" — no longer required for correctness once the cap is raised. All 456 deterministic tests pass (was 455 + 1 new cap-detection test). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…cp1252 (#53) Windows CI on PR #54 caught the emoji `⚠️ ` (U+26A0 + U+FE0F) I added in the previous commit. Two cooperating issues: 1. tests/test_install_python_probe.py:169 reads install.sh with a bare `Path.read_text()`. Python on Windows defaults that to cp1252, which can't decode the emoji's UTF-8 byte 0x8f at position 7802. Same bug class as v0.4.2's stdout encoding fix (#34). Test now passes `encoding="utf-8"` explicitly with a comment naming the failure mode. 2. The emoji wasn't pulling its weight as UX. Cohort is ~90% Windows, where Git Bash and cmd render emoji unreliably. Replaced `⚠️ ` with plain `NOTE:` in the install.sh warning. ASCII renders consistently on every terminal. The two fixes are belt-and-suspenders: the test fix prevents this specific class of failure from ever recurring; the emoji removal removes the immediate trigger AND improves the install output for the cohort's actual platform mix. install.sh has only em-dashes as non-ASCII now (3-byte UTF-8 that survives cp1252 decode without hitting the 0x8f land mine). All 456 deterministic tests pass locally; the previously failing test_install_sh_invokes_pip_via_python_module_form now decodes install.sh cleanly under both UTF-8 and cp1252 simulation. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #53.
Summary
Adds OpenCode (sst/opencode, opencode.ai) as a fourth supported host alongside Claude Code, Codex CLI, and Gemini CLI.
The spike comment on #53 confirmed both architectural risks resolved positively before committing to scope:
~/.claude/skills/natively as a Claude-compat directory. Verified empirically:opencode debug skillfrom a/tmp/opencode-spike/directory with resumasher cloned to.claude/skills/resumasher/discovered the skill and parsed our YAML frontmatter cleanly.tasktool has identical{description, prompt, subagent_type}shape to Claude Code'sTask(confirmed inpackages/opencode/src/tool/task.ts). Resumasher's existing dispatch pattern translates 1:1.Tool surface is full parity with Claude Code:
task↔Task,webfetch↔WebFetch,websearch↔WebSearch(gated onOPENCODE_ENABLE_EXA=1or the OpenCode provider),question↔AskUserQuestion.Concrete edits
bin/resumasher-telemetry-log— sniffOPENCODE/OPENCODE_VERSIONenv vars; add~/.opencode/skills/to user-scope detection sohost=opencode_clilands in the dashboard.scripts/orchestration.py— add.opencodetoDEFAULT_IGNORE_DIRSso a project-scope OpenCode install doesn't get its own skill source mined as evidence.SKILL.md— add~/.opencode/skills/resumasher(and project variants) to the path resolution prologue (both copies, guarded bytest_skill_md_prologue.py); documenttask/webfetch/websearch/questiontool names alongside the existing three hosts at every cross-host enumeration; addopencode_clito the HOST enum everywhere.README.md— OpenCode install block (defaulting to Claude-compat path; native~/.opencode/skills/documented as the alternative) + OpenCode update block alongside the existing three.PRIVACY.md— install-path enumerations and host list updated.OPENCODE_VERSIONenv-var sniff assertion, install-scope auto-detection for~/.opencode/skills/,.opencodeadded to the parametrized self-mining-risk regression.Decisions explicitly skipped (per scoping convo)
install.shstays document-only — Claude-compat path is the recommended default, no install.sh branching needed..opencode/agents/<name>.mdfiles shipped — OpenCode's built-ingeneralsubagent_type accepts arbitrary prompts at runtime, identical to how Codex handles dispatch.Test plan
RESUMASHER_SKIP_LIVE=1opencode authconfigured against Anthropic, before declaring the port done🤖 Generated with Claude Code