Skip to content

feat: add OpenCode support - #746

Closed
baleen37 wants to merge 14 commits into
mainfrom
feat/setup-oc
Closed

feat: add OpenCode support#746
baleen37 wants to merge 14 commits into
mainfrom
feat/setup-oc

Conversation

@baleen37

@baleen37 baleen37 commented Aug 2, 2026

Copy link
Copy Markdown
Owner

Summary

  • Generate an OpenCode plugin bundle (.opencode/) from the Claude Code source of truth via bun run sync:opencode (generator scripts/generate-opencode-artifacts.sh, template scripts/opencode-plugin/bstack.ts)
  • Add drift check (bun run check:opencode), symlink installer (bun run install:opencode~/.config/opencode/), and bstack.ts commit-guard plugin (blocks --no-verify, hook bypasses)
  • Wire .opencode/ into semantic-release (prepare sync + commit assets) and CI/release/sync workflows (sync + drift steps)
  • Add BATS coverage for generated artifacts and workflow steps
  • Document OpenCode compatibility in README/CLAUDE.md

Test Plan

  • bun run test — all BATS suites pass
  • bun run check:opencode — no drift
  • pre-commit run --all-files — only pre-existing markdownlint violations (present on main before this branch)
  • Smoke test: install:opencode symlinks 13 skills, 4 agents, command, plugin

Summary by CodeRabbit

  • New Features

    • Added OpenCode compatibility with synchronized skills, agents, commands, and plugins.
    • Added installation support that safely backs up existing configurations and creates OpenCode links.
    • Added research, verification, testing, deployment, pull-request, and workflow automation capabilities.
    • Added safeguards against Git hook bypasses and history-rewriting commands.
  • Documentation

    • Documented OpenCode setup, supported features, limitations, and artifact maintenance.
  • Tests

    • Added coverage for artifact generation, drift detection, installation, plugin behavior, and workflow integration.

@baleen37
baleen37 enabled auto-merge (squash) August 2, 2026 12:39
@coderabbitai

coderabbitai Bot commented Aug 2, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The PR adds generated OpenCode support, including skills, agents, commands, plugins, artifact scripts, CI and release integration, installation, documentation, research evaluation tooling, and Bats coverage.

Changes

OpenCode support

Layer / File(s) Summary
Artifact generation and installation
package.json, scripts/*opencode*, scripts/opencode-plugin/*, docs/plans/*
Adds OpenCode generation, drift checking, synchronization, installation, and Git command protection.
OpenCode agents, commands, and skills
.opencode/agents/*, .opencode/command/*, .opencode/skills/*
Adds operational skills, agent definitions, command workflows, templates, and references.
Research evaluation runtime and scoring
.opencode/skills/research/evals/*, .opencode/skills/research/scripts/*
Adds research schemas, scenarios, runtime adapters, scoring, persistence, rescoring, and baseline comparison.
CI, release, and marketplace integration
.github/workflows/*, .releaserc.js, CLAUDE.md, README.md
Synchronizes and validates OpenCode artifacts in CI, releases, and marketplace updates. Documentation describes generation, installation, limitations, and validation.
Artifact and workflow validation
tests/*opencode*, tests/github_workflows.bats
Adds coverage for artifact contents, frontmatter conversion, plugin behavior, drift detection, and workflow configuration.

Estimated code review effort: 5 (Critical) | ~120 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Marketplace
  participant Generator
  participant OpenCodeBundle
  participant CI
  participant Release
  Marketplace->>Generator: provide eligible plugin metadata
  Generator->>OpenCodeBundle: generate skills, agents, commands, and plugin
  CI->>Generator: synchronize and check artifacts
  CI->>OpenCodeBundle: verify tracked and untracked files
  Release->>Generator: synchronize release artifacts
  Release->>OpenCodeBundle: include .opencode assets
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 1.41% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the pull request's primary change: adding OpenCode support.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 2
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🛠️ Fix failing CI checks 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/setup-oc

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 17

Note

Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
.opencode/skills/ship/SKILL.md (1)

1-104: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Regenerate the stale artifact before merge.

CI reports that .opencode/skills/ship/SKILL.md differs from the expected generated output, so the artifact check fails. Update plugins/me/skills/ship/SKILL.md, run bun run sync:opencode, then run bun run check:opencode, and commit the generated result. Do not edit generated .opencode content directly.

As per coding guidelines, committed .opencode/ bundles must be checked with bun run check:opencode.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.opencode/skills/ship/SKILL.md around lines 1 - 104, Regenerate the stale
ship skill artifact by updating the source content in
plugins/me/skills/ship/SKILL.md rather than editing
.opencode/skills/ship/SKILL.md directly. Run bun run sync:opencode, verify the
generated bundle with bun run check:opencode, and commit the resulting generated
changes.

Sources: Coding guidelines, Pipeline failures

🟡 Minor comments (10)
CLAUDE.md-58-59 (1)

58-59: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Document every OpenCode source that requires regeneration.

The generator copies skills and converts agent and command files. Metadata-only guidance can leave .opencode/ stale after source content changes.

  • CLAUDE.md#L58-L59: Require bun run sync:opencode after changes to marketplace metadata, skills, agents, commands, or the plugin template.
  • README.md#L38-L43: List those source directories and files with the marketplace metadata as OpenCode generation inputs.

As per coding guidelines, .opencode/** requires bun run check:opencode to verify that the committed bundle is in sync.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@CLAUDE.md` around lines 58 - 59, Update CLAUDE.md lines 58-59 to require bun
run sync:opencode after changes to marketplace metadata, skills, agents,
commands, or the plugin template, while retaining bun run check:opencode for
verification. Update README.md lines 38-43 to document the marketplace metadata,
source directories, and files that feed OpenCode generation.

Source: Coding guidelines

.opencode/skills/research/scripts/evaluator.ts-612-623 (1)

612-623: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

A candidate-only scenario is reported as a quality regression.

Line 622 sets qualityRegression when baselineRun is absent. A scenario that exists only in the candidate summary has no baseline counterpart. That happens whenever a new scenario is added to scenarios.json and the baseline artifacts predate it. compareSummaries then returns recommendation: "reject" at Lines 667-668, which is the strongest verdict.

The reverse case is already handled separately at Lines 625-635, where a baseline-only scenario means lost coverage. Distinguish new coverage from lost coverage.

♻️ Proposed fix
-    if (!baselineRun || severity[candidateRun.status] > severity[baselineRun.status]) qualityRegression = true;
+    if (baselineRun && severity[candidateRun.status] > severity[baselineRun.status]) qualityRegression = true;
+    if (!baselineRun && candidateRun.status !== "pass") qualityRegression = true;

If treating every new scenario as a rejection is intentional, add a comment that records the decision.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.opencode/skills/research/scripts/evaluator.ts around lines 612 - 623,
Update the qualityRegression assignment in compareSummaries so candidate-only
runs (when baselineRun is absent) do not count as regressions; only compare
severity when both baselineRun and candidateRun exist. Preserve the separate
baseline-only handling for lost coverage, and add a clarifying comment only if
candidate-only scenarios are intentionally meant to reject.
scripts/install-opencode.sh-20-25 (1)

20-25: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Preserve an existing backup.

If ${link_path}.bak already exists as a regular file, mv overwrites it. This can destroy the only backup of user-managed OpenCode configuration.

Fail before moving, or generate a unique backup name when ${link_path}.bak already exists.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/install-opencode.sh` around lines 20 - 25, Update the backup handling
before the ln command so an existing regular ${link_path}.bak is never
overwritten: either fail before mv or select a unique unused backup path when
the default exists, then move ${link_path} to that path and preserve the symlink
creation flow.
docs/plans/2026-08-02-opencode-support.md-7-7 (1)

7-7: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Use the actual plugin filename.

The architecture summary names plugin.ts, but the implementation creates bstack.ts in scripts/opencode-plugin/ and .opencode/plugins/. Use bstack.ts consistently to prevent incorrect file references.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/plans/2026-08-02-opencode-support.md` at line 7, Update the architecture
summary to replace every reference to the generated plugin filename “plugin.ts”
with “bstack.ts,” matching the implementation under scripts/opencode-plugin/ and
.opencode/plugins/.
docs/plans/2026-08-02-opencode-support.md-625-625 (1)

625-625: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Document the actual collision behavior.

The generator exits with Skill name collision; it does not resolve collisions through OpenCode configuration permissions. Tell users to rename the source skill or change the generator's collision policy.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/plans/2026-08-02-opencode-support.md` at line 625, Update the skill-name
collision guidance in the OpenCode support plan to state that the generator
exits with “Skill name collision” rather than resolving collisions through
OpenCode configuration permissions. Instruct users to rename the source skill or
change the generator’s collision policy.
docs/plans/2026-08-02-opencode-support.md-608-629 (1)

608-629: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Fix the nested Markdown fences.

The ```bash fence at Line 617 closes the outer ```markdown fence. The README example then renders as normal plan text, and the fence at Line 629 is parsed incorrectly. Use four backticks for the outer fence or indent the inner code block.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/plans/2026-08-02-opencode-support.md` around lines 608 - 629, The
OpenCode compatibility section’s outer Markdown fence conflicts with its nested
bash code fence. Update the outer fence around the section beginning with “##
OpenCode 호환성” to use four backticks, preserving the inner ```bash block and all
surrounding content.

Source: Linters/SAST tools

.opencode/command/autoresearch.md-15-18 (1)

15-18: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Add blank lines around the fenced block.

markdownlint-cli2 reports MD031 at Lines 16 and 18. Add one blank line before and after the bash fence.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.opencode/command/autoresearch.md around lines 15 - 18, Add a blank line
immediately before and after the fenced bash block in the autoresearch
instructions, preserving the existing command and surrounding text.

Source: Linters/SAST tools

.opencode/skills/competitive-agents/SKILL.md-87-97 (1)

87-97: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Add blank lines after the headings.

markdownlint-cli2 reports MD022 at Lines 89, 93, and 96. Add a blank line after each heading.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.opencode/skills/competitive-agents/SKILL.md around lines 87 - 97, Update
the “Analyze and present in this format” template in SKILL.md by adding a blank
line after each heading: “### Analysis,” “### Synthesized Solution,” and “###
Rationale.” Preserve the existing content and formatting otherwise.

Source: Linters/SAST tools

.opencode/skills/autoresearch/SKILL.md-144-150 (1)

144-150: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Use a Conventional Commit subject for experiment keeps.

The keep path uses raw <description> as the commit subject. It does not require type(scope): description, so semantic-release may not classify the commit correctly.

Based on learnings, use Conventional Commits format: type(scope): description.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.opencode/skills/autoresearch/SKILL.md around lines 144 - 150, Update the
“If keep” commit command in the autoresearch instructions to require a
Conventional Commit subject in the form type(scope): description instead of raw
<description>. Preserve the existing result JSON in the commit body and apply
this format specifically to experiment keep commits.

Source: Learnings

.opencode/skills/autoresearch/SKILL.md-142-168 (1)

142-168: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Add blank lines around the shell fences.

markdownlint-cli2 reports MD031 at Lines 145, 153, and 158. Add blank lines before and after each fenced block.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.opencode/skills/autoresearch/SKILL.md around lines 142 - 168, Add blank
lines immediately before and after each shell fenced code block in the “Git
operations” and “Append result to JSONL” sections of SKILL.md, including the
fences around the keep, hash, discard/crash, and JSONL commands, so the markdown
satisfies MD031.

Source: Linters/SAST tools

🧹 Nitpick comments (5)
docs/plans/2026-08-02-opencode-support-design.md (1)

39-46: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add a language identifier to this fenced block.

Line 39 has an unlabeled fenced block. Use text for the directory tree to satisfy MD040.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/plans/2026-08-02-opencode-support-design.md` around lines 39 - 46, Label
the fenced directory-tree block in the design document with the text language
identifier by changing its opening fence to use text; leave the tree content
unchanged.

Source: Linters/SAST tools

.opencode/skills/research/scripts/evaluate.ts (1)

262-264: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Score the run once, after the harness delegation event is added.

aggregate already calls scoreRun at Line 195. Line 264 calls scoreRun a second time. The second call is required only for the researcher route, because Line 263 appends the harness delegation event after the first scoring. For the direct route the second call repeats identical work, and the intermediate run value carries status and assertions that do not reflect the appended event.

Pass the extra event into aggregate so scoring happens once.

♻️ Proposed refactor
-function aggregate(results: RuntimeResult[], answer: StructuredAnswer, route: RouteName, scenario: Scenario, instructions: Instructions, runtime: RuntimeName, variant: VariantName): EvaluationRun {
+function aggregate(results: RuntimeResult[], answer: StructuredAnswer, route: RouteName, scenario: Scenario, instructions: Instructions, runtime: RuntimeName, variant: VariantName, extraEvents: NormalizedEvent[] = []): EvaluationRun {
   const stdoutLines = results.flatMap((result) => result.stdoutLines);
   let events;
   try {
     events = normalizeEvents(runtime, stdoutLines);
   } catch (error) {
     const detail = error instanceof Error ? error.message : String(error);
     results.push({ exitCode: 1, stdoutLines: [], stderr: detail, finalJson: undefined, elapsedMs: 0, availability: "available" });
     events = [];
   }
+  events = [...events, ...extraEvents];
-  const run = aggregate([routeResult, execution], answer, route.route, scenario, instructions, runtime, variant);
-  if (route.route === "researcher") run.events.push({ action: "delegate", tool: "harness", rawType: "harness" });
-  return scoreRun(run);
+  const harnessEvents: NormalizedEvent[] = route.route === "researcher"
+    ? [{ action: "delegate", tool: "harness", rawType: "harness" }]
+    : [];
+  return aggregate([routeResult, execution], answer, route.route, scenario, instructions, runtime, variant, harnessEvents);

This requires importing type NormalizedEvent from ./evaluator.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.opencode/skills/research/scripts/evaluate.ts around lines 262 - 264, Update
aggregate and its caller so the optional harness delegation event is supplied to
aggregate before scoring, using the NormalizedEvent type from ./evaluator.
Remove the post-aggregate run.events mutation and the separate scoreRun call,
ensuring both researcher and direct routes are scored exactly once with the
complete event set.
.opencode/skills/research/scripts/runtime-adapters.ts (1)

164-177: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Skip unparseable stdout lines instead of failing the whole run.

Line 167 calls JSON.parse(line) with no guard. One non-JSON line on stdout, for example a CLI warning, throws. The caller at Line 238 converts that throw into exitCode: 1, so the run is discarded even though the result event may be present. runtimeErrorMessage at Lines 104-119 already skips unparseable lines. Use the same handling.

♻️ Proposed fix
   for (const line of stdoutLines) {
-    const value = JSON.parse(line) as unknown;
+    let value: unknown;
+    try {
+      value = JSON.parse(line) as unknown;
+    } catch {
+      continue;
+    }
     if (typeof value === "object" && value !== null && !Array.isArray(value)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.opencode/skills/research/scripts/runtime-adapters.ts around lines 164 -
177, Update parseClaudeResult to catch JSON.parse failures for individual stdout
lines and continue scanning subsequent lines, matching the tolerant handling
already used by runtimeErrorMessage. Preserve result-event detection and
downstream structured_output/result parsing unchanged, while still throwing when
no valid result event is found.
.opencode/skills/research/scripts/evaluator.ts (1)

326-329: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Make .key literal before narrowing JsonObject.

value is JsonObject (Record<string, unknown>), and for (const key of ["url", "ref_id", "href"]) infers key: string because the array is not as const. value[key] remains unknown, so the string guard and return are not visible to TypeScript control-flow analysis. Use ["url", "ref_id", "href"] as const or bind value[key] to a local variable before the typeof candidate === "string" check.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.opencode/skills/research/scripts/evaluator.ts around lines 326 - 329,
Update the key iteration in the value extraction logic to preserve literal key
types by making the ["url", "ref_id", "href"] array const, or bind value[key] to
a local candidate before narrowing it. Ensure the typeof check and returned
value are recognized as string by TypeScript while preserving the existing URL
validation behavior.
docs/plans/2026-08-02-opencode-support.md (1)

388-400: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Compare skill contents, not only file names.

This test claims to verify a verbatim copy but compares only relative paths. Generated files can change content and the test will still pass. Add a recursive content comparison such as diff -ru.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/plans/2026-08-02-opencode-support.md` around lines 388 - 400, Update the
“opencode bundle copies skill subdirectories verbatim” test to compare the
source and bundled skill directory contents recursively, using a content-aware
comparison such as diff -ru, while retaining coverage for the complete directory
tree.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/workflows/sync-marketplace.yml:
- Around line 32-46: Update the sync workflow’s change-detection and staging
commands to include the `plugins/*/.claude-plugin/plugin.json` pathspec
alongside the existing manifest paths. Add it to both the `git diff --quiet`
check and the corresponding `git add` command, preserving the requirement that
all plugin manifests are synchronized and committed.

In @.opencode/command/autoresearch.md:
- Around line 13-25: In .opencode/command/autoresearch.md lines 13-25, terminate
the “arguments = off” branch immediately after creating the sentinel and
reporting the paused state so resume or fresh-start logic cannot delete it. In
.opencode/skills/autoresearch/SKILL.md lines 223-234, check for
.autoresearch/off before every iteration and stop when present, while enforcing
configured run-count, time, and cost limits.

In @.opencode/skills/autoresearch/SKILL.md:
- Around line 112-118: The benchmark wrapper around .autoresearch/run.sh must
preserve the benchmark’s exit status and avoid shared output files. Replace the
fixed /tmp/autoresearch-output.txt path with a unique per-run temporary file,
and capture the script’s status using PIPESTATUS[0] (or enable pipefail) before
subsequent commands overwrite pipeline status.
- Around line 142-161: Update the Git operations section in SKILL.md to restrict
staging, reverting, and cleaning to explicitly recorded experiment-owned paths
instead of using repository-wide commands. Preserve the keep flow’s commit and
hash retrieval, and ensure discard or crash cleanup cannot modify unrelated user
files; alternatively require a dedicated worktree for the experiment.
- Around line 165-169: Update the result-appending command in the “Append result
to JSONL” section to construct each record with a JSON serializer such as jq
-cn, passing run, commit, metric, metrics, status, description, timestamp, and
segment as typed values. Remove the hand-built echo template so quotes,
backslashes, and newlines are escaped correctly while preserving the existing
JSONL fields.

In @.opencode/skills/create-pr/scripts/wait-for-merge.sh:
- Around line 39-43: Update the polling logic around the state variable and gh
pr merge command to handle CLOSED as a distinct terminal outcome, preventing
merge attempts and AWAITING_REVIEW reporting for closed PRs. Preserve MERGED
handling, but stop suppressing merge failure details and return a distinct
non-review error status with the underlying failure information for merge,
conflict, or API errors.
- Around line 21-37: Update the polling loop in wait-for-merge.sh to capture the
exit status of gh pr checks instead of converting command failures to an empty
JSON array, and handle authentication, network, or CLI errors explicitly. Treat
a valid empty check response as complete rather than polling forever, while
preserving failure/cancellation detection for returned checks. Add a bounded
wait or cancellation path so the loop cannot run indefinitely.

In @.opencode/skills/research/scripts/evaluate.ts:
- Around line 301-309: In .opencode/skills/research/scripts/evaluate.ts, add a
validateRun helper that verifies runtime, variant, status, scenario.id, and
instructionHashes.skill/researcher have the expected types, calling usage("run
artifact is malformed") for invalid data. Apply it to parsed files before
pushing into existingRuns at lines 301-309 and to each sourceRuns entry before
the identity checks at lines 405-412, so both paths operate only on validated
EvaluationRun objects.
- Around line 458-462: Update compare to group or match baseline and candidate
summaries by their summary.runtime value before invoking compareSummaries, so
each comparison only pairs summaries from the same runtime. Preserve the
existing comparison aggregation and rejection behavior for genuine cross-runtime
absence or regressions.
- Around line 21-25: Update the root and resource path setup in the evaluate
script so it resolves the repository root reliably from a repository marker
rather than climbing beyond the checkout. Point skill, researcher, scenario, and
schema paths at the generated .opencode layout, ensuring gitCommit() and bun
check:opencode operate within the repository.

In @.opencode/skills/research/scripts/evaluator.ts:
- Around line 371-377: Update normalizeEvents so token events are emitted only
from the intended top-level token record, not every nested mapping visited
through visit. Prevent traversal of Codex token_count fields such as
total_token_usage and last_token_usage from producing additional events, while
preserving one deterministic event with the relevant input and output token
counts for aggregate.

In @.opencode/skills/research/scripts/runtime-adapters.ts:
- Around line 129-144: Update the Bun.spawn call in the runtime adapter to
configure a finite timeout and killSignal so hung CLI processes are terminated
and child.exited cannot block indefinitely. Also await child.stdin.end() in the
input path and handle a rejection from early child termination to prevent an
unhandled promise rejection.

In @.opencode/skills/verify/SKILL.md:
- Around line 4-11: Align the allowed-tools list in the verify skill with its
non-mutating contract by removing the write-capable Write and Edit entries.
Preserve the remaining read, search, execution, and delegation tools, and leave
the contract unchanged.

In `@docs/plans/2026-08-02-opencode-support.md`:
- Line 34: Update the Git command blocking logic represented by the BLOCKS
patterns and trailer check to detect hook bypasses using `-c
core.hooksPath=/dev/null`, including when Git options appear before `commit`.
Preserve blocking for the existing `git config ... core.hooksPath` form and
ensure both argument orders are covered.
- Around line 153-154: Update the bundle replacement sequence around tmp_root
and its cleanup trap so the existing .opencode bundle remains intact until the
temporary tree has been installed successfully. Avoid removing the current
bundle before the move; use an atomic or rollback-safe replacement flow, and
ensure cleanup does not delete the preserved bundle if installation fails.

In `@scripts/generate-opencode-artifacts.sh`:
- Around line 93-97: The bundle replacement logic around generated_any must not
leave an existing OPENCODE_ROOT when no eligible plugin directories are
generated. Unconditionally replace the bundle after successful generation, or
explicitly fail when generated_any is zero; ensure the script never exits
successfully while preserving stale artifacts.

In `@scripts/opencode-plugin/bstack.ts`:
- Around line 18-38: The Git hook guard must block all supported bypass forms,
including short -n, git -c core.hooksPath=... commit invocations, and Git
commands within compound shell commands. Update isGitCommand and the related
checks in scripts/opencode-plugin/bstack.ts to conservatively inspect compound
commands and detect these forms, then regenerate .opencode/plugins/bstack.ts
from the corrected source with the same changes.

---

Outside diff comments:
In @.opencode/skills/ship/SKILL.md:
- Around line 1-104: Regenerate the stale ship skill artifact by updating the
source content in plugins/me/skills/ship/SKILL.md rather than editing
.opencode/skills/ship/SKILL.md directly. Run bun run sync:opencode, verify the
generated bundle with bun run check:opencode, and commit the resulting generated
changes.

---

Minor comments:
In @.opencode/command/autoresearch.md:
- Around line 15-18: Add a blank line immediately before and after the fenced
bash block in the autoresearch instructions, preserving the existing command and
surrounding text.

In @.opencode/skills/autoresearch/SKILL.md:
- Around line 144-150: Update the “If keep” commit command in the autoresearch
instructions to require a Conventional Commit subject in the form type(scope):
description instead of raw <description>. Preserve the existing result JSON in
the commit body and apply this format specifically to experiment keep commits.
- Around line 142-168: Add blank lines immediately before and after each shell
fenced code block in the “Git operations” and “Append result to JSONL” sections
of SKILL.md, including the fences around the keep, hash, discard/crash, and
JSONL commands, so the markdown satisfies MD031.

In @.opencode/skills/competitive-agents/SKILL.md:
- Around line 87-97: Update the “Analyze and present in this format” template in
SKILL.md by adding a blank line after each heading: “### Analysis,” “###
Synthesized Solution,” and “### Rationale.” Preserve the existing content and
formatting otherwise.

In @.opencode/skills/research/scripts/evaluator.ts:
- Around line 612-623: Update the qualityRegression assignment in
compareSummaries so candidate-only runs (when baselineRun is absent) do not
count as regressions; only compare severity when both baselineRun and
candidateRun exist. Preserve the separate baseline-only handling for lost
coverage, and add a clarifying comment only if candidate-only scenarios are
intentionally meant to reject.

In `@CLAUDE.md`:
- Around line 58-59: Update CLAUDE.md lines 58-59 to require bun run
sync:opencode after changes to marketplace metadata, skills, agents, commands,
or the plugin template, while retaining bun run check:opencode for verification.
Update README.md lines 38-43 to document the marketplace metadata, source
directories, and files that feed OpenCode generation.

In `@docs/plans/2026-08-02-opencode-support.md`:
- Line 7: Update the architecture summary to replace every reference to the
generated plugin filename “plugin.ts” with “bstack.ts,” matching the
implementation under scripts/opencode-plugin/ and .opencode/plugins/.
- Line 625: Update the skill-name collision guidance in the OpenCode support
plan to state that the generator exits with “Skill name collision” rather than
resolving collisions through OpenCode configuration permissions. Instruct users
to rename the source skill or change the generator’s collision policy.
- Around line 608-629: The OpenCode compatibility section’s outer Markdown fence
conflicts with its nested bash code fence. Update the outer fence around the
section beginning with “## OpenCode 호환성” to use four backticks, preserving the
inner ```bash block and all surrounding content.

In `@scripts/install-opencode.sh`:
- Around line 20-25: Update the backup handling before the ln command so an
existing regular ${link_path}.bak is never overwritten: either fail before mv or
select a unique unused backup path when the default exists, then move
${link_path} to that path and preserve the symlink creation flow.

---

Nitpick comments:
In @.opencode/skills/research/scripts/evaluate.ts:
- Around line 262-264: Update aggregate and its caller so the optional harness
delegation event is supplied to aggregate before scoring, using the
NormalizedEvent type from ./evaluator. Remove the post-aggregate run.events
mutation and the separate scoreRun call, ensuring both researcher and direct
routes are scored exactly once with the complete event set.

In @.opencode/skills/research/scripts/evaluator.ts:
- Around line 326-329: Update the key iteration in the value extraction logic to
preserve literal key types by making the ["url", "ref_id", "href"] array const,
or bind value[key] to a local candidate before narrowing it. Ensure the typeof
check and returned value are recognized as string by TypeScript while preserving
the existing URL validation behavior.

In @.opencode/skills/research/scripts/runtime-adapters.ts:
- Around line 164-177: Update parseClaudeResult to catch JSON.parse failures for
individual stdout lines and continue scanning subsequent lines, matching the
tolerant handling already used by runtimeErrorMessage. Preserve result-event
detection and downstream structured_output/result parsing unchanged, while still
throwing when no valid result event is found.

In `@docs/plans/2026-08-02-opencode-support-design.md`:
- Around line 39-46: Label the fenced directory-tree block in the design
document with the text language identifier by changing its opening fence to use
text; leave the tree content unchanged.

In `@docs/plans/2026-08-02-opencode-support.md`:
- Around line 388-400: Update the “opencode bundle copies skill subdirectories
verbatim” test to compare the source and bundled skill directory contents
recursively, using a content-aware comparison such as diff -ru, while retaining
coverage for the complete directory tree.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 8fb57dbe-eaba-45b3-b0cd-8b1bc63c00ee

📥 Commits

Reviewing files that changed from the base of the PR and between fed5467 and a866550.

📒 Files selected for processing (46)
  • .github/workflows/ci.yml
  • .github/workflows/release.yml
  • .github/workflows/sync-marketplace.yml
  • .opencode/agents/code-reviewer.md
  • .opencode/agents/researcher.md
  • .opencode/agents/security-auditor.md
  • .opencode/agents/test-engineer.md
  • .opencode/command/autoresearch.md
  • .opencode/plugins/bstack.ts
  • .opencode/skills/autoresearch/SKILL.md
  • .opencode/skills/competitive-agents/SKILL.md
  • .opencode/skills/create-pr/SKILL.md
  • .opencode/skills/create-pr/scripts/preflight-check.sh
  • .opencode/skills/create-pr/scripts/wait-for-merge.sh
  • .opencode/skills/datadog/SKILL.md
  • .opencode/skills/e2e-scenario-testing/SKILL.md
  • .opencode/skills/handoff/SKILL.md
  • .opencode/skills/research/SKILL.md
  • .opencode/skills/research/evals/result.schema.json
  • .opencode/skills/research/evals/scenarios.json
  • .opencode/skills/research/scripts/evaluate.ts
  • .opencode/skills/research/scripts/evaluator.ts
  • .opencode/skills/research/scripts/runtime-adapters.ts
  • .opencode/skills/ship/SKILL.md
  • .opencode/skills/story-loop/SKILL.md
  • .opencode/skills/verify/SKILL.md
  • .opencode/skills/verify/references/exploration-guide.md
  • .opencode/skills/verify/references/issue-taxonomy.md
  • .opencode/skills/verify/templates/report-template.md
  • .opencode/skills/write-skill/SKILL.md
  • .opencode/skills/write-skill/agents/openai.yaml
  • .opencode/skills/writing-prds/SKILL.md
  • .opencode/skills/writing-rfcs/SKILL.md
  • .releaserc.js
  • CLAUDE.md
  • README.md
  • docs/plans/2026-08-02-opencode-support-design.md
  • docs/plans/2026-08-02-opencode-support.md
  • package.json
  • scripts/check-opencode-artifacts.sh
  • scripts/generate-opencode-artifacts.sh
  • scripts/install-opencode.sh
  • scripts/opencode-plugin/bstack.ts
  • scripts/sync-opencode-artifacts.sh
  • tests/github_workflows.bats
  • tests/opencode_artifacts.bats

Comment on lines 32 to +46
- name: Check for changes
id: check_changes
run: |
untracked_artifacts="$(
git ls-files --others --exclude-standard -- \
.agents/plugins/marketplace.json \
'plugins/*/.codex-plugin/plugin.json'
'plugins/*/.codex-plugin/plugin.json' \
.opencode
)"

if git diff --quiet -- \
.claude-plugin/marketplace.json \
.agents/plugins/marketplace.json \
'plugins/*/.codex-plugin/plugin.json' && \
'plugins/*/.codex-plugin/plugin.json' \
.opencode && \

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Stage the source plugin manifests.

The workflow does not check or stage plugins/*/.claude-plugin/plugin.json. Version synchronization can leave those source manifests unstaged while committing the marketplace version. Add this path to both the git diff --quiet and git add pathspecs.

As per coding guidelines, “Version synchronization must update marketplace.json and every plugins/*/.claude-plugin/plugin.json; all plugins share one version.”

Proposed fix
           if git diff --quiet -- \
             .claude-plugin/marketplace.json \
             .agents/plugins/marketplace.json \
+            'plugins/*/.claude-plugin/plugin.json' \
             'plugins/*/.codex-plugin/plugin.json' \
             .opencode && \
@@
           git add \
             .claude-plugin/marketplace.json \
             .agents/plugins/marketplace.json \
+            'plugins/*/.claude-plugin/plugin.json' \
             'plugins/*/.codex-plugin/plugin.json' \
             .opencode

Also applies to: 58-62

🧰 Tools
🪛 actionlint (1.7.12)

[error] 34-34: shellcheck reported issue in this script: SC2086:info:14:27: Double quote to prevent globbing and word splitting

(shellcheck)


[error] 34-34: shellcheck reported issue in this script: SC2086:info:16:26: Double quote to prevent globbing and word splitting

(shellcheck)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/sync-marketplace.yml around lines 32 - 46, Update the sync
workflow’s change-detection and staging commands to include the
`plugins/*/.claude-plugin/plugin.json` pathspec alongside the existing manifest
paths. Add it to both the `git diff --quiet` check and the corresponding `git
add` command, preserving the requirement that all plugin manifests are
synchronized and committed.

Source: Coding guidelines

Comment on lines +13 to +25
### If arguments = "off"

Create a `.autoresearch/off` sentinel file in the current directory:
```bash
mkdir -p .autoresearch && touch .autoresearch/off
```
Then tell the user autoresearch mode is paused. It can be resumed by running `/autoresearch` again (which will delete the sentinel).

### If `.autoresearch/autoresearch.md` exists in the current directory (resume)

This is a resume. Do the following:

1. Delete `.autoresearch/off` if it exists

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Make the autoresearch pause protocol reliable.

The command creates .autoresearch/off but can continue into logic that deletes it. The running skill also does not check the sentinel and has no resource limit.

  • .opencode/command/autoresearch.md#L13-L25: terminate the off branch before the resume or fresh-start branch.
  • .opencode/skills/autoresearch/SKILL.md#L223-L234: check .autoresearch/off before each iteration and enforce run, time, and cost limits.
🧰 Tools
🪛 markdownlint-cli2 (0.23.1)

[warning] 16-16: Fenced code blocks should be surrounded by blank lines

(MD031, blanks-around-fences)


[warning] 18-18: Fenced code blocks should be surrounded by blank lines

(MD031, blanks-around-fences)

📍 Affects 2 files
  • .opencode/command/autoresearch.md#L13-L25 (this comment)
  • .opencode/skills/autoresearch/SKILL.md#L223-L234
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.opencode/command/autoresearch.md around lines 13 - 25, In
.opencode/command/autoresearch.md lines 13-25, terminate the “arguments = off”
branch immediately after creating the sentinel and reporting the paused state so
resume or fresh-start logic cannot delete it. In
.opencode/skills/autoresearch/SKILL.md lines 223-234, check for
.autoresearch/off before every iteration and stop when present, while enforcing
configured run-count, time, and cost limits.

Source: Linters/SAST tools

Comment on lines +112 to +118
```bash
START_TIME=$(date +%s%N)
bash -c "./.autoresearch/run.sh" 2>&1 | tee /tmp/autoresearch-output.txt
EXIT_CODE=$?
END_TIME=$(date +%s%N)
DURATION=$(echo "scale=3; ($END_TIME - $START_TIME) / 1000000000" | bc)
echo "Duration: ${DURATION}s, Exit code: ${EXIT_CODE}"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Preserve the benchmark status and isolate its output.

The pipeline returns tee's status unless the caller enables pipefail. A failing .autoresearch/run.sh can therefore be recorded as successful. The fixed /tmp/autoresearch-output.txt path can also mix output from concurrent loops. Use a per-run temporary file and capture the benchmark process status with ${PIPESTATUS[0]} or pipefail.

🧰 Tools
🪛 SkillSpector (2.4.4)

[warning] 225: [EA4] Unbounded Resource Access: Skill allows unbounded resource consumption (API calls, storage, compute). Without rate limits or quotas, a compromised or misbehaving agent can cause denial-of-service or cost overruns.

Remediation: Set explicit rate limits, timeouts, and resource quotas for API calls, file operations, and compute. Implement circuit breakers for runaway loops.

(Excessive Agency (EA4))

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.opencode/skills/autoresearch/SKILL.md around lines 112 - 118, The benchmark
wrapper around .autoresearch/run.sh must preserve the benchmark’s exit status
and avoid shared output files. Replace the fixed /tmp/autoresearch-output.txt
path with a unique per-run temporary file, and capture the script’s status using
PIPESTATUS[0] (or enable pipefail) before subsequent commands overwrite pipeline
status.

Comment on lines +142 to +161
### 2. Git operations

**If keep:**
```bash
git add -A
git diff --cached --quiet && echo "nothing to commit" || git commit -m "<description>

Result: {\"status\":\"keep\",\"<metricName>\":<value>,<secondary metrics>}"
```

Then get the new commit hash:
```bash
git rev-parse --short=7 HEAD
```

**If discard or crash:**
```bash
git checkout -- .
git clean -fd
```

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🔴 Critical | 🏗️ Heavy lift

Restrict Git operations to experiment-owned paths.

git add -A stages unrelated user changes. git checkout -- . and git clean -fd can revert or delete unrelated files. Use a dedicated worktree, or record the experiment-owned paths and restore only those paths. The current instructions can cause irreversible data loss and unrelated commits.

🧰 Tools
🪛 markdownlint-cli2 (0.23.1)

[warning] 145-145: Fenced code blocks should be surrounded by blank lines

(MD031, blanks-around-fences)


[warning] 153-153: Fenced code blocks should be surrounded by blank lines

(MD031, blanks-around-fences)


[warning] 158-158: Fenced code blocks should be surrounded by blank lines

(MD031, blanks-around-fences)

🪛 SkillSpector (2.4.4)

[warning] 225: [EA4] Unbounded Resource Access: Skill allows unbounded resource consumption (API calls, storage, compute). Without rate limits or quotas, a compromised or misbehaving agent can cause denial-of-service or cost overruns.

Remediation: Set explicit rate limits, timeouts, and resource quotas for API calls, file operations, and compute. Implement circuit breakers for runaway loops.

(Excessive Agency (EA4))

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.opencode/skills/autoresearch/SKILL.md around lines 142 - 161, Update the
Git operations section in SKILL.md to restrict staging, reverting, and cleaning
to explicitly recorded experiment-owned paths instead of using repository-wide
commands. Preserve the keep flow’s commit and hash retrieval, and ensure discard
or crash cleanup cannot modify unrelated user files; alternatively require a
dedicated worktree for the experiment.

Comment on lines +165 to +169
### 3. Append result to JSONL

```bash
echo '{"run":<N>,"commit":"<hash>","metric":<value>,"metrics":{<secondaries>},"status":"<status>","description":"<desc>","timestamp":'$(date +%s)',"segment":<seg>}' >> .autoresearch/autoresearch.jsonl
```

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Serialize result records with a JSON serializer.

The echo template inserts descriptions, metric names, and values without JSON escaping. A quote, backslash, or newline can corrupt .autoresearch/autoresearch.jsonl and break resume parsing. Use jq -cn or another JSON serializer.

🧰 Tools
🪛 SkillSpector (2.4.4)

[warning] 225: [EA4] Unbounded Resource Access: Skill allows unbounded resource consumption (API calls, storage, compute). Without rate limits or quotas, a compromised or misbehaving agent can cause denial-of-service or cost overruns.

Remediation: Set explicit rate limits, timeouts, and resource quotas for API calls, file operations, and compute. Implement circuit breakers for runaway loops.

(Excessive Agency (EA4))

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.opencode/skills/autoresearch/SKILL.md around lines 165 - 169, Update the
result-appending command in the “Append result to JSONL” section to construct
each record with a JSON serializer such as jq -cn, passing run, commit, metric,
metrics, status, description, timestamp, and segment as typed values. Remove the
hand-built echo template so quotes, backslashes, and newlines are escaped
correctly while preserving the existing JSONL fields.

Comment on lines +4 to +11
allowed-tools:
- Bash
- Read
- Write
- Edit
- Glob
- Grep
- Agent

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf 'Tracked files under .opencode:\n'
git ls-files '.opencode/**' | sed -n '1,200p'

printf '\nTarget file with line numbers:\n'
if [ -f .opencode/skills/verify/SKILL.md ]; then
  nl -ba .opencode/skills/verify/SKILL.md | sed -n '1,220p'
else
  echo 'target file missing'
fi

printf '\nSearch for /verify, verify skill references, and allowed-tools:\n'
rg -n "(/verify|verify|allowed-tools|Write|Edit)" .opencode || true

printf '\nPackage/check scripts:\n'
if [ -f package.json ]; then
  jq -r '.scripts // {} | to_entries[] | "\(.key): \(.value)"' package.json | rg -n 'opencode|check' || true
fi

Repository: baleen37/bstack

Length of output: 1503


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf 'Package/check scripts:\n'
if [ -f package.json ]; then
  node -e "const p=require('./package.json'); const s=p.scripts||{}; for (const k of Object.keys(s).sort()) if (/opencode|check/i.test(k)) console.log(k+': '+s[k]);" || true
fi

printf '\nRelevant verify skill lines (grep and read-only parse):\n'
python3 - <<'PY'
from pathlib import Path
p=Path('.opencode/skills/verify/SKILL.md')
print(f'exists={p.exists}')
lines=p.read_text().splitlines()
for i,l in enumerate(lines,1):
    if i<=220:
        print(f'{i:4d}: {l}')
PY

printf '\nSearch verify contract/permissions across .opencode:\n'
python3 - <<'PY'
from pathlib import Path
terms=['/verify','does not fix','allowed-tools','Write','Edit','Write']
for p in sorted(Path('.opencode').rglob('*')):
    if p.is_file() and p.suffix.lower()=='.md':
        text=p.read_text(errors='ignore').splitlines()
        for i,l in enumerate(text,1):
            if any(t in l for t in terms) or p == Path('.opencode/skills/verify/SKILL.md'):
                if i<=250 or p != Path('.opencode/skills/verify/SKILL.md'):
                    print(f'{p}:{i}: {l}')
PY

Repository: baleen37/bstack

Length of output: 18477


🌐 Web query:

OpenCode skills allowed-tools Write Edit tool permission documentation

💡 Result:

In OpenCode, the "write" and "edit" tools are managed under the unified "edit" permission [1][2][3]. The "edit" permission covers all file modification actions, including "edit", "write", and "apply_patch" [1][4][5]. Key details regarding OpenCode permissions: Configuration: Permissions are defined in the "permission" field of your configuration [1][5]. As of version 1.1.1, the legacy "tools" boolean configuration has been deprecated in favor of this granular permission system, though it remains supported for backwards compatibility [1][3][5]. Permission Actions: Each permission can be set to one of three states [5]: - "allow": Executes the action without approval [5][6]. - "ask": Prompts the user for approval [5][6]. - "deny": Blocks the action [5][6]. Granular Control: You can set permissions globally (using a "" wildcard) or for specific tools [1][5]. For tools like "edit" and "bash", you can provide an object syntax to match specific file patterns or commands [1][3][6]. For example, you can allow editing for specific file types while denying it globally [3][6]: { "permission": { "edit": { ".mdx": "allow", "*": "deny" } } } When OpenCode prompts for approval (the "ask" state), you can choose to approve the action "once," "always" (for the duration of the session), or "reject" it [5].

Citations:


Align allowed-tools with the non-mutating /verify contract.

/verify states it does not fix code, but allowed-tools grants Write and Edit. These tools enable file changes in the repository. Remove write-capable tools, or update the contract to allow mutation and name it clearly.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.opencode/skills/verify/SKILL.md around lines 4 - 11, Align the
allowed-tools list in the verify skill with its non-mutating contract by
removing the write-capable Write and Edit entries. Preserve the remaining read,
search, execution, and delegation tools, and leave the contract unchanged.

**Step 1: Write the plugin template**

```ts
const PLUGIN_ROOT = new URL("..", import.meta.url).pathname.replace(/\/$/, "")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Block hook bypasses that use Git options.

git -c core.hooksPath=/dev/null commit passes isGitCommand, but no BLOCKS pattern matches it. The existing rule only matches git config ... core.hooksPath, and the trailer check requires commit immediately after git. Add argument-order coverage or parse the command before checking it.

Also applies to: 36-48, 69-71

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/plans/2026-08-02-opencode-support.md` at line 34, Update the Git command
blocking logic represented by the BLOCKS patterns and trailer check to detect
hook bypasses using `-c core.hooksPath=/dev/null`, including when Git options
appear before `commit`. Preserve blocking for the existing `git config ...
core.hooksPath` form and ensure both argument orders are covered.

Comment on lines +153 to +154
tmp_root="$(mktemp -d "${OPENCODE_ROOT}.tmp.XXXXXX")"
trap 'rm -rf "${tmp_root}"' EXIT

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Preserve the current bundle until replacement succeeds.

The script removes .opencode/ before moving the temporary tree. If mv fails, the old bundle is already gone and the cleanup trap removes the temporary tree. Use a replacement sequence that preserves the current bundle until the new tree is installed successfully.

Also applies to: 197-200

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/plans/2026-08-02-opencode-support.md` around lines 153 - 154, Update the
bundle replacement sequence around tmp_root and its cleanup trap so the existing
.opencode bundle remains intact until the temporary tree has been installed
successfully. Avoid removing the current bundle before the move; use an atomic
or rollback-safe replacement flow, and ensure cleanup does not delete the
preserved bundle if installation fails.

Comment on lines +93 to +97
if [ "${generated_any}" -eq 1 ]; then
rm -rf "${OPENCODE_ROOT}"
mv "${tmp_root}" "${OPENCODE_ROOT}"
trap - EXIT
fi

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Do not preserve stale artifacts when no plugin is generated.

If eligible_plugins produces no existing plugin directory, generated_any stays 0. The script then exits successfully and leaves the previous .opencode/ bundle unchanged. This makes removed marketplace artifacts appear current.

Replace the bundle unconditionally after successful generation, or fail explicitly when zero source plugins are invalid. Do not return success with stale generated output.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/generate-opencode-artifacts.sh` around lines 93 - 97, The bundle
replacement logic around generated_any must not leave an existing OPENCODE_ROOT
when no eligible plugin directories are generated. Unconditionally replace the
bundle after successful generation, or explicitly fail when generated_any is
zero; ensure the script never exits successfully while preserving stale
artifacts.

Comment on lines +18 to +38
const isGitCommand = (command: string): boolean => /^\s*(\S*=\S*\s+)*git\s+/.test(command)

export const Bstack = async () => {
return {
"tool.execute.before": async (
input: { tool: string },
output: { args?: { command?: string } },
) => {
if (input.tool !== "bash") return
const command = output.args?.command ?? ""
if (!isGitCommand(command)) return

for (const block of BLOCKS) {
if (block.pattern.test(command)) {
throw new Error(block.info ? `${block.message}\n${block.info}` : block.message)
}
}

if (/git\s+commit/.test(command) && /Co-Authored-By:/.test(command)) {
throw new Error("Co-Authored-By trailers are not allowed in commit messages\nPlease remove 'Co-Authored-By:' from your commit message.")
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Block all supported Git hook-bypass forms.

The guard allows git commit -n, which is Git’s short form of --no-verify. It also ignores compound commands such as cd repo && git commit --no-verify because isGitCommand() requires git at the command start. A command such as git -c core.hooksPath=/dev/null commit also bypasses the current core.hooksPath check.

  • scripts/opencode-plugin/bstack.ts#L18-L38: parse or conservatively inspect compound shell commands, and block -n plus git -c core.hooksPath=... bypasses.
  • .opencode/plugins/bstack.ts#L18-L38: regenerate this artifact from the corrected source plugin.

Based on learnings: "Never bypass pre-commit hooks with --no-verify."

📍 Affects 2 files
  • scripts/opencode-plugin/bstack.ts#L18-L38 (this comment)
  • .opencode/plugins/bstack.ts#L18-L38
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/opencode-plugin/bstack.ts` around lines 18 - 38, The Git hook guard
must block all supported bypass forms, including short -n, git -c
core.hooksPath=... commit invocations, and Git commands within compound shell
commands. Update isGitCommand and the related checks in
scripts/opencode-plugin/bstack.ts to conservatively inspect compound commands
and detect these forms, then regenerate .opencode/plugins/bstack.ts from the
corrected source with the same changes.

Source: Learnings

@baleen37 baleen37 closed this Aug 5, 2026
auto-merge was automatically disabled August 5, 2026 00:24

Pull request was closed

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant