@aiwg/cli 2026.10.0 → 2026.10.3
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/agentic/code/providers/capability-matrix.yaml +270 -0
- package/agentic/code/providers/model-capabilities.v1.json +3091 -24
- package/bin/aiwg.mjs +86 -21
- package/dist/src/a2a/codecs.js +9 -0
- package/dist/src/artifacts/corpus-tools/vision-extract.js +105 -15
- package/dist/src/artifacts/repair.js +9 -4
- package/dist/src/channel/manager.mjs +59 -10
- package/dist/src/cli/child-process-result.js +40 -0
- package/dist/src/cli/env.js +38 -16
- package/dist/src/cli/flag-parsing.js +236 -0
- package/dist/src/cli/handlers/decision.js +297 -2
- package/dist/src/cli/handlers/diagnose.js +9 -2
- package/dist/src/cli/handlers/feedback.js +14 -5
- package/dist/src/cli/handlers/init.js +245 -30
- package/dist/src/cli/handlers/mc.js +23 -0
- package/dist/src/cli/handlers/models.js +69 -6
- package/dist/src/cli/handlers/ralph.js +3 -0
- package/dist/src/cli/handlers/refresh.js +16 -0
- package/dist/src/cli/handlers/regenerate.js +14 -0
- package/dist/src/cli/handlers/runtime-info.js +24 -9
- package/dist/src/cli/handlers/script-runner.js +16 -6
- package/dist/src/cli/handlers/session.js +396 -62
- package/dist/src/cli/handlers/sessions.js +146 -40
- package/dist/src/cli/handlers/setup-manifest.js +439 -39
- package/dist/src/cli/handlers/setup.js +139 -39
- package/dist/src/cli/handlers/subcommands.js +3 -1
- package/dist/src/cli/handlers/team.js +18 -8
- package/dist/src/cli/handlers/use.js +13 -0
- package/dist/src/cli/handlers/utilities.js +106 -23
- package/dist/src/cli/prompt-utils.js +72 -0
- package/dist/src/cli/provider-resolution.js +11 -0
- package/dist/src/config/aiwg-config.js +268 -4
- package/dist/src/config/cli.js +146 -19
- package/dist/src/config/dispatch-thresholds-runtime.mjs +38 -0
- package/dist/src/config/project-artifacts-health.mjs +14 -3
- package/dist/src/config/user-config.js +36 -10
- package/dist/src/decision/adapters/jev.js +80 -28
- package/dist/src/decision/ask.js +352 -0
- package/dist/src/decision/driver.js +4 -0
- package/dist/src/decision/entry.js +38 -6
- package/dist/src/decision/heldout/collector.js +10 -1
- package/dist/src/decision/index.js +3 -0
- package/dist/src/decision/interop.js +60 -8
- package/dist/src/decision/jev-credentials.js +121 -0
- package/dist/src/decision/ledger.js +839 -0
- package/dist/src/decision/validate.js +6 -3
- package/dist/src/effects/dispatch-verifier.js +252 -0
- package/dist/src/effects/index.js +1 -0
- package/dist/src/features/installer.js +2 -2
- package/dist/src/flow/graph-metadata.js +5 -0
- package/dist/src/gates/providers/loader.js +3 -1
- package/dist/src/installation/manager-command.mjs +1 -1
- package/dist/src/installation/manager.mjs +17 -2
- package/dist/src/lint/runner.js +2 -2
- package/dist/src/mcp/adapters/codex-runtime.js +360 -56
- package/dist/src/mcp/cli.mjs +587 -77
- package/dist/src/mcp/config-file.mjs +279 -0
- package/dist/src/mcp/config-layers.mjs +348 -0
- package/dist/src/mcp/credentials.mjs +484 -0
- package/dist/src/mcp/grok-build-config.mjs +16 -26
- package/dist/src/mcp/helpers.mjs +106 -2
- package/dist/src/mcp/omp-config.mjs +50 -29
- package/dist/src/mcp/profiles.js +97 -23
- package/dist/src/mcp/profiles.mjs +99 -27
- package/dist/src/mcp/registry.js +264 -57
- package/dist/src/mcp/registry.mjs +198 -45
- package/dist/src/mcp/server.mjs +403 -20
- package/dist/src/mcp/toml-string.mjs +9 -0
- package/dist/src/mcp/toml-strip-mcp.mjs +135 -0
- package/dist/src/mcp/tool-filters.mjs +357 -0
- package/dist/src/mcp/tools/agentic-sandbox.mjs +3 -1
- package/dist/src/mcp/tools/decision.mjs +58 -0
- package/dist/src/mcp/tools/orchestration.mjs +38 -0
- package/dist/src/mcp/tools/subsystems.mjs +39 -8
- package/dist/src/mcp/tools/worker-invoke.mjs +1539 -0
- package/dist/src/mcp/tools/worker-shell-control.mjs +258 -0
- package/dist/src/mcp/tools/worker-stdin-runtime.mjs +79 -0
- package/dist/src/memory/project-registry.js +2 -2
- package/dist/src/mission-protocol/codecs.js +639 -1
- package/dist/src/mission-protocol/types.js +110 -0
- package/dist/src/mission-protocol/worker-status.js +2 -0
- package/dist/src/models/config-loader.js +70 -13
- package/dist/src/models/grok-build-models.js +93 -41
- package/dist/src/models/model-capabilities.v1.json +3091 -24
- package/dist/src/models/model-discovery.js +862 -63
- package/dist/src/models/provider-policy.js +625 -11
- package/dist/src/models/qualification-digest.mjs +17 -0
- package/dist/src/models/resolver.js +259 -0
- package/dist/src/models/worker-direct-readiness.js +529 -0
- package/dist/src/models/worker-dispatch-offer.js +169 -0
- package/dist/src/models/worker-health-readiness.js +131 -0
- package/dist/src/models/worker-installation.mjs +879 -0
- package/dist/src/models/wrapper-route.js +18 -0
- package/dist/src/ops/registry.js +9 -14
- package/dist/src/plugins/standalone-packager.js +4 -3
- package/dist/src/process/bounded-child-process.mjs +387 -0
- package/dist/src/process/cli-command.mjs +75 -0
- package/dist/src/providers/capability-matrix.js +8 -0
- package/dist/src/providers/capability-matrix.yaml +270 -0
- package/dist/src/providers/grok-build-headless.js +43 -21
- package/dist/src/providers/provider-definitions.js +9 -1
- package/dist/src/providers/provider-definitions.mjs +9 -2
- package/dist/src/serve/executor-registry.js +16 -24
- package/dist/src/serve/fleet-mission-conductor.js +9 -0
- package/dist/src/serve/mission-conductor.js +9 -0
- package/dist/src/serve/shared-host-scheduler.js +2 -0
- package/dist/src/serve/worker-invocation.js +538 -0
- package/dist/src/serve/worker-stdin-frame.mjs +107 -0
- package/dist/src/sessions/adapter-registry.js +45 -0
- package/dist/src/sessions/adapters/generic.js +37 -3
- package/dist/src/sessions/adapters/grokbot-locator.js +111 -0
- package/dist/src/sessions/adapters/grokbot.js +423 -24
- package/dist/src/sessions/adapters/muse-workspace-scan.js +108 -0
- package/dist/src/sessions/adapters/muse.js +68 -29
- package/dist/src/sessions/adapters/pi.js +1 -0
- package/dist/src/sessions/batch-contracts.js +13 -1
- package/dist/src/sessions/batch-import.js +23 -42
- package/dist/src/sessions/candidates.js +47 -11
- package/dist/src/sessions/contracts.js +35 -0
- package/dist/src/sessions/coverage-preflight.js +134 -0
- package/dist/src/sessions/discovery.js +3 -0
- package/dist/src/sessions/grok-build-cli.js +67 -5
- package/dist/src/sessions/importer.js +24 -8
- package/dist/src/sessions/index.js +2 -0
- package/dist/src/sessions/repository.js +54 -13
- package/dist/src/sessions/workspace-discovery.js +99 -37
- package/dist/src/skills/adapters/clawhub.js +10 -8
- package/dist/src/skills/adapters/openclaw.js +10 -7
- package/dist/src/smiths/context-pipeline/parallelism-section.js +192 -3
- package/dist/src/smiths/context-pipeline/workspace-context.js +116 -9
- package/dist/src/smiths/hook-bridge/codex-translator.js +6 -5
- package/dist/src/smiths/mcpsmith/example.js +1 -1
- package/dist/src/smiths/toolsmith/runtime-discovery.mjs +10 -14
- package/dist/src/storage/backends/logseq.js +68 -7
- package/dist/src/storage/fortemi-qualification-receipt.js +2 -2
- package/dist/src/testing/generators/test-code-generator.js +57 -30
- package/dist/src/uhp/client.js +11 -1
- package/dist/src/uhp/mission.js +5 -0
- package/dist/src/update/checker.mjs +5 -5
- package/dist/src/update/notifier.mjs +54 -2
- package/dist/src/writing/fidelity.js +18 -1
- package/package.json +18 -1
- package/schemas/mission-v1.schema.json +2429 -0
- package/schemas/models/model-catalog.v1.schema.json +301 -0
- package/schemas/models/model-config-compatibility.v1.schema.json +281 -0
- package/schemas/models/model-policy.v1.schema.json +14 -0
- package/schemas/models/provider-model-capabilities.v1.schema.json +1066 -0
- package/tools/_resolve-impl.mjs +39 -0
- package/tools/agents/deploy-agents.mjs +15 -2
- package/tools/agents/providers/base.mjs +63 -43
- package/tools/agents/providers/grokbot.mjs +11 -12
- package/tools/agents/providers/hermes.mjs +6 -3
- package/tools/agents/providers/omp.mjs +5 -2
- package/tools/agents/providers/openclaw-translator.mjs +19 -5
- package/tools/providers/deepseek-harness-transport.mjs +358 -0
- package/tools/providers/omp-transport.mjs +192 -0
- package/tools/ralph-external/lib/claude-adapter.mjs +389 -0
- package/tools/ralph-external/lib/codex-adapter.mjs +432 -0
- package/tools/ralph-external/lib/deepseek-harness-adapter.mjs +397 -0
- package/tools/ralph-external/lib/factory-adapter.mjs +503 -0
- package/tools/ralph-external/lib/muse-adapter.mjs +364 -0
- package/tools/ralph-external/lib/omp-adapter.mjs +315 -0
- package/tools/ralph-external/lib/opencode-adapter.mjs +575 -0
- package/tools/ralph-external/lib/openhuman-adapter.mjs +75 -0
- package/tools/ralph-external/lib/pi-adapter.mjs +333 -0
- package/tools/ralph-external/lib/provider-adapter.mjs +690 -0
- package/tools/ralph-external/lib/worker-health.mjs +439 -0
- package/vscode-extension/schemas/aiwg.config.v1.json +1953 -0
package/dist/src/cli/env.js
CHANGED
|
@@ -25,9 +25,24 @@
|
|
|
25
25
|
*
|
|
26
26
|
* Handlers MUST call this helper instead of re-deriving the check locally.
|
|
27
27
|
* Divergent isTTY checks scattered across handlers have caused bugs in the
|
|
28
|
-
* past (#918 audit findings
|
|
28
|
+
* past (#918 audit findings, and #3062 review item 12: `init` only checked
|
|
29
|
+
* `CI === 'true'`, `setup project` ignored CI/TERM entirely, and `session`
|
|
30
|
+
* ignored its own `--non-interactive` flag — each reinventing a narrower
|
|
31
|
+
* version of this same check instead of using it).
|
|
32
|
+
*
|
|
33
|
+
* @param explicitNonInteractive When `true`, short-circuits to `false`
|
|
34
|
+
* without inspecting the environment at all. Pass the caller's own
|
|
35
|
+
* explicit non-interactive flag here (e.g. `--non-interactive`, or
|
|
36
|
+
* `--yes` where it means "don't prompt") so the single interactive
|
|
37
|
+
* decision takes the command's explicit flags as input, rather than
|
|
38
|
+
* every call site re-deriving its own mix of flag-vs-environment
|
|
39
|
+
* precedence. Omit it (or pass `false`) to fall through to the
|
|
40
|
+
* environment-only checks below, unchanged from before this parameter
|
|
41
|
+
* existed.
|
|
29
42
|
*/
|
|
30
|
-
export function isInteractive() {
|
|
43
|
+
export function isInteractive(explicitNonInteractive = false) {
|
|
44
|
+
if (explicitNonInteractive)
|
|
45
|
+
return false;
|
|
31
46
|
if (!process.stdin.isTTY)
|
|
32
47
|
return false;
|
|
33
48
|
if (!process.stdout.isTTY)
|
|
@@ -40,25 +55,32 @@ export function isInteractive() {
|
|
|
40
55
|
return false;
|
|
41
56
|
return true;
|
|
42
57
|
}
|
|
58
|
+
/**
|
|
59
|
+
* Variables that mark a continuous-integration environment. Exported so tests
|
|
60
|
+
* that need the interactive path can clear exactly what `isCI()` reads.
|
|
61
|
+
*/
|
|
62
|
+
export const CI_ENV_VARS = [
|
|
63
|
+
'CI',
|
|
64
|
+
'CONTINUOUS_INTEGRATION',
|
|
65
|
+
'BUILD_NUMBER',
|
|
66
|
+
'RUN_ID',
|
|
67
|
+
'GITHUB_ACTIONS',
|
|
68
|
+
'GITLAB_CI',
|
|
69
|
+
'CIRCLECI',
|
|
70
|
+
'TRAVIS',
|
|
71
|
+
'BUILDKITE',
|
|
72
|
+
'JENKINS_URL',
|
|
73
|
+
'TF_BUILD', // Azure DevOps
|
|
74
|
+
'TEAMCITY_VERSION',
|
|
75
|
+
];
|
|
76
|
+
/** Other variables `isInteractive()` reads. */
|
|
77
|
+
export const INTERACTIVE_ENV_VARS = ['TERM', 'DEBIAN_FRONTEND'];
|
|
43
78
|
/**
|
|
44
79
|
* Return true iff we're running in a continuous-integration environment.
|
|
45
80
|
* Checks the common CI env vars set by major CI systems.
|
|
46
81
|
*/
|
|
47
82
|
export function isCI() {
|
|
48
|
-
const ciVars =
|
|
49
|
-
'CI',
|
|
50
|
-
'CONTINUOUS_INTEGRATION',
|
|
51
|
-
'BUILD_NUMBER',
|
|
52
|
-
'RUN_ID',
|
|
53
|
-
'GITHUB_ACTIONS',
|
|
54
|
-
'GITLAB_CI',
|
|
55
|
-
'CIRCLECI',
|
|
56
|
-
'TRAVIS',
|
|
57
|
-
'BUILDKITE',
|
|
58
|
-
'JENKINS_URL',
|
|
59
|
-
'TF_BUILD', // Azure DevOps
|
|
60
|
-
'TEAMCITY_VERSION',
|
|
61
|
-
];
|
|
83
|
+
const ciVars = CI_ENV_VARS;
|
|
62
84
|
for (const v of ciVars) {
|
|
63
85
|
const val = process.env[v];
|
|
64
86
|
if (val && val !== '0' && val.toLowerCase() !== 'false')
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared strict CLI flag/value parsing (#3062 review item 11, and the
|
|
3
|
+
* second-round review's finding 4).
|
|
4
|
+
*
|
|
5
|
+
* Several handlers (`init`, `setup`, `session`, `setup-run`) each grew
|
|
6
|
+
* their own small `flagValue(args, name)` helper, and every one of them
|
|
7
|
+
* treated a flag that is present but missing its value the same as a flag
|
|
8
|
+
* that was never given at all — `--prompt` as the very last token, or
|
|
9
|
+
* immediately followed by another flag, silently resolved to `undefined`
|
|
10
|
+
* instead of erroring. At a terminal that let the handler fall through
|
|
11
|
+
* into an interactive prompt instead of reporting the mistake; without one
|
|
12
|
+
* it could proceed on a silently-wrong default.
|
|
13
|
+
*
|
|
14
|
+
* The first-round shared parser fixed that but introduced its own gaps,
|
|
15
|
+
* found by the second review:
|
|
16
|
+
*
|
|
17
|
+
* - It validated only the *first* occurrence of a repeated single-value
|
|
18
|
+
* flag, so `--provider claude --provider` (a malformed second
|
|
19
|
+
* occurrence) silently passed.
|
|
20
|
+
* - It always resolved a repeated flag to its first occurrence, which
|
|
21
|
+
* silently changed `aiwg session`'s pre-existing last-occurrence-wins
|
|
22
|
+
* precedence for `--provider`/`--profile` to first-wins — a real
|
|
23
|
+
* behavior regression, not just a new validation gap.
|
|
24
|
+
* - `--flag=value` was never recognized at all.
|
|
25
|
+
* - Scanning never stopped at a bare `--` terminator.
|
|
26
|
+
* - An empty-string value (`--prompt ''`) was accepted as a valid value
|
|
27
|
+
* for flags where that is never meaningful.
|
|
28
|
+
*
|
|
29
|
+
* {@link scanFlagOccurrences} is the single-pass tokenizer every other
|
|
30
|
+
* helper here is built on: it finds *every* occurrence of the named
|
|
31
|
+
* flag(s), in one scan, validating each one independently, and stops
|
|
32
|
+
* recognizing flags entirely once it sees a bare `--` token (everything
|
|
33
|
+
* after that point is positional, per POSIX convention). `--flag value`
|
|
34
|
+
* and `--flag=value` are both accepted; in the space-separated form a
|
|
35
|
+
* value that itself looks like another flag (starts with `--`) is treated
|
|
36
|
+
* as a missing value — pass it via the `=` form instead
|
|
37
|
+
* (`--flag=--literal`) when a value must start with `--`. A value starting
|
|
38
|
+
* with a single `-` is always accepted in either form.
|
|
39
|
+
*
|
|
40
|
+
* Callers choose `precedence: 'first' | 'last'` to match whatever
|
|
41
|
+
* precedence the command already had before this module existed — see
|
|
42
|
+
* each call site's own comment for which one applies and why. Callers
|
|
43
|
+
* MUST catch {@link MissingFlagValueError} (or let it propagate to a
|
|
44
|
+
* handler-level catch) and report it before any preflight check, spawn,
|
|
45
|
+
* or write — never after.
|
|
46
|
+
*/
|
|
47
|
+
/** Thrown when a recognized flag is present but has no usable value following it. */
|
|
48
|
+
export class MissingFlagValueError extends Error {
|
|
49
|
+
/** The exact flag token that was missing its value (e.g. `--prompt`). */
|
|
50
|
+
flagName;
|
|
51
|
+
constructor(flagName) {
|
|
52
|
+
super(`${flagName} requires a value`);
|
|
53
|
+
this.name = 'MissingFlagValueError';
|
|
54
|
+
this.flagName = flagName;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
function splitEquals(token) {
|
|
58
|
+
const eq = token.indexOf('=');
|
|
59
|
+
if (eq === -1)
|
|
60
|
+
return null;
|
|
61
|
+
return [token.slice(0, eq), token.slice(eq + 1)];
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Single-pass scan for every occurrence of any of `names` in `args`. See
|
|
65
|
+
* the module doc comment for the full set of semantics this implements.
|
|
66
|
+
*
|
|
67
|
+
* Throws {@link MissingFlagValueError} on the *first* occurrence (in
|
|
68
|
+
* left-to-right order) that has no usable value or an empty one (when
|
|
69
|
+
* `options.rejectEmpty`) — every occurrence is checked, not just the
|
|
70
|
+
* first one found overall, so a malformed *later* occurrence is still an
|
|
71
|
+
* error even when an earlier one was valid.
|
|
72
|
+
*/
|
|
73
|
+
export function scanFlagOccurrences(args, names, options = {}) {
|
|
74
|
+
const occurrences = [];
|
|
75
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
76
|
+
const token = args[i];
|
|
77
|
+
if (token === undefined)
|
|
78
|
+
continue;
|
|
79
|
+
if (token === '--')
|
|
80
|
+
break; // bare terminator — stop recognizing flags entirely
|
|
81
|
+
if (token.startsWith('--')) {
|
|
82
|
+
const eqSplit = splitEquals(token);
|
|
83
|
+
if (eqSplit && names.includes(eqSplit[0])) {
|
|
84
|
+
const [flag, value] = eqSplit;
|
|
85
|
+
if (options.rejectEmpty && value === '')
|
|
86
|
+
throw new MissingFlagValueError(flag);
|
|
87
|
+
occurrences.push({ index: i, flag, value });
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
if (names.includes(token)) {
|
|
92
|
+
const next = args[i + 1];
|
|
93
|
+
if (next === undefined || next.startsWith('--')) {
|
|
94
|
+
throw new MissingFlagValueError(token);
|
|
95
|
+
}
|
|
96
|
+
if (options.rejectEmpty && next === '') {
|
|
97
|
+
throw new MissingFlagValueError(token);
|
|
98
|
+
}
|
|
99
|
+
occurrences.push({ index: i, flag: token, value: next });
|
|
100
|
+
i += 1; // skip the consumed value token
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return occurrences;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Look up the value of a (possibly repeated) single-value flag.
|
|
107
|
+
*
|
|
108
|
+
* - None of `names` appear at all → `undefined` (the flag was never
|
|
109
|
+
* given; not an error).
|
|
110
|
+
* - `names` appear once or more, every occurrence validated → the first
|
|
111
|
+
* or last occurrence's value, per `options.precedence` (default
|
|
112
|
+
* `'first'`). Pass `'last'` only for a command whose pre-existing
|
|
113
|
+
* behavior (before this module existed) already let a later occurrence
|
|
114
|
+
* override an earlier one — see the call site's comment.
|
|
115
|
+
*/
|
|
116
|
+
export function requireFlagValue(args, options, ...names) {
|
|
117
|
+
const occurrences = scanFlagOccurrences(args, names, options);
|
|
118
|
+
if (occurrences.length === 0)
|
|
119
|
+
return undefined;
|
|
120
|
+
return options.precedence === 'last'
|
|
121
|
+
? occurrences[occurrences.length - 1].value
|
|
122
|
+
: occurrences[0].value;
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Same validation contract as {@link requireFlagValue}, but collects the
|
|
126
|
+
* value of *every* occurrence in left-to-right order — for repeatable
|
|
127
|
+
* flags such as `--param`/`--script`. Returns `[]` when the flag never
|
|
128
|
+
* appears.
|
|
129
|
+
*/
|
|
130
|
+
export function collectFlagValues(args, options, ...names) {
|
|
131
|
+
return scanFlagOccurrences(args, names, options).map((occurrence) => occurrence.value);
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Parse a strict `true`/`false` flag value. `undefined` in means the flag
|
|
135
|
+
* was not given (not an error, and not a default — callers decide the
|
|
136
|
+
* default). Anything other than exactly `true`/`false` throws, naming
|
|
137
|
+
* `flagName` in the message.
|
|
138
|
+
*/
|
|
139
|
+
export function parseBooleanFlagValue(value, flagName) {
|
|
140
|
+
if (value === undefined)
|
|
141
|
+
return undefined;
|
|
142
|
+
if (value === 'true')
|
|
143
|
+
return true;
|
|
144
|
+
if (value === 'false')
|
|
145
|
+
return false;
|
|
146
|
+
throw new Error(`${flagName} must be true or false (got '${value}')`);
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Parse a comma-separated list flag value, trimming whitespace and
|
|
150
|
+
* dropping empty entries. Returns `undefined` when `raw` is `undefined`
|
|
151
|
+
* (flag not given); returns `[]` only if every entry was empty.
|
|
152
|
+
*/
|
|
153
|
+
export function parseListFlagValue(raw) {
|
|
154
|
+
if (raw === undefined)
|
|
155
|
+
return undefined;
|
|
156
|
+
return raw.split(',').map((entry) => entry.trim()).filter(Boolean);
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Split one repeatable `<name>=<value>` entry (as used by `--script`,
|
|
160
|
+
* `--param`) into its parts. Throws, naming `flagName`, when `entry` has
|
|
161
|
+
* no `=`, an empty name, or an empty value.
|
|
162
|
+
*/
|
|
163
|
+
export function parseKeyValueEntry(entry, flagName) {
|
|
164
|
+
const eq = entry.indexOf('=');
|
|
165
|
+
if (eq <= 0 || eq === entry.length - 1) {
|
|
166
|
+
throw new Error(`${flagName} requires the form <name>=<value> (got '${entry}')`);
|
|
167
|
+
}
|
|
168
|
+
return [entry.slice(0, eq).trim(), entry.slice(eq + 1).trim()];
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* The index of the first bare `--` terminator in `args`, or `args.length`
|
|
172
|
+
* when there is none — a loop bounded by this never looks at anything
|
|
173
|
+
* after the terminator, which per POSIX convention is never a flag or a
|
|
174
|
+
* command word again (#3062 go/no-go review, blocker 4).
|
|
175
|
+
*/
|
|
176
|
+
export function argsTerminatorIndex(args) {
|
|
177
|
+
const index = args.indexOf('--');
|
|
178
|
+
return index === -1 ? args.length : index;
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* The set of array indexes in `args` consumed as the *value* of a
|
|
182
|
+
* `--flag value` (two-token) occurrence of any name in `valueFlagNames`
|
|
183
|
+
* (#3062 go/no-go review, blocker 4). A `--flag=value` (one-token) form
|
|
184
|
+
* has no separate value index to report — the value is part of the same
|
|
185
|
+
* token, so it can never coincidentally equal a bare positional word or a
|
|
186
|
+
* different flag's own name the way a space-separated value can.
|
|
187
|
+
*
|
|
188
|
+
* This does not validate occurrences (a missing/malformed value is the
|
|
189
|
+
* paired value-flag parser's own job — e.g. {@link requireFlagValue} on
|
|
190
|
+
* the same `args` and `valueFlagNames`, called separately); it exists
|
|
191
|
+
* purely so a positional/switch scan knows which indexes are already
|
|
192
|
+
* spoken for and must not be reinterpreted as something else.
|
|
193
|
+
*/
|
|
194
|
+
export function valueTokenIndexes(args, valueFlagNames) {
|
|
195
|
+
const consumed = new Set();
|
|
196
|
+
const limit = args.length;
|
|
197
|
+
for (let i = 0; i < limit; i += 1) {
|
|
198
|
+
const token = args[i];
|
|
199
|
+
if (token === undefined)
|
|
200
|
+
continue;
|
|
201
|
+
if (token === '--')
|
|
202
|
+
break;
|
|
203
|
+
if (token.startsWith('--') && token.includes('='))
|
|
204
|
+
continue; // value is part of this same token
|
|
205
|
+
if (valueFlagNames.includes(token) && i + 1 < limit) {
|
|
206
|
+
consumed.add(i + 1);
|
|
207
|
+
i += 1; // the value token itself is spoken for; do not also treat it as a flag name
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
return consumed;
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* True iff `name` appears as a bare positional token or an exact-match
|
|
214
|
+
* boolean switch anywhere in `args` *before* the `--` terminator, and is
|
|
215
|
+
* not itself the already-consumed value of one of `valueFlagNames` (#3062
|
|
216
|
+
* go/no-go review, blocker 4).
|
|
217
|
+
*
|
|
218
|
+
* Use this instead of `args.includes(name)` (or an equivalent hand-rolled
|
|
219
|
+
* loop) for any boolean switch or positional command word that a value-
|
|
220
|
+
* taking flag's own value could coincidentally equal — e.g. a free-text
|
|
221
|
+
* `--prompt` argument that happens to contain the literal word a command
|
|
222
|
+
* also uses as a switch, or a value that collides with another flag's own
|
|
223
|
+
* name.
|
|
224
|
+
*/
|
|
225
|
+
export function hasPositionalOrSwitch(args, name, valueFlagNames) {
|
|
226
|
+
const consumed = valueTokenIndexes(args, valueFlagNames);
|
|
227
|
+
const limit = argsTerminatorIndex(args);
|
|
228
|
+
for (let i = 0; i < limit; i += 1) {
|
|
229
|
+
if (consumed.has(i))
|
|
230
|
+
continue;
|
|
231
|
+
if (args[i] === name)
|
|
232
|
+
return true;
|
|
233
|
+
}
|
|
234
|
+
return false;
|
|
235
|
+
}
|
|
236
|
+
//# sourceMappingURL=flag-parsing.js.map
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import { decisionCapabilities, evaluateRequestPath, listPatterns, livePlan, materializeSyntheticClassificationSetup, runOfflinePattern, showPattern, syntheticClassificationSetup, validateDecisionInput, } from '../../decision/driver.js';
|
|
1
|
+
import { ATTRIBUTION_FIELD_PATTERN, askDecision, DecisionAskUsageError, decisionCapabilities, DecisionLedgerSinceError, decisionLedgerHistory, decisionLedgerStats, evaluateRequestPath, listPatterns, livePlan, MAX_ATTRIBUTION_FIELD_LENGTH, materializeSyntheticClassificationSetup, parseDecisionLedgerSince, readDecisionContextFile, recordDecisionAsk, runOfflinePattern, setupJev, showPattern, syntheticClassificationSetup, validateDecisionInput, } from '../../decision/driver.js';
|
|
2
2
|
function jsonResult(value, exitCode = 0) {
|
|
3
3
|
return { exitCode, message: JSON.stringify(value, null, 2), rawOutput: true };
|
|
4
4
|
}
|
|
5
5
|
function usage() {
|
|
6
6
|
return [
|
|
7
|
-
'Usage: aiwg decision <capabilities|status|patterns|validate|evaluate|setup> [options]',
|
|
7
|
+
'Usage: aiwg decision <capabilities|status|patterns|validate|evaluate|setup|history|stats> [options]',
|
|
8
|
+
' aiwg decision ask --question "<q>" (--yes-no | --choices a,b,c | --scale 1-5) [--context <text> | --context-file <path> | --context-stdin] [--threshold 0.8] [--timeout-ms 15000] [--provider <id>] [--session <id>] [--caller <tag>] [--record-text] [--json]',
|
|
8
9
|
'',
|
|
9
10
|
'Commands:',
|
|
10
11
|
' capabilities|status Show offline readiness, primitives, config and feature availability',
|
|
@@ -15,6 +16,34 @@ function usage() {
|
|
|
15
16
|
' validate <request|definition|ruleset|binding> <path>',
|
|
16
17
|
' evaluate --request <path> [--host-policy-module <path>]',
|
|
17
18
|
' setup synthetic-classification [--output-dir <dir>]',
|
|
19
|
+
' setup jev [--token-stdin] [--region <r>] [--endpoint <url>] [--verify] [--remove]',
|
|
20
|
+
' ask --question "<q>" (--yes-no | --choices a,b,c | --scale 1-5) [--provider <id>] [--session <id>] [--caller <tag>] [--record-text]',
|
|
21
|
+
' history [--since <date>] [--provider <id>] [--session <id>] [--kind yes-no|choices|scale] [--status answered|fallback] [--caller <tag>|<prefix>* | --no-caller] [--json]',
|
|
22
|
+
' stats [--since <date>] [--provider <id>] [--session <id>] [--kind yes-no|choices|scale] [--status answered|fallback] [--caller <tag>|<prefix>* | --no-caller] [--group-by provider|session|kind|caller] [--json]',
|
|
23
|
+
'',
|
|
24
|
+
'Notes:',
|
|
25
|
+
' Every COMPLETED `ask` call (CLI and MCP) appends one record to the decision',
|
|
26
|
+
' ledger (.aiwg/decision/ledger.jsonl, resolved like `aiwg artifacts path`).',
|
|
27
|
+
' Usage errors (bad flags, oversized context) are never recorded. Question',
|
|
28
|
+
' and context text are never written unless --record-text is passed, and even',
|
|
29
|
+
' then only after redaction and truncation to a documented size cap. Every',
|
|
30
|
+
' other persisted field (session, caller, choice ids, answer, model, provider,',
|
|
31
|
+
' project root) is also redacted before being written. `questionDigest` and',
|
|
32
|
+
' `contextDigest` are plain SHA-256 over the raw text: a short, predictable',
|
|
33
|
+
' question can still be matched by hashing candidate guesses, so a digest is',
|
|
34
|
+
' not a confidentiality guarantee on its own.',
|
|
35
|
+
' `history` and `stats` read that ledger offline — no network, no Jev call.',
|
|
36
|
+
' `--since` is strict ISO-8601: a bare date or an offsetless timestamp is',
|
|
37
|
+
' interpreted as UTC; invalid calendar dates (e.g. 2026-02-30) are rejected,',
|
|
38
|
+
' including when an explicit offset is present.',
|
|
39
|
+
` --session and --caller must match ${ATTRIBUTION_FIELD_PATTERN.source} (<= ${MAX_ATTRIBUTION_FIELD_LENGTH} characters).`,
|
|
40
|
+
' `history`/`stats` accept `--caller <tag>` (exact), `--caller \'<prefix>*\'` (prefix; the only wildcard), or `--no-caller` (records with no tag; not combinable with `--caller`).',
|
|
41
|
+
' `history`/`stats` reject unknown flags and positional arguments with exit 2.',
|
|
42
|
+
' The ledger has no built-in retention: it grows without bound and the',
|
|
43
|
+
' operator may truncate or delete it at any time; readers tolerate a missing',
|
|
44
|
+
' file. The ledger trusts only the local filesystem under the project',
|
|
45
|
+
' artifact root, owned by the same user running aiwg — it is not a defense',
|
|
46
|
+
' against a hostile process already running as that user.',
|
|
18
47
|
].join('\n');
|
|
19
48
|
}
|
|
20
49
|
function takeOption(args, name) {
|
|
@@ -23,11 +52,266 @@ function takeOption(args, name) {
|
|
|
23
52
|
return undefined;
|
|
24
53
|
return args[index + 1];
|
|
25
54
|
}
|
|
55
|
+
function takeRequiredOption(args, name) {
|
|
56
|
+
const value = takeOption(args, name);
|
|
57
|
+
if (!value || value.startsWith('--'))
|
|
58
|
+
throw new DecisionAskUsageError(`missing-${name.slice(2)}`, `${name} requires a value`);
|
|
59
|
+
return value;
|
|
60
|
+
}
|
|
26
61
|
function patternId(value) {
|
|
27
62
|
if (!value)
|
|
28
63
|
throw new Error('Pattern id is required');
|
|
29
64
|
return value;
|
|
30
65
|
}
|
|
66
|
+
function flagCount(args, names) {
|
|
67
|
+
return args.filter(arg => names.includes(arg)).length;
|
|
68
|
+
}
|
|
69
|
+
async function readStdin() {
|
|
70
|
+
const chunks = [];
|
|
71
|
+
for await (const chunk of process.stdin)
|
|
72
|
+
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk)));
|
|
73
|
+
return Buffer.concat(chunks).toString('utf8');
|
|
74
|
+
}
|
|
75
|
+
function parseNumber(value, name) {
|
|
76
|
+
if (value === undefined)
|
|
77
|
+
throw new DecisionAskUsageError(`missing-${name.slice(2)}`, `${name} requires a value`);
|
|
78
|
+
const parsed = Number(value);
|
|
79
|
+
if (!Number.isFinite(parsed))
|
|
80
|
+
throw new DecisionAskUsageError(`invalid-${name.slice(2)}`, `${name} must be numeric`);
|
|
81
|
+
return parsed;
|
|
82
|
+
}
|
|
83
|
+
function parseScale(value) {
|
|
84
|
+
if (!value)
|
|
85
|
+
throw new DecisionAskUsageError('invalid-scale', '--scale requires a range like 1-5');
|
|
86
|
+
const match = /^(-?\d+)-(-?\d+)$/.exec(value);
|
|
87
|
+
if (!match)
|
|
88
|
+
throw new DecisionAskUsageError('invalid-scale', '--scale requires a range like 1-5');
|
|
89
|
+
return { low: Number(match[1]), high: Number(match[2]) };
|
|
90
|
+
}
|
|
91
|
+
/** Shared by --session and --caller (review r1 finding 1): a conservative identifier shape, rejected with exit 2. */
|
|
92
|
+
function takeAttributionOption(args, name, reason) {
|
|
93
|
+
const value = takeOption(args, name);
|
|
94
|
+
if (value === undefined)
|
|
95
|
+
return undefined;
|
|
96
|
+
if (!ATTRIBUTION_FIELD_PATTERN.test(value)) {
|
|
97
|
+
throw new DecisionAskUsageError(reason, `${name} must match ${ATTRIBUTION_FIELD_PATTERN.source} (<= ${MAX_ATTRIBUTION_FIELD_LENGTH} characters)`);
|
|
98
|
+
}
|
|
99
|
+
return value;
|
|
100
|
+
}
|
|
101
|
+
async function decisionSetupJev(rest) {
|
|
102
|
+
const token = rest.includes('--token-stdin') ? (await readStdin()).trim() : undefined;
|
|
103
|
+
const result = await setupJev({
|
|
104
|
+
token,
|
|
105
|
+
region: takeOption(rest, '--region'),
|
|
106
|
+
endpoint: takeOption(rest, '--endpoint'),
|
|
107
|
+
verify: rest.includes('--verify'),
|
|
108
|
+
remove: rest.includes('--remove'),
|
|
109
|
+
}, { env: process.env });
|
|
110
|
+
return jsonResult(result);
|
|
111
|
+
}
|
|
112
|
+
async function decisionAsk(ctx) {
|
|
113
|
+
const args = ctx.args.slice(1);
|
|
114
|
+
const modeCount = flagCount(args, ['--yes-no']) + (args.includes('--choices') ? 1 : 0) + (args.includes('--scale') ? 1 : 0);
|
|
115
|
+
if (modeCount !== 1)
|
|
116
|
+
throw new DecisionAskUsageError('invalid-mode', 'Select exactly one of --yes-no, --choices, or --scale');
|
|
117
|
+
const contextModes = flagCount(args, ['--context', '--context-file', '--context-stdin']);
|
|
118
|
+
if (contextModes > 1)
|
|
119
|
+
throw new DecisionAskUsageError('invalid-context', 'Select at most one context source');
|
|
120
|
+
// Validated before any Jev call, so a malformed attribution flag never wastes one.
|
|
121
|
+
const explicitSessionId = takeAttributionOption(args, '--session', 'invalid-session');
|
|
122
|
+
const caller = takeAttributionOption(args, '--caller', 'invalid-caller');
|
|
123
|
+
const question = args.includes('--question') ? takeRequiredOption(args, '--question') : '';
|
|
124
|
+
let context = takeOption(args, '--context');
|
|
125
|
+
if (args.includes('--context') && (context === undefined || context.startsWith('--'))) {
|
|
126
|
+
throw new DecisionAskUsageError('missing-context', '--context requires text');
|
|
127
|
+
}
|
|
128
|
+
if (args.includes('--context-file')) {
|
|
129
|
+
const filePath = takeRequiredOption(args, '--context-file');
|
|
130
|
+
context = await readDecisionContextFile(filePath);
|
|
131
|
+
}
|
|
132
|
+
if (args.includes('--context-stdin'))
|
|
133
|
+
context = await readStdin();
|
|
134
|
+
const choices = args.includes('--choices') ? takeRequiredOption(args, '--choices') : undefined;
|
|
135
|
+
const scale = args.includes('--scale') ? parseScale(takeOption(args, '--scale')) : null;
|
|
136
|
+
const input = {
|
|
137
|
+
question,
|
|
138
|
+
mode: args.includes('--yes-no') ? { kind: 'yes-no' }
|
|
139
|
+
: choices !== undefined ? { kind: 'choices', choices: choices.split(',').map(value => value.trim()).filter(Boolean) }
|
|
140
|
+
: { kind: 'scale', ...scale },
|
|
141
|
+
...(context === undefined ? {} : { context }),
|
|
142
|
+
...(args.includes('--threshold') ? { threshold: parseNumber(takeOption(args, '--threshold'), '--threshold') } : {}),
|
|
143
|
+
...(args.includes('--timeout-ms') ? { timeoutMs: parseNumber(takeOption(args, '--timeout-ms'), '--timeout-ms') } : {}),
|
|
144
|
+
};
|
|
145
|
+
const result = await askDecision(input, { env: process.env });
|
|
146
|
+
// Best-effort and bounded (recordDecisionAsk never throws and never hangs past its own
|
|
147
|
+
// deadline): a ledger failure, timeout, or attribution-detection error can never change
|
|
148
|
+
// `result` or the exit code decided below (review r1 findings 3 and 4).
|
|
149
|
+
await recordDecisionAsk(input, result, {
|
|
150
|
+
transport: 'cli',
|
|
151
|
+
projectRoot: ctx.cwd,
|
|
152
|
+
env: process.env,
|
|
153
|
+
explicitProvider: takeOption(args, '--provider'),
|
|
154
|
+
explicitSessionId,
|
|
155
|
+
caller: caller ?? null,
|
|
156
|
+
recordText: args.includes('--record-text'),
|
|
157
|
+
});
|
|
158
|
+
if (args.includes('--json'))
|
|
159
|
+
return jsonResult(result);
|
|
160
|
+
if (result.status === 'fallback')
|
|
161
|
+
return { exitCode: 0, message: `FALLBACK to LLM: ${result.reason}`, rawOutput: true };
|
|
162
|
+
return { exitCode: 0, message: `${String(result.answer)} (confidence ${result.confidence?.toFixed(2) ?? 'unknown'}, ${result.model ?? 'unknown-model'})`, rawOutput: true };
|
|
163
|
+
}
|
|
164
|
+
function parseSince(value) {
|
|
165
|
+
if (value === undefined)
|
|
166
|
+
return undefined;
|
|
167
|
+
try {
|
|
168
|
+
return parseDecisionLedgerSince(value);
|
|
169
|
+
}
|
|
170
|
+
catch (error) {
|
|
171
|
+
if (error instanceof DecisionLedgerSinceError)
|
|
172
|
+
throw new DecisionAskUsageError('invalid-since', error.message);
|
|
173
|
+
throw error;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
const DECISION_LEDGER_KINDS = ['yes-no', 'choices', 'scale'];
|
|
177
|
+
const DECISION_LEDGER_STATUSES = ['answered', 'fallback'];
|
|
178
|
+
const DECISION_LEDGER_GROUP_BYS = ['provider', 'session', 'kind', 'caller'];
|
|
179
|
+
function parseKindFilter(value) {
|
|
180
|
+
if (value === undefined)
|
|
181
|
+
return undefined;
|
|
182
|
+
if (!DECISION_LEDGER_KINDS.includes(value)) {
|
|
183
|
+
throw new DecisionAskUsageError('invalid-kind', `--kind must be one of ${DECISION_LEDGER_KINDS.join(', ')}`);
|
|
184
|
+
}
|
|
185
|
+
return value;
|
|
186
|
+
}
|
|
187
|
+
function parseStatusFilter(value) {
|
|
188
|
+
if (value === undefined)
|
|
189
|
+
return undefined;
|
|
190
|
+
if (!DECISION_LEDGER_STATUSES.includes(value)) {
|
|
191
|
+
throw new DecisionAskUsageError('invalid-status', `--status must be one of ${DECISION_LEDGER_STATUSES.join(', ')}`);
|
|
192
|
+
}
|
|
193
|
+
return value;
|
|
194
|
+
}
|
|
195
|
+
function parseGroupBy(value) {
|
|
196
|
+
if (value === undefined)
|
|
197
|
+
return undefined;
|
|
198
|
+
if (!DECISION_LEDGER_GROUP_BYS.includes(value)) {
|
|
199
|
+
throw new DecisionAskUsageError('invalid-group-by', `--group-by must be one of ${DECISION_LEDGER_GROUP_BYS.join(', ')}`);
|
|
200
|
+
}
|
|
201
|
+
return value;
|
|
202
|
+
}
|
|
203
|
+
const HISTORY_VALUE_FLAGS = ['--since', '--provider', '--session', '--kind', '--status', '--caller'];
|
|
204
|
+
const HISTORY_BOOL_FLAGS = ['--json', '--no-caller'];
|
|
205
|
+
const STATS_VALUE_FLAGS = [...HISTORY_VALUE_FLAGS, '--group-by'];
|
|
206
|
+
const STATS_BOOL_FLAGS = HISTORY_BOOL_FLAGS;
|
|
207
|
+
/**
|
|
208
|
+
* Strict allow-list for `history`/`stats`: unknown `--flags` and positional
|
|
209
|
+
* arguments are usage errors (exit 2) naming the offending token, instead of
|
|
210
|
+
* being silently ignored. `ask`/`setup` parsing is intentionally untouched.
|
|
211
|
+
*/
|
|
212
|
+
function assertLedgerArgs(args, valueFlags, boolFlags) {
|
|
213
|
+
let index = 0;
|
|
214
|
+
while (index < args.length) {
|
|
215
|
+
const token = args[index];
|
|
216
|
+
if (valueFlags.includes(token)) {
|
|
217
|
+
if (args[index + 1] === undefined)
|
|
218
|
+
throw new DecisionAskUsageError(`missing-${token.slice(2)}`, `${token} requires a value`);
|
|
219
|
+
index += 2;
|
|
220
|
+
continue;
|
|
221
|
+
}
|
|
222
|
+
if (boolFlags.includes(token)) {
|
|
223
|
+
index += 1;
|
|
224
|
+
continue;
|
|
225
|
+
}
|
|
226
|
+
if (token.startsWith('--'))
|
|
227
|
+
throw new DecisionAskUsageError('unknown-flag', `unknown flag ${token}`);
|
|
228
|
+
throw new DecisionAskUsageError('unexpected-argument', `unexpected argument ${token}`);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
* Parses `--caller <tag>` for `history`/`stats`: an exact match, except a
|
|
233
|
+
* single trailing `*` means prefix match (`--caller 'address-issues:*'`).
|
|
234
|
+
* The tag itself must match the attribution shape; `--no-caller` (records
|
|
235
|
+
* with no caller tag) is parsed separately and cannot be combined with it.
|
|
236
|
+
*/
|
|
237
|
+
function parseCallerFilter(args) {
|
|
238
|
+
const raw = takeOption(args, '--caller');
|
|
239
|
+
const noCaller = args.includes('--no-caller');
|
|
240
|
+
if (raw !== undefined && noCaller) {
|
|
241
|
+
throw new DecisionAskUsageError('invalid-caller-filter', '--caller and --no-caller cannot be used together');
|
|
242
|
+
}
|
|
243
|
+
if (noCaller)
|
|
244
|
+
return { noCaller: true };
|
|
245
|
+
if (raw === undefined)
|
|
246
|
+
return {};
|
|
247
|
+
if (raw === '' || raw.startsWith('--')) {
|
|
248
|
+
throw new DecisionAskUsageError('invalid-caller', '--caller requires a non-empty value');
|
|
249
|
+
}
|
|
250
|
+
const prefix = raw.endsWith('*');
|
|
251
|
+
const tag = prefix ? raw.slice(0, -1) : raw;
|
|
252
|
+
if (tag === '' || !ATTRIBUTION_FIELD_PATTERN.test(tag)) {
|
|
253
|
+
throw new DecisionAskUsageError('invalid-caller', `--caller must match ${ATTRIBUTION_FIELD_PATTERN.source} (<= ${MAX_ATTRIBUTION_FIELD_LENGTH} characters), allowing one trailing *`);
|
|
254
|
+
}
|
|
255
|
+
return prefix ? { callerPrefix: tag } : { caller: tag };
|
|
256
|
+
}
|
|
257
|
+
/** Shared by `history` and `stats` (review r1 finding 9): the same filters, parsed the same way. */
|
|
258
|
+
function parseLedgerFilters(args) {
|
|
259
|
+
return {
|
|
260
|
+
since: parseSince(takeOption(args, '--since')),
|
|
261
|
+
provider: takeOption(args, '--provider'),
|
|
262
|
+
sessionId: takeOption(args, '--session'),
|
|
263
|
+
kind: parseKindFilter(takeOption(args, '--kind')),
|
|
264
|
+
status: parseStatusFilter(takeOption(args, '--status')),
|
|
265
|
+
...parseCallerFilter(args),
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
/** One header line stating the caller filter, shown only when one is set. */
|
|
269
|
+
function callerFilterHeader(filters) {
|
|
270
|
+
if (filters.noCaller)
|
|
271
|
+
return 'Caller filter: no caller (records with no caller tag)';
|
|
272
|
+
if (filters.caller !== undefined)
|
|
273
|
+
return `Caller filter: ${filters.caller}`;
|
|
274
|
+
if (filters.callerPrefix !== undefined)
|
|
275
|
+
return `Caller filter: ${filters.callerPrefix}*`;
|
|
276
|
+
return null;
|
|
277
|
+
}
|
|
278
|
+
async function decisionHistory(ctx) {
|
|
279
|
+
const args = ctx.args.slice(1);
|
|
280
|
+
assertLedgerArgs(args, HISTORY_VALUE_FLAGS, HISTORY_BOOL_FLAGS);
|
|
281
|
+
const filters = parseLedgerFilters(args);
|
|
282
|
+
const result = await decisionLedgerHistory(ctx.cwd, filters);
|
|
283
|
+
if (args.includes('--json'))
|
|
284
|
+
return jsonResult(result);
|
|
285
|
+
const header = result.skipped > 0 ? [`(${result.skipped} invalid line(s) skipped)`] : [];
|
|
286
|
+
const callerHeader = callerFilterHeader(filters);
|
|
287
|
+
if (callerHeader)
|
|
288
|
+
header.push(callerHeader);
|
|
289
|
+
if (result.count === 0)
|
|
290
|
+
return { exitCode: 0, message: [...header, 'No decision ledger records match the filters.'].join('\n'), rawOutput: true };
|
|
291
|
+
const lines = result.records.map(record => `${record.timestamp} ${record.status.padEnd(8)} provider=${record.provider} kind=${record.decisionKind} confidence=${record.confidence ?? '-'} caller=${record.caller ?? '-'} id=${record.id}`);
|
|
292
|
+
return { exitCode: 0, message: [...header, ...lines].join('\n'), rawOutput: true };
|
|
293
|
+
}
|
|
294
|
+
async function decisionStats(ctx) {
|
|
295
|
+
const args = ctx.args.slice(1);
|
|
296
|
+
assertLedgerArgs(args, STATS_VALUE_FLAGS, STATS_BOOL_FLAGS);
|
|
297
|
+
const filters = { ...parseLedgerFilters(args), groupBy: parseGroupBy(takeOption(args, '--group-by')) };
|
|
298
|
+
const result = await decisionLedgerStats(ctx.cwd, filters);
|
|
299
|
+
if (args.includes('--json'))
|
|
300
|
+
return jsonResult(result);
|
|
301
|
+
const header = result.skipped > 0 ? [`(${result.skipped} invalid line(s) skipped)`] : [];
|
|
302
|
+
const callerHeader = callerFilterHeader(filters);
|
|
303
|
+
if (callerHeader)
|
|
304
|
+
header.push(callerHeader);
|
|
305
|
+
if (result.groups.length === 0)
|
|
306
|
+
return { exitCode: 0, message: [...header, 'No decision ledger records match the filters.'].join('\n'), rawOutput: true };
|
|
307
|
+
const lines = result.groups.flatMap(group => [
|
|
308
|
+
`${result.groupBy ?? 'all'}=${group.key ?? 'null'}: ${group.totalCalls} calls (${group.answered} answered, ${group.fallback} fallback, rate=${group.fallbackRate.toFixed(2)})`,
|
|
309
|
+
` confidence: mean=${group.confidence.mean?.toFixed(2) ?? '-'} min=${group.confidence.min?.toFixed(2) ?? '-'} max=${group.confidence.max?.toFixed(2) ?? '-'} (n=${group.confidence.count})`,
|
|
310
|
+
` jev usage: input=${group.jevUsage.inputTokens} output=${group.jevUsage.outputTokens}`,
|
|
311
|
+
` estimated LLM tokens avoided=${group.estimatedLlmTokensAvoidedTotal} net savings=${group.netSavingsTokens}`,
|
|
312
|
+
]);
|
|
313
|
+
return { exitCode: 0, message: [...header, ...lines].join('\n'), rawOutput: true };
|
|
314
|
+
}
|
|
31
315
|
async function executeDecision(ctx) {
|
|
32
316
|
const [command, subcommand, ...rest] = ctx.args;
|
|
33
317
|
const options = { cwd: ctx.cwd, frameworkRoot: ctx.frameworkRoot, env: process.env };
|
|
@@ -36,6 +320,12 @@ async function executeDecision(ctx) {
|
|
|
36
320
|
return { exitCode: 0, message: usage(), rawOutput: true };
|
|
37
321
|
if (command === 'capabilities' || command === 'status')
|
|
38
322
|
return jsonResult(decisionCapabilities(options));
|
|
323
|
+
if (command === 'ask')
|
|
324
|
+
return await decisionAsk(ctx);
|
|
325
|
+
if (command === 'history')
|
|
326
|
+
return await decisionHistory(ctx);
|
|
327
|
+
if (command === 'stats')
|
|
328
|
+
return await decisionStats(ctx);
|
|
39
329
|
if (command === 'patterns') {
|
|
40
330
|
if (subcommand === 'list')
|
|
41
331
|
return jsonResult(listPatterns());
|
|
@@ -70,9 +360,14 @@ async function executeDecision(ctx) {
|
|
|
70
360
|
return jsonResult(await materializeSyntheticClassificationSetup(outputDir, {}, options));
|
|
71
361
|
return jsonResult(syntheticClassificationSetup({}, options));
|
|
72
362
|
}
|
|
363
|
+
if (command === 'setup' && subcommand === 'jev')
|
|
364
|
+
return await decisionSetupJev(rest);
|
|
73
365
|
return { exitCode: 2, message: usage(), rawOutput: true };
|
|
74
366
|
}
|
|
75
367
|
catch (error) {
|
|
368
|
+
if (error instanceof DecisionAskUsageError) {
|
|
369
|
+
return { exitCode: 2, message: JSON.stringify({ schema: 'aiwg-decision-error/v1', reason: error.reason, message: error.message }, null, 2), rawOutput: true };
|
|
370
|
+
}
|
|
76
371
|
return { exitCode: 1, message: error instanceof Error ? error.message : String(error) };
|
|
77
372
|
}
|
|
78
373
|
}
|