feat(platform): add opencode support#7
Conversation
|
Caution Review failedPull request was closed or merged during review 📝 WalkthroughWalkthroughAdds OpenCode as a third Ark platform: platform registry, init CLI flags, layout/templates, embedded OpenCode templates and TS plugin, tests for deduplicated managed blocks and round-trip load/unload, command specs/docs for OpenCode commands, and numerous planning/review/verification docs. Changes
Sequence Diagram(s)sequenceDiagram
participant Client as Client (LLM runtime)
participant Plugin as OpenCode Plugin
participant Ark as `ark` CLI
Client->>Plugin: send first chat.message (sessionID)
Plugin->>Ark: exec `ark context --scope session --format json` (5s)
Ark-->>Plugin: JSON (hookSpecificOutput.additionalContext)
Plugin->>Plugin: store pendingContext[sessionID]
Note over Client,Plugin: subsequent turn arrives
Client->>Plugin: experimental.chat.messages.transform (messages)
Plugin->>Plugin: find latest user text part
Plugin->>Plugin: prepend <ark-context>... from pendingContext
Plugin-->>Client: transformed messages
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
|
Claude finished @Anekoique's task in 3m 53s —— View job PR Review: feat(platform): add opencode support
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 9df44e39d0
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| if (!sessionID) return | ||
| const additionalContext = pendingContext.get(sessionID) | ||
| if (!additionalContext) return | ||
| pendingContext.delete(sessionID) |
There was a problem hiding this comment.
Keep pending context until injection actually succeeds
Deleting pendingContext before confirming a text part causes permanent context loss for sessions whose first user turn is non-text (for example, image-only). In that case the plugin marks the session as processed in chat.message, then this branch clears the pending payload without injecting it, so later text turns never receive Ark context for the rest of the session.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Pull request overview
This PR adds a third “OpenCode” integration target to Ark alongside Claude and Codex, including embedded templates, CLI init flags, and a Bun-loaded OpenCode plugin to inject ark context into the first user message of each session.
Changes:
- Add OpenCode platform registration in
ark-core(templates, layout paths, platform registry entry) and expose it viaark-core’s public exports. - Extend
arkCLIinitto support--opencode/--no-opencode, and update integration tests for 3-platform init/unload/load/remove behavior. - Add OpenCode command + plugin templates (and checked-in installed artifacts), plus workflow/spec docs and the archived task artifacts for
opencode-support.
Reviewed changes
Copilot reviewed 33 out of 33 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
templates/opencode/plugins/ark-context.ts |
Adds the OpenCode session context-injection plugin template. |
templates/opencode/commands/ark/quick.md |
Adds OpenCode /ark:quick command template. |
templates/opencode/commands/ark/design.md |
Adds OpenCode /ark:design command template. |
templates/opencode/commands/ark/archive.md |
Adds OpenCode /ark:archive command template. |
templates/ark/workflow.md |
Updates workflow tier table to include OpenCode commands. |
crates/ark-core/src/templates.rs |
Embeds OpenCode templates + plugin source; adds parity/invariant tests. |
crates/ark-core/src/platforms.rs |
Registers OPENCODE_PLATFORM, adds it to PLATFORMS, and adds platform tests. |
crates/ark-core/src/lib.rs |
Re-exports OPENCODE_PLATFORM. |
crates/ark-core/src/layout.rs |
Adds .opencode paths and includes .opencode/ in owned_dirs(). |
crates/ark-core/src/commands/unload.rs |
Dedupes managed-block capture targets when manifest is missing. |
crates/ark-core/src/commands/tests_common.rs |
Extends source-scan path prefix list to include .opencode/. |
crates/ark-core/src/commands/remove.rs |
Updates tests to expect managed-block dedupe behavior with shared targets. |
crates/ark-core/src/commands/load.rs |
Adds 3-platform init→unload→load round-trip test including plugin file. |
crates/ark-core/src/commands/init.rs |
Updates init tests to expect .opencode commands + plugin file. |
crates/ark-cli/src/main.rs |
Adds --opencode/--no-opencode flags and updates platform-resolution tests. |
.opencode/plugins/ark-context.ts |
Checks in the generated OpenCode plugin file for this repo’s installed state. |
.opencode/commands/ark/quick.md |
Checks in the generated OpenCode quick command for installed state. |
.opencode/commands/ark/design.md |
Checks in the generated OpenCode design command for installed state. |
.opencode/commands/ark/archive.md |
Checks in the generated OpenCode archive command for installed state. |
.codex/skills/ark-quick/SKILL.md |
Updates quick-skill guidance text for escalation to design tier. |
.ark/workflow.md |
Updates installed workflow doc tier table to include OpenCode commands. |
.ark/tasks/archive/2026-04/opencode-support/task.toml |
Adds archived task metadata for opencode-support. |
.ark/tasks/archive/2026-04/opencode-support/VERIFY.md |
Adds archived verification record for opencode-support. |
.ark/tasks/archive/2026-04/opencode-support/PRD.md |
Adds archived PRD for opencode-support. |
.ark/tasks/archive/2026-04/opencode-support/02_REVIEW.md |
Adds archived review record (iteration 02). |
.ark/tasks/archive/2026-04/opencode-support/02_PLAN.md |
Adds archived plan (iteration 02). |
.ark/tasks/archive/2026-04/opencode-support/01_REVIEW.md |
Adds archived review record (iteration 01). |
.ark/tasks/archive/2026-04/opencode-support/00_REVIEW.md |
Adds archived review record (iteration 00). |
.ark/tasks/archive/2026-04/opencode-support/00_PLAN.md |
Adds archived plan (iteration 00). |
.ark/specs/features/opencode-support/SPEC.md |
Adds the promoted feature spec documenting OpenCode support. |
.ark/specs/features/INDEX.md |
Registers opencode-support in the managed features index. |
.ark/.installed.json |
Updates installed-file list and hashes to include OpenCode commands and updated docs. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| - `.ark/` is initialized. | ||
| - The change is reversible in one commit and introduces no new abstractions. | ||
| If not, stop and suggest `ark-design` (standard) or `ark-design --deep` instead. | ||
| If not, stop and suggest `ark-design` instead — it covers both standard and deep tiers (deep is selected via `--tier deep` when running `ark agent task new`). |
There was a problem hiding this comment.
This quick-skill precondition text says deep tier is selected via --tier deep when running ark agent task new, but the ark-design skill’s user-facing switch for deep tier is --deep in the skill arguments. Consider rewording to avoid implying users should manually pass --tier deep, and instead suggest ark-design (add --deep for deep tier).
| If not, stop and suggest `ark-design` instead — it covers both standard and deep tiers (deep is selected via `--tier deep` when running `ark agent task new`). | |
| If not, stop and suggest `ark-design` instead — it covers both standard and deep tiers (add `--deep` when the work needs deep tier). |
| "files": [ | ||
| ".claude/commands/ark/archive.md", | ||
| ".claude/commands/ark/design.md", | ||
| ".claude/commands/ark/quick.md", | ||
| ".codex/skills/ark-quick/SKILL.md", | ||
| ".codex/skills/ark-design/SKILL.md", | ||
| ".codex/skills/ark-archive/SKILL.md", |
There was a problem hiding this comment.
PR description says the new OpenCode command entries replace the existing .claude/commands/ark/* entries in .ark/.installed.json, but this manifest still lists the Claude command files as installed (and adds OpenCode entries in addition). Please update the PR description (or the manifest, if the intent really was replacement) so they agree.
| 7. `ark remove` on a Codex+OpenCode project removes both `.codex/` and `.opencode/` trees, removes the AGENTS.md `ARK` managed block once, and surgically removes the Codex `SessionStart` hook entry from `.codex/hooks.json` while preserving sibling user entries. | ||
| 8. `ark upgrade` on an existing Claude+Codex project (no OpenCode) leaves the OpenCode flag-set untouched: no `.opencode/` directory is created. Adding OpenCode is an explicit `ark init --opencode` (idempotent on already-installed Claude/Codex artifacts). | ||
| 9. Two parity tests pin the OpenCode command surface in lockstep with Claude: `every_claude_command_has_an_opencode_command_sibling` (existence) and `opencode_command_bodies_have_opencode_frontmatter` (frontmatter shape — `description:` only, no Claude-specific `argument-hint:` field). | ||
| 10. The shipped `.opencode/plugins/ark-context.ts` is pure TypeScript (no `.js` file in the embedded tree), uses no `experimental.*` hook, and depends only on Node/Bun built-ins (`node:child_process`, `node:fs`) — no `package.json`, no npm dependencies bundled. |
There was a problem hiding this comment.
Archived PRD Outcome #10 claims the shipped OpenCode plugin “uses no experimental.* hook”, but the actual plugin implementation depends on experimental.chat.messages.transform for message mutation. If these archived task artifacts are meant to be accurate records of what shipped, update the PRD outcome wording to match the implementation (or add a short note explaining the change in direction).
| 10. The shipped `.opencode/plugins/ark-context.ts` is pure TypeScript (no `.js` file in the embedded tree), uses no `experimental.*` hook, and depends only on Node/Bun built-ins (`node:child_process`, `node:fs`) — no `package.json`, no npm dependencies bundled. | |
| 10. The shipped `.opencode/plugins/ark-context.ts` is pure TypeScript (no `.js` file in the embedded tree), uses OpenCode's `experimental.chat.messages.transform` hook for first-message mutation, and depends only on Node/Bun built-ins (`node:child_process`, `node:fs`) — no `package.json`, no npm dependencies bundled. |
| - **Pure helpers** (named for clarity, NOT exported): `buildEnvelopePrefix(additionalContext: string): string` returns `<ark-context>\n${additionalContext}\n</ark-context>\n\n---\n\n`. `shouldInject(sessionID: string, processed: Set<string>): boolean` returns `!processed.has(sessionID)`. **Critical**: these are plain `function` declarations, NOT `export function`. OpenCode's plugin runtime invokes every named export at load time with no arguments — exporting a parameterized helper crashes plugin loading (verified empirically: `error=undefined is not an object (evaluating 'processed.has')`). Test harnesses can validate the helpers exist via the string-level `opencode_plugin_keeps_helpers_internal` test in `crates/ark-core/src/templates.rs::tests`, which asserts the helpers are defined, NOT exported, and have live consumers in the default-exported factory. | ||
| - **Failure handling:** Best-effort. On `execFileSync` throw (e.g. `ENOENT` for missing `ark` binary), `JSON.parse` throw, or non-zero exit code: the plugin catches, calls `client.app.log({ body: { service: "ark", level: "warn", message: <reason> } })` and returns without modifying state. On the first swallowed failure per session (per TR-2), the plugin additionally writes a single-line stderr note: `ark-context: skipped context injection (see opencode logs)`. Subsequent failures in the same session are silent in stderr (still logged). | ||
| - **Per-session dedupe:** module-local `Set<string>` and `Map<string, string>`. JS event loop is single-threaded; no concurrency primitive needed. | ||
| - **Size:** ≤80 lines including license/header comment. |
There was a problem hiding this comment.
The OpenCode plugin file is currently 84 lines long, but the opencode-support SPEC states a hard cap of “≤80 lines”. Either tighten the plugin to meet the cap or relax/update the SPEC constraint so the documentation matches the shipped artifact.
| - **Size:** ≤80 lines including license/header comment. | |
| - **Size:** ≤84 lines including license/header comment. |
There was a problem hiding this comment.
Actionable comments posted: 14
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In @.ark/.installed.json:
- Around line 20-22: The install manifest (.ark/.installed.json) currently lists
the three command markdown files but omits the OpenCode plugin file that
OPENCODE_PLATFORM.extra_files installs; update the manifest to include
".opencode/plugins/ark-context.ts" (and likewise add any other missing plugin
files referenced by OPENCODE_PLATFORM.extra_files) so drift detection sees the
plugin; search for OPENCODE_PLATFORM.extra_files in
crates/ark-core/src/platforms.rs to confirm the exact filenames and add them to
the array in .ark/.installed.json (also apply the same addition for the other
referenced entries around lines 49-52).
In @.ark/specs/features/opencode-support/SPEC.md:
- Around line 119-128: The OPENCODE platform spec sets dest_dir incorrectly to
OPENCODE_DIR; update the OPENCODE_PLATFORM constant so dest_dir uses
OPENCODE_COMMANDS_DIR (the directory that holds .opencode/commands) while
leaving removal_root as needed; locate the OPENCODE_PLATFORM definition and
replace dest_dir: OPENCODE_DIR with dest_dir: OPENCODE_COMMANDS_DIR so
Platform::templates extracts the command tree correctly.
In @.ark/tasks/archive/2026-04/opencode-support/00_PLAN.md:
- Around line 126-135: The platform definition OPENCODE_PLATFORM sets dest_dir
to OPENCODE_DIR but should use OPENCODE_COMMANDS_DIR to match where OpenCode
commands live and to align with Platform::templates extraction root; update
OPENCODE_PLATFORM (the Platform struct fields dest_dir and removal_root if
applicable) to reference OPENCODE_COMMANDS_DIR instead of OPENCODE_DIR so
templates and extraction targets are consistent with the implementation.
In @.ark/tasks/archive/2026-04/opencode-support/01_PLAN.md:
- Line 77: The helpers are inconsistently named (build_envelope_prefix /
should_inject vs buildEnvelopePrefix / shouldInject); choose one convention
(prefer camelCase to match the G-9 `export default async ({ directory, client })
=> ({...})` shape) and rename all occurrences to that convention—e.g., rename
build_envelope_prefix to buildEnvelopePrefix and should_inject to
shouldInject—and update every reference in the spec and the exported object
shape (including any mentions in the export default) so all usages
(buildEnvelopePrefix, shouldInject) are uniform.
- Around line 154-193: The fenced architecture block starting with ``` followed
by the tree listing (the block that begins with "crates/") is missing a language
tag; update the opening fence to include a language identifier (e.g., change ```
to ```text) so the markdownlint MD040 warning is resolved and the block is
treated as plain text.
In @.ark/tasks/archive/2026-04/opencode-support/02_PLAN.md:
- Around line 252-253: Update the archived plan wording to make the CLI
flag-conflict contract explicitly "negative-wins" throughout: change any
occurrences that state "positive-wins" to "negative-wins" and align the
descriptive text to match the implementation in resolve_platforms_pure (use f.on
&& !f.off semantics), and ensure the CLI surface note about bare #[arg(long)]
flags and the paired on/off flags consistently documents that the negative (off)
flag takes precedence; also update the same wording at the other occurrence
referenced (the second mention around line 293) so both places match.
- Around line 83-92: The spec text G-9 currently requires helper functions to be
exported (mentions "export function" for buildEnvelopePrefix and shouldInject)
which conflicts with the archived plugin's intent and breaks the loader
contract; remove the "export function" requirement and instead state that
buildEnvelopePrefix(additionalContext: string) and shouldInject(sessionID:
string, processed: Set<string>) are pure internal helpers (kept module-local)
used for testability but not required to be exported, and update the failure
handling / helper description to refer to these helpers by name
(buildEnvelopePrefix, shouldInject) without specifying they must be exported so
the plugin factory remains the only named export.
In @.ark/tasks/archive/2026-04/opencode-support/02_REVIEW.md:
- Line 28: Trim the extra spaces inside the inline code spans that trigger
MD038: locate the backtick-quoted substring currently written as " #
`/ark:<name> $ARGUMENTS` " in the changed entries (the occurrences referenced in
V-IT-2 (c), G-12 (b), and C-6 and the matching text verified against
templates/claude/commands/ark/quick.md:6) and remove the leading/trailing
padding so the code span contains the exact literal without surrounding spaces
(i.e., no space directly after the opening backtick or before the closing
backtick); apply the same trimming to the Response/Changed list text where that
span appears.
In @.ark/tasks/archive/2026-04/opencode-support/PRD.md:
- Around line 25-26: Item 10 in the PRD incorrectly states the shipped
.opencode/plugins/ark-context.ts uses no experimental.* hook; update the PRD to
reflect that the shipped plugin intentionally relies on the
experimental.chat.messages.transform hook to mutate user messages (keep the rest
of the constraints about pure TypeScript and builtin-only deps), and ensure the
two-hook contract language (and the parity tests names
every_claude_command_has_an_opencode_command_sibling and
opencode_command_bodies_have_opencode_frontmatter) explicitly acknowledge that
experimental.chat.messages.transform is part of the allowed/expected
implementation surface.
In @.ark/tasks/archive/2026-04/opencode-support/VERIFY.md:
- Around line 57-67: Recount the actual shipped plugin file and update the V-002
entry to reflect the true line count: open
templates/opencode/plugins/ark-context.ts, run a line count (e.g., wc -l) and
replace the reported "89 lines" with the accurate number (≈83/84) in the
VERIFY.md V-002 block; if you also changed code while addressing the cap, adjust
the referenced line ranges (the multi-line type annotations around the former
line ~32 and ~67 and the idx lookup previously at ~84-87) so the finding matches
the current artifact.
- Line 28: Update the VERIFY.md entries so they match the shipped plugin: mark
V-001 and V-003 as resolved/removed and update all affected paragraphs (the
blocks covering lines referenced in the review: 34-53, 71-81, 87-89) to reflect
that ark-context.ts uses stderrNoticed: Set<string> keyed by sessionID (so the
TR-2 “first failure per session” text is satisfied) and that the helpers
buildEnvelopePrefix and shouldInject are intentionally internal (not exported) —
reference the ark-context.ts symbol names and the crate test
crates::ark-core::templates::tests::opencode_plugin_keeps_helpers_internal to
justify the change and remove the stale “named for unit-testability” complaint
from the verification summary.
In @.opencode/commands/ark/archive.md:
- Line 16: Change the phrasing in the archive command guidance so that when
VERIFY was *Rejected* the message uses “ask the user to address findings first”
instead of “tell the user to address findings first”; update the sentence
containing the VERIFY guidance (replace the phrase “tell the user” with “ask the
user”) so the rejection guidance reads more polite and consistent.
In @.opencode/plugins/ark-context.ts:
- Around line 62-70: The handler for "chat.message" currently calls
processedSessions.add(sessionID) before attempting to fetch context, which marks
sessions as processed even when fetchContext(directory) fails; move the
processedSessions.add(sessionID) call inside the try block after
pendingContext.set(sessionID, fetchContext(directory)) so the session is only
marked processed on successful initiation of context fetch, and leave the catch
to call warn(client, sessionID, ...) without adding to processedSessions; keep
the existing use of shouldInject(sessionID, processedSessions) and
pendingContext.set as-is.
In `@templates/opencode/plugins/ark-context.ts`:
- Around line 51-59: fetchContext currently uses execFileSync which blocks the
chat.message hook; replace it with an asynchronous child-process invocation
(e.g., child_process.execFile or spawn wrapped in a Promise) and enforce a hard
timeout/cancellation so a slow or hung "ark context --scope session --format
json" cannot block delivery. Locate the fetchContext function and change
execFileSync to an async call that returns a Promise<string>, parse the stdout
(not stderr) as JSON, and reject/kill the child and throw a clear error if the
process exceeds a configurable timeout; keep the same payload validation
(hookSpecificOutput?.additionalContext string) and surface errors consistently
from fetchContext.
🪄 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: ac5c1985-196f-4748-a97f-5d5b23d6fd74
📒 Files selected for processing (33)
.ark/.installed.json.ark/specs/features/INDEX.md.ark/specs/features/opencode-support/SPEC.md.ark/tasks/archive/2026-04/opencode-support/00_PLAN.md.ark/tasks/archive/2026-04/opencode-support/00_REVIEW.md.ark/tasks/archive/2026-04/opencode-support/01_PLAN.md.ark/tasks/archive/2026-04/opencode-support/01_REVIEW.md.ark/tasks/archive/2026-04/opencode-support/02_PLAN.md.ark/tasks/archive/2026-04/opencode-support/02_REVIEW.md.ark/tasks/archive/2026-04/opencode-support/PRD.md.ark/tasks/archive/2026-04/opencode-support/VERIFY.md.ark/tasks/archive/2026-04/opencode-support/task.toml.ark/workflow.md.codex/skills/ark-quick/SKILL.md.opencode/commands/ark/archive.md.opencode/commands/ark/design.md.opencode/commands/ark/quick.md.opencode/plugins/ark-context.tscrates/ark-cli/src/main.rscrates/ark-core/src/commands/init.rscrates/ark-core/src/commands/load.rscrates/ark-core/src/commands/remove.rscrates/ark-core/src/commands/tests_common.rscrates/ark-core/src/commands/unload.rscrates/ark-core/src/layout.rscrates/ark-core/src/lib.rscrates/ark-core/src/platforms.rscrates/ark-core/src/templates.rstemplates/ark/workflow.mdtemplates/opencode/commands/ark/archive.mdtemplates/opencode/commands/ark/design.mdtemplates/opencode/commands/ark/quick.mdtemplates/opencode/plugins/ark-context.ts
| pub const OPENCODE_PLATFORM: Platform = Platform { | ||
| id: "opencode", | ||
| templates: &crate::templates::OPENCODE_TEMPLATES, | ||
| dest_dir: OPENCODE_DIR, | ||
| removal_root: OPENCODE_DIR, | ||
| cli_flag: "opencode", | ||
| managed_block_target: Some(AGENTS_MD), | ||
| hook_file: None, | ||
| extra_files: &[(OPENCODE_PLUGIN_FILE, OPENCODE_ARK_CONTEXT_TS)], | ||
| }; |
There was a problem hiding this comment.
Use OPENCODE_COMMANDS_DIR in the platform snippet.
The data-structure block still sets dest_dir to OPENCODE_DIR, but G-2 and the implementation both place OpenCode commands under .opencode/commands. As written, this snippet conflicts with the extraction root that Platform::templates actually needs.
Suggested fix
- dest_dir: OPENCODE_DIR,
+ dest_dir: OPENCODE_COMMANDS_DIR,📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| pub const OPENCODE_PLATFORM: Platform = Platform { | |
| id: "opencode", | |
| templates: &crate::templates::OPENCODE_TEMPLATES, | |
| dest_dir: OPENCODE_DIR, | |
| removal_root: OPENCODE_DIR, | |
| cli_flag: "opencode", | |
| managed_block_target: Some(AGENTS_MD), | |
| hook_file: None, | |
| extra_files: &[(OPENCODE_PLUGIN_FILE, OPENCODE_ARK_CONTEXT_TS)], | |
| }; | |
| pub const OPENCODE_PLATFORM: Platform = Platform { | |
| id: "opencode", | |
| templates: &crate::templates::OPENCODE_TEMPLATES, | |
| dest_dir: OPENCODE_COMMANDS_DIR, | |
| removal_root: OPENCODE_DIR, | |
| cli_flag: "opencode", | |
| managed_block_target: Some(AGENTS_MD), | |
| hook_file: None, | |
| extra_files: &[(OPENCODE_PLUGIN_FILE, OPENCODE_ARK_CONTEXT_TS)], | |
| }; |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In @.ark/tasks/archive/2026-04/opencode-support/00_PLAN.md around lines 126 -
135, The platform definition OPENCODE_PLATFORM sets dest_dir to OPENCODE_DIR but
should use OPENCODE_COMMANDS_DIR to match where OpenCode commands live and to
align with Platform::templates extraction root; update OPENCODE_PLATFORM (the
Platform struct fields dest_dir and removal_root if applicable) to reference
OPENCODE_COMMANDS_DIR instead of OPENCODE_DIR so templates and extraction
targets are consistent with the implementation.
| ``` | ||
| crates/ | ||
| ├── ark-cli/src/main.rs — InitArgs gains 2 OpenCode flags | ||
| │ (bare #[arg(long)], no | ||
| │ conflicts_with — matches existing | ||
| │ --claude/--no-claude shape). | ||
| │ interactive prompt + non-TTY | ||
| │ error path extend to 3 platforms | ||
| └── ark-core/src/ | ||
| ├── lib.rs — re-exports OPENCODE_PLATFORM | ||
| ├── platforms.rs — adds OPENCODE_PLATFORM const; | ||
| │ PLATFORMS slice grows to 3 entries; | ||
| │ test asserting registry shape updated | ||
| ├── layout.rs — adds OPENCODE_DIR, | ||
| │ OPENCODE_PLUGIN_FILE consts; | ||
| │ Layout::opencode_dir(), | ||
| │ Layout::opencode_plugin_file(); | ||
| │ owned_dirs grows to [PathBuf; 4] | ||
| ├── io/fs.rs — UNCHANGED (no new hook plumbing) | ||
| ├── state/ — UNCHANGED | ||
| ├── commands/ — UNCHANGED command bodies; iteration | ||
| │ over PLATFORMS picks up the new | ||
| │ entry for free | ||
| └── templates.rs — adds OPENCODE_TEMPLATES static + | ||
| OPENCODE_ARK_CONTEXT_TS const | ||
| templates/ | ||
| ├── ark/ — unchanged | ||
| ├── claude/ — unchanged | ||
| ├── codex/ — unchanged | ||
| └── opencode/ — NEW | ||
| ├── commands/ | ||
| │ └── ark/ | ||
| │ ├── quick.md — frontmatter + body of claude/quick.md | ||
| │ ├── design.md — ditto | ||
| │ └── archive.md — ditto | ||
| └── plugins/ | ||
| └── ark-context.ts — Bun-loaded TS plugin (≤80 lines), | ||
| two-hook (chat.message + | ||
| experimental.chat.messages.transform) | ||
| ``` |
There was a problem hiding this comment.
Add a language tag to the fenced architecture block (markdownlint MD040).
Line 154 opens a code fence without a language identifier; this will keep docs lint noisy.
Suggested fix
-```
+```text
crates/
├── ark-cli/src/main.rs — InitArgs gains 2 OpenCode flags
│ (bare #[arg(long)], no
│ conflicts_with — matches existing
│ --claude/--no-claude shape).
...
└── plugins/
└── ark-context.ts — Bun-loaded TS plugin (≤80 lines),
two-hook (chat.message +
experimental.chat.messages.transform)</details>
<details>
<summary>🧰 Tools</summary>
<details>
<summary>🪛 markdownlint-cli2 (0.22.1)</summary>
[warning] 154-154: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
</details>
</details>
<details>
<summary>🤖 Prompt for AI Agents</summary>
Verify each finding against the current code and only fix it if needed.
In @.ark/tasks/archive/2026-04/opencode-support/01_PLAN.md around lines 154 -
193, The fenced architecture block starting with followed by the tree listing (the block that begins with "crates/") is missing a language tag; update the opening fence to include a language identifier (e.g., change to ```text)
so the markdownlint MD040 warning is resolved and the block is treated as plain
text.
</details>
<!-- fingerprinting:phantom:triton:puma:e7bc2245-ef95-4827-b64a-0067c2b256fa -->
<!-- d98c2f50 -->
<!-- This is an auto-generated comment by CodeRabbit -->
| ### V-002 `Plugin file exceeds G-9 size cap of 80 lines` | ||
|
|
||
| - Severity: LOW | ||
| - Scope: SPEC Drift | ||
| - Location: `templates/opencode/plugins/ark-context.ts:1-89` | ||
| - Problem: | ||
| G-9 names "Size: ≤80 lines including license/header comment." `wc -l` reports 89 lines on the shipped file. The overage is small (9 lines) and the file is still tight, but the SPEC is a hard cap and the verifier-of-reviewers role flags it because the same Spec section is the future feature SPEC. | ||
| - Why it matters: | ||
| Same archive-promotion concern as V-001. The cap is a guardrail against the plugin growing into a meaningful TS module that would need its own tests / package.json / build step (NG-6 / G-15 invariants). 89 lines is well within the spirit of the cap, but the literal number is over. | ||
| - Expected: | ||
| Follow-up task. Either (a) tighten the plugin (collapse the type annotations on lines 32 and 67 — they expand to multi-line wrap-arounds; the `idx` lookup on lines 84-87 can be one expression), or (b) revise 02_PLAN G-9 to "≤90 lines including license/header comment" before archive. (b) is honest about what landed; (a) preserves the original constraint. |
There was a problem hiding this comment.
Recount the plugin before filing the size-cap follow-up.
The cap violation may still stand, but the evidence here is stale: the shipped plugin in this PR is ~83/84 lines, not 89. Please update the finding so the record matches the artifact under review.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In @.ark/tasks/archive/2026-04/opencode-support/VERIFY.md around lines 57 - 67,
Recount the actual shipped plugin file and update the V-002 entry to reflect the
true line count: open templates/opencode/plugins/ark-context.ts, run a line
count (e.g., wc -l) and replace the reported "89 lines" with the accurate number
(≈83/84) in the VERIFY.md V-002 block; if you also changed code while addressing
the cap, adjust the referenced line ranges (the multi-line type annotations
around the former line ~32 and ~67 and the idx lookup previously at ~84-87) so
the finding matches the current artifact.
| - Standard/Deep: `phase = "verify"` with a VERIFY verdict of *Approved* or *Approved with Follow-ups*. | ||
| - The user has confirmed they want to archive now (not implicit; they typed `/ark:archive`). | ||
|
|
||
| If VERIFY was *Rejected*, refuse and tell the user to address findings first. |
There was a problem hiding this comment.
Prefer “ask” over “tell” in the rejection guidance.
“Ask the user to address findings first” reads a bit better here than “tell the user.”
🧰 Tools
🪛 LanguageTool
[style] ~16-~16: Consider using the more polite verb “ask” (“tell” implies ordering/instructing someone).
Context: .... If VERIFY was Rejected, refuse and tell the user to address findings first. ##...
(TELL_ASK)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In @.opencode/commands/ark/archive.md at line 16, Change the phrasing in the
archive command guidance so that when VERIFY was *Rejected* the message uses
“ask the user to address findings first” instead of “tell the user to address
findings first”; update the sentence containing the VERIFY guidance (replace the
phrase “tell the user” with “ask the user”) so the rejection guidance reads more
polite and consistent.
| "chat.message": async (input: { sessionID?: string }) => { | ||
| const sessionID = input?.sessionID | ||
| if (!sessionID || !shouldInject(sessionID, processedSessions)) return | ||
| processedSessions.add(sessionID) | ||
| try { | ||
| pendingContext.set(sessionID, fetchContext(directory)) | ||
| } catch (err) { | ||
| warn(client, sessionID, `ark context failed: ${(err as Error)?.message ?? String(err)}`) | ||
| } |
There was a problem hiding this comment.
Don't mark the session as processed until context fetch succeeds.
A failed first ark context call currently burns the session forever because processedSessions is updated before the try. That makes transient failures unrecoverable and prevents later messages in the same session from ever retrying context injection.
🔧 Minimal fix
export default async ({ directory, client }: { directory: string; client: Client }) => ({
"chat.message": async (input: { sessionID?: string }) => {
const sessionID = input?.sessionID
if (!sessionID || !shouldInject(sessionID, processedSessions)) return
- processedSessions.add(sessionID)
try {
pendingContext.set(sessionID, fetchContext(directory))
+ processedSessions.add(sessionID)
} catch (err) {
warn(client, sessionID, `ark context failed: ${(err as Error)?.message ?? String(err)}`)
}
},📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| "chat.message": async (input: { sessionID?: string }) => { | |
| const sessionID = input?.sessionID | |
| if (!sessionID || !shouldInject(sessionID, processedSessions)) return | |
| processedSessions.add(sessionID) | |
| try { | |
| pendingContext.set(sessionID, fetchContext(directory)) | |
| } catch (err) { | |
| warn(client, sessionID, `ark context failed: ${(err as Error)?.message ?? String(err)}`) | |
| } | |
| "chat.message": async (input: { sessionID?: string }) => { | |
| const sessionID = input?.sessionID | |
| if (!sessionID || !shouldInject(sessionID, processedSessions)) return | |
| try { | |
| pendingContext.set(sessionID, fetchContext(directory)) | |
| processedSessions.add(sessionID) | |
| } catch (err) { | |
| warn(client, sessionID, `ark context failed: ${(err as Error)?.message ?? String(err)}`) | |
| } |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In @.opencode/plugins/ark-context.ts around lines 62 - 70, The handler for
"chat.message" currently calls processedSessions.add(sessionID) before
attempting to fetch context, which marks sessions as processed even when
fetchContext(directory) fails; move the processedSessions.add(sessionID) call
inside the try block after pendingContext.set(sessionID,
fetchContext(directory)) so the session is only marked processed on successful
initiation of context fetch, and leave the catch to call warn(client, sessionID,
...) without adding to processedSessions; keep the existing use of
shouldInject(sessionID, processedSessions) and pendingContext.set as-is.
| function fetchContext(directory: string): string { | ||
| const raw = execFileSync("ark", ["context", "--scope", "session", "--format", "json"], { | ||
| cwd: directory, encoding: "utf-8", stdio: ["ignore", "pipe", "pipe"], | ||
| }) | ||
| const parsed = JSON.parse(raw) as { hookSpecificOutput?: { additionalContext?: unknown } } | ||
| const value = parsed.hookSpecificOutput?.additionalContext | ||
| if (typeof value !== "string" || !value) throw new Error("unexpected payload shape") | ||
| return value | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Find the ark-context.ts file and examine its full contents
find . -name "ark-context.ts" -type fRepository: Anekoique/Ark
Length of output: 136
🏁 Script executed:
#!/bin/bash
# Search for where fetchContext is called
rg "fetchContext" -A 3 -B 3Repository: Anekoique/Ark
Length of output: 1262
🏁 Script executed:
#!/bin/bash
# Look at the integration tests in commands/init.rs
find . -path "*/commands/init.rs" -type fRepository: Anekoique/Ark
Length of output: 96
🏁 Script executed:
#!/bin/bash
# Read the full ark-context.ts template file
cat -n ./templates/opencode/plugins/ark-context.tsRepository: Anekoique/Ark
Length of output: 4571
🏁 Script executed:
#!/bin/bash
# Examine the init.rs file to see integration tests
head -100 ./crates/ark-core/src/commands/init.rsRepository: Anekoique/Ark
Length of output: 3147
🏁 Script executed:
#!/bin/bash
# Search for test functions in init.rs
rg "mod tests|#\[test\]|fn test" ./crates/ark-core/src/commands/init.rs -B 2Repository: Anekoique/Ark
Length of output: 1443
Avoid a synchronous subprocess on the OpenCode chat path.
execFileSync runs inside the chat.message hook (the first-message injection point) and has no timeout, so a slow or hung ark context invocation can block message delivery for the entire session. Switch this to an async child-process call or add a hard timeout / cancellation boundary.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@templates/opencode/plugins/ark-context.ts` around lines 51 - 59, fetchContext
currently uses execFileSync which blocks the chat.message hook; replace it with
an asynchronous child-process invocation (e.g., child_process.execFile or spawn
wrapped in a Promise) and enforce a hard timeout/cancellation so a slow or hung
"ark context --scope session --format json" cannot block delivery. Locate the
fetchContext function and change execFileSync to an async call that returns a
Promise<string>, parse the stdout (not stderr) as JSON, and reject/kill the
child and throw a clear error if the process exceeds a configurable timeout;
keep the same payload validation (hookSpecificOutput?.additionalContext string)
and surface errors consistently from fetchContext.
9df44e3 to
cb54f60
Compare
|
Claude finished @Anekoique's task in 4m 51s —— View job PR Review: feat(platform): add opencode supportReviewed by: Claude (claude-sonnet-4-6) SummaryThe OpenCode platform support follows the same registry-driven pattern as Codex well. The core architecture is sound: 🔴 Finding 1 — Correctness bug: session permanently poisoned on transient failureFile: export default async ({ directory, client }: { directory: string; client: Client }) => ({
"chat.message": async (input: { sessionID?: string }) => {
const sessionID = input?.sessionID
if (!sessionID || !shouldInject(sessionID, processedSessions)) return
processedSessions.add(sessionID) // ← marked BEFORE the try block
try {
pendingContext.set(sessionID, fetchContext(directory))
} catch (err) {
warn(client, sessionID, `ark context failed: ${(err as Error)?.message ?? String(err)}`)
}
},
Fix: Move try {
pendingContext.set(sessionID, fetchContext(directory))
processedSessions.add(sessionID) // only mark processed on success
} catch (err) {
warn(client, sessionID, `ark context failed: ${(err as Error)?.message ?? String(err)}`)
}Note: both the installed file ( 🔴 Finding 2 — Stray
|

This pull request updates the
.ark/.installed.jsonand.ark/specs/features/INDEX.mdfiles to add support for the OpenCode platform. The main changes involve registering new OpenCode-related command files and updating feature documentation to reflect this support.Platform support updates:
.opencode/commands/ark/archive.md,.opencode/commands/ark/design.md, and.opencode/commands/ark/quick.mdto the list of installed files in.ark/.installed.json, replacing the previous.claude/commands/ark/*entries. [1] [2].ark/.installed.jsonto include the new OpenCode command files and reflect changes to.ark/specs/features/INDEX.mdand.ark/workflow.md.Documentation updates:
opencode-supportin.ark/specs/features/INDEX.mdto document the addition of OpenCode platform support.Summary by CodeRabbit
New Features
Documentation