Document: 13-PROJECT-BREAKDOWN.md
GitHub Issue: #206
Status: Approved for Implementation
Date: 2026-02-19
Based on: 12-MASTER-SYNTHESIS.md + Architecture Review (CodeReviewer)
Architecture Review Key Finding: The synthesis plan is 75% solid. The critical gap is a missing provider/adapter pattern for non-IDE subsystems (context, task management, registry). This must be built in Project 1 before anything else. It enables users to swap out any subsystem — including the context system and task management — with their own implementations.
| # | Project | What It Builds | Depends On |
|---|---|---|---|
| P1 | @nextsystems/oac-core |
Shared interfaces, schemas, provider contracts | Nothing |
| P2 | @nextsystems/oac-cli |
Full CLI (commander.js, all commands, config, lockfile) | P1 |
| P3 | Context System | 6-layer resolver, bundle/manifest, auto-update | P1, P2 |
| P4 | Agent & Skill Management | agent.json+prompt.md, presets, skills packaging | P1, P2 |
| P5 | Plugin System | OpenCode TS plugin, Claude Code rewrite, Cursor/Windsurf | P1, P2, P4 |
| P6 | Registry & Community | shadcn registry, oac.lock, community publishing | P1, P2 |
Projects P3–P6 can be worked in parallel once P1 and P2 are complete.
@nextsystems/oac-core — Shared Interfaces & Provider ContractsPurpose: Zero-dependency package containing all TypeScript interfaces, Zod schemas, and provider contracts. Every other package depends on this. Users implement these interfaces to swap subsystems.
Why first: Without this, schemas diverge between packages (already happening: AgentFrontmatterSchema in compatibility-layer vs planned AgentSchema in CLI). Fixes the dual-source-of-truth problem identified in the review.
These are the interfaces users implement to replace OAC's default subsystems:
// src/providers/context.ts
export interface IContextProvider {
readonly id: string;
readonly displayName: string;
resolve(name: string): Promise<ContextFile | null>;
list(query?: ContextQuery): Promise<ContextFile[]>;
install(name: string, source: string | Buffer): Promise<void>;
update(name: string, newContent: string, expectedSha256: string): Promise<'updated' | 'skipped' | 'conflict'>;
isModified(name: string, installedSha256: string): Promise<boolean>;
validate(name: string): Promise<ValidationResult>;
}
// src/providers/task-management.ts
export interface ITaskManagementProvider {
readonly id: string;
readonly displayName: string;
createSession(tasks: Omit<Task, 'id'>[]): Promise<TaskSession>;
getCurrentSession(): Promise<TaskSession | null>;
getNextTask(sessionId: string): Promise<Task | null>;
completeTask(sessionId: string, taskId: string): Promise<void>;
listSessions(): Promise<TaskSession[]>;
cleanSessions(olderThanDays: number): Promise<number>;
}
// src/providers/registry.ts
export interface IRegistryProvider {
readonly id: string;
readonly displayName: string;
readonly baseUrl: string;
fetch(name: string, type: ComponentType): Promise<RegistryItem>;
search(query: string, options?: RegistrySearchOptions): Promise<RegistryItem[]>;
download(item: RegistryItem): Promise<Array<{ file: RegistryFile; content: string }>>;
ping(): Promise<boolean>;
getLatestVersion(name: string, type: ComponentType): Promise<string>;
}
// src/providers/ide-adapter.ts
export interface IIDEAdapter {
readonly id: string;
readonly displayName: string;
fromOAC(agent: OACAgent, context: OACContext[]): Promise<IDEAdapterResult>;
toOAC(source: string): Promise<OACAgent>;
getOutputPath(): string;
getCapabilities(): IDECapabilities;
validate(output: IDEAdapterResult): ValidationResult;
}
// src/providers/agent-profile.ts
export interface IAgentProfileProvider {
readonly id: string;
readonly displayName: string;
list(): Promise<AgentProfile[]>;
get(name: string): Promise<AgentProfile | null>;
has(name: string): Promise<boolean>;
}
One schema per concept, used by all packages:
// src/schemas/agent.ts — The canonical agent schema
export const AgentConfigSchema = z.object({
name: z.string().regex(/^[a-z][a-z0-9-]*$/),
displayName: z.string(),
version: z.string(),
description: z.string(),
model: z.string().optional(),
temperature: z.number().min(0).max(1).optional(),
maxSteps: z.number().optional(),
mode: z.enum(['primary', 'subagent', 'all']).optional(),
permission: z.array(PermissionRuleSchema).optional(),
tools: z.array(z.string()).optional(),
skills: z.array(z.string()).optional(),
oac: z.object({
bundledSha256: z.string().optional(),
installedAt: z.string().optional(),
source: z.enum(['registry', 'bundled', 'local']),
presetApplied: z.string().optional(),
tags: z.array(z.string()).default([]),
category: z.string().optional(),
}).optional(),
});
// src/schemas/config.ts — Global + project config
// src/schemas/lockfile.ts — oac.lock format
// src/schemas/registry.ts — registry.json + registry item format
// src/schemas/skill.ts — SKILL.md frontmatter
// src/schemas/context.ts — Context file frontmatter
// src/schemas/manifest.ts — manifest.json (npm bundle inventory)
// src/provider-registry.ts
export class ProviderRegistry {
private contextProvider: IContextProvider;
private taskProvider: ITaskManagementProvider;
private registryProviders: Map<string, IRegistryProvider>;
private ideAdapters: Map<string, IIDEAdapter>;
private agentProfileProviders: Map<string, IAgentProfileProvider>;
constructor(config: OACConfig) { /* initialize defaults */ }
async loadFromConfig(config: OACConfig): Promise<void> {
// Dynamically import custom providers from config.providers.*
// Supports: npm package name, local path, or URL (for registry)
}
getContextProvider(): IContextProvider { ... }
getTaskProvider(): ITaskManagementProvider { ... }
getRegistry(name?: string): IRegistryProvider { ... }
getAllRegistries(): IRegistryProvider[] { ... }
getIDEAdapter(id: string): IIDEAdapter | undefined { ... }
}
// src/types/index.ts
export type ComponentType = 'agent' | 'skill' | 'context' | 'plugin';
export type UpdateMode = 'manual' | 'auto-safe' | 'auto-all' | 'locked';
export type ConflictStrategy = 'ask' | 'skip' | 'overwrite' | 'backup' | 'yolo';
export type InstallLocation = 'local' | 'global';
export interface ValidationResult {
valid: boolean;
errors: string[];
warnings: string[];
}
export interface ContextFile {
name: string;
content: string;
path: string;
layer: string;
userOwned: boolean;
sha256: string;
}
export interface OACAgent {
config: AgentConfig;
promptMd: string;
systemMd?: string;
}
// ... all shared types
packages/core/
├── src/
│ ├── providers/
│ │ ├── context.ts # IContextProvider interface
│ │ ├── task-management.ts # ITaskManagementProvider interface
│ │ ├── registry.ts # IRegistryProvider interface
│ │ ├── ide-adapter.ts # IIDEAdapter interface
│ │ └── agent-profile.ts # IAgentProfileProvider interface
│ ├── schemas/
│ │ ├── agent.ts # AgentConfigSchema (Zod)
│ │ ├── config.ts # OACConfigSchema (Zod)
│ │ ├── lockfile.ts # LockfileSchema (Zod)
│ │ ├── registry.ts # RegistrySchema + RegistryItemSchema (Zod)
│ │ ├── skill.ts # SkillFrontmatterSchema (Zod)
│ │ ├── context.ts # ContextFrontmatterSchema (Zod)
│ │ └── manifest.ts # ManifestSchema (Zod)
│ ├── provider-registry.ts # ProviderRegistry class
│ └── types/
│ └── index.ts # All shared TypeScript types
├── package.json # Zero runtime deps (only zod as peer)
└── tsconfig.json
zod as a peer dep)index.ts for clean imports: import { IContextProvider } from '@nextsystems/oac-core'.opencode/context/core/standards/code-quality.md.opencode/context/core/standards/test-coverage.mdProviderRegistry loads custom providers from config.providers.* via dynamic importindex.ts@nextsystems/oac-cli — Full CLI PackagePurpose: The main oac command. Commander.js-based CLI with all commands, config system, lockfile, approval/YOLO system, and wiring to all providers.
Depends on: Project 1 (@nextsystems/oac-core)
// src/index.ts — lazy-loaded commands (keeps oac --version < 50ms)
program
.command('init')
.action(async (...args) => {
const { initCommand } = await import('./commands/init.js');
return initCommand(...args);
});
// All commands use dynamic import — never eager-load
Complete command surface:
| Command | Description |
|---|---|
oac init [profile] |
First-run wizard, project setup |
oac install <ide> [profile] |
Install for specific IDE |
oac add <component> |
Add individual component (agent/skill/context) |
oac update [component] |
Update installed components |
oac remove <component> |
Remove a component |
oac list [type] |
List installed/available components |
oac browse [type] |
Interactive TUI browser |
oac search <query> |
Search registry |
oac configure [subcommand] |
Manage configuration |
oac context <subcommand> |
Context system management |
oac skill <subcommand> |
Skill management |
oac plugin <subcommand> |
Plugin management |
oac doctor |
Diagnose installation health |
oac rollback [component] |
Undo last operation |
oac publish <path> |
Publish to community registry |
oac compat <subcommand> |
IDE compatibility tools |
oac show <component> |
Show component details |
oac presets <subcommand> |
Manage personal presets |
oac registry <subcommand> |
Manage registry sources |
Global flags (all commands):
--yolo Skip all confirmations, auto-resolve conflicts
--dry-run Preview changes without executing
--local Force local install (.opencode/ in CWD)
--global Force global install (~/.config/oac/)
--verbose Detailed output
--quiet Suppress output except errors
// src/config/manager.ts
export class ConfigManager {
// Merges: defaults → global (~/.config/oac/config.json) → project (.oac/config.json)
async load(): Promise<OACConfig>;
async set(keyPath: string, value: unknown, scope: 'global' | 'local'): Promise<void>;
async get(keyPath: string): Promise<unknown>;
async validate(config: unknown): Promise<OACConfig>; // Uses OACConfigSchema from core
}
Config file locations:
~/.config/oac/config.json.oac/config.json (committed to git)OAC_YOLO=true, OAC_REGISTRY_URL=..., OAC_BRANCH=...Config schema (key sections):
{
"version": "1.0.0",
"preferences": {
"defaultIDE": "opencode",
"installLocation": "local",
"yoloMode": false,
"conflictStrategy": "ask",
"autoBackup": true,
"updateMode": "manual"
},
"providers": {
"context": null,
"taskManagement": null,
"registry": null,
"ideAdapters": []
},
"registries": [
{ "name": "official", "url": "https://registry.nextsystems.dev/oac", "priority": 1 }
],
"ides": {
"opencode": { "enabled": true, "path": ".opencode", "profile": "developer" },
"cursor": { "enabled": false, "path": ".cursor", "profile": "developer" },
"claude": { "enabled": false, "path": ".claude", "profile": "developer" }
}
}
// src/approval/manager.ts
export class ApprovalManager {
constructor(private opts: { yolo: boolean; strategy: ConflictStrategy }) {}
// Returns: 'proceed' | 'skip' | 'backup-and-proceed' | 'abort'
async resolveConflict(file: ConflictFile): Promise<ConflictResolution>;
// Batch approval for multiple files
async resolveAll(files: ConflictFile[]): Promise<Map<string, ConflictResolution>>;
// Show diff between existing and new file
async showDiff(existing: string, incoming: string): Promise<void>;
}
Conflict resolution flow:
--yolo → backup all conflicts, overwrite all, report at end// src/lockfile/manager.ts
export class LockfileManager {
// oac.lock format (committed to git)
async read(): Promise<OACLock>;
async write(lock: OACLock): Promise<void>;
async addComponent(component: InstalledComponent): Promise<void>;
async removeComponent(name: string, type: ComponentType): Promise<void>;
async getComponent(name: string, type: ComponentType): Promise<InstalledComponent | null>;
async isModified(name: string, type: ComponentType): Promise<boolean>;
async pruneHistory(maxEntries?: number): Promise<void>; // Prevents unbounded growth
}
oac.lock format:
{
"version": "1",
"oacVersion": "1.0.0",
"generated": "2026-02-19T00:00:00Z",
"installed": {
"opencode": {
"profile": "developer",
"location": "local",
"path": ".opencode",
"components": {
"agent:openagent": {
"version": "1.0.0",
"sha256": "abc123",
"installedAt": "2026-02-19T10:00:00Z",
"source": "https://registry.nextsystems.dev/oac",
"userModified": false
}
}
}
},
"historyPolicy": { "maxEntries": 50 },
"history": [
{ "timestamp": "...", "action": "install", "component": "agent:openagent", "version": "1.0.0" }
]
}
// src/backup/manager.ts
export class BackupManager {
// Backups stored in .oac/backups/ (git-ignored)
async backup(filePath: string): Promise<string>; // Returns backup path
async restore(backupPath: string): Promise<void>;
async listBackups(component?: string): Promise<Backup[]>;
async pruneBackups(maxPerComponent?: number): Promise<void>;
}
oac doctor CommandChecks (in order):
oac.lock in sync with installed filestask-cli.js compiled (not just .ts)context-manager/router.sh is not a stubscripts/
├── generate-manifest.ts # Auto-generate manifest.json from .opencode/context/ files
├── generate-navigation.ts # Auto-generate navigation.md files per category
├── validate-registry.ts # Validate registry.json integrity (already exists, keep)
├── validate-content.ts # Validate all context/agent/skill files
└── add-content.ts # Scaffold new content with correct metadata
Adding a new context file (the simplified workflow):
# Before: manual SHA256, manual manifest.json, manual registry.json
# After:
npm run add-content -- --type context --name typescript-patterns --category development
# → Creates .opencode/context/development/typescript-patterns.md with frontmatter
# → Auto-updates manifest.json with SHA256
# → Auto-updates registry.json
# → Auto-updates navigation.md for that category
packages/cli/
├── src/
│ ├── commands/
│ │ ├── init.ts
│ │ ├── install.ts
│ │ ├── add.ts
│ │ ├── update.ts
│ │ ├── remove.ts
│ │ ├── list.ts
│ │ ├── browse.ts
│ │ ├── search.ts
│ │ ├── configure.ts
│ │ ├── context.ts
│ │ ├── skill.ts
│ │ ├── plugin.ts
│ │ ├── doctor.ts
│ │ ├── rollback.ts
│ │ ├── publish.ts
│ │ ├── compat.ts
│ │ ├── show.ts
│ │ ├── presets.ts
│ │ └── registry.ts
│ ├── config/
│ │ ├── manager.ts
│ │ └── defaults.ts
│ ├── approval/
│ │ ├── manager.ts
│ │ └── strategies.ts
│ ├── lockfile/
│ │ └── manager.ts
│ ├── backup/
│ │ └── manager.ts
│ ├── ui/
│ │ ├── prompts.ts # @inquirer/prompts wrappers
│ │ ├── progress.ts # ora + cli-progress wrappers
│ │ └── logger.ts # chalk + log levels
│ └── index.ts # Commander setup, lazy command loading
├── scripts/
│ ├── generate-manifest.ts
│ ├── generate-navigation.ts
│ ├── validate-content.ts
│ └── add-content.ts
├── package.json
└── tsconfig.json
{
"dependencies": {
"@nextsystems/oac-core": "workspace:*",
"commander": "^12.0.0",
"@inquirer/prompts": "^5.0.0",
"chalk": "^5.3.0",
"ora": "^8.0.1",
"cli-progress": "^3.12.0",
"update-notifier": "^7.3.1",
"conf": "^13.0.0",
"fs-extra": "^11.2.0",
"glob": "^10.3.0",
"gray-matter": "^4.0.3",
"semver": "^7.6.0",
"diff": "^5.2.0"
}
}
oac --version completes in < 50ms (lazy loading)oac init developer --yolo completes in < 2 minutes--dry-run support--yolo flag skips all interactive promptsoac.lock written after every install/update/removeoac.lock history capped at 50 entriesoac doctor catches all 12 defined failure modesconfig.providers.*Purpose: 6-layer context resolution, bundle/manifest management, auto-update, and the oac context * commands. Implements IContextProvider from Project 1.
Depends on: P1 (core interfaces), P2 (CLI commands, config)
// src/providers/default-context-provider.ts
export class DefaultContextProvider implements IContextProvider {
readonly id = 'oac-default';
readonly displayName = 'OAC 6-Layer Context Resolver';
private layers: string[];
private cache: Map<string, ContextFile>; // (filename, mtime) → ContextFile
constructor(config: OACConfig) {
this.layers = this.buildLayers(config);
}
private buildLayers(config: OACConfig): string[] {
const projectRoot = process.cwd();
const home = os.homedir();
const pkgPath = dirname(require.resolve('@nextsystems/oac/package.json'));
return [
join(projectRoot, '.oac/context'), // L1: project override (highest)
join(projectRoot, '.opencode/context'), // L2: project context
join(projectRoot, '.claude/context'), // L3: IDE-specific (claude)
join(projectRoot, '.cursor/context'), // L3: IDE-specific (cursor)
join(projectRoot, 'docs/context'), // L4: project docs
join(home, '.config/oac/context'), // L5: user global
join(pkgPath, 'context'), // L6: OAC bundled (lowest)
];
}
async resolve(name: string): Promise<ContextFile | null> {
// Check cache first (invalidate on mtime change)
for (const [index, basePath] of this.layers.entries()) {
const fullPath = join(basePath, name);
if (await pathExists(fullPath)) {
return this.buildContextFile(fullPath, LAYER_NAMES[index]);
}
}
return null;
}
}
// src/manifest/manager.ts
export class ManifestManager {
// manifest.json = npm package inventory (never modified by users)
async readBundled(): Promise<BundledManifest>; // Reads from npm package
// .opencode/.oac-manifest.json = project install state
async readInstalled(projectRoot: string): Promise<InstalledManifest>;
async writeInstalled(projectRoot: string, manifest: InstalledManifest): Promise<void>;
// Compare to find what needs updating
async computeDrift(projectRoot: string): Promise<DriftReport>;
}
Manifest contract (clarified from review):
manifest.json (in npm package) = what ships in the package — never modified by users, auto-generated by scripts/generate-manifest.ts.opencode/.oac-manifest.json (in project) = what's installed — written by oac context install, read by oac context updateoac.lock = full install state including non-context components// scripts/generate-manifest.ts (runs at npm publish time)
// Scans .opencode/context/ → computes SHA256 for each file → writes manifest.json
// NEVER hand-maintain SHA256s
// scripts/generate-navigation.ts (runs at npm publish time)
// Reads manifest.json → generates navigation.md per category directory
// NEVER hand-maintain navigation.md files
// Written by OpenCode plugin at session.created
// Tells ContextScout which layer each file came from
// .oac/context-resolution-map.json (git-ignored)
{
"generatedAt": "2026-02-19T10:00:00Z",
"resolved": {
"core/standards/code-quality.md": {
"layer": 2,
"layerName": "project-context",
"path": ".opencode/context/core/standards/code-quality.md",
"userModified": false
}
}
}
oac context install # Install from npm bundle (interactive)
oac context install --profile standard # Select profile
oac context install --global # Install to ~/.config/oac/context/
oac context install --ide claude # Install to .claude/context/
oac context install --dry-run # Preview
oac context update # Update from npm bundle (interactive)
oac context update --check # Show what would change
oac context update --yolo # Auto-apply all updates
oac context validate # Full validation report
oac context validate --ci # Exit 1 on failure (for CI)
oac context validate --fix # Auto-fix recoverable issues
oac context list # List all context files
oac context list --tree # As directory tree
oac context resolve <ref> # Show which layer wins
oac context sources # Show all context source directories
oac context override <ref> # Copy to .oac/context/ for customization
oac context add <source> # Add external context (GitHub/local)
oac context diff <ref> # Diff installed vs bundled version
// In manifest.json
{
"profiles": {
"essential": {
"files": ["core/standards/code-quality", "core/standards/documentation", "core/standards/test-coverage"]
},
"standard": {
"extends": "essential",
"files": ["core/workflows/task-delegation-basics", "core/workflows/code-review", "core/standards/security-patterns"]
},
"developer": {
"extends": "standard",
"files": ["development/principles/clean-code", "development/principles/api-design"]
}
}
}
.oac/context/) is the escape hatch — always wins, user-ownedoac context resolve core/standards/code-quality.md shows correct layerscripts/generate-manifest.ts produces correct SHA256sscripts/generate-navigation.ts produces valid navigation.md filesoac context validate --ci exits 1 on broken referencesconfig.providers.contextPurpose: agent.json + prompt.md architecture, preset/customization system, skill packaging, multi-IDE format conversion, and oac add/customize/presets/skill commands.
Depends on: P1 (core interfaces), P2 (CLI, config, lockfile)
Source of truth: agent.json (config) + prompt.md (prose) per agent directory.
.opencode/agents/
├── core/
│ ├── openagent/
│ │ ├── agent.json # Config, permissions, metadata
│ │ └── prompt.md # Prose content (what the AI reads)
│ └── opencoder/
│ ├── agent.json
│ └── prompt.md
└── subagents/
├── contextscout/
│ ├── agent.json
│ └── prompt.md
└── ...
agent.json schema (from @nextsystems/oac-core):
{
"name": "openagent",
"displayName": "OpenAgent",
"version": "1.0.0",
"description": "Universal orchestrator for complex tasks",
"mode": "primary",
"temperature": 0.1,
"permission": [
{ "bash": { "*": "deny", "git status*": "allow" } },
{ "edit": { "**/*.env*": "deny" } }
],
"skills": ["task-management", "context-manager"],
"oac": {
"source": "bundled",
"tags": ["universal", "orchestration"],
"category": "core"
}
}
IDE format generation (from agent.json + prompt.md):
.opencode/agent/core/openagent.md.claude/agents/openagent.md.cursorrules (router pattern).windsurf/agents/openagent.json~/.config/oac/presets/ # User's personal presets (global)
├── agents/
│ ├── my-openagent.md # Preset file with CUSTOMIZATION markers
│ └── strict-reviewer.md
└── .presets.json # Index of presets
.oac/presets/ # Team presets (committed to git)
├── team-lead.json
└── solo-dev.json
Preset file format (merge-safe):
---
preset:
name: my-openagent
base: agent:openagent
baseVersion: 1.0.0
updateStrategy: manual
---
<!-- CUSTOMIZATION: Approval Gates -->
Auto-approve read operations (glob, read, grep)
<!-- END CUSTOMIZATION -->
[Rest of base agent prompt unchanged]
Preset commands:
oac customize agent:openagent # Create preset (wizard)
oac use preset:my-openagent # Activate for project
oac use preset:my-openagent --global # Activate globally
oac presets list # List all presets
oac presets list --active # Show active presets
oac import preset ./team-preset.md # Import team preset
oac export preset:my-openagent # Export for sharing
Skill structure (standardized):
.opencode/skills/{skill-name}/
├── SKILL.md # REQUIRED: frontmatter + instructions
├── router.sh # OPTIONAL: CLI entry point
├── scripts/
│ └── *.js # COMPILED (not .ts) — eliminates ts-node dependency
└── config/
└── *.json
Critical: All TypeScript scripts compiled to JS as part of package build. task-cli.ts → task-cli.js. No ts-node at runtime.
Skill commands:
oac skill install task-management # Install from OAC registry
oac skill install task-management@1.0.0 # Specific version
oac skill install --all # Install all bundled skills
oac skill list # List installed skills
oac skill update # Update all skills
oac skill remove task-management # Remove skill
oac skill validate # Validate all skills
oac skill doctor # Health check (router.sh, scripts exist, etc.)
Critical gap: context-manager/router.sh is currently a stub. Must implement:
.opencode/skills/context-manager/
├── SKILL.md
├── router.sh # Routes to scripts below
└── scripts/
├── discover.js # Glob-based context file discovery
├── fetch.js # Calls ExternalScout / Context7 API
├── harvest.js # Parses source doc, creates permanent context
├── extract.js # Targeted extraction from context files
├── compress.js # Summary/truncation of large files
├── organize.js # File reorganization by concern
├── cleanup.js # Removes stale .tmp/ files
└── process.js # Orchestrates multi-step guided workflows
# Build step: compile task-cli.ts → task-cli.js
# Included in npm package files
# oac doctor checks for task-cli.js presence
oac task commands (wrapping compiled task-cli.js):
oac task status [feature] # Show task status
oac task next [feature] # Show next eligible tasks
oac task complete <feature> <seq> "msg" # Mark complete
oac task validate [feature] # Validate JSON files
oac task plan [feature] --visualize # Show execution plan
oac session commands:
oac session list # List active sessions
oac session resume {session-id} # Resume a session
oac session cleanup {session-id} # Remove session files
oac session archive {session-id} # Archive to .tmp/archive/
agent.json + prompt.md generates valid OpenCode frontmatteragent.json + prompt.md generates valid Claude Code formatagent.json + prompt.md generates valid .cursorrules router<!-- CUSTOMIZATION: --> markers survive agent updatestask-cli.js compiled and included in npm packagecontext-manager/router.sh routes to real implementations (not stub).js (no .ts at runtime)config.providers.taskManagement.oac/presets/ override global presetsPurpose: OpenCode TypeScript plugin (primary), Claude Code plugin rewrite, Cursor/Windsurf adapters, and oac plugin * commands.
Depends on: P1 (core interfaces), P2 (CLI), P4 (agent/skill management)
// .opencode/plugin/oac.ts (installed by oac plugin install opencode)
import type { Plugin } from "@opencode-ai/plugin";
export const OACPlugin: Plugin = async ({ project, client, $, directory }) => {
const config = await loadOACConfig(directory);
const manifest = await loadInstalledManifest(directory);
const skillMap = await buildSkillMap(directory);
return {
// Register OAC agents
config: async (currentConfig) => ({
...currentConfig,
agents: [...(currentConfig.agents || []), ...await loadOACAgents(directory)]
}),
// Skills as tools + tool.execute.before hooks
tool: createSkillTools(skillMap),
// Session start: inject workflow + check updates
"session.created": async ({ event }) => {
// 1. Write context resolution map
await writeContextResolutionMap(directory);
// 2. Inject using-oac workflow (non-blocking)
const workflow = skillMap.get('using-oac')?.content;
if (workflow) {
await client.session.prompt({
path: { id: event.id },
body: { noReply: true, parts: [{ type: "text", text: workflow }] }
});
}
// 3. Check for updates (throttled: once per 24h, non-blocking)
checkForUpdates(directory, client, event.id, config).catch(() => {});
},
// Skill invocation via tool hooks
"tool.execute.before": async (input, output) => {
if (input.tool.startsWith("oac_skill_")) {
const skill = skillMap.get(input.tool);
if (skill) {
await client.session.prompt({
path: { id: input.sessionID },
body: { noReply: true, parts: [{ type: "text", text: skill.content }] }
});
}
}
},
// Background cleanup on session idle
"session.idle": async ({ event }) => {
if (config.cleanup?.autoPrompt) {
await cleanupOldTempFiles(directory, config.cleanup);
}
},
};
};
Auto-update via session.created:
client.tui.showToast()autoUpdate: "safe": silently update non-modified filesautoUpdate: false (default): show toast with oac update hintCurrent: session-start.sh (bash, fragile JSON escaping)
Target: session-start.js (compiled TypeScript, proper JSON.stringify)
// plugins/claude-code/hooks/session-start.ts → compiled to session-start.js
import { readFileSync } from 'fs';
import { join } from 'path';
const skillPath = join(__dirname, '../skills/using-oac/SKILL.md');
const skillContent = readFileSync(skillPath, 'utf-8');
const output = {
additionalContext: skillContent,
hookSpecificOutput: {
type: 'session-start',
message: '🤖 OAC Active — 6-stage workflow enabled'
}
};
process.stdout.write(JSON.stringify(output));
// Generates .cursorrules from all installed agents
// Router pattern: single file, all agents merged with section headers
// 100KB limit enforced with warnings
export class CursorPluginAdapter {
async generate(agents: OACAgent[], contexts: ContextFile[]): Promise<string> {
// Sort: core agents first, then specialists
// Embed essential context inline
// Warn if > 80KB
// Error if > 100KB
}
}
oac plugin install opencode # Install OpenCode TypeScript plugin
oac plugin install claude # Install Claude Code plugin
oac plugin install cursor # Generate .cursorrules
oac plugin install windsurf # Install Windsurf config
oac plugin install --all # Install for all configured IDEs
oac plugin update opencode # Update plugin
oac plugin update --all # Update all plugins
oac plugin update --check # Check only
oac plugin remove opencode # Remove plugin
oac plugin list # List installed plugins
oac plugin status # Health check
oac plugin configure opencode # Configure plugin settings
oac plugin create <name> # Scaffold new plugin
oac plugin test <name> # Test plugin
oac plugin publish <path> # Publish to community
// oac.json — plugin manifest for community plugins
{
"name": "oac-plugin-security-agents",
"version": "1.0.0",
"type": "plugin",
"provides": ["agents", "skills", "context"],
"ides": ["opencode", "claude"],
"registry": "./registry.json"
}
# Install community plugin
oac plugin add oac-plugin-security-agents
oac plugin add https://github.com/user/my-oac-plugin
.opencode/plugin/oac.tssession.created fires and injects workflow within 5 secondssession.created never crashes (silent failure on errors)session-start.js uses JSON.stringify (not manual escaping).cursorrules warns at 80KB, errors at 100KBconfig.providers.ideAdaptersoac plugin status shows health for all installed pluginsPurpose: shadcn-inspired registry, oac.lock lockfile, community publishing, security scanning, and oac publish/browse/search/registry commands.
Depends on: P1 (core interfaces), P2 (CLI, lockfile)
// src/registry/client.ts
export class RegistryClient {
constructor(private providers: IRegistryProvider[]) {}
// Multi-registry resolution: highest priority wins
async fetch(name: string, type: ComponentType): Promise<RegistryItem> {
for (const provider of this.providers) {
try {
return await provider.fetch(name, type);
} catch { continue; }
}
throw new Error(`Component not found: ${type}:${name}`);
}
async search(query: string, options?: RegistrySearchOptions): Promise<RegistryItem[]> {
// Search all registries, deduplicate by name+type, sort by priority
const results = await Promise.allSettled(
this.providers.map(p => p.search(query, options))
);
return deduplicateAndSort(results);
}
}
Registry index (registry.json):
{
"$schema": "https://registry.nextsystems.dev/oac/schema/registry.json",
"version": "3.0.0",
"items": [
{
"name": "openagent",
"type": "oac:agent",
"title": "OpenAgent",
"description": "Universal orchestrator",
"version": "1.0.0",
"ides": ["opencode", "claude", "cursor", "windsurf"],
"registryDependencies": ["contextscout", "task-manager"],
"files": [
{
"path": "agents/core/openagent/agent.json",
"type": "oac:agent-config",
"target": ".opencode/agents/core/openagent/agent.json"
},
{
"path": "agents/core/openagent/prompt.md",
"type": "oac:agent-prompt",
"target": ".opencode/agents/core/openagent/prompt.md"
}
]
}
]
}
// config.json
{
"registries": [
{ "name": "private", "url": "https://registry.company.com/oac", "priority": 1, "authToken": "${OAC_PRIVATE_TOKEN}" },
{ "name": "official", "url": "https://registry.nextsystems.dev/oac", "priority": 2 }
]
}
Resolution: highest priority registry wins. oac add agent:openagent --registry official to override.
oac publish ./my-agent/ # Publish to community registry
# Flow:
# 1. Validate oac.json schema
# 2. Run security scan (secrets detection, malware check)
# 3. Compute SHA256 for all files
# 4. Submit PR to community registry repo
# 5. CI runs security scan
# 6. Maintainer reviews and merges
oac publish ./my-agent/ --private # Publish to private registry
// src/security/scanner.ts
export class SecurityScanner {
async scan(files: string[]): Promise<ScanResult> {
return {
secrets: await this.detectSecrets(files), // gitleaks patterns
malware: await this.scanMalware(files), // basic pattern matching
permissions: await this.analyzePermissions(files), // permission audit
externalCalls: await this.findExternalCalls(files), // network calls
};
}
}
// src/commands/browse.ts — interactive TUI using @inquirer/prompts
// Categories → Components → Preview → Install
// Arrow keys to navigate, Space to select, Enter to preview, 'i' to install
oac browse [type] # Interactive TUI browser
oac search <query> # Search all registries
oac show agent:openagent # Show component details
oac verify agent:openagent # Verify SHA256 + signature
oac registry list # List configured registries
oac registry add <url> [--name <name>] # Add registry
oac registry remove <name> # Remove registry
oac registry ping # Check all registries reachable
oac registry sync # Sync local cache
oac publish <path> # Publish to community
oac publish <path> --registry private # Publish to private registry
oac search works across all configured registriesoac publishoac browse TUI navigable with arrow keysconfig.providers.registryoac.lock updated after every registry operationThis is the key extensibility story. Users configure custom providers in .oac/config.json:
{
"providers": {
"context": "@my-company/oac-notion-context",
"taskManagement": "@my-company/oac-linear-provider",
"registry": "https://registry.internal.company.com/oac",
"ideAdapters": ["@my-company/oac-jetbrains-adapter"]
}
}
Custom context provider (replaces ContextScout + 6-layer resolution):
// @my-company/oac-notion-context
import type { IContextProvider } from '@nextsystems/oac-core';
export default class NotionContextProvider implements IContextProvider {
readonly id = 'notion';
readonly displayName = 'Notion Context Provider';
// Fetches context from Notion database instead of filesystem
}
Custom task management provider (replaces TaskManager/BatchExecutor):
// @my-company/oac-linear-provider
import type { ITaskManagementProvider } from '@nextsystems/oac-core';
export default class LinearTaskProvider implements ITaskManagementProvider {
readonly id = 'linear';
readonly displayName = 'Linear Issue Tracker';
// Creates/reads tasks from Linear API instead of .tmp/tasks/ JSON files
}
Custom IDE adapter (adds JetBrains support):
// @my-company/oac-jetbrains-adapter
import type { IIDEAdapter } from '@nextsystems/oac-core';
export default class JetBrainsAdapter implements IIDEAdapter {
readonly id = 'jetbrains';
readonly displayName = 'JetBrains AI Assistant';
// Converts OAC agents to JetBrains AI Assistant format
}
Week 1-2: P1 (core interfaces) — MUST BE FIRST
Week 3-4: P2 (CLI foundation) — MUST BE SECOND
Week 5-6: P3 + P4 in parallel (context system + agent/skill management)
Week 7: P5 (plugin system) — needs P4 complete
Week 8-9: P6 (registry + community) — can start after P2
P1 ──► P2 ──► P3 (parallel)
P4 (parallel) ──► P5
P6 (parallel)
| Project | Reuse | Rewrite | New |
|---|---|---|---|
| P1 (core) | Types from compatibility-layer | Unify schemas | Provider interfaces |
| P2 (CLI) | bin/oac.js entry point |
Full CLI (91 lines → full commander) | Config, lockfile, approval, backup |
| P3 (context) | .opencode/context/ files |
Context resolution (currently ContextScout only) | Manifest auto-gen, navigation auto-gen |
| P4 (agents/skills) | All .opencode/agent/ files, skills |
task-cli.ts → .js, context-manager stub | agent.json+prompt.md split, preset system |
| P5 (plugins) | Claude Code plugin structure | session-start.sh → .js | OpenCode TS plugin (new) |
| P6 (registry) | registry.json structure, validate-registry.ts |
Registry format (v2→v3) | Multi-registry, community publishing, TUI |
npx @nextsystems/oac init developer completes in < 2 minutesoac --version responds in < 50msoac doctor catches all known failure modesnpm update without overwriting user customizationsoac.lock enables reproducible installs across machines