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,531 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ usage() {
5
+ cat <<'EOF'
6
+ Usage: bash cli/scripts/live-api-proxy-preflight-smoke.sh [--mode happy|overflow|both] [--keep-temp]
7
+
8
+ Runs a live smoke harness for api_proxy preflight tokenization using a temp copy
9
+ of examples/governed-todo-app.
10
+ EOF
11
+ }
12
+
13
+ MODE="both"
14
+ KEEP_TEMP=0
15
+
16
+ while [[ $# -gt 0 ]]; do
17
+ case "$1" in
18
+ --mode)
19
+ MODE="${2:-}"
20
+ shift 2
21
+ ;;
22
+ --keep-temp)
23
+ KEEP_TEMP=1
24
+ shift
25
+ ;;
26
+ -h|--help)
27
+ usage
28
+ exit 0
29
+ ;;
30
+ *)
31
+ echo "Unknown argument: $1" >&2
32
+ usage >&2
33
+ exit 1
34
+ ;;
35
+ esac
36
+ done
37
+
38
+ case "$MODE" in
39
+ happy|overflow|both) ;;
40
+ *)
41
+ echo "Invalid --mode: $MODE" >&2
42
+ usage >&2
43
+ exit 1
44
+ ;;
45
+ esac
46
+
47
+ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
48
+ CLI_DIR="${SCRIPT_DIR}/.."
49
+ REPO_ROOT="$(cd "${CLI_DIR}/.." && pwd)"
50
+ EXAMPLE_DIR="${REPO_ROOT}/examples/governed-todo-app"
51
+
52
+ load_repo_env() {
53
+ local env_file="${REPO_ROOT}/.env"
54
+ if [[ ! -f "$env_file" ]]; then
55
+ return 0
56
+ fi
57
+
58
+ while IFS= read -r line || [[ -n "$line" ]]; do
59
+ line="${line#"${line%%[![:space:]]*}"}"
60
+ [[ -z "$line" || "${line:0:1}" == "#" ]] && continue
61
+ [[ "$line" != *=* ]] && continue
62
+
63
+ local key="${line%%=*}"
64
+ local value="${line#*=}"
65
+ key="${key%"${key##*[![:space:]]}"}"
66
+ value="${value#"${value%%[![:space:]]*}"}"
67
+ value="${value%"${value##*[![:space:]]}"}"
68
+
69
+ if [[ "${value:0:1}" == "\"" && "${value: -1}" == "\"" ]]; then
70
+ value="${value:1:${#value}-2}"
71
+ elif [[ "${value:0:1}" == "'" && "${value: -1}" == "'" ]]; then
72
+ value="${value:1:${#value}-2}"
73
+ fi
74
+
75
+ if [[ -z "${!key:-}" ]]; then
76
+ export "$key=$value"
77
+ fi
78
+ done < "$env_file"
79
+ }
80
+
81
+ require_cmd() {
82
+ if ! command -v "$1" >/dev/null 2>&1; then
83
+ echo "Required command not found: $1" >&2
84
+ exit 1
85
+ fi
86
+ }
87
+
88
+ make_workspace() {
89
+ local scenario="$1"
90
+ local workspace
91
+ workspace="$(mktemp -d "${TMPDIR:-/tmp}/agentxchain-preflight-${scenario}-XXXXXX")"
92
+ cp -R "${EXAMPLE_DIR}/." "$workspace"
93
+ echo "$workspace"
94
+ }
95
+
96
+ patch_runtime_config() {
97
+ local workspace="$1"
98
+ local scenario="$2"
99
+
100
+ node --input-type=commonjs - "$workspace" "$scenario" <<'NODE'
101
+ const fs = require('fs');
102
+ const path = require('path');
103
+
104
+ const workspace = process.argv[2];
105
+ const scenario = process.argv[3];
106
+ const configPath = path.join(workspace, 'agentxchain.json');
107
+ const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
108
+ const runtime = config.runtimes['api-qa'];
109
+
110
+ if (!runtime || runtime.type !== 'api_proxy') {
111
+ throw new Error('api-qa runtime missing or not api_proxy');
112
+ }
113
+
114
+ runtime.preflight_tokenization = {
115
+ enabled: true,
116
+ tokenizer: 'provider_local',
117
+ safety_margin_tokens: scenario === 'happy' ? 1024 : 64,
118
+ };
119
+ runtime.context_window_tokens = scenario === 'happy' ? 200000 : 512;
120
+ runtime.max_output_tokens = scenario === 'happy' ? 2048 : 128;
121
+ if (scenario === 'happy') {
122
+ runtime.retry_policy = {
123
+ enabled: true,
124
+ max_attempts: 3,
125
+ base_delay_ms: 1000,
126
+ max_delay_ms: 1000,
127
+ backoff_multiplier: 1,
128
+ jitter: 'none',
129
+ retry_on: ['turn_result_extraction_failure'],
130
+ };
131
+ }
132
+
133
+ fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + '\n');
134
+ NODE
135
+ }
136
+
137
+ bootstrap_to_qa() {
138
+ local workspace="$1"
139
+
140
+ node --input-type=commonjs - "$workspace" "$CLI_DIR" <<'NODE'
141
+ const fs = require('fs');
142
+ const path = require('path');
143
+ const { execSync } = require('child_process');
144
+ const { pathToFileURL } = require('url');
145
+
146
+ async function main() {
147
+ const workspace = process.argv[2];
148
+ const cliDir = process.argv[3];
149
+
150
+ const normalized = await import(pathToFileURL(path.join(cliDir, 'src/lib/normalized-config.js')).href);
151
+ const governed = await import(pathToFileURL(path.join(cliDir, 'src/lib/governed-state.js')).href);
152
+
153
+ const rawConfig = JSON.parse(fs.readFileSync(path.join(workspace, 'agentxchain.json'), 'utf8'));
154
+ const loaded = normalized.loadNormalizedConfig(rawConfig);
155
+ if (!loaded.ok) {
156
+ throw new Error(`Config normalization failed: ${loaded.errors.join('; ')}`);
157
+ }
158
+ const config = loaded.normalized;
159
+
160
+ const git = (args) => {
161
+ execSync(args, {
162
+ cwd: workspace,
163
+ stdio: 'ignore',
164
+ env: {
165
+ ...process.env,
166
+ GIT_AUTHOR_NAME: 'AgentXchain Smoke',
167
+ GIT_AUTHOR_EMAIL: 'smoke@agentxchain.local',
168
+ GIT_COMMITTER_NAME: 'AgentXchain Smoke',
169
+ GIT_COMMITTER_EMAIL: 'smoke@agentxchain.local',
170
+ },
171
+ });
172
+ };
173
+
174
+ const readState = () => JSON.parse(fs.readFileSync(path.join(workspace, '.agentxchain/state.json'), 'utf8'));
175
+ const stageResult = (state, overrides = {}) => {
176
+ const base = {
177
+ schema_version: '1.0',
178
+ run_id: state.run_id,
179
+ turn_id: state.current_turn.turn_id,
180
+ role: state.current_turn.assigned_role,
181
+ runtime_id: state.current_turn.runtime_id,
182
+ status: 'completed',
183
+ summary: `Smoke bootstrap turn completed by ${state.current_turn.assigned_role}.`,
184
+ decisions: [
185
+ {
186
+ id: 'DEC-001',
187
+ category: 'process',
188
+ statement: 'Bootstrap progressed to the next governed phase.',
189
+ rationale: 'Live preflight smoke only needs a stable QA entry point.',
190
+ },
191
+ ],
192
+ objections: [
193
+ {
194
+ id: 'OBJ-001',
195
+ severity: 'low',
196
+ statement: 'Bootstrap result is synthetic and exists only to reach QA.',
197
+ status: 'raised',
198
+ },
199
+ ],
200
+ files_changed: [],
201
+ artifacts_created: [],
202
+ verification: {
203
+ status: 'pass',
204
+ commands: ['echo smoke-bootstrap'],
205
+ evidence_summary: 'Synthetic bootstrap succeeded.',
206
+ machine_evidence: [{ command: 'echo smoke-bootstrap', exit_code: 0 }],
207
+ },
208
+ artifact: { type: 'review', ref: null },
209
+ proposed_next_role: 'human',
210
+ phase_transition_request: null,
211
+ run_completion_request: false,
212
+ needs_human_reason: null,
213
+ cost: { input_tokens: 0, output_tokens: 0, usd: 0 },
214
+ };
215
+
216
+ fs.writeFileSync(
217
+ path.join(workspace, '.agentxchain/staging/turn-result.json'),
218
+ JSON.stringify({ ...base, ...overrides }, null, 2) + '\n'
219
+ );
220
+ };
221
+
222
+ git('git init');
223
+ git('git add -A');
224
+ git('git commit --allow-empty -m "initial scaffold"');
225
+
226
+ let result = governed.initializeGovernedRun(workspace, config);
227
+ if (!result.ok) throw new Error(result.error);
228
+
229
+ result = governed.assignGovernedTurn(workspace, config, 'pm');
230
+ if (!result.ok) throw new Error(result.error);
231
+
232
+ fs.writeFileSync(path.join(workspace, '.planning/PM_SIGNOFF.md'), '# PM Signoff\nApproved: YES\n');
233
+ fs.writeFileSync(
234
+ path.join(workspace, '.planning/ROADMAP.md'),
235
+ '# Roadmap\n\n## Scope\n\nBootstrap workspace for live api_proxy preflight smoke.\n'
236
+ );
237
+ git('git add -A');
238
+ git('git commit --allow-empty -m "pm artifacts"');
239
+
240
+ stageResult(readState(), {
241
+ summary: 'PM bootstrap completed.',
242
+ artifacts_created: ['.planning/PM_SIGNOFF.md', '.planning/ROADMAP.md'],
243
+ artifact: { type: 'review', path: '.planning/PM_SIGNOFF.md' },
244
+ proposed_next_role: 'human',
245
+ phase_transition_request: 'implementation',
246
+ });
247
+
248
+ result = governed.acceptGovernedTurn(workspace, config);
249
+ if (!result.ok) throw new Error(result.error);
250
+
251
+ result = governed.approvePhaseTransition(workspace);
252
+ if (!result.ok) throw new Error(result.error);
253
+
254
+ git('git add -A');
255
+ git('git commit --allow-empty -m "orchestrator: accept pm turn"');
256
+
257
+ result = governed.assignGovernedTurn(workspace, config, 'dev');
258
+ if (!result.ok) throw new Error(result.error);
259
+
260
+ fs.writeFileSync(path.join(workspace, 'index.js'), 'console.log("smoke");\n');
261
+ fs.writeFileSync(
262
+ path.join(workspace, 'package.json'),
263
+ JSON.stringify({
264
+ name: 'governed-todo-app-smoke',
265
+ version: '0.0.0-smoke',
266
+ private: true,
267
+ type: 'module',
268
+ scripts: {
269
+ test: 'node -e "console.log(\'ok\')"',
270
+ },
271
+ }, null, 2) + '\n'
272
+ );
273
+ git('git add -A');
274
+ git('git commit --allow-empty -m "dev bootstrap"');
275
+
276
+ stageResult(readState(), {
277
+ summary: 'Dev bootstrap completed.',
278
+ files_changed: ['index.js', 'package.json'],
279
+ artifact: { type: 'commit', ref: 'smoke-bootstrap' },
280
+ proposed_next_role: 'qa',
281
+ phase_transition_request: 'qa',
282
+ verification: {
283
+ status: 'pass',
284
+ commands: ['node index.js'],
285
+ evidence_summary: 'Bootstrap implementation is runnable.',
286
+ machine_evidence: [{ command: 'node index.js', exit_code: 0 }],
287
+ },
288
+ });
289
+
290
+ result = governed.acceptGovernedTurn(workspace, config);
291
+ if (!result.ok) throw new Error(result.error);
292
+
293
+ git('git add -A');
294
+ git('git commit --allow-empty -m "orchestrator: accept dev turn"');
295
+ }
296
+
297
+ main().catch((err) => {
298
+ console.error(err.message || err);
299
+ process.exit(1);
300
+ });
301
+ NODE
302
+ }
303
+
304
+ run_scenario() {
305
+ local scenario="$1"
306
+ local workspace="$2"
307
+
308
+ node --input-type=commonjs - "$workspace" "$CLI_DIR" "$scenario" <<'NODE'
309
+ const fs = require('fs');
310
+ const path = require('path');
311
+ const { pathToFileURL } = require('url');
312
+
313
+ async function main() {
314
+ const workspace = process.argv[2];
315
+ const cliDir = process.argv[3];
316
+ const scenario = process.argv[4];
317
+
318
+ const normalized = await import(pathToFileURL(path.join(cliDir, 'src/lib/normalized-config.js')).href);
319
+ const governed = await import(pathToFileURL(path.join(cliDir, 'src/lib/governed-state.js')).href);
320
+ const dispatchBundle = await import(pathToFileURL(path.join(cliDir, 'src/lib/dispatch-bundle.js')).href);
321
+ const apiProxy = await import(pathToFileURL(path.join(cliDir, 'src/lib/adapters/api-proxy-adapter.js')).href);
322
+ const validator = await import(pathToFileURL(path.join(cliDir, 'src/lib/turn-result-validator.js')).href);
323
+
324
+ const rawConfig = JSON.parse(fs.readFileSync(path.join(workspace, 'agentxchain.json'), 'utf8'));
325
+ const loaded = normalized.loadNormalizedConfig(rawConfig);
326
+ if (!loaded.ok) {
327
+ throw new Error(`Config normalization failed: ${loaded.errors.join('; ')}`);
328
+ }
329
+ const config = loaded.normalized;
330
+ let promptOverride = null;
331
+
332
+ let assign = governed.assignGovernedTurn(workspace, config, 'qa');
333
+ if (!assign.ok) {
334
+ throw new Error(assign.error);
335
+ }
336
+
337
+ if (scenario === 'happy') {
338
+ // Use a literal PROMPT.md override for the live happy path so the smoke
339
+ // harness exercises adapter/preflight behavior instead of general QA
340
+ // prompt-following variance.
341
+ const runId = assign.state.run_id;
342
+ const turnId = assign.state.current_turn.turn_id;
343
+ promptOverride = `# Live QA Smoke Prompt Override
344
+
345
+ Return ONLY the JSON object below EXACTLY as written.
346
+ Do not add any text before or after it.
347
+ Do not wrap it in markdown fences.
348
+ Do not change field names, values, array element types, or nulls.
349
+ In particular, \`artifacts_created\` must remain an empty array of strings.
350
+
351
+ {
352
+ "schema_version": "1.0",
353
+ "run_id": "${runId}",
354
+ "turn_id": "${turnId}",
355
+ "role": "qa",
356
+ "runtime_id": "api-qa",
357
+ "status": "completed",
358
+ "summary": "QA smoke response returned exact governed JSON for live preflight validation.",
359
+ "decisions": [
360
+ {
361
+ "id": "DEC-001",
362
+ "category": "quality",
363
+ "statement": "Happy-path smoke dispatch succeeded.",
364
+ "rationale": "This harness validates provider-backed preflight send plus governed JSON extraction."
365
+ }
366
+ ],
367
+ "objections": [
368
+ {
369
+ "id": "OBJ-001",
370
+ "severity": "low",
371
+ "statement": "This is a synthetic smoke artifact, not a substantive QA verdict.",
372
+ "status": "raised"
373
+ }
374
+ ],
375
+ "files_changed": [],
376
+ "artifacts_created": [],
377
+ "verification": {
378
+ "status": "skipped",
379
+ "commands": [],
380
+ "evidence_summary": "Smoke harness response only; no verification commands were run.",
381
+ "machine_evidence": []
382
+ },
383
+ "artifact": {
384
+ "type": "review",
385
+ "ref": null
386
+ },
387
+ "proposed_next_role": "human",
388
+ "phase_transition_request": null,
389
+ "run_completion_request": null,
390
+ "needs_human_reason": null,
391
+ "cost": {
392
+ "input_tokens": 0,
393
+ "output_tokens": 0,
394
+ "usd": 0
395
+ }
396
+ }
397
+ `;
398
+ } else if (scenario === 'overflow') {
399
+ const filler = '\n\n## Overflow Filler\n' + 'This line intentionally inflates the QA prompt.\n'.repeat(800);
400
+ fs.appendFileSync(path.join(workspace, '.agentxchain/prompts/qa.md'), filler);
401
+ }
402
+
403
+ const bundle = dispatchBundle.writeDispatchBundle(workspace, assign.state, config);
404
+ if (!bundle.ok) {
405
+ throw new Error(bundle.error);
406
+ }
407
+ if (promptOverride) {
408
+ fs.writeFileSync(
409
+ path.join(workspace, '.agentxchain/dispatch/current/PROMPT.md'),
410
+ promptOverride
411
+ );
412
+ }
413
+
414
+ const dispatch = await apiProxy.dispatchApiProxy(workspace, assign.state, config);
415
+ const tokenBudgetPath = path.join(workspace, '.agentxchain/dispatch/current/TOKEN_BUDGET.json');
416
+ const effectiveContextPath = path.join(workspace, '.agentxchain/dispatch/current/CONTEXT.effective.md');
417
+ const providerResponsePath = path.join(workspace, '.agentxchain/staging/provider-response.json');
418
+ const stagedResultPath = path.join(workspace, '.agentxchain/staging/turn-result.json');
419
+
420
+ if (!fs.existsSync(tokenBudgetPath)) {
421
+ throw new Error('TOKEN_BUDGET.json missing');
422
+ }
423
+ if (!fs.existsSync(effectiveContextPath)) {
424
+ throw new Error('CONTEXT.effective.md missing');
425
+ }
426
+
427
+ const report = JSON.parse(fs.readFileSync(tokenBudgetPath, 'utf8'));
428
+ const sentToProvider = report.sent_to_provider === true;
429
+ const providerResponseExists = fs.existsSync(providerResponsePath);
430
+ const stagedResultExists = fs.existsSync(stagedResultPath);
431
+
432
+ if (scenario === 'happy') {
433
+ if (!dispatch.ok) {
434
+ throw new Error(`Happy-path dispatch failed: ${dispatch.error}`);
435
+ }
436
+ if (!sentToProvider) {
437
+ throw new Error('Happy-path report indicates sent_to_provider = false');
438
+ }
439
+ if (!providerResponseExists) {
440
+ throw new Error('provider-response.json missing for happy path');
441
+ }
442
+ if (!stagedResultExists) {
443
+ throw new Error('turn-result.json missing for happy path');
444
+ }
445
+
446
+ const validation = validator.validateStagedTurnResult(workspace, assign.state, config);
447
+ console.log(JSON.stringify({
448
+ mode: scenario,
449
+ workspace,
450
+ dispatch_ok: true,
451
+ validation_ok: validation.ok === true,
452
+ error_class: null,
453
+ sent_to_provider: true,
454
+ token_budget_path: tokenBudgetPath,
455
+ effective_context_path: effectiveContextPath,
456
+ provider_response_path: providerResponsePath,
457
+ staged_result_path: stagedResultPath,
458
+ }, null, 2));
459
+ return;
460
+ }
461
+
462
+ if (dispatch.ok) {
463
+ throw new Error('Overflow-path dispatch unexpectedly succeeded');
464
+ }
465
+ if (dispatch.classified?.error_class !== 'context_overflow') {
466
+ throw new Error(`Expected context_overflow, got ${dispatch.classified?.error_class || 'unknown'}`);
467
+ }
468
+ if (sentToProvider) {
469
+ throw new Error('Overflow-path report indicates sent_to_provider = true');
470
+ }
471
+ if (providerResponseExists) {
472
+ throw new Error('provider-response.json should not exist for overflow path');
473
+ }
474
+
475
+ console.log(JSON.stringify({
476
+ mode: scenario,
477
+ workspace,
478
+ dispatch_ok: false,
479
+ error_class: dispatch.classified?.error_class || null,
480
+ sent_to_provider: false,
481
+ token_budget_path: tokenBudgetPath,
482
+ effective_context_path: effectiveContextPath,
483
+ provider_response_path: providerResponsePath,
484
+ staged_result_path: stagedResultPath,
485
+ }, null, 2));
486
+ }
487
+
488
+ main().catch((err) => {
489
+ console.error(err.message || err);
490
+ process.exit(1);
491
+ });
492
+ NODE
493
+ }
494
+
495
+ cleanup_workspace() {
496
+ local workspace="$1"
497
+ if [[ "$KEEP_TEMP" -eq 1 ]]; then
498
+ return 0
499
+ fi
500
+ rm -rf "$workspace"
501
+ }
502
+
503
+ require_cmd node
504
+ require_cmd git
505
+ load_repo_env
506
+
507
+ if [[ "$MODE" != "overflow" && -z "${ANTHROPIC_API_KEY:-}" ]]; then
508
+ echo "ANTHROPIC_API_KEY is not set. Load it in the shell or repo-root .env before running this harness." >&2
509
+ exit 1
510
+ fi
511
+
512
+ echo "Live api_proxy preflight smoke"
513
+ echo "Mode: $MODE"
514
+
515
+ if [[ "$MODE" == "happy" || "$MODE" == "both" ]]; then
516
+ HAPPY_WORKSPACE="$(make_workspace happy)"
517
+ echo "Happy workspace: $HAPPY_WORKSPACE"
518
+ patch_runtime_config "$HAPPY_WORKSPACE" happy
519
+ bootstrap_to_qa "$HAPPY_WORKSPACE"
520
+ run_scenario happy "$HAPPY_WORKSPACE"
521
+ cleanup_workspace "$HAPPY_WORKSPACE"
522
+ fi
523
+
524
+ if [[ "$MODE" == "overflow" || "$MODE" == "both" ]]; then
525
+ OVERFLOW_WORKSPACE="$(make_workspace overflow)"
526
+ echo "Overflow workspace: $OVERFLOW_WORKSPACE"
527
+ patch_runtime_config "$OVERFLOW_WORKSPACE" overflow
528
+ bootstrap_to_qa "$OVERFLOW_WORKSPACE"
529
+ run_scenario overflow "$OVERFLOW_WORKSPACE"
530
+ cleanup_workspace "$OVERFLOW_WORKSPACE"
531
+ fi
@@ -0,0 +1,88 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
5
+ CLI_DIR="${SCRIPT_DIR}/.."
6
+ cd "$CLI_DIR"
7
+
8
+ TMP_NPMRC=""
9
+
10
+ cleanup() {
11
+ if [[ -n "$TMP_NPMRC" && -f "$TMP_NPMRC" ]]; then
12
+ rm -f "$TMP_NPMRC"
13
+ fi
14
+ }
15
+
16
+ trap cleanup EXIT
17
+
18
+ usage() {
19
+ echo "Usage: bash scripts/publish-from-tag.sh <vX.Y.Z>" >&2
20
+ }
21
+
22
+ TAG="${1:-}"
23
+ if [[ -z "$TAG" ]]; then
24
+ echo "Error: release tag is required" >&2
25
+ usage
26
+ exit 1
27
+ fi
28
+
29
+ if ! [[ "$TAG" =~ ^v([0-9]+\.[0-9]+\.[0-9]+)$ ]]; then
30
+ echo "Error: release tag must match v<semver>; got '${TAG}'" >&2
31
+ usage
32
+ exit 1
33
+ fi
34
+
35
+ RELEASE_VERSION="${BASH_REMATCH[1]}"
36
+ PACKAGE_NAME="$(node -e "console.log(JSON.parse(require('fs').readFileSync('package.json', 'utf8')).name)")"
37
+ PACKAGE_VERSION="$(node -e "console.log(JSON.parse(require('fs').readFileSync('package.json', 'utf8')).version)")"
38
+
39
+ if [[ "$PACKAGE_VERSION" != "$RELEASE_VERSION" ]]; then
40
+ echo "Error: package.json version is ${PACKAGE_VERSION}, expected ${RELEASE_VERSION} from ${TAG}" >&2
41
+ exit 1
42
+ fi
43
+
44
+ RETRY_ATTEMPTS="${NPM_VIEW_RETRY_ATTEMPTS:-12}"
45
+ RETRY_DELAY_SECONDS="${NPM_VIEW_RETRY_DELAY_SECONDS:-5}"
46
+
47
+ if ! [[ "$RETRY_ATTEMPTS" =~ ^[0-9]+$ ]] || [[ "$RETRY_ATTEMPTS" -lt 1 ]]; then
48
+ echo "Error: NPM_VIEW_RETRY_ATTEMPTS must be a positive integer" >&2
49
+ exit 1
50
+ fi
51
+
52
+ if ! [[ "$RETRY_DELAY_SECONDS" =~ ^[0-9]+$ ]]; then
53
+ echo "Error: NPM_VIEW_RETRY_DELAY_SECONDS must be a non-negative integer" >&2
54
+ exit 1
55
+ fi
56
+
57
+ echo "Publishing ${PACKAGE_NAME}@${RELEASE_VERSION} from ${TAG}"
58
+ echo "Running strict release preflight..."
59
+ bash scripts/release-preflight.sh --strict --target-version "${RELEASE_VERSION}"
60
+
61
+ echo "Running npm publish..."
62
+ if [[ -n "${NPM_TOKEN:-}" ]]; then
63
+ echo "Publish auth mode: token"
64
+ TMP_NPMRC="$(mktemp "${TMPDIR:-/tmp}/agentxchain-npmrc.XXXXXX")"
65
+ chmod 600 "$TMP_NPMRC"
66
+ printf '%s\n' "//registry.npmjs.org/:_authToken=${NPM_TOKEN}" > "$TMP_NPMRC"
67
+ NPM_CONFIG_USERCONFIG="$TMP_NPMRC" npm publish --access public
68
+ else
69
+ echo "Publish auth mode: trusted publishing (OIDC)"
70
+ npm publish --access public
71
+ fi
72
+
73
+ echo "Verifying registry visibility..."
74
+ for ((attempt = 1; attempt <= RETRY_ATTEMPTS; attempt++)); do
75
+ PUBLISHED_VERSION="$(npm view "${PACKAGE_NAME}@${RELEASE_VERSION}" version 2>/dev/null || true)"
76
+ if [[ "$PUBLISHED_VERSION" == "$RELEASE_VERSION" ]]; then
77
+ echo "Verified ${PACKAGE_NAME}@${RELEASE_VERSION} on npm (attempt ${attempt}/${RETRY_ATTEMPTS})"
78
+ exit 0
79
+ fi
80
+
81
+ if [[ "$attempt" -lt "$RETRY_ATTEMPTS" ]]; then
82
+ echo "Registry not updated yet (attempt ${attempt}/${RETRY_ATTEMPTS}); retrying in ${RETRY_DELAY_SECONDS}s..."
83
+ sleep "$RETRY_DELAY_SECONDS"
84
+ fi
85
+ done
86
+
87
+ echo "Error: npm registry did not serve ${PACKAGE_NAME}@${RELEASE_VERSION} after ${RETRY_ATTEMPTS} attempts" >&2
88
+ exit 1