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
package/README.md
CHANGED
|
@@ -26,6 +26,22 @@ Or run without installing:
|
|
|
26
26
|
npx agentxchain init --governed -y
|
|
27
27
|
```
|
|
28
28
|
|
|
29
|
+
## Testing
|
|
30
|
+
|
|
31
|
+
The CLI currently uses two runners on purpose: a 36-file Vitest coexistence slice for fast feedback and `node --test` for the full suite.
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
npm run test:vitest
|
|
35
|
+
npm run test:node
|
|
36
|
+
npm test
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
- `npm run test:vitest`: the current 36-file Vitest slice
|
|
40
|
+
- `npm run test:node`: full integration, subprocess, and E2E suite
|
|
41
|
+
- `npm test`: both runners in sequence; this is the CI requirement today
|
|
42
|
+
|
|
43
|
+
Duplicate execution remains intentional for the current 36-file slice until a later slice explicitly changes the redundancy model. For watch mode, run `npx vitest`.
|
|
44
|
+
|
|
29
45
|
## Quick Start
|
|
30
46
|
|
|
31
47
|
### Governed workflow
|
|
@@ -51,6 +67,7 @@ Built-in governed templates:
|
|
|
51
67
|
- `generic`: baseline governed scaffold
|
|
52
68
|
- `api-service`: API contract, operational readiness, error budget
|
|
53
69
|
- `cli-tool`: command surface, platform support, distribution checklist
|
|
70
|
+
- `library`: public API, compatibility policy, release and adoption checklist
|
|
54
71
|
- `web-app`: user flows, UI acceptance, browser support
|
|
55
72
|
|
|
56
73
|
`step` writes a turn-scoped bundle under `.agentxchain/dispatch/turns/<turn_id>/` and expects a staged result at `.agentxchain/staging/<turn_id>/turn-result.json`. Typical continuation:
|
package/bin/agentxchain.js
CHANGED
|
@@ -79,6 +79,7 @@ import {
|
|
|
79
79
|
} from '../src/commands/plugin.js';
|
|
80
80
|
import { templateSetCommand } from '../src/commands/template-set.js';
|
|
81
81
|
import { templateListCommand } from '../src/commands/template-list.js';
|
|
82
|
+
import { templateValidateCommand } from '../src/commands/template-validate.js';
|
|
82
83
|
import {
|
|
83
84
|
multiInitCommand,
|
|
84
85
|
multiStatusCommand,
|
|
@@ -86,6 +87,14 @@ import {
|
|
|
86
87
|
multiApproveGateCommand,
|
|
87
88
|
multiResyncCommand,
|
|
88
89
|
} from '../src/commands/multi.js';
|
|
90
|
+
import { intakeRecordCommand } from '../src/commands/intake-record.js';
|
|
91
|
+
import { intakeTriageCommand } from '../src/commands/intake-triage.js';
|
|
92
|
+
import { intakeApproveCommand } from '../src/commands/intake-approve.js';
|
|
93
|
+
import { intakePlanCommand } from '../src/commands/intake-plan.js';
|
|
94
|
+
import { intakeStartCommand } from '../src/commands/intake-start.js';
|
|
95
|
+
import { intakeScanCommand } from '../src/commands/intake-scan.js';
|
|
96
|
+
import { intakeResolveCommand } from '../src/commands/intake-resolve.js';
|
|
97
|
+
import { intakeStatusCommand } from '../src/commands/intake-status.js';
|
|
89
98
|
|
|
90
99
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
91
100
|
const pkg = JSON.parse(readFileSync(join(__dirname, '..', 'package.json'), 'utf8'));
|
|
@@ -102,7 +111,7 @@ program
|
|
|
102
111
|
.description('Create a new AgentXchain project folder')
|
|
103
112
|
.option('-y, --yes', 'Skip prompts, use defaults')
|
|
104
113
|
.option('--governed', 'Create a governed project (orchestrator-owned state)')
|
|
105
|
-
.option('--template <id>', 'Governed scaffold template: generic, api-service, cli-tool, web-app')
|
|
114
|
+
.option('--template <id>', 'Governed scaffold template: generic, api-service, cli-tool, library, web-app')
|
|
106
115
|
.option('--schema-version <version>', 'Schema version (3 for legacy, or use --governed for current)')
|
|
107
116
|
.action(initCommand);
|
|
108
117
|
|
|
@@ -328,6 +337,12 @@ templateCmd
|
|
|
328
337
|
.option('-j, --json', 'Output as JSON')
|
|
329
338
|
.action(templateListCommand);
|
|
330
339
|
|
|
340
|
+
templateCmd
|
|
341
|
+
.command('validate')
|
|
342
|
+
.description('Validate the built-in governed template registry and current project template binding')
|
|
343
|
+
.option('-j, --json', 'Output as JSON')
|
|
344
|
+
.action(templateValidateCommand);
|
|
345
|
+
|
|
331
346
|
const multiCmd = program
|
|
332
347
|
.command('multi')
|
|
333
348
|
.description('Multi-repo coordinator orchestration');
|
|
@@ -363,4 +378,85 @@ multiCmd
|
|
|
363
378
|
.option('--dry-run', 'Detect divergence without resyncing')
|
|
364
379
|
.action(multiResyncCommand);
|
|
365
380
|
|
|
381
|
+
// --- Intake (v3) -----------------------------------------------------------
|
|
382
|
+
|
|
383
|
+
const intakeCmd = program
|
|
384
|
+
.command('intake')
|
|
385
|
+
.description('Continuous governed delivery intake — record signals, triage intents, view status');
|
|
386
|
+
|
|
387
|
+
intakeCmd
|
|
388
|
+
.command('record')
|
|
389
|
+
.description('Record a delivery trigger event and create a detected intent')
|
|
390
|
+
.option('--file <path>', 'Read event payload from a JSON file')
|
|
391
|
+
.option('--stdin', 'Read event payload from stdin')
|
|
392
|
+
.option('--source <source>', 'Inline event source (manual, ci_failure, git_ref_change, schedule)')
|
|
393
|
+
.option('--signal <json>', 'Inline signal object (JSON string, requires --source)')
|
|
394
|
+
.option('--evidence <json>', 'Inline evidence entry (JSON string, requires --source)')
|
|
395
|
+
.option('--category <category>', 'Optional event category override')
|
|
396
|
+
.option('-j, --json', 'Output as JSON')
|
|
397
|
+
.action(intakeRecordCommand);
|
|
398
|
+
|
|
399
|
+
intakeCmd
|
|
400
|
+
.command('triage')
|
|
401
|
+
.description('Triage a detected intent — set priority, template, charter, and acceptance')
|
|
402
|
+
.requiredOption('--intent <id>', 'Intent ID to triage')
|
|
403
|
+
.option('--priority <level>', 'Priority level (p0, p1, p2, p3)')
|
|
404
|
+
.option('--template <id>', 'Governed template (generic, api-service, cli-tool, library, web-app)')
|
|
405
|
+
.option('--charter <text>', 'Delivery charter text')
|
|
406
|
+
.option('--acceptance <text>', 'Comma-separated acceptance criteria')
|
|
407
|
+
.option('--suppress', 'Suppress the intent instead of triaging')
|
|
408
|
+
.option('--reject', 'Reject a triaged intent')
|
|
409
|
+
.option('--reason <text>', 'Reason for suppress or reject')
|
|
410
|
+
.option('-j, --json', 'Output as JSON')
|
|
411
|
+
.action(intakeTriageCommand);
|
|
412
|
+
|
|
413
|
+
intakeCmd
|
|
414
|
+
.command('approve')
|
|
415
|
+
.description('Approve a triaged intent for planning')
|
|
416
|
+
.option('--intent <id>', 'Intent ID to approve')
|
|
417
|
+
.option('--approver <name>', 'Name of the approving authority', 'operator')
|
|
418
|
+
.option('--reason <text>', 'Reason for approval')
|
|
419
|
+
.option('-j, --json', 'Output as JSON')
|
|
420
|
+
.action(intakeApproveCommand);
|
|
421
|
+
|
|
422
|
+
intakeCmd
|
|
423
|
+
.command('plan')
|
|
424
|
+
.description('Generate planning artifacts and transition an approved intent to planned')
|
|
425
|
+
.option('--intent <id>', 'Intent ID to plan')
|
|
426
|
+
.option('--project-name <name>', 'Project name for template substitution')
|
|
427
|
+
.option('--force', 'Overwrite existing planning artifacts')
|
|
428
|
+
.option('-j, --json', 'Output as JSON')
|
|
429
|
+
.action(intakePlanCommand);
|
|
430
|
+
|
|
431
|
+
intakeCmd
|
|
432
|
+
.command('start')
|
|
433
|
+
.description('Start governed execution for a planned intent')
|
|
434
|
+
.option('--intent <id>', 'Intent ID to start')
|
|
435
|
+
.option('--role <role>', 'Override the default entry role for the governed phase')
|
|
436
|
+
.option('-j, --json', 'Output as JSON')
|
|
437
|
+
.action(intakeStartCommand);
|
|
438
|
+
|
|
439
|
+
intakeCmd
|
|
440
|
+
.command('scan')
|
|
441
|
+
.description('Scan a structured source snapshot into intake events')
|
|
442
|
+
.requiredOption('--source <id>', 'Source type: ci_failure, git_ref_change, schedule')
|
|
443
|
+
.option('--file <path>', 'Path to snapshot JSON file')
|
|
444
|
+
.option('--stdin', 'Read snapshot from stdin')
|
|
445
|
+
.option('-j, --json', 'Output as JSON')
|
|
446
|
+
.action(intakeScanCommand);
|
|
447
|
+
|
|
448
|
+
intakeCmd
|
|
449
|
+
.command('resolve')
|
|
450
|
+
.description('Resolve an executing intent by reading the governed run outcome')
|
|
451
|
+
.option('--intent <id>', 'Intent ID to resolve')
|
|
452
|
+
.option('-j, --json', 'Output as JSON')
|
|
453
|
+
.action(intakeResolveCommand);
|
|
454
|
+
|
|
455
|
+
intakeCmd
|
|
456
|
+
.command('status')
|
|
457
|
+
.description('Show current intake state — events, intents, and aggregate counts')
|
|
458
|
+
.option('--intent <id>', 'Show detail for a specific intent')
|
|
459
|
+
.option('-j, --json', 'Output as JSON')
|
|
460
|
+
.action(intakeStatusCommand);
|
|
461
|
+
|
|
366
462
|
program.parse();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agentxchain",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.4.0",
|
|
4
4
|
"description": "CLI for AgentXchain — governed multi-agent software delivery",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -15,7 +15,9 @@
|
|
|
15
15
|
],
|
|
16
16
|
"scripts": {
|
|
17
17
|
"dev": "node bin/agentxchain.js",
|
|
18
|
-
"test": "
|
|
18
|
+
"test": "npm run test:vitest && npm run test:node",
|
|
19
|
+
"test:vitest": "vitest run --reporter=verbose",
|
|
20
|
+
"test:node": "node --test test/*.test.js",
|
|
19
21
|
"preflight:release": "bash scripts/release-preflight.sh",
|
|
20
22
|
"preflight:release:strict": "bash scripts/release-preflight.sh --strict",
|
|
21
23
|
"build:macos": "bun build bin/agentxchain.js --compile --target=bun-darwin-arm64 --outfile=dist/agentxchain-macos-arm64",
|
|
@@ -46,12 +48,17 @@
|
|
|
46
48
|
"homepage": "https://agentxchain.dev",
|
|
47
49
|
"dependencies": {
|
|
48
50
|
"@anthropic-ai/tokenizer": "0.0.4",
|
|
51
|
+
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
49
52
|
"chalk": "^5.4.0",
|
|
50
53
|
"commander": "^13.0.0",
|
|
51
54
|
"inquirer": "^12.0.0",
|
|
52
|
-
"ora": "^8.0.0"
|
|
55
|
+
"ora": "^8.0.0",
|
|
56
|
+
"zod": "^4.3.6"
|
|
53
57
|
},
|
|
54
58
|
"engines": {
|
|
55
59
|
"node": ">=18.17.0 || >=20.5.0"
|
|
60
|
+
},
|
|
61
|
+
"devDependencies": {
|
|
62
|
+
"vitest": "^4.1.2"
|
|
56
63
|
}
|
|
57
64
|
}
|
|
@@ -58,16 +58,21 @@ echo "Publishing ${PACKAGE_NAME}@${RELEASE_VERSION} from ${TAG}"
|
|
|
58
58
|
echo "Running strict release preflight..."
|
|
59
59
|
bash scripts/release-preflight.sh --strict --target-version "${RELEASE_VERSION}"
|
|
60
60
|
|
|
61
|
-
|
|
62
|
-
if [[
|
|
63
|
-
echo "
|
|
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
|
|
61
|
+
EXISTING_VERSION="$(npm view "${PACKAGE_NAME}@${RELEASE_VERSION}" version 2>/dev/null || true)"
|
|
62
|
+
if [[ "$EXISTING_VERSION" == "$RELEASE_VERSION" ]]; then
|
|
63
|
+
echo "Registry already serves ${PACKAGE_NAME}@${RELEASE_VERSION}; skipping npm publish and proceeding to verification."
|
|
68
64
|
else
|
|
69
|
-
echo "
|
|
70
|
-
|
|
65
|
+
echo "Running npm publish..."
|
|
66
|
+
if [[ -n "${NPM_TOKEN:-}" ]]; then
|
|
67
|
+
echo "Publish auth mode: token"
|
|
68
|
+
TMP_NPMRC="$(mktemp "${TMPDIR:-/tmp}/agentxchain-npmrc.XXXXXX")"
|
|
69
|
+
chmod 600 "$TMP_NPMRC"
|
|
70
|
+
printf '%s\n' "//registry.npmjs.org/:_authToken=${NPM_TOKEN}" > "$TMP_NPMRC"
|
|
71
|
+
NPM_CONFIG_USERCONFIG="$TMP_NPMRC" npm publish --access public
|
|
72
|
+
else
|
|
73
|
+
echo "Publish auth mode: trusted publishing (OIDC)"
|
|
74
|
+
npm publish --access public
|
|
75
|
+
fi
|
|
71
76
|
fi
|
|
72
77
|
|
|
73
78
|
echo "Verifying registry visibility..."
|
|
@@ -11,7 +11,7 @@ cd "$CLI_DIR"
|
|
|
11
11
|
|
|
12
12
|
TARGET_VERSION=""
|
|
13
13
|
TAG=""
|
|
14
|
-
RETRY_ATTEMPTS="${RELEASE_POSTFLIGHT_RETRY_ATTEMPTS:-
|
|
14
|
+
RETRY_ATTEMPTS="${RELEASE_POSTFLIGHT_RETRY_ATTEMPTS:-12}"
|
|
15
15
|
RETRY_DELAY_SECONDS="${RELEASE_POSTFLIGHT_RETRY_DELAY_SECONDS:-10}"
|
|
16
16
|
|
|
17
17
|
usage() {
|
|
@@ -75,6 +75,7 @@ FAIL=0
|
|
|
75
75
|
TARBALL_URL=""
|
|
76
76
|
REGISTRY_CHECKSUM=""
|
|
77
77
|
PACKAGE_NAME="$(node -e "console.log(JSON.parse(require('fs').readFileSync('package.json', 'utf8')).name)")"
|
|
78
|
+
PACKAGE_BIN_NAME="$(node -e "const pkg = JSON.parse(require('fs').readFileSync('package.json', 'utf8')); if (typeof pkg.bin === 'string') { console.log(pkg.name); process.exit(0); } const names = Object.keys(pkg.bin || {}); if (names.length !== 1) { console.error('package.json bin must declare exactly one entry'); process.exit(1); } console.log(names[0]);")"
|
|
78
79
|
|
|
79
80
|
pass() { PASS=$((PASS + 1)); echo " PASS: $1"; }
|
|
80
81
|
fail() { FAIL=$((FAIL + 1)); echo " FAIL: $1"; }
|
|
@@ -96,6 +97,45 @@ trim_last_line() {
|
|
|
96
97
|
printf '%s\n' "$1" | awk 'NF { line=$0 } END { gsub(/^[[:space:]]+|[[:space:]]+$/, "", line); print line }'
|
|
97
98
|
}
|
|
98
99
|
|
|
100
|
+
run_install_smoke() {
|
|
101
|
+
if [[ -z "$TARBALL_URL" ]]; then
|
|
102
|
+
echo "registry tarball metadata unavailable for install smoke" >&2
|
|
103
|
+
return 1
|
|
104
|
+
fi
|
|
105
|
+
|
|
106
|
+
local smoke_root
|
|
107
|
+
local bin_path
|
|
108
|
+
local install_status
|
|
109
|
+
local version_status
|
|
110
|
+
|
|
111
|
+
smoke_root="$(mktemp -d "${TMPDIR:-/tmp}/agentxchain-postflight.XXXXXX")"
|
|
112
|
+
bin_path="${smoke_root}/bin/${PACKAGE_BIN_NAME}"
|
|
113
|
+
|
|
114
|
+
# Isolate the install from CI auth environment (OIDC tokens from actions/setup-node
|
|
115
|
+
# are scoped for publish, not read, and can cause npm install to fail on public packages).
|
|
116
|
+
local smoke_npmrc="${smoke_root}/.npmrc"
|
|
117
|
+
echo "registry=https://registry.npmjs.org/" > "$smoke_npmrc"
|
|
118
|
+
|
|
119
|
+
env -u NODE_AUTH_TOKEN NPM_CONFIG_USERCONFIG="$smoke_npmrc" \
|
|
120
|
+
npm install --global --prefix "$smoke_root" "$TARBALL_URL" >/dev/null 2>&1
|
|
121
|
+
install_status=$?
|
|
122
|
+
if [[ "$install_status" -ne 0 ]]; then
|
|
123
|
+
rm -rf "$smoke_root"
|
|
124
|
+
return "$install_status"
|
|
125
|
+
fi
|
|
126
|
+
|
|
127
|
+
if [[ ! -x "$bin_path" ]]; then
|
|
128
|
+
echo "installed binary missing at ${bin_path}" >&2
|
|
129
|
+
rm -rf "$smoke_root"
|
|
130
|
+
return 1
|
|
131
|
+
fi
|
|
132
|
+
|
|
133
|
+
"$bin_path" --version
|
|
134
|
+
version_status=$?
|
|
135
|
+
rm -rf "$smoke_root"
|
|
136
|
+
return "$version_status"
|
|
137
|
+
}
|
|
138
|
+
|
|
99
139
|
run_with_retry() {
|
|
100
140
|
local __output_var="$1"
|
|
101
141
|
local description="$2"
|
|
@@ -201,7 +241,7 @@ else
|
|
|
201
241
|
fi
|
|
202
242
|
|
|
203
243
|
echo "[5/5] Install smoke"
|
|
204
|
-
if run_with_retry EXEC_OUTPUT "install smoke" nonempty ""
|
|
244
|
+
if run_with_retry EXEC_OUTPUT "install smoke" nonempty "" run_install_smoke; then
|
|
205
245
|
EXEC_VERSION="$(trim_last_line "$EXEC_OUTPUT")"
|
|
206
246
|
if [[ "$EXEC_VERSION" == "$TARGET_VERSION" ]]; then
|
|
207
247
|
pass "published CLI executes and reports ${TARGET_VERSION}"
|
package/src/commands/init.js
CHANGED
|
@@ -483,6 +483,7 @@ async function initGoverned(opts) {
|
|
|
483
483
|
console.error(' generic Default governed scaffold');
|
|
484
484
|
console.error(' api-service Governed scaffold for a backend service');
|
|
485
485
|
console.error(' cli-tool Governed scaffold for a CLI tool');
|
|
486
|
+
console.error(' library Governed scaffold for a reusable package');
|
|
486
487
|
console.error(' web-app Governed scaffold for a web application');
|
|
487
488
|
process.exit(1);
|
|
488
489
|
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { findProjectRoot } from '../lib/config.js';
|
|
3
|
+
import { approveIntent } from '../lib/intake.js';
|
|
4
|
+
|
|
5
|
+
export async function intakeApproveCommand(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 = approveIntent(root, opts.intent, {
|
|
27
|
+
approver: opts.approver || undefined,
|
|
28
|
+
reason: opts.reason || undefined,
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
if (opts.json) {
|
|
32
|
+
console.log(JSON.stringify(result, null, 2));
|
|
33
|
+
} else if (result.ok) {
|
|
34
|
+
console.log('');
|
|
35
|
+
console.log(chalk.green(` Approved intent ${result.intent.intent_id}`));
|
|
36
|
+
console.log(chalk.dim(` Approver: ${result.intent.approved_by}`));
|
|
37
|
+
console.log(chalk.dim(` Status: triaged → approved`));
|
|
38
|
+
console.log('');
|
|
39
|
+
} else {
|
|
40
|
+
console.log(chalk.red(` ${result.error}`));
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
process.exit(result.exitCode);
|
|
44
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { findProjectRoot } from '../lib/config.js';
|
|
3
|
+
import { planIntent } from '../lib/intake.js';
|
|
4
|
+
import { basename } from 'node:path';
|
|
5
|
+
|
|
6
|
+
export async function intakePlanCommand(opts) {
|
|
7
|
+
const root = findProjectRoot(process.cwd());
|
|
8
|
+
if (!root) {
|
|
9
|
+
if (opts.json) {
|
|
10
|
+
console.log(JSON.stringify({ ok: false, error: 'agentxchain.json not found' }, null, 2));
|
|
11
|
+
} else {
|
|
12
|
+
console.log(chalk.red('agentxchain.json not found'));
|
|
13
|
+
}
|
|
14
|
+
process.exit(2);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
if (!opts.intent) {
|
|
18
|
+
const msg = '--intent <id> is required';
|
|
19
|
+
if (opts.json) {
|
|
20
|
+
console.log(JSON.stringify({ ok: false, error: msg }, null, 2));
|
|
21
|
+
} else {
|
|
22
|
+
console.log(chalk.red(msg));
|
|
23
|
+
}
|
|
24
|
+
process.exit(1);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const result = planIntent(root, opts.intent, {
|
|
28
|
+
projectName: opts.projectName || undefined,
|
|
29
|
+
force: opts.force || false,
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
if (opts.json) {
|
|
33
|
+
console.log(JSON.stringify(result, null, 2));
|
|
34
|
+
} else if (result.ok) {
|
|
35
|
+
console.log('');
|
|
36
|
+
console.log(chalk.green(` Planned intent ${result.intent.intent_id}`));
|
|
37
|
+
console.log(chalk.dim(` Template: ${result.intent.template}`));
|
|
38
|
+
if (result.artifacts_generated.length > 0) {
|
|
39
|
+
console.log(chalk.dim(' Generated planning artifacts:'));
|
|
40
|
+
for (const a of result.artifacts_generated) {
|
|
41
|
+
console.log(chalk.dim(` ${a}`));
|
|
42
|
+
}
|
|
43
|
+
} else {
|
|
44
|
+
console.log(chalk.dim(' No template-specific planning artifacts to generate.'));
|
|
45
|
+
}
|
|
46
|
+
console.log(chalk.dim(` Status: approved → planned`));
|
|
47
|
+
console.log('');
|
|
48
|
+
} else if (result.conflicts) {
|
|
49
|
+
console.log('');
|
|
50
|
+
console.log(chalk.red(` Cannot plan intent ${opts.intent}`));
|
|
51
|
+
console.log(chalk.red(' Existing planning artifacts would be overwritten:'));
|
|
52
|
+
for (const c of result.conflicts) {
|
|
53
|
+
console.log(chalk.red(` ${c}`));
|
|
54
|
+
}
|
|
55
|
+
console.log(chalk.yellow(' Use --force to overwrite, or remove the existing files first.'));
|
|
56
|
+
console.log('');
|
|
57
|
+
} else {
|
|
58
|
+
console.log(chalk.red(` ${result.error}`));
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
process.exit(result.exitCode);
|
|
62
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { readFileSync } from 'node:fs';
|
|
3
|
+
import { resolve } from 'node:path';
|
|
4
|
+
import { findProjectRoot } from '../lib/config.js';
|
|
5
|
+
import { recordEvent } from '../lib/intake.js';
|
|
6
|
+
|
|
7
|
+
export async function intakeRecordCommand(opts) {
|
|
8
|
+
const root = findProjectRoot(process.cwd());
|
|
9
|
+
if (!root) {
|
|
10
|
+
if (opts.json) {
|
|
11
|
+
console.log(JSON.stringify({ ok: false, error: 'agentxchain.json not found' }, null, 2));
|
|
12
|
+
} else {
|
|
13
|
+
console.log(chalk.red('agentxchain.json not found'));
|
|
14
|
+
}
|
|
15
|
+
process.exit(2);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
let payload;
|
|
19
|
+
try {
|
|
20
|
+
payload = parsePayload(opts);
|
|
21
|
+
} catch (err) {
|
|
22
|
+
if (opts.json) {
|
|
23
|
+
console.log(JSON.stringify({ ok: false, error: err.message }, null, 2));
|
|
24
|
+
} else {
|
|
25
|
+
console.log(chalk.red(err.message));
|
|
26
|
+
}
|
|
27
|
+
process.exit(1);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const result = recordEvent(root, payload);
|
|
31
|
+
|
|
32
|
+
if (opts.json) {
|
|
33
|
+
console.log(JSON.stringify(result, null, 2));
|
|
34
|
+
} else if (result.ok) {
|
|
35
|
+
if (result.deduplicated) {
|
|
36
|
+
console.log('');
|
|
37
|
+
console.log(chalk.yellow(` Event already recorded: ${result.event.event_id} (deduplicated)`));
|
|
38
|
+
if (result.intent) {
|
|
39
|
+
console.log(chalk.dim(` Linked intent: ${result.intent.intent_id} (${result.intent.status})`));
|
|
40
|
+
}
|
|
41
|
+
} else {
|
|
42
|
+
console.log('');
|
|
43
|
+
console.log(chalk.green(` Recorded event ${result.event.event_id}`));
|
|
44
|
+
console.log(chalk.green(` Created intent ${result.intent.intent_id} (detected)`));
|
|
45
|
+
}
|
|
46
|
+
console.log('');
|
|
47
|
+
} else {
|
|
48
|
+
console.log(chalk.red(` ${result.error}`));
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
process.exit(result.exitCode);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function parsePayload(opts) {
|
|
55
|
+
if (opts.file) {
|
|
56
|
+
const raw = readFileSync(resolve(opts.file), 'utf8');
|
|
57
|
+
return JSON.parse(raw);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (opts.stdin) {
|
|
61
|
+
const raw = readFileSync(0, 'utf8');
|
|
62
|
+
return JSON.parse(raw);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (opts.source) {
|
|
66
|
+
if (!opts.signal) throw new Error('--source requires --signal');
|
|
67
|
+
if (!opts.evidence) throw new Error('--source requires --evidence');
|
|
68
|
+
|
|
69
|
+
const signal = JSON.parse(opts.signal);
|
|
70
|
+
let evidence;
|
|
71
|
+
if (Array.isArray(opts.evidence)) {
|
|
72
|
+
evidence = opts.evidence.map(e => JSON.parse(e));
|
|
73
|
+
} else {
|
|
74
|
+
evidence = [JSON.parse(opts.evidence)];
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return {
|
|
78
|
+
source: opts.source,
|
|
79
|
+
signal,
|
|
80
|
+
evidence,
|
|
81
|
+
category: opts.category || undefined,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
throw new Error('one of --file, --stdin, or --source is required');
|
|
86
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { findProjectRoot } from '../lib/config.js';
|
|
3
|
+
import { resolveIntent } from '../lib/intake.js';
|
|
4
|
+
|
|
5
|
+
export async function intakeResolveCommand(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 = resolveIntent(root, opts.intent);
|
|
27
|
+
|
|
28
|
+
if (opts.json) {
|
|
29
|
+
console.log(JSON.stringify(result, null, 2));
|
|
30
|
+
} else if (result.ok) {
|
|
31
|
+
console.log('');
|
|
32
|
+
if (result.no_change) {
|
|
33
|
+
console.log(chalk.yellow(` Intent ${opts.intent} — run still ${result.run_outcome}, no transition`));
|
|
34
|
+
} else {
|
|
35
|
+
console.log(chalk.green(` Resolved intent ${opts.intent}`));
|
|
36
|
+
console.log(chalk.dim(` ${result.previous_status} → ${result.new_status}`));
|
|
37
|
+
console.log(chalk.dim(` Run outcome: ${result.run_outcome}`));
|
|
38
|
+
}
|
|
39
|
+
console.log('');
|
|
40
|
+
} else {
|
|
41
|
+
console.log(chalk.red(` ${result.error}`));
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
process.exit(result.exitCode);
|
|
45
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { readFileSync } from 'node:fs';
|
|
3
|
+
import { resolve } from 'node:path';
|
|
4
|
+
import { findProjectRoot } from '../lib/config.js';
|
|
5
|
+
import { scanSource, SCAN_SOURCES } from '../lib/intake.js';
|
|
6
|
+
|
|
7
|
+
export async function intakeScanCommand(opts) {
|
|
8
|
+
const root = findProjectRoot(process.cwd());
|
|
9
|
+
if (!root) {
|
|
10
|
+
if (opts.json) {
|
|
11
|
+
console.log(JSON.stringify({ ok: false, error: 'agentxchain.json not found' }, null, 2));
|
|
12
|
+
} else {
|
|
13
|
+
console.log(chalk.red('agentxchain.json not found'));
|
|
14
|
+
}
|
|
15
|
+
process.exit(2);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
if (!opts.source) {
|
|
19
|
+
const msg = `--source is required. Supported scan sources: ${SCAN_SOURCES.join(', ')}`;
|
|
20
|
+
if (opts.json) {
|
|
21
|
+
console.log(JSON.stringify({ ok: false, error: msg }, null, 2));
|
|
22
|
+
} else {
|
|
23
|
+
console.log(chalk.red(msg));
|
|
24
|
+
}
|
|
25
|
+
process.exit(1);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (!opts.file && !opts.stdin) {
|
|
29
|
+
const msg = 'one of --file or --stdin is required';
|
|
30
|
+
if (opts.json) {
|
|
31
|
+
console.log(JSON.stringify({ ok: false, error: msg }, null, 2));
|
|
32
|
+
} else {
|
|
33
|
+
console.log(chalk.red(msg));
|
|
34
|
+
}
|
|
35
|
+
process.exit(1);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (opts.file && opts.stdin) {
|
|
39
|
+
const msg = '--file and --stdin are mutually exclusive';
|
|
40
|
+
if (opts.json) {
|
|
41
|
+
console.log(JSON.stringify({ ok: false, error: msg }, null, 2));
|
|
42
|
+
} else {
|
|
43
|
+
console.log(chalk.red(msg));
|
|
44
|
+
}
|
|
45
|
+
process.exit(1);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
let snapshot;
|
|
49
|
+
try {
|
|
50
|
+
let raw;
|
|
51
|
+
if (opts.file) {
|
|
52
|
+
raw = readFileSync(resolve(opts.file), 'utf8');
|
|
53
|
+
} else {
|
|
54
|
+
raw = readFileSync(0, 'utf8');
|
|
55
|
+
}
|
|
56
|
+
snapshot = JSON.parse(raw);
|
|
57
|
+
} catch (err) {
|
|
58
|
+
const msg = opts.file
|
|
59
|
+
? `failed to read snapshot from ${opts.file}: ${err.message}`
|
|
60
|
+
: `failed to read snapshot from stdin: ${err.message}`;
|
|
61
|
+
if (opts.json) {
|
|
62
|
+
console.log(JSON.stringify({ ok: false, error: msg }, null, 2));
|
|
63
|
+
} else {
|
|
64
|
+
console.log(chalk.red(msg));
|
|
65
|
+
}
|
|
66
|
+
process.exit(opts.file && err.code === 'ENOENT' ? 2 : 1);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const result = scanSource(root, opts.source, snapshot);
|
|
70
|
+
|
|
71
|
+
if (opts.json) {
|
|
72
|
+
console.log(JSON.stringify(result, null, 2));
|
|
73
|
+
} else if (result.ok) {
|
|
74
|
+
console.log('');
|
|
75
|
+
console.log(chalk.green(` Scan complete: ${result.scanned} item(s) processed`));
|
|
76
|
+
console.log(` Created: ${result.created}`);
|
|
77
|
+
console.log(` Deduplicated: ${result.deduplicated}`);
|
|
78
|
+
if (result.rejected > 0) {
|
|
79
|
+
console.log(chalk.yellow(` Rejected: ${result.rejected}`));
|
|
80
|
+
}
|
|
81
|
+
console.log('');
|
|
82
|
+
} else {
|
|
83
|
+
console.log(chalk.red(` ${result.error}`));
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
process.exit(result.exitCode);
|
|
87
|
+
}
|