@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
|
@@ -1,26 +1,42 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { boundedSpawnSync } from '../../process/bounded-child-process.mjs';
|
|
2
2
|
import { existsSync } from 'fs';
|
|
3
3
|
import { emptyConfig, FORCE_PUSH_POLICY_ALIAS_NOTE, normalizeForcePushPolicy as normalizeForcePushPolicyShared, getConfigPath, getProjectDir, readAiwgConfig, resolveRemoteProvider, VALID_PROVIDERS, writeAiwgConfig, } from '../../config/aiwg-config.js';
|
|
4
4
|
import { AiwgError, EXIT_CODES } from '../errors.js';
|
|
5
|
-
import { askChoice, askString, askYesNo, createPromptInterface } from '../prompt-utils.js';
|
|
5
|
+
import { askChoice, askConsent, askString, askYesNo, createPromptInterface } from '../prompt-utils.js';
|
|
6
6
|
import * as ui from '../ui.js';
|
|
7
7
|
import { projectAiwgPath } from '../../config/project-artifacts.js';
|
|
8
8
|
import { normalizeProviderDefinitionId } from '../../providers/provider-definitions.js';
|
|
9
|
+
import { isInteractive } from '../env.js';
|
|
10
|
+
import { requireFlagValue as strictFlagValue, MissingFlagValueError } from '../flag-parsing.js';
|
|
11
|
+
import { detectInstalledWorkerClis, evaluateWorkerDispatchOffer, workerDispatchOfferHintWithArg, workerDispatchOfferQuestion, WORKER_DISPATCH_ARG_VALUES, } from '../../models/worker-dispatch-offer.js';
|
|
9
12
|
const DELIVERY_MODES = ['pr-required', 'feature-branch', 'direct'];
|
|
10
13
|
const FORCE_PUSH_POLICIES = ['never', 'own-branch-only', 'allowed'];
|
|
11
14
|
const ISSUE_PROVIDERS = ['gitea', 'github', 'local'];
|
|
12
15
|
const SIGNING_FORMATS = ['openpgp', 'ssh', 'x509'];
|
|
13
16
|
const TRACKER_VIA = ['tea', 'gh', 'mcp', 'api'];
|
|
17
|
+
/**
|
|
18
|
+
* A value-taking flag present without a usable value is a usage error, not
|
|
19
|
+
* "not given" (#3062 review item 11) — delegates to the shared strict
|
|
20
|
+
* parser, translating its `MissingFlagValueError` into this file's own
|
|
21
|
+
* `AiwgError`/`EXIT_CODES.USAGE` convention. `precedence: 'first'` matches
|
|
22
|
+
* this command's own pre-#3062 behavior (a hand-rolled loop that returned
|
|
23
|
+
* on the first match) — unchanged by the shared parser's second-round fix
|
|
24
|
+
* (#3062 second review, finding 4).
|
|
25
|
+
*/
|
|
14
26
|
function flagValue(args, ...names) {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
27
|
+
try {
|
|
28
|
+
return strictFlagValue(args, {}, ...names);
|
|
29
|
+
}
|
|
30
|
+
catch (err) {
|
|
31
|
+
if (err instanceof MissingFlagValueError) {
|
|
32
|
+
throw new AiwgError({
|
|
33
|
+
code: 'ERR_USAGE_MISSING_FLAG_VALUE',
|
|
34
|
+
message: `${err.message}.`,
|
|
35
|
+
exitCode: EXIT_CODES.USAGE,
|
|
36
|
+
});
|
|
21
37
|
}
|
|
38
|
+
throw err;
|
|
22
39
|
}
|
|
23
|
-
return undefined;
|
|
24
40
|
}
|
|
25
41
|
function boolFlag(args, name) {
|
|
26
42
|
return args.includes(name);
|
|
@@ -92,6 +108,10 @@ export function parseSetupProjectOptions(ctx) {
|
|
|
92
108
|
trackerActorVia: parseEnum(flagValue(args, '--tracker-actor-via'), TRACKER_VIA, '--tracker-actor-via'),
|
|
93
109
|
customerTrackerActorLogin: flagValue(args, '--customer-tracker-actor-login'),
|
|
94
110
|
customerTrackerActorVia: parseEnum(flagValue(args, '--customer-tracker-actor-via'), TRACKER_VIA, '--customer-tracker-actor-via'),
|
|
111
|
+
// #3062: answers the worker-dispatch question directly, with or without
|
|
112
|
+
// a terminal, and suppresses the interactive question below (see
|
|
113
|
+
// `SetupProjectOptions.workerDispatchChoice`'s doc comment).
|
|
114
|
+
workerDispatchChoice: parseEnum(flagValue(args, '--worker-dispatch'), WORKER_DISPATCH_ARG_VALUES, '--worker-dispatch'),
|
|
95
115
|
providers: (() => {
|
|
96
116
|
const providers = parseStringList(flagValue(args, '--providers'));
|
|
97
117
|
return providers ? normalizeProviders(providers) : undefined;
|
|
@@ -99,7 +119,7 @@ export function parseSetupProjectOptions(ctx) {
|
|
|
99
119
|
};
|
|
100
120
|
}
|
|
101
121
|
export function detectGitRemotes(projectDir) {
|
|
102
|
-
const result =
|
|
122
|
+
const result = boundedSpawnSync('git', ['-C', projectDir, 'remote', '-v'], 'quick', {
|
|
103
123
|
encoding: 'utf-8',
|
|
104
124
|
stdio: ['ignore', 'pipe', 'ignore'],
|
|
105
125
|
});
|
|
@@ -122,21 +142,21 @@ export function detectGitRemotes(projectDir) {
|
|
|
122
142
|
return [...byName.values()];
|
|
123
143
|
}
|
|
124
144
|
function detectDefaultBranch(projectDir) {
|
|
125
|
-
const symbolic =
|
|
145
|
+
const symbolic = boundedSpawnSync('git', ['-C', projectDir, 'symbolic-ref', '--quiet', '--short', 'refs/remotes/origin/HEAD'], 'quick', {
|
|
126
146
|
encoding: 'utf-8',
|
|
127
147
|
stdio: ['ignore', 'pipe', 'ignore'],
|
|
128
148
|
});
|
|
129
149
|
const fromOrigin = symbolic.stdout.trim().replace(/^origin\//, '');
|
|
130
150
|
if (symbolic.status === 0 && fromOrigin)
|
|
131
151
|
return fromOrigin;
|
|
132
|
-
const branch =
|
|
152
|
+
const branch = boundedSpawnSync('git', ['-C', projectDir, 'branch', '--show-current'], 'quick', {
|
|
133
153
|
encoding: 'utf-8',
|
|
134
154
|
stdio: ['ignore', 'pipe', 'ignore'],
|
|
135
155
|
});
|
|
136
156
|
return branch.stdout.trim() || 'main';
|
|
137
157
|
}
|
|
138
158
|
function gitConfig(projectDir, key) {
|
|
139
|
-
const result =
|
|
159
|
+
const result = boundedSpawnSync('git', ['-C', projectDir, 'config', '--get', key], 'quick', {
|
|
140
160
|
encoding: 'utf-8',
|
|
141
161
|
stdio: ['ignore', 'pipe', 'ignore'],
|
|
142
162
|
});
|
|
@@ -321,6 +341,19 @@ export async function buildSetupProjectPlan(options) {
|
|
|
321
341
|
}
|
|
322
342
|
base.remotes = remotesConfig;
|
|
323
343
|
base.delivery = delivery;
|
|
344
|
+
// #3050: apply an already-made worker-dispatch opt-in choice (from the
|
|
345
|
+
// interactive wizard), then recompute whether any surface should still
|
|
346
|
+
// offer/hint it — a choice just applied here always clears the offer.
|
|
347
|
+
// The decline is a setup/onboarding preference, not worker policy, so it
|
|
348
|
+
// lives in `setup`, not in the frozen `parallelism` contract fragment
|
|
349
|
+
// (B-OG-29).
|
|
350
|
+
if (options.workerDispatchChoice === 'enable') {
|
|
351
|
+
base.parallelism = { ...base.parallelism, worker_dispatch: true };
|
|
352
|
+
}
|
|
353
|
+
else if (options.workerDispatchChoice === 'decline') {
|
|
354
|
+
base.setup = { ...base.setup, worker_dispatch_offer: 'declined' };
|
|
355
|
+
}
|
|
356
|
+
const workerDispatchOffer = evaluateWorkerDispatchOffer(base.parallelism, base.setup, detectInstalledWorkerClis());
|
|
324
357
|
if (primaryRemote?.provider === 'unknown' && issueProvider !== 'local') {
|
|
325
358
|
warnings.push(`Remote '${primary}' is self-hosted or unknown; provider '${issueProvider}' was selected explicitly/defaulted.`);
|
|
326
359
|
}
|
|
@@ -347,9 +380,10 @@ export async function buildSetupProjectPlan(options) {
|
|
|
347
380
|
issueProvider,
|
|
348
381
|
warnings,
|
|
349
382
|
diff: formatPreviewDiff(existing, base),
|
|
383
|
+
workerDispatchOffer,
|
|
350
384
|
};
|
|
351
385
|
}
|
|
352
|
-
async function applyInteractivePrompts(plan, rl, signal) {
|
|
386
|
+
export async function applyInteractivePrompts(plan, rl, signal, supplied = {}) {
|
|
353
387
|
const remoteNames = plan.remotes.map(r => r.name);
|
|
354
388
|
const current = plan.next;
|
|
355
389
|
console.log('');
|
|
@@ -360,28 +394,59 @@ async function applyInteractivePrompts(plan, rl, signal) {
|
|
|
360
394
|
}
|
|
361
395
|
if (plan.remotes.length === 0)
|
|
362
396
|
console.log(' (none)');
|
|
363
|
-
|
|
397
|
+
// #3062 review item 10: a value already supplied by flag is never
|
|
398
|
+
// re-asked — only the fields `supplied` leaves `undefined` prompt here.
|
|
399
|
+
// `??`'s right-hand side is only evaluated when the left is nullish, so
|
|
400
|
+
// a supplied value skips the corresponding `ask*` call entirely.
|
|
401
|
+
const primary = supplied.primary ?? (remoteNames.length > 0
|
|
364
402
|
? await askChoice(rl, `Primary remote [${current.remotes?.primary}]: `, remoteNames, current.remotes?.primary, signal)
|
|
365
|
-
: await askString(rl, `Primary remote [${current.remotes?.primary}]: `, current.remotes?.primary ?? 'origin', signal);
|
|
366
|
-
const issueProvider =
|
|
403
|
+
: await askString(rl, `Primary remote [${current.remotes?.primary}]: `, current.remotes?.primary ?? 'origin', signal));
|
|
404
|
+
const issueProvider = supplied.issueProvider
|
|
405
|
+
?? await askChoice(rl, `Issue provider (${ISSUE_PROVIDERS.join('/')}): `, ISSUE_PROVIDERS, plan.issueProvider, signal);
|
|
367
406
|
const issueTracker = issueProvider === 'local'
|
|
368
407
|
? 'local'
|
|
369
|
-
: remoteNames.length > 0
|
|
408
|
+
: supplied.issueTracker ?? (remoteNames.length > 0
|
|
370
409
|
? await askChoice(rl, `Issue tracker remote [${current.remotes?.issue_tracker}]: `, remoteNames, current.remotes?.issue_tracker, signal)
|
|
371
|
-
: await askString(rl, `Issue tracker remote [${current.remotes?.issue_tracker}]: `, current.remotes?.issue_tracker ?? primary, signal);
|
|
372
|
-
const ci = remoteNames.length > 0
|
|
410
|
+
: await askString(rl, `Issue tracker remote [${current.remotes?.issue_tracker}]: `, current.remotes?.issue_tracker ?? primary, signal));
|
|
411
|
+
const ci = supplied.ci ?? (remoteNames.length > 0
|
|
373
412
|
? await askChoice(rl, `CI remote [${current.remotes?.ci}]: `, remoteNames, current.remotes?.ci, signal)
|
|
374
|
-
: await askString(rl, `CI remote [${current.remotes?.ci}]: `, current.remotes?.ci ?? primary, signal);
|
|
375
|
-
const deliveryMode =
|
|
376
|
-
|
|
377
|
-
const
|
|
378
|
-
|
|
379
|
-
const
|
|
380
|
-
|
|
381
|
-
const
|
|
413
|
+
: await askString(rl, `CI remote [${current.remotes?.ci}]: `, current.remotes?.ci ?? primary, signal));
|
|
414
|
+
const deliveryMode = supplied.deliveryMode
|
|
415
|
+
?? await askChoice(rl, `Delivery mode (${DELIVERY_MODES.join('/')}): `, DELIVERY_MODES, current.delivery?.mode, signal);
|
|
416
|
+
const defaultBranch = supplied.defaultBranch
|
|
417
|
+
?? await askString(rl, `Default branch [${current.delivery?.default_branch}]: `, current.delivery?.default_branch ?? 'main', signal);
|
|
418
|
+
const requireCiGreen = supplied.requireCiGreen
|
|
419
|
+
?? await askYesNo(rl, 'Require CI green before done? [Y/n]: ', current.delivery?.require_ci_green ?? true, signal);
|
|
420
|
+
const autoCloseIssues = supplied.autoCloseIssues
|
|
421
|
+
?? await askYesNo(rl, 'Auto-close linked issues from delivery? [Y/n]: ', current.delivery?.auto_close_issues ?? true, signal);
|
|
422
|
+
const forcePushPolicy = supplied.forcePushPolicy
|
|
423
|
+
?? await askChoice(rl, `Force-push policy (${FORCE_PUSH_POLICIES.join('/')}): `, FORCE_PUSH_POLICIES, current.delivery?.force_push_policy, signal);
|
|
424
|
+
const requireSignedCommits = supplied.requireSignedCommits
|
|
425
|
+
?? await askYesNo(rl, 'Require signed delivery commits? [y/N]: ', current.delivery?.require_signed_commits ?? false, signal);
|
|
426
|
+
const providers = supplied.providers
|
|
427
|
+
?? parseProviders(await askString(rl, `Providers [${current.providers.join(',')}]: `, current.providers.join(','), signal), current.providers);
|
|
428
|
+
// #3050: offer direct worker dispatch, only when there is something to
|
|
429
|
+
// offer (two or more worker CLIs detected, dispatch not already on or
|
|
430
|
+
// declined), and only in a context where the answer could actually be
|
|
431
|
+
// acted on: CI and dry-run never prompt (review item 5) — an unattended
|
|
432
|
+
// CI run could otherwise stall on a prompt timeout, and a dry-run would
|
|
433
|
+
// ask a question whose answer is never written. Both cases fall back to
|
|
434
|
+
// the informational hint at the end of `execute()` instead (via
|
|
435
|
+
// `plan.workerDispatchOffer.shouldOffer` staying true on the rebuilt plan).
|
|
436
|
+
// Strict/distinguishable consent (review items 2 and 4): only an actually
|
|
437
|
+
// answered "no" is recorded as a persistent decline. An explicit
|
|
438
|
+
// `--worker-dispatch` already answers this directly (#3062) — skip the
|
|
439
|
+
// question the same way as every other supplied field.
|
|
440
|
+
let workerDispatchChoice = supplied.workerDispatchChoice;
|
|
441
|
+
if (!workerDispatchChoice && plan.workerDispatchOffer.shouldOffer && process.env.CI !== 'true' && !supplied.dryRun) {
|
|
442
|
+
console.log('');
|
|
443
|
+
const outcome = await askConsent(rl, workerDispatchOfferQuestion(plan.workerDispatchOffer.detected), undefined, signal);
|
|
444
|
+
if (outcome.kind === 'answered')
|
|
445
|
+
workerDispatchChoice = outcome.value ? 'enable' : 'decline';
|
|
446
|
+
}
|
|
382
447
|
return {
|
|
383
448
|
projectDir: plan.projectDir,
|
|
384
|
-
providers
|
|
449
|
+
providers,
|
|
385
450
|
primary,
|
|
386
451
|
issueProvider,
|
|
387
452
|
issueTracker,
|
|
@@ -392,6 +457,7 @@ async function applyInteractivePrompts(plan, rl, signal) {
|
|
|
392
457
|
autoCloseIssues,
|
|
393
458
|
forcePushPolicy,
|
|
394
459
|
requireSignedCommits,
|
|
460
|
+
...(workerDispatchChoice ? { workerDispatchChoice } : {}),
|
|
395
461
|
};
|
|
396
462
|
}
|
|
397
463
|
function printPlan(plan) {
|
|
@@ -420,12 +486,32 @@ export const setupHandler = {
|
|
|
420
486
|
}
|
|
421
487
|
const args = subcommand === 'project' ? ctx.args.slice(1) : ctx.args;
|
|
422
488
|
const options = parseSetupProjectOptions({ ...ctx, args });
|
|
489
|
+
// #3062 review item 12: the one shared interactive decision (TTY, TERM,
|
|
490
|
+
// CI, DEBIAN_FRONTEND) — this used to check only `process.stdin.isTTY &&
|
|
491
|
+
// process.stdout.isTTY`, so a CI job with a pseudo-terminal could still
|
|
492
|
+
// reach these prompts.
|
|
493
|
+
const canPrompt = isInteractive(options.nonInteractive);
|
|
494
|
+
// #3062 second review, finding 6: refuse before any child process runs
|
|
495
|
+
// — `buildSetupProjectPlan()` below makes four `git` calls on its own,
|
|
496
|
+
// and this exact refusal used to happen only after building (and
|
|
497
|
+
// printing) the full plan. A write that can never be confirmed (no
|
|
498
|
+
// `--yes`, no terminal, not a dry run) is known from `options` alone.
|
|
499
|
+
if (!options.dryRun && !options.yes && !canPrompt) {
|
|
500
|
+
throw new AiwgError({
|
|
501
|
+
code: 'ERR_NON_INTERACTIVE_CONFIRMATION_REQUIRED',
|
|
502
|
+
message: 'Refusing to write project setup in a non-interactive context without --yes.',
|
|
503
|
+
hint: 'Re-run with `aiwg setup project --yes` or use `--dry-run` to preview only.',
|
|
504
|
+
exitCode: EXIT_CODES.USAGE,
|
|
505
|
+
});
|
|
506
|
+
}
|
|
423
507
|
let plan = await buildSetupProjectPlan(options);
|
|
424
|
-
const canPrompt = !options.nonInteractive && process.stdin.isTTY && process.stdout.isTTY;
|
|
425
508
|
if (!options.yes && canPrompt) {
|
|
426
509
|
const rl = createPromptInterface();
|
|
427
510
|
try {
|
|
428
|
-
|
|
511
|
+
// #3062 review item 10: every field already supplied by flag is
|
|
512
|
+
// passed straight through so it is never re-asked — only genuinely
|
|
513
|
+
// missing ones prompt.
|
|
514
|
+
const prompted = await applyInteractivePrompts(plan, rl, ctx.signal, options);
|
|
429
515
|
plan = await buildSetupProjectPlan({ ...options, ...prompted });
|
|
430
516
|
}
|
|
431
517
|
finally {
|
|
@@ -445,6 +531,19 @@ export const setupHandler = {
|
|
|
445
531
|
exitCode: EXIT_CODES.CONFIG,
|
|
446
532
|
});
|
|
447
533
|
}
|
|
534
|
+
// #3050: non-interactive/--yes/CI/dry-run runs never prompt — the
|
|
535
|
+
// question above already applied a choice when it was asked (which
|
|
536
|
+
// clears `shouldOffer` on the rebuilt plan), so `shouldOffer` is only
|
|
537
|
+
// ever still true here when nothing asked. In `--json` mode the offer
|
|
538
|
+
// is structured data on the JSON object, never a separate
|
|
539
|
+
// human-readable line — a stray print after the JSON would make
|
|
540
|
+
// `setup project --json` unparseable (review item 6).
|
|
541
|
+
// #3062: an explicit `--worker-dispatch` argument clears `shouldOffer`
|
|
542
|
+
// the same way an answered prompt does, so `applied_choice` (alongside
|
|
543
|
+
// the top-level `dry_run` flag) is how `--json --dry-run` reports what
|
|
544
|
+
// the argument would have written — `previewBlocks()` deliberately
|
|
545
|
+
// excludes `parallelism`/`setup` (an unrelated, pre-existing contract),
|
|
546
|
+
// so this field carries that information instead of widening it.
|
|
448
547
|
if (options.json) {
|
|
449
548
|
console.log(JSON.stringify({
|
|
450
549
|
project_dir: plan.projectDir,
|
|
@@ -453,21 +552,22 @@ export const setupHandler = {
|
|
|
453
552
|
issue_provider: plan.issueProvider,
|
|
454
553
|
warnings: plan.warnings,
|
|
455
554
|
config: previewBlocks(plan.next),
|
|
555
|
+
worker_dispatch_offer: plan.workerDispatchOffer.shouldOffer
|
|
556
|
+
? { should_offer: true, detected: plan.workerDispatchOffer.detected,
|
|
557
|
+
hint: workerDispatchOfferHintWithArg(plan.workerDispatchOffer.detected) }
|
|
558
|
+
: { should_offer: false, ...(options.workerDispatchChoice ? { applied_choice: options.workerDispatchChoice } : {}) },
|
|
456
559
|
}, null, 2));
|
|
457
560
|
}
|
|
458
561
|
else {
|
|
459
562
|
printPlan(plan);
|
|
563
|
+
if (plan.workerDispatchOffer.shouldOffer) {
|
|
564
|
+
ui.info(workerDispatchOfferHintWithArg(plan.workerDispatchOffer.detected));
|
|
565
|
+
}
|
|
460
566
|
}
|
|
461
567
|
if (options.dryRun)
|
|
462
568
|
return { exitCode: 0 };
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
code: 'ERR_NON_INTERACTIVE_CONFIRMATION_REQUIRED',
|
|
466
|
-
message: 'Refusing to write project setup in a non-interactive context without --yes.',
|
|
467
|
-
hint: 'Re-run with `aiwg setup project --yes` or use `--dry-run` to preview only.',
|
|
468
|
-
exitCode: EXIT_CODES.USAGE,
|
|
469
|
-
});
|
|
470
|
-
}
|
|
569
|
+
// The `!options.yes && !canPrompt` refusal above (review finding 6)
|
|
570
|
+
// already covers this exact condition before any git call ran.
|
|
471
571
|
if (!options.yes && canPrompt) {
|
|
472
572
|
const rl = createPromptInterface();
|
|
473
573
|
try {
|
|
@@ -699,7 +699,9 @@ export const removeHandler = {
|
|
|
699
699
|
// adapter validates receipt hashes and preserves operator modifications.
|
|
700
700
|
const nativeRemovalProvider = parseRemoveProvider(ctx.args);
|
|
701
701
|
if (firstRemovePositional(ctx.args) === 'omp' && nativeRemovalProvider.provider === 'omp') {
|
|
702
|
-
|
|
702
|
+
// Loaded from the package root, not by a path relative to this file: the
|
|
703
|
+
// compiled handler sits one directory deeper than its source.
|
|
704
|
+
const { uninstall } = (await import(pathToFileURL(path.join(await getFrameworkRoot(), 'tools/agents/providers/omp.mjs')).href));
|
|
703
705
|
const { getProjectDir } = await import('../../config/aiwg-config.js');
|
|
704
706
|
const dryRun = ctx.args.includes('--dry-run');
|
|
705
707
|
const scope = ctx.args.includes('--user') || isScopeUser(ctx.args) ? 'user' : 'project';
|
|
@@ -16,15 +16,25 @@ import { join, basename, resolve } from 'node:path';
|
|
|
16
16
|
import { pathToFileURL } from 'node:url';
|
|
17
17
|
import { projectAiwgPath } from '../../config/project-artifacts.js';
|
|
18
18
|
import { normalizeProviderDefinitionId } from '../../providers/provider-definitions.js';
|
|
19
|
+
import { isSessionProviderResolution, resolveActiveProvider } from '../provider-resolution.js';
|
|
19
20
|
// ── Provider Detection ───────────────────────────────────────
|
|
20
|
-
function detectProvider(args) {
|
|
21
|
+
async function detectProvider(args, cwd) {
|
|
21
22
|
const providerFlag = parseFlag(args, '--provider');
|
|
22
23
|
if (providerFlag)
|
|
23
24
|
return normalizeProviderDefinitionId(providerFlag) ?? providerFlag;
|
|
24
|
-
//
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
25
|
+
// Resolved through the same shared provider-resolution path `runtime-info`
|
|
26
|
+
// and `steward capabilities` use (`resolveActiveProvider` -> the `claude`
|
|
27
|
+
// provider's `detection.env` markers in provider-definitions.ts) so none
|
|
28
|
+
// of them disagree about which provider the current session is running
|
|
29
|
+
// under (#2623, #2627, #3054). The ad-hoc `CLAUDE_CODE_VERSION`/
|
|
30
|
+
// `ANTHROPIC_API_KEY` check this replaced was stale (neither is set by
|
|
31
|
+
// current Claude Code) and `ANTHROPIC_API_KEY` was a false positive for
|
|
32
|
+
// any non-Claude session with that key set.
|
|
33
|
+
// Only a session signal counts. Native team dispatch needs the command to
|
|
34
|
+
// be running inside that harness; project configuration alone does not
|
|
35
|
+
// establish that.
|
|
36
|
+
const resolution = await resolveActiveProvider({ cwd });
|
|
37
|
+
return isSessionProviderResolution(resolution) ? (resolution.provider ?? 'unknown') : 'unknown';
|
|
28
38
|
}
|
|
29
39
|
function isNativeTeamsProvider(provider) {
|
|
30
40
|
// Only Claude Code has native agent team support
|
|
@@ -154,7 +164,7 @@ async function teamRunOmp(ctx) {
|
|
|
154
164
|
}
|
|
155
165
|
}
|
|
156
166
|
async function teamRun(ctx) {
|
|
157
|
-
if (['omp', 'oh-my-pi'].includes(detectProvider(ctx.args)))
|
|
167
|
+
if (['omp', 'oh-my-pi'].includes(await detectProvider(ctx.args, ctx.cwd)))
|
|
158
168
|
return teamRunOmp(ctx);
|
|
159
169
|
const positional = getPositionalArgs(ctx.args);
|
|
160
170
|
const slug = positional[0];
|
|
@@ -162,7 +172,7 @@ async function teamRun(ctx) {
|
|
|
162
172
|
ui.error('Usage: aiwg team run <name> [--provider <p>] [--objective "<text>"]');
|
|
163
173
|
return { exitCode: 1 };
|
|
164
174
|
}
|
|
165
|
-
const provider = detectProvider(ctx.args);
|
|
175
|
+
const provider = await detectProvider(ctx.args, ctx.cwd);
|
|
166
176
|
const objective = parseFlag(ctx.args, '--objective') || `Execute ${slug} team workflow`;
|
|
167
177
|
const team = await loadTeam(slug, ctx.frameworkRoot, ctx.cwd);
|
|
168
178
|
if (!team) {
|
|
@@ -221,7 +231,7 @@ async function teamRun(ctx) {
|
|
|
221
231
|
// ── Subcommand: list ─────────────────────────────────────────
|
|
222
232
|
async function teamList(ctx) {
|
|
223
233
|
const json = hasFlag(ctx.args, '--json');
|
|
224
|
-
const provider = detectProvider(ctx.args);
|
|
234
|
+
const provider = await detectProvider(ctx.args, ctx.cwd);
|
|
225
235
|
const teams = await listAllTeams(ctx.frameworkRoot, ctx.cwd);
|
|
226
236
|
if (json) {
|
|
227
237
|
console.log(JSON.stringify(teams.map(t => ({
|
|
@@ -145,6 +145,11 @@ const MODEL_OVERRIDE_VALUE_FLAGS = new Set([
|
|
|
145
145
|
'--model', '--reasoning-model', '--coding-model', '--efficiency-model', '--model-tier',
|
|
146
146
|
]);
|
|
147
147
|
const MODEL_DEPLOY_BOOLEAN_FLAGS = new Set(['--save', '--save-user']);
|
|
148
|
+
/** Forward explicit user scope to every provider deploy subprocess. */
|
|
149
|
+
export function collectUseScopeDeployArgs(args) {
|
|
150
|
+
const scope = detectScope(args);
|
|
151
|
+
return scope === 'user' || args.includes('--user') ? ['--scope', 'user'] : [];
|
|
152
|
+
}
|
|
148
153
|
export function collectUseModelDeployArgs(args) {
|
|
149
154
|
const forwarded = [];
|
|
150
155
|
for (let i = 0; i < args.length; i++) {
|
|
@@ -1291,6 +1296,7 @@ async function deployOneProjectLocalBundle(opts) {
|
|
|
1291
1296
|
'--deploy-version', projectLocalDeployVersion(bundle),
|
|
1292
1297
|
...modelArgs,
|
|
1293
1298
|
];
|
|
1299
|
+
args.push(...collectUseScopeDeployArgs(ctx.args));
|
|
1294
1300
|
if (dryRun)
|
|
1295
1301
|
args.push('--dry-run');
|
|
1296
1302
|
if (verbose)
|
|
@@ -2031,6 +2037,7 @@ async function deploySourceDirectory(opts) {
|
|
|
2031
2037
|
'--target', opts.target,
|
|
2032
2038
|
...opts.modelArgs,
|
|
2033
2039
|
];
|
|
2040
|
+
args.push(...collectUseScopeDeployArgs(opts.ctx.args));
|
|
2034
2041
|
if (opts.dryRun)
|
|
2035
2042
|
args.push('--dry-run');
|
|
2036
2043
|
if (opts.verbose)
|
|
@@ -2975,6 +2982,8 @@ export class UseHandler {
|
|
|
2975
2982
|
}
|
|
2976
2983
|
const runner = createScriptRunner(frameworkRoot);
|
|
2977
2984
|
const addonBaseArgs = ['--deploy-commands', '--deploy-skills', '--deploy-rules'];
|
|
2985
|
+
if (addonScope === 'user')
|
|
2986
|
+
addonBaseArgs.push('--scope', 'user');
|
|
2978
2987
|
// An explicitly selected upstream addon must be self-contained in the
|
|
2979
2988
|
// project. Unlike a full framework deploy, its standard skills cannot be
|
|
2980
2989
|
// left index-only: the user asked to install this specific bundle and
|
|
@@ -3253,6 +3262,8 @@ export class UseHandler {
|
|
|
3253
3262
|
+ ` (active: ${process.env.AIWG_USER_REGISTRY_PATH})`);
|
|
3254
3263
|
}
|
|
3255
3264
|
}
|
|
3265
|
+
if (scope === 'user' && !deployArgs.includes('--scope'))
|
|
3266
|
+
deployArgs.push('--scope', 'user');
|
|
3256
3267
|
const filteredArgs = deployArgs.filter(a => a !== '--no-utils' && a !== '--no-project-local' && a !== '--ci-hooks-enabled' && a !== '--force' && a !== '--skip-conflicts' && a !== '--no-harness-agents');
|
|
3257
3268
|
const deployFilteredArgs = removeFlagWithOptionalValue(filteredArgs, '--harness-agents');
|
|
3258
3269
|
// Pass --quiet to suppress deploy-agents.mjs header/footer in default mode (#460)
|
|
@@ -3430,6 +3441,8 @@ export class UseHandler {
|
|
|
3430
3441
|
}
|
|
3431
3442
|
// Build common args for addon deployments (inherit provider and target)
|
|
3432
3443
|
const addonBaseArgs = ['--deploy-commands', '--deploy-skills', '--deploy-rules'];
|
|
3444
|
+
if (scope === 'user')
|
|
3445
|
+
addonBaseArgs.push('--scope', 'user');
|
|
3433
3446
|
if (bulkKernelOnly)
|
|
3434
3447
|
addonBaseArgs.push('--kernel-only');
|
|
3435
3448
|
addonBaseArgs.push(...modelDeployArgs);
|
|
@@ -17,6 +17,7 @@ import { getFrameworkRoot } from '../../channel/manager.mjs';
|
|
|
17
17
|
import { updateInstallation } from '../../update/service.mjs';
|
|
18
18
|
import { useHandler as useFrameworkHandler } from './use.js';
|
|
19
19
|
import { projectAiwgPath } from '../../config/project-artifacts.js';
|
|
20
|
+
import { argsTerminatorIndex, hasPositionalOrSwitch, requireFlagValue, valueTokenIndexes } from '../flag-parsing.js';
|
|
20
21
|
import { checkCollisions, } from '../../smiths/skillsmith/collision-detector.js';
|
|
21
22
|
import { renderUseDeploymentResult, verifyConfiguredDeployments, } from '../services/deployment-verification.js';
|
|
22
23
|
/**
|
|
@@ -326,6 +327,62 @@ export const validateMetadataHandler = {
|
|
|
326
327
|
return result;
|
|
327
328
|
},
|
|
328
329
|
};
|
|
330
|
+
/**
|
|
331
|
+
* A directory is a deployed skill only if it carries a SKILL.md manifest —
|
|
332
|
+
* the same marker `isOwnedByNamespace()` in collision-detector.ts reads for
|
|
333
|
+
* ownership. Harness container/bucket directories (e.g. Claude Code's own
|
|
334
|
+
* `.claude/skills/synced/` sync storage, which holds UUID-named dirs with
|
|
335
|
+
* `manifest.json`, not SKILL.md) have no manifest of their own and must
|
|
336
|
+
* never be reported as a colliding skill (#278).
|
|
337
|
+
*
|
|
338
|
+
* A container directory without its own SKILL.md is still descended exactly
|
|
339
|
+
* one level: a real skill nested inside it (`<container>/<skill>/SKILL.md`)
|
|
340
|
+
* is reported as `<container>/<skill>`, while the container itself never is.
|
|
341
|
+
*/
|
|
342
|
+
async function hasSkillManifest(dirPath) {
|
|
343
|
+
try {
|
|
344
|
+
await fsp.access(path.join(dirPath, 'SKILL.md'));
|
|
345
|
+
return true;
|
|
346
|
+
}
|
|
347
|
+
catch {
|
|
348
|
+
return false;
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
async function collectDeployedSkillDirNames(skillsDir) {
|
|
352
|
+
let topEntries;
|
|
353
|
+
try {
|
|
354
|
+
topEntries = await fsp.readdir(skillsDir, { withFileTypes: true });
|
|
355
|
+
}
|
|
356
|
+
catch {
|
|
357
|
+
return [];
|
|
358
|
+
}
|
|
359
|
+
const names = [];
|
|
360
|
+
for (const entry of topEntries) {
|
|
361
|
+
if (!entry.isDirectory())
|
|
362
|
+
continue;
|
|
363
|
+
const dirPath = path.join(skillsDir, entry.name);
|
|
364
|
+
if (await hasSkillManifest(dirPath)) {
|
|
365
|
+
names.push(entry.name);
|
|
366
|
+
continue;
|
|
367
|
+
}
|
|
368
|
+
let nestedEntries;
|
|
369
|
+
try {
|
|
370
|
+
nestedEntries = await fsp.readdir(dirPath, { withFileTypes: true });
|
|
371
|
+
}
|
|
372
|
+
catch {
|
|
373
|
+
continue;
|
|
374
|
+
}
|
|
375
|
+
for (const nested of nestedEntries) {
|
|
376
|
+
if (!nested.isDirectory())
|
|
377
|
+
continue;
|
|
378
|
+
const nestedPath = path.join(dirPath, nested.name);
|
|
379
|
+
if (await hasSkillManifest(nestedPath)) {
|
|
380
|
+
names.push(path.join(entry.name, nested.name));
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
return names;
|
|
385
|
+
}
|
|
329
386
|
/**
|
|
330
387
|
* #1156 Phase 1 — Validate the per-user registry at `~/.aiwg/installed.json`.
|
|
331
388
|
*
|
|
@@ -450,8 +507,9 @@ Options:
|
|
|
450
507
|
--bundle <name> Limit deployment verification to one bundle
|
|
451
508
|
--scope <scope> Verification scope: project or user
|
|
452
509
|
--user Alias for --scope user
|
|
510
|
+
--probe-workers Run bounded worker CLI version probes (default: config-only, no subprocess)
|
|
453
511
|
--project-local Include project-local artifact diagnostics
|
|
454
|
-
--json Emit machine-readable deployment verification
|
|
512
|
+
--json Emit machine-readable output (with --probe-workers: one worker-probe record per CLI; with --deployment: deployment verification)
|
|
455
513
|
--verbose, -v Include verbose diagnostics
|
|
456
514
|
--help, -h Show this help
|
|
457
515
|
`;
|
|
@@ -477,28 +535,55 @@ export const doctorHandler = {
|
|
|
477
535
|
return { exitCode: 0, message: DOCTOR_HELP, rawOutput: true };
|
|
478
536
|
},
|
|
479
537
|
async execute(ctx) {
|
|
480
|
-
|
|
538
|
+
// Validate before any early-return mode: deployment and user-scope doctor
|
|
539
|
+
// never invoke doctor.mjs, so they must enforce the same flag contract.
|
|
540
|
+
const valueFlags = ['--provider', '--bundle', '--scope', '--context-baseline', '--context-budget-tokens'];
|
|
541
|
+
const knownFlags = new Set([
|
|
542
|
+
...valueFlags, '--all-providers', '--probe-workers', '--no-budget-check', '--strict-context', '--json',
|
|
543
|
+
'--deployment', '--user', '--verbose', '-v', '--project-local', '--quiet', '--help', '-h',
|
|
544
|
+
]);
|
|
545
|
+
const hasSwitch = (name) => hasPositionalOrSwitch(ctx.args, name, valueFlags);
|
|
546
|
+
const values = new Map();
|
|
547
|
+
try {
|
|
548
|
+
for (const flag of valueFlags) {
|
|
549
|
+
values.set(flag, requireFlagValue(ctx.args, { rejectEmpty: true, precedence: 'last' }, flag));
|
|
550
|
+
}
|
|
551
|
+
const consumed = valueTokenIndexes(ctx.args, valueFlags);
|
|
552
|
+
for (let i = 0; i < argsTerminatorIndex(ctx.args); i += 1) {
|
|
553
|
+
const token = ctx.args[i];
|
|
554
|
+
if (consumed.has(i) || !token.startsWith('-'))
|
|
555
|
+
continue;
|
|
556
|
+
const name = token.split('=')[0];
|
|
557
|
+
if (!knownFlags.has(name) || (token.includes('=') && !valueFlags.includes(name))) {
|
|
558
|
+
throw new Error(`Unknown flag: ${name}`);
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
if (hasSwitch('--json') && !hasSwitch('--probe-workers') && !hasSwitch('--deployment')) {
|
|
562
|
+
throw new Error('--json requires --probe-workers or --deployment');
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
catch (error) {
|
|
566
|
+
return { exitCode: 2, message: `${error instanceof Error ? error.message : String(error)}. See 'aiwg doctor --help'.` };
|
|
567
|
+
}
|
|
568
|
+
if (hasSwitch('--help') || hasSwitch('-h')) {
|
|
481
569
|
return { exitCode: 0, message: DOCTOR_HELP, rawOutput: true };
|
|
482
570
|
}
|
|
483
|
-
if (
|
|
484
|
-
const
|
|
485
|
-
const bundleIndex = ctx.args.indexOf('--bundle');
|
|
486
|
-
const scopeIndex = ctx.args.indexOf('--scope');
|
|
487
|
-
const scopeValue = scopeIndex >= 0 ? ctx.args[scopeIndex + 1] : undefined;
|
|
571
|
+
if (hasSwitch('--deployment')) {
|
|
572
|
+
const scopeValue = values.get('--scope');
|
|
488
573
|
if (scopeValue && scopeValue !== 'project' && scopeValue !== 'user') {
|
|
489
574
|
return { exitCode: 2, message: `Invalid deployment verification scope: ${scopeValue}` };
|
|
490
575
|
}
|
|
491
576
|
const result = await verifyConfiguredDeployments(ctx.cwd || process.cwd(), {
|
|
492
|
-
provider:
|
|
493
|
-
bundle:
|
|
577
|
+
provider: values.get('--provider'),
|
|
578
|
+
bundle: values.get('--bundle'),
|
|
494
579
|
scope: scopeValue,
|
|
495
580
|
}, ctx.frameworkRoot);
|
|
496
581
|
return {
|
|
497
582
|
exitCode: result.exitCode,
|
|
498
|
-
message:
|
|
583
|
+
message: hasSwitch('--json')
|
|
499
584
|
? JSON.stringify(result, null, 2)
|
|
500
585
|
: renderUseDeploymentResult(result),
|
|
501
|
-
rawOutput:
|
|
586
|
+
rawOutput: hasSwitch('--json'),
|
|
502
587
|
};
|
|
503
588
|
}
|
|
504
589
|
// #1156 Phase 1 — `aiwg doctor --scope user` / `aiwg doctor --user`
|
|
@@ -506,15 +591,20 @@ export const doctorHandler = {
|
|
|
506
591
|
// running the project-scope diagnostics. Operators need this to verify
|
|
507
592
|
// their user-scope deployments from any cwd, including shells with no
|
|
508
593
|
// project at all.
|
|
509
|
-
const userScopeRequested =
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
return await runUserScopeDoctor(ctx.args.includes('--verbose') || ctx.args.includes('-v'));
|
|
594
|
+
const userScopeRequested = hasSwitch('--user') || values.get('--scope') === 'user';
|
|
595
|
+
if (userScopeRequested && !hasSwitch('--probe-workers')) {
|
|
596
|
+
return await runUserScopeDoctor(hasSwitch('--verbose') || hasSwitch('-v'));
|
|
513
597
|
}
|
|
514
598
|
const frameworkRoot = await getFrameworkRoot();
|
|
515
599
|
const runner = createScriptRunner(frameworkRoot);
|
|
516
600
|
// Run core doctor diagnostics
|
|
517
601
|
const result = await runner.run('tools/cli/doctor.mjs', ctx.args, { cwd: ctx.cwd });
|
|
602
|
+
// Machine-readable worker probes (#3066): tools/cli/doctor.mjs already
|
|
603
|
+
// printed exactly one JSON document (or a usage error) on its own
|
|
604
|
+
// stdio — every section below writes human-readable text, so returning
|
|
605
|
+
// here keeps stdout to the JSON document alone.
|
|
606
|
+
if (hasSwitch('--json') || result.exitCode === 2)
|
|
607
|
+
return result;
|
|
518
608
|
let agentSkillsFailure = false;
|
|
519
609
|
try {
|
|
520
610
|
const { inspectGrokBuildNative } = await import('../../mcp/grok-build-config.mjs');
|
|
@@ -562,14 +652,7 @@ export const doctorHandler = {
|
|
|
562
652
|
try {
|
|
563
653
|
const projectDir = ctx.cwd || process.cwd();
|
|
564
654
|
const skillsDir = path.join(projectDir, '.claude', 'skills');
|
|
565
|
-
|
|
566
|
-
try {
|
|
567
|
-
const entries = await fsp.readdir(skillsDir, { withFileTypes: true });
|
|
568
|
-
deployedSkillNames = entries.filter(e => e.isDirectory()).map(e => e.name);
|
|
569
|
-
}
|
|
570
|
-
catch {
|
|
571
|
-
// No .claude/skills — nothing to check
|
|
572
|
-
}
|
|
655
|
+
const deployedSkillNames = await collectDeployedSkillDirNames(skillsDir);
|
|
573
656
|
if (deployedSkillNames.length > 0) {
|
|
574
657
|
const collisions = await checkCollisions({
|
|
575
658
|
platform: 'claude',
|