@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
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { validateDecisionConfig } from './decision-config.js';
|
|
1
2
|
/**
|
|
2
3
|
* Project-Level AIWG Config
|
|
3
4
|
*
|
|
@@ -8,6 +9,18 @@
|
|
|
8
9
|
*
|
|
9
10
|
* @implements #621
|
|
10
11
|
*/
|
|
12
|
+
import { validateImportExclusions } from '../sessions/import-exclusions.js';
|
|
13
|
+
import { resolveDispatchHostThresholds } from './dispatch-thresholds-runtime.mjs';
|
|
14
|
+
export { DEFAULT_DISPATCH_HOST_THRESHOLDS, resolveDispatchHostThresholds } from './dispatch-thresholds-runtime.mjs';
|
|
15
|
+
import { validateIndexGraphOverrides } from './index-graph-overrides.js';
|
|
16
|
+
import { DEFAULT_RULES_INLINE_BUDGET_TOKENS } from '../rules/inline-budget.mjs';
|
|
17
|
+
export { validateRulesConfig, resolveRulesInlineBudget } from '../rules/inline-budget.mjs';
|
|
18
|
+
import { loadRulePinConfigs, installedRuleCapabilities, validateRulePinCapabilities } from '../rules/pins.mjs';
|
|
19
|
+
import Ajv from 'ajv';
|
|
20
|
+
import addFormats from 'ajv-formats';
|
|
21
|
+
import { readFileSync, existsSync } from 'node:fs';
|
|
22
|
+
import { fileURLToPath } from 'node:url';
|
|
23
|
+
import { isCanonicalWorkerModelKey, loadProviderModelCapabilities, resolveWorkerInstallation } from '../models/provider-policy.js';
|
|
11
24
|
import { readFile, writeFile, mkdir, access, readdir, rename, unlink } from 'fs/promises';
|
|
12
25
|
import { createHash, randomBytes } from 'crypto';
|
|
13
26
|
import { resolve, join, dirname, isAbsolute } from 'path';
|
|
@@ -162,6 +175,166 @@ export function resolveDelivery(delivery) {
|
|
|
162
175
|
issue_comment_on_cycle: delivery?.issue_comment_on_cycle ?? true,
|
|
163
176
|
};
|
|
164
177
|
}
|
|
178
|
+
let parallelismValidator;
|
|
179
|
+
/** Offline shape and declaration validation. Qualification is checked at use time. */
|
|
180
|
+
export function validateParallelismConfig(value) {
|
|
181
|
+
if (value === undefined)
|
|
182
|
+
return [];
|
|
183
|
+
if (!parallelismValidator) {
|
|
184
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
185
|
+
const candidates = [resolve(here, '../../vscode-extension/schemas/aiwg.config.v1.json'), resolve(here, '../../../vscode-extension/schemas/aiwg.config.v1.json')];
|
|
186
|
+
const file = candidates.find(existsSync);
|
|
187
|
+
if (!file)
|
|
188
|
+
throw new Error('Parallelism schema is unavailable');
|
|
189
|
+
const schema = JSON.parse(readFileSync(file, 'utf8'));
|
|
190
|
+
const ajv = addFormats(new Ajv({ strict: true, allErrors: true }));
|
|
191
|
+
parallelismValidator = ajv.compile({ ...schema.properties.parallelism, definitions: schema.definitions });
|
|
192
|
+
}
|
|
193
|
+
if (!parallelismValidator(value))
|
|
194
|
+
return (parallelismValidator.errors ?? []).map(error => `OG_CONFIG_REFERENCE /parallelism${error.instancePath}: ${error.message}`);
|
|
195
|
+
const policy = value;
|
|
196
|
+
const errors = [];
|
|
197
|
+
const error = (pointer, message) => { errors.push(`OG_CONFIG_REFERENCE /parallelism/${pointer}: ${message}`); };
|
|
198
|
+
const builtinIds = new Set(PROVIDER_IDS.filter(id => id !== 'generic'));
|
|
199
|
+
for (const [id, installation] of Object.entries(policy.clis ?? {})) {
|
|
200
|
+
if (installation.adapter.startsWith('providers:') && !builtinIds.has(installation.adapter.slice(10)))
|
|
201
|
+
error(`clis/${id}/adapter`, 'Unknown provider binding');
|
|
202
|
+
}
|
|
203
|
+
for (const [id, group] of Object.entries(policy.groups ?? {})) {
|
|
204
|
+
const ids = new Set();
|
|
205
|
+
for (const member of group.members) {
|
|
206
|
+
if (ids.has(member.id))
|
|
207
|
+
error(`groups/${id}/members`, 'Duplicate member ID');
|
|
208
|
+
ids.add(member.id);
|
|
209
|
+
if (!policy.clis?.[member.cli] && !builtinIds.has(member.cli))
|
|
210
|
+
error(`groups/${id}/members/${member.id}/cli`, 'Unknown installation');
|
|
211
|
+
}
|
|
212
|
+
for (const member of group.fallback ?? [])
|
|
213
|
+
if (!ids.has(member))
|
|
214
|
+
error(`groups/${id}/fallback`, 'Unknown fallback member');
|
|
215
|
+
for (const role of group.requiredRoles ?? []) {
|
|
216
|
+
if (!group.members.some(m => m.enabled !== false && policy.clis?.[m.cli]?.enabled !== false && m.roles.includes(role)))
|
|
217
|
+
error(`groups/${id}/requiredRoles`, `No enabled member covers ${role}`);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
const admission = policy.admission;
|
|
221
|
+
for (const group of Object.keys(admission?.groupCaps ?? {}))
|
|
222
|
+
if (!policy.groups?.[group])
|
|
223
|
+
error('admission/groupCaps', 'Unknown group');
|
|
224
|
+
for (const cli of Object.keys(admission?.cliCaps ?? {}))
|
|
225
|
+
if (!policy.clis?.[cli] && !builtinIds.has(cli))
|
|
226
|
+
error('admission/cliCaps', 'Unknown installation');
|
|
227
|
+
for (const key of Object.keys(admission?.memberCaps ?? {})) {
|
|
228
|
+
const [group, member, extra] = key.split('/');
|
|
229
|
+
if (extra !== undefined || !policy.groups?.[group]?.members.some(m => m.id === member))
|
|
230
|
+
error('admission/memberCaps', 'Unknown group/member');
|
|
231
|
+
}
|
|
232
|
+
for (const key of Object.keys(admission?.modelCaps ?? {}))
|
|
233
|
+
if (!isCanonicalWorkerModelKey(key))
|
|
234
|
+
error('admission/modelCaps', 'Noncanonical inference model key');
|
|
235
|
+
if (admission?.hostResources !== undefined) {
|
|
236
|
+
try {
|
|
237
|
+
resolveDispatchHostThresholds(undefined, admission.hostResources);
|
|
238
|
+
}
|
|
239
|
+
catch (validation) {
|
|
240
|
+
error('admission/hostResources', validation instanceof Error ? validation.message : String(validation));
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
errors.push(...validateWorkerHealthConfig(policy.health));
|
|
244
|
+
return errors;
|
|
245
|
+
}
|
|
246
|
+
/** Cross-field health preferences are validated offline; controls are qualified at dispatch. */
|
|
247
|
+
export function validateWorkerHealthConfig(health) {
|
|
248
|
+
if (health === undefined)
|
|
249
|
+
return [];
|
|
250
|
+
const errors = [];
|
|
251
|
+
const error = (field, message) => { errors.push(`OG_CONFIG_REFERENCE /parallelism/health/${field}: ${message}`); };
|
|
252
|
+
const checkSeconds = health.checkSeconds ?? 15;
|
|
253
|
+
if (checkSeconds > (health.startupSeconds ?? 60))
|
|
254
|
+
error('checkSeconds', 'Must not exceed startupSeconds');
|
|
255
|
+
if ((health.quietSeconds ?? 90) >= (health.stallSeconds ?? 300))
|
|
256
|
+
error('quietSeconds', 'Must be less than stallSeconds');
|
|
257
|
+
if ((health.cooldownSeconds ?? 120) > (health.maxCooldownSeconds ?? 900))
|
|
258
|
+
error('cooldownSeconds', 'Must not exceed maxCooldownSeconds');
|
|
259
|
+
for (const role of Object.keys(health.roleScale ?? {})) {
|
|
260
|
+
if (!['efficiency', 'coding', 'reasoning'].includes(role))
|
|
261
|
+
error('roleScale', `Unknown role ${role}`);
|
|
262
|
+
}
|
|
263
|
+
for (const field of ['modelScale', 'modelStartupSeconds']) {
|
|
264
|
+
for (const [key, value] of Object.entries(health[field] ?? {})) {
|
|
265
|
+
if (!isCanonicalWorkerModelKey(key))
|
|
266
|
+
error(field, 'Noncanonical inference model key');
|
|
267
|
+
if (field === 'modelStartupSeconds' && checkSeconds > value)
|
|
268
|
+
error(field, 'Model startup must cover checkSeconds');
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
return errors;
|
|
272
|
+
}
|
|
273
|
+
/** Resolve one enabled installation through trusted metadata, without probes.
|
|
274
|
+
* Implicit canonical IDs inherit current-account auth; no account/quota identity
|
|
275
|
+
* is invented. Profile qualification remains the invocation owner's obligation.
|
|
276
|
+
*/
|
|
277
|
+
export function resolveWorkerGroupInstallation(parallelism, installationId, registry = loadProviderModelCapabilities()) {
|
|
278
|
+
const errors = validateParallelismConfig(parallelism);
|
|
279
|
+
if (errors.length)
|
|
280
|
+
throw new Error(errors.join('; '));
|
|
281
|
+
if (!parallelism.worker_dispatch)
|
|
282
|
+
throw new Error('OG_CONFIG_REFERENCE /parallelism/worker_dispatch: Worker dispatch is disabled');
|
|
283
|
+
const explicit = parallelism.clis?.[installationId];
|
|
284
|
+
const installation = explicit
|
|
285
|
+
? structuredClone(explicit)
|
|
286
|
+
: { adapter: `providers:${installationId}`, enabled: true };
|
|
287
|
+
if (!explicit && (!PROVIDER_IDS.includes(installationId) || installationId === 'generic')) {
|
|
288
|
+
throw new Error(`OG_CONFIG_REFERENCE /parallelism/clis/${installationId}: Unknown canonical installation`);
|
|
289
|
+
}
|
|
290
|
+
if (installation.enabled === false)
|
|
291
|
+
throw new Error(`OG_CONFIG_REFERENCE /parallelism/clis/${installationId}: Installation is disabled`);
|
|
292
|
+
try {
|
|
293
|
+
return { installation: { ...installation, enabled: true }, ...resolveWorkerInstallation(registry, installation) };
|
|
294
|
+
}
|
|
295
|
+
catch (error) {
|
|
296
|
+
throw new Error(`OG_ADAPTER_UNQUALIFIED /parallelism/clis/${installationId}: ${error}`);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
/** Validate current model restrictions and group references before any write.
|
|
300
|
+
* Disabled/unbound projects retain their existing config-only edit behavior.
|
|
301
|
+
* This is shared validation, not Tier 1 coherent activation.
|
|
302
|
+
*
|
|
303
|
+
* `options.aiwgHome`, when provided, is passed through to `ConfigLoader`
|
|
304
|
+
* unchanged — it never reads or writes `process.env.AIWG_HOME`. Omitting it
|
|
305
|
+
* keeps `ConfigLoader`'s own default precedence (`AIWG_HOME` env, else the
|
|
306
|
+
* running package root).
|
|
307
|
+
*/
|
|
308
|
+
export async function validateWorkerConfigCandidate(projectDir, config, options) {
|
|
309
|
+
const errors = validateParallelismConfig(config.parallelism);
|
|
310
|
+
if (errors.length)
|
|
311
|
+
throw new Error(errors.join('; '));
|
|
312
|
+
if (!config.parallelism?.worker_dispatch)
|
|
313
|
+
return;
|
|
314
|
+
const { ConfigLoader } = await import('../models/config-loader.js');
|
|
315
|
+
await new ConfigLoader(projectDir, options).load(config.parallelism);
|
|
316
|
+
}
|
|
317
|
+
/** Hash canonical JSON, treating absent optional model files as null. */
|
|
318
|
+
export function workerConfigDigest(value) {
|
|
319
|
+
const canonical = (node) => {
|
|
320
|
+
if (Array.isArray(node))
|
|
321
|
+
return node.map(canonical);
|
|
322
|
+
if (node !== null && typeof node === 'object')
|
|
323
|
+
return Object.fromEntries(Object.keys(node).sort().filter(key => node[key] !== undefined).map(key => [key, canonical(node[key])]));
|
|
324
|
+
return node ?? null;
|
|
325
|
+
};
|
|
326
|
+
return `sha256:${createHash('sha256').update(JSON.stringify(canonical(value))).digest('hex')}`;
|
|
327
|
+
}
|
|
328
|
+
export function workerActivationDigests(config, projectModels, userModels, builtinModels) {
|
|
329
|
+
const candidate = structuredClone(config);
|
|
330
|
+
if (candidate.parallelism)
|
|
331
|
+
delete candidate.parallelism.activation;
|
|
332
|
+
return { configDigest: workerConfigDigest(candidate), projectModelsDigest: workerConfigDigest(projectModels), userModelsDigest: workerConfigDigest(userModels), builtinModelsDigest: workerConfigDigest(builtinModels) };
|
|
333
|
+
}
|
|
334
|
+
/** Pure inventory only: this does not activate, lock, synchronize, or admit work. */
|
|
335
|
+
export function workerActivationCopyInventory(projectDir, compatibilityConfig, userModels, builtinModels) {
|
|
336
|
+
return [...new Set([getConfigPath(projectDir), join(projectDir, 'models.json'), compatibilityConfig, userModels, builtinModels].map(file => resolve(file)))];
|
|
337
|
+
}
|
|
165
338
|
/**
|
|
166
339
|
* Per-provider parallelism defaults. Conservative numbers for Anthropic-backed
|
|
167
340
|
* providers reflect Pro/Team-plan rate limits — operators on Enterprise tiers
|
|
@@ -235,6 +408,30 @@ export function resolveParallelism(parallelism, primaryProvider) {
|
|
|
235
408
|
};
|
|
236
409
|
if (parallelism?.rationale !== undefined)
|
|
237
410
|
resolved.rationale = parallelism.rationale;
|
|
411
|
+
const errors = validateParallelismConfig(parallelism);
|
|
412
|
+
if (errors.length)
|
|
413
|
+
throw new Error(errors.join('; '));
|
|
414
|
+
if (parallelism?.worker_dispatch) {
|
|
415
|
+
Object.assign(resolved, Object.fromEntries(Object.entries(structuredClone(parallelism)).filter(([, value]) => value !== undefined)));
|
|
416
|
+
resolved.worker_mode ??= 'direct';
|
|
417
|
+
for (const group of Object.values(resolved.groups ?? {})) {
|
|
418
|
+
group.fallback ??= [];
|
|
419
|
+
group.requiredRoles ??= [];
|
|
420
|
+
group.execution = { isolation: 'worktree', sandbox: 'workspace-write', approval: 'never', network: 'deny', transport: 'shell', receipts: 'optional', ...group.execution };
|
|
421
|
+
for (const member of group.members) {
|
|
422
|
+
member.enabled ??= true;
|
|
423
|
+
member.priority ??= 50;
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
for (const cli of Object.values(resolved.clis ?? {}))
|
|
427
|
+
cli.enabled ??= true;
|
|
428
|
+
if (resolved.admission)
|
|
429
|
+
resolved.admission = { maxQueued: 128, queueTimeoutSeconds: 300, leaseSeconds: 60, authorityRef: 'project', ...resolved.admission };
|
|
430
|
+
if (resolved.budgets)
|
|
431
|
+
resolved.budgets = { enforcement: 'observed', unknownCost: 'deny', ...resolved.budgets };
|
|
432
|
+
if (resolved.retry)
|
|
433
|
+
resolved.retry = { maxAttempts: 3, on: ['rate-limit', 'unavailable', 'timeout'], baseDelaySeconds: 2, maxDelaySeconds: 60, ...resolved.retry };
|
|
434
|
+
}
|
|
238
435
|
return resolved;
|
|
239
436
|
}
|
|
240
437
|
const GRAPH_BACKENDS = ['json', 'graphology', 'sqlite'];
|
|
@@ -265,37 +462,7 @@ export function validateIndexConfig(index) {
|
|
|
265
462
|
errors.push(`index.graphBackend: must be one of ${GRAPH_BACKENDS.join(' | ')}`);
|
|
266
463
|
}
|
|
267
464
|
const isStringArray = (v) => Array.isArray(v) && v.every((x) => typeof x === 'string');
|
|
268
|
-
|
|
269
|
-
if (graphOverrides !== undefined) {
|
|
270
|
-
if (typeof graphOverrides !== 'object' || graphOverrides === null || Array.isArray(graphOverrides)) {
|
|
271
|
-
errors.push('index.graphOverrides: must be an object mapping supported built-in graph names to overrides');
|
|
272
|
-
}
|
|
273
|
-
else {
|
|
274
|
-
for (const [name, rawOverride] of Object.entries(graphOverrides)) {
|
|
275
|
-
const where = `index.graphOverrides.${name}`;
|
|
276
|
-
if (name !== 'codebase') {
|
|
277
|
-
errors.push(`${where}: unsupported built-in graph override (supported: codebase)`);
|
|
278
|
-
continue;
|
|
279
|
-
}
|
|
280
|
-
if (typeof rawOverride !== 'object' || rawOverride === null || Array.isArray(rawOverride)) {
|
|
281
|
-
errors.push(`${where}: must be an object`);
|
|
282
|
-
continue;
|
|
283
|
-
}
|
|
284
|
-
const override = rawOverride;
|
|
285
|
-
for (const field of Object.keys(override)) {
|
|
286
|
-
if (field !== 'scanDirs' && field !== 'extensions') {
|
|
287
|
-
errors.push(`${where}.${field}: unknown field (supported: scanDirs, extensions)`);
|
|
288
|
-
}
|
|
289
|
-
}
|
|
290
|
-
if (override.scanDirs !== undefined && (!isStringArray(override.scanDirs) || override.scanDirs.length === 0)) {
|
|
291
|
-
errors.push(`${where}.scanDirs: must be a non-empty array of strings`);
|
|
292
|
-
}
|
|
293
|
-
if (override.extensions !== undefined && (!isStringArray(override.extensions) || override.extensions.length === 0)) {
|
|
294
|
-
errors.push(`${where}.extensions: must be a non-empty array of strings`);
|
|
295
|
-
}
|
|
296
|
-
}
|
|
297
|
-
}
|
|
298
|
-
}
|
|
465
|
+
errors.push(...validateIndexGraphOverrides(indexObject.graphOverrides));
|
|
299
466
|
const graphs = indexObject.graphs;
|
|
300
467
|
if (graphs === undefined)
|
|
301
468
|
return errors; // index with no graphs is permissible
|
|
@@ -782,6 +949,7 @@ export function emptyConfig(providers = ['claude']) {
|
|
|
782
949
|
threatAssessment: defaultThreatAssessmentConfig(),
|
|
783
950
|
},
|
|
784
951
|
artifact_outputs: defaultArtifactOutputs(),
|
|
952
|
+
rules: { inline_budget_tokens: DEFAULT_RULES_INLINE_BUDGET_TOKENS },
|
|
785
953
|
delivery: {
|
|
786
954
|
mode: 'pr-required',
|
|
787
955
|
default_branch: 'main',
|
|
@@ -826,24 +994,35 @@ export function getProjectDir(ctx, args = []) {
|
|
|
826
994
|
return targetValue || ctx?.cwd || process.cwd();
|
|
827
995
|
}
|
|
828
996
|
/**
|
|
829
|
-
* Read .aiwg/aiwg.config
|
|
997
|
+
* Read and independently validate .aiwg/aiwg.config EXCEPT for the
|
|
998
|
+
* worker-model defaults step (`validateWorkerConfigCandidate`), which needs
|
|
999
|
+
* its own `aiwgHome` resolution and can fail solely because the bundled
|
|
1000
|
+
* model defaults are unreachable. Callers that want to degrade gracefully
|
|
1001
|
+
* on that one failure mode (e.g. generated-context rendering) read the
|
|
1002
|
+
* candidate here, then call `validateWorkerConfigCandidate` themselves and
|
|
1003
|
+
* handle its failure explicitly. `readAiwgConfig` below is the composition
|
|
1004
|
+
* of both steps for callers that want the original all-or-nothing contract.
|
|
830
1005
|
* Returns null if the file does not exist.
|
|
831
1006
|
*/
|
|
832
|
-
export async function
|
|
1007
|
+
export async function readAiwgConfigCandidate(projectDir) {
|
|
833
1008
|
const localPath = getConfigPath(projectDir);
|
|
834
1009
|
const artifactPath = projectAiwgPath(projectDir, CONFIG_FILENAME);
|
|
835
1010
|
let filePath = localPath;
|
|
836
1011
|
try {
|
|
837
1012
|
await access(localPath);
|
|
838
1013
|
}
|
|
839
|
-
catch {
|
|
1014
|
+
catch (error) {
|
|
1015
|
+
if (error.code !== 'ENOENT')
|
|
1016
|
+
throw error;
|
|
840
1017
|
if (artifactPath === localPath)
|
|
841
1018
|
return null;
|
|
842
1019
|
try {
|
|
843
1020
|
await access(artifactPath);
|
|
844
1021
|
filePath = artifactPath;
|
|
845
1022
|
}
|
|
846
|
-
catch {
|
|
1023
|
+
catch (error) {
|
|
1024
|
+
if (error.code !== 'ENOENT')
|
|
1025
|
+
throw error;
|
|
847
1026
|
return null;
|
|
848
1027
|
}
|
|
849
1028
|
}
|
|
@@ -856,6 +1035,30 @@ export async function readAiwgConfig(projectDir) {
|
|
|
856
1035
|
parsed.installed = {};
|
|
857
1036
|
if (!parsed.scripts)
|
|
858
1037
|
parsed.scripts = {};
|
|
1038
|
+
if (parsed.sessions !== undefined && (parsed.sessions === null || typeof parsed.sessions !== 'object' || Array.isArray(parsed.sessions)
|
|
1039
|
+
|| (parsed.sessions.codex_roots !== undefined && (!Array.isArray(parsed.sessions.codex_roots)
|
|
1040
|
+
|| !parsed.sessions.codex_roots.every((root) => typeof root === 'string' && root.trim().length > 0))))) {
|
|
1041
|
+
throw new Error('sessions.codex_roots must be an array of non-empty paths');
|
|
1042
|
+
}
|
|
1043
|
+
if (parsed.sessions?.discovery_manifests !== undefined && (!Array.isArray(parsed.sessions.discovery_manifests)
|
|
1044
|
+
|| !parsed.sessions.discovery_manifests.every((file) => typeof file === 'string' && file.trim().length > 0))) {
|
|
1045
|
+
throw new Error('sessions.discovery_manifests must be an array of non-empty paths');
|
|
1046
|
+
}
|
|
1047
|
+
if (parsed.sessions?.import !== undefined) {
|
|
1048
|
+
const limits = parsed.sessions.import;
|
|
1049
|
+
if (limits === null || typeof limits !== 'object' || Array.isArray(limits)) {
|
|
1050
|
+
throw new Error('sessions.import must be an object');
|
|
1051
|
+
}
|
|
1052
|
+
if (limits.exclude !== undefined)
|
|
1053
|
+
validateImportExclusions(limits.exclude);
|
|
1054
|
+
for (const key of ['max_record_bytes', 'max_source_bytes']) {
|
|
1055
|
+
const value = limits[key];
|
|
1056
|
+
const minimum = key === 'max_source_bytes' ? 0 : 1;
|
|
1057
|
+
if (value !== undefined && (typeof value !== 'number' || !Number.isSafeInteger(value) || value < minimum)) {
|
|
1058
|
+
throw new Error(`sessions.import.${key} must be a ${minimum ? 'positive' : 'non-negative'} integer`);
|
|
1059
|
+
}
|
|
1060
|
+
}
|
|
1061
|
+
}
|
|
859
1062
|
const externalLinkErrors = validateExternalLinks(parsed.externalLinks);
|
|
860
1063
|
if (externalLinkErrors.length > 0) {
|
|
861
1064
|
throw new Error(`Invalid .aiwg/aiwg.config:\n${externalLinkErrors.join('\n')}`);
|
|
@@ -873,6 +1076,9 @@ export async function readAiwgConfig(projectDir) {
|
|
|
873
1076
|
const threatAssessmentErrors = [
|
|
874
1077
|
...validateThreatAssessmentConfig(parsed.security?.threatAssessment),
|
|
875
1078
|
...validateProjectConfig(parsed.project),
|
|
1079
|
+
...validateParallelismConfig(parsed.parallelism),
|
|
1080
|
+
...validateDecisionConfig(parsed.decision),
|
|
1081
|
+
...validateRulePinCapabilities(parsed.rules, installedRuleCapabilities(loadRulePinConfigs(projectDir, parsed)), loadRulePinConfigs(projectDir, parsed).user.rules?.pin_groups),
|
|
876
1082
|
];
|
|
877
1083
|
if (threatAssessmentErrors.length > 0) {
|
|
878
1084
|
throw new Error(`Invalid .aiwg/aiwg.config:\n${threatAssessmentErrors.join('\n')}`);
|
|
@@ -894,15 +1100,68 @@ export async function readAiwgConfig(projectDir) {
|
|
|
894
1100
|
}
|
|
895
1101
|
return parsed;
|
|
896
1102
|
}
|
|
1103
|
+
/**
|
|
1104
|
+
* Read .aiwg/aiwg.config, including worker-model default validation.
|
|
1105
|
+
* Returns null if the file does not exist.
|
|
1106
|
+
*
|
|
1107
|
+
* `aiwgHome` (optional) resolves the bundled AIWG model-defaults location
|
|
1108
|
+
* passed through to `validateWorkerConfigCandidate` / `ConfigLoader`,
|
|
1109
|
+
* without ever reading or writing `process.env.AIWG_HOME`. Omit it to keep
|
|
1110
|
+
* the default precedence (`AIWG_HOME` env, else the user-home install).
|
|
1111
|
+
*/
|
|
1112
|
+
export async function readAiwgConfig(projectDir, options) {
|
|
1113
|
+
const parsed = await readAiwgConfigCandidate(projectDir);
|
|
1114
|
+
if (!parsed)
|
|
1115
|
+
return null;
|
|
1116
|
+
await validateWorkerConfigCandidate(projectDir, parsed, options);
|
|
1117
|
+
return parsed;
|
|
1118
|
+
}
|
|
897
1119
|
/**
|
|
898
1120
|
* Write aiwg.config to the repository-local control plane. When a reachable
|
|
899
1121
|
* external corpus also carries the compatibility control copy, keep it in
|
|
900
1122
|
* sync so split-root health remains deterministic.
|
|
901
1123
|
*/
|
|
902
1124
|
export async function writeAiwgConfig(projectDir, config) {
|
|
1125
|
+
// Uninstall is an explicit capability change. Retire project selectors for
|
|
1126
|
+
// removed capabilities before strict validation, and tell the operator.
|
|
1127
|
+
const previousFile = getConfigPath(projectDir);
|
|
1128
|
+
let previous = null;
|
|
1129
|
+
if (existsSync(previousFile)) {
|
|
1130
|
+
try {
|
|
1131
|
+
previous = JSON.parse(readFileSync(previousFile, 'utf8'));
|
|
1132
|
+
}
|
|
1133
|
+
catch (error) {
|
|
1134
|
+
if (!(error instanceof SyntaxError))
|
|
1135
|
+
throw error;
|
|
1136
|
+
}
|
|
1137
|
+
}
|
|
1138
|
+
const pinConfigs = loadRulePinConfigs(projectDir, config);
|
|
1139
|
+
const currentCapabilities = new Set(installedRuleCapabilities(pinConfigs));
|
|
1140
|
+
const removed = previous ? installedRuleCapabilities({ ...pinConfigs, project: previous }).filter(id => !currentCapabilities.has(id)) : [];
|
|
1141
|
+
if (removed.length && (config.rules?.always_load?.length || Object.keys(config.rules?.pin_groups ?? {}).length)) {
|
|
1142
|
+
const removedSet = new Set(removed);
|
|
1143
|
+
const rules = config.rules;
|
|
1144
|
+
const userGroups = loadRulePinConfigs(projectDir, config).user.rules?.pin_groups ?? {};
|
|
1145
|
+
const pinGroups = Object.fromEntries(Object.entries(rules.pin_groups ?? {})
|
|
1146
|
+
.map(([name, ids]) => [name, ids.filter(id => !removedSet.has(id))]).filter(([, ids]) => ids.length));
|
|
1147
|
+
const selectors = (rules.always_load ?? []).flatMap(selector => {
|
|
1148
|
+
if (selector.group && ((!Object.hasOwn(pinGroups, selector.group) && Object.hasOwn(rules.pin_groups ?? {}, selector.group))
|
|
1149
|
+
|| (!Object.hasOwn(pinGroups, selector.group) && userGroups[selector.group]?.some(id => removedSet.has(id)))))
|
|
1150
|
+
return [];
|
|
1151
|
+
if (!selector.capabilities)
|
|
1152
|
+
return [selector];
|
|
1153
|
+
const capabilities = selector.capabilities.filter(id => !removedSet.has(id));
|
|
1154
|
+
return capabilities.length ? [{ ...selector, capabilities }] : [];
|
|
1155
|
+
});
|
|
1156
|
+
config = { ...config, rules: { ...config.rules, pin_groups: pinGroups, always_load: selectors } };
|
|
1157
|
+
console.warn(`Uninstalled ${removed.join(', ')}: their rules leave the effective pinned set; retired project capability selectors/groups. User pins remain visible as inactive warnings.`);
|
|
1158
|
+
}
|
|
903
1159
|
const threatAssessmentErrors = [
|
|
904
1160
|
...validateThreatAssessmentConfig(config.security?.threatAssessment),
|
|
905
1161
|
...validateProjectConfig(config.project),
|
|
1162
|
+
...validateParallelismConfig(config.parallelism),
|
|
1163
|
+
...validateDecisionConfig(config.decision),
|
|
1164
|
+
...validateRulePinCapabilities(config.rules, installedRuleCapabilities(loadRulePinConfigs(projectDir, config)), loadRulePinConfigs(projectDir, config).user.rules?.pin_groups),
|
|
906
1165
|
];
|
|
907
1166
|
if (threatAssessmentErrors.length > 0) {
|
|
908
1167
|
throw new Error(`Invalid .aiwg/aiwg.config:\n${threatAssessmentErrors.join('\n')}`);
|
|
@@ -916,6 +1175,7 @@ export async function writeAiwgConfig(projectDir, config) {
|
|
|
916
1175
|
const gatesErrors = validateGatesConfig(config.gates);
|
|
917
1176
|
if (gatesErrors.length > 0)
|
|
918
1177
|
throw new Error(`Invalid .aiwg/aiwg.config:\n${gatesErrors.join('\n')}`);
|
|
1178
|
+
await validateWorkerConfigCandidate(projectDir, config);
|
|
919
1179
|
const localPath = getConfigPath(projectDir);
|
|
920
1180
|
const artifactDir = resolveProjectAiwgDir(projectDir);
|
|
921
1181
|
const artifactPath = join(artifactDir, CONFIG_FILENAME);
|
|
@@ -1134,4 +1394,58 @@ export async function populateDeployedTo(config, projectDir) {
|
|
|
1134
1394
|
}
|
|
1135
1395
|
return config;
|
|
1136
1396
|
}
|
|
1397
|
+
/** Shape validation alone cannot infer process/HTTP metadata or model authority. */
|
|
1398
|
+
export function validateWorkerInstallationBindings(parallelism, adapters) {
|
|
1399
|
+
const errors = [];
|
|
1400
|
+
for (const [id, cli] of Object.entries(parallelism.clis ?? {})) {
|
|
1401
|
+
if (cli.enabled === false)
|
|
1402
|
+
continue;
|
|
1403
|
+
const metadata = adapters[cli.adapter];
|
|
1404
|
+
if (!metadata)
|
|
1405
|
+
errors.push(`OG_ADAPTER_UNQUALIFIED /parallelism/clis/${id}/adapter: Unknown trusted binding`);
|
|
1406
|
+
else if (metadata.transport === 'http' ? (!cli.endpoint || cli.executable !== undefined) : (cli.endpoint !== undefined || !(cli.executable ?? metadata.executable)))
|
|
1407
|
+
errors.push(`OG_CONFIG_REFERENCE /parallelism/clis/${id}: Invalid transport binding`);
|
|
1408
|
+
}
|
|
1409
|
+
return errors;
|
|
1410
|
+
}
|
|
1411
|
+
export function validateWorkerGroupReferences(parallelism, selection, qualifiedRoles) {
|
|
1412
|
+
const errors = validateParallelismConfig(parallelism);
|
|
1413
|
+
if (errors.length)
|
|
1414
|
+
return errors;
|
|
1415
|
+
for (const [role, guidance] of Object.entries(selection.roleGuidance ?? {})) {
|
|
1416
|
+
for (const group of guidance.groups)
|
|
1417
|
+
if (!parallelism.groups?.[group])
|
|
1418
|
+
errors.push(`OG_CONFIG_REFERENCE /defaults/workerSelection/roleGuidance/${role}/groups: Unknown group ${group}`);
|
|
1419
|
+
}
|
|
1420
|
+
if (qualifiedRoles)
|
|
1421
|
+
errors.push(...validateWorkerGroupQualifications(parallelism, qualifiedRoles)
|
|
1422
|
+
.map(diagnostic => `${diagnostic.code} ${diagnostic.pointer}: ${diagnostic.message}`));
|
|
1423
|
+
return errors;
|
|
1424
|
+
}
|
|
1425
|
+
/** Structured qualification gaps retain role scope for readiness projections. */
|
|
1426
|
+
export function validateWorkerGroupQualifications(parallelism, qualifiedRoles) {
|
|
1427
|
+
const diagnostics = [];
|
|
1428
|
+
for (const [id, group] of Object.entries(parallelism.groups ?? {})) {
|
|
1429
|
+
for (const role of group.requiredRoles ?? []) {
|
|
1430
|
+
if (!group.members.some(m => m.enabled !== false && parallelism.clis?.[m.cli]?.enabled !== false
|
|
1431
|
+
&& m.roles.includes(role) && qualifiedRoles[`${id}/${m.id}`]?.includes(role))) {
|
|
1432
|
+
diagnostics.push({ code: 'OG_ADAPTER_UNQUALIFIED', pointer: `/parallelism/groups/${id}/requiredRoles`,
|
|
1433
|
+
message: `No qualified route covers ${role}`, roles: [role] });
|
|
1434
|
+
}
|
|
1435
|
+
}
|
|
1436
|
+
}
|
|
1437
|
+
return diagnostics;
|
|
1438
|
+
}
|
|
1439
|
+
/** A configured hard ceiling is eligible only where a qualified control enforces it. */
|
|
1440
|
+
export function validateWorkerBudgetEnforcement(budgets, enforceable) {
|
|
1441
|
+
if (budgets?.enforcement !== 'hard')
|
|
1442
|
+
return [];
|
|
1443
|
+
const errors = [];
|
|
1444
|
+
for (const scope of ['perRun', 'perTask'])
|
|
1445
|
+
for (const dimension of ['tokens', 'costUsd', 'wallSeconds']) {
|
|
1446
|
+
if (budgets[scope]?.[dimension] !== undefined && !enforceable.includes(dimension))
|
|
1447
|
+
errors.push(`OG_BUDGET_UNMEASURABLE /parallelism/budgets/${scope}/${dimension}: No qualified enforcing control`);
|
|
1448
|
+
}
|
|
1449
|
+
return errors;
|
|
1450
|
+
}
|
|
1137
1451
|
//# sourceMappingURL=aiwg-config.js.map
|