HCDR3SZH4R7VEAIUVYY3WDA3HQYYWR7GYKJTKOXK2ZUJVYZOZR6AC {"compilerOptions": {"lib": ["ESNext", "DOM"],"target": "ESNext","module": "ESNext","moduleResolution": "bundler","rootDir": "src","outDir": "dist","declaration": true,"declarationMap": true,"sourceMap": true,"strict": true,"skipLibCheck": true,"types": ["bun-types"]},"include": ["src"]}
/// <reference types="bun-types" />import { describe, expect, test } from 'bun:test';import { slugify } from '../../src/index';describe('slugify', () => {test('normalizes and trims', () => {expect(slugify(' Hello World! ')).toBe('hello-world');});test('removes consecutive non-alphanumerics', () => {expect(slugify('Feature__Name--Test')).toBe('feature-name-test');});});
/// <reference types="bun-types" />import { describe, expect, test } from 'bun:test';import { parseFrontmatter } from '../../src/index';describe('parseFrontmatter', () => {test('parses yaml frontmatter and body', () => {const input = `---description: helloagent: test-agentmodel: gpt-testsubtask: true---Body content`;const result = parseFrontmatter(input);expect(result.frontmatter).toEqual({description: 'hello',agent: 'test-agent',model: 'gpt-test',subtask: true,});expect(result.body).toBe('Body content');});test('returns empty frontmatter when missing', () => {const input = 'Just text.';const result = parseFrontmatter(input);expect(result.frontmatter).toEqual({});expect(result.body).toBe('Just text.');});});
/// <reference types="bun-types" />import { describe, expect, test } from 'bun:test';import { loadCommands } from '../../src/index';describe('loadCommands', () => {test('loads command templates from the command directory', async () => {const commands = await loadCommands();const names = commands.map((command) => command.name);expect(commands.length).toBeGreaterThan(0);expect(names).toContain('pijul-init');expect(names).toContain('pijul-sync');const initCommand = commands.find((command) => command.name === 'pijul-init');expect(initCommand?.template).toContain('Call `pijul_manager`');});});
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,};}},};};
---description: Sync a feature workspace with trunk.---You are a feature sub-agent. Sync your workspace with trunk before proceeding.Call `pijul_manager` with:- action: "sync"- feature: the current feature nameIf the tool reports unresolved changes, resolve conflicts, record changes, and retry.
---description: Show all active Pijul feature channels.---Call `pijul_manager` with action "status" and summarize active features, status, and readiness.
---description: Create a feature channel and workspace for a sub-agent.---You are the coordinator. Create a Pijul feature workspace and channel for the requested feature.1. Call the `pijul_manager` tool with action "spawn" and the feature name.2. Share the workspace path and channel with the user.3. Open a subtask for the feature agent that includes:- Goals, constraints, and acceptance criteria from the user.- The rule: run `pijul_manager` action "sync" before "push".- The rule: resolve conflicts locally and keep trunk clean.
---description: Record all current changes for a feature.---You are a feature sub-agent. Record all changes before pushing.Call `pijul_manager` with:- action: "record"- feature: the current feature name- message: a short change summary
---description: Push a feature channel into trunk.---You are a feature sub-agent. Push your ready feature into trunk after syncing.Call `pijul_manager` with:- action: "push"- feature: the current feature nameIf the tool reports unresolved changes, resolve conflicts locally and retry.
---description: Initialize pijul manager settings.---Call `pijul_manager` with action "init".If the user provides a trunk channel or remote, include:- trunk: channel name- remote: remote URL
---description: Send design direction to a feature sub-agent.---Send the following direction to the feature sub-agent:- Feature name- Updated goals or design constraints- Acceptance criteria updates- Files or areas to avoid touchingRemind the sub-agent to sync with trunk before pushing and to resolve conflicts locally.
import fs from 'fs/promises';import path from 'path';import { fileURLToPath } from 'url';const scriptDir = path.dirname(fileURLToPath(import.meta.url));const root = path.resolve(scriptDir, '..');const srcDir = path.join(root, 'src', 'command');const distDir = path.join(root, 'dist', 'command');async function pathExists(targetPath) {try {await fs.access(targetPath);return true;} catch {return false;}}async function main() {if (!(await pathExists(srcDir))) return;await fs.mkdir(distDir, { recursive: true });await fs.cp(srcDir, distDir, { recursive: true });}main().catch((err) => {console.error(err);process.exit(1);});
{"name": "opencode-anarchy","version": "0.1.0","description": "An OpenCode plugin that adds an anarchy-inspired command and tool.","type": "module","main": "./dist/index.js","exports": {".": {"types": "./dist/index.d.ts","default": "./dist/index.js"}},"files": ["dist","src/command"],"scripts": {"build": "tsc -p tsconfig.json && node ./scripts/copy-commands.mjs","check": "tsc -p tsconfig.json --noEmit","copy-commands": "node ./scripts/copy-commands.mjs","test": "bun test"},"dependencies": {"@opencode-ai/plugin": "1.0.85"},"devDependencies": {"@types/node": "^20.11.5","bun-types": "latest","typescript": "^5.8.3"}}
{description = "opencode-anarchy dev environment";inputs = {nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";};outputs ={ self, nixpkgs }:letsystems = ["x86_64-linux""aarch64-linux""x86_64-darwin""aarch64-darwin"];forAllSystems = f: nixpkgs.lib.genAttrs systems (system: f system);in{devShells = forAllSystems (system:letpkgs = import nixpkgs { inherit system; };in{default = pkgs.mkShell {packages = [pkgs.bunpkgs.nodejs_22pkgs.typescript];};});};}
{"lockfileVersion": 1,"configVersion": 1,"workspaces": {"": {"name": "opencode-anarchy","dependencies": {"@opencode-ai/plugin": "1.0.85",},"devDependencies": {"@types/node": "^20.11.5","bun-types": "latest","typescript": "^5.8.3",},},},"packages": {"@opencode-ai/plugin": ["@opencode-ai/plugin@1.0.85", "", { "dependencies": { "@opencode-ai/sdk": "1.0.85", "zod": "4.1.8" } }, "sha512-4uU15occ/rZALIvDk3VwKdZwUZVAcNEcBZNJQKKMX+Lp+sQ/YvHKqawwz1ktOIS2UDsCKh+gyvV8fRCuUBEdKQ=="],"@opencode-ai/sdk": ["@opencode-ai/sdk@1.0.85", "", {}, "sha512-7qVa7Psu7dFQBI1aMeQZXRho/6QMebHS2klNRaYO8b+n4jMUg20HEBIMFw5S1B2RF00+DFiTDxbbdByfirRNtg=="],"@types/node": ["@types/node@20.19.30", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-WJtwWJu7UdlvzEAUm484QNg5eAoq5QR08KDNx7g45Usrs2NtOPiX8ugDqmKdXkyL03rBqU5dYNYVQetEpBHq2g=="],"bun-types": ["bun-types@1.3.7", "", { "dependencies": { "@types/node": "*" } }, "sha512-qyschsA03Qz+gou+apt6HNl6HnI+sJJLL4wLDke4iugsE6584CMupOtTY1n+2YC9nGVrEKUlTs99jjRLKgWnjQ=="],"typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],"undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="],"zod": ["zod@4.1.8", "", {}, "sha512-5R1P+WwQqmmMIEACyzSvo4JXHY5WiAFHRMg+zBZKgKS+Q1viRa0C1hmUKtHltoIFKtIdki3pRxkmpP74jnNYHQ=="],}}
# opencode-anarchyAn OpenCode plugin that adds an anarchy-inspired command and tool.## Features- Custom tool: `anarchy_manifest`- Slash command: `/manifesto`- Custom tool: `pijul_manager`- Slash commands: `/pijul-init`, `/pijul-spawn`, `/pijul-direct`, `/pijul-sync`, `/pijul-record`, `/pijul-push`, `/pijul-status`## Installation1. Install dependencies:```bashbun install```2. Build the plugin:```bashbun run build```3. Add to your OpenCode config (`~/.config/opencode/config.json`):```json{"plugins": ["opencode-anarchy"]}```## Usage- Tool: `anarchy_manifest`- Command: `/manifesto`- Tool: `pijul_manager`- Commands: `/pijul-init`, `/pijul-spawn`, `/pijul-direct`, `/pijul-sync`, `/pijul-record`, `/pijul-push`, `/pijul-status`## Repository- `fitzgibbon@nest.pijul.com/fitzgibbon/opencode-anarchy`
# Agent Rules## Pijul1. Always add ephemeral files and build artifact patterns to `.ignore`.2. Always add all files before recording changes.3. Never allow Pijul to open an editor; run Pijul commands non-interactively.4. Record changes with `pijul record -a -m "..." --no-prompt` to avoid interactive selection.5. Use `--no-prompt` with `pijul add`, `pijul record`, and `pijul push`.6. For pushing everything, use `pijul push -a --no-prompt`.7. Ensure tests pass (`bun test`) before recording or pushing changes.
.git.DS_Storenode_modulesdist.env.env.*bun.lockbpackage-lock.jsonpnpm-lock.yaml.opencode
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 `${feature.feature} (${feature.status}) - channel=${feature.channel} workspace=${feature.workspace}`;
const depth = feature.depth ?? DEFAULT_FEATURE_DEPTH;const parent = feature.parentFeature ?? 'user';return `${feature.feature} (${feature.status}) depth=${depth} parent=${parent} channel=${feature.channel} workspace=${feature.workspace}`;
const agentId = args.agentId ?? args.agent;const sessionId = args.sessionId;if (!agentId || !sessionId) {return 'Missing agentId/sessionId for feature; provide both for the child agent.';}const parentKey = args.parentFeature ? slugify(args.parentFeature) : undefined;let parentFeature: FeatureState | undefined;let depth = DEFAULT_FEATURE_DEPTH;let parentAgentId = args.parentAgentId;let parentSessionId = args.parentSessionId;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;parentSessionId = parentSessionId ?? parentFeature.sessionId;if (!parentAgentId || !parentSessionId) {return 'Missing parent agentId/sessionId; provide them to spawn a child.';}}
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}`;
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 featureSwitchResult = await runCommand('pijul',['channel', 'switch', state.trunkChannel, '--no-prompt'],feature.workspace,);if (featureSwitchResult.exitCode !== 0) {return `Failed to switch feature workspace to trunk: ${featureSwitchResult.stderr || featureSwitchResult.stdout}`;}const deleteResult = await runCommand('pijul',['channel', 'delete', feature.channel, '--no-prompt'],feature.workspace,);if (deleteResult.exitCode !== 0) {const combined = `${deleteResult.stdout}\n${deleteResult.stderr}`.toLowerCase();if (!combined.includes('not found')) {return `Failed to delete feature channel: ${deleteResult.stderr || deleteResult.stdout}`;}}
Only communicate with your parent; if you need user input, ask your parent to escalate.
1. Call the `pijul_manager` tool with action "spawn" and the feature name.
1. Call the `pijul_manager` tool with action "spawn" and include:- feature: the requested feature name- agentId: the child agent id- sessionId: the child agent session id- parentFeature: the parent feature name (if this is a child of another feature)- parentAgentId: the parent agent id (if applicable)- parentSessionId: the parent session id (if applicable)
- The rule: run `pijul_manager` action "sync" before "push".- The rule: resolve conflicts locally and keep trunk clean.
- The workflow: sync → resolve conflicts → record → push → verify trunk clean → delete channel → report summary.- The rule: communicate with the parent only; escalate to the user through the parent if information is missing.- The depth limit: maximum depth is 5.
Only communicate with your parent; if you need user input, ask your parent to escalate.
After a successful push, confirm the feature channel is deleted and report a human-readable summary to your parent.Only communicate with your parent; if you need user input, ask your parent to escalate.
Remind the sub-agent to sync with trunk before pushing and to resolve conflicts locally.
Remind the sub-agent of the workflow: sync → resolve conflicts → record → push → verify trunk clean → delete channel → report summary.If they need more information, they should query their parent; if the parent cannot answer, it should escalate up the chain with the query context.