OpenWriterControle/docs/planning/12-MASTER-SYNTHESIS.md
Darren Hinde d2a633d7dc
feat(cli): Bun-first CLI migration with 7 commands and comprehensive test suite (#259)
* feat(cli): migrate packages/cli to Bun-first with full test suite

- Replace fs-extra with Bun.file/Bun.write/node:fs/promises across all 9 source files
- Fix --help fast-path so all 8 subcommands are visible
- Replace tsup/tsx/vitest with bun build/bun run/bun:test
- Replace __dirname with import.meta.dir; use JSON import assertion for version
- Fix hardcoded OAC_VERSION in add.ts; add hashesMatch import to status.ts
- Replace computeFileHash with Bun.file().bytes() in sha256.ts
- Rename checkNodeVersion → checkBunVersion; parallelise doctor checks
- Fix TypeScript void-inference errors in update.ts, list.ts, status.ts
  by replacing .catch() with let/try-catch where result is used downstream
- Add ManifestError named error class; remove unsafe type casts
- Add 43 bun:test unit tests (sha256, manifest, installer, version) — 0 failures

* fix(cli): resolve critical bugs and standards violations found in review

Critical bug fixes:
- ide-detect.ts: replace Bun.file(dir).exists() with stat().isDirectory()
  for all directory checks — Bun.file().exists() always returns false for
  directories, breaking Cursor/Windsurf/OpenCode/Claude detection entirely
- bundled.ts: add registry.json exclusion anchor to findPackageRoot() to
  prevent monorepo root from matching before the CLI package root

Standards cleanup (§4.1, §5.1, §6.1, §15.3, §21.1):
- status.ts: parallelise findModifiedFiles + detectIdes with Promise.all
- apply.ts: fix duplicate warn/limit messages; remove else after return in
  reportWarnings; remove redundant mkdir before Bun.write
- version.ts: remove unnecessary (pkgJson as {version?:string}) cast
- manifest.ts: remove redundant mkdir before Bun.write; remove as unknown cast
- installer.ts: remove redundant mkdir calls before Bun.write throughout
- add.ts: add comment explaining why node:fs/promises rm is used

Tests (43 → 142, +99 new tests across 4 new files):
- ide-detect.test.ts: 26 tests covering all 4 IDEs, both claude indicators,
  detectIdes parallel, isIdePresent — directly validates the directory fix
- bundled.test.ts: 27 tests for classifyBundledFile, findPackageRoot,
  listBundledFiles, getBundledFilePath, bundledFileExists
- config.test.ts: 25 tests for readConfig/writeConfig round-trips,
  createDefaultConfig, mergeConfig, isYoloMode, isAutoBackup
- installer-update.test.ts: 14 tests covering all 5 updateFiles decision
  branches (install/update/skip/yolo/dry-run) plus isProjectRoot
- sha256.test.ts: +4 tests for empty file, large file, binary content

* fix(cli): fix P0 bugs — global flags, .git detection, package root resolution

- Remove duplicate --dry-run/--yolo/--verbose from parent program; Commander.js
  global option stealing caused all safety flags to be silently dropped in every
  subcommand action callback
- Fix isProjectRoot() to use stat() for .git detection; Bun.file().exists()
  returns false for directories, breaking oac init in standard git repos
- Add OAC_PACKAGE_ROOT env var override to getPackageRoot() for dev/monorepo mode;
  registry.json heuristic excluded the repo root causing oac init/add/update to
  throw when run from source
- Fix build script: remove --banner flag that caused double shebang in dist/index.js
- Rewrite bin/oac.js to invoke bun instead of node (dist is bun-only target)
- Refactor installer.ts let accumulators to const using Promise.all + reduce
- Add MVP planning docs (00-MVP-PLAN.md, master synthesis, project breakdown)

* fix(install.sh): handle both singular and plural component type formats

The get_registry_key() function was always adding 's' to the type,
causing 'contexts' to become 'contextss' which doesn't exist in registry.json.

Now handles:
- Singular forms: context → contexts, agent → agents, skill → skills
- Plural forms: contexts → contexts (unchanged), agents → agents
- Config stays singular
- Fallback for any type ending in 's'

Fixes #257
2026-03-24 21:54:03 +00:00

1875 lines
65 KiB
Markdown

# OAC Package Refactor — Master Planning Document
**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)
---
## Table of Contents
1. [Executive Summary](#1-executive-summary)
2. [Architecture Overview](#2-architecture-overview)
3. [The 5 Core Subsystems](#3-the-5-core-subsystems)
4. [Implementation Phases (9 Weeks)](#4-implementation-phases-9-weeks)
5. [Key Technical Decisions](#5-key-technical-decisions)
6. [Critical Gaps to Address](#6-critical-gaps-to-address)
7. [Configuration Schema](#7-configuration-schema)
8. [Auto-Update Strategy](#8-auto-update-strategy)
9. [CLI Command Reference](#9-cli-command-reference)
10. [Success Metrics](#10-success-metrics)
---
## 1. Executive Summary
### What We Are Building
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 |
### Key Architectural Decisions (Summary)
1. **OpenCode is the primary target** — richest plugin API (25+ events, custom tools, TypeScript SDK). All other IDEs are adaptation layers.
2. **Bundle context into npm, do not fetch at runtime** — eliminates network dependency during AI sessions, enables offline use, provides version-locked reproducibility.
3. **`agent.json` + `prompt.md` as source of truth** — clean separation of metadata/configuration from prose content; generate IDE-specific formats from this canonical form.
4. **shadcn-inspired registry** — adapt the shadcn component registry pattern for agents, skills, and context files. Already have `registry.json` in the repo.
5. **`oac.lock` lockfile** — reproducible installs across machines and CI, analogous to `package-lock.json`.
6. **Two-layer update system** — `update-notifier` for the CLI binary itself; hash-based registry polling for content files.
7. **Monorepo with tsup** — `packages/cli` (new) + `packages/compatibility-layer` (existing) + `packages/plugin-abilities` (existing).
### Success Criteria
- `npx @nextsystems/oac init` completes full project setup in under 2 minutes
- All bundled content survives `npm update` without overwriting user customizations
- OpenCode auto-updates agent/context files on every session start via `session.created` event
- `oac doctor` catches 100% of common misconfiguration issues
- Registry supports community-published agents/skills with SHA256 verification
- Zero bash script dependency for any core functionality (install.sh becomes legacy/deprecated)
---
## 2. Architecture Overview
### Package Structure (Monorepo)
```
@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
```
### The 5 Core Subsystems and Their Relationships
```
┌─────────────────────────────────────────────────────────────────┐
│ 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 │ └─────────────┘
└──────────────┘
```
### Data Flow: From Registry to IDE
```
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)
```
### Data Flow: Auto-Update via OpenCode Plugin
```
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
```
---
## 3. The 5 Core Subsystems
---
### Subsystem 1: CLI & Package Distribution
#### Current State
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.
#### Target State
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.
#### Package Structure
```json
// 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"
}
}
```
#### Commander.js Structure
```typescript
// 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();
```
#### Key Design Decisions
- **tsup over tsc**: tsup bundles dependencies into a single file, eliminating runtime `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 cases
- **`conf` for global config**: Uses OS-appropriate config directory (`~/.config/oac/` on Linux/Mac, `%APPDATA%/oac/` on Windows). Survives npm updates
- **`update-notifier`**: Background process checks npm registry for CLI updates; shows non-blocking notification at session end
#### Critical Gaps
- The entire `packages/cli/` package does not exist yet — needs to be created from scratch
- `bin/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.0
---
### Subsystem 2: Context System
#### Current State
Context 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.
#### Target State
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`.
#### The 6-Layer Priority Resolution
```
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:
1. Walk layers 1-6 in order
2. First file found at a given logical name wins
3. User-owned layers (1, 4, 5) are never overwritten by `oac update`
4. OAC-managed layer (2) is updated only when no user modifications detected (SHA256 match)
#### Bundle/Manifest Approach
```json
// 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"]
}
}
}
```
#### Auto-Update Mechanism
```
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
```
#### CLI Commands
```bash
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
```
#### Key Design Decisions
- **Bundle into npm, not fetch at runtime**: Context files are needed the moment an AI session starts. Network dependency at that point is unacceptable. Bundling ensures offline functionality and version-locked reproducibility.
- **ContextScout navigation**: The existing ContextScout agent that discovers relevant context via navigation is preserved. The CLI manages the underlying files; ContextScout operates on them.
- **`manifest.json` is the authoritative truth**: Neither `package.json` version nor git history determines what's installed — only `manifest.json` + SHA256 comparison.
---
### Subsystem 3: Agent & Skill Management
#### Current State
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.
#### Target State
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 Architecture: `agent.json` + `prompt.md`
```
.opencode/agent/openagent/
├── agent.json # Metadata, permissions, config, OAC-specific data
└── prompt.md # The actual agent prompt content (prose)
```
```json
// 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"
}
}
```
```markdown
---
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.
#### Permission System
The `permission:` field uses **last-match-wins** evaluation (same as OpenCode's native system):
```json
"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.
#### Preset System
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:
1. Stock `agent.json` + `prompt.md` are updated from registry
2. Preset is re-applied on top of updated stock
3. Final merged files written to `.opencode/agent/`
4. User never loses customizations
#### Essential Agents
| 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 |
#### Skill Packaging
```
.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
```
```yaml
# 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/**"
---
```
#### Four Core Skills
| 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 |
#### CLI Commands
```bash
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
```
---
### Subsystem 4: Plugin System
#### Current State
**Two existing plugin systems**:
1. **Claude Code** (`.claude-plugin/`): File-based plugin using `session-start.sh` bash script. Functional but bash-only.
2. **OpenCode** (`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.
#### Target State
OAC becomes a first-class OpenCode npm plugin registered in `opencode.json`:
```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 Plugin (PRIMARY)
OpenCode offers the richest integration API:
- 25+ lifecycle events (session.created, file.changed, tool.before, tool.after, etc.)
- Custom tool registration
- TypeScript SDK with full type safety
- npm-based distribution (no file copying required)
```typescript
// 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;
```
```typescript
// 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);
}
```
#### Claude Code Plugin (Secondary)
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
```
```json
// .claude-plugin/plugin.json
{
"name": "@nextsystems/oac",
"version": "0.7.1",
"hooks": {
"session-start": "node session-start.js"
}
}
```
#### Cursor Integration
Cursor uses a single `.cursorrules` file (100KB limit). OAC generates this file from installed agents/context:
```bash
oac compat apply --ide=cursor
# Generates .cursorrules with router pattern:
# - Lists available agent personas
# - Includes abbreviated context
# - Stays under 100KB limit
```
#### Windsurf Integration
Windsurf uses `.windsurfrules`. Adapter is functionally complete; needs CLI wiring only.
#### Compatibility Layer CLI (Missing — High Priority)
The compatibility adapters exist but have NO CLI. This must be added:
```bash
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
```
#### Plugin CLI Commands
```bash
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
```
---
### Subsystem 5: Registry & Community
#### Current State
`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.
#### Target State
A shadcn-inspired registry with:
- Typed component entries with SHA256 verification
- `oac.lock` lockfile for reproducible installs
- Community contribution via PR workflow
- Security scanning on all community contributions
#### Registry Item Format
```json
// 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
```json
// 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": "..."
}
}
}
}
```
#### Directory Ownership (oh-my-zsh Pattern)
```
.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.
#### CLI Commands
```bash
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
```
---
## 4. Implementation Phases (9 Weeks)
### Phase Overview
```
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
```
---
### Phase 1: Foundation (Week 1-2)
**Goal**: Working CLI binary with package structure. `oac --help` works and shows all commands.
**What Gets Built**:
1. Create `packages/cli/` package
- `tsconfig.json`, `package.json` with all dependencies
- `tsup.config.ts` for build
- `src/index.ts` with commander.js skeleton
- All command files as stubs (return "not yet implemented")
2. Rewrite `bin/oac.js`
```javascript
#!/usr/bin/env node
require('../packages/cli/dist/index.js');
```
3. Add `packages/cli` to root `workspaces` in `package.json`
4. Configure `tsup` to compile `task-cli.ts` → `task-cli.js`
- Remove `ts-node` dependency from skills
- Update `router.sh` references to use compiled `task-cli.js`
5. Set up `vitest` for testing across all packages
6. 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 descriptions
- `oac --version` outputs correct version
- `npx @nextsystems/oac init` runs without error
- All packages build with zero TypeScript errors
- `task-cli.js` executes correctly without `ts-node`
---
### Phase 2: Init & Doctor (Week 2)
**Goal**: `oac init` sets up a project correctly. `oac doctor` diagnoses problems.
**What Gets Built**:
1. `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)
```
- Creates `.oac/config.json` (project config)
- Creates `oac.lock` (empty initially)
- Copies bundled content to `.opencode/`
- Creates `manifest.json` copy in project
- Adds entries to `.gitignore` (sessions, tmp)
2. `oac doctor` command
- Checks: OAC version vs latest
- Checks: oac.lock exists and is valid
- Checks: all agents in oac.lock are present on disk
- Checks: all SHA256s match (detects corruption)
- Checks: IDE-specific config is correct
- Checks: `task-cli.js` is compiled (not `.ts`)
- Checks: context-manager `router.sh` is not a stub
- Reports: pass/warn/fail per check
**Validation Criteria**:
- `oac init` completes in < 30 seconds on first run
- `oac doctor` correctly identifies a deliberately broken install
- `oac doctor --fix` auto-repairs fixable issues
---
### Phase 3: Context System (Week 3)
**Goal**: Full context system with 6-layer resolution and CLI management.
**What Gets Built**:
1. `ContextResolver` class implementing 6-layer resolution
2. `oac context list` — shows all context with source layer
3. `oac context resolve <name>` — shows which layer wins
4. `oac context install <name>` — installs specific context file
5. `oac context update` — updates all OAC-managed context
6. `oac context validate` — validates syntax
7. `oac context diff <name>` — diff vs stock version
**Context Resolver Implementation**:
```typescript
// 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 file
- Updating a user-modified file is blocked/warned
- `oac context resolve` output matches manual file system inspection
---
### Phase 4: Agent & Skill Management (Week 4)
**Goal**: Full agent and skill lifecycle management. Preset system working.
**What Gets Built**:
1. `agent.json` schema with Zod validation
2. `oac add agent <name>` — install from registry
3. `oac remove agent <name>` — remove with confirmation
4. `oac list agents` — tabular view with versions
5. `oac customize agent <name>` — opens editor, saves to preset
6. `oac presets list/apply` — preset management
7. `oac validate agents` — schema validation
8. **Fix context-manager router.sh stub** — implement full dispatch logic
9. `oac skill install/list/update/remove/validate`
**Agent Schema (Zod)**:
```typescript
// 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):
```bash
#!/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.lock`
- `oac customize agent openagent` opens editor and saves preset
- After `oac update`, customizations survive
- `oac validate agents` catches malformed `agent.json`
- context-manager `router.sh` dispatches all 8 commands correctly
---
### Phase 5: OpenCode Plugin (Week 5)
**Goal**: OAC registers as an OpenCode npm plugin. Auto-update works on session start.
**What Gets Built**:
1. Complete `packages/plugin-abilities/src/index.ts` as a proper OpenCode plugin
2. `session.created` event handler with update check logic
3. Register in `.opencode/opencode.json` as `"plugin": ["@nextsystems/oac"]`
4. Session management CLI: `oac session list/clean`
**OpenCode Plugin Registration**:
```json
// .opencode/opencode.json (updated)
{
"plugin": ["@nextsystems/oac"],
"model": "claude-sonnet-4-5",
"theme": "opencode"
}
```
**Session Created Handler**:
```typescript
// 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**:
- OpenCode loads `@nextsystems/oac` plugin without error
- `session.created` fires and completes without affecting session start time > 500ms
- Auto-update correctly identifies modified files and skips them
- Plugin unloads cleanly when removed from `opencode.json`
---
### Phase 6: Compatibility Layer CLI (Week 6)
**Goal**: All existing compatibility adapters (Claude, Cursor, Windsurf) wired to CLI commands.
**What Gets Built**:
1. `oac compat apply --ide=<ide>` command wiring to existing adapters
2. `oac compat status` — show what's generated for each IDE
3. `oac compat validate --ide=<ide>` — validate generated files
4. Claude Code plugin: rewrite `session-start.sh` → `session-start.ts` → compile to `session-start.js`
5. Cursor: enforce 100KB limit in generator, warn if exceeded
**Compat Apply Implementation**:
```typescript
// 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 install
- Cursor output stays under 100KB
- Claude Code `session-start.js` runs without `ts-node`
- `oac compat status` correctly shows missing/present/outdated state
---
### Phase 7: Registry & Lockfile (Week 7)
**Goal**: Full registry client with lockfile. `oac add` fetches from registry and records in lock.
**What Gets Built**:
1. Registry client with SHA256 verification
2. `oac.lock` read/write with atomic updates
3. `oac add` fetches from registry, verifies hash, writes to disk, updates lock
4. `oac remove` removes files and updates lock
5. `oac browse` TUI browser of registry items
6. `oac search <query>` search registry
**Registry Client**:
```typescript
// 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**:
```typescript
// 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.lock`
- Corrupted download (wrong SHA256) is rejected with clear error
- `oac.lock` is valid JSON after every operation
- `oac remove agent openagent` removes files and updates `oac.lock`
---
### Phase 8: Auto-Update & Community (Week 8)
**Goal**: Background update checking, conflict resolution, community publish workflow.
**What Gets Built**:
1. `update-notifier` integration for CLI binary updates
2. Registry polling with configurable interval
3. `oac update` with `--interactive` conflict resolution
4. `oac rollback <component>` to previous version
5. `oac publish` workflow for community contributions
6. Update modes: `manual` / `auto-safe` / `auto-all` / `locked`
**Update Modes**:
```typescript
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**:
```typescript
// 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 available
- `oac update --interactive` correctly shows diffs and prompts for each conflict
- `oac rollback openagent` restores previous version
- Update mode `auto-safe` correctly skips user-modified files
- Update mode `auto-all` creates backup before overwriting
---
### Phase 9: Polish & Testing (Week 9)
**Goal**: Production-ready package with comprehensive test coverage and documentation.
**What Gets Built**:
1. Unit tests for all core functionality (vitest)
2. Integration tests for CLI commands
3. End-to-end test: fresh install → configure → update cycle
4. `oac doctor` covers all known failure modes
5. Performance: measure and optimize `session.created` handler
6. README and getting-started guide updates
7. Deprecation of `install.sh` with migration notice
**Test Coverage Targets**:
- Unit tests: ≥ 80% coverage on `packages/cli/src/`
- Integration tests: all 20+ commands
- E2E test: full install/update/rollback cycle
**Validation Criteria**:
- All tests pass on macOS, Linux, Windows (via GitHub Actions)
- `oac init` completes in < 2 minutes on fresh machine
- `session.created` handler adds < 500ms to session start
- `oac doctor` passes on a correct install
---
## 5. Key Technical Decisions
### Decision 1: OpenCode as Primary Target
**Rationale**: 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:
- Auto-update on every session start (via `session.created`)
- Type-safe plugin development
- Zero file-copying for plugin distribution (npm handles it)
- Future access to new OpenCode capabilities as they're released
Claude Code, Cursor, and Windsurf are adaptation layers — we write once for OpenCode and adapt for others.
### Decision 2: Bundle Context into npm (Not Fetch at Runtime)
**Rationale**: Context files are needed the moment an AI session starts. A network fetch at that moment creates:
- Latency (adds delay to every session)
- Network dependency (fails offline, in CI, on slow connections)
- Version drift (server updates mid-session)
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.
### Decision 3: `agent.json` + `prompt.md` Separation
**Rationale**: Separating metadata from prose has three benefits:
1. **Diffability**: `agent.json` changes (model, permissions) are clearly separated from prompt content changes — easier to review in PRs
2. **IDE compatibility**: `prompt.md` frontmatter contains only fields OpenCode reads natively. OAC metadata in `agent.json` doesn't pollute the frontmatter
3. **Preset application**: Presets can override specific `agent.json` fields without touching `prompt.md`, and vice versa
### Decision 4: shadcn Registry Pattern
**Rationale**: shadcn demonstrated that a file-copy registry model is superior to package-per-component for configuration files. Benefits:
- Users own their copies — they can modify them freely
- No npm install needed to add an agent/skill/context file
- SHA256 verification provides security without requiring a signing infrastructure
- Community can contribute by PR to `registry.json`
The `oac.lock` lockfile extends this pattern with version pinning and reproducibility.
### Decision 5: Backward Compatibility Strategy
During the 9-week transition:
- `install.sh` continues to work but shows a deprecation notice
- `bin/oac.js` is backward compatible — no arguments → runs equivalent of old behavior
- All files installed by old `install.sh` remain valid; `oac doctor` can adopt them into `oac.lock`
- `oac init --import` scans existing `.opencode/` and creates `oac.lock` from discovered files
---
## 6. Critical Gaps to Address
These are gaps that will break functionality if not addressed before launch. Ordered by priority.
### Gap 1: context-manager router.sh is a STUB (P0)
**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.
### Gap 2: task-cli.ts requires ts-node (P0)
**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.
### Gap 3: Compatibility Layer has no CLI (P1)
**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.
### Gap 4: OpenCode plugin does not exist as a proper plugin (P1)
**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.
### Gap 5: No lockfile system (P1)
**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.
### Gap 6: Full CLI does not exist (P0 — foundation)
**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.
### Gap 7: No test infrastructure (P2)
**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.
---
## 7. Configuration Schema
### Global Config: `~/.config/oac/config.json`
```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.
### Project Config: `.oac/config.json`
```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
```json
{
"$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>"
}
]
}
```
### OAC Component Manifest: `manifest.json`
```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"
}
}
}
```
---
## 8. Auto-Update Strategy
### Two-Layer Update System
**Layer 1: CLI Binary Updates** (via `update-notifier`)
```typescript
// 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
```
### Hash-Based Conflict Detection
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:
```typescript
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;
}
```
### User-Owned vs OAC-Managed Files
```
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)
```
### Update Modes in Detail
| 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 |
### Update in OpenCode Plugin
```typescript
// 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')`);
}
}
```
---
## 9. CLI Command Reference
### Core Commands
```bash
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
```
### Context Commands
```bash
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.
```
### Agent Commands
```bash
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
```
### Skill Commands
```bash
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
```
### Plugin Commands
```bash
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
```
### Compatibility Commands
```bash
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.
```
### Registry Commands
```bash
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
```
### Configuration Commands
```bash
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
```
### Global Flags
```bash
--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
```
---
## 10. Success Metrics
### Technical Metrics
| 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 |
### User Experience Metrics
| 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 |
### Community Adoption Metrics (6-month targets)
| 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` |
### Migration Metrics
| 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 |
---
## Appendix A: File Naming Conventions
```
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)
```
## Appendix B: Dependency Rationale
| 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 |
## Appendix C: Migration Path from install.sh
```bash
# 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`.*