agentxchain 0.8.6 → 0.8.8
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 +36 -38
- package/bin/agentxchain.js +62 -3
- package/package.json +3 -2
- package/scripts/agentxchain-autonudge.applescript +49 -10
- package/scripts/run-autonudge.sh +1 -1
- package/src/adapters/claude-code.js +7 -14
- package/src/adapters/cursor-local.js +26 -29
- package/src/commands/branch.js +2 -2
- package/src/commands/claim.js +86 -15
- package/src/commands/config.js +16 -0
- package/src/commands/doctor.js +9 -1
- package/src/commands/init.js +24 -5
- package/src/commands/rebind.js +77 -0
- package/src/commands/stop.js +65 -33
- package/src/commands/update.js +24 -3
- package/src/commands/watch.js +115 -34
- package/src/lib/config.js +47 -12
- package/src/lib/filter-agents.js +12 -0
- package/src/lib/generate-vscode.js +158 -51
- package/src/lib/next-owner.js +116 -0
- package/src/lib/notify.js +14 -12
- package/src/lib/prompt-core.js +108 -0
- package/src/lib/safe-write.js +44 -0
- package/src/lib/schema.js +68 -0
- package/src/lib/seed-prompt-polling.js +21 -83
- package/src/lib/seed-prompt.js +17 -63
- package/src/lib/validation.js +30 -19
- package/src/lib/verify-command.js +72 -0
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from 'fs';
|
|
2
|
+
import { join } from 'path';
|
|
3
|
+
|
|
4
|
+
export function resolveExpectedClaimer(root, config, lock = {}) {
|
|
5
|
+
const agents = Object.keys(config.agents || {});
|
|
6
|
+
if (agents.length === 0) return { next: null, source: 'none', raw: null };
|
|
7
|
+
|
|
8
|
+
const trigger = readTrigger(root);
|
|
9
|
+
if (
|
|
10
|
+
trigger &&
|
|
11
|
+
typeof trigger.turn_number === 'number' &&
|
|
12
|
+
trigger.turn_number === lock.turn_number &&
|
|
13
|
+
typeof trigger.agent === 'string' &&
|
|
14
|
+
agents.includes(trigger.agent)
|
|
15
|
+
) {
|
|
16
|
+
return { next: trigger.agent, source: 'trigger', raw: trigger.agent };
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
return resolveNextAgent(root, config, lock);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function resolveNextAgent(root, config, lock = {}) {
|
|
23
|
+
const agents = Object.keys(config.agents || {});
|
|
24
|
+
if (agents.length === 0) return { next: null, source: 'none', raw: null };
|
|
25
|
+
|
|
26
|
+
const talkFile = config.talk_file || 'TALK.md';
|
|
27
|
+
const talkPath = join(root, talkFile);
|
|
28
|
+
const fromTalk = parseNextOwnerFromTalk(talkPath, agents);
|
|
29
|
+
if (fromTalk) {
|
|
30
|
+
return { next: fromTalk, source: 'talk', raw: fromTalk };
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (config.rules?.strict_next_owner) {
|
|
34
|
+
return { next: null, source: 'strict-missing', raw: null };
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const last = lock.last_released_by;
|
|
38
|
+
if (last && agents.includes(last)) {
|
|
39
|
+
const idx = agents.indexOf(last);
|
|
40
|
+
return { next: agents[(idx + 1) % agents.length], source: 'fallback-cyclic', raw: null };
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return { next: agents[0], source: 'fallback-first', raw: null };
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const NEXT_OWNER_PATTERNS = [
|
|
47
|
+
/^(?:-|\*)?\s*\**next\s*owner\**\s*:\s*(.+)$/i,
|
|
48
|
+
/^(?:-|\*)?\s*\**handoff\s*(?:to)?\**\s*:\s*(.+)$/i,
|
|
49
|
+
/^(?:-|\*)?\s*\**next\**\s*:\s*(.+)$/i,
|
|
50
|
+
/^(?:-|\*)?\s*\**hand\s*off\s*to\**\s*:\s*(.+)$/i,
|
|
51
|
+
];
|
|
52
|
+
|
|
53
|
+
function parseNextOwnerFromTalk(talkPath, validAgentIds) {
|
|
54
|
+
if (!existsSync(talkPath)) return null;
|
|
55
|
+
|
|
56
|
+
let text = '';
|
|
57
|
+
try {
|
|
58
|
+
text = readFileSync(talkPath, 'utf8');
|
|
59
|
+
} catch {
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (!text.trim()) return null;
|
|
64
|
+
|
|
65
|
+
const lines = text.split(/\r?\n/);
|
|
66
|
+
for (let i = lines.length - 1; i >= 0; i -= 1) {
|
|
67
|
+
const line = lines[i].trim();
|
|
68
|
+
if (!line) continue;
|
|
69
|
+
|
|
70
|
+
for (const pattern of NEXT_OWNER_PATTERNS) {
|
|
71
|
+
const match = line.match(pattern);
|
|
72
|
+
if (!match) continue;
|
|
73
|
+
|
|
74
|
+
const candidate = normalizeAgentId(match[1]);
|
|
75
|
+
if (candidate && validAgentIds.includes(candidate)) {
|
|
76
|
+
return candidate;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const fuzzy = fuzzyMatchAgentId(match[1], validAgentIds);
|
|
80
|
+
if (fuzzy) return fuzzy;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function normalizeAgentId(raw) {
|
|
88
|
+
if (!raw) return null;
|
|
89
|
+
let value = String(raw).trim();
|
|
90
|
+
value = value.replace(/[`*_\[\]]/g, '').trim();
|
|
91
|
+
value = value.replace(/\(.*?\)/g, '').trim();
|
|
92
|
+
value = value.split(/[,\s]+/)[0];
|
|
93
|
+
value = value.toLowerCase();
|
|
94
|
+
return /^[a-z0-9_-]+$/.test(value) ? value : null;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function fuzzyMatchAgentId(raw, validAgentIds) {
|
|
98
|
+
if (!raw) return null;
|
|
99
|
+
const cleaned = String(raw).replace(/[`*_\[\]]/g, '').replace(/\(.*?\)/g, '').trim().toLowerCase();
|
|
100
|
+
for (const id of validAgentIds) {
|
|
101
|
+
if (cleaned.startsWith(id)) return id;
|
|
102
|
+
if (cleaned.includes(id)) return id;
|
|
103
|
+
}
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function readTrigger(root) {
|
|
108
|
+
const triggerPath = join(root, '.agentxchain-trigger.json');
|
|
109
|
+
if (!existsSync(triggerPath)) return null;
|
|
110
|
+
try {
|
|
111
|
+
return JSON.parse(readFileSync(triggerPath, 'utf8'));
|
|
112
|
+
} catch {
|
|
113
|
+
return null;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
package/src/lib/notify.js
CHANGED
|
@@ -1,24 +1,26 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { execFileSync } from 'child_process';
|
|
2
|
+
|
|
3
|
+
function sanitize(str) {
|
|
4
|
+
return String(str).replace(/[\\"]/g, ' ').replace(/'/g, ' ').slice(0, 200);
|
|
5
|
+
}
|
|
2
6
|
|
|
3
7
|
export function notifyHuman(message, title = 'AgentXchain') {
|
|
4
|
-
// Terminal bell
|
|
5
8
|
process.stdout.write('\x07');
|
|
6
9
|
|
|
7
|
-
|
|
10
|
+
const safeMsg = sanitize(message);
|
|
11
|
+
const safeTitle = sanitize(title);
|
|
12
|
+
|
|
8
13
|
if (process.platform === 'darwin') {
|
|
9
14
|
try {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
}
|
|
15
|
+
execFileSync('osascript', [
|
|
16
|
+
'-e', `display notification "${safeMsg}" with title "${safeTitle}"`
|
|
17
|
+
], { stdio: 'ignore' });
|
|
18
|
+
} catch {}
|
|
14
19
|
}
|
|
15
20
|
|
|
16
|
-
// Linux notification
|
|
17
21
|
if (process.platform === 'linux') {
|
|
18
22
|
try {
|
|
19
|
-
|
|
20
|
-
} catch {
|
|
21
|
-
// notify-send not available
|
|
22
|
-
}
|
|
23
|
+
execFileSync('notify-send', [safeTitle, safeMsg], { stdio: 'ignore' });
|
|
24
|
+
} catch {}
|
|
23
25
|
}
|
|
24
26
|
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared prompt building blocks used by seed-prompt.js, seed-prompt-polling.js,
|
|
3
|
+
* and generate-vscode.js. Single source of truth for protocol rules expressed
|
|
4
|
+
* in prompts.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export function buildReadSection(config, opts = {}) {
|
|
8
|
+
const stateFile = config.state_file || 'state.md';
|
|
9
|
+
const historyFile = config.history_file || 'history.jsonl';
|
|
10
|
+
const logFile = config.log || 'log.md';
|
|
11
|
+
const talkFile = config.talk_file || 'TALK.md';
|
|
12
|
+
const useSplit = config.state_file || config.history_file;
|
|
13
|
+
|
|
14
|
+
if (useSplit) {
|
|
15
|
+
const lines = [
|
|
16
|
+
`"${stateFile}" — the living project state. Read fully. Primary context.`,
|
|
17
|
+
`"${historyFile}" — turn history. Read last 3 lines for recent context.`,
|
|
18
|
+
];
|
|
19
|
+
if (opts.includeTalk) lines.push(`"${talkFile}" — team handoff updates. Read the latest 5 entries.`);
|
|
20
|
+
lines.push('lock.json — who holds the lock.');
|
|
21
|
+
lines.push('state.json — phase and blocked status.');
|
|
22
|
+
return `READ THESE FILES EVERY TURN:\n${lines.map(l => `- ${l}`).join('\n')}`;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const lines = [
|
|
26
|
+
`"${logFile}" — the message log. Read last few messages.`,
|
|
27
|
+
];
|
|
28
|
+
if (opts.includeTalk) lines.push(`"${talkFile}" — team handoff updates. Read the latest 5 entries.`);
|
|
29
|
+
lines.push('lock.json — who holds the lock.');
|
|
30
|
+
lines.push('state.json — phase and blocked status.');
|
|
31
|
+
return `READ THESE FILES EVERY TURN:\n${lines.map(l => `- ${l}`).join('\n')}`;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function buildWriteSection(agentId, agentDef, config, opts = {}) {
|
|
35
|
+
const stateFile = config.state_file || 'state.md';
|
|
36
|
+
const historyFile = config.history_file || 'history.jsonl';
|
|
37
|
+
const logFile = config.log || 'log.md';
|
|
38
|
+
const talkFile = config.talk_file || 'TALK.md';
|
|
39
|
+
const useSplit = config.state_file || config.history_file;
|
|
40
|
+
const agentIds = Object.keys(config.agents);
|
|
41
|
+
|
|
42
|
+
const steps = ['a. Do your actual work: write code, create files, run commands, make decisions.'];
|
|
43
|
+
|
|
44
|
+
if (useSplit) {
|
|
45
|
+
steps.push(`b. Update "${stateFile}" — OVERWRITE with current project state.`);
|
|
46
|
+
steps.push(`c. Append ONE line to "${historyFile}":\n {"turn": N, "agent": "${agentId}", "summary": "what you did", "files_changed": [...], "verify_result": "pass|fail|skipped", "timestamp": "ISO8601"}`);
|
|
47
|
+
if (opts.includeTalk) {
|
|
48
|
+
steps.push(`d. Append ONE handoff entry to "${talkFile}" with:\n Turn, Status, Decision, Action, Risks/Questions, Next owner.\n IMPORTANT: "Next owner" must be a valid agent id from [${agentIds.join(', ')}].`);
|
|
49
|
+
steps.push('e. Update state.json if phase or blocked status changed.');
|
|
50
|
+
} else {
|
|
51
|
+
steps.push('d. Update state.json if phase or blocked status changed.');
|
|
52
|
+
}
|
|
53
|
+
} else {
|
|
54
|
+
steps.push(
|
|
55
|
+
`b. Append ONE message to ${logFile}:\n` +
|
|
56
|
+
` ---\n` +
|
|
57
|
+
` ### [${agentId}] (${agentDef.name}) | Turn N\n` +
|
|
58
|
+
` **Status:** Current project state.\n` +
|
|
59
|
+
` **Decision:** What you decided and why.\n` +
|
|
60
|
+
` **Action:** What you did. Commands, files, results.\n` +
|
|
61
|
+
` **Next:** What the next agent should focus on.`
|
|
62
|
+
);
|
|
63
|
+
if (opts.includeTalk) {
|
|
64
|
+
steps.push(`c. Append ONE handoff entry to "${talkFile}" with:\n Turn, Status, Decision, Action, Risks/Questions, Next owner.\n IMPORTANT: "Next owner" must be a valid agent id from [${agentIds.join(', ')}].`);
|
|
65
|
+
steps.push('d. Update state.json if phase or blocked status changed.');
|
|
66
|
+
} else {
|
|
67
|
+
steps.push('c. Update state.json if phase or blocked status changed.');
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return `WRITE (in this order):\n${steps.join('\n')}`;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function buildVerifySection(config) {
|
|
75
|
+
const verifyCmd = config.rules?.verify_command || null;
|
|
76
|
+
if (!verifyCmd) return '';
|
|
77
|
+
return `\nVERIFY (mandatory before release):\nRun: ${verifyCmd}\nIf it FAILS: fix the problem. Run again. Do NOT release with failing verification.\nIf it PASSES: report the result. Then release.`;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export function buildRulesSection(agentId, config) {
|
|
81
|
+
const maxClaims = config.rules?.max_consecutive_claims || 2;
|
|
82
|
+
return [
|
|
83
|
+
'- Never write files or code without holding the lock. Reading is always allowed.',
|
|
84
|
+
`- One git commit per turn: "Turn N - ${agentId} - description"`,
|
|
85
|
+
`- Max ${maxClaims} consecutive turns. If you have held the lock ${maxClaims} times in a row, do a short turn and release.`,
|
|
86
|
+
'- ALWAYS release the lock. A stuck lock blocks the entire team.',
|
|
87
|
+
'- ALWAYS find at least one problem, risk, or question about the previous work. Blind agreement is forbidden.',
|
|
88
|
+
].join('\n');
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export function buildPlanningDocsSection() {
|
|
92
|
+
return `PROJECT DOCUMENTATION (.planning/ folder):
|
|
93
|
+
|
|
94
|
+
These files give you project context. Read the ones relevant to your role.
|
|
95
|
+
|
|
96
|
+
- .planning/PROJECT.md — Vision, constraints, stack decisions. PM writes this.
|
|
97
|
+
- .planning/REQUIREMENTS.md — Scoped requirements with acceptance criteria. PM writes this.
|
|
98
|
+
- .planning/ROADMAP.md — Phased delivery plan. PM maintains this.
|
|
99
|
+
- .planning/research/ — Domain research, prior art, technical investigation.
|
|
100
|
+
- .planning/phases/ — Per-phase plans (PLAN.md), reviews (REVIEW.md), test results (TESTS.md), bugs (BUGS.md).
|
|
101
|
+
- .planning/qa/TEST-COVERAGE.md — Which features are tested and how. QA maintains this.
|
|
102
|
+
- .planning/qa/BUGS.md — Open and fixed bugs with reproduction steps. QA maintains this.
|
|
103
|
+
- .planning/qa/UX-AUDIT.md — UX checklist and visual/usability issues. QA maintains this.
|
|
104
|
+
- .planning/qa/ACCEPTANCE-MATRIX.md — Requirements mapped to test status. QA maintains this.
|
|
105
|
+
- .planning/qa/REGRESSION-LOG.md — Fixed bugs and their regression tests.
|
|
106
|
+
|
|
107
|
+
When your role requires it, CREATE or UPDATE these files.`;
|
|
108
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { writeFileSync, renameSync, mkdirSync, unlinkSync } from 'fs';
|
|
2
|
+
import { join, dirname } from 'path';
|
|
3
|
+
import { randomBytes } from 'crypto';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Atomically write JSON to a file using write-to-temp-then-rename.
|
|
7
|
+
* renameSync is atomic on POSIX; on Windows it is close enough for
|
|
8
|
+
* single-process coordination (the only gap is cross-process, which
|
|
9
|
+
* we guard against at the protocol level).
|
|
10
|
+
*/
|
|
11
|
+
export function safeWriteJson(filePath, data) {
|
|
12
|
+
const dir = dirname(filePath);
|
|
13
|
+
const tmpName = `.tmp-${randomBytes(6).toString('hex')}.json`;
|
|
14
|
+
const tmpPath = join(dir, tmpName);
|
|
15
|
+
|
|
16
|
+
mkdirSync(dir, { recursive: true });
|
|
17
|
+
writeFileSync(tmpPath, JSON.stringify(data, null, 2) + '\n');
|
|
18
|
+
|
|
19
|
+
try {
|
|
20
|
+
renameSync(tmpPath, filePath);
|
|
21
|
+
} catch (err) {
|
|
22
|
+
try { unlinkSync(tmpPath); } catch {}
|
|
23
|
+
throw err;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Atomically write plain text to a file.
|
|
29
|
+
*/
|
|
30
|
+
export function safeWriteText(filePath, text) {
|
|
31
|
+
const dir = dirname(filePath);
|
|
32
|
+
const tmpName = `.tmp-${randomBytes(6).toString('hex')}`;
|
|
33
|
+
const tmpPath = join(dir, tmpName);
|
|
34
|
+
|
|
35
|
+
mkdirSync(dir, { recursive: true });
|
|
36
|
+
writeFileSync(tmpPath, text);
|
|
37
|
+
|
|
38
|
+
try {
|
|
39
|
+
renameSync(tmpPath, filePath);
|
|
40
|
+
} catch (err) {
|
|
41
|
+
try { unlinkSync(tmpPath); } catch {}
|
|
42
|
+
throw err;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lightweight schema validation for AgentXchain protocol files.
|
|
3
|
+
* No external dependencies — validates shape and types only.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export function validateLockSchema(data) {
|
|
7
|
+
const errors = [];
|
|
8
|
+
if (data === null || typeof data !== 'object') {
|
|
9
|
+
return { ok: false, errors: ['lock.json must be a JSON object'] };
|
|
10
|
+
}
|
|
11
|
+
if (!('holder' in data)) errors.push('Missing field: holder');
|
|
12
|
+
else if (data.holder !== null && typeof data.holder !== 'string') errors.push('holder must be a string or null');
|
|
13
|
+
if (!('turn_number' in data)) errors.push('Missing field: turn_number');
|
|
14
|
+
else if (typeof data.turn_number !== 'number' || !Number.isInteger(data.turn_number)) errors.push('turn_number must be an integer');
|
|
15
|
+
if (!('last_released_by' in data)) errors.push('Missing field: last_released_by');
|
|
16
|
+
else if (data.last_released_by !== null && typeof data.last_released_by !== 'string') errors.push('last_released_by must be a string or null');
|
|
17
|
+
if (!('claimed_at' in data)) errors.push('Missing field: claimed_at');
|
|
18
|
+
else if (data.claimed_at !== null && typeof data.claimed_at !== 'string') errors.push('claimed_at must be a string or null');
|
|
19
|
+
return { ok: errors.length === 0, errors };
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function validateStateSchema(data) {
|
|
23
|
+
const errors = [];
|
|
24
|
+
if (data === null || typeof data !== 'object') {
|
|
25
|
+
return { ok: false, errors: ['state.json must be a JSON object'] };
|
|
26
|
+
}
|
|
27
|
+
if (typeof data.phase !== 'string') errors.push('phase must be a string');
|
|
28
|
+
if (typeof data.blocked !== 'boolean' && data.blocked !== undefined) errors.push('blocked must be a boolean');
|
|
29
|
+
return { ok: errors.length === 0, errors };
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function validateConfigSchema(data) {
|
|
33
|
+
const errors = [];
|
|
34
|
+
if (data === null || typeof data !== 'object') {
|
|
35
|
+
return { ok: false, errors: ['agentxchain.json must be a JSON object'] };
|
|
36
|
+
}
|
|
37
|
+
if (data.version !== 3) errors.push('version must be 3');
|
|
38
|
+
if (typeof data.project !== 'string' || !data.project.trim()) errors.push('project must be a non-empty string');
|
|
39
|
+
if (!data.agents || typeof data.agents !== 'object') {
|
|
40
|
+
errors.push('agents must be an object');
|
|
41
|
+
} else {
|
|
42
|
+
for (const [id, agent] of Object.entries(data.agents)) {
|
|
43
|
+
if (!/^[a-z0-9_-]+$/.test(id)) errors.push(`Invalid agent id: "${id}" (must be lowercase alphanumeric, hyphens, underscores)`);
|
|
44
|
+
if (!agent || typeof agent !== 'object') { errors.push(`Agent "${id}" must be an object`); continue; }
|
|
45
|
+
if (typeof agent.name !== 'string' || !agent.name.trim()) errors.push(`Agent "${id}": name must be a non-empty string`);
|
|
46
|
+
if (typeof agent.mandate !== 'string' || !agent.mandate.trim()) errors.push(`Agent "${id}": mandate must be a non-empty string`);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return { ok: errors.length === 0, errors };
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Safely parse JSON with schema validation.
|
|
54
|
+
* Returns { ok, data, errors }.
|
|
55
|
+
*/
|
|
56
|
+
export function safeParseJson(raw, validator) {
|
|
57
|
+
let data;
|
|
58
|
+
try {
|
|
59
|
+
data = JSON.parse(raw);
|
|
60
|
+
} catch (err) {
|
|
61
|
+
return { ok: false, data: null, errors: [`Invalid JSON: ${err.message}`] };
|
|
62
|
+
}
|
|
63
|
+
if (validator) {
|
|
64
|
+
const result = validator(data);
|
|
65
|
+
return { ok: result.ok, data, errors: result.errors };
|
|
66
|
+
}
|
|
67
|
+
return { ok: true, data, errors: [] };
|
|
68
|
+
}
|
|
@@ -1,63 +1,20 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
const useSplit = config.state_file || config.history_file;
|
|
1
|
+
import {
|
|
2
|
+
buildReadSection,
|
|
3
|
+
buildWriteSection,
|
|
4
|
+
buildVerifySection,
|
|
5
|
+
buildRulesSection,
|
|
6
|
+
buildPlanningDocsSection,
|
|
7
|
+
} from './prompt-core.js';
|
|
9
8
|
|
|
9
|
+
export function generatePollingPrompt(agentId, agentDef, config, projectRoot = '.') {
|
|
10
10
|
const agentIds = Object.keys(config.agents);
|
|
11
11
|
const myIndex = agentIds.indexOf(agentId);
|
|
12
|
-
|
|
13
|
-
const
|
|
14
|
-
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
const readSection = useSplit
|
|
20
|
-
? `READ THESE FILES:
|
|
21
|
-
- "${stateFile}" — the living project state. Read fully. Primary context.
|
|
22
|
-
- "${historyFile}" — turn history. Read last 3 lines for recent context.
|
|
23
|
-
- "${talkFile}" — team handoff updates. Read the latest 5 entries.
|
|
24
|
-
- lock.json — who holds the lock.
|
|
25
|
-
- state.json — phase and blocked status.`
|
|
26
|
-
: `READ THESE FILES:
|
|
27
|
-
- "${logFile}" — the message log. Read last few messages.
|
|
28
|
-
- "${talkFile}" — team handoff updates. Read the latest 5 entries.
|
|
29
|
-
- lock.json — who holds the lock.
|
|
30
|
-
- state.json — phase and blocked status.`;
|
|
31
|
-
|
|
32
|
-
const writeSection = useSplit
|
|
33
|
-
? `WRITE (in this order):
|
|
34
|
-
a. Do your actual work: write code, create files, run commands, make decisions.
|
|
35
|
-
b. Update "${stateFile}" — OVERWRITE with current project state.
|
|
36
|
-
c. Append ONE line to "${historyFile}":
|
|
37
|
-
{"turn": N, "agent": "${agentId}", "summary": "what you did", "files_changed": [...], "verify_result": "pass|fail|skipped", "timestamp": "ISO8601"}
|
|
38
|
-
d. Append ONE handoff entry to "${talkFile}" with:
|
|
39
|
-
Turn, Status, Decision, Action, Risks/Questions, Next owner.
|
|
40
|
-
e. Update state.json if phase or blocked status changed.`
|
|
41
|
-
: `WRITE (in this order):
|
|
42
|
-
a. Do your actual work: write code, create files, run commands, make decisions.
|
|
43
|
-
b. Append ONE message to ${logFile}:
|
|
44
|
-
---
|
|
45
|
-
### [${agentId}] (${agentDef.name}) | Turn N
|
|
46
|
-
**Status:** Current project state.
|
|
47
|
-
**Decision:** What you decided and why.
|
|
48
|
-
**Action:** What you did. Commands, files, results.
|
|
49
|
-
**Next:** What the next agent should focus on.
|
|
50
|
-
c. Append ONE handoff entry to "${talkFile}" with:
|
|
51
|
-
Turn, Status, Decision, Action, Risks/Questions, Next owner.
|
|
52
|
-
d. Update state.json if phase or blocked status changed.`;
|
|
53
|
-
|
|
54
|
-
const verifySection = verifyCmd
|
|
55
|
-
? `
|
|
56
|
-
VERIFY (mandatory before release):
|
|
57
|
-
Run: ${verifyCmd}
|
|
58
|
-
If it FAILS: fix the problem. Run again. Do NOT release with failing verification.
|
|
59
|
-
If it PASSES: report the result. Then release.`
|
|
60
|
-
: '';
|
|
12
|
+
|
|
13
|
+
const readSection = buildReadSection(config, { includeTalk: true });
|
|
14
|
+
const writeSection = buildWriteSection(agentId, agentDef, config, { includeTalk: true });
|
|
15
|
+
const verifySection = buildVerifySection(config);
|
|
16
|
+
const rulesSection = buildRulesSection(agentId, config);
|
|
17
|
+
const planningDocs = buildPlanningDocsSection();
|
|
61
18
|
|
|
62
19
|
return `You are "${agentId}" — ${agentDef.name}.
|
|
63
20
|
|
|
@@ -74,22 +31,7 @@ PROJECT ROOT (strict boundary):
|
|
|
74
31
|
|
|
75
32
|
---
|
|
76
33
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
These files give you project context. Read the ones relevant to your role.
|
|
80
|
-
|
|
81
|
-
- .planning/PROJECT.md — Vision, constraints, stack decisions. PM writes this.
|
|
82
|
-
- .planning/REQUIREMENTS.md — Scoped requirements with acceptance criteria. PM writes this.
|
|
83
|
-
- .planning/ROADMAP.md — Phased delivery plan. PM maintains this.
|
|
84
|
-
- .planning/research/ — Domain research, prior art, technical investigation.
|
|
85
|
-
- .planning/phases/ — Per-phase plans (PLAN.md), reviews (REVIEW.md), test results (TESTS.md), bugs (BUGS.md).
|
|
86
|
-
- .planning/qa/TEST-COVERAGE.md — Which features are tested and how. QA maintains this.
|
|
87
|
-
- .planning/qa/BUGS.md — Open and fixed bugs with reproduction steps. QA maintains this.
|
|
88
|
-
- .planning/qa/UX-AUDIT.md — UX checklist and visual/usability issues. QA maintains this.
|
|
89
|
-
- .planning/qa/ACCEPTANCE-MATRIX.md — Requirements mapped to test status. QA maintains this.
|
|
90
|
-
- .planning/qa/REGRESSION-LOG.md — Fixed bugs and their regression tests.
|
|
91
|
-
|
|
92
|
-
When your role requires it, CREATE or UPDATE these files.
|
|
34
|
+
${planningDocs}
|
|
93
35
|
|
|
94
36
|
GET SHIT DONE FRAMEWORK (mandatory):
|
|
95
37
|
- Plan in waves and phases (not ad hoc tasks).
|
|
@@ -101,9 +43,9 @@ GET SHIT DONE FRAMEWORK (mandatory):
|
|
|
101
43
|
|
|
102
44
|
---
|
|
103
45
|
|
|
104
|
-
TEAM
|
|
46
|
+
TEAM IDS: ${agentIds.join(', ')}
|
|
105
47
|
YOUR POSITION: ${agentId} (index ${myIndex} of ${agentIds.length})
|
|
106
|
-
|
|
48
|
+
Turn assignment is handoff-driven: previous owner writes "Next owner" in TALK.md.
|
|
107
49
|
|
|
108
50
|
---
|
|
109
51
|
|
|
@@ -115,11 +57,11 @@ TURN MODE (single turn only, referee wakes you again later):
|
|
|
115
57
|
- Never run broad searches outside this project root.
|
|
116
58
|
|
|
117
59
|
1. READ lock.json.
|
|
60
|
+
Also read .agentxchain-trigger.json when present.
|
|
118
61
|
|
|
119
62
|
2. CHECK — is it my turn?
|
|
120
|
-
${
|
|
121
|
-
|
|
122
|
-
: `- It is your turn only when lock holder is null and last_released_by is "${prevAgent}".`}
|
|
63
|
+
- It is your turn when lock holder is null AND trigger.agent is "${agentId}".
|
|
64
|
+
- If trigger file is missing, you may still attempt claim; claim guardrails enforce expected next owner.
|
|
123
65
|
- If NOT your turn: STOP. Do not claim lock and do not write files.
|
|
124
66
|
|
|
125
67
|
3. CLAIM the lock:
|
|
@@ -144,10 +86,6 @@ TURN MODE (single turn only, referee wakes you again later):
|
|
|
144
86
|
---
|
|
145
87
|
|
|
146
88
|
CRITICAL RULES:
|
|
147
|
-
|
|
148
|
-
- One git commit per turn: "Turn N - ${agentId} - description"
|
|
149
|
-
- Max ${maxClaims} consecutive turns. If you have held the lock ${maxClaims} times in a row, do a short turn and release.
|
|
150
|
-
- ALWAYS release the lock. A stuck lock blocks the entire team.
|
|
151
|
-
- ALWAYS find at least one problem, risk, or question about the previous work. Blind agreement is forbidden.
|
|
89
|
+
${rulesSection}
|
|
152
90
|
- This session is SINGLE-TURN. After release, STOP and wait for the referee to wake you again.`;
|
|
153
91
|
}
|
package/src/lib/seed-prompt.js
CHANGED
|
@@ -1,46 +1,17 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
const stateSection = useSplit
|
|
10
|
-
? `READ THESE FILES EVERY TURN:
|
|
11
|
-
- "${stateFile}" — the living project state. Read fully. Primary context.
|
|
12
|
-
- "${historyFile}" — turn history. Read last 3 lines for recent context.
|
|
13
|
-
- lock.json — who holds the lock.
|
|
14
|
-
- state.json — phase and blocked status.`
|
|
15
|
-
: `READ THESE FILES EVERY TURN:
|
|
16
|
-
- "${logFile}" — the message log. Read last few messages.
|
|
17
|
-
- lock.json — who holds the lock.
|
|
18
|
-
- state.json — phase and blocked status.`;
|
|
19
|
-
|
|
20
|
-
const writeSection = useSplit
|
|
21
|
-
? `WRITE (in this order):
|
|
22
|
-
a. Do your actual work: write code, create files, run commands, make decisions.
|
|
23
|
-
b. Update "${stateFile}" — OVERWRITE with current project state.
|
|
24
|
-
c. Append ONE line to "${historyFile}":
|
|
25
|
-
{"turn": N, "agent": "${agentId}", "summary": "what you did", "files_changed": [...], "verify_result": "pass|fail|skipped", "timestamp": "ISO8601"}
|
|
26
|
-
d. Update state.json if phase or blocked status changed.`
|
|
27
|
-
: `WRITE (in this order):
|
|
28
|
-
a. Do your actual work: write code, create files, run commands, make decisions.
|
|
29
|
-
b. Append ONE message to ${logFile}:
|
|
30
|
-
---
|
|
31
|
-
### [${agentId}] (${agentDef.name}) | Turn N
|
|
32
|
-
**Status:** Current project state.
|
|
33
|
-
**Decision:** What you decided and why.
|
|
34
|
-
**Action:** What you did. Commands, files, results.
|
|
35
|
-
**Next:** What the next agent should focus on.
|
|
36
|
-
c. Update state.json if phase or blocked status changed.`;
|
|
1
|
+
import {
|
|
2
|
+
buildReadSection,
|
|
3
|
+
buildWriteSection,
|
|
4
|
+
buildVerifySection,
|
|
5
|
+
buildRulesSection,
|
|
6
|
+
buildPlanningDocsSection,
|
|
7
|
+
} from './prompt-core.js';
|
|
37
8
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
9
|
+
export function generateSeedPrompt(agentId, agentDef, config) {
|
|
10
|
+
const readSection = buildReadSection(config);
|
|
11
|
+
const writeSection = buildWriteSection(agentId, agentDef, config);
|
|
12
|
+
const verifySection = buildVerifySection(config);
|
|
13
|
+
const rulesSection = buildRulesSection(agentId, config);
|
|
14
|
+
const planningDocs = buildPlanningDocsSection();
|
|
44
15
|
|
|
45
16
|
return `You are "${agentId}" — ${agentDef.name}.
|
|
46
17
|
|
|
@@ -48,22 +19,9 @@ ${agentDef.mandate}
|
|
|
48
19
|
|
|
49
20
|
---
|
|
50
21
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
These files give you project context. Read the ones relevant to your role.
|
|
54
|
-
|
|
55
|
-
- .planning/PROJECT.md — Vision, constraints, stack decisions. PM writes this.
|
|
56
|
-
- .planning/REQUIREMENTS.md — Scoped requirements with acceptance criteria. PM writes this.
|
|
57
|
-
- .planning/ROADMAP.md — Phased delivery plan. PM maintains this.
|
|
58
|
-
- .planning/research/ — Domain research, prior art, technical investigation.
|
|
59
|
-
- .planning/phases/ — Per-phase plans (PLAN.md), reviews (REVIEW.md), test results (TESTS.md), bugs (BUGS.md).
|
|
60
|
-
- .planning/qa/TEST-COVERAGE.md — Which features are tested and how. QA maintains this.
|
|
61
|
-
- .planning/qa/BUGS.md — Open and fixed bugs with reproduction steps. QA maintains this.
|
|
62
|
-
- .planning/qa/UX-AUDIT.md — UX checklist and visual/usability issues. QA maintains this.
|
|
63
|
-
- .planning/qa/ACCEPTANCE-MATRIX.md — Requirements mapped to test status. QA maintains this.
|
|
64
|
-
- .planning/qa/REGRESSION-LOG.md — Fixed bugs and their regression tests.
|
|
22
|
+
${planningDocs}
|
|
65
23
|
|
|
66
|
-
|
|
24
|
+
The PM creates PROJECT.md, REQUIREMENTS.md, ROADMAP.md on the first turn. QA creates phase test files and updates the qa/ docs every turn. Dev reads plans and writes code. Eng Director reads code and writes reviews.
|
|
67
25
|
|
|
68
26
|
---
|
|
69
27
|
|
|
@@ -73,16 +31,12 @@ The AgentXchain Watch process coordinates your team. You don't poll or wait. Whe
|
|
|
73
31
|
|
|
74
32
|
YOUR TURN:
|
|
75
33
|
1. CLAIM: Write lock.json with holder="${agentId}", claimed_at=now. Re-read to confirm.
|
|
76
|
-
2. READ: ${
|
|
34
|
+
2. READ: ${readSection}
|
|
77
35
|
3. THINK: What did the previous agent do? What's most important for YOUR role? What's one risk?
|
|
78
36
|
4. ${writeSection}${verifySection}
|
|
79
37
|
5. RELEASE: Write lock.json: holder=null, last_released_by="${agentId}", turn_number=previous+1, claimed_at=null.
|
|
80
38
|
THIS MUST BE THE LAST THING YOU WRITE.
|
|
81
39
|
|
|
82
40
|
HARD RULES:
|
|
83
|
-
|
|
84
|
-
- One commit per turn: "Turn N - ${agentId} - description"
|
|
85
|
-
- Max ${maxClaims} consecutive turns. If limit hit, do a short turn and release.
|
|
86
|
-
- ALWAYS release the lock. A stuck lock kills the whole team.
|
|
87
|
-
- ALWAYS find at least one problem, risk, or question about the previous work. Blind agreement is forbidden.`;
|
|
41
|
+
${rulesSection}`;
|
|
88
42
|
}
|