import type { Plugin, PluginInput } from '@opencode-ai/plugin';
import { tool } from '@opencode-ai/plugin';
import { existsSync } from 'fs';
import fs from 'fs/promises';
import os from 'os';
import path from 'path';
interface CommandFrontmatter {
description?: string;
agent?: string;
model?: string;
subtask?: boolean;
}
interface ParsedCommand {
name: string;
frontmatter: CommandFrontmatter;
template: string;
}
type FeatureStatus = 'active' | 'blocked' | 'ready' | 'pushed';
interface FeatureState {
feature: string;
channel?: string;
workspace: string;
agent?: string;
agentId?: string;
sessionId?: string;
parentFeature?: string;
parentAgentId?: string;
parentSessionId?: string;
childFeatures?: string[];
depth?: number;
status: FeatureStatus;
lastSyncedTrunk?: string;
lastRecorded?: string;
lastPush?: string;
}
interface ManagerState {
root: string;
trunkChannel: string;
remote?: string;
features: Record<string, FeatureState>;
}
interface PijulManagerArgs {
action: 'init' | 'spawn' | 'sync' | 'record' | 'push' | 'status' | 'close';
feature?: string;
description?: string;
prompt?: string;
spawns?: SpawnRequest[];
parentFeature?: string;
parentAgentId?: string;
parentSessionId?: string;
trunk?: string;
remote?: string;
workspaceRoot?: string;
agent?: string;
agentId?: string;
sessionId?: string;
message?: string;
}
interface SpawnRequest {
feature: string;
description: string;
prompt: string;
agent: string;
parentFeature?: string;
parentAgentId?: string;
parentSessionId?: string;
workspaceRoot?: string;
}
export function parseFrontmatter(content: string): { frontmatter: CommandFrontmatter; body: string } {
const frontmatterRegex = /^---\n([\s\S]*?)\n---\n([\s\S]*)$/;
const match = content.match(frontmatterRegex);
if (!match) {
return { frontmatter: {}, body: content.trim() };
}
const [, yamlContent, body] = match;
const frontmatter: CommandFrontmatter = {};
for (const line of yamlContent.split('\n')) {
const colonIndex = line.indexOf(':');
if (colonIndex === -1) continue;
const key = line.slice(0, colonIndex).trim();
const value = line.slice(colonIndex + 1).trim();
if (key === 'description') frontmatter.description = value;
if (key === 'agent') frontmatter.agent = value;
if (key === 'model') frontmatter.model = value;
if (key === 'subtask') frontmatter.subtask = value === 'true';
}
return { frontmatter, body: body.trim() };
}
export async function loadCommands(): Promise<ParsedCommand[]> {
const commands: ParsedCommand[] = [];
let commandDir = path.join(import.meta.dir, 'command');
if (!existsSync(commandDir)) {
const fallbackDir = path.join(import.meta.dir, '..', 'src', 'command');
if (existsSync(fallbackDir)) {
commandDir = fallbackDir;
} else {
return commands;
}
}
const glob = new Bun.Glob('**/*.md');
for await (const file of glob.scan({ cwd: commandDir, absolute: true })) {
const content = await Bun.file(file).text();
const { frontmatter, body } = parseFrontmatter(content);
const relativePath = path.relative(commandDir, file);
const name = relativePath.replace(/\.md$/, '').replace(/\//g, '-');
commands.push({
name,
frontmatter,
template: body,
});
}
return commands;
}
const STATE_DIR_NAME = '.opencode';
const STATE_FILE_NAME = 'pijul-manager.json';
const DEFAULT_TRUNK_CHANNEL = 'main';
const DEFAULT_FEATURE_DEPTH = 1;
const MAX_FEATURE_DEPTH = 5;
function nowIso(): string {
return new Date().toISOString();
}
function isoAfterOrEqual(a?: string, b?: string): boolean {
if (!a || !b) return false;
return Date.parse(a) >= Date.parse(b);
}
export function slugify(value: string): string {
return value
.trim()
.toLowerCase()
.replace(/[^a-z0-9]+/g, '-')
.replace(/^-+|-+$/g, '')
.slice(0, 64);
}
async function ensureDir(dirPath: string): Promise<void> {
await fs.mkdir(dirPath, { recursive: true });
}
async function pathExists(targetPath: string): Promise<boolean> {
try {
await fs.access(targetPath);
return true;
} catch {
return false;
}
}
async function readState(root: string): Promise<ManagerState> {
const statePath = path.join(root, STATE_DIR_NAME, STATE_FILE_NAME);
if (!(await pathExists(statePath))) {
return {
root,
trunkChannel: DEFAULT_TRUNK_CHANNEL,
features: {},
};
}
const raw = await fs.readFile(statePath, 'utf8');
const parsed = JSON.parse(raw) as ManagerState;
const features: Record<string, FeatureState> = {};
for (const [key, feature] of Object.entries(parsed.features ?? {})) {
features[key] = {
...feature,
agentId: feature.agentId ?? feature.agent,
depth: feature.depth ?? DEFAULT_FEATURE_DEPTH,
childFeatures: feature.childFeatures ?? [],
};
}
return {
root,
trunkChannel: parsed.trunkChannel ?? DEFAULT_TRUNK_CHANNEL,
remote: parsed.remote,
features,
};
}
async function writeState(root: string, state: ManagerState): Promise<void> {
const stateDir = path.join(root, STATE_DIR_NAME);
await ensureDir(stateDir);
const statePath = path.join(stateDir, STATE_FILE_NAME);
await fs.writeFile(statePath, `${JSON.stringify(state, null, 2)}\n`, 'utf8');
}
async function ensurePijulRepo(root: string): Promise<void> {
const pijulDir = path.join(root, '.pijul');
if (!existsSync(pijulDir)) {
throw new Error(`No .pijul directory found in ${root}`);
}
}
async function runPijulCommand(args: string[], cwd: string): Promise<{ exitCode: number; stdout: string; stderr: string }> {
const proc = Bun.spawn(['pijul', ...args], {
cwd,
stdout: 'pipe',
stderr: 'pipe',
});
const stdout = await new Response(proc.stdout).text();
const stderr = await new Response(proc.stderr).text();
const exitCode = await proc.exited;
return { exitCode, stdout: stdout.trim(), stderr: stderr.trim() };
}
async function pijulChannel(root: string): Promise<{ exitCode: number; stdout: string; stderr: string }> {
return runPijulCommand(['channel'], root);
}
async function pijulDiff(cwd: string): Promise<{ exitCode: number; stdout: string; stderr: string }> {
return runPijulCommand(['diff'], cwd);
}
async function pijulCloneRepo(source: string, destination: string, cwd: string): Promise<{ exitCode: number; stdout: string; stderr: string }> {
return runPijulCommand(['clone', source, destination, '--no-prompt'], cwd);
}
async function pijulPullFromChannel(cwd: string, channel: string): Promise<{ exitCode: number; stdout: string; stderr: string }> {
return runPijulCommand(['pull', '--from-channel', channel, '-a', '--no-prompt'], cwd);
}
async function pijulAddAll(cwd: string): Promise<{ exitCode: number; stdout: string; stderr: string }> {
return runPijulCommand(['add', '--recursive', '--no-prompt', '.'], cwd);
}
async function pijulRecordAll(cwd: string, message: string): Promise<{ exitCode: number; stdout: string; stderr: string }> {
return runPijulCommand(['record', '-a', '-m', message, '--no-prompt'], cwd);
}
async function pijulPushToChannel(cwd: string, channel: string): Promise<{ exitCode: number; stdout: string; stderr: string }> {
return runPijulCommand(['push', '--to-channel', channel, '-a', '--no-prompt'], cwd);
}
async function getCurrentPijulChannel(root: string): Promise<string | undefined> {
const result = await pijulChannel(root);
if (result.exitCode !== 0 || !result.stdout) return undefined;
const lines = result.stdout.split('\n');
for (const line of lines) {
const starMatch = line.match(/^\s*\*\s*([^\s()]+)/);
if (starMatch) return starMatch[1];
const currentMatch = line.match(/^\s*([^\s()]+)\s*\(current\)\s*$/i);
if (currentMatch) return currentMatch[1];
}
return undefined;
}
async function pijulDiffIsClean(cwd: string): Promise<boolean> {
const result = await pijulDiff(cwd);
return result.exitCode === 0 && result.stdout.length === 0 && result.stderr.length === 0;
}
async function ensureDefaultRemote(repoPath: string, remote: string): Promise<void> {
const configPath = path.join(repoPath, '.pijul', 'config');
let config = '';
if (await pathExists(configPath)) {
config = await fs.readFile(configPath, 'utf8');
}
const entry = `default_remote = "${remote}"`;
if (config.trim().length === 0) {
await fs.writeFile(configPath, `${entry}\n`, 'utf8');
return;
}
if (config.match(/^default_remote\s*=.*$/m)) {
const updated = config.replace(/^default_remote\s*=.*$/m, entry);
await fs.writeFile(configPath, updated, 'utf8');
return;
}
const suffix = config.endsWith('\n') ? '' : '\n';
await fs.writeFile(configPath, `${config}${suffix}${entry}\n`, 'utf8');
}
function formatFeatureState(feature: FeatureState): string {
const depth = feature.depth ?? DEFAULT_FEATURE_DEPTH;
const parent = feature.parentFeature ?? 'user';
return `${feature.feature} (${feature.status}) depth=${depth} parent=${parent} workspace=${feature.workspace}`;
}
function resolveSpawnRequests(args: PijulManagerArgs): SpawnRequest[] {
if (args.spawns && args.spawns.length > 0) return args.spawns;
if (!args.feature) return [];
if (!args.description || !args.prompt || !args.agent) return [];
return [
{
feature: args.feature,
description: args.description,
prompt: args.prompt,
agent: args.agent,
parentFeature: args.parentFeature,
parentAgentId: args.parentAgentId,
parentSessionId: args.parentSessionId,
workspaceRoot: args.workspaceRoot,
},
];
}
function argsContainPijul(value: unknown): boolean {
if (!value) return false;
if (typeof value === 'string') return /(^|\s)pijul(\s|$)/.test(value);
if (Array.isArray(value)) return value.some((entry) => argsContainPijul(entry));
if (typeof value === 'object') {
return Object.values(value as Record<string, unknown>).some((entry) => argsContainPijul(entry));
}
return false;
}
async function createSubtaskSession(input: {
client: PluginInput['client'];
directory: string;
parentSessionId: string;
agent: string;
title: string;
prompt: string;
}): Promise<string> {
const createResponse = await input.client.session.create({
query: { directory: input.directory },
body: { parentID: input.parentSessionId, title: input.title },
});
const createError = (createResponse as { error?: { message?: string } }).error;
if (!createResponse || createError) {
const error = createError;
throw new Error(error?.message ?? 'Failed to create subtask session.');
}
const session = (createResponse as { data?: { id?: string } }).data;
if (!session?.id) throw new Error('Failed to create subtask session.');
const promptResponse = await input.client.session.prompt({
path: { id: session.id },
query: { directory: input.directory },
body: {
agent: input.agent,
parts: [
{
type: 'text',
text: input.prompt,
},
],
},
});
const promptError = (promptResponse as { error?: { message?: string } }).error;
if (!promptResponse || promptError) {
const error = promptError;
throw new Error(error?.message ?? 'Failed to start subtask session.');
}
return session.id;
}
export const AnarchyPlugin: Plugin = async (input) => {
const commands = await loadCommands();
const z = tool.schema;
const anarchyTool = tool({
description: 'Returns a short anarchy manifesto line.',
args: {},
async execute() {
return 'No gods, no masters.';
},
});
const pijulManagerTool = tool({
description: 'Manage parallel feature work on Pijul repositories.',
args: {
action: z.enum(['init', 'spawn', 'sync', 'record', 'push', 'status', 'close']),
feature: z.string().optional(),
description: z.string().optional(),
prompt: z.string().optional(),
spawns: z
.array(
z.object({
feature: z.string(),
description: z.string(),
prompt: z.string(),
agent: z.string(),
parentFeature: z.string().optional(),
parentAgentId: z.string().optional(),
parentSessionId: z.string().optional(),
workspaceRoot: z.string().optional(),
}),
)
.optional(),
parentFeature: z.string().optional(),
parentAgentId: z.string().optional(),
parentSessionId: z.string().optional(),
trunk: z.string().optional(),
remote: z.string().optional(),
workspaceRoot: z.string().optional(),
agent: z.string().optional(),
agentId: z.string().optional(),
sessionId: z.string().optional(),
message: z.string().optional(),
},
async execute(args: PijulManagerArgs, context) {
const action = args.action;
if (!action) {
return 'Missing action. Expected one of: init, spawn, sync, record, push, status, close.';
}
const root = process.cwd();
await ensurePijulRepo(root);
const statePath = path.join(root, STATE_DIR_NAME, STATE_FILE_NAME);
const hasStateFile = await pathExists(statePath);
const state = await readState(root);
if (action === 'init') {
const providedTrunk = args.trunk?.trim();
let trunk = providedTrunk || (hasStateFile ? state.trunkChannel : undefined);
if (!trunk) trunk = (await getCurrentPijulChannel(root)) || DEFAULT_TRUNK_CHANNEL;
state.trunkChannel = trunk;
if (args.remote) state.remote = args.remote.trim();
await writeState(root, state);
return `Initialized pijul manager. trunk=${state.trunkChannel}${state.remote ? ` remote=${state.remote}` : ''}`;
}
if (action === 'status') {
const features = Object.values(state.features);
if (features.length === 0) return 'No active feature workspaces.';
return features.map(formatFeatureState).join('\n');
}
if (action === 'close') {
const featureKey = args.feature ? slugify(args.feature) : '';
if (!featureKey || !state.features[featureKey]) {
return 'Unknown feature; provide a valid feature name.';
}
state.features[featureKey].status = 'pushed';
await writeState(root, state);
return `Closed feature ${featureKey}.`;
}
if (action === 'spawn') {
const spawnRequests = resolveSpawnRequests(args);
if (spawnRequests.length === 0) {
return 'Missing spawn details. Provide feature + description + prompt + agent, or use spawns[].';
}
const results: Array<{ feature: string; workspace: string; sessionId?: string; status: string }> = [];
const subtaskJobs: Array<{
featureKey: string;
workspace: string;
agent: string;
title: string;
prompt: string;
parentSessionId: string;
}> = [];
for (const request of spawnRequests) {
const featureKey = slugify(request.feature);
if (!featureKey) return 'Invalid feature name.';
if (!request.agent || !request.description || !request.prompt) {
return `Missing subtask details for ${request.feature}; include agent, description, and prompt.`;
}
const parentKey = request.parentFeature ? slugify(request.parentFeature) : undefined;
let parentFeature: FeatureState | undefined;
let depth = DEFAULT_FEATURE_DEPTH;
let parentAgentId = request.parentAgentId ?? args.parentAgentId ?? context.agent;
let parentSessionId = request.parentSessionId ?? args.parentSessionId ?? context.sessionID;
if (parentKey) {
parentFeature = state.features[parentKey];
if (!parentFeature) return 'Unknown parent feature; spawn the parent first.';
depth = (parentFeature.depth ?? DEFAULT_FEATURE_DEPTH) + 1;
if (depth > MAX_FEATURE_DEPTH) {
return `Max feature depth ${MAX_FEATURE_DEPTH} exceeded.`;
}
parentAgentId = parentAgentId ?? parentFeature.agentId ?? parentFeature.agent ?? context.agent;
parentSessionId = parentSessionId ?? parentFeature.sessionId ?? context.sessionID;
}
const workspaceRoot = request.workspaceRoot
? path.resolve(request.workspaceRoot)
: args.workspaceRoot
? path.resolve(args.workspaceRoot)
: path.join(os.tmpdir(), 'opencode-pijul-workspaces');
const workspace = path.join(workspaceRoot, featureKey);
await ensureDir(workspaceRoot);
const existing = state.features[featureKey];
const alreadyExists = await pathExists(workspace);
if (!alreadyExists) {
const cloneResult = await pijulCloneRepo(root, workspace, root);
if (cloneResult.exitCode !== 0) {
return `Failed to clone repo: ${cloneResult.stderr || cloneResult.stdout}`;
}
}
await ensureDefaultRemote(workspace, root);
const sessionId = existing?.sessionId;
state.features[featureKey] = {
feature: featureKey,
workspace,
agent: request.agent,
agentId: existing?.agentId ?? request.agent,
sessionId,
parentFeature: parentKey,
parentAgentId,
parentSessionId,
childFeatures: existing?.childFeatures ?? [],
depth: existing?.depth ?? depth,
status: 'active',
lastSyncedTrunk: existing?.lastSyncedTrunk,
lastRecorded: existing?.lastRecorded,
lastPush: existing?.lastPush,
};
if (parentKey && parentFeature) {
const children = new Set(parentFeature.childFeatures ?? []);
children.add(featureKey);
parentFeature.childFeatures = Array.from(children);
}
results.push({
feature: featureKey,
workspace,
sessionId,
status: alreadyExists ? 'existing' : 'cloned',
});
if (!sessionId) {
subtaskJobs.push({
featureKey,
workspace,
agent: request.agent,
title: request.description,
prompt: `${request.prompt.trim()}\n\nWorkspace: ${workspace}\nFeature: ${featureKey}`,
parentSessionId: parentSessionId ?? context.sessionID,
});
}
}
const subtaskResults = await Promise.allSettled(
subtaskJobs.map((job) =>
createSubtaskSession({
client: input.client,
directory: input.directory,
parentSessionId: job.parentSessionId,
agent: job.agent,
title: job.title,
prompt: job.prompt,
}).then((sessionId) => ({ featureKey: job.featureKey, sessionId })),
),
);
const subtaskErrors: string[] = [];
for (const result of subtaskResults) {
if (result.status === 'fulfilled') {
const featureState = state.features[result.value.featureKey];
if (featureState) featureState.sessionId = result.value.sessionId;
const entry = results.find((item) => item.feature === result.value.featureKey);
if (entry) entry.sessionId = result.value.sessionId;
} else {
subtaskErrors.push(result.reason instanceof Error ? result.reason.message : String(result.reason));
}
}
await writeState(root, state);
const lines = results.map((item) => {
const session = item.sessionId ? ` session=${item.sessionId}` : ' session=pending';
return `${item.feature} ${item.status} workspace=${item.workspace}${session}`;
});
if (subtaskErrors.length > 0) {
lines.push(`Subtask errors: ${subtaskErrors.join(' | ')}`);
}
return lines.join('\n');
}
if (action === 'sync') {
if (!args.feature) return 'Missing feature name.';
const featureKey = slugify(args.feature);
const feature = state.features[featureKey];
if (!feature) return 'Unknown feature; spawn it first.';
await ensureDefaultRemote(feature.workspace, root);
const pullResult = await pijulPullFromChannel(feature.workspace, state.trunkChannel);
if (pullResult.exitCode !== 0) {
return `Failed to pull trunk: ${pullResult.stderr || pullResult.stdout}`;
}
const clean = await pijulDiffIsClean(feature.workspace);
feature.lastSyncedTrunk = nowIso();
feature.status = clean ? 'ready' : 'blocked';
await writeState(root, state);
if (!clean) {
return `Sync completed, but working copy has unresolved changes. Resolve conflicts, record changes, then retry.`;
}
return `Synced ${featureKey} with trunk (${state.trunkChannel}).`;
}
if (action === 'record') {
if (!args.feature) return 'Missing feature name.';
const featureKey = slugify(args.feature);
const feature = state.features[featureKey];
if (!feature) return 'Unknown feature; spawn it first.';
await ensureDefaultRemote(feature.workspace, root);
const message = args.message?.trim() || `WIP: ${featureKey}`;
const addResult = await pijulAddAll(feature.workspace);
if (addResult.exitCode !== 0) {
return `Failed to add files: ${addResult.stderr || addResult.stdout}`;
}
const recordResult = await pijulRecordAll(feature.workspace, message);
if (recordResult.exitCode !== 0) {
return `Failed to record changes: ${recordResult.stderr || recordResult.stdout}`;
}
feature.status = 'ready';
feature.lastRecorded = nowIso();
await writeState(root, state);
return `Recorded changes for ${featureKey}.`;
}
if (action === 'push') {
if (!args.feature) return 'Missing feature name.';
const featureKey = slugify(args.feature);
const feature = state.features[featureKey];
if (!feature) return 'Unknown feature; spawn it first.';
if (!feature.lastSyncedTrunk) {
return 'Sync with trunk required before push; run sync.';
}
if (!feature.lastRecorded) {
return 'Record required before push; run record.';
}
if (!isoAfterOrEqual(feature.lastRecorded, feature.lastSyncedTrunk)) {
return 'Record must happen after sync; re-sync, resolve conflicts, and record again.';
}
const featureClean = await pijulDiffIsClean(feature.workspace);
if (!featureClean) {
feature.status = 'blocked';
await writeState(root, state);
return 'Feature workspace has unrecorded changes; resolve and record before push.';
}
const trunkClean = await pijulDiffIsClean(root);
if (!trunkClean) {
return 'Trunk workspace has unrecorded changes; clean trunk before pushing.';
}
const pushResult = await pijulPushToChannel(feature.workspace, state.trunkChannel);
if (pushResult.exitCode !== 0) {
return `Failed to push trunk: ${pushResult.stderr || pushResult.stdout}`;
}
feature.status = 'pushed';
feature.lastPush = nowIso();
await writeState(root, state);
return `Pushed ${featureKey} to trunk (${state.trunkChannel}).`;
}
return `Unknown action: ${action}`;
},
});
return {
tool: {
anarchy_manifest: anarchyTool,
pijul_manager: pijulManagerTool,
},
async 'tool.execute.before'(input, output) {
if (input.tool !== 'shell' && input.tool !== 'bash') return;
if (!argsContainPijul(output.args)) return;
throw new Error('Direct pijul usage is disabled. Use pijul_manager tool actions instead.');
},
async config(config) {
config.command = config.command ?? {};
for (const cmd of commands) {
config.command[cmd.name] = {
template: cmd.template,
description: cmd.frontmatter.description,
agent: cmd.frontmatter.agent,
model: cmd.frontmatter.model,
subtask: cmd.frontmatter.subtask,
};
}
},
};
};