@aiwg/cli 2026.10.1 → 2026.10.4
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 +2 -2
- package/agentic/code/providers/capability-matrix.yaml +270 -0
- package/agentic/code/providers/model-capabilities.v1.json +3183 -24
- package/bin/aiwg.mjs +135 -47
- package/dist/src/a2a/codecs.js +9 -0
- package/dist/src/agents/grounding/knowledge-base.js +54 -6
- package/dist/src/artifacts/browser-export.js +6 -0
- package/dist/src/artifacts/checksum-manifest.js +1 -0
- package/dist/src/artifacts/citation-parser.js +173 -92
- package/dist/src/artifacts/citation-table-schema.js +59 -0
- package/dist/src/artifacts/cli.js +57 -18
- package/dist/src/artifacts/corpus-tools/snapshot.js +1 -1
- package/dist/src/artifacts/corpus-tools/vision-extract.js +107 -15
- package/dist/src/artifacts/corpus-views/ref-parser.js +3 -1
- package/dist/src/artifacts/corpus-views/renderers.js +3 -3
- package/dist/src/artifacts/discovery-eval.js +6 -4
- package/dist/src/artifacts/fortemi-core-query-adapter.js +36 -3
- package/dist/src/artifacts/fortemi-core-sync.js +166 -40
- package/dist/src/artifacts/framework-capabilities.js +171 -0
- package/dist/src/artifacts/framework-index-paths.js +73 -0
- package/dist/src/artifacts/framework-source-digest.js +75 -0
- package/dist/src/artifacts/index-builder.js +285 -131
- package/dist/src/artifacts/index-files.js +132 -18
- package/dist/src/artifacts/index-preparation.js +65 -0
- package/dist/src/artifacts/index-refresh.js +576 -0
- package/dist/src/artifacts/index-worker.mjs +9 -0
- package/dist/src/artifacts/prebuilt-build-lock.js +47 -4
- package/dist/src/artifacts/prebuilt-deployment.js +46 -0
- package/dist/src/artifacts/prebuilt-relations.js +80 -0
- package/dist/src/artifacts/query-engine.js +410 -88
- package/dist/src/artifacts/relations-builder.js +168 -0
- package/dist/src/artifacts/relations.js +148 -0
- package/dist/src/artifacts/repair.js +9 -4
- package/dist/src/artifacts/stats.js +5 -2
- package/dist/src/artifacts/types.js +30 -10
- package/dist/src/artifacts/watcher.js +1 -1
- package/dist/src/auth/credential-store.js +2 -1
- package/dist/src/catalog/cli.d.mts +5 -0
- package/dist/src/channel/manager.d.mts +20 -0
- package/dist/src/channel/manager.mjs +78 -12
- package/dist/src/cli/child-process-result.js +40 -0
- package/dist/src/cli/command-log.js +2 -3
- package/dist/src/cli/env.js +38 -16
- package/dist/src/cli/flag-parsing.js +236 -0
- package/dist/src/cli/handlers/artifacts.js +3 -0
- package/dist/src/cli/handlers/auth.js +3 -0
- package/dist/src/cli/handlers/decision.js +218 -5
- package/dist/src/cli/handlers/diagnose.js +13 -5
- package/dist/src/cli/handlers/effect.js +4 -2
- package/dist/src/cli/handlers/evidence.js +3 -0
- package/dist/src/cli/handlers/feedback.js +14 -5
- package/dist/src/cli/handlers/help.js +1 -0
- package/dist/src/cli/handlers/index.js +3 -1
- package/dist/src/cli/handlers/init.js +248 -30
- package/dist/src/cli/handlers/lint.js +5 -0
- package/dist/src/cli/handlers/mc.js +23 -0
- package/dist/src/cli/handlers/models.js +169 -10
- package/dist/src/cli/handlers/ralph-launcher.js +2 -0
- package/dist/src/cli/handlers/ralph.js +3 -0
- package/dist/src/cli/handlers/refresh.js +28 -0
- package/dist/src/cli/handlers/regenerate.js +14 -0
- package/dist/src/cli/handlers/resource-versions.js +3 -0
- package/dist/src/cli/handlers/rules.js +195 -0
- package/dist/src/cli/handlers/run.js +4 -0
- package/dist/src/cli/handlers/runtime-info.js +25 -11
- package/dist/src/cli/handlers/script-runner.js +19 -8
- package/dist/src/cli/handlers/session.js +406 -62
- package/dist/src/cli/handlers/sessions.js +243 -76
- package/dist/src/cli/handlers/setup-manifest.js +474 -79
- package/dist/src/cli/handlers/setup.js +180 -39
- package/dist/src/cli/handlers/steward.js +11 -0
- package/dist/src/cli/handlers/subcommands.js +60 -3
- package/dist/src/cli/handlers/team.js +18 -8
- package/dist/src/cli/handlers/uhp.js +3 -0
- package/dist/src/cli/handlers/use.js +307 -55
- package/dist/src/cli/handlers/utilities.js +125 -26
- package/dist/src/cli/handlers/version.js +8 -0
- package/dist/src/cli/prompt-utils.js +72 -0
- package/dist/src/cli/provider-resolution.js +11 -0
- package/dist/src/cli/router.js +15 -1
- package/dist/src/cli/services/deployment-verification.js +19 -9
- package/dist/src/cli/services/provider-prepare-coordinator.js +24 -0
- package/dist/src/cli/skill-usage.js +2 -3
- package/dist/src/config/aiwg-config.js +349 -35
- package/dist/src/config/cli.js +256 -23
- package/dist/src/config/decision-config.js +106 -0
- package/dist/src/config/dispatch-thresholds-runtime.d.mts +8 -0
- package/dist/src/config/dispatch-thresholds-runtime.mjs +38 -0
- package/dist/src/config/gitignore.js +50 -0
- package/dist/src/config/index-graph-overrides.js +36 -0
- package/dist/src/config/project-artifacts-health.d.mts +37 -0
- package/dist/src/config/project-artifacts-health.mjs +14 -3
- package/dist/src/config/project-artifacts-runtime.d.mts +16 -0
- package/dist/src/config/user-config-dir.d.mts +9 -0
- package/dist/src/config/user-config.js +38 -11
- package/dist/src/config/user-registry.js +23 -15
- package/dist/src/config/user-state.js +7 -0
- package/dist/src/decision/adapters/jev.js +75 -27
- package/dist/src/decision/ask.js +203 -43
- package/dist/src/decision/context-live-qualification.js +2 -2
- package/dist/src/decision/driver.js +14 -7
- package/dist/src/decision/ensemble/aggregate.js +23 -10
- package/dist/src/decision/ensemble-study/multifact-study.js +6 -2
- package/dist/src/decision/entry.js +25 -6
- package/dist/src/decision/graph-live-qualification.js +1 -1
- package/dist/src/decision/graph-run.js +9 -0
- package/dist/src/decision/heldout/collector.js +15 -14
- package/dist/src/decision/heldout/contract.js +30 -7
- package/dist/src/decision/index.js +1 -0
- package/dist/src/decision/interop.js +60 -8
- package/dist/src/decision/jev-credentials.js +15 -1
- package/dist/src/decision/ledger.js +942 -0
- package/dist/src/decision/offload-activation.js +137 -0
- package/dist/src/decision/offload.js +550 -0
- package/dist/src/decision/qualification/load.js +4 -6
- package/dist/src/decision/savings.js +28 -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/effects/types.js +5 -1
- package/dist/src/effects/verifiers/git.js +2 -1
- package/dist/src/effects/verifiers/tracker.js +26 -14
- package/dist/src/extensions/commands/definitions.js +19 -0
- package/dist/src/extensions/project-local-remove.js +36 -1
- package/dist/src/features/installer.js +2 -2
- package/dist/src/flow/graph-metadata.js +5 -0
- package/dist/src/gates/providers/loader.js +64 -18
- package/dist/src/installation/manager-command.d.mts +18 -0
- package/dist/src/installation/manager-command.mjs +15 -3
- package/dist/src/installation/manager.d.mts +49 -0
- package/dist/src/installation/manager.mjs +95 -5
- package/dist/src/issues/cli.js +4 -4
- package/dist/src/issues/local.js +24 -5
- package/dist/src/jobs/runner.js +1 -1
- package/dist/src/lint/cli.js +27 -1
- package/dist/src/lint/loader.js +2 -1
- package/dist/src/lint/reporters.js +25 -1
- package/dist/src/lint/rule-activation.js +43 -0
- package/dist/src/lint/runner.js +137 -62
- package/dist/src/mcp/adapters/codex-runtime.js +366 -56
- package/dist/src/mcp/claude-install.d.mts +4 -0
- package/dist/src/mcp/claude-install.mjs +41 -0
- package/dist/src/mcp/cli.d.mts +1 -0
- package/dist/src/mcp/cli.mjs +620 -85
- package/dist/src/mcp/config-file.d.mts +28 -0
- package/dist/src/mcp/config-file.mjs +279 -0
- package/dist/src/mcp/config-layers.d.mts +36 -0
- package/dist/src/mcp/config-layers.mjs +348 -0
- package/dist/src/mcp/credentials.d.mts +51 -0
- package/dist/src/mcp/credentials.mjs +484 -0
- package/dist/src/mcp/grok-build-config.d.mts +6 -0
- package/dist/src/mcp/grok-build-config.mjs +17 -27
- package/dist/src/mcp/helpers.mjs +109 -4
- package/dist/src/mcp/muse-native-config.d.mts +13 -0
- package/dist/src/mcp/muse-native-config.mjs +1 -0
- package/dist/src/mcp/omp-config.d.mts +4 -0
- 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 +248 -131
- package/dist/src/mcp/registry.mjs +169 -100
- package/dist/src/mcp/server-config.d.mts +3 -0
- package/dist/src/mcp/server-config.mjs +89 -0
- package/dist/src/mcp/server.mjs +127 -82
- package/dist/src/mcp/test-simple.mjs +2 -1
- package/dist/src/mcp/toml-editor.d.mts +4 -0
- package/dist/src/mcp/toml-string.d.mts +1 -0
- package/dist/src/mcp/toml-string.mjs +9 -0
- package/dist/src/mcp/toml-strip-mcp.d.mts +1 -0
- package/dist/src/mcp/toml-strip-mcp.mjs +135 -0
- package/dist/src/mcp/tool-filters.d.mts +46 -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 +22 -3
- package/dist/src/mcp/tools/discovery.mjs +62 -55
- package/dist/src/mcp/tools/orchestration.mjs +50 -2
- package/dist/src/mcp/tools/subsystems.mjs +39 -8
- package/dist/src/mcp/tools/worker-invoke.mjs +2856 -0
- package/dist/src/mcp/tools/worker-shell-control.mjs +289 -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 +647 -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 +76 -14
- package/dist/src/models/grok-build-models.js +93 -41
- package/dist/src/models/model-capabilities.v1.json +3183 -24
- package/dist/src/models/model-discovery.js +931 -67
- package/dist/src/models/provider-policy.js +719 -11
- package/dist/src/models/qualification-digest.d.mts +2 -0
- package/dist/src/models/qualification-digest.mjs +17 -0
- package/dist/src/models/resolver.js +261 -0
- package/dist/src/models/worker-direct-readiness.js +550 -0
- package/dist/src/models/worker-dispatch-offer.js +169 -0
- package/dist/src/models/worker-health-readiness.js +144 -0
- package/dist/src/models/worker-installation.d.mts +39 -0
- package/dist/src/models/worker-installation.mjs +986 -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.d.mts +84 -0
- package/dist/src/process/bounded-child-process.mjs +430 -0
- package/dist/src/process/cli-command.d.mts +16 -0
- package/dist/src/process/cli-command.mjs +75 -0
- package/dist/src/process/signal-child.d.mts +6 -0
- package/dist/src/process/signal-child.mjs +7 -0
- package/dist/src/providers/capability-matrix.js +8 -0
- package/dist/src/providers/capability-matrix.yaml +270 -0
- package/dist/src/providers/deployed-skill-roots.js +20 -0
- package/dist/src/providers/grok-build-acp.js +3 -2
- package/dist/src/providers/grok-build-headless.js +53 -23
- package/dist/src/providers/grok-build-inspect.js +1 -0
- package/dist/src/providers/omp-agent.d.mts +8 -0
- package/dist/src/providers/omp-diagnostics.d.mts +1 -0
- package/dist/src/providers/omp-paths.d.mts +13 -0
- package/dist/src/providers/provider-definitions.js +9 -1
- package/dist/src/providers/provider-definitions.mjs +9 -2
- package/dist/src/providers/skill-ownership.d.mts +9 -0
- package/dist/src/providers/skill-ownership.mjs +88 -0
- package/dist/src/rules/activation.d.mts +21 -0
- package/dist/src/rules/activation.mjs +62 -0
- package/dist/src/rules/inline-budget.d.mts +26 -0
- package/dist/src/rules/inline-budget.mjs +66 -0
- package/dist/src/rules/inventory.js +129 -0
- package/dist/src/rules/pin-state.js +73 -0
- package/dist/src/rules/pins.d.mts +14 -0
- package/dist/src/rules/pins.mjs +112 -0
- package/dist/src/rules/rule-frontmatter.schema.json +197 -0
- 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 +47 -12
- package/dist/src/serve/shared-host-scheduler.js +2 -0
- package/dist/src/serve/worker-invocation.js +558 -0
- package/dist/src/serve/worker-stdin-frame.d.mts +10 -0
- package/dist/src/serve/worker-stdin-frame.mjs +107 -0
- package/dist/src/sessions/adapter-registry.js +49 -0
- package/dist/src/sessions/adapters/claude.js +39 -54
- package/dist/src/sessions/adapters/codex.js +82 -67
- package/dist/src/sessions/adapters/copilot.js +4 -4
- package/dist/src/sessions/adapters/cursor.js +40 -57
- package/dist/src/sessions/adapters/deepseek-harness.js +15 -12
- package/dist/src/sessions/adapters/factory.js +31 -27
- package/dist/src/sessions/adapters/generic.js +43 -9
- package/dist/src/sessions/adapters/grok-build.js +11 -11
- package/dist/src/sessions/adapters/grokbot-locator.js +111 -0
- package/dist/src/sessions/adapters/grokbot.js +434 -24
- package/dist/src/sessions/adapters/hermes.js +27 -29
- package/dist/src/sessions/adapters/muse-workspace-scan.js +128 -0
- package/dist/src/sessions/adapters/muse.js +200 -104
- package/dist/src/sessions/adapters/omp.js +28 -14
- package/dist/src/sessions/adapters/openclaw.js +89 -79
- package/dist/src/sessions/adapters/opencode.js +27 -24
- package/dist/src/sessions/adapters/openhuman.js +18 -60
- package/dist/src/sessions/adapters/pi.js +30 -14
- package/dist/src/sessions/adapters/warp.js +3 -3
- package/dist/src/sessions/adapters/windsurf.js +19 -48
- package/dist/src/sessions/batch-contracts.js +25 -5
- package/dist/src/sessions/batch-import.js +72 -51
- package/dist/src/sessions/candidates.js +47 -11
- package/dist/src/sessions/contracts.js +75 -2
- package/dist/src/sessions/coverage-preflight.js +134 -0
- package/dist/src/sessions/discovery.js +3 -0
- package/dist/src/sessions/export-plan.js +4 -3
- package/dist/src/sessions/grok-build-cli.js +67 -5
- package/dist/src/sessions/import-exclusions.js +72 -0
- package/dist/src/sessions/import-lease.js +34 -18
- package/dist/src/sessions/import-limits.js +37 -0
- package/dist/src/sessions/importer.js +84 -46
- package/dist/src/sessions/index.js +2 -0
- package/dist/src/sessions/lifecycle.js +45 -0
- package/dist/src/sessions/policy.js +133 -25
- package/dist/src/sessions/readers.js +106 -53
- package/dist/src/sessions/refresh.js +106 -0
- package/dist/src/sessions/repository.js +199 -63
- package/dist/src/sessions/workspace-discovery.js +315 -88
- package/dist/src/sessions/workspace-matching.js +107 -0
- package/dist/src/skills/adapters/clawhub.js +10 -8
- package/dist/src/skills/adapters/openclaw.js +10 -7
- package/dist/src/skills/importer.js +1 -0
- package/dist/src/smiths/context-pipeline/aiwg-md.js +11 -0
- package/dist/src/smiths/context-pipeline/claude-hook.js +3 -26
- package/dist/src/smiths/context-pipeline/finalization.js +6 -5
- package/dist/src/smiths/context-pipeline/generator.js +21 -4
- package/dist/src/smiths/context-pipeline/parallelism-section.js +195 -14
- 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/mcpsmith/generator.js +1 -1
- package/dist/src/smiths/toolsmith/runtime-discovery.d.mts +10 -0
- package/dist/src/smiths/toolsmith/runtime-discovery.mjs +85 -31
- package/dist/src/storage/backends/fs.js +18 -76
- package/dist/src/storage/backends/logseq.js +68 -7
- package/dist/src/storage/backends/obsidian.js +1 -0
- package/dist/src/storage/fortemi-qualification-receipt.js +2 -2
- package/dist/src/testing/generators/test-code-generator.js +57 -30
- package/dist/src/tracker/forge-http.js +2 -1
- package/dist/src/uhp/client.js +11 -1
- package/dist/src/uhp/mission.js +5 -0
- package/dist/src/update/checker.d.mts +2 -0
- package/dist/src/update/checker.mjs +5 -5
- package/dist/src/update/notifier.mjs +54 -2
- package/dist/src/update/service.d.mts +22 -0
- package/dist/src/utils/contained-filesystem.d.mts +37 -0
- package/dist/src/utils/contained-filesystem.mjs +683 -0
- package/dist/src/utils/temporary-directory.js +36 -0
- package/dist/src/writing/example-generator.js +1 -1
- package/dist/src/writing/fidelity.js +18 -1
- package/package.json +32 -2
- 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 +1072 -0
- package/tools/_resolve-impl.mjs +39 -2
- package/tools/agents/deploy-agents.mjs +64 -23
- package/tools/agents/providers/base.mjs +153 -166
- package/tools/agents/providers/claude.mjs +8 -24
- package/tools/agents/providers/codex.mjs +18 -11
- package/tools/agents/providers/cursor.mjs +19 -18
- package/tools/agents/providers/grok-build.mjs +3 -1
- package/tools/agents/providers/grokbot.mjs +11 -12
- package/tools/agents/providers/hermes.mjs +62 -49
- package/tools/agents/providers/omp.mjs +5 -2
- package/tools/agents/providers/openclaw-translator.mjs +19 -5
- package/tools/agents/providers/openclaw.mjs +9 -4
- package/tools/agents/providers/pi.mjs +5 -0
- package/tools/agents/providers/provider-prepare-channel.mjs +5 -0
- package/tools/agents/providers/provider-prepare-coordinator.mjs +247 -0
- package/tools/agents/providers/provider-prepare-worker.mjs +84 -0
- package/tools/agents/providers/rule-delivery.mjs +1270 -0
- package/tools/agents/providers/warp.mjs +20 -26
- package/tools/cli/decision-offload-report.mjs +40 -0
- package/tools/cli/workspace-status.mjs +862 -0
- package/tools/plugin/package-plugins.mjs +186 -8
- package/tools/plugin/plugin-links.mjs +114 -0
- package/tools/providers/antigravity-transport.mjs +3 -1
- package/tools/providers/deepseek-harness-transport.mjs +360 -0
- package/tools/providers/omp-transport.mjs +194 -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/consecutive-failure-circuit-breaker.mjs +1748 -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/launcher-recovery-evidence.mjs +55 -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 +329 -0
- package/tools/ralph-external/lib/provider-adapter.mjs +748 -0
- package/tools/ralph-external/lib/recovery-probe.mjs +72 -0
- package/tools/ralph-external/lib/session-environment.mjs +28 -0
- package/tools/ralph-external/lib/systemd-client.mjs +49 -0
- package/tools/ralph-external/lib/worker-health.mjs +439 -0
- package/tools/ralph-external/lib/worker-isolation.mjs +717 -0
- package/tools/ralph-external/lib/worker-overflow.mjs +50 -0
- package/tools/ralph-external/process-monitor.mjs +1017 -0
- package/tools/ralph-external/session-launcher.mjs +5057 -0
- package/tools/scripts/resource-preflight.mjs +541 -0
- package/tools/skills/deploy-skills-codex.mjs +2 -1
- package/vscode-extension/schemas/aiwg.config.v1.json +2916 -0
package/dist/src/config/cli.js
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* validate — Validate all config files
|
|
9
9
|
* reset [<key>] — Reset key or all config to defaults
|
|
10
10
|
* path — Print the active config directory path
|
|
11
|
-
* edit
|
|
11
|
+
* edit [--print-path] — Open config in $EDITOR/$VISUAL, or print its path headlessly
|
|
12
12
|
* gitignore — Show/check/fix .gitignore for AIWG runtime dirs
|
|
13
13
|
*
|
|
14
14
|
* Global flags:
|
|
@@ -19,10 +19,36 @@
|
|
|
19
19
|
*/
|
|
20
20
|
import { fileURLToPath } from 'url';
|
|
21
21
|
import path from 'path';
|
|
22
|
+
import { isDeepStrictEqual } from 'node:util';
|
|
23
|
+
import { normalizeProviderDefinitionId } from '../providers/provider-definitions.js';
|
|
22
24
|
import { UserConfig } from './user-config.js';
|
|
23
25
|
import { AiwgError, EXIT_CODES } from '../cli/errors.js';
|
|
26
|
+
import { isInteractive } from '../cli/env.js';
|
|
27
|
+
import { exitCodeForSpawnResult } from '../cli/child-process-result.js';
|
|
24
28
|
import { projectControlPath, resolveProjectAiwgDir } from './project-artifacts.js';
|
|
25
29
|
const _scriptDir = path.dirname(fileURLToPath(import.meta.url));
|
|
30
|
+
function warnStaleParallelism(cfg, projectDir, key, before) {
|
|
31
|
+
if (key !== 'parallelism' && !key.startsWith('parallelism.'))
|
|
32
|
+
return;
|
|
33
|
+
if (isDeepStrictEqual(before, getDottedPath(cfg, key)))
|
|
34
|
+
return;
|
|
35
|
+
const configured = (cfg.providers ?? []).map(normalizeProviderDefinitionId).filter((id) => !!id);
|
|
36
|
+
const recorded = Object.values(cfg.installed ?? {}).flatMap(entry => Object.keys(entry.deployedTo ?? {}))
|
|
37
|
+
.map(normalizeProviderDefinitionId).filter((id) => !!id).sort();
|
|
38
|
+
const providers = [...new Set([...configured, ...recorded])];
|
|
39
|
+
const targets = providers.length ? providers : ['<provider>'];
|
|
40
|
+
const lines = [
|
|
41
|
+
`WARNING: ${key} changed project configuration; generated workspace/provider guidance may now be stale.`,
|
|
42
|
+
`From project root ${path.resolve(projectDir)}, preview and refresh every configured or recorded provider context:`,
|
|
43
|
+
...targets.flatMap(provider => [
|
|
44
|
+
` aiwg regenerate --workspace --provider ${provider} --dry-run`,
|
|
45
|
+
` aiwg regenerate --workspace --provider ${provider}`,
|
|
46
|
+
]),
|
|
47
|
+
' aiwg workspace-context doctor',
|
|
48
|
+
`Shared parallelism defaults and labels use primary provider '${cfg.providers?.[0] ?? 'unknown'}' (providers[0]); --provider selects the adapter/context target.`,
|
|
49
|
+
];
|
|
50
|
+
console.error(lines.join('\n'));
|
|
51
|
+
}
|
|
26
52
|
/**
|
|
27
53
|
* Main CLI entry point for `aiwg config <subcommand> [args]`
|
|
28
54
|
*/
|
|
@@ -63,7 +89,7 @@ export async function main(args) {
|
|
|
63
89
|
handlePath(config);
|
|
64
90
|
break;
|
|
65
91
|
case 'edit':
|
|
66
|
-
await handleEdit(config);
|
|
92
|
+
await handleEdit(config, subArgs);
|
|
67
93
|
break;
|
|
68
94
|
case 'gitignore':
|
|
69
95
|
await handleGitignore(subArgs);
|
|
@@ -86,7 +112,7 @@ export async function main(args) {
|
|
|
86
112
|
}
|
|
87
113
|
async function handleGet(config, args) {
|
|
88
114
|
const isProject = args.includes('--project');
|
|
89
|
-
const positional = args
|
|
115
|
+
const positional = configPositionals(args);
|
|
90
116
|
const key = positional[0];
|
|
91
117
|
if (!key) {
|
|
92
118
|
throw new AiwgError({
|
|
@@ -100,6 +126,11 @@ async function handleGet(config, args) {
|
|
|
100
126
|
await projectConfigGet(key, args);
|
|
101
127
|
return;
|
|
102
128
|
}
|
|
129
|
+
if (key === 'rules' || key.startsWith('rules.')) {
|
|
130
|
+
const { loadRulePinConfigs } = await import('../rules/pins.mjs');
|
|
131
|
+
console.log(JSON.stringify(getDottedPath(loadRulePinConfigs(process.cwd(), undefined, config.getPath()).user, key)) ?? '(not set)');
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
103
134
|
const value = await config.get(key);
|
|
104
135
|
if (value === undefined) {
|
|
105
136
|
console.log(`(not set)`);
|
|
@@ -113,7 +144,7 @@ async function handleGet(config, args) {
|
|
|
113
144
|
}
|
|
114
145
|
async function handleSet(config, args) {
|
|
115
146
|
const isProject = args.includes('--project');
|
|
116
|
-
const positional = args
|
|
147
|
+
const positional = configPositionals(args);
|
|
117
148
|
const key = positional[0];
|
|
118
149
|
const value = positional[1];
|
|
119
150
|
if (!key || value === undefined) {
|
|
@@ -128,6 +159,11 @@ async function handleSet(config, args) {
|
|
|
128
159
|
await projectConfigSet(key, value, args);
|
|
129
160
|
return;
|
|
130
161
|
}
|
|
162
|
+
if (key === 'rules' || key.startsWith('rules.')) {
|
|
163
|
+
await setRuleConfigKey(process.cwd(), 'user', key, value, config.getPath());
|
|
164
|
+
console.log(`Set --user ${key} = ${value}`);
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
131
167
|
await config.set(key, value);
|
|
132
168
|
console.log(`Set ${key} = ${value}`);
|
|
133
169
|
}
|
|
@@ -167,8 +203,14 @@ const ENUM_RULES = {
|
|
|
167
203
|
'security.threatAssessment.mode': ['off', 'audit', 'enforce'],
|
|
168
204
|
'artifact_outputs.canonical': ['aiwg'],
|
|
169
205
|
'artifact_outputs.provider_native': ['disabled', 'explicit-only', 'project-default'],
|
|
206
|
+
// #3050: a setup/onboarding preference, not worker policy — kept out of
|
|
207
|
+
// the frozen `parallelism` contract fragment (B-OG-29). String enum (not
|
|
208
|
+
// boolean) so a future state (e.g. "snoozed") can be added without a
|
|
209
|
+
// breaking schema change.
|
|
210
|
+
'setup.worker_dispatch_offer': ['declined'],
|
|
170
211
|
};
|
|
171
212
|
const BOOLEAN_FIELDS = new Set([
|
|
213
|
+
'decision.enabled',
|
|
172
214
|
'delivery.delete_branch_on_merge',
|
|
173
215
|
'delivery.require_ci_green',
|
|
174
216
|
'delivery.require_signed_commits',
|
|
@@ -178,6 +220,7 @@ const BOOLEAN_FIELDS = new Set([
|
|
|
178
220
|
'telemetry.skill_usage.enabled',
|
|
179
221
|
]);
|
|
180
222
|
const STRING_ARRAY_FIELDS = new Set([
|
|
223
|
+
'providers',
|
|
181
224
|
'remotes.tracker_actor.forbid_actors',
|
|
182
225
|
'remotes.customer_tracker_actor.forbid_actors',
|
|
183
226
|
'command_log.scopes',
|
|
@@ -186,6 +229,10 @@ const STRING_ARRAY_FIELDS = new Set([
|
|
|
186
229
|
// Integer fields with valid range constraints. Validated at `aiwg config set`.
|
|
187
230
|
// Bounds mirror the JSON schema (vscode-extension/schemas/aiwg.config.v1.json).
|
|
188
231
|
const INTEGER_FIELDS = {
|
|
232
|
+
'decision.ask.passes': { min: 1, max: Number.MAX_SAFE_INTEGER },
|
|
233
|
+
'decision.ask.escalation_passes': { min: 1, max: Number.MAX_SAFE_INTEGER },
|
|
234
|
+
'decision.ask.savings.reasoning_tokens': { min: 0, max: 1_000_000 },
|
|
235
|
+
'decision.ask.savings.output_tokens': { min: 0, max: 1_000_000 },
|
|
189
236
|
'parallelism.max_parallel_subagents': { min: 1, max: 50 },
|
|
190
237
|
'parallelism.max_parallel_ralph_loops': { min: 1, max: 20 },
|
|
191
238
|
'parallelism.max_parallel_mc_missions': { min: 1, max: 20 },
|
|
@@ -193,6 +240,7 @@ const INTEGER_FIELDS = {
|
|
|
193
240
|
'telemetry.skill_usage.max_bytes': { min: 1024, max: 104_857_600 },
|
|
194
241
|
};
|
|
195
242
|
async function projectConfigGet(key, args) {
|
|
243
|
+
assertSafeConfigPath(key);
|
|
196
244
|
const { readAiwgConfig, getProjectDir } = await import('./aiwg-config.js');
|
|
197
245
|
const projectDir = getProjectDir(undefined, args);
|
|
198
246
|
const cfg = await readAiwgConfig(projectDir);
|
|
@@ -204,6 +252,21 @@ async function projectConfigGet(key, args) {
|
|
|
204
252
|
exitCode: EXIT_CODES.CONFIG,
|
|
205
253
|
});
|
|
206
254
|
}
|
|
255
|
+
// Model preferences keep their existing file owner. Inspection uses the same
|
|
256
|
+
// merged policy and pre-merge origins as worker resolution, never aiwg.config.
|
|
257
|
+
if (key === 'defaults.workerSelection' || key.startsWith('defaults.workerSelection.')) {
|
|
258
|
+
const { ConfigLoader } = await import('../models/config-loader.js');
|
|
259
|
+
const loader = new ConfigLoader(projectDir);
|
|
260
|
+
const models = await loader.load(cfg.parallelism);
|
|
261
|
+
const value = getDottedPath(models, key);
|
|
262
|
+
const pointer = `/${key.split('.').join('/')}`;
|
|
263
|
+
const origins = Object.fromEntries(Object.entries(models.workerPolicyOrigins ?? {})
|
|
264
|
+
.filter(([field]) => field === pointer || field.startsWith(`${pointer}/`)));
|
|
265
|
+
console.log(args.includes('--origins')
|
|
266
|
+
? JSON.stringify({ value: value ?? null, origins, digests: loader.getInputDigests() }, null, 2)
|
|
267
|
+
: value === undefined ? '(not set)' : JSON.stringify(value, null, 2));
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
207
270
|
const value = getDottedPath(cfg, key);
|
|
208
271
|
if (value === undefined) {
|
|
209
272
|
console.log('(not set)');
|
|
@@ -215,7 +278,16 @@ async function projectConfigGet(key, args) {
|
|
|
215
278
|
console.log(String(value));
|
|
216
279
|
}
|
|
217
280
|
}
|
|
218
|
-
async function projectConfigSet(key, raw, args) {
|
|
281
|
+
async function projectConfigSet(key, raw, args, quiet = false) {
|
|
282
|
+
assertSafeConfigPath(key);
|
|
283
|
+
if (key === 'defaults' || key.startsWith('defaults.')) {
|
|
284
|
+
throw new AiwgError({
|
|
285
|
+
code: 'ERR_INVALID_KEY',
|
|
286
|
+
message: `Project model preferences '${key}' belong in models.json, not aiwg.config.`,
|
|
287
|
+
hint: 'Edit project-root models.json to change defaults, including defaults.workerSelection.',
|
|
288
|
+
exitCode: EXIT_CODES.USAGE,
|
|
289
|
+
});
|
|
290
|
+
}
|
|
219
291
|
const { readAiwgConfig, writeAiwgConfig, getProjectDir, emptyConfig, validateExternalLinks, } = await import('./aiwg-config.js');
|
|
220
292
|
const projectDir = getProjectDir(undefined, args);
|
|
221
293
|
// Validate enum fields before writing. Deprecated spellings normalize forward with a
|
|
@@ -237,6 +309,12 @@ async function projectConfigSet(key, raw, args) {
|
|
|
237
309
|
}
|
|
238
310
|
// Coerce booleans for known boolean fields
|
|
239
311
|
let value = raw;
|
|
312
|
+
if (key === 'rules' || key.startsWith('rules.') || key === 'parallelism' || /^parallelism\.(worker_dispatch|worker_mode|groups|clis|admission|budgets|health|retry|activation)(\.|$)/.test(key)) {
|
|
313
|
+
try {
|
|
314
|
+
value = JSON.parse(raw);
|
|
315
|
+
}
|
|
316
|
+
catch { /* String leaves are validated by the whole-object writer. */ }
|
|
317
|
+
}
|
|
240
318
|
if (/^externalLinks\.[^.]+$/.test(key) || key === 'security.threatAssessment') {
|
|
241
319
|
try {
|
|
242
320
|
value = JSON.parse(raw);
|
|
@@ -267,10 +345,24 @@ async function projectConfigSet(key, raw, args) {
|
|
|
267
345
|
}
|
|
268
346
|
}
|
|
269
347
|
if (STRING_ARRAY_FIELDS.has(key)) {
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
348
|
+
let items = raw.split(',');
|
|
349
|
+
if (raw.trimStart().startsWith('[')) {
|
|
350
|
+
try {
|
|
351
|
+
items = JSON.parse(raw);
|
|
352
|
+
}
|
|
353
|
+
catch {
|
|
354
|
+
items = undefined;
|
|
355
|
+
}
|
|
356
|
+
if (!Array.isArray(items) || items.some(item => typeof item !== 'string')) {
|
|
357
|
+
throw new AiwgError({
|
|
358
|
+
code: 'ERR_INVALID_VALUE',
|
|
359
|
+
message: `${key} must be a comma-separated list or a JSON array of strings`,
|
|
360
|
+
hint: `Try: aiwg config set --project ${key} '["a","b"]' (or a,b)`,
|
|
361
|
+
exitCode: EXIT_CODES.USAGE,
|
|
362
|
+
});
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
value = items.map(item => item.trim()).filter(Boolean);
|
|
274
366
|
if (key === 'command_log.scopes' || key === 'telemetry.skill_usage.scopes') {
|
|
275
367
|
const invalid = value.filter(item => item !== 'project' && item !== 'global');
|
|
276
368
|
if (invalid.length > 0) {
|
|
@@ -287,7 +379,7 @@ async function projectConfigSet(key, raw, args) {
|
|
|
287
379
|
const intRule = INTEGER_FIELDS[key];
|
|
288
380
|
if (intRule) {
|
|
289
381
|
const n = Number(raw);
|
|
290
|
-
if (!Number.isInteger(n) || n < intRule.min || n > intRule.max) {
|
|
382
|
+
if (!raw.trim() || !Number.isInteger(n) || n < intRule.min || n > intRule.max) {
|
|
291
383
|
throw new AiwgError({
|
|
292
384
|
code: 'ERR_INVALID_VALUE',
|
|
293
385
|
message: `${key} must be an integer between ${intRule.min} and ${intRule.max}, got '${raw}'`,
|
|
@@ -297,9 +389,32 @@ async function projectConfigSet(key, raw, args) {
|
|
|
297
389
|
}
|
|
298
390
|
value = n;
|
|
299
391
|
}
|
|
392
|
+
if (key === 'decision.ask.min_agreement' || key === 'decision.ask.agreement_confidence_floor') {
|
|
393
|
+
const n = Number(raw);
|
|
394
|
+
if (!raw.trim() || !Number.isFinite(n) || n < 0 || n > 1) {
|
|
395
|
+
throw new AiwgError({
|
|
396
|
+
code: 'ERR_INVALID_VALUE',
|
|
397
|
+
message: `${key} must be a probability from 0 to 1, got '${raw}'`,
|
|
398
|
+
exitCode: EXIT_CODES.USAGE,
|
|
399
|
+
});
|
|
400
|
+
}
|
|
401
|
+
value = n;
|
|
402
|
+
}
|
|
403
|
+
if (key === 'decision.ask.cost.llm_per_mtok' || key === 'decision.ask.cost.jev_per_mtok') {
|
|
404
|
+
const n = Number(raw);
|
|
405
|
+
if (!raw.trim() || !Number.isFinite(n) || n < 0 || n > 1_000_000) {
|
|
406
|
+
throw new AiwgError({ code: 'ERR_INVALID_VALUE', message: `${key} must be a non-negative number <= 1000000`, exitCode: EXIT_CODES.USAGE });
|
|
407
|
+
}
|
|
408
|
+
value = n;
|
|
409
|
+
}
|
|
300
410
|
// Read-modify-write — preserve unrelated fields. emptyConfig() seeds the
|
|
301
411
|
// base shape when no config file exists yet (e.g. brand-new project).
|
|
302
|
-
const
|
|
412
|
+
const { existsSync, readFileSync } = await import('node:fs');
|
|
413
|
+
const file = projectControlPath(projectDir, 'aiwg.config');
|
|
414
|
+
const cfg = key === 'rules' || key.startsWith('rules.')
|
|
415
|
+
? (existsSync(file) ? JSON.parse(readFileSync(file, 'utf8')) : emptyConfig())
|
|
416
|
+
: (await readAiwgConfig(projectDir)) ?? emptyConfig();
|
|
417
|
+
const before = structuredClone(getDottedPath(cfg, key));
|
|
303
418
|
setDottedPath(cfg, key, value);
|
|
304
419
|
const externalLinkErrors = validateExternalLinks(cfg.externalLinks);
|
|
305
420
|
if (externalLinkErrors.length > 0) {
|
|
@@ -321,7 +436,10 @@ async function projectConfigSet(key, raw, args) {
|
|
|
321
436
|
});
|
|
322
437
|
}
|
|
323
438
|
await writeAiwgConfig(projectDir, cfg);
|
|
324
|
-
|
|
439
|
+
if (!quiet)
|
|
440
|
+
console.log(`Set --project ${key} = ${raw}`);
|
|
441
|
+
if (!quiet)
|
|
442
|
+
warnStaleParallelism(cfg, projectDir, key, before);
|
|
325
443
|
}
|
|
326
444
|
/** Read a dotted path from a nested record. Returns undefined when any segment is missing. */
|
|
327
445
|
function getDottedPath(obj, key) {
|
|
@@ -336,6 +454,7 @@ function getDottedPath(obj, key) {
|
|
|
336
454
|
}
|
|
337
455
|
/** Write a dotted path into a nested record, creating intermediate objects. */
|
|
338
456
|
function setDottedPath(obj, key, value) {
|
|
457
|
+
assertSafeConfigPath(key);
|
|
339
458
|
const parts = key.split('.');
|
|
340
459
|
const last = parts.pop();
|
|
341
460
|
let cur = obj;
|
|
@@ -348,6 +467,25 @@ function setDottedPath(obj, key, value) {
|
|
|
348
467
|
}
|
|
349
468
|
cur[last] = value;
|
|
350
469
|
}
|
|
470
|
+
function assertSafeConfigPath(key) {
|
|
471
|
+
if (key.split('.').some(part => !part || ['__proto__', 'prototype', 'constructor'].includes(part))) {
|
|
472
|
+
throw new AiwgError({ code: 'ERR_INVALID_KEY', message: 'Unsafe configuration path', exitCode: EXIT_CODES.USAGE });
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
/** Scope/inspection flags are not dotted keys, irrespective of flag order. */
|
|
476
|
+
function configPositionals(args) {
|
|
477
|
+
const result = [];
|
|
478
|
+
for (let i = 0; i < args.length; i++) {
|
|
479
|
+
if (['--target', '--prefix'].includes(args[i])) {
|
|
480
|
+
i++;
|
|
481
|
+
continue;
|
|
482
|
+
}
|
|
483
|
+
if (['--project', '--user', '--origins'].includes(args[i]))
|
|
484
|
+
continue;
|
|
485
|
+
result.push(args[i]);
|
|
486
|
+
}
|
|
487
|
+
return result;
|
|
488
|
+
}
|
|
351
489
|
async function handleList(config) {
|
|
352
490
|
const allConfig = await config.list();
|
|
353
491
|
console.log(`Config directory: ${config.getPath()}\n`);
|
|
@@ -368,6 +506,16 @@ async function handleValidate(config, args) {
|
|
|
368
506
|
return;
|
|
369
507
|
}
|
|
370
508
|
const issues = await config.validate();
|
|
509
|
+
try {
|
|
510
|
+
const { loadRulePinConfigs, installedRuleCapabilities, validateRulePinCapabilities } = await import('../rules/pins.mjs');
|
|
511
|
+
const configs = loadRulePinConfigs(process.cwd(), undefined, config.getPath());
|
|
512
|
+
for (const message of validateRulePinCapabilities(configs.user.rules, installedRuleCapabilities(configs), configs.project.rules?.pin_groups)) {
|
|
513
|
+
issues.push({ file: 'aiwg.config', severity: 'error', message });
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
catch (error) {
|
|
517
|
+
issues.push({ file: 'aiwg.config', severity: 'error', message: error instanceof Error ? error.message : String(error) });
|
|
518
|
+
}
|
|
371
519
|
console.log(`Config directory: ${config.getPath()}\n`);
|
|
372
520
|
if (issues.length === 0) {
|
|
373
521
|
console.log('✓ All config files valid');
|
|
@@ -392,7 +540,7 @@ async function handleValidate(config, args) {
|
|
|
392
540
|
}
|
|
393
541
|
}
|
|
394
542
|
async function handleProjectValidate(args) {
|
|
395
|
-
const { getProjectDir, readAiwgConfig, resolveIssueLabels, validateExternalLinks, validateIndexConfig, validateIssueLabels, } = await import('./aiwg-config.js');
|
|
543
|
+
const { getProjectDir, readAiwgConfig, resolveIssueLabels, validateExternalLinks, validateIndexConfig, validateIssueLabels, validateWorkerConfigCandidate, } = await import('./aiwg-config.js');
|
|
396
544
|
const projectDir = getProjectDir(undefined, args);
|
|
397
545
|
const cfg = await readAiwgConfig(projectDir);
|
|
398
546
|
if (!cfg) {
|
|
@@ -403,6 +551,7 @@ async function handleProjectValidate(args) {
|
|
|
403
551
|
exitCode: EXIT_CODES.CONFIG,
|
|
404
552
|
});
|
|
405
553
|
}
|
|
554
|
+
await validateWorkerConfigCandidate(projectDir, cfg);
|
|
406
555
|
const providerIndex = args.indexOf('--provider');
|
|
407
556
|
const providerArg = providerIndex >= 0 ? args[providerIndex + 1] : undefined;
|
|
408
557
|
const allowedProviders = ['gitea', 'github', 'local'];
|
|
@@ -463,7 +612,7 @@ async function handleProjectValidate(args) {
|
|
|
463
612
|
}
|
|
464
613
|
async function handleReset(config, args) {
|
|
465
614
|
const isProject = args.includes('--project');
|
|
466
|
-
const positional = args
|
|
615
|
+
const positional = configPositionals(args);
|
|
467
616
|
const key = positional[0];
|
|
468
617
|
if (isProject) {
|
|
469
618
|
await projectConfigReset(key, args);
|
|
@@ -496,6 +645,7 @@ async function projectConfigReset(key, args) {
|
|
|
496
645
|
exitCode: EXIT_CODES.USAGE,
|
|
497
646
|
});
|
|
498
647
|
}
|
|
648
|
+
assertSafeConfigPath(key);
|
|
499
649
|
const projectDir = getProjectDir(undefined, args);
|
|
500
650
|
const cfg = await readAiwgConfig(projectDir);
|
|
501
651
|
if (!cfg) {
|
|
@@ -508,6 +658,7 @@ async function projectConfigReset(key, args) {
|
|
|
508
658
|
}
|
|
509
659
|
const primary = cfg.providers[0];
|
|
510
660
|
const defaults = getProviderParallelismDefaults(primary);
|
|
661
|
+
const before = structuredClone(getDottedPath(cfg, key));
|
|
511
662
|
if (key === 'parallelism') {
|
|
512
663
|
cfg.parallelism = {
|
|
513
664
|
max_parallel_subagents: defaults.max_parallel_subagents,
|
|
@@ -517,15 +668,31 @@ async function projectConfigReset(key, args) {
|
|
|
517
668
|
};
|
|
518
669
|
await writeAiwgConfig(projectDir, cfg);
|
|
519
670
|
console.log(`Reset --project parallelism to provider default (${primary ?? 'unknown'})`);
|
|
671
|
+
warnStaleParallelism(cfg, projectDir, key, before);
|
|
520
672
|
return;
|
|
521
673
|
}
|
|
522
674
|
if (key.startsWith('parallelism.')) {
|
|
675
|
+
const workerField = key.split('.')[1];
|
|
676
|
+
if (['worker_dispatch', 'worker_mode', 'clis', 'groups', 'admission', 'budgets', 'retry', 'health', 'activation'].includes(workerField)) {
|
|
677
|
+
const parts = key.split('.').slice(1);
|
|
678
|
+
const last = parts.pop();
|
|
679
|
+
let current = cfg.parallelism;
|
|
680
|
+
for (const part of parts)
|
|
681
|
+
current = current?.[part];
|
|
682
|
+
if (current)
|
|
683
|
+
delete current[last];
|
|
684
|
+
await writeAiwgConfig(projectDir, cfg);
|
|
685
|
+
console.log(`Reset --project ${key}`);
|
|
686
|
+
warnStaleParallelism(cfg, projectDir, key, before);
|
|
687
|
+
return;
|
|
688
|
+
}
|
|
523
689
|
const field = key.slice('parallelism.'.length);
|
|
524
690
|
if (field in defaults) {
|
|
525
691
|
cfg.parallelism = cfg.parallelism ?? {};
|
|
526
692
|
cfg.parallelism[field] = defaults[field];
|
|
527
693
|
await writeAiwgConfig(projectDir, cfg);
|
|
528
694
|
console.log(`Reset --project ${key} to provider default (${defaults[field]})`);
|
|
695
|
+
warnStaleParallelism(cfg, projectDir, key, before);
|
|
529
696
|
return;
|
|
530
697
|
}
|
|
531
698
|
}
|
|
@@ -539,13 +706,40 @@ async function projectConfigReset(key, args) {
|
|
|
539
706
|
function handlePath(config) {
|
|
540
707
|
console.log(config.getPath());
|
|
541
708
|
}
|
|
542
|
-
|
|
543
|
-
|
|
709
|
+
/**
|
|
710
|
+
* `aiwg config edit [--print-path]` (#3062). `--print-path` is the headless
|
|
711
|
+
* equivalent: it prints the resolved config file path and exits, so scripts
|
|
712
|
+
* and agents can read/edit the file themselves (or compose with
|
|
713
|
+
* `aiwg config get`/`set`) instead of needing $EDITOR. Without a terminal
|
|
714
|
+
* and without `--print-path`, this refuses instead of spawning an editor
|
|
715
|
+
* that would hang waiting for input nobody can supply.
|
|
716
|
+
*/
|
|
717
|
+
async function handleEdit(config, args = []) {
|
|
544
718
|
const configPath = `${config.getPath()}/config.yaml`;
|
|
719
|
+
if (args.includes('--print-path')) {
|
|
720
|
+
// Ensure the config file exists before pointing a caller at it.
|
|
721
|
+
await config.ensureDir();
|
|
722
|
+
console.log(configPath);
|
|
723
|
+
return;
|
|
724
|
+
}
|
|
725
|
+
if (!isInteractive()) {
|
|
726
|
+
throw new AiwgError({
|
|
727
|
+
code: 'ERR_NON_INTERACTIVE_EDIT',
|
|
728
|
+
message: 'aiwg config edit requires an interactive terminal to open $EDITOR/$VISUAL.',
|
|
729
|
+
hint: 'Use `aiwg config set <key> <value>` to change a value headlessly, or `aiwg config edit --print-path` to get the file path and edit it yourself.',
|
|
730
|
+
exitCode: EXIT_CODES.USAGE,
|
|
731
|
+
});
|
|
732
|
+
}
|
|
733
|
+
const editor = process.env.EDITOR || process.env.VISUAL || 'vi';
|
|
545
734
|
// Ensure the config file exists before opening
|
|
546
735
|
await config.ensureDir();
|
|
547
736
|
const { execSync } = await import('child_process');
|
|
548
737
|
try {
|
|
738
|
+
// sync-spawn: interactive editor-launch — launches $EDITOR/$VISUAL on the
|
|
739
|
+
// config file; the user interacts with it directly (see tools/testing/
|
|
740
|
+
// sync-spawn-allowlist.json). Reached only when isInteractive() is true
|
|
741
|
+
// (#3062) — the no-terminal path above refuses before spawning anything.
|
|
742
|
+
// Not a candidate for a bounded timeout.
|
|
549
743
|
execSync(`${editor} "${configPath}"`, { stdio: 'inherit' });
|
|
550
744
|
}
|
|
551
745
|
catch (err) {
|
|
@@ -584,7 +778,7 @@ For project-level config: aiwg config show --project [--json]
|
|
|
584
778
|
}
|
|
585
779
|
// Lazy-import to keep startup cost low for unrelated subcommands.
|
|
586
780
|
const { readAiwgConfig, resolveRemotes, getProjectDir } = await import('./aiwg-config.js');
|
|
587
|
-
const {
|
|
781
|
+
const { boundedSpawnSync } = await import('../process/bounded-child-process.mjs');
|
|
588
782
|
const projectDir = getProjectDir(undefined, args);
|
|
589
783
|
const cfg = await readAiwgConfig(projectDir);
|
|
590
784
|
if (!cfg) {
|
|
@@ -598,7 +792,7 @@ For project-level config: aiwg config show --project [--json]
|
|
|
598
792
|
const resolvedRemotes = resolveRemotes(cfg.remotes);
|
|
599
793
|
// Resolve each declared remote name to its URL via git. Best-effort.
|
|
600
794
|
function getUrl(name) {
|
|
601
|
-
const r =
|
|
795
|
+
const r = boundedSpawnSync('git', ['-C', projectDir, 'remote', 'get-url', name], 'quick', {
|
|
602
796
|
encoding: 'utf-8',
|
|
603
797
|
stdio: ['ignore', 'pipe', 'ignore'],
|
|
604
798
|
});
|
|
@@ -691,14 +885,24 @@ For project-level config: aiwg config show --project [--json]
|
|
|
691
885
|
console.log('');
|
|
692
886
|
}
|
|
693
887
|
async function handleGitignore(args) {
|
|
694
|
-
const {
|
|
888
|
+
const { boundedSpawnSync } = await import('../process/bounded-child-process.mjs');
|
|
695
889
|
// Locate the gitignore CLI script relative to this compiled module
|
|
696
890
|
// (#1228). At runtime this module lives at `dist/src/config/cli.js`,
|
|
697
891
|
// so we need three `..` segments to reach the package root where
|
|
698
892
|
// `tools/cli/` is shipped (see package.json `files`).
|
|
699
893
|
const scriptPath = path.resolve(_scriptDir, '../../../tools/cli/config-gitignore.mjs');
|
|
700
|
-
|
|
894
|
+
// Local script editing .gitignore blocks; quick tier (#3062).
|
|
895
|
+
const result = boundedSpawnSync(process.execPath, [scriptPath, ...args], 'quick', { stdio: 'inherit' });
|
|
701
896
|
if (result.status !== 0) {
|
|
897
|
+
// #3062 review item 7: already correctly treats a null status
|
|
898
|
+
// (timeout/signal) as failure (`process.exit(result.status ?? 1)`
|
|
899
|
+
// below); surface the bounded helper's diagnostic too — `stdio:
|
|
900
|
+
// 'inherit'` means a normal script failure already printed its own
|
|
901
|
+
// output, but a bounded-helper timeout/kill would otherwise never be
|
|
902
|
+
// reported anywhere.
|
|
903
|
+
const { message } = exitCodeForSpawnResult(result, 'aiwg config gitignore');
|
|
904
|
+
if (message)
|
|
905
|
+
console.error(message);
|
|
702
906
|
process.exit(result.status ?? 1);
|
|
703
907
|
}
|
|
704
908
|
}
|
|
@@ -707,8 +911,8 @@ function printUsage() {
|
|
|
707
911
|
|
|
708
912
|
Subcommands:
|
|
709
913
|
get <key> Read a user config value
|
|
710
|
-
get --project <key> Read a project config value
|
|
711
|
-
|
|
914
|
+
get --project <key> Read a project config value or merged defaults.workerSelection
|
|
915
|
+
[--origins] Include model policy sources, pointers and input digests
|
|
712
916
|
set <key> <value> Write a user config value
|
|
713
917
|
set --project <key> <value> Write a project config value (validates enums)
|
|
714
918
|
list Show all user config
|
|
@@ -718,7 +922,8 @@ Subcommands:
|
|
|
718
922
|
[--provider gitea|github|local] [--available-label NAME ...]
|
|
719
923
|
reset [<key>] Reset key or all config to defaults
|
|
720
924
|
path Print config directory path
|
|
721
|
-
edit Open config in $EDITOR
|
|
925
|
+
edit Open config in $EDITOR/$VISUAL (requires a terminal)
|
|
926
|
+
edit --print-path Print the config file path instead of opening an editor
|
|
722
927
|
gitignore Show/check/fix .gitignore AIWG entries
|
|
723
928
|
|
|
724
929
|
Global flags:
|
|
@@ -735,6 +940,7 @@ Examples:
|
|
|
735
940
|
aiwg config set --project delivery.mode pr-required
|
|
736
941
|
aiwg config set --project delivery.merge_style squash
|
|
737
942
|
aiwg config get --project parallelism
|
|
943
|
+
aiwg config get --project defaults.workerSelection --origins
|
|
738
944
|
aiwg config set --project parallelism.max_parallel_subagents 6
|
|
739
945
|
aiwg config reset --project parallelism
|
|
740
946
|
aiwg config validate
|
|
@@ -745,4 +951,31 @@ Examples:
|
|
|
745
951
|
aiwg config gitignore --fix
|
|
746
952
|
aiwg config gitignore --check`);
|
|
747
953
|
}
|
|
954
|
+
/** Same configuration owner and validation used by the rules command wrappers. */
|
|
955
|
+
export async function setRuleConfigKey(cwd, source, key, raw, userConfigDir) {
|
|
956
|
+
assertSafeConfigPath(key);
|
|
957
|
+
if (source === 'project') {
|
|
958
|
+
await projectConfigSet(key, raw, ['--project', '--target', cwd], true);
|
|
959
|
+
return;
|
|
960
|
+
}
|
|
961
|
+
const { loadRulePinConfigs, rulePinConfigPath, installedRuleCapabilities, validateRulePinCapabilities } = await import('../rules/pins.mjs');
|
|
962
|
+
const { mkdir, writeFile, rename } = await import('node:fs/promises');
|
|
963
|
+
const configs = loadRulePinConfigs(cwd, undefined, userConfigDir);
|
|
964
|
+
let value;
|
|
965
|
+
try {
|
|
966
|
+
value = JSON.parse(raw);
|
|
967
|
+
}
|
|
968
|
+
catch {
|
|
969
|
+
value = raw;
|
|
970
|
+
}
|
|
971
|
+
setDottedPath(configs.user, key, value);
|
|
972
|
+
const errors = validateRulePinCapabilities(configs.user.rules, installedRuleCapabilities(configs), configs.project.rules?.pin_groups);
|
|
973
|
+
if (errors.length)
|
|
974
|
+
throw new Error(errors.join('\n'));
|
|
975
|
+
const file = rulePinConfigPath(cwd, 'user', userConfigDir);
|
|
976
|
+
await mkdir(path.dirname(file), { recursive: true });
|
|
977
|
+
const temp = `${file}.${process.pid}.tmp`;
|
|
978
|
+
await writeFile(temp, JSON.stringify(configs.user, null, 2) + '\n');
|
|
979
|
+
await rename(temp, file);
|
|
980
|
+
}
|
|
748
981
|
//# sourceMappingURL=cli.js.map
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { parseYamlSimple } from './user-config.js';
|
|
4
|
+
import { resolveUserConfigDir } from './user-config-dir.mjs';
|
|
5
|
+
export function validateDecisionConfig(value) {
|
|
6
|
+
if (value === undefined)
|
|
7
|
+
return [];
|
|
8
|
+
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
9
|
+
return ['decision must be an object'];
|
|
10
|
+
const errors = [];
|
|
11
|
+
const { enabled, ask, offload_report: report } = value;
|
|
12
|
+
if (enabled !== undefined && typeof enabled !== 'boolean')
|
|
13
|
+
errors.push('decision.enabled must be a boolean');
|
|
14
|
+
if (report !== undefined) {
|
|
15
|
+
if (!report || typeof report !== 'object' || Array.isArray(report))
|
|
16
|
+
errors.push('decision.offload_report must be an object');
|
|
17
|
+
else {
|
|
18
|
+
for (const key of ['window_hours', 'minimum_eligible', 'repeated_fallbacks']) {
|
|
19
|
+
const configured = report[key];
|
|
20
|
+
if (configured !== undefined && (!Number.isSafeInteger(configured) || configured < (key === 'minimum_eligible' ? 0 : 1))) {
|
|
21
|
+
errors.push(`decision.offload_report.${key} must be a ${key === 'minimum_eligible' ? 'non-negative' : 'positive'} safe integer`);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
if (report.minimum_attempt_rate !== undefined && (typeof report.minimum_attempt_rate !== 'number'
|
|
25
|
+
|| !Number.isFinite(report.minimum_attempt_rate) || report.minimum_attempt_rate < 0 || report.minimum_attempt_rate > 1)) {
|
|
26
|
+
errors.push('decision.offload_report.minimum_attempt_rate must be a probability from 0 to 1');
|
|
27
|
+
}
|
|
28
|
+
for (const key of Object.keys(report))
|
|
29
|
+
if (!['window_hours', 'minimum_eligible', 'minimum_attempt_rate', 'repeated_fallbacks'].includes(key)) {
|
|
30
|
+
errors.push(`unknown decision.offload_report.${key}`);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
if (ask === undefined)
|
|
35
|
+
return errors;
|
|
36
|
+
if (!ask || typeof ask !== 'object' || Array.isArray(ask))
|
|
37
|
+
return [...errors, 'decision.ask must be an object'];
|
|
38
|
+
const sampling = [];
|
|
39
|
+
for (const key of ['passes', 'escalation_passes']) {
|
|
40
|
+
if (ask[key] !== undefined && (!Number.isSafeInteger(ask[key]) || ask[key] <= 0)) {
|
|
41
|
+
sampling.push(`decision.ask.${key} must be a positive safe integer`);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
if (!sampling.length && ask.escalation_passes !== undefined && ask.escalation_passes < (ask.passes ?? 5)) {
|
|
45
|
+
sampling.push('decision.ask.escalation_passes must be >= decision.ask.passes');
|
|
46
|
+
}
|
|
47
|
+
for (const key of ['min_agreement', 'agreement_confidence_floor']) {
|
|
48
|
+
if (ask[key] !== undefined && (typeof ask[key] !== 'number' || !Number.isFinite(ask[key]) || ask[key] < 0 || ask[key] > 1)) {
|
|
49
|
+
sampling.push(`decision.ask.${key} must be a probability from 0 to 1`);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
for (const section of ['cost', 'savings']) {
|
|
53
|
+
const settings = ask[section];
|
|
54
|
+
if (settings === undefined)
|
|
55
|
+
continue;
|
|
56
|
+
if (!settings || typeof settings !== 'object' || Array.isArray(settings)) {
|
|
57
|
+
sampling.push(`decision.ask.${section} must be an object`);
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
const allowed = section === 'cost' ? ['llm_per_mtok', 'jev_per_mtok'] : ['caller_model_class', 'reasoning_tokens', 'output_tokens'];
|
|
61
|
+
for (const [key, value] of Object.entries(settings)) {
|
|
62
|
+
if (!allowed.includes(key))
|
|
63
|
+
sampling.push(`unknown decision.ask.${section}.${key}`);
|
|
64
|
+
else if (key === 'caller_model_class') {
|
|
65
|
+
if (value !== 'standard' && value !== 'reasoning')
|
|
66
|
+
sampling.push('decision.ask.savings.caller_model_class must be standard or reasoning');
|
|
67
|
+
}
|
|
68
|
+
else if (typeof value !== 'number' || !Number.isFinite(value) || value < 0 || value > 1_000_000
|
|
69
|
+
|| (section === 'savings' && !Number.isSafeInteger(value))) {
|
|
70
|
+
sampling.push(`decision.ask.${section}.${key} must be a non-negative ${section === 'savings' ? 'integer' : 'number'} <= 1000000`);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return [...errors, ...sampling];
|
|
75
|
+
}
|
|
76
|
+
function readEnabled(filePath, yaml = false) {
|
|
77
|
+
let content;
|
|
78
|
+
try {
|
|
79
|
+
content = readFileSync(filePath, 'utf8');
|
|
80
|
+
}
|
|
81
|
+
catch (error) {
|
|
82
|
+
if (error.code === 'ENOENT')
|
|
83
|
+
return undefined;
|
|
84
|
+
throw error;
|
|
85
|
+
}
|
|
86
|
+
const config = (yaml ? parseYamlSimple(content) : JSON.parse(content));
|
|
87
|
+
const errors = validateDecisionConfig(config.decision);
|
|
88
|
+
if (errors.length)
|
|
89
|
+
throw new Error(`${filePath}: ${errors.join('; ')}`);
|
|
90
|
+
return config.decision?.enabled;
|
|
91
|
+
}
|
|
92
|
+
/** Resolve only enablement; never read credentials or probe a backend. */
|
|
93
|
+
export function resolveDecisionEnablement(options = {}) {
|
|
94
|
+
const env = options.env ?? process.env;
|
|
95
|
+
if (env.AIWG_DECISION_ENABLED === '0' || env.AIWG_DECISION_ENABLED === '1') {
|
|
96
|
+
return { enabled: env.AIWG_DECISION_ENABLED === '1', source: 'env' };
|
|
97
|
+
}
|
|
98
|
+
const project = readEnabled(path.join(options.cwd ?? process.cwd(), '.aiwg', 'aiwg.config'));
|
|
99
|
+
if (project !== undefined)
|
|
100
|
+
return { enabled: project, source: 'project' };
|
|
101
|
+
const user = readEnabled(path.join(resolveUserConfigDir({ env, homeDir: options.homeDir }), 'config.yaml'), true);
|
|
102
|
+
if (user !== undefined)
|
|
103
|
+
return { enabled: user, source: 'user' };
|
|
104
|
+
return { enabled: false, source: 'default' };
|
|
105
|
+
}
|
|
106
|
+
//# sourceMappingURL=decision-config.js.map
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { DispatchHostThresholds } from './aiwg-config.js';
|
|
2
|
+
|
|
3
|
+
export const DEFAULT_DISPATCH_HOST_THRESHOLDS: Readonly<Required<Omit<DispatchHostThresholds, 'max_workers'>>>;
|
|
4
|
+
export function resolveDispatchHostThresholds(
|
|
5
|
+
buildDispatch?: DispatchHostThresholds,
|
|
6
|
+
admissionHostResources?: DispatchHostThresholds,
|
|
7
|
+
overlay?: DispatchHostThresholds,
|
|
8
|
+
): Required<Omit<DispatchHostThresholds, 'max_workers'>> & Pick<DispatchHostThresholds, 'max_workers'>;
|