agentxchain 0.8.7 → 2.1.1
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 +123 -154
- package/bin/agentxchain.js +240 -8
- package/dashboard/app.js +305 -0
- package/dashboard/components/blocked.js +145 -0
- package/dashboard/components/cross-repo.js +126 -0
- package/dashboard/components/gate.js +311 -0
- package/dashboard/components/hooks.js +177 -0
- package/dashboard/components/initiative.js +147 -0
- package/dashboard/components/ledger.js +165 -0
- package/dashboard/components/timeline.js +222 -0
- package/dashboard/index.html +352 -0
- package/package.json +16 -7
- package/scripts/agentxchain-autonudge.applescript +32 -5
- package/scripts/live-api-proxy-preflight-smoke.sh +531 -0
- package/scripts/publish-from-tag.sh +88 -0
- package/scripts/release-postflight.sh +231 -0
- package/scripts/release-preflight.sh +167 -0
- package/scripts/run-autonudge.sh +1 -1
- package/src/adapters/claude-code.js +7 -14
- package/src/adapters/cursor-local.js +17 -16
- package/src/commands/accept-turn.js +160 -0
- package/src/commands/approve-completion.js +80 -0
- package/src/commands/approve-transition.js +85 -0
- package/src/commands/branch.js +2 -2
- package/src/commands/claim.js +84 -9
- package/src/commands/config.js +16 -0
- package/src/commands/dashboard.js +70 -0
- package/src/commands/doctor.js +9 -1
- package/src/commands/init.js +540 -5
- package/src/commands/migrate.js +348 -0
- package/src/commands/multi.js +549 -0
- package/src/commands/plugin.js +157 -0
- package/src/commands/reject-turn.js +204 -0
- package/src/commands/resume.js +389 -0
- package/src/commands/status.js +196 -3
- package/src/commands/step.js +947 -0
- package/src/commands/stop.js +65 -33
- package/src/commands/template-list.js +33 -0
- package/src/commands/template-set.js +279 -0
- package/src/commands/update.js +24 -3
- package/src/commands/validate.js +20 -11
- package/src/commands/verify.js +71 -0
- package/src/commands/watch.js +112 -25
- package/src/lib/adapters/api-proxy-adapter.js +1076 -0
- package/src/lib/adapters/local-cli-adapter.js +337 -0
- package/src/lib/adapters/manual-adapter.js +169 -0
- package/src/lib/blocked-state.js +94 -0
- package/src/lib/config.js +143 -12
- package/src/lib/context-compressor.js +121 -0
- package/src/lib/context-section-parser.js +220 -0
- package/src/lib/coordinator-acceptance.js +428 -0
- package/src/lib/coordinator-config.js +461 -0
- package/src/lib/coordinator-dispatch.js +276 -0
- package/src/lib/coordinator-gates.js +487 -0
- package/src/lib/coordinator-hooks.js +239 -0
- package/src/lib/coordinator-recovery.js +523 -0
- package/src/lib/coordinator-state.js +365 -0
- package/src/lib/cross-repo-context.js +247 -0
- package/src/lib/dashboard/bridge-server.js +284 -0
- package/src/lib/dashboard/file-watcher.js +93 -0
- package/src/lib/dashboard/state-reader.js +96 -0
- package/src/lib/dispatch-bundle.js +568 -0
- package/src/lib/dispatch-manifest.js +252 -0
- package/src/lib/filter-agents.js +12 -0
- package/src/lib/gate-evaluator.js +285 -0
- package/src/lib/generate-vscode.js +158 -68
- package/src/lib/governed-state.js +2139 -0
- package/src/lib/governed-templates.js +145 -0
- package/src/lib/hook-runner.js +788 -0
- package/src/lib/next-owner.js +61 -6
- package/src/lib/normalized-config.js +539 -0
- package/src/lib/notify.js +14 -12
- package/src/lib/plugin-config-schema.js +192 -0
- package/src/lib/plugins.js +692 -0
- package/src/lib/prompt-core.js +108 -0
- package/src/lib/protocol-conformance.js +291 -0
- package/src/lib/reference-conformance-adapter.js +717 -0
- package/src/lib/repo-observer.js +597 -0
- package/src/lib/repo.js +0 -31
- package/src/lib/safe-write.js +44 -0
- package/src/lib/schema.js +189 -0
- package/src/lib/schemas/turn-result.schema.json +205 -0
- package/src/lib/seed-prompt-polling.js +15 -73
- package/src/lib/seed-prompt.js +17 -63
- package/src/lib/token-budget.js +206 -0
- package/src/lib/token-counter.js +27 -0
- package/src/lib/turn-paths.js +67 -0
- package/src/lib/turn-result-validator.js +496 -0
- package/src/lib/validation.js +167 -19
- package/src/lib/verify-command.js +72 -0
- package/src/templates/governed/api-service.json +31 -0
- package/src/templates/governed/cli-tool.json +30 -0
- package/src/templates/governed/generic.json +10 -0
- package/src/templates/governed/web-app.json +30 -0
|
@@ -0,0 +1,189 @@
|
|
|
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 validateGovernedStateSchema(data) {
|
|
33
|
+
const errors = [];
|
|
34
|
+
const VALID_RUN_STATUSES = ['idle', 'active', 'paused', 'blocked', 'completed', 'failed'];
|
|
35
|
+
const isV1_1 = data?.schema_version === '1.1';
|
|
36
|
+
const hasLegacyCurrentTurn = Object.prototype.hasOwnProperty.call(data || {}, 'current_turn');
|
|
37
|
+
|
|
38
|
+
function validateTurn(turn, label) {
|
|
39
|
+
if (typeof turn !== 'object' || Array.isArray(turn)) {
|
|
40
|
+
errors.push(`${label} must be an object`);
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (typeof turn.turn_id !== 'string' || !turn.turn_id.trim()) errors.push(`${label}.turn_id must be a non-empty string`);
|
|
45
|
+
if (typeof turn.assigned_role !== 'string' || !turn.assigned_role.trim()) errors.push(`${label}.assigned_role must be a non-empty string`);
|
|
46
|
+
if (typeof turn.status !== 'string' || !turn.status.trim()) errors.push(`${label}.status must be a non-empty string`);
|
|
47
|
+
if (typeof turn.runtime_id !== 'string' || !turn.runtime_id.trim()) errors.push(`${label}.runtime_id must be a non-empty string`);
|
|
48
|
+
if (typeof turn.attempt !== 'number' || !Number.isInteger(turn.attempt)) errors.push(`${label}.attempt must be an integer`);
|
|
49
|
+
if (isV1_1 && (!Number.isInteger(turn.assigned_sequence) || turn.assigned_sequence < 1)) {
|
|
50
|
+
errors.push(`${label}.assigned_sequence must be an integer >= 1`);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (data === null || typeof data !== 'object') {
|
|
55
|
+
return { ok: false, errors: ['governed state must be a JSON object'] };
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const hasRunId = typeof data.run_id === 'string' && data.run_id.trim();
|
|
59
|
+
const hasNoActiveTurn = isV1_1
|
|
60
|
+
? data.active_turns && typeof data.active_turns === 'object' && !Array.isArray(data.active_turns)
|
|
61
|
+
? Object.keys(data.active_turns).length === 0
|
|
62
|
+
: false
|
|
63
|
+
: data.current_turn === null;
|
|
64
|
+
const isUninitializedIdle = data.run_id === null && data.status === 'idle' && hasNoActiveTurn;
|
|
65
|
+
if (!hasRunId && !isUninitializedIdle) {
|
|
66
|
+
errors.push('run_id must be a non-empty string, or null only for an idle state with no active turn');
|
|
67
|
+
}
|
|
68
|
+
if (typeof data.project_id !== 'string' || !data.project_id.trim()) errors.push('project_id must be a non-empty string');
|
|
69
|
+
if (typeof data.status !== 'string' || !data.status.trim()) {
|
|
70
|
+
errors.push('status must be a non-empty string');
|
|
71
|
+
} else if (!VALID_RUN_STATUSES.includes(data.status)) {
|
|
72
|
+
errors.push(`status must be one of: ${VALID_RUN_STATUSES.join(', ')}`);
|
|
73
|
+
}
|
|
74
|
+
if (typeof data.phase !== 'string' || !data.phase.trim()) errors.push('phase must be a non-empty string');
|
|
75
|
+
|
|
76
|
+
if (isV1_1) {
|
|
77
|
+
if (hasLegacyCurrentTurn) {
|
|
78
|
+
errors.push('current_turn is not allowed when schema_version is "1.1"; use active_turns');
|
|
79
|
+
}
|
|
80
|
+
if (!data.active_turns || typeof data.active_turns !== 'object' || Array.isArray(data.active_turns)) {
|
|
81
|
+
errors.push('active_turns must be an object when schema_version is "1.1"');
|
|
82
|
+
} else {
|
|
83
|
+
for (const [turnId, turn] of Object.entries(data.active_turns)) {
|
|
84
|
+
validateTurn(turn, `active_turns.${turnId}`);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
if (!Number.isInteger(data.turn_sequence) || data.turn_sequence < 0) {
|
|
88
|
+
errors.push('turn_sequence must be an integer >= 0 when schema_version is "1.1"');
|
|
89
|
+
}
|
|
90
|
+
} else if ('current_turn' in data && data.current_turn !== null) {
|
|
91
|
+
validateTurn(data.current_turn, 'current_turn');
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if ('phase_gate_status' in data && data.phase_gate_status !== null && typeof data.phase_gate_status !== 'object') {
|
|
95
|
+
errors.push('phase_gate_status must be an object');
|
|
96
|
+
}
|
|
97
|
+
if ('budget_status' in data && data.budget_status !== null && typeof data.budget_status !== 'object') {
|
|
98
|
+
errors.push('budget_status must be an object');
|
|
99
|
+
}
|
|
100
|
+
if (isV1_1 && 'budget_reservations' in data && data.budget_reservations !== null && typeof data.budget_reservations !== 'object') {
|
|
101
|
+
errors.push('budget_reservations must be an object when provided');
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (data.status === 'blocked') {
|
|
105
|
+
if (typeof data.blocked_on !== 'string' || !data.blocked_on.trim()) {
|
|
106
|
+
errors.push('blocked_on must be a non-empty string when status is "blocked"');
|
|
107
|
+
}
|
|
108
|
+
if (!data.blocked_reason || typeof data.blocked_reason !== 'object') {
|
|
109
|
+
errors.push('blocked_reason must be an object when status is "blocked"');
|
|
110
|
+
} else {
|
|
111
|
+
if (typeof data.blocked_reason.category !== 'string' || !data.blocked_reason.category.trim()) {
|
|
112
|
+
errors.push('blocked_reason.category must be a non-empty string');
|
|
113
|
+
}
|
|
114
|
+
if (typeof data.blocked_reason.blocked_at !== 'string' || !data.blocked_reason.blocked_at.trim()) {
|
|
115
|
+
errors.push('blocked_reason.blocked_at must be a non-empty string');
|
|
116
|
+
}
|
|
117
|
+
if (!('turn_id' in data.blocked_reason) || (data.blocked_reason.turn_id !== null && typeof data.blocked_reason.turn_id !== 'string')) {
|
|
118
|
+
errors.push('blocked_reason.turn_id must be a string or null');
|
|
119
|
+
}
|
|
120
|
+
const recovery = data.blocked_reason.recovery;
|
|
121
|
+
if (!recovery || typeof recovery !== 'object') {
|
|
122
|
+
errors.push('blocked_reason.recovery must be an object');
|
|
123
|
+
} else {
|
|
124
|
+
if (typeof recovery.typed_reason !== 'string' || !recovery.typed_reason.trim()) errors.push('blocked_reason.recovery.typed_reason must be a non-empty string');
|
|
125
|
+
if (typeof recovery.owner !== 'string' || !recovery.owner.trim()) errors.push('blocked_reason.recovery.owner must be a non-empty string');
|
|
126
|
+
if (typeof recovery.recovery_action !== 'string' || !recovery.recovery_action.trim()) errors.push('blocked_reason.recovery.recovery_action must be a non-empty string');
|
|
127
|
+
if (typeof recovery.turn_retained !== 'boolean') errors.push('blocked_reason.recovery.turn_retained must be a boolean');
|
|
128
|
+
if ('detail' in recovery && recovery.detail !== null && typeof recovery.detail !== 'string') {
|
|
129
|
+
errors.push('blocked_reason.recovery.detail must be a string or null');
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (data.status === 'paused' && !data.pending_phase_transition && !data.pending_run_completion) {
|
|
136
|
+
errors.push('paused state must include pending_phase_transition or pending_run_completion');
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if (data.status === 'active' && data.blocked_on != null) {
|
|
140
|
+
errors.push('active state must not include blocked_on');
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
return { ok: errors.length === 0, errors };
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export function validateProjectStateSchema(data) {
|
|
147
|
+
if (data && typeof data === 'object' && ('run_id' in data || 'current_turn' in data || 'active_turns' in data || 'phase_gate_status' in data)) {
|
|
148
|
+
return validateGovernedStateSchema(data);
|
|
149
|
+
}
|
|
150
|
+
return validateStateSchema(data);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export function validateConfigSchema(data) {
|
|
154
|
+
const errors = [];
|
|
155
|
+
if (data === null || typeof data !== 'object') {
|
|
156
|
+
return { ok: false, errors: ['agentxchain.json must be a JSON object'] };
|
|
157
|
+
}
|
|
158
|
+
if (data.version !== 3) errors.push('version must be 3');
|
|
159
|
+
if (typeof data.project !== 'string' || !data.project.trim()) errors.push('project must be a non-empty string');
|
|
160
|
+
if (!data.agents || typeof data.agents !== 'object') {
|
|
161
|
+
errors.push('agents must be an object');
|
|
162
|
+
} else {
|
|
163
|
+
for (const [id, agent] of Object.entries(data.agents)) {
|
|
164
|
+
if (!/^[a-z0-9_-]+$/.test(id)) errors.push(`Invalid agent id: "${id}" (must be lowercase alphanumeric, hyphens, underscores)`);
|
|
165
|
+
if (!agent || typeof agent !== 'object') { errors.push(`Agent "${id}" must be an object`); continue; }
|
|
166
|
+
if (typeof agent.name !== 'string' || !agent.name.trim()) errors.push(`Agent "${id}": name must be a non-empty string`);
|
|
167
|
+
if (typeof agent.mandate !== 'string' || !agent.mandate.trim()) errors.push(`Agent "${id}": mandate must be a non-empty string`);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
return { ok: errors.length === 0, errors };
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Safely parse JSON with schema validation.
|
|
175
|
+
* Returns { ok, data, errors }.
|
|
176
|
+
*/
|
|
177
|
+
export function safeParseJson(raw, validator) {
|
|
178
|
+
let data;
|
|
179
|
+
try {
|
|
180
|
+
data = JSON.parse(raw);
|
|
181
|
+
} catch (err) {
|
|
182
|
+
return { ok: false, data: null, errors: [`Invalid JSON: ${err.message}`] };
|
|
183
|
+
}
|
|
184
|
+
if (validator) {
|
|
185
|
+
const result = validator(data);
|
|
186
|
+
return { ok: result.ok, data, errors: result.errors };
|
|
187
|
+
}
|
|
188
|
+
return { ok: true, data, errors: [] };
|
|
189
|
+
}
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://agentxchain.dev/schemas/turn-result/v1",
|
|
4
|
+
"title": "AgentXchain Turn Result v1",
|
|
5
|
+
"description": "Structured output produced by every agent turn. The orchestrator validates and accepts/rejects based on this contract.",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"required": [
|
|
8
|
+
"schema_version",
|
|
9
|
+
"run_id",
|
|
10
|
+
"turn_id",
|
|
11
|
+
"role",
|
|
12
|
+
"runtime_id",
|
|
13
|
+
"status",
|
|
14
|
+
"summary",
|
|
15
|
+
"decisions",
|
|
16
|
+
"objections",
|
|
17
|
+
"files_changed",
|
|
18
|
+
"verification",
|
|
19
|
+
"artifact",
|
|
20
|
+
"proposed_next_role"
|
|
21
|
+
],
|
|
22
|
+
"additionalProperties": false,
|
|
23
|
+
"properties": {
|
|
24
|
+
"schema_version": {
|
|
25
|
+
"const": "1.0",
|
|
26
|
+
"description": "Must be '1.0' for v1 turn results."
|
|
27
|
+
},
|
|
28
|
+
"run_id": {
|
|
29
|
+
"type": "string",
|
|
30
|
+
"minLength": 1,
|
|
31
|
+
"description": "Must match the current run. Format: run_XXXX..."
|
|
32
|
+
},
|
|
33
|
+
"turn_id": {
|
|
34
|
+
"type": "string",
|
|
35
|
+
"minLength": 1,
|
|
36
|
+
"description": "Must match the current turn assignment. Format: turn-NNNN"
|
|
37
|
+
},
|
|
38
|
+
"role": {
|
|
39
|
+
"type": "string",
|
|
40
|
+
"pattern": "^[a-z0-9_-]+$",
|
|
41
|
+
"description": "Must match the role assigned for this turn."
|
|
42
|
+
},
|
|
43
|
+
"runtime_id": {
|
|
44
|
+
"type": "string",
|
|
45
|
+
"minLength": 1,
|
|
46
|
+
"description": "The runtime that executed this turn."
|
|
47
|
+
},
|
|
48
|
+
"status": {
|
|
49
|
+
"enum": ["completed", "blocked", "needs_human", "failed"],
|
|
50
|
+
"description": "Outcome of this turn."
|
|
51
|
+
},
|
|
52
|
+
"summary": {
|
|
53
|
+
"type": "string",
|
|
54
|
+
"minLength": 1,
|
|
55
|
+
"description": "Human-readable summary of what happened."
|
|
56
|
+
},
|
|
57
|
+
"decisions": {
|
|
58
|
+
"type": "array",
|
|
59
|
+
"items": {
|
|
60
|
+
"type": "object",
|
|
61
|
+
"required": ["id", "category", "statement", "rationale"],
|
|
62
|
+
"additionalProperties": false,
|
|
63
|
+
"properties": {
|
|
64
|
+
"id": {
|
|
65
|
+
"type": "string",
|
|
66
|
+
"pattern": "^DEC-\\d+$",
|
|
67
|
+
"description": "Decision ID, e.g. DEC-014"
|
|
68
|
+
},
|
|
69
|
+
"category": {
|
|
70
|
+
"enum": ["implementation", "architecture", "scope", "process", "quality", "release"]
|
|
71
|
+
},
|
|
72
|
+
"statement": {
|
|
73
|
+
"type": "string",
|
|
74
|
+
"minLength": 1
|
|
75
|
+
},
|
|
76
|
+
"rationale": {
|
|
77
|
+
"type": "string",
|
|
78
|
+
"minLength": 1
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
},
|
|
83
|
+
"objections": {
|
|
84
|
+
"type": "array",
|
|
85
|
+
"items": {
|
|
86
|
+
"type": "object",
|
|
87
|
+
"required": ["id", "severity", "statement"],
|
|
88
|
+
"additionalProperties": false,
|
|
89
|
+
"properties": {
|
|
90
|
+
"id": {
|
|
91
|
+
"type": "string",
|
|
92
|
+
"pattern": "^OBJ-\\d+$",
|
|
93
|
+
"description": "Objection ID, e.g. OBJ-007"
|
|
94
|
+
},
|
|
95
|
+
"severity": {
|
|
96
|
+
"enum": ["low", "medium", "high", "blocking"]
|
|
97
|
+
},
|
|
98
|
+
"against_turn_id": {
|
|
99
|
+
"type": "string",
|
|
100
|
+
"description": "Turn that this objection challenges."
|
|
101
|
+
},
|
|
102
|
+
"against_decision_id": {
|
|
103
|
+
"type": "string",
|
|
104
|
+
"description": "Decision ID being challenged."
|
|
105
|
+
},
|
|
106
|
+
"statement": {
|
|
107
|
+
"type": "string",
|
|
108
|
+
"minLength": 1
|
|
109
|
+
},
|
|
110
|
+
"proposed_resolution": {
|
|
111
|
+
"type": "string"
|
|
112
|
+
},
|
|
113
|
+
"status": {
|
|
114
|
+
"enum": ["raised", "acknowledged", "resolved", "escalated", "resolved_by_human", "resolved_by_director"],
|
|
115
|
+
"default": "raised"
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
},
|
|
120
|
+
"files_changed": {
|
|
121
|
+
"type": "array",
|
|
122
|
+
"items": { "type": "string" },
|
|
123
|
+
"description": "Paths of files modified during this turn. Must match observed diff for authoritative runtimes."
|
|
124
|
+
},
|
|
125
|
+
"artifacts_created": {
|
|
126
|
+
"type": "array",
|
|
127
|
+
"items": { "type": "string" },
|
|
128
|
+
"description": "Paths of planning/review artifacts created (under .planning/ or .agentxchain/)."
|
|
129
|
+
},
|
|
130
|
+
"verification": {
|
|
131
|
+
"type": "object",
|
|
132
|
+
"required": ["status"],
|
|
133
|
+
"additionalProperties": false,
|
|
134
|
+
"properties": {
|
|
135
|
+
"status": {
|
|
136
|
+
"enum": ["pass", "fail", "skipped"],
|
|
137
|
+
"description": "Raw status from agent. Orchestrator may normalize to attested_pass or not_reproducible."
|
|
138
|
+
},
|
|
139
|
+
"commands": {
|
|
140
|
+
"type": "array",
|
|
141
|
+
"items": { "type": "string" },
|
|
142
|
+
"description": "Verification commands that were run."
|
|
143
|
+
},
|
|
144
|
+
"evidence_summary": {
|
|
145
|
+
"type": "string",
|
|
146
|
+
"description": "Human-readable description of verification evidence."
|
|
147
|
+
},
|
|
148
|
+
"machine_evidence": {
|
|
149
|
+
"type": "array",
|
|
150
|
+
"items": {
|
|
151
|
+
"type": "object",
|
|
152
|
+
"required": ["command", "exit_code"],
|
|
153
|
+
"properties": {
|
|
154
|
+
"command": { "type": "string" },
|
|
155
|
+
"exit_code": { "type": "integer" }
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
},
|
|
161
|
+
"artifact": {
|
|
162
|
+
"type": "object",
|
|
163
|
+
"required": ["type"],
|
|
164
|
+
"additionalProperties": false,
|
|
165
|
+
"properties": {
|
|
166
|
+
"type": {
|
|
167
|
+
"enum": ["workspace", "patch", "commit", "review"],
|
|
168
|
+
"description": "workspace = direct local changes, patch = unified diff, commit = git commit, review = no code changes"
|
|
169
|
+
},
|
|
170
|
+
"ref": {
|
|
171
|
+
"type": ["string", "null"],
|
|
172
|
+
"description": "Git SHA, patch path, or null for review-only."
|
|
173
|
+
},
|
|
174
|
+
"diff_summary": {
|
|
175
|
+
"type": "string"
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
},
|
|
179
|
+
"proposed_next_role": {
|
|
180
|
+
"type": "string",
|
|
181
|
+
"pattern": "^[a-z0-9_-]+$|^human$",
|
|
182
|
+
"description": "Role that should work next. Must be in routing allowlist or 'human'."
|
|
183
|
+
},
|
|
184
|
+
"phase_transition_request": {
|
|
185
|
+
"type": ["string", "null"],
|
|
186
|
+
"description": "If the agent believes the phase should advance, name the target phase."
|
|
187
|
+
},
|
|
188
|
+
"run_completion_request": {
|
|
189
|
+
"type": ["boolean", "null"],
|
|
190
|
+
"description": "Set to true only in the final phase when the run should complete. Mutually exclusive with phase_transition_request."
|
|
191
|
+
},
|
|
192
|
+
"needs_human_reason": {
|
|
193
|
+
"type": ["string", "null"],
|
|
194
|
+
"description": "If status is needs_human, explain why."
|
|
195
|
+
},
|
|
196
|
+
"cost": {
|
|
197
|
+
"type": "object",
|
|
198
|
+
"properties": {
|
|
199
|
+
"input_tokens": { "type": "integer", "minimum": 0 },
|
|
200
|
+
"output_tokens": { "type": "integer", "minimum": 0 },
|
|
201
|
+
"usd": { "type": "number", "minimum": 0 }
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
@@ -1,59 +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
12
|
|
|
13
|
-
const readSection =
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
- lock.json — who holds the lock.
|
|
19
|
-
- state.json — phase and blocked status.`
|
|
20
|
-
: `READ THESE FILES:
|
|
21
|
-
- "${logFile}" — the message log. Read last few messages.
|
|
22
|
-
- "${talkFile}" — team handoff updates. Read the latest 5 entries.
|
|
23
|
-
- lock.json — who holds the lock.
|
|
24
|
-
- state.json — phase and blocked status.`;
|
|
25
|
-
|
|
26
|
-
const writeSection = useSplit
|
|
27
|
-
? `WRITE (in this order):
|
|
28
|
-
a. Do your actual work: write code, create files, run commands, make decisions.
|
|
29
|
-
b. Update "${stateFile}" — OVERWRITE with current project state.
|
|
30
|
-
c. Append ONE line to "${historyFile}":
|
|
31
|
-
{"turn": N, "agent": "${agentId}", "summary": "what you did", "files_changed": [...], "verify_result": "pass|fail|skipped", "timestamp": "ISO8601"}
|
|
32
|
-
d. Append ONE handoff entry to "${talkFile}" with:
|
|
33
|
-
Turn, Status, Decision, Action, Risks/Questions, Next owner.
|
|
34
|
-
IMPORTANT: "Next owner" must be a valid agent id from [${agentIds.join(', ')}].
|
|
35
|
-
e. Update state.json if phase or blocked status changed.`
|
|
36
|
-
: `WRITE (in this order):
|
|
37
|
-
a. Do your actual work: write code, create files, run commands, make decisions.
|
|
38
|
-
b. Append ONE message to ${logFile}:
|
|
39
|
-
---
|
|
40
|
-
### [${agentId}] (${agentDef.name}) | Turn N
|
|
41
|
-
**Status:** Current project state.
|
|
42
|
-
**Decision:** What you decided and why.
|
|
43
|
-
**Action:** What you did. Commands, files, results.
|
|
44
|
-
**Next:** What the next agent should focus on.
|
|
45
|
-
c. Append ONE handoff entry to "${talkFile}" with:
|
|
46
|
-
Turn, Status, Decision, Action, Risks/Questions, Next owner.
|
|
47
|
-
IMPORTANT: "Next owner" must be a valid agent id from [${agentIds.join(', ')}].
|
|
48
|
-
d. Update state.json if phase or blocked status changed.`;
|
|
49
|
-
|
|
50
|
-
const verifySection = verifyCmd
|
|
51
|
-
? `
|
|
52
|
-
VERIFY (mandatory before release):
|
|
53
|
-
Run: ${verifyCmd}
|
|
54
|
-
If it FAILS: fix the problem. Run again. Do NOT release with failing verification.
|
|
55
|
-
If it PASSES: report the result. Then release.`
|
|
56
|
-
: '';
|
|
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();
|
|
57
18
|
|
|
58
19
|
return `You are "${agentId}" — ${agentDef.name}.
|
|
59
20
|
|
|
@@ -70,22 +31,7 @@ PROJECT ROOT (strict boundary):
|
|
|
70
31
|
|
|
71
32
|
---
|
|
72
33
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
These files give you project context. Read the ones relevant to your role.
|
|
76
|
-
|
|
77
|
-
- .planning/PROJECT.md — Vision, constraints, stack decisions. PM writes this.
|
|
78
|
-
- .planning/REQUIREMENTS.md — Scoped requirements with acceptance criteria. PM writes this.
|
|
79
|
-
- .planning/ROADMAP.md — Phased delivery plan. PM maintains this.
|
|
80
|
-
- .planning/research/ — Domain research, prior art, technical investigation.
|
|
81
|
-
- .planning/phases/ — Per-phase plans (PLAN.md), reviews (REVIEW.md), test results (TESTS.md), bugs (BUGS.md).
|
|
82
|
-
- .planning/qa/TEST-COVERAGE.md — Which features are tested and how. QA maintains this.
|
|
83
|
-
- .planning/qa/BUGS.md — Open and fixed bugs with reproduction steps. QA maintains this.
|
|
84
|
-
- .planning/qa/UX-AUDIT.md — UX checklist and visual/usability issues. QA maintains this.
|
|
85
|
-
- .planning/qa/ACCEPTANCE-MATRIX.md — Requirements mapped to test status. QA maintains this.
|
|
86
|
-
- .planning/qa/REGRESSION-LOG.md — Fixed bugs and their regression tests.
|
|
87
|
-
|
|
88
|
-
When your role requires it, CREATE or UPDATE these files.
|
|
34
|
+
${planningDocs}
|
|
89
35
|
|
|
90
36
|
GET SHIT DONE FRAMEWORK (mandatory):
|
|
91
37
|
- Plan in waves and phases (not ad hoc tasks).
|
|
@@ -140,10 +86,6 @@ TURN MODE (single turn only, referee wakes you again later):
|
|
|
140
86
|
---
|
|
141
87
|
|
|
142
88
|
CRITICAL RULES:
|
|
143
|
-
|
|
144
|
-
- One git commit per turn: "Turn N - ${agentId} - description"
|
|
145
|
-
- Max ${maxClaims} consecutive turns. If you have held the lock ${maxClaims} times in a row, do a short turn and release.
|
|
146
|
-
- ALWAYS release the lock. A stuck lock blocks the entire team.
|
|
147
|
-
- ALWAYS find at least one problem, risk, or question about the previous work. Blind agreement is forbidden.
|
|
89
|
+
${rulesSection}
|
|
148
90
|
- This session is SINGLE-TURN. After release, STOP and wait for the referee to wake you again.`;
|
|
149
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
|
}
|