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.
@@ -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 { execSync } from 'child_process';
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
- // macOS notification
10
+ const safeMsg = sanitize(message);
11
+ const safeTitle = sanitize(title);
12
+
8
13
  if (process.platform === 'darwin') {
9
14
  try {
10
- execSync(`osascript -e 'display notification "${message}" with title "${title}"'`);
11
- } catch {
12
- // osascript not available or permission denied
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
- execSync(`notify-send "${title}" "${message}"`);
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
- export function generatePollingPrompt(agentId, agentDef, config, projectRoot = '.') {
2
- const logFile = config.log || 'log.md';
3
- const talkFile = config.talk_file || 'TALK.md';
4
- const maxClaims = config.rules?.max_consecutive_claims || 2;
5
- const verifyCmd = config.rules?.verify_command || null;
6
- const stateFile = config.state_file || 'state.md';
7
- const historyFile = config.history_file || 'history.jsonl';
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
- const prevAgent = myIndex === 0 ? null : agentIds[myIndex - 1];
13
- const isFirstAgent = myIndex === 0;
14
-
15
- const turnCondition = isFirstAgent
16
- ? `It is YOUR turn when lock.json shows holder=null AND (last_released_by is null, "human", "system", OR the LAST agent in the rotation: "${agentIds[agentIds.length - 1]}")`
17
- : `It is YOUR turn when lock.json shows holder=null AND last_released_by="${prevAgent}"`;
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
- PROJECT DOCUMENTATION (.planning/ folder):
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 ROTATION: ${agentIds.join(' ')} → (repeat)
46
+ TEAM IDS: ${agentIds.join(', ')}
105
47
  YOUR POSITION: ${agentId} (index ${myIndex} of ${agentIds.length})
106
- ${turnCondition}
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
- ${isFirstAgent
121
- ? `- It is your turn only when lock holder is null and last_released_by is null/human/system/${agentIds[agentIds.length - 1]}.`
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
- - Never write files or code without holding the lock. Reading is always allowed.
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
  }
@@ -1,46 +1,17 @@
1
- export function generateSeedPrompt(agentId, agentDef, config) {
2
- const logFile = config.log || 'log.md';
3
- const maxClaims = config.rules?.max_consecutive_claims || 2;
4
- const verifyCmd = config.rules?.verify_command || null;
5
- const stateFile = config.state_file || 'state.md';
6
- const historyFile = config.history_file || 'history.jsonl';
7
- const useSplit = config.state_file || config.history_file;
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
- const verifySection = verifyCmd
39
- ? `\nVERIFY (mandatory):
40
- Before releasing the lock, run: ${verifyCmd}
41
- If it FAILS: fix the problem. Run again. Do NOT release with failing verification.
42
- If it PASSES: report the result. Then release.`
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
- PROJECT DOCUMENTATION (.planning/ folder):
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
- When your role requires it, CREATE or UPDATE these files. 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.
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: ${stateSection}
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
- - Never write without holding the lock.
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
  }