agentxchain 2.2.0 → 2.4.0
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 +17 -0
- package/bin/agentxchain.js +97 -1
- package/package.json +10 -3
- package/scripts/publish-from-tag.sh +14 -9
- package/scripts/release-postflight.sh +42 -2
- package/src/commands/init.js +1 -0
- package/src/commands/intake-approve.js +44 -0
- package/src/commands/intake-plan.js +62 -0
- package/src/commands/intake-record.js +86 -0
- package/src/commands/intake-resolve.js +45 -0
- package/src/commands/intake-scan.js +87 -0
- package/src/commands/intake-start.js +53 -0
- package/src/commands/intake-status.js +113 -0
- package/src/commands/intake-triage.js +54 -0
- package/src/commands/step.js +56 -2
- package/src/commands/template-validate.js +159 -0
- package/src/commands/verify.js +8 -3
- package/src/lib/adapters/api-proxy-adapter.js +125 -27
- package/src/lib/adapters/mcp-adapter.js +306 -0
- package/src/lib/governed-templates.js +236 -1
- package/src/lib/intake.js +924 -0
- package/src/lib/normalized-config.js +44 -1
- package/src/lib/protocol-conformance.js +28 -4
- package/src/lib/repo-observer.js +9 -8
- package/src/lib/validation.js +23 -0
- package/src/templates/governed/library.json +31 -0
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { findProjectRoot } from '../lib/config.js';
|
|
3
|
+
import { startIntent } from '../lib/intake.js';
|
|
4
|
+
|
|
5
|
+
export async function intakeStartCommand(opts) {
|
|
6
|
+
const root = findProjectRoot(process.cwd());
|
|
7
|
+
if (!root) {
|
|
8
|
+
if (opts.json) {
|
|
9
|
+
console.log(JSON.stringify({ ok: false, error: 'agentxchain.json not found' }, null, 2));
|
|
10
|
+
} else {
|
|
11
|
+
console.log(chalk.red('agentxchain.json not found'));
|
|
12
|
+
}
|
|
13
|
+
process.exit(2);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
if (!opts.intent) {
|
|
17
|
+
const msg = '--intent <id> is required';
|
|
18
|
+
if (opts.json) {
|
|
19
|
+
console.log(JSON.stringify({ ok: false, error: msg }, null, 2));
|
|
20
|
+
} else {
|
|
21
|
+
console.log(chalk.red(msg));
|
|
22
|
+
}
|
|
23
|
+
process.exit(1);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const result = startIntent(root, opts.intent, {
|
|
27
|
+
role: opts.role || undefined,
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
if (opts.json) {
|
|
31
|
+
console.log(JSON.stringify(result, null, 2));
|
|
32
|
+
} else if (result.ok) {
|
|
33
|
+
console.log('');
|
|
34
|
+
console.log(chalk.green(` Started intent ${result.intent.intent_id}`));
|
|
35
|
+
console.log(` Run: ${result.run_id}`);
|
|
36
|
+
console.log(` Turn: ${result.turn_id}`);
|
|
37
|
+
console.log(` Role: ${result.role}`);
|
|
38
|
+
console.log(chalk.dim(` Status: planned \u2192 executing`));
|
|
39
|
+
console.log('');
|
|
40
|
+
} else if (result.missing) {
|
|
41
|
+
console.log('');
|
|
42
|
+
console.log(chalk.red(` Cannot start intent ${opts.intent}`));
|
|
43
|
+
console.log(chalk.red(' Recorded planning artifacts are missing on disk:'));
|
|
44
|
+
for (const m of result.missing) {
|
|
45
|
+
console.log(chalk.red(` ${m}`));
|
|
46
|
+
}
|
|
47
|
+
console.log('');
|
|
48
|
+
} else {
|
|
49
|
+
console.log(chalk.red(` ${result.error}`));
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
process.exit(result.exitCode);
|
|
53
|
+
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { findProjectRoot } from '../lib/config.js';
|
|
3
|
+
import { intakeStatus } from '../lib/intake.js';
|
|
4
|
+
|
|
5
|
+
export async function intakeStatusCommand(opts) {
|
|
6
|
+
const root = findProjectRoot(process.cwd());
|
|
7
|
+
if (!root) {
|
|
8
|
+
if (opts.json) {
|
|
9
|
+
console.log(JSON.stringify({ ok: false, error: 'agentxchain.json not found' }, null, 2));
|
|
10
|
+
} else {
|
|
11
|
+
console.log(chalk.red('agentxchain.json not found'));
|
|
12
|
+
}
|
|
13
|
+
process.exit(2);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const result = intakeStatus(root, opts.intent || null);
|
|
17
|
+
|
|
18
|
+
if (opts.json) {
|
|
19
|
+
console.log(JSON.stringify(result, null, 2));
|
|
20
|
+
process.exit(result.exitCode);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (!result.ok) {
|
|
24
|
+
console.log(chalk.red(` ${result.error}`));
|
|
25
|
+
process.exit(result.exitCode);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Detail mode: single intent
|
|
29
|
+
if (result.intent) {
|
|
30
|
+
printIntentDetail(result.intent, result.event);
|
|
31
|
+
process.exit(0);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// List mode: summary
|
|
35
|
+
printSummary(result.summary);
|
|
36
|
+
process.exit(0);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function printSummary(summary) {
|
|
40
|
+
console.log('');
|
|
41
|
+
console.log(chalk.bold(' AgentXchain Intake Status'));
|
|
42
|
+
console.log(chalk.dim(' ' + '─'.repeat(44)));
|
|
43
|
+
console.log(` ${chalk.dim('Events:')} ${summary.total_events}`);
|
|
44
|
+
|
|
45
|
+
const statusParts = Object.entries(summary.by_status)
|
|
46
|
+
.map(([s, c]) => `${s}: ${c}`)
|
|
47
|
+
.join(', ');
|
|
48
|
+
console.log(` ${chalk.dim('Intents:')} ${summary.total_intents} (${statusParts || 'none'})`);
|
|
49
|
+
console.log('');
|
|
50
|
+
|
|
51
|
+
if (summary.intents.length > 0) {
|
|
52
|
+
console.log(chalk.dim(' Recent Intents:'));
|
|
53
|
+
for (const i of summary.intents.slice(0, 20)) {
|
|
54
|
+
const pri = i.priority ? i.priority.padEnd(3) : '---';
|
|
55
|
+
const tpl = (i.template || '---').padEnd(12);
|
|
56
|
+
const st = statusColor(i.status);
|
|
57
|
+
console.log(` ${chalk.dim(i.intent_id)} ${pri} ${tpl} ${st} ${chalk.dim(i.updated_at)}`);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
console.log('');
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function printIntentDetail(intent, event) {
|
|
65
|
+
console.log('');
|
|
66
|
+
console.log(chalk.bold(` Intent: ${intent.intent_id}`));
|
|
67
|
+
console.log(chalk.dim(' ' + '─'.repeat(44)));
|
|
68
|
+
console.log(` ${chalk.dim('Status:')} ${statusColor(intent.status)}`);
|
|
69
|
+
console.log(` ${chalk.dim('Event:')} ${intent.event_id}`);
|
|
70
|
+
console.log(` ${chalk.dim('Priority:')} ${intent.priority || '—'}`);
|
|
71
|
+
console.log(` ${chalk.dim('Template:')} ${intent.template || '—'}`);
|
|
72
|
+
console.log(` ${chalk.dim('Charter:')} ${intent.charter || '—'}`);
|
|
73
|
+
console.log(` ${chalk.dim('Created:')} ${intent.created_at}`);
|
|
74
|
+
console.log(` ${chalk.dim('Updated:')} ${intent.updated_at}`);
|
|
75
|
+
|
|
76
|
+
if (intent.acceptance_contract && intent.acceptance_contract.length > 0) {
|
|
77
|
+
console.log('');
|
|
78
|
+
console.log(chalk.dim(' Acceptance Contract:'));
|
|
79
|
+
for (const a of intent.acceptance_contract) {
|
|
80
|
+
console.log(` - ${a}`);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (intent.history && intent.history.length > 0) {
|
|
85
|
+
console.log('');
|
|
86
|
+
console.log(chalk.dim(' History:'));
|
|
87
|
+
for (const h of intent.history) {
|
|
88
|
+
const from = h.from || '(new)';
|
|
89
|
+
console.log(` ${chalk.dim(h.at)} ${from} → ${h.to} ${chalk.dim(h.reason)}`);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (event) {
|
|
94
|
+
console.log('');
|
|
95
|
+
console.log(chalk.dim(' Source Event:'));
|
|
96
|
+
console.log(` ${chalk.dim('Source:')} ${event.source}`);
|
|
97
|
+
console.log(` ${chalk.dim('Signal:')} ${JSON.stringify(event.signal)}`);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
console.log('');
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function statusColor(status) {
|
|
104
|
+
switch (status) {
|
|
105
|
+
case 'detected': return chalk.yellow(status);
|
|
106
|
+
case 'triaged': return chalk.cyan(status);
|
|
107
|
+
case 'approved': return chalk.green(status);
|
|
108
|
+
case 'planned': return chalk.green(status);
|
|
109
|
+
case 'suppressed': return chalk.dim(status);
|
|
110
|
+
case 'rejected': return chalk.red(status);
|
|
111
|
+
default: return status;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { findProjectRoot } from '../lib/config.js';
|
|
3
|
+
import { triageIntent } from '../lib/intake.js';
|
|
4
|
+
|
|
5
|
+
export async function intakeTriageCommand(opts) {
|
|
6
|
+
const root = findProjectRoot(process.cwd());
|
|
7
|
+
if (!root) {
|
|
8
|
+
if (opts.json) {
|
|
9
|
+
console.log(JSON.stringify({ ok: false, error: 'agentxchain.json not found' }, null, 2));
|
|
10
|
+
} else {
|
|
11
|
+
console.log(chalk.red('agentxchain.json not found'));
|
|
12
|
+
}
|
|
13
|
+
process.exit(2);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
if (!opts.intent) {
|
|
17
|
+
const msg = '--intent <id> is required';
|
|
18
|
+
if (opts.json) {
|
|
19
|
+
console.log(JSON.stringify({ ok: false, error: msg }, null, 2));
|
|
20
|
+
} else {
|
|
21
|
+
console.log(chalk.red(msg));
|
|
22
|
+
}
|
|
23
|
+
process.exit(1);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const fields = {
|
|
27
|
+
suppress: opts.suppress || false,
|
|
28
|
+
reject: opts.reject || false,
|
|
29
|
+
reason: opts.reason || null,
|
|
30
|
+
priority: opts.priority || null,
|
|
31
|
+
template: opts.template || null,
|
|
32
|
+
charter: opts.charter || null,
|
|
33
|
+
acceptance_contract: opts.acceptance
|
|
34
|
+
? opts.acceptance.split(',').map(s => s.trim()).filter(Boolean)
|
|
35
|
+
: [],
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const result = triageIntent(root, opts.intent, fields);
|
|
39
|
+
|
|
40
|
+
if (opts.json) {
|
|
41
|
+
console.log(JSON.stringify(result, null, 2));
|
|
42
|
+
} else if (result.ok) {
|
|
43
|
+
console.log('');
|
|
44
|
+
console.log(chalk.green(` Intent ${result.intent.intent_id} → ${result.intent.status}`));
|
|
45
|
+
if (result.intent.priority) {
|
|
46
|
+
console.log(chalk.dim(` Priority: ${result.intent.priority} Template: ${result.intent.template}`));
|
|
47
|
+
}
|
|
48
|
+
console.log('');
|
|
49
|
+
} else {
|
|
50
|
+
console.log(chalk.red(` ${result.error}`));
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
process.exit(result.exitCode);
|
|
54
|
+
}
|
package/src/commands/step.js
CHANGED
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
* - local_cli: implemented via subprocess dispatch + staged turn result
|
|
18
18
|
* - api_proxy: implemented for synchronous review-only turns and stages
|
|
19
19
|
* provider-backed JSON before validation/acceptance
|
|
20
|
+
* - mcp: implemented for synchronous MCP stdio tool dispatch
|
|
20
21
|
*/
|
|
21
22
|
|
|
22
23
|
import chalk from 'chalk';
|
|
@@ -45,6 +46,7 @@ import {
|
|
|
45
46
|
saveDispatchLogs,
|
|
46
47
|
resolvePromptTransport,
|
|
47
48
|
} from '../lib/adapters/local-cli-adapter.js';
|
|
49
|
+
import { dispatchMcp } from '../lib/adapters/mcp-adapter.js';
|
|
48
50
|
import {
|
|
49
51
|
getDispatchAssignmentPath,
|
|
50
52
|
getDispatchContextPath,
|
|
@@ -423,12 +425,64 @@ export async function stepCommand(opts) {
|
|
|
423
425
|
console.log(chalk.dim(` Tokens: ${apiResult.usage.input_tokens || 0} in / ${apiResult.usage.output_tokens || 0} out`));
|
|
424
426
|
}
|
|
425
427
|
console.log('');
|
|
428
|
+
} else if (runtimeType === 'mcp') {
|
|
429
|
+
console.log(chalk.cyan(`Dispatching to MCP stdio: ${runtime?.command || '(unknown)'}`));
|
|
430
|
+
console.log(chalk.dim(`Turn: ${turn.turn_id} Role: ${roleId} Phase: ${state.phase} Tool: ${runtime?.tool_name || 'agentxchain_turn'}`));
|
|
431
|
+
|
|
432
|
+
const mcpResult = await dispatchMcp(root, state, config, {
|
|
433
|
+
signal: controller.signal,
|
|
434
|
+
onStatus: (msg) => console.log(chalk.dim(` ${msg}`)),
|
|
435
|
+
onStderr: opts.verbose ? (text) => process.stderr.write(chalk.yellow(text)) : undefined,
|
|
436
|
+
verifyManifest: true,
|
|
437
|
+
});
|
|
438
|
+
|
|
439
|
+
if (mcpResult.logs?.length) {
|
|
440
|
+
saveDispatchLogs(root, turn.turn_id, mcpResult.logs);
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
if (mcpResult.aborted) {
|
|
444
|
+
console.log('');
|
|
445
|
+
console.log(chalk.yellow('Aborted. Turn remains assigned.'));
|
|
446
|
+
console.log(chalk.dim('Resume later with: agentxchain step --resume'));
|
|
447
|
+
console.log(chalk.dim('Or accept/reject manually: agentxchain accept-turn / reject-turn'));
|
|
448
|
+
process.exit(0);
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
if (!mcpResult.ok) {
|
|
452
|
+
const blocked = markRunBlocked(root, {
|
|
453
|
+
blockedOn: 'dispatch:mcp_failure',
|
|
454
|
+
category: 'dispatch_error',
|
|
455
|
+
recovery: {
|
|
456
|
+
typed_reason: 'dispatch_error',
|
|
457
|
+
owner: 'human',
|
|
458
|
+
recovery_action: 'Resolve the MCP dispatch issue, then run agentxchain step --resume',
|
|
459
|
+
turn_retained: true,
|
|
460
|
+
detail: mcpResult.error,
|
|
461
|
+
},
|
|
462
|
+
turnId: turn.turn_id,
|
|
463
|
+
hooksConfig,
|
|
464
|
+
});
|
|
465
|
+
if (blocked.ok) {
|
|
466
|
+
state = blocked.state;
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
console.log('');
|
|
470
|
+
console.log(chalk.red(`MCP dispatch failed: ${mcpResult.error}`));
|
|
471
|
+
console.log(chalk.dim('The turn remains assigned. You can:'));
|
|
472
|
+
console.log(chalk.dim(' - Fix the MCP server/runtime and retry: agentxchain step --resume'));
|
|
473
|
+
console.log(chalk.dim(' - Complete manually: edit .agentxchain/staging/turn-result.json'));
|
|
474
|
+
console.log(chalk.dim(' - Reject: agentxchain reject-turn --reason "mcp dispatch failed"'));
|
|
475
|
+
process.exit(1);
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
console.log(chalk.green(`MCP tool completed${mcpResult.toolName ? ` (${mcpResult.toolName})` : ''}. Staged result detected.`));
|
|
479
|
+
console.log('');
|
|
426
480
|
}
|
|
427
481
|
|
|
428
482
|
// ── Phase 3: Wait for turn completion ─────────────────────────────────────
|
|
429
483
|
|
|
430
|
-
if (runtimeType === 'api_proxy') {
|
|
431
|
-
// api_proxy
|
|
484
|
+
if (runtimeType === 'api_proxy' || runtimeType === 'mcp') {
|
|
485
|
+
// api_proxy and mcp are synchronous — result already staged in Phase 2
|
|
432
486
|
} else if (runtimeType === 'local_cli') {
|
|
433
487
|
// ── Local CLI adapter: spawn subprocess ──
|
|
434
488
|
const transport = runtime ? resolvePromptTransport(runtime) : 'dispatch_bundle_only';
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import chalk from 'chalk';
|
|
4
|
+
import { CONFIG_FILE, findProjectRoot } from '../lib/config.js';
|
|
5
|
+
import {
|
|
6
|
+
validateGovernedProjectTemplate,
|
|
7
|
+
validateGovernedTemplateRegistry,
|
|
8
|
+
validateProjectPlanningArtifacts,
|
|
9
|
+
validateAcceptanceHintCompletion,
|
|
10
|
+
} from '../lib/governed-templates.js';
|
|
11
|
+
|
|
12
|
+
function loadProjectTemplateValidation() {
|
|
13
|
+
const root = findProjectRoot();
|
|
14
|
+
if (!root) {
|
|
15
|
+
return {
|
|
16
|
+
present: false,
|
|
17
|
+
root: null,
|
|
18
|
+
template: null,
|
|
19
|
+
source: null,
|
|
20
|
+
ok: true,
|
|
21
|
+
errors: [],
|
|
22
|
+
warnings: [],
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const configPath = join(root, CONFIG_FILE);
|
|
27
|
+
let parsed;
|
|
28
|
+
try {
|
|
29
|
+
parsed = JSON.parse(readFileSync(configPath, 'utf8'));
|
|
30
|
+
} catch (err) {
|
|
31
|
+
return {
|
|
32
|
+
present: true,
|
|
33
|
+
root,
|
|
34
|
+
template: null,
|
|
35
|
+
source: 'agentxchain.json',
|
|
36
|
+
ok: false,
|
|
37
|
+
errors: [`Failed to parse ${CONFIG_FILE}: ${err.message}`],
|
|
38
|
+
warnings: [],
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const projectValidation = validateGovernedProjectTemplate(parsed.template);
|
|
43
|
+
return {
|
|
44
|
+
present: true,
|
|
45
|
+
root,
|
|
46
|
+
...projectValidation,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function templateValidateCommand(opts = {}) {
|
|
51
|
+
const registry = validateGovernedTemplateRegistry();
|
|
52
|
+
const project = loadProjectTemplateValidation();
|
|
53
|
+
|
|
54
|
+
// Planning artifact completeness check
|
|
55
|
+
let planningArtifacts = null;
|
|
56
|
+
if (project.present && project.ok && project.root) {
|
|
57
|
+
planningArtifacts = validateProjectPlanningArtifacts(project.root, project.template);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Acceptance hint completion check
|
|
61
|
+
let acceptanceHints = null;
|
|
62
|
+
if (project.present && project.ok && project.root) {
|
|
63
|
+
acceptanceHints = validateAcceptanceHintCompletion(project.root, project.template);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const errors = [
|
|
67
|
+
...registry.errors,
|
|
68
|
+
...project.errors,
|
|
69
|
+
...(planningArtifacts?.errors || []),
|
|
70
|
+
];
|
|
71
|
+
const warnings = [
|
|
72
|
+
...registry.warnings,
|
|
73
|
+
...project.warnings,
|
|
74
|
+
...(planningArtifacts?.warnings || []),
|
|
75
|
+
...(acceptanceHints?.warnings || []),
|
|
76
|
+
];
|
|
77
|
+
const ok = errors.length === 0;
|
|
78
|
+
|
|
79
|
+
const payload = {
|
|
80
|
+
ok,
|
|
81
|
+
registry,
|
|
82
|
+
project,
|
|
83
|
+
planning_artifacts: planningArtifacts,
|
|
84
|
+
acceptance_hints: acceptanceHints,
|
|
85
|
+
errors,
|
|
86
|
+
warnings,
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
if (opts.json) {
|
|
90
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
91
|
+
if (!ok) process.exit(1);
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
console.log('');
|
|
96
|
+
console.log(chalk.bold(' AgentXchain Template Validate'));
|
|
97
|
+
console.log(chalk.dim(' ' + '─'.repeat(44)));
|
|
98
|
+
console.log('');
|
|
99
|
+
|
|
100
|
+
if (ok) {
|
|
101
|
+
console.log(chalk.green(' ✓ Template validation passed.'));
|
|
102
|
+
} else {
|
|
103
|
+
console.log(chalk.red(` ✗ Template validation failed (${errors.length} errors).`));
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
console.log('');
|
|
107
|
+
console.log(` ${chalk.dim('Registry:')} ${registry.ok ? chalk.green('OK') : chalk.red('FAIL')} (${registry.registered_ids.length} registered, ${registry.manifest_ids.length} manifests)`);
|
|
108
|
+
|
|
109
|
+
if (project.present) {
|
|
110
|
+
const sourceLabel = project.source === 'implicit_default'
|
|
111
|
+
? 'implicit default'
|
|
112
|
+
: project.source;
|
|
113
|
+
console.log(` ${chalk.dim('Project:')} ${project.ok ? chalk.green('OK') : chalk.red('FAIL')} (${project.template} via ${sourceLabel})`);
|
|
114
|
+
if (project.root && existsSync(project.root)) {
|
|
115
|
+
console.log(` ${chalk.dim('Root:')} ${project.root}`);
|
|
116
|
+
}
|
|
117
|
+
if (planningArtifacts) {
|
|
118
|
+
const total = planningArtifacts.expected.length;
|
|
119
|
+
const found = planningArtifacts.present.length;
|
|
120
|
+
if (total === 0) {
|
|
121
|
+
console.log(` ${chalk.dim('Planning:')} ${chalk.green('OK')} (no template artifacts required)`);
|
|
122
|
+
} else if (planningArtifacts.ok) {
|
|
123
|
+
console.log(` ${chalk.dim('Planning:')} ${chalk.green('OK')} (${found}/${total} present)`);
|
|
124
|
+
} else {
|
|
125
|
+
console.log(` ${chalk.dim('Planning:')} ${chalk.red('FAIL')} (${planningArtifacts.missing.length}/${total} missing: ${planningArtifacts.missing.join(', ')})`);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
if (acceptanceHints) {
|
|
129
|
+
if (acceptanceHints.total === 0) {
|
|
130
|
+
console.log(` ${chalk.dim('Acceptance:')} ${chalk.green('OK')} (no template hints defined)`);
|
|
131
|
+
} else if (acceptanceHints.unchecked === 0) {
|
|
132
|
+
console.log(` ${chalk.dim('Acceptance:')} ${chalk.green('OK')} (${acceptanceHints.checked}/${acceptanceHints.total} checked)`);
|
|
133
|
+
} else {
|
|
134
|
+
console.log(` ${chalk.dim('Acceptance:')} ${chalk.yellow('WARN')} (${acceptanceHints.unchecked}/${acceptanceHints.total} unchecked)`);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
} else {
|
|
138
|
+
console.log(` ${chalk.dim('Project:')} ${chalk.dim('No project detected; registry-only validation')}`);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
if (errors.length > 0) {
|
|
142
|
+
console.log('');
|
|
143
|
+
console.log(chalk.red(' Errors:'));
|
|
144
|
+
for (const error of errors) {
|
|
145
|
+
console.log(` - ${error}`);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if (warnings.length > 0) {
|
|
150
|
+
console.log('');
|
|
151
|
+
console.log(chalk.yellow(' Warnings:'));
|
|
152
|
+
for (const warning of warnings) {
|
|
153
|
+
console.log(` - ${warning}`);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
console.log('');
|
|
158
|
+
if (!ok) process.exit(1);
|
|
159
|
+
}
|
package/src/commands/verify.js
CHANGED
|
@@ -57,13 +57,18 @@ function printProtocolReport(report) {
|
|
|
57
57
|
: tier.status === 'skipped'
|
|
58
58
|
? chalk.yellow('skipped')
|
|
59
59
|
: chalk.red(tier.status);
|
|
60
|
-
|
|
60
|
+
const niCount = tier.fixtures_not_implemented || 0;
|
|
61
|
+
const niSuffix = niCount > 0 ? chalk.yellow(`, ${niCount} not implemented`) : '';
|
|
62
|
+
console.log(` ${tierKey}: ${label} (${tier.fixtures_passed}/${tier.fixtures_run} passed${niSuffix})`);
|
|
61
63
|
|
|
64
|
+
for (const ni of tier.not_implemented || []) {
|
|
65
|
+
console.log(chalk.yellow(` ○ ${ni.fixture_id}: ${ni.message}`));
|
|
66
|
+
}
|
|
62
67
|
for (const failure of tier.failures || []) {
|
|
63
|
-
console.log(chalk.red(`
|
|
68
|
+
console.log(chalk.red(` ✗ ${failure.fixture_id}: ${failure.message}`));
|
|
64
69
|
}
|
|
65
70
|
for (const error of tier.errors || []) {
|
|
66
|
-
console.log(chalk.red(`
|
|
71
|
+
console.log(chalk.red(` ✗ ${error.fixture_id}: ${error.message}`));
|
|
67
72
|
}
|
|
68
73
|
}
|
|
69
74
|
|