agentxchain 2.2.0 → 2.3.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 +16 -0
- package/bin/agentxchain.js +89 -0
- package/package.json +7 -2
- package/scripts/publish-from-tag.sh +14 -9
- package/scripts/release-postflight.sh +42 -2
- 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/verify.js +8 -3
- package/src/lib/adapters/api-proxy-adapter.js +125 -27
- package/src/lib/intake.js +924 -0
- package/src/lib/normalized-config.js +10 -0
- package/src/lib/protocol-conformance.js +28 -4
- package/src/lib/repo-observer.js +1 -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
|
package/bin/agentxchain.js
CHANGED
|
@@ -86,6 +86,14 @@ import {
|
|
|
86
86
|
multiApproveGateCommand,
|
|
87
87
|
multiResyncCommand,
|
|
88
88
|
} from '../src/commands/multi.js';
|
|
89
|
+
import { intakeRecordCommand } from '../src/commands/intake-record.js';
|
|
90
|
+
import { intakeTriageCommand } from '../src/commands/intake-triage.js';
|
|
91
|
+
import { intakeApproveCommand } from '../src/commands/intake-approve.js';
|
|
92
|
+
import { intakePlanCommand } from '../src/commands/intake-plan.js';
|
|
93
|
+
import { intakeStartCommand } from '../src/commands/intake-start.js';
|
|
94
|
+
import { intakeScanCommand } from '../src/commands/intake-scan.js';
|
|
95
|
+
import { intakeResolveCommand } from '../src/commands/intake-resolve.js';
|
|
96
|
+
import { intakeStatusCommand } from '../src/commands/intake-status.js';
|
|
89
97
|
|
|
90
98
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
91
99
|
const pkg = JSON.parse(readFileSync(join(__dirname, '..', 'package.json'), 'utf8'));
|
|
@@ -363,4 +371,85 @@ multiCmd
|
|
|
363
371
|
.option('--dry-run', 'Detect divergence without resyncing')
|
|
364
372
|
.action(multiResyncCommand);
|
|
365
373
|
|
|
374
|
+
// --- Intake (v3) -----------------------------------------------------------
|
|
375
|
+
|
|
376
|
+
const intakeCmd = program
|
|
377
|
+
.command('intake')
|
|
378
|
+
.description('Continuous governed delivery intake — record signals, triage intents, view status');
|
|
379
|
+
|
|
380
|
+
intakeCmd
|
|
381
|
+
.command('record')
|
|
382
|
+
.description('Record a delivery trigger event and create a detected intent')
|
|
383
|
+
.option('--file <path>', 'Read event payload from a JSON file')
|
|
384
|
+
.option('--stdin', 'Read event payload from stdin')
|
|
385
|
+
.option('--source <source>', 'Inline event source (manual, ci_failure, git_ref_change, schedule)')
|
|
386
|
+
.option('--signal <json>', 'Inline signal object (JSON string, requires --source)')
|
|
387
|
+
.option('--evidence <json>', 'Inline evidence entry (JSON string, requires --source)')
|
|
388
|
+
.option('--category <category>', 'Optional event category override')
|
|
389
|
+
.option('-j, --json', 'Output as JSON')
|
|
390
|
+
.action(intakeRecordCommand);
|
|
391
|
+
|
|
392
|
+
intakeCmd
|
|
393
|
+
.command('triage')
|
|
394
|
+
.description('Triage a detected intent — set priority, template, charter, and acceptance')
|
|
395
|
+
.requiredOption('--intent <id>', 'Intent ID to triage')
|
|
396
|
+
.option('--priority <level>', 'Priority level (p0, p1, p2, p3)')
|
|
397
|
+
.option('--template <id>', 'Governed template (generic, api-service, cli-tool, web-app)')
|
|
398
|
+
.option('--charter <text>', 'Delivery charter text')
|
|
399
|
+
.option('--acceptance <text>', 'Comma-separated acceptance criteria')
|
|
400
|
+
.option('--suppress', 'Suppress the intent instead of triaging')
|
|
401
|
+
.option('--reject', 'Reject a triaged intent')
|
|
402
|
+
.option('--reason <text>', 'Reason for suppress or reject')
|
|
403
|
+
.option('-j, --json', 'Output as JSON')
|
|
404
|
+
.action(intakeTriageCommand);
|
|
405
|
+
|
|
406
|
+
intakeCmd
|
|
407
|
+
.command('approve')
|
|
408
|
+
.description('Approve a triaged intent for planning')
|
|
409
|
+
.option('--intent <id>', 'Intent ID to approve')
|
|
410
|
+
.option('--approver <name>', 'Name of the approving authority', 'operator')
|
|
411
|
+
.option('--reason <text>', 'Reason for approval')
|
|
412
|
+
.option('-j, --json', 'Output as JSON')
|
|
413
|
+
.action(intakeApproveCommand);
|
|
414
|
+
|
|
415
|
+
intakeCmd
|
|
416
|
+
.command('plan')
|
|
417
|
+
.description('Generate planning artifacts and transition an approved intent to planned')
|
|
418
|
+
.option('--intent <id>', 'Intent ID to plan')
|
|
419
|
+
.option('--project-name <name>', 'Project name for template substitution')
|
|
420
|
+
.option('--force', 'Overwrite existing planning artifacts')
|
|
421
|
+
.option('-j, --json', 'Output as JSON')
|
|
422
|
+
.action(intakePlanCommand);
|
|
423
|
+
|
|
424
|
+
intakeCmd
|
|
425
|
+
.command('start')
|
|
426
|
+
.description('Start governed execution for a planned intent')
|
|
427
|
+
.option('--intent <id>', 'Intent ID to start')
|
|
428
|
+
.option('--role <role>', 'Override the default entry role for the governed phase')
|
|
429
|
+
.option('-j, --json', 'Output as JSON')
|
|
430
|
+
.action(intakeStartCommand);
|
|
431
|
+
|
|
432
|
+
intakeCmd
|
|
433
|
+
.command('scan')
|
|
434
|
+
.description('Scan a structured source snapshot into intake events')
|
|
435
|
+
.requiredOption('--source <id>', 'Source type: ci_failure, git_ref_change, schedule')
|
|
436
|
+
.option('--file <path>', 'Path to snapshot JSON file')
|
|
437
|
+
.option('--stdin', 'Read snapshot from stdin')
|
|
438
|
+
.option('-j, --json', 'Output as JSON')
|
|
439
|
+
.action(intakeScanCommand);
|
|
440
|
+
|
|
441
|
+
intakeCmd
|
|
442
|
+
.command('resolve')
|
|
443
|
+
.description('Resolve an executing intent by reading the governed run outcome')
|
|
444
|
+
.option('--intent <id>', 'Intent ID to resolve')
|
|
445
|
+
.option('-j, --json', 'Output as JSON')
|
|
446
|
+
.action(intakeResolveCommand);
|
|
447
|
+
|
|
448
|
+
intakeCmd
|
|
449
|
+
.command('status')
|
|
450
|
+
.description('Show current intake state — events, intents, and aggregate counts')
|
|
451
|
+
.option('--intent <id>', 'Show detail for a specific intent')
|
|
452
|
+
.option('-j, --json', 'Output as JSON')
|
|
453
|
+
.action(intakeStatusCommand);
|
|
454
|
+
|
|
366
455
|
program.parse();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agentxchain",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.3.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",
|
|
@@ -53,5 +55,8 @@
|
|
|
53
55
|
},
|
|
54
56
|
"engines": {
|
|
55
57
|
"node": ">=18.17.0 || >=20.5.0"
|
|
58
|
+
},
|
|
59
|
+
"devDependencies": {
|
|
60
|
+
"vitest": "^4.1.2"
|
|
56
61
|
}
|
|
57
62
|
}
|
|
@@ -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}"
|
|
@@ -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
|
+
}
|
|
@@ -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
|
+
}
|