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
|
@@ -13,9 +13,11 @@
|
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
15
|
import { validateHooksConfig } from './hook-runner.js';
|
|
16
|
+
import { SUPPORTED_TOKEN_COUNTER_PROVIDERS } from './token-counter.js';
|
|
16
17
|
|
|
17
18
|
const VALID_WRITE_AUTHORITIES = ['authoritative', 'proposed', 'review_only'];
|
|
18
|
-
const VALID_RUNTIME_TYPES = ['manual', 'local_cli', 'api_proxy'];
|
|
19
|
+
const VALID_RUNTIME_TYPES = ['manual', 'local_cli', 'api_proxy', 'mcp'];
|
|
20
|
+
const VALID_API_PROXY_PROVIDERS = ['anthropic', 'openai'];
|
|
19
21
|
const VALID_PROMPT_TRANSPORTS = ['argv', 'stdin', 'dispatch_bundle_only'];
|
|
20
22
|
const VALID_PHASES = ['planning', 'implementation', 'qa'];
|
|
21
23
|
const VALID_API_PROXY_RETRY_JITTER = ['none', 'full'];
|
|
@@ -44,6 +46,36 @@ const VALID_API_PROXY_PREFLIGHT_FIELDS = [
|
|
|
44
46
|
'safety_margin_tokens',
|
|
45
47
|
];
|
|
46
48
|
|
|
49
|
+
function validateMcpRuntime(runtimeId, runtime, errors) {
|
|
50
|
+
const command = runtime?.command;
|
|
51
|
+
|
|
52
|
+
if (typeof command === 'string') {
|
|
53
|
+
if (!command.trim()) {
|
|
54
|
+
errors.push(`Runtime "${runtimeId}": mcp command must be a non-empty string`);
|
|
55
|
+
}
|
|
56
|
+
} else if (Array.isArray(command)) {
|
|
57
|
+
if (command.length === 0 || command.some((part) => typeof part !== 'string' || !part.trim())) {
|
|
58
|
+
errors.push(`Runtime "${runtimeId}": mcp command array must contain at least one non-empty string and no empty parts`);
|
|
59
|
+
}
|
|
60
|
+
} else {
|
|
61
|
+
errors.push(`Runtime "${runtimeId}": mcp requires "command" as a string or string array`);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if ('args' in runtime) {
|
|
65
|
+
if (!Array.isArray(runtime.args) || runtime.args.some((part) => typeof part !== 'string')) {
|
|
66
|
+
errors.push(`Runtime "${runtimeId}": mcp args must be an array of strings`);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if ('tool_name' in runtime && (typeof runtime.tool_name !== 'string' || !runtime.tool_name.trim())) {
|
|
71
|
+
errors.push(`Runtime "${runtimeId}": mcp tool_name must be a non-empty string`);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if ('cwd' in runtime && (typeof runtime.cwd !== 'string' || !runtime.cwd.trim())) {
|
|
75
|
+
errors.push(`Runtime "${runtimeId}": mcp cwd must be a non-empty string`);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
47
79
|
function validateApiProxyRetryPolicy(runtimeId, retryPolicy, errors) {
|
|
48
80
|
if (!retryPolicy || typeof retryPolicy !== 'object' || Array.isArray(retryPolicy)) {
|
|
49
81
|
errors.push(`Runtime "${runtimeId}": retry_policy must be an object`);
|
|
@@ -146,6 +178,12 @@ function validateApiProxyPreflightTokenization(runtimeId, runtime, errors) {
|
|
|
146
178
|
}
|
|
147
179
|
|
|
148
180
|
if (preflight.enabled === true) {
|
|
181
|
+
if (!SUPPORTED_TOKEN_COUNTER_PROVIDERS.includes(runtime.provider)) {
|
|
182
|
+
errors.push(
|
|
183
|
+
`Runtime "${runtimeId}": preflight_tokenization tokenizer "provider_local" is not supported for provider "${runtime.provider}". Supported providers: ${SUPPORTED_TOKEN_COUNTER_PROVIDERS.join(', ')}`
|
|
184
|
+
);
|
|
185
|
+
}
|
|
186
|
+
|
|
149
187
|
if (!Number.isInteger(runtime.context_window_tokens) || runtime.context_window_tokens <= 0) {
|
|
150
188
|
errors.push(`Runtime "${runtimeId}": context_window_tokens is required when preflight_tokenization.enabled is true`);
|
|
151
189
|
return;
|
|
@@ -239,6 +277,8 @@ export function validateV4Config(data, projectRoot) {
|
|
|
239
277
|
if (rt.type === 'api_proxy') {
|
|
240
278
|
if (typeof rt.provider !== 'string' || !rt.provider.trim()) {
|
|
241
279
|
errors.push(`Runtime "${id}": api_proxy requires "provider" (e.g. "anthropic", "openai")`);
|
|
280
|
+
} else if (!VALID_API_PROXY_PROVIDERS.includes(rt.provider)) {
|
|
281
|
+
errors.push(`Runtime "${id}": api_proxy provider must be one of: ${VALID_API_PROXY_PROVIDERS.join(', ')}`);
|
|
242
282
|
}
|
|
243
283
|
if (typeof rt.model !== 'string' || !rt.model.trim()) {
|
|
244
284
|
errors.push(`Runtime "${id}": api_proxy requires "model" (e.g. "claude-sonnet-4-6")`);
|
|
@@ -253,6 +293,9 @@ export function validateV4Config(data, projectRoot) {
|
|
|
253
293
|
validateApiProxyPreflightTokenization(id, rt, errors);
|
|
254
294
|
}
|
|
255
295
|
}
|
|
296
|
+
if (rt.type === 'mcp') {
|
|
297
|
+
validateMcpRuntime(id, rt, errors);
|
|
298
|
+
}
|
|
256
299
|
}
|
|
257
300
|
}
|
|
258
301
|
|
|
@@ -5,7 +5,7 @@ import { fileURLToPath } from 'node:url';
|
|
|
5
5
|
|
|
6
6
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
7
7
|
const DEFAULT_FIXTURE_ROOT = resolve(__dirname, '..', '..', '..', '.agentxchain-conformance', 'fixtures');
|
|
8
|
-
const VALID_RESPONSE_STATUSES = new Set(['pass', 'fail', 'error']);
|
|
8
|
+
const VALID_RESPONSE_STATUSES = new Set(['pass', 'fail', 'error', 'not_implemented']);
|
|
9
9
|
const VALID_TIERS = new Set([1, 2, 3]);
|
|
10
10
|
|
|
11
11
|
function readJsonFile(filePath) {
|
|
@@ -119,16 +119,18 @@ function createTierSummary(status = 'skipped', note = null) {
|
|
|
119
119
|
fixtures_passed: 0,
|
|
120
120
|
fixtures_failed: 0,
|
|
121
121
|
fixtures_errored: 0,
|
|
122
|
+
fixtures_not_implemented: 0,
|
|
122
123
|
surfaces: {},
|
|
123
124
|
failures: [],
|
|
124
125
|
errors: [],
|
|
126
|
+
not_implemented: [],
|
|
125
127
|
...(note ? { note } : {}),
|
|
126
128
|
};
|
|
127
129
|
}
|
|
128
130
|
|
|
129
131
|
function ensureSurfaceSummary(tierSummary, surface) {
|
|
130
132
|
if (!tierSummary.surfaces[surface]) {
|
|
131
|
-
tierSummary.surfaces[surface] = { passed: 0, failed: 0, errored: 0 };
|
|
133
|
+
tierSummary.surfaces[surface] = { passed: 0, failed: 0, errored: 0, not_implemented: 0 };
|
|
132
134
|
}
|
|
133
135
|
return tierSummary.surfaces[surface];
|
|
134
136
|
}
|
|
@@ -149,7 +151,7 @@ function executeFixture(targetRoot, adapterCommand, fixture) {
|
|
|
149
151
|
};
|
|
150
152
|
}
|
|
151
153
|
|
|
152
|
-
if (![0, 1, 2].includes(result.status ?? -1)) {
|
|
154
|
+
if (![0, 1, 2, 3].includes(result.status ?? -1)) {
|
|
153
155
|
return {
|
|
154
156
|
status: 'error',
|
|
155
157
|
message: `Adapter exited with unsupported status ${result.status}`,
|
|
@@ -182,7 +184,7 @@ function executeFixture(targetRoot, adapterCommand, fixture) {
|
|
|
182
184
|
};
|
|
183
185
|
}
|
|
184
186
|
|
|
185
|
-
const expectedExitCode = parsed.status === 'pass' ? 0 : parsed.status === 'fail' ? 1 : 2;
|
|
187
|
+
const expectedExitCode = parsed.status === 'pass' ? 0 : parsed.status === 'fail' ? 1 : parsed.status === 'not_implemented' ? 3 : 2;
|
|
186
188
|
if (result.status !== expectedExitCode) {
|
|
187
189
|
return {
|
|
188
190
|
status: 'error',
|
|
@@ -210,6 +212,17 @@ export function verifyProtocolConformance({
|
|
|
210
212
|
|
|
211
213
|
const resolvedTargetRoot = resolve(targetRoot);
|
|
212
214
|
const capabilities = loadCapabilities(resolvedTargetRoot);
|
|
215
|
+
|
|
216
|
+
// Enforce surface claims when capabilities.surfaces exists and --surface is requested
|
|
217
|
+
if (surface && capabilities.surfaces && typeof capabilities.surfaces === 'object') {
|
|
218
|
+
if (!capabilities.surfaces[surface]) {
|
|
219
|
+
throw new Error(
|
|
220
|
+
`Surface "${surface}" is not claimed in capabilities.json. ` +
|
|
221
|
+
`Claimed surfaces: ${Object.keys(capabilities.surfaces).join(', ')}`
|
|
222
|
+
);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
213
226
|
const fixtureEntries = selectFixtureFiles(fixtureRoot, requestedTier, surface);
|
|
214
227
|
const claimedTiers = new Set(capabilities.tiers);
|
|
215
228
|
const report = {
|
|
@@ -250,6 +263,17 @@ export function verifyProtocolConformance({
|
|
|
250
263
|
continue;
|
|
251
264
|
}
|
|
252
265
|
|
|
266
|
+
if (adapterResult.status === 'not_implemented') {
|
|
267
|
+
tierSummary.fixtures_not_implemented += 1;
|
|
268
|
+
surfaceSummary.not_implemented += 1;
|
|
269
|
+
tierSummary.not_implemented.push({
|
|
270
|
+
fixture_id: fixture.fixture_id,
|
|
271
|
+
surface: fixture.surface,
|
|
272
|
+
message: adapterResult.message || 'Not implemented',
|
|
273
|
+
});
|
|
274
|
+
continue;
|
|
275
|
+
}
|
|
276
|
+
|
|
253
277
|
if (adapterResult.status === 'fail') {
|
|
254
278
|
tierSummary.fixtures_failed += 1;
|
|
255
279
|
surfaceSummary.failed += 1;
|
package/src/lib/repo-observer.js
CHANGED
|
@@ -25,6 +25,7 @@ import { join } from 'path';
|
|
|
25
25
|
const OPERATIONAL_PATH_PREFIXES = [
|
|
26
26
|
'.agentxchain/dispatch/',
|
|
27
27
|
'.agentxchain/staging/',
|
|
28
|
+
'.agentxchain/intake/',
|
|
28
29
|
'.agentxchain/locks/',
|
|
29
30
|
'.agentxchain/transactions/',
|
|
30
31
|
];
|
|
@@ -269,13 +270,13 @@ export function buildObservedArtifact(observation, baseline) {
|
|
|
269
270
|
*
|
|
270
271
|
* Normalization rules (spec §5.3):
|
|
271
272
|
* - manual + external pass → attested_pass
|
|
272
|
-
* -
|
|
273
|
-
* -
|
|
273
|
+
* - executable runtime + pass + machine evidence all zero → pass
|
|
274
|
+
* - executable runtime + pass + no reproducible evidence → not_reproducible
|
|
274
275
|
* - any external fail → fail
|
|
275
276
|
* - external skipped → skipped
|
|
276
277
|
*
|
|
277
278
|
* @param {object} verification — the actor-supplied verification object
|
|
278
|
-
* @param {string} runtimeType — 'manual' | 'local_cli' | 'api_proxy'
|
|
279
|
+
* @param {string} runtimeType — 'manual' | 'local_cli' | 'api_proxy' | 'mcp'
|
|
279
280
|
* @returns {{ status: string, reason: string, reproducible: boolean }}
|
|
280
281
|
*/
|
|
281
282
|
export function normalizeVerification(verification, runtimeType) {
|
|
@@ -298,18 +299,18 @@ export function normalizeVerification(verification, runtimeType) {
|
|
|
298
299
|
return { status: 'attested_pass', reason: 'API proxy runtime — no direct execution environment', reproducible: false };
|
|
299
300
|
}
|
|
300
301
|
|
|
301
|
-
// local_cli — check for machine evidence
|
|
302
|
+
// local_cli / mcp — check for machine evidence
|
|
302
303
|
const evidence = verification?.machine_evidence;
|
|
303
304
|
if (Array.isArray(evidence) && evidence.length > 0) {
|
|
304
305
|
const allZero = evidence.every(e => typeof e.exit_code === 'number' && e.exit_code === 0);
|
|
305
306
|
if (allZero) {
|
|
306
|
-
return { status: 'pass', reason:
|
|
307
|
+
return { status: 'pass', reason: `${runtimeType} turn provided machine evidence with zero exit codes`, reproducible: true };
|
|
307
308
|
}
|
|
308
|
-
return { status: 'not_reproducible', reason:
|
|
309
|
+
return { status: 'not_reproducible', reason: `${runtimeType} turn has machine evidence with non-zero exit codes despite claiming pass`, reproducible: false };
|
|
309
310
|
}
|
|
310
311
|
|
|
311
|
-
//
|
|
312
|
-
return { status: 'not_reproducible', reason:
|
|
312
|
+
// executable runtime + pass but no machine evidence
|
|
313
|
+
return { status: 'not_reproducible', reason: `${runtimeType} turn claimed pass but provided no machine evidence`, reproducible: false };
|
|
313
314
|
}
|
|
314
315
|
|
|
315
316
|
// ── Declared vs Observed Comparison ─────────────────────────────────────────
|
package/src/lib/validation.js
CHANGED
|
@@ -2,6 +2,12 @@ import { existsSync, readFileSync, readdirSync } from 'fs';
|
|
|
2
2
|
import { join } from 'path';
|
|
3
3
|
import { validateStagedTurnResult, STAGING_PATH } from './turn-result-validator.js';
|
|
4
4
|
import { getActiveTurn } from './governed-state.js';
|
|
5
|
+
import {
|
|
6
|
+
validateGovernedProjectTemplate,
|
|
7
|
+
validateGovernedTemplateRegistry,
|
|
8
|
+
validateProjectPlanningArtifacts,
|
|
9
|
+
validateAcceptanceHintCompletion,
|
|
10
|
+
} from './governed-templates.js';
|
|
5
11
|
|
|
6
12
|
const DEFAULT_REQUIRED_FILES = [
|
|
7
13
|
'.planning/PROJECT.md',
|
|
@@ -87,6 +93,23 @@ export function validateGovernedProject(root, rawConfig, config, opts = {}) {
|
|
|
87
93
|
const errors = [];
|
|
88
94
|
const warnings = [];
|
|
89
95
|
|
|
96
|
+
const templateRegistry = validateGovernedTemplateRegistry();
|
|
97
|
+
errors.push(...templateRegistry.errors);
|
|
98
|
+
warnings.push(...templateRegistry.warnings);
|
|
99
|
+
|
|
100
|
+
const projectTemplate = validateGovernedProjectTemplate(rawConfig?.template);
|
|
101
|
+
errors.push(...projectTemplate.errors);
|
|
102
|
+
warnings.push(...projectTemplate.warnings);
|
|
103
|
+
|
|
104
|
+
// Validate planning artifact completeness against the configured template
|
|
105
|
+
const planningArtifacts = validateProjectPlanningArtifacts(root, rawConfig?.template);
|
|
106
|
+
errors.push(...planningArtifacts.errors);
|
|
107
|
+
warnings.push(...planningArtifacts.warnings);
|
|
108
|
+
|
|
109
|
+
// Validate acceptance hint completion against the configured template
|
|
110
|
+
const acceptanceHints = validateAcceptanceHintCompletion(root, rawConfig?.template);
|
|
111
|
+
warnings.push(...acceptanceHints.warnings);
|
|
112
|
+
|
|
90
113
|
const mustExist = [
|
|
91
114
|
config.files?.state || '.agentxchain/state.json',
|
|
92
115
|
config.files?.history || '.agentxchain/history.jsonl',
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "library",
|
|
3
|
+
"display_name": "Library",
|
|
4
|
+
"description": "Governed scaffold for reusable packages with public-API, compatibility, and adoption planning.",
|
|
5
|
+
"version": "1",
|
|
6
|
+
"protocol_compatibility": ["1.0", "1.1"],
|
|
7
|
+
"planning_artifacts": [
|
|
8
|
+
{
|
|
9
|
+
"filename": "public-api.md",
|
|
10
|
+
"content_template": "# Public API — {{project_name}}\n\n## Supported Entrypoints\n- Package name:\n- Import / require paths:\n- Stable vs experimental exports:\n\n## Consumer-Facing Surface\n| Export / command | Consumer use case | Stability | Notes |\n|------------------|-------------------|-----------|-------|\n| | | | |\n\n## Breaking-Change Triggers\n- What counts as breaking:\n- What can ship behind deprecation first:\n- What must stay internal:\n"
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
"filename": "compatibility-policy.md",
|
|
14
|
+
"content_template": "# Compatibility Policy — {{project_name}}\n\n## Versioning Contract\n- Semver policy:\n- Supported runtime versions:\n- Supported platforms / environments:\n\n## Deprecation And Migration\n| Surface | Deprecation path | Migration guidance | Removal target |\n|---------|------------------|--------------------|----------------|\n| | | | |\n\n## Compatibility Risks\n- Dependency upgrade hazards:\n- Packaging / module-format risks:\n- Consumer breakage watchpoints:\n"
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
"filename": "release-adoption.md",
|
|
18
|
+
"content_template": "# Release And Adoption — {{project_name}}\n\n## Consumer Smoke Proof\n- Install command:\n- Import / usage smoke check:\n- Example consumer environment:\n\n## Release Notes Inputs\n- User-visible changes:\n- Upgrade steps:\n- Rollback or pinning advice:\n\n## Adoption Risks\n- Migration blockers:\n- Documentation gaps:\n- Support expectations after release:\n"
|
|
19
|
+
}
|
|
20
|
+
],
|
|
21
|
+
"prompt_overrides": {
|
|
22
|
+
"pm": "Define the exported surface, compatibility promise, and consumer upgrade expectations before the team treats the package as ready to ship.",
|
|
23
|
+
"dev": "Treat exported entrypoints, backward compatibility, and package-consumer install or import paths as product behavior, not release polish.",
|
|
24
|
+
"qa": "Verify public API stability, install/import smoke, migration notes, and compatibility risks across supported runtimes before sign-off."
|
|
25
|
+
},
|
|
26
|
+
"acceptance_hints": [
|
|
27
|
+
"Public API surface reviewed and intentionally versioned",
|
|
28
|
+
"Compatibility or migration expectations documented for consumers",
|
|
29
|
+
"Install/import or package-consumer smoke path verified"
|
|
30
|
+
]
|
|
31
|
+
}
|