Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,8 @@ and runs on [OpenCode](https://opencode.ai) and [Pi](https://github.com/earendil
```bash
opencode
```
> **Windows users:** `.opencode/{agents,commands,skills,rules}` are git symlinks and require `core.symlinks=true` (+ Developer Mode) to check out correctly — see [Known Issues](#-known-issues) if `/` shows no commands.

Type `/` to browse all 77 skills and 54 commands, or `/start` for onboarding.

### Pi
Expand Down Expand Up @@ -374,6 +376,7 @@ skills, commands, rules, and plugins.
| Issue | Impact | Workaround |
|-------|--------|------------|
| **Subagent model resolution via `task`** — Agent `model:` frontmatter fails with `ProviderModelNotFoundError` for models that work when used directly via `opencode -m <model>`. Subagents inherit the caller's model per OpenCode docs, so the frontmatter model may only apply when the agent runs as a primary session. | Agents using `opencode-go/kimi-k2.6` and `opencode-go/deepseek-v4-flash` as subagents via `task` | Use `opencode-go/qwen3.6-plus` for subagent-heavy workflows, or start dedicated sessions with `opencode -m <model>` for director-level agents. Root cause being tracked upstream in OpenCode. |
| **`.opencode/*` symlinks inert on Windows** — `.opencode/{agents,commands,skills,rules}` are committed as git symlinks. With the Windows default `core.symlinks=false`, git checks them out as plain-text stub files instead of real symlinks, so OpenCode's `/`-command and skill/agent discovery finds nothing. | OpenCode is non-functional on a stock Windows clone (Pi is unaffected — it reads `.agents/` directly) | `git config --global core.symlinks true` + enable Developer Mode, then re-clone; or run `node tools/fix-opencode-symlinks.mjs` to repair an existing checkout. |

---

Expand Down
14 changes: 14 additions & 0 deletions docs/pi-compatibility.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,3 +72,17 @@ If you have an existing OCGS project:
1. Run `node tools/migrate-to-agents.mjs` to migrate content from `.opencode/` to `.agents/`
2. Run with `--dry-run` first to preview
3. Use `--remove-old` to delete the original `.opencode/` content after verification

## Windows Symlink Setup

`.opencode/{agents,commands,skills,rules}` are git symlinks pointing at the
canonical `.agents/*` directories. On Windows, git only checks these out as
real symlinks if `core.symlinks=true` is set (and Developer Mode/admin rights
are enabled) **before** cloning. Otherwise they check out as inert stub files
containing the link-target text, and OpenCode's `.opencode/`-based discovery
silently finds nothing (Pi is unaffected — it reads `.agents/` directly).

Fix: `git config --global core.symlinks true` + enable Developer Mode, then
re-clone (or run `git checkout -- .opencode` after enabling). Alternatively,
run `node tools/fix-opencode-symlinks.mjs` to detect and repair inert stub
files in an existing checkout.
113 changes: 113 additions & 0 deletions tools/fix-opencode-symlinks.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
#!/usr/bin/env node
/**
* Detects and repairs inert `.opencode/{agents,commands,skills,rules}` entries.
*
* These paths are committed as git symlinks pointing at `../.agents/<name>`.
* On Windows, `git checkout` materializes symlinks as real OS symlinks only
* when `core.symlinks=true` is set (which itself requires Developer Mode or
* admin rights). With the default `core.symlinks=false`, git instead writes
* a small plain-text file containing the link target string — which silently
* breaks OpenCode's `.opencode/`-based agent/skill/command/rule discovery.
*
* This script detects that broken state and repairs it by replacing the stub
* file with a real directory symlink/junction pointing at the canonical
* `.agents/<name>` directory.
*
* Usage: node tools/fix-opencode-symlinks.mjs [--dry-run]
*/
import fs from "node:fs";
import path from "node:path";
import { fileURLToPath } from "node:url";

const __dirname = path.dirname(fileURLToPath(import.meta.url));
const ROOT = path.resolve(__dirname, "..");
const LINKS = ["agents", "commands", "skills", "rules"];
const STUB_RE = /^\.\.[\\/]\.agents[\\/][a-z-]+\s*$/i;

const dryRun = process.argv.includes("--dry-run");

function classify(linkPath) {
if (!fs.existsSync(linkPath)) return "missing";
const stat = fs.lstatSync(linkPath);
if (stat.isSymbolicLink()) {
try {
const real = fs.realpathSync(linkPath);
return fs.existsSync(real) && fs.statSync(real).isDirectory()
? "ok-symlink"
: "broken-symlink";
} catch {
return "broken-symlink";
}
}
if (stat.isDirectory()) return "ok-directory"; // e.g. a real materialized copy
if (stat.isFile()) {
const content = fs.readFileSync(linkPath, "utf8").trim();
if (STUB_RE.test(content)) return "stub-file";
return "unknown-file";
}
return "unknown";
}

function repair(name) {
const linkPath = path.join(ROOT, ".opencode", name);
const targetRel = path.join("..", ".agents", name);
const targetAbs = path.join(ROOT, ".agents", name);

if (!fs.existsSync(targetAbs) || !fs.statSync(targetAbs).isDirectory()) {
console.error(` ✗ ${name}: canonical source .agents/${name} does not exist — skipping`);
return false;
}

if (dryRun) {
console.log(` → would replace .opencode/${name} with a symlink/junction to .agents/${name}`);
return true;
}

fs.rmSync(linkPath, { force: true });
try {
// 'junction' works without elevated privileges on Windows; falls back
// to a regular symlink type on POSIX (junction is ignored there).
fs.symlinkSync(targetRel, linkPath, process.platform === "win32" ? "junction" : "dir");
} catch (err) {
// Junctions require an absolute target on Windows.
if (process.platform === "win32") {
fs.symlinkSync(targetAbs, linkPath, "junction");
} else {
throw err;
}
}
console.log(` ✓ ${name}: repaired -> .agents/${name}`);
return true;
}

console.log("Checking .opencode/{agents,commands,skills,rules}...\n");

let anyBroken = false;
for (const name of LINKS) {
const linkPath = path.join(ROOT, ".opencode", name);
const state = classify(linkPath);
if (state === "ok-symlink" || state === "ok-directory") {
console.log(` ✓ ${name}: OK (${state})`);
continue;
}
anyBroken = true;
console.log(` ✗ ${name}: ${state} — this is broken, OpenCode cannot see .agents/${name}`);
repair(name);
}

if (!anyBroken) {
console.log("\nAll .opencode/* links are healthy. Nothing to do.");
process.exit(0);
}

console.log(
dryRun
? "\nDry run complete. Re-run without --dry-run to apply fixes."
: "\nRepair complete. Re-run this script any time after a fresh clone/checkout."
);
console.log(
"\nTip: to avoid this permanently, enable symlink support before cloning:\n" +
" git config --global core.symlinks true\n" +
" (Windows also requires Developer Mode or running git as Administrator)\n" +
"then re-clone, or run `git checkout -- .opencode` after enabling it."
);