agentxchain 2.23.0 → 2.24.2
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/package.json +1 -1
- package/scripts/release-postflight.sh +74 -9
- package/src/commands/demo.js +1 -1
- package/src/commands/init.js +26 -0
- package/src/commands/run.js +34 -1
- package/src/commands/step.js +13 -1
- package/src/lib/adapters/manual-adapter.js +77 -8
package/package.json
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
2
|
# Release postflight — run this after publish succeeds.
|
|
3
3
|
# Verifies: release tag exists, npm registry serves the version, metadata is present,
|
|
4
|
-
# the published package
|
|
5
|
-
# are importable in a
|
|
4
|
+
# the published package resolves through npx, the published package can execute its
|
|
5
|
+
# CLI entrypoint from the tarball, and runner package exports are importable in a
|
|
6
|
+
# clean consumer project.
|
|
6
7
|
# Usage: bash scripts/release-postflight.sh --target-version <semver> [--tag vX.Y.Z]
|
|
7
8
|
set -uo pipefail
|
|
8
9
|
|
|
@@ -100,6 +101,27 @@ trim_last_line() {
|
|
|
100
101
|
printf '%s\n' "$1" | awk 'NF { line=$0 } END { gsub(/^[[:space:]]+|[[:space:]]+$/, "", line); print line }'
|
|
101
102
|
}
|
|
102
103
|
|
|
104
|
+
extract_matching_line() {
|
|
105
|
+
local output="$1"
|
|
106
|
+
local expected="$2"
|
|
107
|
+
printf '%s\n' "$output" | awk -v expected="$expected" '
|
|
108
|
+
{
|
|
109
|
+
line=$0
|
|
110
|
+
gsub(/^[[:space:]]+|[[:space:]]+$/, "", line)
|
|
111
|
+
if (line == expected) {
|
|
112
|
+
print line
|
|
113
|
+
found=1
|
|
114
|
+
exit
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
END {
|
|
118
|
+
if (!found) {
|
|
119
|
+
exit 1
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
'
|
|
123
|
+
}
|
|
124
|
+
|
|
103
125
|
run_install_smoke() {
|
|
104
126
|
if [[ -z "$TARBALL_URL" ]]; then
|
|
105
127
|
echo "registry tarball metadata unavailable for install smoke" >&2
|
|
@@ -139,6 +161,36 @@ run_install_smoke() {
|
|
|
139
161
|
return "$version_status"
|
|
140
162
|
}
|
|
141
163
|
|
|
164
|
+
run_npx_smoke() {
|
|
165
|
+
local smoke_root
|
|
166
|
+
local smoke_npmrc
|
|
167
|
+
local npx_output
|
|
168
|
+
local npx_status
|
|
169
|
+
|
|
170
|
+
smoke_root="$(mktemp -d "${TMPDIR:-/tmp}/agentxchain-npx-postflight.XXXXXX")"
|
|
171
|
+
mkdir -p "${smoke_root}/home" "${smoke_root}/cache" "${smoke_root}/npm-cache" "${smoke_root}/workspace"
|
|
172
|
+
|
|
173
|
+
smoke_npmrc="${smoke_root}/.npmrc"
|
|
174
|
+
echo "registry=https://registry.npmjs.org/" > "$smoke_npmrc"
|
|
175
|
+
|
|
176
|
+
npx_output="$(
|
|
177
|
+
(
|
|
178
|
+
cd "${smoke_root}/workspace" || exit 1
|
|
179
|
+
env -u NODE_AUTH_TOKEN \
|
|
180
|
+
HOME="${smoke_root}/home" \
|
|
181
|
+
XDG_CACHE_HOME="${smoke_root}/cache" \
|
|
182
|
+
NPM_CONFIG_CACHE="${smoke_root}/npm-cache" \
|
|
183
|
+
NPM_CONFIG_USERCONFIG="$smoke_npmrc" \
|
|
184
|
+
npx --yes -p "${PACKAGE_NAME}@${TARGET_VERSION}" -c "${PACKAGE_BIN_NAME} --version" 2>&1
|
|
185
|
+
)
|
|
186
|
+
)"
|
|
187
|
+
npx_status=$?
|
|
188
|
+
|
|
189
|
+
printf '%s\n' "$npx_output"
|
|
190
|
+
rm -rf "$smoke_root"
|
|
191
|
+
return "$npx_status"
|
|
192
|
+
}
|
|
193
|
+
|
|
142
194
|
run_runner_export_smoke() {
|
|
143
195
|
if [[ -z "$TARBALL_URL" ]]; then
|
|
144
196
|
echo "registry tarball metadata unavailable for runner export smoke" >&2
|
|
@@ -261,17 +313,17 @@ run_with_retry() {
|
|
|
261
313
|
|
|
262
314
|
echo "AgentXchain v${TARGET_VERSION} Release Postflight"
|
|
263
315
|
echo "====================================="
|
|
264
|
-
echo "Checks release truth after publish: tag, registry visibility, metadata, CLI install smoke, and package export smoke."
|
|
316
|
+
echo "Checks release truth after publish: tag, registry visibility, metadata, npx smoke, CLI install smoke, and package export smoke."
|
|
265
317
|
echo ""
|
|
266
318
|
|
|
267
|
-
echo "[1/
|
|
319
|
+
echo "[1/7] Git tag"
|
|
268
320
|
if git rev-parse -q --verify "refs/tags/${TAG}" >/dev/null 2>&1; then
|
|
269
321
|
pass "Git tag ${TAG} exists locally"
|
|
270
322
|
else
|
|
271
323
|
fail "Git tag ${TAG} is missing locally"
|
|
272
324
|
fi
|
|
273
325
|
|
|
274
|
-
echo "[2/
|
|
326
|
+
echo "[2/7] Registry version"
|
|
275
327
|
if run_with_retry VERSION_OUTPUT "registry version" equals "${TARGET_VERSION}" npm view "${PACKAGE_NAME}@${TARGET_VERSION}" version; then
|
|
276
328
|
PUBLISHED_VERSION="$(trim_last_line "$VERSION_OUTPUT")"
|
|
277
329
|
if [[ "$PUBLISHED_VERSION" == "$TARGET_VERSION" ]]; then
|
|
@@ -284,7 +336,7 @@ else
|
|
|
284
336
|
printf '%s\n' "$VERSION_OUTPUT" | tail -20
|
|
285
337
|
fi
|
|
286
338
|
|
|
287
|
-
echo "[3/
|
|
339
|
+
echo "[3/7] Registry tarball metadata"
|
|
288
340
|
if run_with_retry TARBALL_OUTPUT "registry tarball metadata" nonempty "" npm view "${PACKAGE_NAME}@${TARGET_VERSION}" dist.tarball; then
|
|
289
341
|
TARBALL_URL="$(trim_last_line "$TARBALL_OUTPUT")"
|
|
290
342
|
if [[ -n "$TARBALL_URL" ]]; then
|
|
@@ -297,7 +349,7 @@ else
|
|
|
297
349
|
printf '%s\n' "$TARBALL_OUTPUT" | tail -20
|
|
298
350
|
fi
|
|
299
351
|
|
|
300
|
-
echo "[4/
|
|
352
|
+
echo "[4/7] Registry checksum metadata"
|
|
301
353
|
if run_with_retry INTEGRITY_OUTPUT "registry checksum metadata" nonempty "" npm view "${PACKAGE_NAME}@${TARGET_VERSION}" dist.integrity; then
|
|
302
354
|
REGISTRY_CHECKSUM="$(trim_last_line "$INTEGRITY_OUTPUT")"
|
|
303
355
|
fi
|
|
@@ -312,7 +364,20 @@ else
|
|
|
312
364
|
fail "registry did not return checksum metadata"
|
|
313
365
|
fi
|
|
314
366
|
|
|
315
|
-
echo "[5/
|
|
367
|
+
echo "[5/7] npx smoke"
|
|
368
|
+
if run_with_retry NPX_OUTPUT "npx smoke" nonempty "" run_npx_smoke; then
|
|
369
|
+
NPX_VERSION="$(extract_matching_line "$NPX_OUTPUT" "$TARGET_VERSION" 2>/dev/null || trim_last_line "$NPX_OUTPUT")"
|
|
370
|
+
if [[ "$NPX_VERSION" == "$TARGET_VERSION" ]]; then
|
|
371
|
+
pass "published npx command resolves and reports ${TARGET_VERSION}"
|
|
372
|
+
else
|
|
373
|
+
fail "published npx command reported '${NPX_VERSION}', expected '${TARGET_VERSION}'"
|
|
374
|
+
fi
|
|
375
|
+
else
|
|
376
|
+
fail "published npx smoke failed"
|
|
377
|
+
printf '%s\n' "$NPX_OUTPUT" | tail -20
|
|
378
|
+
fi
|
|
379
|
+
|
|
380
|
+
echo "[6/7] Install smoke"
|
|
316
381
|
if run_with_retry EXEC_OUTPUT "install smoke" nonempty "" run_install_smoke; then
|
|
317
382
|
EXEC_VERSION="$(trim_last_line "$EXEC_OUTPUT")"
|
|
318
383
|
if [[ "$EXEC_VERSION" == "$TARGET_VERSION" ]]; then
|
|
@@ -325,7 +390,7 @@ else
|
|
|
325
390
|
printf '%s\n' "$EXEC_OUTPUT" | tail -20
|
|
326
391
|
fi
|
|
327
392
|
|
|
328
|
-
echo "[
|
|
393
|
+
echo "[7/7] Package export smoke"
|
|
329
394
|
if run_with_retry RUNNER_EXPORT_OUTPUT "runner export smoke" nonempty "" run_runner_export_smoke; then
|
|
330
395
|
RUNNER_EXPORT_JSON="$(trim_last_line "$RUNNER_EXPORT_OUTPUT")"
|
|
331
396
|
RUNNER_EXPORT_VERSION="$(printf '%s' "$RUNNER_EXPORT_JSON" | node --input-type=module -e "process.stdin.setEncoding('utf8'); let raw=''; process.stdin.on('data', (chunk) => raw += chunk); process.stdin.on('end', () => { const parsed = JSON.parse(raw); console.log(parsed.runner_interface_version || ''); });")"
|
package/src/commands/demo.js
CHANGED
|
@@ -605,7 +605,7 @@ All acceptance criteria met. OBJ-002 (clock skew) noted for follow-up. OBJ-003 (
|
|
|
605
605
|
console.log(chalk.dim(' ─'.repeat(26)));
|
|
606
606
|
console.log('');
|
|
607
607
|
console.log(` ${chalk.bold('Try it for real:')} agentxchain init --governed`);
|
|
608
|
-
console.log(` ${chalk.bold('Step by step:')} https://agentxchain.dev/docs/
|
|
608
|
+
console.log(` ${chalk.bold('Step by step:')} https://agentxchain.dev/docs/getting-started`);
|
|
609
609
|
console.log(` ${chalk.bold('Read more:')} https://agentxchain.dev/docs/quickstart`);
|
|
610
610
|
console.log('');
|
|
611
611
|
}
|
package/src/commands/init.js
CHANGED
|
@@ -105,6 +105,7 @@ const GOVERNED_RUNTIMES = {
|
|
|
105
105
|
'manual-pm': { type: 'manual' },
|
|
106
106
|
'local-dev': DEFAULT_GOVERNED_LOCAL_DEV_RUNTIME,
|
|
107
107
|
'api-qa': { type: 'api_proxy', provider: 'anthropic', model: 'claude-sonnet-4-6', auth_env: 'ANTHROPIC_API_KEY' },
|
|
108
|
+
'manual-qa': { type: 'manual' },
|
|
108
109
|
'manual-director': { type: 'manual' }
|
|
109
110
|
};
|
|
110
111
|
|
|
@@ -684,6 +685,29 @@ async function initGoverned(opts) {
|
|
|
684
685
|
console.log(` ${chalk.dim('Dev runtime:')} ${formatGovernedRuntimeCommand(localDevRuntime)} ${chalk.dim(`(${localDevRuntime.prompt_transport})`)}`);
|
|
685
686
|
console.log(` ${chalk.dim('Protocol:')} governed convergence`);
|
|
686
687
|
console.log('');
|
|
688
|
+
|
|
689
|
+
// Readiness hint: tell user which roles work immediately vs which need API keys
|
|
690
|
+
const allRuntimes = { ...GOVERNED_RUNTIMES, 'local-dev': localDevRuntime };
|
|
691
|
+
const needsKey = Object.entries(allRuntimes)
|
|
692
|
+
.filter(([, rt]) => rt.auth_env)
|
|
693
|
+
.map(([id, rt]) => ({ id, env: rt.auth_env }));
|
|
694
|
+
if (needsKey.length > 0) {
|
|
695
|
+
const envVars = [...new Set(needsKey.map(r => r.env))];
|
|
696
|
+
const roleNames = needsKey.map(r => r.id);
|
|
697
|
+
const hasKeys = envVars.every(v => process.env[v]);
|
|
698
|
+
if (hasKeys) {
|
|
699
|
+
console.log(` ${chalk.green('Ready:')} all runtimes configured (${envVars.join(', ')} detected)`);
|
|
700
|
+
} else {
|
|
701
|
+
console.log(` ${chalk.yellow('Mixed-mode:')} pm and eng_director work immediately (manual).`);
|
|
702
|
+
console.log(` ${chalk.yellow(' ')}${roleNames.join(', ')} need ${chalk.bold(envVars.join(', '))} to dispatch automatically.`);
|
|
703
|
+
console.log(` ${chalk.yellow(' ')}Without it, those turns fall back to manual input.`);
|
|
704
|
+
if (allRuntimes['manual-qa']) {
|
|
705
|
+
console.log(` ${chalk.yellow(' ')}No-key QA path: change ${chalk.bold('roles.qa.runtime')} from ${chalk.bold('"api-qa"')} to ${chalk.bold('"manual-qa"')} in ${chalk.bold('agentxchain.json')}.`);
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
console.log('');
|
|
709
|
+
}
|
|
710
|
+
|
|
687
711
|
console.log(` ${chalk.cyan('Next:')}`);
|
|
688
712
|
if (dir !== process.cwd()) {
|
|
689
713
|
console.log(` ${chalk.bold(`cd ${targetLabel}`)}`);
|
|
@@ -691,6 +715,8 @@ async function initGoverned(opts) {
|
|
|
691
715
|
console.log(` ${chalk.bold('agentxchain step')} ${chalk.dim('# run the first governed turn')}`);
|
|
692
716
|
console.log(` ${chalk.bold('agentxchain status')} ${chalk.dim('# inspect phase, gate, and turn state')}`);
|
|
693
717
|
console.log('');
|
|
718
|
+
console.log(` ${chalk.dim('Guide:')} https://agentxchain.dev/docs/getting-started`);
|
|
719
|
+
console.log('');
|
|
694
720
|
}
|
|
695
721
|
|
|
696
722
|
export async function initCommand(opts) {
|
package/src/commands/run.js
CHANGED
|
@@ -47,7 +47,7 @@ export async function runCommand(opts) {
|
|
|
47
47
|
process.exit(1);
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
-
const { root, config } = context;
|
|
50
|
+
const { root, config, rawConfig } = context;
|
|
51
51
|
|
|
52
52
|
if (config.protocol_mode !== 'governed') {
|
|
53
53
|
console.log(chalk.red('The run command is only available for governed projects.'));
|
|
@@ -112,6 +112,7 @@ export async function runCommand(opts) {
|
|
|
112
112
|
|
|
113
113
|
// ── Track first-call for --role override ────────────────────────────────
|
|
114
114
|
let firstSelectRole = true;
|
|
115
|
+
let qaMissingCredentialsFallback = null;
|
|
115
116
|
|
|
116
117
|
// ── Callbacks ───────────────────────────────────────────────────────────
|
|
117
118
|
const callbacks = {
|
|
@@ -134,6 +135,7 @@ export async function runCommand(opts) {
|
|
|
134
135
|
const runtime = cfg.runtimes?.[runtimeId];
|
|
135
136
|
const runtimeType = runtime?.type || role?.runtime_class || 'manual';
|
|
136
137
|
const hooksConfig = cfg.hooks || {};
|
|
138
|
+
qaMissingCredentialsFallback = null;
|
|
137
139
|
|
|
138
140
|
// Manual adapter is not supported in run mode
|
|
139
141
|
if (runtimeType === 'manual') {
|
|
@@ -219,6 +221,18 @@ export async function runCommand(opts) {
|
|
|
219
221
|
|
|
220
222
|
// Adapter failure
|
|
221
223
|
if (!adapterResult.ok) {
|
|
224
|
+
if (shouldPrintManualQaFallback({
|
|
225
|
+
roleId,
|
|
226
|
+
runtimeId,
|
|
227
|
+
classified: adapterResult.classified,
|
|
228
|
+
rawConfig,
|
|
229
|
+
})) {
|
|
230
|
+
qaMissingCredentialsFallback = {
|
|
231
|
+
roleId,
|
|
232
|
+
runtimeId,
|
|
233
|
+
errorClass: adapterResult.classified.error_class,
|
|
234
|
+
};
|
|
235
|
+
}
|
|
222
236
|
const errorDetail = adapterResult.classified
|
|
223
237
|
? `${adapterResult.classified.error_class}: ${adapterResult.classified.recovery}`
|
|
224
238
|
: adapterResult.error;
|
|
@@ -317,6 +331,10 @@ export async function runCommand(opts) {
|
|
|
317
331
|
}
|
|
318
332
|
}
|
|
319
333
|
|
|
334
|
+
if (qaMissingCredentialsFallback) {
|
|
335
|
+
printManualQaFallback();
|
|
336
|
+
}
|
|
337
|
+
|
|
320
338
|
// Recovery guidance for blocked/rejected states
|
|
321
339
|
if (result.state && (result.stop_reason === 'blocked' || result.stop_reason === 'reject_exhausted' || result.stop_reason === 'dispatch_error')) {
|
|
322
340
|
const recovery = deriveRecoveryDescriptor(result.state);
|
|
@@ -391,3 +409,18 @@ function promptUser(question) {
|
|
|
391
409
|
rl.on('close', () => resolve(''));
|
|
392
410
|
});
|
|
393
411
|
}
|
|
412
|
+
|
|
413
|
+
function shouldPrintManualQaFallback({ roleId, runtimeId, classified, rawConfig }) {
|
|
414
|
+
return classified?.error_class === 'missing_credentials'
|
|
415
|
+
&& roleId === 'qa'
|
|
416
|
+
&& runtimeId === 'api-qa'
|
|
417
|
+
&& rawConfig?.runtimes?.['manual-qa']?.type === 'manual';
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
function printManualQaFallback() {
|
|
421
|
+
console.log('');
|
|
422
|
+
console.log(chalk.dim(' No-key QA fallback:'));
|
|
423
|
+
console.log(chalk.dim(' - Edit agentxchain.json and change roles.qa.runtime from "api-qa" to "manual-qa"'));
|
|
424
|
+
console.log(chalk.dim(' - Then recover the retained QA turn with: agentxchain step --resume'));
|
|
425
|
+
console.log(chalk.dim(' - Guide: https://agentxchain.dev/docs/getting-started'));
|
|
426
|
+
}
|
package/src/commands/step.js
CHANGED
|
@@ -71,7 +71,7 @@ export async function stepCommand(opts) {
|
|
|
71
71
|
process.exit(1);
|
|
72
72
|
}
|
|
73
73
|
|
|
74
|
-
const { root, config } = context;
|
|
74
|
+
const { root, config, rawConfig } = context;
|
|
75
75
|
|
|
76
76
|
if (config.protocol_mode !== 'governed') {
|
|
77
77
|
console.log(chalk.red('The step command is only available for governed projects.'));
|
|
@@ -422,6 +422,18 @@ export async function stepCommand(opts) {
|
|
|
422
422
|
console.log(chalk.dim(` Retry trace: ${apiResult.retry_trace_path}`));
|
|
423
423
|
}
|
|
424
424
|
|
|
425
|
+
if (
|
|
426
|
+
apiResult.classified?.error_class === 'missing_credentials'
|
|
427
|
+
&& roleId === 'qa'
|
|
428
|
+
&& config.roles?.qa?.runtime_id === 'api-qa'
|
|
429
|
+
&& rawConfig?.runtimes?.['manual-qa']?.type === 'manual'
|
|
430
|
+
) {
|
|
431
|
+
console.log(chalk.dim(' No-key QA fallback:'));
|
|
432
|
+
console.log(chalk.dim(' - Edit agentxchain.json and change roles.qa.runtime from "api-qa" to "manual-qa"'));
|
|
433
|
+
console.log(chalk.dim(' - Then rerun: agentxchain step --resume'));
|
|
434
|
+
console.log(chalk.dim(' - Guide: https://agentxchain.dev/docs/getting-started'));
|
|
435
|
+
}
|
|
436
|
+
|
|
425
437
|
console.log(chalk.dim('The turn remains assigned. You can:'));
|
|
426
438
|
console.log(chalk.dim(' - Fix the issue and retry: agentxchain step --resume'));
|
|
427
439
|
console.log(chalk.dim(' - Complete manually: edit .agentxchain/staging/turn-result.json'));
|
|
@@ -30,31 +30,100 @@ export function printManualDispatchInstructions(state, config, options = {}) {
|
|
|
30
30
|
const role = config.roles?.[turn.assigned_role];
|
|
31
31
|
const promptPath = getDispatchPromptPath(turn.turn_id);
|
|
32
32
|
const stagingPath = getTurnStagingResultPath(turn.turn_id);
|
|
33
|
+
const phase = state.phase || 'planning';
|
|
34
|
+
const roleId = turn.assigned_role;
|
|
33
35
|
|
|
34
36
|
const lines = [];
|
|
35
37
|
lines.push('');
|
|
36
38
|
lines.push(' +---------------------------------------------------------+');
|
|
37
39
|
lines.push(' | MANUAL TURN REQUIRED |');
|
|
38
40
|
lines.push(' | |');
|
|
39
|
-
lines.push(` | Role: ${pad(
|
|
41
|
+
lines.push(` | Role: ${pad(roleId, 46)}|`);
|
|
40
42
|
lines.push(` | Turn: ${pad(turn.turn_id, 46)}|`);
|
|
41
|
-
lines.push(` | Phase: ${pad(
|
|
43
|
+
lines.push(` | Phase: ${pad(phase, 46)}|`);
|
|
42
44
|
lines.push(` | Attempt: ${pad(String(turn.attempt), 46)}|`);
|
|
43
45
|
lines.push(' | |');
|
|
44
46
|
lines.push(` | Prompt: ${pad(promptPath, 46)}|`);
|
|
45
47
|
lines.push(` | Result: ${pad(stagingPath, 46)}|`);
|
|
46
|
-
lines.push(' | |');
|
|
47
|
-
lines.push(' | 1. Read the prompt at the path above |');
|
|
48
|
-
lines.push(' | 2. Complete the work described in the prompt |');
|
|
49
|
-
lines.push(' | 3. Write your turn result JSON to the result path |');
|
|
50
|
-
lines.push(' | |');
|
|
51
|
-
lines.push(' | The step command will detect the file and proceed. |');
|
|
52
48
|
lines.push(' +---------------------------------------------------------+');
|
|
53
49
|
lines.push('');
|
|
50
|
+
lines.push(' Steps:');
|
|
51
|
+
lines.push(` 1. Read the prompt: cat ${promptPath}`);
|
|
52
|
+
lines.push(' 2. Do the work described in the prompt');
|
|
53
|
+
lines.push(` 3. Write turn-result.json to: ${stagingPath}`);
|
|
54
|
+
lines.push(' 4. The step command will detect the file and proceed');
|
|
55
|
+
lines.push('');
|
|
56
|
+
|
|
57
|
+
// Phase-aware guidance
|
|
58
|
+
const gateHints = getPhaseGateHints(phase, roleId, config);
|
|
59
|
+
if (gateHints.length > 0) {
|
|
60
|
+
lines.push(' Gate files to update this phase:');
|
|
61
|
+
for (const hint of gateHints) {
|
|
62
|
+
lines.push(` - ${hint}`);
|
|
63
|
+
}
|
|
64
|
+
lines.push('');
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Minimal turn-result example
|
|
68
|
+
lines.push(' Minimal turn-result.json:');
|
|
69
|
+
lines.push(' {');
|
|
70
|
+
lines.push(' "schema_version": "1.0",');
|
|
71
|
+
lines.push(` "run_id": "${state.run_id || 'run_...'}",`);
|
|
72
|
+
lines.push(` "turn_id": "${turn.turn_id}",`);
|
|
73
|
+
lines.push(` "role": "${roleId}",`);
|
|
74
|
+
lines.push(` "runtime_id": "${role?.runtime || 'manual'}",`);
|
|
75
|
+
lines.push(' "status": "completed",');
|
|
76
|
+
lines.push(' "summary": "...",');
|
|
77
|
+
lines.push(' "decisions": [{"id":"DEC-001","category":"scope","statement":"...","rationale":"..."}],');
|
|
78
|
+
lines.push(' "objections": [{"id":"OBJ-001","severity":"medium","statement":"...","status":"raised"}],');
|
|
79
|
+
lines.push(' "files_changed": [],');
|
|
80
|
+
lines.push(' "verification": {"status":"skipped","commands":[],"evidence_summary":"..."},');
|
|
81
|
+
lines.push(' "artifact": {"type":"review","ref":null},');
|
|
82
|
+
lines.push(` "proposed_next_role": "${getDefaultNextRole(roleId, config)}",`);
|
|
83
|
+
lines.push(' "phase_transition_request": null,');
|
|
84
|
+
lines.push(' "run_completion_request": null');
|
|
85
|
+
lines.push(' }');
|
|
86
|
+
lines.push('');
|
|
87
|
+
lines.push(' Docs: https://agentxchain.dev/docs/getting-started');
|
|
88
|
+
lines.push('');
|
|
54
89
|
|
|
55
90
|
return lines.join('\n');
|
|
56
91
|
}
|
|
57
92
|
|
|
93
|
+
/**
|
|
94
|
+
* Return gate-file hints relevant to the current phase and role.
|
|
95
|
+
*/
|
|
96
|
+
function getPhaseGateHints(phase, roleId, config) {
|
|
97
|
+
const hints = [];
|
|
98
|
+
const gates = config.gates || {};
|
|
99
|
+
|
|
100
|
+
if (phase === 'planning' && (roleId === 'pm' || roleId === 'human')) {
|
|
101
|
+
hints.push('.planning/PM_SIGNOFF.md — change "Approved: NO" → "Approved: YES" when ready');
|
|
102
|
+
hints.push('.planning/ROADMAP.md — define phases and acceptance criteria');
|
|
103
|
+
hints.push('.planning/SYSTEM_SPEC.md — define ## Purpose, ## Interface, ## Acceptance Tests');
|
|
104
|
+
} else if (phase === 'implementation' && (roleId === 'dev' || roleId === 'human')) {
|
|
105
|
+
hints.push('.planning/IMPLEMENTATION_NOTES.md — record what you built and how to verify');
|
|
106
|
+
} else if (phase === 'qa' && (roleId === 'qa' || roleId === 'human')) {
|
|
107
|
+
hints.push('.planning/acceptance-matrix.md — mark each requirement PASS/FAIL');
|
|
108
|
+
hints.push('.planning/ship-verdict.md — change "## Verdict: PENDING" → "## Verdict: SHIP"');
|
|
109
|
+
hints.push('.planning/RELEASE_NOTES.md — user impact, verification summary, upgrade notes');
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return hints;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Suggest a reasonable next role based on current role.
|
|
117
|
+
*/
|
|
118
|
+
function getDefaultNextRole(roleId, config) {
|
|
119
|
+
const routing = config.routing || {};
|
|
120
|
+
if (routing[roleId]?.default_next) return routing[roleId].default_next;
|
|
121
|
+
if (roleId === 'pm') return 'dev';
|
|
122
|
+
if (roleId === 'dev') return 'qa';
|
|
123
|
+
if (roleId === 'qa') return 'human';
|
|
124
|
+
return 'human';
|
|
125
|
+
}
|
|
126
|
+
|
|
58
127
|
/**
|
|
59
128
|
* Wait for the staged turn result file to appear.
|
|
60
129
|
*
|