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.
Files changed (94) hide show
  1. package/README.md +123 -154
  2. package/bin/agentxchain.js +240 -8
  3. package/dashboard/app.js +305 -0
  4. package/dashboard/components/blocked.js +145 -0
  5. package/dashboard/components/cross-repo.js +126 -0
  6. package/dashboard/components/gate.js +311 -0
  7. package/dashboard/components/hooks.js +177 -0
  8. package/dashboard/components/initiative.js +147 -0
  9. package/dashboard/components/ledger.js +165 -0
  10. package/dashboard/components/timeline.js +222 -0
  11. package/dashboard/index.html +352 -0
  12. package/package.json +16 -7
  13. package/scripts/agentxchain-autonudge.applescript +32 -5
  14. package/scripts/live-api-proxy-preflight-smoke.sh +531 -0
  15. package/scripts/publish-from-tag.sh +88 -0
  16. package/scripts/release-postflight.sh +231 -0
  17. package/scripts/release-preflight.sh +167 -0
  18. package/scripts/run-autonudge.sh +1 -1
  19. package/src/adapters/claude-code.js +7 -14
  20. package/src/adapters/cursor-local.js +17 -16
  21. package/src/commands/accept-turn.js +160 -0
  22. package/src/commands/approve-completion.js +80 -0
  23. package/src/commands/approve-transition.js +85 -0
  24. package/src/commands/branch.js +2 -2
  25. package/src/commands/claim.js +84 -9
  26. package/src/commands/config.js +16 -0
  27. package/src/commands/dashboard.js +70 -0
  28. package/src/commands/doctor.js +9 -1
  29. package/src/commands/init.js +540 -5
  30. package/src/commands/migrate.js +348 -0
  31. package/src/commands/multi.js +549 -0
  32. package/src/commands/plugin.js +157 -0
  33. package/src/commands/reject-turn.js +204 -0
  34. package/src/commands/resume.js +389 -0
  35. package/src/commands/status.js +196 -3
  36. package/src/commands/step.js +947 -0
  37. package/src/commands/stop.js +65 -33
  38. package/src/commands/template-list.js +33 -0
  39. package/src/commands/template-set.js +279 -0
  40. package/src/commands/update.js +24 -3
  41. package/src/commands/validate.js +20 -11
  42. package/src/commands/verify.js +71 -0
  43. package/src/commands/watch.js +112 -25
  44. package/src/lib/adapters/api-proxy-adapter.js +1076 -0
  45. package/src/lib/adapters/local-cli-adapter.js +337 -0
  46. package/src/lib/adapters/manual-adapter.js +169 -0
  47. package/src/lib/blocked-state.js +94 -0
  48. package/src/lib/config.js +143 -12
  49. package/src/lib/context-compressor.js +121 -0
  50. package/src/lib/context-section-parser.js +220 -0
  51. package/src/lib/coordinator-acceptance.js +428 -0
  52. package/src/lib/coordinator-config.js +461 -0
  53. package/src/lib/coordinator-dispatch.js +276 -0
  54. package/src/lib/coordinator-gates.js +487 -0
  55. package/src/lib/coordinator-hooks.js +239 -0
  56. package/src/lib/coordinator-recovery.js +523 -0
  57. package/src/lib/coordinator-state.js +365 -0
  58. package/src/lib/cross-repo-context.js +247 -0
  59. package/src/lib/dashboard/bridge-server.js +284 -0
  60. package/src/lib/dashboard/file-watcher.js +93 -0
  61. package/src/lib/dashboard/state-reader.js +96 -0
  62. package/src/lib/dispatch-bundle.js +568 -0
  63. package/src/lib/dispatch-manifest.js +252 -0
  64. package/src/lib/filter-agents.js +12 -0
  65. package/src/lib/gate-evaluator.js +285 -0
  66. package/src/lib/generate-vscode.js +158 -68
  67. package/src/lib/governed-state.js +2139 -0
  68. package/src/lib/governed-templates.js +145 -0
  69. package/src/lib/hook-runner.js +788 -0
  70. package/src/lib/next-owner.js +61 -6
  71. package/src/lib/normalized-config.js +539 -0
  72. package/src/lib/notify.js +14 -12
  73. package/src/lib/plugin-config-schema.js +192 -0
  74. package/src/lib/plugins.js +692 -0
  75. package/src/lib/prompt-core.js +108 -0
  76. package/src/lib/protocol-conformance.js +291 -0
  77. package/src/lib/reference-conformance-adapter.js +717 -0
  78. package/src/lib/repo-observer.js +597 -0
  79. package/src/lib/repo.js +0 -31
  80. package/src/lib/safe-write.js +44 -0
  81. package/src/lib/schema.js +189 -0
  82. package/src/lib/schemas/turn-result.schema.json +205 -0
  83. package/src/lib/seed-prompt-polling.js +15 -73
  84. package/src/lib/seed-prompt.js +17 -63
  85. package/src/lib/token-budget.js +206 -0
  86. package/src/lib/token-counter.js +27 -0
  87. package/src/lib/turn-paths.js +67 -0
  88. package/src/lib/turn-result-validator.js +496 -0
  89. package/src/lib/validation.js +167 -19
  90. package/src/lib/verify-command.js +72 -0
  91. package/src/templates/governed/api-service.json +31 -0
  92. package/src/templates/governed/cli-tool.json +30 -0
  93. package/src/templates/governed/generic.json +10 -0
  94. package/src/templates/governed/web-app.json +30 -0
@@ -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,291 @@
1
+ import { existsSync, readFileSync, readdirSync } from 'node:fs';
2
+ import { spawnSync } from 'node:child_process';
3
+ import { dirname, join, resolve } from 'node:path';
4
+ import { fileURLToPath } from 'node:url';
5
+
6
+ const __dirname = dirname(fileURLToPath(import.meta.url));
7
+ const DEFAULT_FIXTURE_ROOT = resolve(__dirname, '..', '..', '..', '.agentxchain-conformance', 'fixtures');
8
+ const VALID_RESPONSE_STATUSES = new Set(['pass', 'fail', 'error']);
9
+ const VALID_TIERS = new Set([1, 2, 3]);
10
+
11
+ function readJsonFile(filePath) {
12
+ return JSON.parse(readFileSync(filePath, 'utf8'));
13
+ }
14
+
15
+ function listJsonFiles(root) {
16
+ const files = [];
17
+
18
+ function walk(dir) {
19
+ for (const entry of readdirSync(dir, { withFileTypes: true })) {
20
+ const fullPath = join(dir, entry.name);
21
+ if (entry.isDirectory()) {
22
+ walk(fullPath);
23
+ } else if (entry.isFile() && entry.name.endsWith('.json')) {
24
+ files.push(fullPath);
25
+ }
26
+ }
27
+ }
28
+
29
+ walk(root);
30
+ return files.sort();
31
+ }
32
+
33
+ function validateFixtureShape(fixture, filePath) {
34
+ const errors = [];
35
+ if (!fixture || typeof fixture !== 'object' || Array.isArray(fixture)) {
36
+ return [`${filePath}: fixture must be a JSON object`];
37
+ }
38
+
39
+ const required = ['fixture_id', 'tier', 'surface', 'description', 'type', 'setup', 'input', 'expected'];
40
+ for (const field of required) {
41
+ if (!(field in fixture)) {
42
+ errors.push(`${filePath}: missing required field "${field}"`);
43
+ }
44
+ }
45
+
46
+ if (!Number.isInteger(fixture.tier) || !VALID_TIERS.has(fixture.tier)) {
47
+ errors.push(`${filePath}: tier must be 1, 2, or 3`);
48
+ }
49
+
50
+ if (!fixture.input || typeof fixture.input !== 'object' || Array.isArray(fixture.input)) {
51
+ errors.push(`${filePath}: input must be an object`);
52
+ } else if (typeof fixture.input.operation !== 'string' || !fixture.input.operation.trim()) {
53
+ errors.push(`${filePath}: input.operation must be a non-empty string`);
54
+ }
55
+
56
+ return errors;
57
+ }
58
+
59
+ function loadCapabilities(targetRoot) {
60
+ const capabilitiesPath = join(targetRoot, '.agentxchain-conformance', 'capabilities.json');
61
+ if (!existsSync(capabilitiesPath)) {
62
+ throw new Error(`Missing capabilities file at ${capabilitiesPath}`);
63
+ }
64
+
65
+ const capabilities = readJsonFile(capabilitiesPath);
66
+ const errors = [];
67
+
68
+ if (typeof capabilities.implementation !== 'string' || !capabilities.implementation.trim()) {
69
+ errors.push('capabilities.implementation must be a non-empty string');
70
+ }
71
+ if (!Array.isArray(capabilities.tiers) || capabilities.tiers.length === 0) {
72
+ errors.push('capabilities.tiers must be a non-empty array');
73
+ } else {
74
+ for (const tier of capabilities.tiers) {
75
+ if (!VALID_TIERS.has(tier)) {
76
+ errors.push(`capabilities.tiers contains invalid tier "${tier}"`);
77
+ }
78
+ }
79
+ }
80
+ if (!capabilities.adapter || typeof capabilities.adapter !== 'object' || Array.isArray(capabilities.adapter)) {
81
+ errors.push('capabilities.adapter must be an object');
82
+ } else {
83
+ if (capabilities.adapter.protocol !== 'stdio-fixture-v1') {
84
+ errors.push('capabilities.adapter.protocol must be "stdio-fixture-v1"');
85
+ }
86
+ if (!Array.isArray(capabilities.adapter.command) || capabilities.adapter.command.length === 0) {
87
+ errors.push('capabilities.adapter.command must be a non-empty array');
88
+ }
89
+ }
90
+
91
+ if (errors.length > 0) {
92
+ throw new Error(`Invalid capabilities.json: ${errors.join('; ')}`);
93
+ }
94
+
95
+ return capabilities;
96
+ }
97
+
98
+ function selectFixtureFiles(fixtureRoot, requestedTier, surface) {
99
+ if (!existsSync(fixtureRoot)) {
100
+ throw new Error(`Fixture root not found at ${fixtureRoot}`);
101
+ }
102
+
103
+ return listJsonFiles(fixtureRoot)
104
+ .filter((filePath) => {
105
+ const fixture = readJsonFile(filePath);
106
+ const shapeErrors = validateFixtureShape(fixture, filePath);
107
+ if (shapeErrors.length > 0) {
108
+ throw new Error(shapeErrors.join('; '));
109
+ }
110
+ return fixture.tier <= requestedTier && (!surface || fixture.surface === surface);
111
+ })
112
+ .map((filePath) => ({ filePath, fixture: readJsonFile(filePath) }));
113
+ }
114
+
115
+ function createTierSummary(status = 'skipped', note = null) {
116
+ return {
117
+ status,
118
+ fixtures_run: 0,
119
+ fixtures_passed: 0,
120
+ fixtures_failed: 0,
121
+ fixtures_errored: 0,
122
+ surfaces: {},
123
+ failures: [],
124
+ errors: [],
125
+ ...(note ? { note } : {}),
126
+ };
127
+ }
128
+
129
+ function ensureSurfaceSummary(tierSummary, surface) {
130
+ if (!tierSummary.surfaces[surface]) {
131
+ tierSummary.surfaces[surface] = { passed: 0, failed: 0, errored: 0 };
132
+ }
133
+ return tierSummary.surfaces[surface];
134
+ }
135
+
136
+ function executeFixture(targetRoot, adapterCommand, fixture) {
137
+ const [executable, ...args] = adapterCommand;
138
+ const result = spawnSync(executable, args, {
139
+ cwd: targetRoot,
140
+ encoding: 'utf8',
141
+ input: `${JSON.stringify(fixture)}\n`,
142
+ });
143
+
144
+ if (result.error) {
145
+ return {
146
+ status: 'error',
147
+ message: `Failed to execute adapter: ${result.error.message}`,
148
+ actual: null,
149
+ };
150
+ }
151
+
152
+ if (![0, 1, 2].includes(result.status ?? -1)) {
153
+ return {
154
+ status: 'error',
155
+ message: `Adapter exited with unsupported status ${result.status}`,
156
+ actual: {
157
+ exit_code: result.status,
158
+ stderr: result.stderr?.trim() || '',
159
+ },
160
+ };
161
+ }
162
+
163
+ let parsed;
164
+ try {
165
+ parsed = JSON.parse((result.stdout || '').trim() || '{}');
166
+ } catch (error) {
167
+ return {
168
+ status: 'error',
169
+ message: `Malformed adapter response: ${error.message}`,
170
+ actual: {
171
+ stdout: result.stdout || '',
172
+ stderr: result.stderr || '',
173
+ },
174
+ };
175
+ }
176
+
177
+ if (!VALID_RESPONSE_STATUSES.has(parsed.status)) {
178
+ return {
179
+ status: 'error',
180
+ message: 'Adapter response missing valid "status"',
181
+ actual: parsed,
182
+ };
183
+ }
184
+
185
+ const expectedExitCode = parsed.status === 'pass' ? 0 : parsed.status === 'fail' ? 1 : 2;
186
+ if (result.status !== expectedExitCode) {
187
+ return {
188
+ status: 'error',
189
+ message: `Adapter exit code ${result.status} does not match response status "${parsed.status}"`,
190
+ actual: parsed,
191
+ };
192
+ }
193
+
194
+ return parsed;
195
+ }
196
+
197
+ export function getDefaultFixtureRoot() {
198
+ return DEFAULT_FIXTURE_ROOT;
199
+ }
200
+
201
+ export function verifyProtocolConformance({
202
+ targetRoot,
203
+ requestedTier = 1,
204
+ surface = null,
205
+ fixtureRoot = DEFAULT_FIXTURE_ROOT,
206
+ }) {
207
+ if (!Number.isInteger(requestedTier) || !VALID_TIERS.has(requestedTier)) {
208
+ throw new Error(`Tier must be 1, 2, or 3. Received "${requestedTier}"`);
209
+ }
210
+
211
+ const resolvedTargetRoot = resolve(targetRoot);
212
+ const capabilities = loadCapabilities(resolvedTargetRoot);
213
+ const fixtureEntries = selectFixtureFiles(fixtureRoot, requestedTier, surface);
214
+ const claimedTiers = new Set(capabilities.tiers);
215
+ const report = {
216
+ implementation: capabilities.implementation,
217
+ protocol_version: capabilities.protocol_version || null,
218
+ tier_requested: requestedTier,
219
+ timestamp: new Date().toISOString(),
220
+ target_root: resolvedTargetRoot,
221
+ fixture_root: fixtureRoot,
222
+ results: {},
223
+ overall: 'pass',
224
+ };
225
+
226
+ for (let tier = 1; tier <= requestedTier; tier += 1) {
227
+ if (claimedTiers.has(tier)) {
228
+ report.results[`tier_${tier}`] = createTierSummary('pass');
229
+ } else {
230
+ report.results[`tier_${tier}`] = createTierSummary('skipped', `Target does not claim Tier ${tier}`);
231
+ }
232
+ }
233
+
234
+ for (const { fixture } of fixtureEntries) {
235
+ const tierKey = `tier_${fixture.tier}`;
236
+ const tierSummary = report.results[tierKey];
237
+
238
+ if (!tierSummary || tierSummary.status === 'skipped') {
239
+ continue;
240
+ }
241
+
242
+ const surfaceSummary = ensureSurfaceSummary(tierSummary, fixture.surface);
243
+ const adapterResult = executeFixture(resolvedTargetRoot, capabilities.adapter.command, fixture);
244
+
245
+ tierSummary.fixtures_run += 1;
246
+
247
+ if (adapterResult.status === 'pass') {
248
+ tierSummary.fixtures_passed += 1;
249
+ surfaceSummary.passed += 1;
250
+ continue;
251
+ }
252
+
253
+ if (adapterResult.status === 'fail') {
254
+ tierSummary.fixtures_failed += 1;
255
+ surfaceSummary.failed += 1;
256
+ tierSummary.status = 'fail';
257
+ tierSummary.failures.push({
258
+ fixture_id: fixture.fixture_id,
259
+ surface: fixture.surface,
260
+ message: adapterResult.message || 'Fixture failed',
261
+ actual: adapterResult.actual ?? null,
262
+ });
263
+ report.overall = 'fail';
264
+ continue;
265
+ }
266
+
267
+ tierSummary.fixtures_errored += 1;
268
+ surfaceSummary.errored += 1;
269
+ tierSummary.status = 'error';
270
+ tierSummary.errors.push({
271
+ fixture_id: fixture.fixture_id,
272
+ surface: fixture.surface,
273
+ message: adapterResult.message || 'Fixture errored',
274
+ actual: adapterResult.actual ?? null,
275
+ });
276
+ report.overall = 'error';
277
+ }
278
+
279
+ if (report.overall === 'pass') {
280
+ const hasHardError = Object.values(report.results).some((tier) => tier.status === 'error');
281
+ const hasFailure = Object.values(report.results).some((tier) => tier.status === 'fail');
282
+ if (hasHardError) {
283
+ report.overall = 'error';
284
+ } else if (hasFailure) {
285
+ report.overall = 'fail';
286
+ }
287
+ }
288
+
289
+ const exitCode = report.overall === 'pass' ? 0 : report.overall === 'fail' ? 1 : 2;
290
+ return { report, exitCode };
291
+ }