Document: 12-MASTER-SYNTHESIS.md
GitHub Issue: #206
Status: Authoritative Implementation Plan
Date: 2026-02-19
Synthesized from: 6 specialist research agents (Context, Agent Behaviour, Task Breakdown, Plugin System, ExternalScout, CLI & Multi-IDE)
OAC (@nextsystems/oac) is transitioning from a 52KB bash-script installer (install.sh) into a proper npm CLI package with a rich plugin system, registry-backed component management, and first-class multi-IDE support.
The refactored OAC manages four types of AI configuration artifacts across four IDEs:
| Artifact | Description | Primary Location |
|---|---|---|
| Agents | .md files with YAML frontmatter defining AI personas and permissions |
.opencode/agent/ |
| Context files | Markdown guides that shape AI behaviour in a session | .opencode/context/ |
| Skills | Loadable modules (SKILL.md + router.sh + scripts/) |
.opencode/skills/ |
| Plugins | IDE integration hooks (TypeScript events, file-based, shell scripts) | IDE-specific |
| IDE | Priority | Integration Method |
|---|---|---|
| OpenCode | PRIMARY | TypeScript npm plugin ("plugin": ["@nextsystems/oac"]) |
| Claude Code | Secondary | File-based plugin (.claude-plugin/) |
| Cursor | Tertiary | Router pattern in .cursorrules |
| Windsurf | Partial | Partial compatibility adapter |
agent.json + prompt.md as source of truth — clean separation of metadata/configuration from prose content; generate IDE-specific formats from this canonical form.registry.json in the repo.oac.lock lockfile — reproducible installs across machines and CI, analogous to package-lock.json.update-notifier for the CLI binary itself; hash-based registry polling for content files.packages/cli (new) + packages/compatibility-layer (existing) + packages/plugin-abilities (existing).npx @nextsystems/oac init completes full project setup in under 2 minutesnpm update without overwriting user customizationssession.created eventoac doctor catches 100% of common misconfiguration issues@nextsystems/oac/ # Root package (npm: @nextsystems/oac)
├── bin/
│ └── oac.js # CLI entry point (compiled)
├── packages/
│ ├── cli/ # NEW: Full commander.js CLI
│ │ ├── src/
│ │ │ ├── commands/ # One file per command group
│ │ │ ├── registry/ # Registry client
│ │ │ ├── resolvers/ # 6-layer context resolver
│ │ │ ├── adapters/ # IDE format adapters
│ │ │ └── index.ts # Entry point
│ │ ├── tsconfig.json
│ │ └── package.json
│ ├── compatibility-layer/ # EXISTING: Multi-IDE adapters
│ │ ├── src/
│ │ │ ├── adapters/
│ │ │ │ ├── claude.ts # COMPLETE
│ │ │ │ ├── cursor.ts # COMPLETE
│ │ │ │ └── windsurf.ts # COMPLETE
│ │ │ └── index.ts
│ │ └── package.json
│ └── plugin-abilities/ # EXISTING: OpenCode plugin
│ ├── src/
│ │ ├── events/ # 25+ OpenCode event handlers
│ │ └── index.ts
│ └── package.json
├── .opencode/ # Bundled OAC configuration
│ ├── agent/ # Agent .md files (YAML frontmatter)
│ ├── context/ # Bundled context files
│ ├── skills/ # Skill packages
│ ├── plugin/ # OpenCode plugin hooks
│ └── opencode.json # OpenCode config
├── registry.json # Component registry (shadcn-style)
├── manifest.json # Bundle manifest with checksums
├── oac.lock # Lockfile template (copied to project)
└── package.json
┌─────────────────────────────────────────────────────────────────┐
│ OAC CLI (Subsystem 1) │
│ oac init / add / update / remove / context / skill / plugin │
│ oac doctor / rollback / publish / browse / search │
└──────────┬────────────┬───────────────┬────────────┬────────────┘
│ │ │ │
▼ ▼ ▼ ▼
┌──────────────┐ ┌────────────┐ ┌──────────────┐ ┌─────────────┐
│ Context │ │ Agent & │ │ Plugin │ │ Registry & │
│ System │ │ Skill │ │ System │ │ Community │
│ (Subsystem 2)│ │ Management │ │ (Subsystem 4)│ │(Subsystem 5)│
│ │ │(Subsystem 3│ │ │ │ │
│ 6-layer │ │ │ │ OpenCode │ │ shadcn │
│ resolution │ │ agent.json │ │ Claude Code │ │ registry │
│ Bundle/ │ │ + prompt.md│ │ Cursor │ │ oac.lock │
│ manifest │ │ Presets │ │ Windsurf │ │ SHA256 hash │
│ Auto-update │ │ Skills pkg │ │ session. │ │ verify │
└──────────────┘ └────────────┘ │ created hook │ └─────────────┘
└──────────────┘
npm registry / community registry
│
│ oac add agent/openagent
▼
Registry Client
│ fetches files + verifies SHA256
▼
oac.lock updated
│
├──► .opencode/agent/openagent/
│ ├── agent.json (metadata, permissions, config)
│ └── prompt.md (prose content)
│
│ oac compat apply --ide=cursor
▼
Compatibility Adapter
│
├──► .cursorrules (Cursor router pattern)
├──► CLAUDE.md (Claude Code format)
└──► .windsurfrules (Windsurf format)
OpenCode session starts
│
│ fires session.created event
▼
plugin-abilities/src/events/session.ts
│
│ reads oac.lock
│ checks registry for newer versions
│ compares SHA256 of installed files
▼
No user modifications detected → auto-update silently
User modifications detected → prompt user (or skip in --yolo mode)
│
▼
Files updated in .opencode/
IDE picks up changes on next tool invocation
The current bin/oac.js is 91 lines of Node.js that spawns install.sh (52KB bash script). It has no command parsing, no help text, no interactive prompts, and no multi-IDE awareness. The npm package files array in package.json already bundles .opencode/ content correctly.
A full commander.js-based CLI with 20+ commands, interactive prompts via @inquirer/prompts, progress indication via ora, and coloured output via chalk. Built with tsup into dist/cli.js, entry point remains bin/oac.js which simply requires the compiled output.
// packages/cli/package.json
{
"name": "@nextsystems/oac-cli",
"private": true,
"main": "dist/index.js",
"scripts": {
"build": "tsup src/index.ts --format cjs --dts",
"test": "vitest run",
"dev": "tsup src/index.ts --watch"
},
"dependencies": {
"commander": "^12.0.0",
"@inquirer/prompts": "^5.0.0",
"ora": "^8.0.0",
"chalk": "^5.0.0",
"conf": "^13.0.0",
"fs-extra": "^11.0.0",
"semver": "^7.6.0",
"zod": "^3.23.0",
"update-notifier": "^7.0.0"
}
}
// packages/cli/src/index.ts
import { Command } from "commander";
import { initCommand } from "./commands/init";
import { addCommand } from "./commands/add";
import { contextCommand } from "./commands/context";
import { skillCommand } from "./commands/skill";
import { pluginCommand } from "./commands/plugin";
import { doctorCommand } from "./commands/doctor";
const program = new Command()
.name("oac")
.description("AI agent configuration manager")
.version(packageJson.version)
.option("--yolo", "skip all confirmations")
.option("--no-color", "disable color output");
program.addCommand(initCommand);
program.addCommand(addCommand);
program.addCommand(contextCommand);
program.addCommand(skillCommand);
program.addCommand(pluginCommand);
program.addCommand(doctorCommand);
// ... additional commands
program.parse();
node_modules resolution issues when installed globally via npm install -g--yolo flag: Skips all inquirer prompts and confirmation gates. Required for CI/automation use casesconf for global config: Uses OS-appropriate config directory (~/.config/oac/ on Linux/Mac, %APPDATA%/oac/ on Windows). Survives npm updatesupdate-notifier: Background process checks npm registry for CLI updates; shows non-blocking notification at session endpackages/cli/ package does not exist yet — needs to be created from scratchbin/oac.js needs to be rewritten to simply require('../packages/cli/dist/index.js')install.sh remains as legacy fallback during transition but should be deprecated in v1.0Context files exist in .opencode/context/ with a rich function-based and concern-based organizational pattern. CONTEXT_SYSTEM_GUIDE.md (16KB) documents the system thoroughly. However there is no programmatic management — files are installed/copied by install.sh and never updated automatically.
A 6-layer resolution system where the CLI and plugin can deterministically locate, version, and update context files. All OAC-maintained context files are bundled into the npm package with SHA256 checksums tracked in manifest.json.
Priority (highest to lowest):
┌─────────────────────────────────────────────────────┐
│ 1. .oac/context/ project override │ ← USER OWNED
│ 2. .opencode/context/ IDE config dir │ ← OAC MANAGED (with user edits)
│ 3. IDE-specific dir e.g. .cursor/context/ │ ← IDE MANAGED
│ 4. docs/ project documentation │ ← PROJECT OWNED
│ 5. ~/.config/oac/context/ user global overrides │ ← USER OWNED
│ 6. npm package bundled @nextsystems/oac/context/ │ ← OAC DEFAULT
└─────────────────────────────────────────────────────┘
Resolution algorithm:
oac update// manifest.json (in npm package root, copied to .oac/manifest.json on init)
{
"version": "0.7.1",
"generatedAt": "2026-02-19T00:00:00Z",
"context": {
"typescript-patterns.md": {
"sha256": "a1b2c3d4...",
"size": 4821,
"category": "language",
"description": "TypeScript coding patterns and conventions"
},
"git-workflow.md": {
"sha256": "e5f6a7b8...",
"size": 2341,
"category": "workflow"
}
},
"agents": {
"openagent": {
"sha256": "c9d0e1f2...",
"files": ["agent.json", "prompt.md"]
}
},
"skills": {
"task-management": {
"sha256": "f3a4b5c6...",
"files": ["SKILL.md", "router.sh", "scripts/task-cli.js"]
}
}
}
oac update context (or: triggered by session.created event)
│
├── Read .oac/manifest.json (installed versions + hashes)
├── Fetch registry for latest manifest
├── For each context file:
│ ├── SHA256(installed file) == manifest.sha256?
│ │ YES → file is stock OAC → safe to update
│ │ NO → file has user modifications
│ │ ├── update mode == "auto-all" → overwrite + backup
│ │ ├── update mode == "auto-safe" → skip + notify
│ │ └── update mode == "manual" → skip silently
│ └── New version available? → download + install + update manifest
└── Print summary of updated / skipped / conflict files
oac context install [name] # Install a specific context file
oac context update [name] # Update installed context files
oac context validate # Validate all context files are syntactically correct
oac context resolve <name> # Show which layer a context file resolves from
oac context list # List all context files with source layer
oac context diff <name> # Show diff between installed and stock version
manifest.json is the authoritative truth: Neither package.json version nor git history determines what's installed — only manifest.json + SHA256 comparison.Agents are .md files with YAML frontmatter in .opencode/agent/. No programmatic management exists. Skills live in .opencode/skills/ as SKILL.md + router.sh + scripts/ bundles. task-cli.ts is a TypeScript file requiring ts-node to run — this is a critical gap.
A canonical agent.json + prompt.md two-file representation per agent, with IDE-specific formats generated on demand. Skills are proper packages with compiled JS. A preset system allows user customizations to survive updates.
agent.json + prompt.md.opencode/agent/openagent/
├── agent.json # Metadata, permissions, config, OAC-specific data
└── prompt.md # The actual agent prompt content (prose)
// agent.json — canonical agent definition
{
"name": "openagent",
"displayName": "Open Agent",
"version": "2.1.0",
"description": "Primary orchestration agent for plan-first development",
"model": "claude-sonnet-4-5",
"maxTokens": 8192,
"permission": [
{ "deny": "bash(**)" },
{ "allow": "bash(git status, git diff, git log)" },
{ "allow": "bash(npm run*)" }
],
"tools": ["read", "write", "edit", "bash", "glob", "grep"],
"tags": ["orchestration", "planning", "primary"],
"oac": {
"bundledSha256": "a1b2c3d4e5f6...",
"installedAt": "2026-02-19T00:00:00Z",
"source": "registry",
"presetApplied": "team-lead-preset"
}
}
---
name: openagent
model: claude-sonnet-4-5
maxTokens: 8192
---
# Open Agent
You are OpenAgent, the primary orchestration agent...
[prose content continues]
Design principle: prompt.md YAML frontmatter contains only fields that the IDE (OpenCode) reads natively. All OAC-specific metadata goes in agent.json. This avoids frontmatter bloat and keeps IDE compatibility clean.
The permission: field uses last-match-wins evaluation (same as OpenCode's native system):
"permission": [
{ "deny": "bash(**)" }, // default deny all bash
{ "allow": "bash(git status)" }, // allow specific git commands
{ "allow": "bash(npm run*)" } // allow npm run commands
]
Rules are evaluated in order; the LAST matching rule wins. This matches OpenCode's permission semantics exactly, ensuring IDE-native compatibility.
User customizations are stored separately from stock agent files:
~/.config/oac/presets/
├── team-lead-preset.json # User's customizations
└── solo-dev-preset.json
// team-lead-preset.json
{
"name": "team-lead-preset",
"appliesTo": ["openagent", "task-manager"],
"overrides": {
"model": "claude-opus-4-5",
"maxTokens": 16384,
"permission": [
{ "allow": "bash(docker*)" }
]
},
"promptAppend": "\n\n## Team Context\nAlways consider team conventions..."
}
When oac update runs:
agent.json + prompt.md are updated from registry.opencode/agent/| Agent | Purpose | Critical |
|---|---|---|
openagent |
Primary orchestration, plan-first development | Yes |
opencoder |
Code implementation | Yes |
contextscout |
Context discovery and navigation | Yes |
externalscout |
External documentation fetching | Yes |
task-manager |
Task breakdown and tracking | Yes |
coder-agent |
Focused coding tasks | Yes |
.opencode/skills/task-management/
├── SKILL.md # Skill definition (YAML frontmatter + prose)
├── router.sh # Dispatch script (must be fully implemented, not stub)
└── scripts/
├── task-cli.js # COMPILED from task-cli.ts (NOT ts-node)
└── helpers.js
# SKILL.md frontmatter
---
name: task-management
version: 1.2.0
description: Task breakdown, tracking, and validation
entrypoint: router.sh
scripts:
- scripts/task-cli.js
permissions:
- read: ".tmp/sessions/**"
- write: ".tmp/sessions/**"
---
| Skill | Status | Critical Gap |
|---|---|---|
task-management |
Mostly complete | task-cli.ts must be compiled to JS |
context-manager |
router.sh is STUB | Needs full implementation (highest priority) |
context7 |
Complete | None |
smart-router-skill |
Complete | None |
oac add agent <name> # Install agent from registry
oac remove agent <name> # Remove agent
oac list agents # List installed agents
oac customize agent <name> # Open editor for agent customization
oac presets list # List available presets
oac presets apply <preset> # Apply preset to agents
oac validate agents # Validate all agent files
oac create agent # Interactive agent creation wizard
oac skill install <name> # Install skill from registry
oac skill list # List installed skills
oac skill update [name] # Update skill(s)
oac skill remove <name> # Remove skill
oac skill validate # Validate all skills
oac task status # Show current task session status
oac task next # Get next task
oac task complete <id> # Mark task as complete
Two existing plugin systems:
.claude-plugin/): File-based plugin using session-start.sh bash script. Functional but bash-only.packages/plugin-abilities/): TypeScript event system. Compatibility layer (Phases 1-3) is ~59% complete. CLI integration is missing.Adapters for Claude, Cursor, and Windsurf exist in packages/compatibility-layer/ and are functionally complete but have no CLI wiring.
OAC becomes a first-class OpenCode npm plugin registered in opencode.json:
// .opencode/opencode.json
{
"plugin": ["@nextsystems/oac"],
"model": "claude-sonnet-4-5",
"theme": "opencode"
}
This single line activates the full OAC plugin system including auto-updates on session start.
OpenCode offers the richest integration API:
// packages/plugin-abilities/src/index.ts
import type { Plugin } from "@opencode/sdk";
import { handleSessionCreated } from "./events/session";
import { handleToolBefore } from "./events/tools";
export default {
name: "@nextsystems/oac",
version: "0.7.1",
events: {
"session.created": handleSessionCreated,
"tool.before": handleToolBefore,
},
tools: [
// Custom OAC tools exposed to the AI
],
} satisfies Plugin;
// packages/plugin-abilities/src/events/session.ts
export async function handleSessionCreated(ctx: SessionContext) {
// 1. Check for OAC updates
const updates = await checkForUpdates();
// 2. Apply safe updates (no user modifications)
await applySafeUpdates(updates);
// 3. Notify about skipped updates (user-modified files)
if (updates.conflicts.length > 0) {
ctx.notify(`OAC: ${updates.conflicts.length} files have local modifications — run 'oac update' to manage`);
}
// 4. Validate active context
await validateActiveContext(ctx);
}
The .claude-plugin/session-start.sh needs to be rewritten in TypeScript and compiled:
.claude-plugin/
├── plugin.json # Plugin manifest
├── session-start.js # Compiled from session-start.ts
└── src/
└── session-start.ts # TypeScript source
// .claude-plugin/plugin.json
{
"name": "@nextsystems/oac",
"version": "0.7.1",
"hooks": {
"session-start": "node session-start.js"
}
}
Cursor uses a single .cursorrules file (100KB limit). OAC generates this file from installed agents/context:
oac compat apply --ide=cursor
# Generates .cursorrules with router pattern:
# - Lists available agent personas
# - Includes abbreviated context
# - Stays under 100KB limit
Windsurf uses .windsurfrules. Adapter is functionally complete; needs CLI wiring only.
The compatibility adapters exist but have NO CLI. This must be added:
oac compat apply --ide=cursor # Generate cursor-specific files
oac compat apply --ide=claude # Generate CLAUDE.md etc.
oac compat apply --ide=windsurf # Generate .windsurfrules
oac compat apply --all # Apply all compatible IDEs
oac compat status # Show compatibility status per IDE
oac compat validate --ide=cursor # Validate generated files
oac plugin install <name> # Install plugin from registry
oac plugin update [name] # Update plugin(s)
oac plugin remove <name> # Remove plugin
oac plugin list # List installed plugins
oac plugin configure <name> # Configure plugin settings
oac plugin create # Scaffold new plugin
registry.json exists at the repo root (106KB). It appears to be a flat JSON file. The exact format and whether it follows a published schema is unclear. No community contribution workflow exists. No lockfile system exists.
A shadcn-inspired registry with:
oac.lock lockfile for reproducible installs// registry.json
{
"version": "1",
"registryUrl": "https://registry.nextsystems.dev/oac",
"items": [
{
"name": "openagent",
"type": "oac:agent",
"version": "2.1.0",
"description": "Primary orchestration agent for plan-first development",
"tags": ["orchestration", "planning", "primary"],
"author": "nextsystems",
"license": "MIT",
"files": [
{
"path": "agent.json",
"target": ".opencode/agent/openagent/agent.json",
"url": "https://registry.nextsystems.dev/oac/agents/openagent/2.1.0/agent.json",
"sha256": "a1b2c3d4..."
},
{
"path": "prompt.md",
"target": ".opencode/agent/openagent/prompt.md",
"url": "https://registry.nextsystems.dev/oac/agents/openagent/2.1.0/prompt.md",
"sha256": "e5f6a7b8..."
}
],
"dependencies": [],
"peerDependencies": ["context7"]
},
{
"name": "task-management",
"type": "oac:skill",
"version": "1.2.0",
"files": [
{
"path": "SKILL.md",
"target": ".opencode/skills/task-management/SKILL.md",
"url": "...",
"sha256": "..."
},
{
"path": "router.sh",
"target": ".opencode/skills/task-management/router.sh",
"url": "...",
"sha256": "..."
},
{
"path": "scripts/task-cli.js",
"target": ".opencode/skills/task-management/scripts/task-cli.js",
"url": "...",
"sha256": "..."
}
]
}
]
}
oac.lock Format// oac.lock (committed to git — enables reproducible installs)
{
"version": "1",
"generatedAt": "2026-02-19T00:00:00Z",
"oacVersion": "0.7.1",
"installed": {
"agents": {
"openagent": {
"version": "2.1.0",
"source": "registry",
"sha256": {
"agent.json": "a1b2c3d4...",
"prompt.md": "e5f6a7b8..."
},
"installedAt": "2026-02-19T00:00:00Z",
"userModified": {
"agent.json": false,
"prompt.md": true
}
}
},
"skills": {
"task-management": {
"version": "1.2.0",
"source": "registry",
"sha256": { ... },
"installedAt": "..."
}
},
"context": {
"typescript-patterns.md": {
"version": "1.0.3",
"source": "bundled",
"sha256": "...",
"installedAt": "..."
}
}
}
}
.opencode/agent/
├── openagent/ # OAC-managed (tracked in oac.lock)
├── task-manager/ # OAC-managed
└── custom/ # USER-OWNED (never touched by oac update)
└── my-custom-agent/
OAC never modifies files in .opencode/agent/custom/ or .opencode/context/custom/. This is the clean separation between OAC-managed and user-owned content.
oac browse # TUI browser of registry
oac search <query> # Search registry
oac publish <path> # Publish to community registry
oac registry add <url> # Add custom registry source
oac registry list # List configured registries
Week 1-2: Foundation & Package Structure
Week 3: Context System
Week 4: Agent & Skill Management
Week 5: Plugin System (OpenCode primary)
Week 6: Compatibility Layer CLI
Week 7: Registry & Lockfile
Week 8: Auto-Update & Community
Week 9: Polish, Doctor, Testing
Goal: Working CLI binary with package structure. oac --help works and shows all commands.
What Gets Built:
Create packages/cli/ package
tsconfig.json, package.json with all dependenciestsup.config.ts for buildsrc/index.ts with commander.js skeletonRewrite bin/oac.js
#!/usr/bin/env node
require('../packages/cli/dist/index.js');
Add packages/cli to root workspaces in package.json
Configure tsup to compile task-cli.ts → task-cli.js
ts-node dependency from skillsrouter.sh references to use compiled task-cli.jsSet up vitest for testing across all packages
Create ~/.config/oac/config.json initialization in oac init
Dependencies: None (this is the foundation everything else builds on)
Validation Criteria:
oac --help lists all planned commands with descriptionsoac --version outputs correct versionnpx @nextsystems/oac init runs without errortask-cli.js executes correctly without ts-nodeGoal: oac init sets up a project correctly. oac doctor diagnoses problems.
What Gets Built:
oac init command (interactive wizard)
? Which IDE are you using? (OpenCode / Claude Code / Cursor / Windsurf / Multiple)
? Install standard agent pack? (Yes / No / Select)
? Enable auto-updates? (Auto-safe / Auto-all / Manual)
? Create .oac/config.json? (Yes)
.oac/config.json (project config)oac.lock (empty initially).opencode/manifest.json copy in project.gitignore (sessions, tmp)oac doctor command
task-cli.js is compiled (not .ts)router.sh is not a stubValidation Criteria:
oac init completes in < 30 seconds on first runoac doctor correctly identifies a deliberately broken installoac doctor --fix auto-repairs fixable issuesGoal: Full context system with 6-layer resolution and CLI management.
What Gets Built:
ContextResolver class implementing 6-layer resolutionoac context list — shows all context with source layeroac context resolve <name> — shows which layer winsoac context install <name> — installs specific context fileoac context update — updates all OAC-managed contextoac context validate — validates syntaxoac context diff <name> — diff vs stock versionContext Resolver Implementation:
// packages/cli/src/resolvers/context.ts
const RESOLUTION_LAYERS = [
{ name: "project-override", path: ".oac/context", userOwned: true },
{ name: "opencode-config", path: ".opencode/context", userOwned: false },
{ name: "ide-specific", path: ".cursor/context", userOwned: false },
{ name: "project-docs", path: "docs", userOwned: true },
{ name: "user-global", path: "~/.config/oac/context", userOwned: true },
{ name: "npm-bundled", path: "__dirname/../../.opencode/context", userOwned: false },
];
export async function resolveContext(name: string): Promise<ResolvedContext> {
for (const layer of RESOLUTION_LAYERS) {
const filePath = join(layer.path, name);
if (await exists(filePath)) {
return { filePath, layer, userOwned: layer.userOwned };
}
}
throw new Error(`Context file not found: ${name}`);
}
Validation Criteria:
oac context list shows correct source layer for each fileoac context resolve output matches manual file system inspectionGoal: Full agent and skill lifecycle management. Preset system working.
What Gets Built:
agent.json schema with Zod validationoac add agent <name> — install from registryoac remove agent <name> — remove with confirmationoac list agents — tabular view with versionsoac customize agent <name> — opens editor, saves to presetoac presets list/apply — preset managementoac validate agents — schema validationoac skill install/list/update/remove/validateAgent Schema (Zod):
// packages/cli/src/schemas/agent.ts
const AgentSchema = z.object({
name: z.string().regex(/^[a-z][a-z0-9-]*$/),
displayName: z.string(),
version: z.string(),
description: z.string(),
model: z.string().optional(),
maxTokens: z.number().optional(),
permission: z.array(z.union([
z.object({ allow: z.string() }),
z.object({ deny: z.string() }),
])).optional(),
tools: z.array(z.string()).optional(),
tags: z.array(z.string()).default([]),
oac: z.object({
bundledSha256: z.string(),
installedAt: z.string(),
source: z.enum(["registry", "bundled", "local"]),
presetApplied: z.string().optional(),
}).optional(),
});
context-manager router.sh (critical gap fix):
#!/usr/bin/env bash
# router.sh — context-manager skill dispatcher
set -euo pipefail
COMMAND="${1:-help}"
SCRIPTS_DIR="$(dirname "$0")/scripts"
case "$COMMAND" in
discover) node "$SCRIPTS_DIR/discover.js" "${@:2}" ;;
fetch) node "$SCRIPTS_DIR/fetch.js" "${@:2}" ;;
harvest) node "$SCRIPTS_DIR/harvest.js" "${@:2}" ;;
extract) node "$SCRIPTS_DIR/extract.js" "${@:2}" ;;
compress) node "$SCRIPTS_DIR/compress.js" "${@:2}" ;;
organize) node "$SCRIPTS_DIR/organize.js" "${@:2}" ;;
cleanup) node "$SCRIPTS_DIR/cleanup.js" "${@:2}" ;;
workflow) node "$SCRIPTS_DIR/workflow.js" "${@:2}" ;;
*) echo "Usage: router.sh <discover|fetch|harvest|extract|compress|organize|cleanup|workflow>"; exit 1 ;;
esac
Validation Criteria:
oac add agent openagent installs correctly and updates oac.lockoac customize agent openagent opens editor and saves presetoac update, customizations surviveoac validate agents catches malformed agent.jsonrouter.sh dispatches all 8 commands correctlyGoal: OAC registers as an OpenCode npm plugin. Auto-update works on session start.
What Gets Built:
packages/plugin-abilities/src/index.ts as a proper OpenCode pluginsession.created event handler with update check logic.opencode/opencode.json as "plugin": ["@nextsystems/oac"]oac session list/cleanOpenCode Plugin Registration:
// .opencode/opencode.json (updated)
{
"plugin": ["@nextsystems/oac"],
"model": "claude-sonnet-4-5",
"theme": "opencode"
}
Session Created Handler:
// packages/plugin-abilities/src/events/session.ts
import { checkForUpdates, applySafeUpdates } from "../updater";
import { readLockfile } from "../lockfile";
export async function handleSessionCreated(ctx: SessionContext) {
try {
const lockfile = await readLockfile();
const updates = await checkForUpdates(lockfile);
if (updates.safe.length > 0) {
await applySafeUpdates(updates.safe);
// Silent — don't interrupt the user's session
}
if (updates.conflicts.length > 0 && !lockfile.config.suppressConflictWarnings) {
ctx.log.warn(
`OAC: ${updates.conflicts.length} components have updates but local modifications. ` +
`Run 'oac update --interactive' to manage.`
);
}
} catch (err) {
// Never crash the user's session due to OAC errors
ctx.log.debug(`OAC update check failed: ${err.message}`);
}
}
Validation Criteria:
@nextsystems/oac plugin without errorsession.created fires and completes without affecting session start time > 500msopencode.jsonGoal: All existing compatibility adapters (Claude, Cursor, Windsurf) wired to CLI commands.
What Gets Built:
oac compat apply --ide=<ide> command wiring to existing adaptersoac compat status — show what's generated for each IDEoac compat validate --ide=<ide> — validate generated filessession-start.sh → session-start.ts → compile to session-start.jsCompat Apply Implementation:
// packages/cli/src/commands/compat.ts
import { ClaudeAdapter } from "@nextsystems/oac-compatibility-layer";
import { CursorAdapter } from "@nextsystems/oac-compatibility-layer";
import { WindsurfAdapter } from "@nextsystems/oac-compatibility-layer";
export const compatApplyCommand = new Command("apply")
.option("--ide <ide>", "target IDE (claude|cursor|windsurf|all)")
.option("--dry-run", "show what would be generated without writing")
.action(async (options) => {
const agents = await loadInstalledAgents();
const context = await loadInstalledContext();
const adapters = resolveAdapters(options.ide);
for (const adapter of adapters) {
const output = await adapter.generate({ agents, context });
if (!options.dryRun) {
await adapter.write(output);
}
console.log(`Generated ${adapter.name} files`);
}
});
Validation Criteria:
oac compat apply --all runs without error with a fresh installsession-start.js runs without ts-nodeoac compat status correctly shows missing/present/outdated stateGoal: Full registry client with lockfile. oac add fetches from registry and records in lock.
What Gets Built:
oac.lock read/write with atomic updatesoac add fetches from registry, verifies hash, writes to disk, updates lockoac remove removes files and updates lockoac browse TUI browser of registry itemsoac search <query> search registryRegistry Client:
// packages/cli/src/registry/client.ts
export class RegistryClient {
async fetch(name: string, type: OACItemType): Promise<RegistryItem> {
const url = `${this.baseUrl}/${type}s/${name}`;
const item = await fetch(url).then(r => r.json());
return RegistryItemSchema.parse(item);
}
async download(item: RegistryItem): Promise<DownloadedFiles> {
const files = await Promise.all(
item.files.map(async (f) => {
const content = await fetch(f.url).then(r => r.text());
const sha256 = await computeSHA256(content);
if (sha256 !== f.sha256) {
throw new Error(`SHA256 mismatch for ${f.path}: expected ${f.sha256}, got ${sha256}`);
}
return { ...f, content };
})
);
return files;
}
}
Atomic Lockfile Updates:
// packages/cli/src/registry/lockfile.ts
export async function updateLockfile(
lockfilePath: string,
updates: LockfileUpdate[]
): Promise<void> {
const tmpPath = lockfilePath + ".tmp";
const lock = await readLockfile(lockfilePath);
for (const update of updates) {
applyUpdate(lock, update);
}
await writeFile(tmpPath, JSON.stringify(lock, null, 2));
await rename(tmpPath, lockfilePath); // Atomic rename
}
Validation Criteria:
oac add agent openagent downloads, verifies SHA256, installs, updates oac.lockoac.lock is valid JSON after every operationoac remove agent openagent removes files and updates oac.lockGoal: Background update checking, conflict resolution, community publish workflow.
What Gets Built:
update-notifier integration for CLI binary updatesoac update with --interactive conflict resolutionoac rollback <component> to previous versionoac publish workflow for community contributionsmanual / auto-safe / auto-all / lockedUpdate Modes:
type UpdateMode =
| "manual" // Never auto-update, always prompt
| "auto-safe" // Auto-update files with no local modifications
| "auto-all" // Auto-update everything, backup modifications
| "locked" // Never update, lock.json is authoritative
Rollback:
// oac rollback openagent
// Reads previous version from lock history
// Fetches from registry at pinned version
// Restores files + updates lock
Validation Criteria:
update-notifier shows update message when newer CLI version availableoac update --interactive correctly shows diffs and prompts for each conflictoac rollback openagent restores previous versionauto-safe correctly skips user-modified filesauto-all creates backup before overwritingGoal: Production-ready package with comprehensive test coverage and documentation.
What Gets Built:
oac doctor covers all known failure modessession.created handlerinstall.sh with migration noticeTest Coverage Targets:
packages/cli/src/Validation Criteria:
oac init completes in < 2 minutes on fresh machinesession.created handler adds < 500ms to session startoac doctor passes on a correct installRationale: OpenCode offers a TypeScript plugin SDK with 25+ lifecycle events, custom tool registration, and npm-based distribution. This is the richest integration model available among the four IDEs. By targeting OpenCode first, we get:
session.created)Claude Code, Cursor, and Windsurf are adaptation layers — we write once for OpenCode and adapt for others.
Rationale: Context files are needed the moment an AI session starts. A network fetch at that moment creates:
By bundling context into the npm package, we get zero-latency access, offline functionality, and version-locked reproducibility. The manifest.json + SHA256 system handles update detection separately from file delivery.
agent.json + prompt.md SeparationRationale: Separating metadata from prose has three benefits:
agent.json changes (model, permissions) are clearly separated from prompt content changes — easier to review in PRsprompt.md frontmatter contains only fields OpenCode reads natively. OAC metadata in agent.json doesn't pollute the frontmatteragent.json fields without touching prompt.md, and vice versaRationale: shadcn demonstrated that a file-copy registry model is superior to package-per-component for configuration files. Benefits:
registry.jsonThe oac.lock lockfile extends this pattern with version pinning and reproducibility.
During the 9-week transition:
install.sh continues to work but shows a deprecation noticebin/oac.js is backward compatible — no arguments → runs equivalent of old behaviorinstall.sh remain valid; oac doctor can adopt them into oac.lockoac init --import scans existing .opencode/ and creates oac.lock from discovered filesThese are gaps that will break functionality if not addressed before launch. Ordered by priority.
Current state: router.sh in .opencode/skills/context-manager/ exists but dispatches nothing.
Impact: The context-manager skill is completely non-functional.
Fix: Implement full dispatch logic to 8 subcommands (see Phase 4 above).
Owner: Phase 4, Week 4.
Current state: task-cli.ts is invoked directly via ts-node in router.sh.
Impact: Breaks in any environment without ts-node (most production setups).
Fix: Compile task-cli.ts → task-cli.js via tsup in the build pipeline. Update router.sh to call node task-cli.js.
Owner: Phase 1, Week 1.
Current state: packages/compatibility-layer/ adapters are functionally complete but have zero CLI exposure.
Impact: Multi-IDE users cannot generate Claude/Cursor/Windsurf files via oac commands.
Fix: Wire adapters to oac compat apply command (Phase 6, Week 6).
Owner: Phase 6, Week 6.
Current state: packages/plugin-abilities/ has event handler stubs but is not registered in opencode.json as a proper plugin.
Impact: Auto-update via session.created does not fire.
Fix: Complete plugin-abilities implementation and add to opencode.json plugin array.
Owner: Phase 5, Week 5.
Current state: Installed components are not tracked. No reproducibility.
Impact: Teams cannot share exact installs. Updates cannot detect user modifications.
Fix: Implement oac.lock with atomic read/write (Phase 7, Week 7).
Owner: Phase 7, Week 7.
Current state: bin/oac.js is 91 lines that spawns install.sh.
Impact: Everything depends on this being fixed.
Fix: Create packages/cli/ and rewrite bin/oac.js (Phase 1, Week 1-2).
Owner: Phase 1, Week 1.
Current state: No tests exist anywhere in the codebase.
Impact: Regressions will go undetected. Cannot validate fixes.
Fix: Set up vitest in Phase 1, write tests progressively through Phases 2-9.
Owner: Phase 1 (setup) + ongoing.
~/.config/oac/config.json{
"$schema": "https://registry.nextsystems.dev/oac/schemas/global-config.json",
"version": "1",
"defaults": {
"ide": "opencode",
"updateMode": "auto-safe",
"registry": "https://registry.nextsystems.dev/oac",
"telemetry": false
},
"presets": {
"default": "~/.config/oac/presets/default.json"
},
"auth": {
"registryToken": null
}
}
updateMode values:
"manual" — Never auto-update. Run oac update explicitly."auto-safe" — Auto-update only files matching stock SHA256 (default)."auto-all" — Auto-update everything. Creates .oac/backups/ before overwriting."locked" — Never update. oac.lock is authoritative..oac/config.json{
"$schema": "https://registry.nextsystems.dev/oac/schemas/project-config.json",
"version": "1",
"project": {
"name": "my-project",
"ide": "opencode",
"updateMode": "auto-safe"
},
"agents": {
"enabled": ["openagent", "task-manager", "contextscout"],
"disabled": []
},
"context": {
"enabled": ["typescript-patterns", "git-workflow"],
"disabled": []
},
"skills": {
"enabled": ["task-management", "context-manager", "context7"],
"disabled": []
},
"compatibility": {
"cursor": { "enabled": true, "autoRegenerate": true },
"claude": { "enabled": true, "autoRegenerate": false },
"windsurf": { "enabled": false }
}
}
oac.lock Format{
"$schema": "https://registry.nextsystems.dev/oac/schemas/lockfile.json",
"version": "1",
"generatedAt": "2026-02-19T00:00:00Z",
"oacVersion": "0.7.1",
"installed": {
"agents": {
"<name>": {
"version": "<semver>",
"source": "registry | bundled | local",
"registryUrl": "<url>",
"sha256": {
"<filename>": "<sha256>"
},
"installedAt": "<iso8601>",
"updatedAt": "<iso8601>",
"userModified": {
"<filename>": true | false
},
"presetApplied": "<preset-name> | null"
}
},
"skills": { "<same shape>" },
"context": { "<same shape>" },
"plugins": { "<same shape>" }
},
"history": [
{
"action": "add | update | remove | rollback",
"component": "<type>/<name>",
"fromVersion": "<semver> | null",
"toVersion": "<semver>",
"timestamp": "<iso8601>"
}
]
}
manifest.json{
"$schema": "https://registry.nextsystems.dev/oac/schemas/manifest.json",
"version": "1",
"packageVersion": "0.7.1",
"generatedAt": "2026-02-19T00:00:00Z",
"agents": {
"<name>": {
"version": "<semver>",
"description": "<string>",
"files": {
"<filename>": {
"sha256": "<sha256>",
"size": 4821
}
}
}
},
"skills": { "<same shape>" },
"context": {
"<filename>": {
"version": "<semver>",
"sha256": "<sha256>",
"size": 2341,
"category": "language | workflow | tooling | project"
}
}
}
Layer 1: CLI Binary Updates (via update-notifier)
// packages/cli/src/index.ts
import updateNotifier from "update-notifier";
import packageJson from "../../package.json";
const notifier = updateNotifier({
pkg: packageJson,
updateCheckInterval: 1000 * 60 * 60 * 24, // Check daily
});
// Non-blocking: shows notification at end of command
notifier.notify();
Layer 2: Content File Updates (hash-based registry polling)
Content update check flow:
1. Read oac.lock (installed versions + sha256)
2. Fetch registry manifest (latest versions + sha256)
3. For each installed component:
a. Compare installed version vs registry version (semver)
b. If newer version available:
i. Compute SHA256 of file on disk
ii. Compare to oac.lock sha256 for that file
MATCH → file is stock OAC → SAFE TO UPDATE
DIFFER → file has user modifications → RESPECT updateMode
4. Apply updates according to updateMode
5. Update oac.lock
The SHA256 stored in oac.lock is the hash of the file as installed from the registry, not the current file on disk. This allows detection of user modifications:
async function detectModification(
installedPath: string,
lockEntry: LockfileEntry
): Promise<boolean> {
const currentContent = await readFile(installedPath);
const currentSha256 = await sha256(currentContent);
const installedSha256 = lockEntry.sha256[basename(installedPath)];
return currentSha256 !== installedSha256;
}
OAC-MANAGED (tracked in oac.lock, updated by oac update):
.opencode/agent/<name>/ (standard agents)
.opencode/context/<name>.md (standard context)
.opencode/skills/<name>/ (standard skills)
USER-OWNED (never touched by oac update):
.opencode/agent/custom/ (user's custom agents)
.opencode/context/custom/ (user's custom context)
.oac/context/ (project overrides, highest priority)
docs/ (project documentation)
~/.config/oac/presets/ (user presets)
| Mode | Behavior | Best For |
|---|---|---|
manual |
Never updates automatically. Must run oac update |
Teams with strict change control |
auto-safe |
Updates only files with SHA256 matching oac.lock (default) | Solo developers, most teams |
auto-all |
Updates everything; backs up modified files to .oac/backups/ |
Users who want always-latest |
locked |
Ignores registry. oac.lock is authoritative. | CI/CD, reproducible builds |
// Runs on every session.created — must be fast
export async function handleSessionCreated(ctx: SessionContext) {
const updateMode = await getUpdateMode();
if (updateMode === "locked") return; // Fast exit for locked mode
const check = await checkUpdatesWithTimeout(5000); // 5s timeout
if (!check) return; // Network unavailable — skip silently
if (check.safeUpdates.length > 0 && updateMode !== "manual") {
await applySafeUpdates(check.safeUpdates); // Unmodified files
}
if (check.conflicts.length > 0) {
ctx.log.info(`OAC: ${check.conflicts.length} updates available (run 'oac update')`);
}
}
oac init [--ide <ide>] [--no-agents] [--no-context] [--yolo]
Initialize OAC in the current project. Interactive wizard by default.
Creates: .oac/config.json, oac.lock, copies bundled content
oac add <type> <name> [--version <semver>] [--yolo]
Install a component from the registry.
Types: agent, skill, context, plugin
Example: oac add agent openagent
oac remove <type> <name> [--yolo]
Remove an installed component.
Example: oac remove agent openagent
oac update [type] [name] [--interactive] [--dry-run] [--yolo]
Update installed components. Without args, updates all safe components.
--interactive: prompts for each conflict
--dry-run: shows what would be updated without making changes
oac list [type] [--format table|json]
List installed components with versions and modification status.
Example: oac list agents
oac doctor [--fix]
Diagnose installation issues. --fix auto-repairs fixable issues.
oac rollback <type> <name> [--version <semver>]
Rollback a component to a previous version.
Example: oac rollback agent openagent --version 2.0.0
oac context install <name>
Install a specific context file from the registry.
oac context update [name]
Update context file(s). Without name, updates all safe context.
oac context list [--format table|json]
List installed context files with source layer.
oac context resolve <name>
Show which resolution layer provides a context file.
oac context validate [name]
Validate context file syntax and frontmatter.
oac context diff <name>
Show diff between installed file and stock (registry) version.
oac add agent <name> # Install from registry
oac remove agent <name> # Remove agent
oac list agents # List with versions and status
oac customize agent <name> # Open editor, save as preset
oac validate agents [name] # Validate agent.json schema
oac create agent # Interactive creation wizard
oac show agent <name> # Show agent configuration
oac presets list # List available presets
oac presets apply <preset> [agents...] # Apply preset to agents
oac presets create <name> # Create preset from current customizations
oac presets remove <name> # Delete preset
oac skill install <name> # Install skill from registry
oac skill update [name] # Update skill(s)
oac skill remove <name> # Remove skill
oac skill list # List installed skills
oac skill validate [name] # Validate SKILL.md and router.sh
oac skill run <name> <command> # Run skill command directly
oac task status # Show current task session
oac task next # Get next task from session
oac task complete <id> # Mark task as complete
oac task session list # List task sessions
oac task session clean [--older-than <days>] # Clean old sessions
oac plugin install <name> # Install plugin from registry
oac plugin update [name] # Update plugin(s)
oac plugin remove <name> # Remove plugin
oac plugin list # List installed plugins
oac plugin configure <name> # Configure plugin settings
oac plugin create # Scaffold new plugin
oac compat apply [--ide <ide>] [--all] [--dry-run]
Generate IDE-specific files from installed agents/context.
IDEs: cursor, claude, windsurf
Example: oac compat apply --ide cursor
oac compat status
Show compatibility file status for each supported IDE.
oac compat validate [--ide <ide>]
Validate generated compatibility files.
oac compat clean [--ide <ide>]
Remove generated compatibility files.
oac browse [type] # TUI browser of registry
oac search <query> [--type <type>] # Search registry
oac publish <path> # Publish component to community registry
oac registry add <url> # Add custom registry source
oac registry remove <url> # Remove custom registry
oac registry list # List configured registries
oac registry status # Check registry connectivity
oac configure # Interactive config editor
oac configure get <key> # Get config value
oac configure set <key> <value> # Set config value
oac configure reset # Reset to defaults
oac show [type] [name] # Show details of installed component
oac edit [type] [name] # Open component in $EDITOR
--yolo # Skip all confirmations
--no-color # Disable color output
--quiet # Minimal output
--verbose # Verbose output
--debug # Debug output with stack traces
--json # Output as JSON (machine-readable)
--config <path> # Use alternate config file
--registry <url> # Use alternate registry URL
| Metric | Target | Measurement Method |
|---|---|---|
| Test coverage | ≥ 80% on packages/cli/src/ |
vitest coverage |
| Build time | < 30 seconds for full build | time npm run build |
| CLI startup time | < 200ms for oac --version |
time oac --version |
oac init time |
< 2 minutes on fresh machine | Manual timing |
session.created overhead |
< 500ms | OpenCode plugin timing |
| Bundle size | < 5MB total npm package | npm pack --dry-run |
| TypeScript errors | 0 errors on tsc --noEmit |
CI check |
| Zero bash dependencies | oac commands work without bash |
Test on fresh Windows VM |
| Metric | Target | Measurement Method |
|---|---|---|
| Setup time (solo dev) | < 2 minutes from npx @nextsystems/oac init to working |
User testing |
| Update friction | Zero prompts in auto-safe mode |
Automated test |
| Customization survival rate | 100% presets survive oac update |
Integration test |
oac doctor detection rate |
100% of documented failure modes | Test against each failure mode |
| Rollback reliability | oac rollback succeeds 100% if oac.lock is present |
Integration test |
| Metric | Target |
|---|---|
| npm weekly downloads | > 1,000 |
| Community registry contributions | > 10 agents/skills submitted |
| GitHub issues: "broken install" | < 5% of total issues |
oac doctor --fix auto-repair success rate |
> 90% |
| Multi-IDE users | > 20% of users run oac compat apply |
| Metric | Target |
|---|---|
install.sh usage |
< 20% of installs by Week 9 |
Users successfully migrated via oac init --import |
> 80% |
| Regressions reported after migration | 0 P0/P1 bugs |
Agents: .opencode/agent/<name>/agent.json + prompt.md
Skills: .opencode/skills/<name>/SKILL.md + router.sh + scripts/*.js
Context: .opencode/context/<name>.md
Plugins: packages/plugin-abilities/ (OpenCode) | .claude-plugin/ (Claude Code)
Config: .oac/config.json (project) | ~/.config/oac/config.json (global)
Lock: oac.lock (project root, commit to git)
Manifest: manifest.json (project root, commit to git)
Backups: .oac/backups/<timestamp>/<component>/ (git-ignored)
Sessions: .tmp/sessions/ (git-ignored)
Presets: ~/.config/oac/presets/<name>.json (survives npm updates)
| Package | Purpose | Why Not Alternative |
|---|---|---|
commander |
CLI framework | Battle-tested, good TypeScript support. Yargs has more complex API. |
@inquirer/prompts |
Interactive prompts | Official Inquirer v9 rewrite, modular. Better than enquirer |
ora |
Spinners | Standard, well-maintained |
chalk |
Colors | Standard, ESM-compatible v5 |
conf |
Global config persistence | OS-correct paths, handles JSON schema |
fs-extra |
File operations | Adds copy, ensureDir, readJSON etc. Better than raw fs |
semver |
Version comparison | npm's own semver library, authoritative |
zod |
Schema validation | Best TypeScript DX, composable schemas |
update-notifier |
CLI update notifications | Non-blocking background check |
tsup |
Build tool | Bundles deps, fast, simple config |
vitest |
Testing | Fast, native ESM, compatible with tsup |
# Old workflow:
curl -fsSL https://install.nextsystems.dev/oac | bash
# New workflow (v1.0):
npx @nextsystems/oac init
# For existing users:
oac init --import # Scans .opencode/ and creates oac.lock
oac doctor # Validates the import
oac compat apply --all # Regenerates IDE-specific files
# install.sh shows deprecation notice but continues to work:
echo "DEPRECATED: install.sh will be removed in v2.0. Run: npx @nextsystems/oac init"
Document status: Authoritative master plan. All implementation decisions should reference this document. Update this document when architectural decisions change.
Next action: Begin Phase 1 — create packages/cli/ package structure and rewrite bin/oac.js.