agentxchain 0.4.3 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +51 -18
- package/bin/agentxchain.js +6 -0
- package/package.json +1 -1
- package/src/commands/generate.js +44 -0
- package/src/commands/init.js +13 -5
- package/src/lib/generate-vscode.js +286 -0
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# agentxchain
|
|
2
2
|
|
|
3
|
-
CLI for multi-agent coordination in your IDE. Define a team of AI agents,
|
|
3
|
+
CLI for multi-agent coordination in your IDE. Define a team of AI agents, let them take turns building your project via shared state and lifecycle hooks.
|
|
4
4
|
|
|
5
5
|
## Install
|
|
6
6
|
|
|
@@ -14,38 +14,52 @@ Or run without installing:
|
|
|
14
14
|
npx agentxchain init
|
|
15
15
|
```
|
|
16
16
|
|
|
17
|
-
## Quick start
|
|
17
|
+
## Quick start — VS Code / Cursor (recommended)
|
|
18
|
+
|
|
19
|
+
No API keys or cloud connection needed. Uses native VS Code custom agents.
|
|
18
20
|
|
|
19
21
|
```bash
|
|
20
|
-
agentxchain init # create
|
|
22
|
+
agentxchain init # create project with agents + hooks
|
|
23
|
+
cd my-project/ && code . # open in VS Code / Cursor
|
|
24
|
+
# Select an agent from Chat dropdown (auto-discovered from .github/agents/)
|
|
25
|
+
agentxchain release # release human lock to begin turns
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
The `Stop` hook acts as referee: when an agent finishes, it hands off to the next agent automatically.
|
|
29
|
+
|
|
30
|
+
## Quick start — Cursor Cloud Agents
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
agentxchain init
|
|
21
34
|
cd my-project/
|
|
22
|
-
echo "CURSOR_API_KEY=your_key" >> .env #
|
|
23
|
-
#
|
|
24
|
-
|
|
25
|
-
agentxchain
|
|
26
|
-
agentxchain
|
|
35
|
+
echo "CURSOR_API_KEY=your_key" >> .env # cursor.com/settings -> Cloud Agents
|
|
36
|
+
# Connect GitHub in Cursor Settings -> GitHub integration
|
|
37
|
+
agentxchain start --ide cursor # launch cloud agents
|
|
38
|
+
agentxchain watch # coordinate turns
|
|
39
|
+
agentxchain release # begin turns (initial lock is human)
|
|
27
40
|
```
|
|
28
41
|
|
|
29
|
-
> `CURSOR_API_KEY` is required for
|
|
42
|
+
> `CURSOR_API_KEY` is required for Cloud commands. Your Cursor account needs GitHub access to the target repository.
|
|
30
43
|
|
|
31
44
|
## Commands
|
|
32
45
|
|
|
33
46
|
| Command | What it does |
|
|
34
47
|
|---------|-------------|
|
|
35
|
-
| `init` | Create project folder with agents, protocol files, and templates |
|
|
36
|
-
| `
|
|
37
|
-
| `
|
|
48
|
+
| `init` | Create project folder with agents, protocol files, hooks, and templates |
|
|
49
|
+
| `generate` | Regenerate VS Code agent files (`.agent.md`, hooks) from `agentxchain.json` |
|
|
50
|
+
| `start` | Launch agents in Cursor Cloud, Claude Code, or VS Code |
|
|
51
|
+
| `watch` | The referee for cloud mode — coordinates turns, enforces TTL, wakes agents |
|
|
38
52
|
| `status` | Show lock, phase, agents, Cursor session info |
|
|
39
53
|
| `claim` | Human takes control (pauses Cursor agents) |
|
|
40
54
|
| `release` | Hand lock back to agents |
|
|
41
|
-
| `stop` | Terminate all running agents |
|
|
55
|
+
| `stop` | Terminate all running cloud agents |
|
|
42
56
|
| `branch` | Show/set Cursor branch override (`cursor.ref`) |
|
|
43
57
|
| `config` | View/edit config, add/remove agents, change rules |
|
|
44
58
|
| `update` | Self-update CLI from npm |
|
|
45
59
|
|
|
46
60
|
### Branch selection
|
|
47
61
|
|
|
48
|
-
By default, Cursor launches use your current local git branch.
|
|
62
|
+
By default, Cursor launches use your current local git branch.
|
|
49
63
|
|
|
50
64
|
```bash
|
|
51
65
|
agentxchain branch # show current/effective branch
|
|
@@ -54,20 +68,39 @@ agentxchain branch --use-current # pin to whatever branch you're on now
|
|
|
54
68
|
agentxchain branch --unset # remove pin; follow active git branch
|
|
55
69
|
```
|
|
56
70
|
|
|
57
|
-
### Additional
|
|
71
|
+
### Additional flags
|
|
58
72
|
|
|
59
73
|
```bash
|
|
60
74
|
agentxchain watch --daemon # run watch in background
|
|
61
75
|
agentxchain release --force # force-release non-human holder lock
|
|
62
76
|
```
|
|
63
77
|
|
|
78
|
+
## VS Code plugin
|
|
79
|
+
|
|
80
|
+
`agentxchain init` generates native VS Code agent files:
|
|
81
|
+
|
|
82
|
+
- `.github/agents/*.agent.md` — custom agents (auto-discovered by VS Code Chat)
|
|
83
|
+
- `.github/hooks/agentxchain.json` — lifecycle hooks (Stop = referee, SessionStart = context injection)
|
|
84
|
+
- `scripts/agentxchain-*.sh` — hook shell scripts
|
|
85
|
+
|
|
86
|
+
VS Code extension (optional, for UI):
|
|
87
|
+
- Status bar: lock holder, turn, phase (live-updated via file watcher)
|
|
88
|
+
- Sidebar: agent dashboard with quick actions
|
|
89
|
+
- Commands: Claim, Release, Status, Generate
|
|
90
|
+
|
|
91
|
+
Install the extension:
|
|
92
|
+
```bash
|
|
93
|
+
code --install-extension cli/vscode-extension/agentxchain-0.1.0.vsix
|
|
94
|
+
```
|
|
95
|
+
|
|
64
96
|
## Key features
|
|
65
97
|
|
|
98
|
+
- **Native VS Code agents** — `.agent.md` files, lifecycle hooks, handoffs
|
|
66
99
|
- **Claim-based coordination** — no fixed turn order; agents self-organize
|
|
100
|
+
- **Stop hook referee** — deterministic turn coordination via VS Code hooks
|
|
67
101
|
- **User-defined teams** — any number of agents, any roles
|
|
68
|
-
- **Cursor Cloud Agents** — launch and manage
|
|
69
|
-
- **Branch-safe launching** — defaults to active git branch
|
|
70
|
-
- **Project `.env` loading** — CLI auto-reads `CURSOR_API_KEY` from project root `.env`
|
|
102
|
+
- **Cursor Cloud Agents** — launch and manage via API (optional)
|
|
103
|
+
- **Branch-safe launching** — defaults to active git branch
|
|
71
104
|
- **Lock TTL** — stale locks auto-released after timeout
|
|
72
105
|
- **Verify command** — agents must pass tests before releasing
|
|
73
106
|
- **Human-in-the-loop** — claim/release to intervene anytime
|
package/bin/agentxchain.js
CHANGED
|
@@ -10,6 +10,7 @@ import { updateCommand } from '../src/commands/update.js';
|
|
|
10
10
|
import { watchCommand } from '../src/commands/watch.js';
|
|
11
11
|
import { claimCommand, releaseCommand } from '../src/commands/claim.js';
|
|
12
12
|
import { branchCommand } from '../src/commands/branch.js';
|
|
13
|
+
import { generateCommand } from '../src/commands/generate.js';
|
|
13
14
|
|
|
14
15
|
const program = new Command();
|
|
15
16
|
|
|
@@ -59,6 +60,11 @@ program
|
|
|
59
60
|
.option('--unset', 'Remove override and follow active git branch automatically')
|
|
60
61
|
.action(branchCommand);
|
|
61
62
|
|
|
63
|
+
program
|
|
64
|
+
.command('generate')
|
|
65
|
+
.description('Regenerate VS Code agent files (.agent.md, hooks) from agentxchain.json')
|
|
66
|
+
.action(generateCommand);
|
|
67
|
+
|
|
62
68
|
program
|
|
63
69
|
.command('watch')
|
|
64
70
|
.description('Watch lock.json and coordinate agent turns (the referee)')
|
package/package.json
CHANGED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { loadConfig } from '../lib/config.js';
|
|
3
|
+
import { generateVSCodeFiles } from '../lib/generate-vscode.js';
|
|
4
|
+
|
|
5
|
+
export async function generateCommand() {
|
|
6
|
+
const result = loadConfig();
|
|
7
|
+
if (!result) {
|
|
8
|
+
console.log(chalk.red(' No agentxchain.json found. Run `agentxchain init` first.'));
|
|
9
|
+
process.exit(1);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const { root, config } = result;
|
|
13
|
+
const agentIds = Object.keys(config.agents || {});
|
|
14
|
+
|
|
15
|
+
if (agentIds.length === 0) {
|
|
16
|
+
console.log(chalk.red(' No agents configured in agentxchain.json.'));
|
|
17
|
+
process.exit(1);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
console.log('');
|
|
21
|
+
console.log(chalk.bold(' Generating VS Code agent files...'));
|
|
22
|
+
console.log(chalk.dim(` Project: ${config.project}`));
|
|
23
|
+
console.log('');
|
|
24
|
+
|
|
25
|
+
const vsResult = generateVSCodeFiles(root, config);
|
|
26
|
+
|
|
27
|
+
console.log(chalk.green(` ✓ Generated ${vsResult.agentCount} agent files`));
|
|
28
|
+
console.log('');
|
|
29
|
+
|
|
30
|
+
for (const id of agentIds) {
|
|
31
|
+
const name = config.agents[id].name;
|
|
32
|
+
console.log(` ${chalk.cyan(id)}.agent.md — ${name}`);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
console.log('');
|
|
36
|
+
console.log(chalk.dim(' Files written:'));
|
|
37
|
+
console.log(` .github/agents/ ${chalk.dim(`(${vsResult.agentCount} .agent.md files)`)}`);
|
|
38
|
+
console.log(` .github/hooks/ ${chalk.dim('agentxchain.json')}`);
|
|
39
|
+
console.log(` scripts/ ${chalk.dim('session-start, stop, pre-tool hooks')}`);
|
|
40
|
+
console.log('');
|
|
41
|
+
console.log(chalk.dim(' VS Code will auto-discover agents from .github/agents/.'));
|
|
42
|
+
console.log(chalk.dim(' Select an agent from the Chat dropdown to start a turn.'));
|
|
43
|
+
console.log('');
|
|
44
|
+
}
|
package/src/commands/init.js
CHANGED
|
@@ -4,6 +4,7 @@ import { fileURLToPath } from 'url';
|
|
|
4
4
|
import chalk from 'chalk';
|
|
5
5
|
import inquirer from 'inquirer';
|
|
6
6
|
import { CONFIG_FILE, LOCK_FILE, STATE_FILE } from '../lib/config.js';
|
|
7
|
+
import { generateVSCodeFiles } from '../lib/generate-vscode.js';
|
|
7
8
|
|
|
8
9
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
9
10
|
const TEMPLATES_DIR = join(__dirname, '../templates');
|
|
@@ -194,7 +195,7 @@ export async function initCommand(opts) {
|
|
|
194
195
|
}
|
|
195
196
|
};
|
|
196
197
|
|
|
197
|
-
const lock = { holder:
|
|
198
|
+
const lock = { holder: 'human', last_released_by: null, turn_number: 0, claimed_at: new Date().toISOString() };
|
|
198
199
|
const state = { phase: 'discovery', blocked: false, blocked_on: null, project };
|
|
199
200
|
|
|
200
201
|
// Core protocol files
|
|
@@ -247,6 +248,9 @@ export async function initCommand(opts) {
|
|
|
247
248
|
|
|
248
249
|
writeFileSync(join(dir, '.planning', 'qa', 'BUGS.md'), `# Bugs — ${project}\n\n## Open\n\n(QA adds bugs here with reproduction steps.)\n\n## Fixed\n\n(Bugs move here when dev confirms the fix and QA verifies it.)\n`);
|
|
249
250
|
|
|
251
|
+
// VS Code agent files (.github/agents/, .github/hooks/, scripts/)
|
|
252
|
+
const vsResult = generateVSCodeFiles(dir, config);
|
|
253
|
+
|
|
250
254
|
const agentCount = Object.keys(agents).length;
|
|
251
255
|
console.log('');
|
|
252
256
|
console.log(chalk.green(` ✓ Created ${chalk.bold(folderName)}/`));
|
|
@@ -255,10 +259,13 @@ export async function initCommand(opts) {
|
|
|
255
259
|
console.log(` ${chalk.dim('├──')} lock.json`);
|
|
256
260
|
console.log(` ${chalk.dim('├──')} state.json / state.md / history.jsonl`);
|
|
257
261
|
console.log(` ${chalk.dim('├──')} log.md / HUMAN_TASKS.md`);
|
|
258
|
-
console.log(` ${chalk.dim('
|
|
259
|
-
console.log(`
|
|
260
|
-
console.log(`
|
|
261
|
-
console.log(`
|
|
262
|
+
console.log(` ${chalk.dim('├──')} .planning/`);
|
|
263
|
+
console.log(` ${chalk.dim('│')} ${chalk.dim('├──')} PROJECT.md / REQUIREMENTS.md / ROADMAP.md`);
|
|
264
|
+
console.log(` ${chalk.dim('│')} ${chalk.dim('├──')} research/ / phases/`);
|
|
265
|
+
console.log(` ${chalk.dim('│')} ${chalk.dim('└──')} qa/ ${chalk.dim('TEST-COVERAGE / BUGS / UX-AUDIT / ACCEPTANCE-MATRIX')}`);
|
|
266
|
+
console.log(` ${chalk.dim('├──')} .github/agents/ ${chalk.dim(`(${agentCount} .agent.md files)`)}`);
|
|
267
|
+
console.log(` ${chalk.dim('├──')} .github/hooks/ ${chalk.dim('agentxchain.json')}`);
|
|
268
|
+
console.log(` ${chalk.dim('└──')} scripts/ ${chalk.dim('hook shell scripts')}`);
|
|
262
269
|
console.log('');
|
|
263
270
|
console.log(` ${chalk.dim('Agents:')} ${Object.keys(agents).join(', ')}`);
|
|
264
271
|
console.log('');
|
|
@@ -267,5 +274,6 @@ export async function initCommand(opts) {
|
|
|
267
274
|
console.log(` ${chalk.bold('edit .env')} ${chalk.dim('# set CURSOR_API_KEY (required for Cursor mode)')}`);
|
|
268
275
|
console.log(` ${chalk.bold('agentxchain start')} ${chalk.dim('# launch agents in Cursor')}`);
|
|
269
276
|
console.log(` ${chalk.bold('agentxchain watch')} ${chalk.dim('# start the referee')}`);
|
|
277
|
+
console.log(` ${chalk.bold('agentxchain release')} ${chalk.dim('# begin automation (initial lock is human)')}`);
|
|
270
278
|
console.log('');
|
|
271
279
|
}
|
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
import { writeFileSync, mkdirSync, existsSync, readFileSync, chmodSync } from 'fs';
|
|
2
|
+
import { join } from 'path';
|
|
3
|
+
import { generateSeedPrompt } from './seed-prompt.js';
|
|
4
|
+
|
|
5
|
+
export function generateVSCodeFiles(dir, config) {
|
|
6
|
+
const agentsDir = join(dir, '.github', 'agents');
|
|
7
|
+
const hooksDir = join(dir, '.github', 'hooks');
|
|
8
|
+
const scriptsDir = join(dir, 'scripts');
|
|
9
|
+
|
|
10
|
+
mkdirSync(agentsDir, { recursive: true });
|
|
11
|
+
mkdirSync(hooksDir, { recursive: true });
|
|
12
|
+
mkdirSync(scriptsDir, { recursive: true });
|
|
13
|
+
|
|
14
|
+
const agentIds = Object.keys(config.agents);
|
|
15
|
+
|
|
16
|
+
for (const id of agentIds) {
|
|
17
|
+
const agent = config.agents[id];
|
|
18
|
+
const md = buildAgentMd(id, agent, config, agentIds);
|
|
19
|
+
writeFileSync(join(agentsDir, `${id}.agent.md`), md);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
writeFileSync(join(hooksDir, 'agentxchain.json'), buildHooksJson());
|
|
23
|
+
writeFileSync(join(scriptsDir, 'agentxchain-session-start.sh'), SESSION_START_SCRIPT);
|
|
24
|
+
writeFileSync(join(scriptsDir, 'agentxchain-stop.sh'), buildStopScript(config));
|
|
25
|
+
writeFileSync(join(scriptsDir, 'agentxchain-pre-tool.sh'), PRE_TOOL_SCRIPT);
|
|
26
|
+
|
|
27
|
+
try {
|
|
28
|
+
chmodSync(join(scriptsDir, 'agentxchain-session-start.sh'), 0o755);
|
|
29
|
+
chmodSync(join(scriptsDir, 'agentxchain-stop.sh'), 0o755);
|
|
30
|
+
chmodSync(join(scriptsDir, 'agentxchain-pre-tool.sh'), 0o755);
|
|
31
|
+
} catch {}
|
|
32
|
+
|
|
33
|
+
return { agentsDir, hooksDir, scriptsDir, agentCount: agentIds.length };
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function buildAgentMd(agentId, agentDef, config, allAgentIds) {
|
|
37
|
+
const otherAgents = allAgentIds.filter(id => id !== agentId);
|
|
38
|
+
const verifyCmd = config.rules?.verify_command || null;
|
|
39
|
+
const maxClaims = config.rules?.max_consecutive_claims || 2;
|
|
40
|
+
const stateFile = config.state_file || 'state.md';
|
|
41
|
+
const historyFile = config.history_file || 'history.jsonl';
|
|
42
|
+
const logFile = config.log || 'log.md';
|
|
43
|
+
const useSplit = config.state_file || config.history_file;
|
|
44
|
+
|
|
45
|
+
const handoffs = otherAgents.map(otherId => {
|
|
46
|
+
const other = config.agents[otherId];
|
|
47
|
+
return ` - label: "Hand off to ${other.name}"
|
|
48
|
+
agent: ${otherId}
|
|
49
|
+
prompt: "Previous agent finished. Read lock.json, claim it, and do your work as ${other.name}."
|
|
50
|
+
send: true`;
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
handoffs.push(` - label: "Request Human Review"
|
|
54
|
+
agent: agent
|
|
55
|
+
prompt: "An agent requests human review. Check HUMAN_TASKS.md and lock.json."
|
|
56
|
+
send: false`);
|
|
57
|
+
|
|
58
|
+
const toolsList = "['search', 'fetch', 'editFiles', 'terminalLastCommand', 'codebase', 'usages']";
|
|
59
|
+
|
|
60
|
+
const frontmatter = `---
|
|
61
|
+
name: "${agentDef.name}"
|
|
62
|
+
description: "${escapeYaml(agentDef.mandate.split('\n')[0].slice(0, 120))}"
|
|
63
|
+
tools: ${toolsList}
|
|
64
|
+
model: ['claude-sonnet-4-5-20250514', 'gpt-4.1']
|
|
65
|
+
handoffs:
|
|
66
|
+
${handoffs.join('\n')}
|
|
67
|
+
hooks:
|
|
68
|
+
Stop:
|
|
69
|
+
- type: command
|
|
70
|
+
command: "./scripts/agentxchain-stop.sh"
|
|
71
|
+
SessionStart:
|
|
72
|
+
- type: command
|
|
73
|
+
command: "./scripts/agentxchain-session-start.sh"
|
|
74
|
+
---`;
|
|
75
|
+
|
|
76
|
+
const readInstructions = useSplit
|
|
77
|
+
? `Read these files at the start of your turn:
|
|
78
|
+
- \`${stateFile}\` — living project state (primary context)
|
|
79
|
+
- \`${historyFile}\` — last 3 lines for recent turns
|
|
80
|
+
- \`lock.json\` — current lock holder
|
|
81
|
+
- \`state.json\` — phase and blocked status`
|
|
82
|
+
: `Read these files at the start of your turn:
|
|
83
|
+
- \`${logFile}\` — message log (read last few messages)
|
|
84
|
+
- \`lock.json\` — current lock holder
|
|
85
|
+
- \`state.json\` — phase and blocked status`;
|
|
86
|
+
|
|
87
|
+
const writeInstructions = useSplit
|
|
88
|
+
? `When you finish your work, write in this order:
|
|
89
|
+
1. Your actual work: code, files, commands, decisions.
|
|
90
|
+
2. Overwrite \`${stateFile}\` with current project state.
|
|
91
|
+
3. Append one line to \`${historyFile}\`:
|
|
92
|
+
\`{"turn": N, "agent": "${agentId}", "summary": "...", "files_changed": [...], "verify_result": "pass|fail|skipped", "timestamp": "ISO8601"}\`
|
|
93
|
+
4. Update \`state.json\` if phase or blocked status changed.`
|
|
94
|
+
: `When you finish your work, write in this order:
|
|
95
|
+
1. Your actual work: code, files, commands, decisions.
|
|
96
|
+
2. Append one message to \`${logFile}\`:
|
|
97
|
+
\`### [${agentId}] (${agentDef.name}) | Turn N\`
|
|
98
|
+
with Status, Decision, Action, Next sections.
|
|
99
|
+
3. Update \`state.json\` if phase or blocked status changed.`;
|
|
100
|
+
|
|
101
|
+
const verifyInstructions = verifyCmd
|
|
102
|
+
? `\n## Verify before release\nBefore releasing the lock, run: \`${verifyCmd}\`\nIf it fails, fix the problem and run again. Do NOT release with a failing verification.`
|
|
103
|
+
: '';
|
|
104
|
+
|
|
105
|
+
const body = `# ${agentDef.name}
|
|
106
|
+
|
|
107
|
+
You are "${agentId}" on an AgentXchain team.
|
|
108
|
+
|
|
109
|
+
${agentDef.mandate}
|
|
110
|
+
|
|
111
|
+
---
|
|
112
|
+
|
|
113
|
+
## Project documentation
|
|
114
|
+
|
|
115
|
+
Read the files relevant to your role in the \`.planning/\` folder:
|
|
116
|
+
- \`.planning/PROJECT.md\` — Vision, constraints, stack (PM writes)
|
|
117
|
+
- \`.planning/REQUIREMENTS.md\` — Requirements with acceptance criteria (PM writes)
|
|
118
|
+
- \`.planning/ROADMAP.md\` — Phased delivery plan (PM maintains)
|
|
119
|
+
- \`.planning/research/\` — Domain research
|
|
120
|
+
- \`.planning/phases/\` — Per-phase plans, reviews, tests, bugs
|
|
121
|
+
- \`.planning/qa/\` — TEST-COVERAGE, BUGS, UX-AUDIT, ACCEPTANCE-MATRIX, REGRESSION-LOG (QA maintains)
|
|
122
|
+
|
|
123
|
+
Create or update these files when your role requires it.
|
|
124
|
+
|
|
125
|
+
---
|
|
126
|
+
|
|
127
|
+
## Your turn
|
|
128
|
+
|
|
129
|
+
The AgentXchain system coordinates turns. When prompted, do this:
|
|
130
|
+
|
|
131
|
+
1. **CLAIM**: Write \`lock.json\` with \`holder="${agentId}"\` and \`claimed_at\` = current time. Re-read to confirm.
|
|
132
|
+
2. **READ**: ${readInstructions}
|
|
133
|
+
3. **THINK**: What did the previous agent do? What is most important for YOUR role? What is one risk?
|
|
134
|
+
4. **WORK**: ${writeInstructions}${verifyInstructions}
|
|
135
|
+
5. **RELEASE**: Write \`lock.json\`: \`holder=null\`, \`last_released_by="${agentId}"\`, \`turn_number\` = previous + 1, \`claimed_at=null\`.
|
|
136
|
+
This MUST be the last thing you write.
|
|
137
|
+
|
|
138
|
+
---
|
|
139
|
+
|
|
140
|
+
## Rules
|
|
141
|
+
|
|
142
|
+
- Never write files without holding the lock.
|
|
143
|
+
- One git commit per turn: "Turn N - ${agentId} - description"
|
|
144
|
+
- Max ${maxClaims} consecutive turns. If limit hit, do a short turn and release.
|
|
145
|
+
- ALWAYS release the lock. A stuck lock blocks the entire team.
|
|
146
|
+
- ALWAYS find at least one problem, risk, or question about the previous work. Blind agreement is forbidden.
|
|
147
|
+
`;
|
|
148
|
+
|
|
149
|
+
return frontmatter + '\n\n' + body;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function escapeYaml(str) {
|
|
153
|
+
return str.replace(/"/g, '\\"').replace(/\n/g, ' ');
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function buildHooksJson() {
|
|
157
|
+
const hooks = {
|
|
158
|
+
hooks: {
|
|
159
|
+
SessionStart: [
|
|
160
|
+
{
|
|
161
|
+
type: 'command',
|
|
162
|
+
command: './scripts/agentxchain-session-start.sh'
|
|
163
|
+
}
|
|
164
|
+
],
|
|
165
|
+
Stop: [
|
|
166
|
+
{
|
|
167
|
+
type: 'command',
|
|
168
|
+
command: './scripts/agentxchain-stop.sh'
|
|
169
|
+
}
|
|
170
|
+
]
|
|
171
|
+
}
|
|
172
|
+
};
|
|
173
|
+
return JSON.stringify(hooks, null, 2) + '\n';
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function buildStopScript(config) {
|
|
177
|
+
const verifyCmd = config.rules?.verify_command || '';
|
|
178
|
+
const verifyBlock = verifyCmd
|
|
179
|
+
? `
|
|
180
|
+
# Run verify command before allowing release
|
|
181
|
+
if [ -z "$HOLDER" ] || [ "$HOLDER" = "null" ]; then
|
|
182
|
+
VERIFY_CMD="${verifyCmd}"
|
|
183
|
+
if [ -n "$VERIFY_CMD" ]; then
|
|
184
|
+
if ! eval "$VERIFY_CMD" > /dev/null 2>&1; then
|
|
185
|
+
echo '{"hookSpecificOutput":{"hookEventName":"Stop","decision":"block","reason":"Verification failed: '"$VERIFY_CMD"'. Fix the issue and release the lock."}}'
|
|
186
|
+
exit 0
|
|
187
|
+
fi
|
|
188
|
+
fi
|
|
189
|
+
fi
|
|
190
|
+
`
|
|
191
|
+
: '';
|
|
192
|
+
|
|
193
|
+
return `#!/bin/bash
|
|
194
|
+
INPUT=$(cat)
|
|
195
|
+
STOP_HOOK_ACTIVE=$(echo "$INPUT" | jq -r '.stop_hook_active // false')
|
|
196
|
+
|
|
197
|
+
if [ "$STOP_HOOK_ACTIVE" = "true" ]; then
|
|
198
|
+
echo '{"continue":true}'
|
|
199
|
+
exit 0
|
|
200
|
+
fi
|
|
201
|
+
|
|
202
|
+
if [ ! -f "lock.json" ]; then
|
|
203
|
+
echo '{"continue":true}'
|
|
204
|
+
exit 0
|
|
205
|
+
fi
|
|
206
|
+
|
|
207
|
+
LOCK=$(cat lock.json 2>/dev/null)
|
|
208
|
+
HOLDER=$(echo "$LOCK" | jq -r '.holder // empty')
|
|
209
|
+
TURN=$(echo "$LOCK" | jq -r '.turn_number // 0')
|
|
210
|
+
${verifyBlock}
|
|
211
|
+
if [ -z "$HOLDER" ] || [ "$HOLDER" = "null" ]; then
|
|
212
|
+
LAST=$(echo "$LOCK" | jq -r '.last_released_by // empty')
|
|
213
|
+
|
|
214
|
+
if [ ! -f "agentxchain.json" ]; then
|
|
215
|
+
echo '{"continue":true}'
|
|
216
|
+
exit 0
|
|
217
|
+
fi
|
|
218
|
+
|
|
219
|
+
NEXT=$(node -e "
|
|
220
|
+
const cfg = JSON.parse(require('fs').readFileSync('agentxchain.json','utf8'));
|
|
221
|
+
const ids = Object.keys(cfg.agents);
|
|
222
|
+
const last = process.argv[1] || '';
|
|
223
|
+
const idx = ids.indexOf(last);
|
|
224
|
+
const next = ids[(idx + 1) % ids.length];
|
|
225
|
+
process.stdout.write(next);
|
|
226
|
+
" -- "$LAST" 2>/dev/null)
|
|
227
|
+
|
|
228
|
+
if [ -z "$NEXT" ]; then
|
|
229
|
+
echo '{"continue":true}'
|
|
230
|
+
exit 0
|
|
231
|
+
fi
|
|
232
|
+
|
|
233
|
+
NEXT_NAME=$(node -e "
|
|
234
|
+
const cfg = JSON.parse(require('fs').readFileSync('agentxchain.json','utf8'));
|
|
235
|
+
const a = cfg.agents[process.argv[1]];
|
|
236
|
+
process.stdout.write(a ? a.name : process.argv[1]);
|
|
237
|
+
" -- "$NEXT" 2>/dev/null)
|
|
238
|
+
|
|
239
|
+
echo "{\\"hookSpecificOutput\\":{\\"hookEventName\\":\\"Stop\\",\\"decision\\":\\"block\\",\\"reason\\":\\"Turn $TURN complete. Next agent: $NEXT ($NEXT_NAME). Read lock.json, claim it, and do your work.\\"}}"
|
|
240
|
+
elif [ "$HOLDER" = "human" ]; then
|
|
241
|
+
echo '{"continue":true}'
|
|
242
|
+
else
|
|
243
|
+
echo '{"continue":true}'
|
|
244
|
+
fi
|
|
245
|
+
`;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
const SESSION_START_SCRIPT = `#!/bin/bash
|
|
249
|
+
if [ ! -f "lock.json" ] || [ ! -f "state.json" ]; then
|
|
250
|
+
echo '{"continue":true}'
|
|
251
|
+
exit 0
|
|
252
|
+
fi
|
|
253
|
+
|
|
254
|
+
LOCK=$(cat lock.json 2>/dev/null)
|
|
255
|
+
STATE=$(cat state.json 2>/dev/null)
|
|
256
|
+
|
|
257
|
+
HOLDER=$(echo "$LOCK" | jq -r '.holder // "none"')
|
|
258
|
+
TURN=$(echo "$LOCK" | jq -r '.turn_number // 0')
|
|
259
|
+
LAST=$(echo "$LOCK" | jq -r '.last_released_by // "none"')
|
|
260
|
+
PHASE=$(echo "$STATE" | jq -r '.phase // "unknown"')
|
|
261
|
+
BLOCKED=$(echo "$STATE" | jq -r '.blocked // false')
|
|
262
|
+
PROJECT=$(echo "$STATE" | jq -r '.project // "unknown"')
|
|
263
|
+
|
|
264
|
+
CONTEXT="AgentXchain context: Project=$PROJECT | Phase=$PHASE | Turn=$TURN | Lock=$HOLDER | Last released by=$LAST | Blocked=$BLOCKED"
|
|
265
|
+
|
|
266
|
+
echo "{\\"hookSpecificOutput\\":{\\"hookEventName\\":\\"SessionStart\\",\\"additionalContext\\":\\"$CONTEXT\\"}}"
|
|
267
|
+
`;
|
|
268
|
+
|
|
269
|
+
const PRE_TOOL_SCRIPT = `#!/bin/bash
|
|
270
|
+
INPUT=$(cat)
|
|
271
|
+
TOOL_NAME=$(echo "$INPUT" | jq -r '.tool_name // empty')
|
|
272
|
+
|
|
273
|
+
WRITE_TOOLS="editFiles|createFile|create_file|replace_string_in_file|deleteFile"
|
|
274
|
+
|
|
275
|
+
if echo "$TOOL_NAME" | grep -qE "^($WRITE_TOOLS)\$"; then
|
|
276
|
+
if [ -f "lock.json" ]; then
|
|
277
|
+
HOLDER=$(cat lock.json | jq -r '.holder // empty')
|
|
278
|
+
if [ -z "$HOLDER" ] || [ "$HOLDER" = "null" ]; then
|
|
279
|
+
echo '{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"deny","permissionDecisionReason":"You must claim lock.json before writing files. Write holder=your_agent_id first."}}'
|
|
280
|
+
exit 0
|
|
281
|
+
fi
|
|
282
|
+
fi
|
|
283
|
+
fi
|
|
284
|
+
|
|
285
|
+
echo '{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"allow"}}'
|
|
286
|
+
`;
|