import type { Plugin } from '@opencode-ai/plugin';
import { tool } from '@opencode-ai/plugin';
import { existsSync } from 'fs';
import fs from 'fs/promises';
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;
status: FeatureStatus;
lastSyncedTrunk?: 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;
trunk?: string;
remote?: string;
workspaceRoot?: string;
agent?: string;
message?: 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';
function nowIso(): string {
return new Date().toISOString();
}
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;
return {
root,
trunkChannel: parsed.trunkChannel ?? DEFAULT_TRUNK_CHANNEL,
remote: parsed.remote,
features: parsed.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 runCommand(command: string, args: string[], cwd: string): Promise<{ exitCode: number; stdout: string; stderr: string }> {
const proc = Bun.spawn([command, ...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 pijulDiffIsClean(cwd: string): Promise<boolean> {
const result = await runCommand('pijul', ['diff'], cwd);
return result.exitCode === 0 && result.stdout.length === 0 && result.stderr.length === 0;
}
function formatFeatureState(feature: FeatureState): string {
return `${feature.feature} (${feature.status}) - channel=${feature.channel} workspace=${feature.workspace}`;
}
export const AnarchyPlugin: Plugin = async () => {
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(),
trunk: z.string().optional(),
remote: z.string().optional(),
workspaceRoot: z.string().optional(),
agent: z.string().optional(),
message: z.string().optional(),
},
async execute(args: PijulManagerArgs) {
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 state = await readState(root);
if (action === 'init') {
state.trunkChannel = args.trunk?.trim() || state.trunkChannel || DEFAULT_TRUNK_CHANNEL;
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 channels.';
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') {
if (!args.feature) return 'Missing feature name.';
const featureKey = slugify(args.feature);
if (!featureKey) return 'Invalid feature name.';
const channel = `feature/${featureKey}`;
const stateDir = path.join(root, STATE_DIR_NAME);
const workspaceRoot = args.workspaceRoot
? path.resolve(args.workspaceRoot)
: path.join(stateDir, 'workspaces');
const workspace = path.join(workspaceRoot, featureKey);
await ensureDir(workspaceRoot);
if (await pathExists(workspace)) {
state.features[featureKey] = {
feature: featureKey,
channel,
workspace,
agent: args.agent,
status: 'active',
lastSyncedTrunk: state.features[featureKey]?.lastSyncedTrunk,
lastPush: state.features[featureKey]?.lastPush,
};
await writeState(root, state);
return `Workspace already exists at ${workspace}.`;
}
const cloneResult = await runCommand('pijul', ['clone', root, workspace, '--no-prompt'], root);
if (cloneResult.exitCode !== 0) {
return `Failed to clone repo: ${cloneResult.stderr || cloneResult.stdout}`;
}
const channelResult = await runCommand('pijul', ['channel', 'new', channel, '--no-prompt'], workspace);
if (channelResult.exitCode !== 0) {
const combined = `${channelResult.stdout}\n${channelResult.stderr}`.toLowerCase();
if (!combined.includes('already') && !combined.includes('exists')) {
return `Failed to create channel: ${channelResult.stderr || channelResult.stdout}`;
}
}
const switchResult = await runCommand('pijul', ['channel', 'switch', channel, '--no-prompt'], workspace);
if (switchResult.exitCode !== 0) {
return `Failed to switch channel: ${switchResult.stderr || switchResult.stdout}`;
}
state.features[featureKey] = {
feature: featureKey,
channel,
workspace,
agent: args.agent,
status: 'active',
};
await writeState(root, state);
return `Spawned ${featureKey} in ${workspace} on ${channel}.`;
}
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.';
const pullResult = await runCommand(
'pijul',
['pull', root, '--from-channel', state.trunkChannel, '-a', '--no-prompt'],
feature.workspace,
);
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.';
const message = args.message?.trim() || `WIP: ${featureKey}`;
const addResult = await runCommand(
'pijul',
['add', '--recursive', '--no-prompt', '.'],
feature.workspace,
);
if (addResult.exitCode !== 0) {
return `Failed to add files: ${addResult.stderr || addResult.stdout}`;
}
const recordResult = await runCommand(
'pijul',
['record', '-a', '-m', message, '--no-prompt'],
feature.workspace,
);
if (recordResult.exitCode !== 0) {
return `Failed to record changes: ${recordResult.stderr || recordResult.stdout}`;
}
feature.status = 'ready';
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.';
const syncResult = await runCommand(
'pijul',
['pull', root, '--from-channel', state.trunkChannel, '-a', '--no-prompt'],
feature.workspace,
);
if (syncResult.exitCode !== 0) {
return `Failed to sync trunk before push: ${syncResult.stderr || syncResult.stdout}`;
}
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 switchResult = await runCommand('pijul', ['channel', 'switch', state.trunkChannel, '--no-prompt'], root);
if (switchResult.exitCode !== 0) {
return `Failed to switch trunk channel: ${switchResult.stderr || switchResult.stdout}`;
}
const pullResult = await runCommand(
'pijul',
['pull', feature.workspace, '--from-channel', feature.channel, '-a', '--no-prompt'],
root,
);
if (pullResult.exitCode !== 0) {
return `Failed to pull feature changes into trunk: ${pullResult.stderr || pullResult.stdout}`;
}
const trunkAfterPullClean = await pijulDiffIsClean(root);
if (!trunkAfterPullClean) {
feature.status = 'blocked';
await writeState(root, state);
return 'Trunk has unresolved changes after pull; resolve conflicts in trunk then retry push.';
}
if (state.remote) {
const pushResult = await runCommand(
'pijul',
['push', state.remote, '--from-channel', state.trunkChannel, '-a', '--no-prompt'],
root,
);
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 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,
};
}
},
};
};