@aiwg/cli 2026.10.3 → 2026.10.5
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/model-capabilities.v1.json +96 -4
- package/bin/aiwg.mjs +50 -27
- 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 +69 -18
- package/dist/src/artifacts/corpus-tools/snapshot.js +1 -1
- package/dist/src/artifacts/corpus-tools/vision-extract.js +2 -0
- 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 +286 -131
- package/dist/src/artifacts/index-files.js +132 -18
- package/dist/src/artifacts/index-preparation.js +130 -0
- package/dist/src/artifacts/index-refresh.js +591 -0
- package/dist/src/artifacts/index-worker.mjs +11 -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/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 +19 -2
- package/dist/src/cli/command-log.js +2 -3
- 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 +19 -12
- package/dist/src/cli/handlers/diagnose.js +4 -3
- package/dist/src/cli/handlers/effect.js +4 -2
- package/dist/src/cli/handlers/evidence.js +3 -0
- 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 +4 -1
- package/dist/src/cli/handlers/lint.js +5 -0
- package/dist/src/cli/handlers/models.js +104 -8
- package/dist/src/cli/handlers/ralph-launcher.js +2 -0
- package/dist/src/cli/handlers/refresh.js +12 -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 +1 -2
- package/dist/src/cli/handlers/script-runner.js +3 -2
- package/dist/src/cli/handlers/session.js +19 -9
- package/dist/src/cli/handlers/sessions.js +137 -43
- package/dist/src/cli/handlers/setup-manifest.js +39 -44
- package/dist/src/cli/handlers/setup.js +41 -0
- package/dist/src/cli/handlers/steward.js +11 -0
- package/dist/src/cli/handlers/subcommands.js +57 -2
- package/dist/src/cli/handlers/uhp.js +3 -0
- package/dist/src/cli/handlers/use.js +294 -55
- package/dist/src/cli/handlers/utilities.js +26 -10
- package/dist/src/cli/handlers/version.js +8 -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 +87 -37
- package/dist/src/config/cli.js +111 -5
- package/dist/src/config/decision-config.js +106 -0
- package/dist/src/config/dispatch-thresholds-runtime.d.mts +8 -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-runtime.d.mts +16 -0
- package/dist/src/config/user-config-dir.d.mts +9 -0
- package/dist/src/config/user-config.js +2 -1
- package/dist/src/config/user-registry.js +23 -15
- package/dist/src/config/user-state.js +7 -0
- package/dist/src/decision/ask.js +203 -43
- package/dist/src/decision/context-live-qualification.js +2 -2
- package/dist/src/decision/driver.js +13 -8
- 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 +2 -15
- 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 +5 -13
- package/dist/src/decision/heldout/contract.js +30 -7
- package/dist/src/decision/jev-credentials.js +15 -1
- package/dist/src/decision/ledger.js +131 -28
- 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/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/gates/providers/loader.js +61 -17
- package/dist/src/installation/manager-command.d.mts +18 -0
- package/dist/src/installation/manager-command.mjs +14 -2
- package/dist/src/installation/manager.d.mts +49 -0
- package/dist/src/installation/manager.mjs +78 -3
- 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 +7 -1
- 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 +36 -11
- package/dist/src/mcp/config-file.d.mts +28 -0
- package/dist/src/mcp/config-layers.d.mts +36 -0
- package/dist/src/mcp/credentials.d.mts +51 -0
- package/dist/src/mcp/grok-build-config.d.mts +6 -0
- package/dist/src/mcp/grok-build-config.mjs +1 -1
- package/dist/src/mcp/helpers.mjs +3 -2
- 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/registry.js +3 -93
- package/dist/src/mcp/registry.mjs +3 -87
- 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 +37 -375
- 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-strip-mcp.d.mts +1 -0
- package/dist/src/mcp/tool-filters.d.mts +46 -0
- package/dist/src/mcp/tools/decision.mjs +12 -11
- package/dist/src/mcp/tools/discovery.mjs +62 -55
- package/dist/src/mcp/tools/orchestration.mjs +12 -2
- package/dist/src/mcp/tools/worker-invoke.mjs +1340 -23
- package/dist/src/mcp/tools/worker-shell-control.mjs +52 -21
- package/dist/src/mcp/tools/worker-stdin-runtime.mjs +3 -3
- package/dist/src/mission-protocol/codecs.js +8 -0
- package/dist/src/models/config-loader.js +7 -2
- package/dist/src/models/model-capabilities.v1.json +96 -4
- package/dist/src/models/model-discovery.js +76 -11
- package/dist/src/models/provider-policy.js +101 -7
- package/dist/src/models/qualification-digest.d.mts +2 -0
- package/dist/src/models/resolver.js +12 -10
- package/dist/src/models/worker-direct-readiness.js +65 -44
- package/dist/src/models/worker-dispatch-offer.js +3 -3
- package/dist/src/models/worker-health-readiness.js +16 -3
- package/dist/src/models/worker-installation.d.mts +39 -0
- package/dist/src/models/worker-installation.mjs +135 -28
- package/dist/src/process/bounded-child-process.d.mts +84 -0
- package/dist/src/process/bounded-child-process.mjs +43 -0
- package/dist/src/process/cli-command.d.mts +16 -0
- package/dist/src/process/signal-child.d.mts +6 -0
- package/dist/src/process/signal-child.mjs +7 -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 +12 -4
- 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/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/mission-conductor.js +38 -12
- package/dist/src/serve/worker-invocation.js +30 -10
- package/dist/src/serve/worker-stdin-frame.d.mts +10 -0
- package/dist/src/sessions/adapter-registry.js +23 -19
- package/dist/src/sessions/adapters/claude.js +39 -54
- package/dist/src/sessions/adapters/codex.js +95 -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 +10 -10
- package/dist/src/sessions/adapters/grok-build.js +11 -11
- package/dist/src/sessions/adapters/grokbot.js +24 -13
- package/dist/src/sessions/adapters/hermes.js +27 -29
- package/dist/src/sessions/adapters/muse-workspace-scan.js +31 -11
- package/dist/src/sessions/adapters/muse.js +150 -93
- 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 +29 -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 +16 -8
- package/dist/src/sessions/batch-import.js +252 -12
- package/dist/src/sessions/contracts.js +50 -3
- package/dist/src/sessions/coverage-preflight.js +27 -2
- package/dist/src/sessions/export-plan.js +4 -3
- 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 +60 -38
- package/dist/src/sessions/lifecycle.js +45 -0
- package/dist/src/sessions/policy.js +145 -26
- package/dist/src/sessions/readers.js +106 -53
- package/dist/src/sessions/refresh.js +105 -0
- package/dist/src/sessions/repository.js +151 -54
- package/dist/src/sessions/workspace-discovery.js +292 -60
- package/dist/src/sessions/workspace-matching.js +107 -0
- 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 +7 -15
- 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 +76 -18
- package/dist/src/storage/backends/fs.js +18 -76
- package/dist/src/storage/backends/obsidian.js +1 -0
- package/dist/src/tracker/forge-http.js +2 -1
- package/dist/src/update/checker.d.mts +2 -0
- 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/package.json +15 -2
- package/schemas/models/model-catalog.v1.schema.json +1 -1
- package/schemas/models/provider-model-capabilities.v1.schema.json +6 -0
- package/tools/_resolve-impl.mjs +13 -15
- package/tools/agents/deploy-agents.mjs +49 -21
- package/tools/agents/providers/base.mjs +104 -134
- 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/hermes.mjs +57 -47
- 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 +6 -4
- package/tools/providers/omp-transport.mjs +4 -2
- package/tools/ralph-external/lib/consecutive-failure-circuit-breaker.mjs +1748 -0
- package/tools/ralph-external/lib/launcher-recovery-evidence.mjs +55 -0
- package/tools/ralph-external/lib/omp-adapter.mjs +1 -1
- package/tools/ralph-external/lib/pi-adapter.mjs +0 -4
- package/tools/ralph-external/lib/provider-adapter.mjs +59 -1
- 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 +1 -1
- 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 +1194 -231
package/dist/src/config/cli.js
CHANGED
|
@@ -19,12 +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';
|
|
24
26
|
import { isInteractive } from '../cli/env.js';
|
|
25
27
|
import { exitCodeForSpawnResult } from '../cli/child-process-result.js';
|
|
26
28
|
import { projectControlPath, resolveProjectAiwgDir } from './project-artifacts.js';
|
|
27
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
|
+
}
|
|
28
52
|
/**
|
|
29
53
|
* Main CLI entry point for `aiwg config <subcommand> [args]`
|
|
30
54
|
*/
|
|
@@ -102,6 +126,11 @@ async function handleGet(config, args) {
|
|
|
102
126
|
await projectConfigGet(key, args);
|
|
103
127
|
return;
|
|
104
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
|
+
}
|
|
105
134
|
const value = await config.get(key);
|
|
106
135
|
if (value === undefined) {
|
|
107
136
|
console.log(`(not set)`);
|
|
@@ -130,6 +159,11 @@ async function handleSet(config, args) {
|
|
|
130
159
|
await projectConfigSet(key, value, args);
|
|
131
160
|
return;
|
|
132
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
|
+
}
|
|
133
167
|
await config.set(key, value);
|
|
134
168
|
console.log(`Set ${key} = ${value}`);
|
|
135
169
|
}
|
|
@@ -176,6 +210,7 @@ const ENUM_RULES = {
|
|
|
176
210
|
'setup.worker_dispatch_offer': ['declined'],
|
|
177
211
|
};
|
|
178
212
|
const BOOLEAN_FIELDS = new Set([
|
|
213
|
+
'decision.enabled',
|
|
179
214
|
'delivery.delete_branch_on_merge',
|
|
180
215
|
'delivery.require_ci_green',
|
|
181
216
|
'delivery.require_signed_commits',
|
|
@@ -194,6 +229,10 @@ const STRING_ARRAY_FIELDS = new Set([
|
|
|
194
229
|
// Integer fields with valid range constraints. Validated at `aiwg config set`.
|
|
195
230
|
// Bounds mirror the JSON schema (vscode-extension/schemas/aiwg.config.v1.json).
|
|
196
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 },
|
|
197
236
|
'parallelism.max_parallel_subagents': { min: 1, max: 50 },
|
|
198
237
|
'parallelism.max_parallel_ralph_loops': { min: 1, max: 20 },
|
|
199
238
|
'parallelism.max_parallel_mc_missions': { min: 1, max: 20 },
|
|
@@ -239,7 +278,7 @@ async function projectConfigGet(key, args) {
|
|
|
239
278
|
console.log(String(value));
|
|
240
279
|
}
|
|
241
280
|
}
|
|
242
|
-
async function projectConfigSet(key, raw, args) {
|
|
281
|
+
async function projectConfigSet(key, raw, args, quiet = false) {
|
|
243
282
|
assertSafeConfigPath(key);
|
|
244
283
|
if (key === 'defaults' || key.startsWith('defaults.')) {
|
|
245
284
|
throw new AiwgError({
|
|
@@ -270,7 +309,7 @@ async function projectConfigSet(key, raw, args) {
|
|
|
270
309
|
}
|
|
271
310
|
// Coerce booleans for known boolean fields
|
|
272
311
|
let value = raw;
|
|
273
|
-
if (key === 'parallelism' || /^parallelism\.(worker_dispatch|worker_mode|groups|clis|admission|budgets|health|retry|activation)(\.|$)/.test(key)) {
|
|
312
|
+
if (key === 'rules' || key.startsWith('rules.') || key === 'parallelism' || /^parallelism\.(worker_dispatch|worker_mode|groups|clis|admission|budgets|health|retry|activation)(\.|$)/.test(key)) {
|
|
274
313
|
try {
|
|
275
314
|
value = JSON.parse(raw);
|
|
276
315
|
}
|
|
@@ -340,7 +379,7 @@ async function projectConfigSet(key, raw, args) {
|
|
|
340
379
|
const intRule = INTEGER_FIELDS[key];
|
|
341
380
|
if (intRule) {
|
|
342
381
|
const n = Number(raw);
|
|
343
|
-
if (!Number.isInteger(n) || n < intRule.min || n > intRule.max) {
|
|
382
|
+
if (!raw.trim() || !Number.isInteger(n) || n < intRule.min || n > intRule.max) {
|
|
344
383
|
throw new AiwgError({
|
|
345
384
|
code: 'ERR_INVALID_VALUE',
|
|
346
385
|
message: `${key} must be an integer between ${intRule.min} and ${intRule.max}, got '${raw}'`,
|
|
@@ -350,9 +389,32 @@ async function projectConfigSet(key, raw, args) {
|
|
|
350
389
|
}
|
|
351
390
|
value = n;
|
|
352
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
|
+
}
|
|
353
410
|
// Read-modify-write — preserve unrelated fields. emptyConfig() seeds the
|
|
354
411
|
// base shape when no config file exists yet (e.g. brand-new project).
|
|
355
|
-
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));
|
|
356
418
|
setDottedPath(cfg, key, value);
|
|
357
419
|
const externalLinkErrors = validateExternalLinks(cfg.externalLinks);
|
|
358
420
|
if (externalLinkErrors.length > 0) {
|
|
@@ -374,7 +436,10 @@ async function projectConfigSet(key, raw, args) {
|
|
|
374
436
|
});
|
|
375
437
|
}
|
|
376
438
|
await writeAiwgConfig(projectDir, cfg);
|
|
377
|
-
|
|
439
|
+
if (!quiet)
|
|
440
|
+
console.log(`Set --project ${key} = ${raw}`);
|
|
441
|
+
if (!quiet)
|
|
442
|
+
warnStaleParallelism(cfg, projectDir, key, before);
|
|
378
443
|
}
|
|
379
444
|
/** Read a dotted path from a nested record. Returns undefined when any segment is missing. */
|
|
380
445
|
function getDottedPath(obj, key) {
|
|
@@ -441,6 +506,16 @@ async function handleValidate(config, args) {
|
|
|
441
506
|
return;
|
|
442
507
|
}
|
|
443
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
|
+
}
|
|
444
519
|
console.log(`Config directory: ${config.getPath()}\n`);
|
|
445
520
|
if (issues.length === 0) {
|
|
446
521
|
console.log('✓ All config files valid');
|
|
@@ -583,6 +658,7 @@ async function projectConfigReset(key, args) {
|
|
|
583
658
|
}
|
|
584
659
|
const primary = cfg.providers[0];
|
|
585
660
|
const defaults = getProviderParallelismDefaults(primary);
|
|
661
|
+
const before = structuredClone(getDottedPath(cfg, key));
|
|
586
662
|
if (key === 'parallelism') {
|
|
587
663
|
cfg.parallelism = {
|
|
588
664
|
max_parallel_subagents: defaults.max_parallel_subagents,
|
|
@@ -592,6 +668,7 @@ async function projectConfigReset(key, args) {
|
|
|
592
668
|
};
|
|
593
669
|
await writeAiwgConfig(projectDir, cfg);
|
|
594
670
|
console.log(`Reset --project parallelism to provider default (${primary ?? 'unknown'})`);
|
|
671
|
+
warnStaleParallelism(cfg, projectDir, key, before);
|
|
595
672
|
return;
|
|
596
673
|
}
|
|
597
674
|
if (key.startsWith('parallelism.')) {
|
|
@@ -606,6 +683,7 @@ async function projectConfigReset(key, args) {
|
|
|
606
683
|
delete current[last];
|
|
607
684
|
await writeAiwgConfig(projectDir, cfg);
|
|
608
685
|
console.log(`Reset --project ${key}`);
|
|
686
|
+
warnStaleParallelism(cfg, projectDir, key, before);
|
|
609
687
|
return;
|
|
610
688
|
}
|
|
611
689
|
const field = key.slice('parallelism.'.length);
|
|
@@ -614,6 +692,7 @@ async function projectConfigReset(key, args) {
|
|
|
614
692
|
cfg.parallelism[field] = defaults[field];
|
|
615
693
|
await writeAiwgConfig(projectDir, cfg);
|
|
616
694
|
console.log(`Reset --project ${key} to provider default (${defaults[field]})`);
|
|
695
|
+
warnStaleParallelism(cfg, projectDir, key, before);
|
|
617
696
|
return;
|
|
618
697
|
}
|
|
619
698
|
}
|
|
@@ -872,4 +951,31 @@ Examples:
|
|
|
872
951
|
aiwg config gitignore --fix
|
|
873
952
|
aiwg config gitignore --check`);
|
|
874
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
|
+
}
|
|
875
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'>;
|
|
@@ -8,9 +8,11 @@
|
|
|
8
8
|
* @module src/config/gitignore
|
|
9
9
|
*/
|
|
10
10
|
import * as fs from 'fs/promises';
|
|
11
|
+
import { existsSync, realpathSync } from 'fs';
|
|
11
12
|
import * as path from 'path';
|
|
12
13
|
import { execFile } from 'child_process';
|
|
13
14
|
import { promisify } from 'util';
|
|
15
|
+
import { boundedSpawnSync } from '../process/bounded-child-process.mjs';
|
|
14
16
|
const execFileAsync = promisify(execFile);
|
|
15
17
|
const GITIGNORE_PROBE_BASENAME = '.aiwg-ignore-probe';
|
|
16
18
|
// ===========================
|
|
@@ -205,4 +207,52 @@ export async function appendGitignore(projectRoot, patterns) {
|
|
|
205
207
|
created: !fileExists && toAdd.length > 0,
|
|
206
208
|
};
|
|
207
209
|
}
|
|
210
|
+
/**
|
|
211
|
+
* Drop files git ignores (#2555).
|
|
212
|
+
*
|
|
213
|
+
* Shared by lint and artifact discovery. `git check-ignore` is asked once for
|
|
214
|
+
* the whole set so the semantics are git's own (negations, nested
|
|
215
|
+
* `.gitignore`, `core.excludesFile`) rather than a re-implementation. Outside a
|
|
216
|
+
* repository, or without git, every file is kept. NUL delimiters preserve
|
|
217
|
+
* whitespace and quoting in filenames.
|
|
218
|
+
*/
|
|
219
|
+
export function dropGitignored(targetDir, files) {
|
|
220
|
+
if (files.length === 0)
|
|
221
|
+
return files;
|
|
222
|
+
try {
|
|
223
|
+
// Avoid starting Git for synthetic/source trees outside a repository. Resolve
|
|
224
|
+
// symlinked working directories just as Git does, and probe on every call so
|
|
225
|
+
// initializing a repository between scans takes effect immediately. Explicit
|
|
226
|
+
// Git environment and bare repositories still use Git's own discovery.
|
|
227
|
+
if (!process.env.GIT_DIR && !process.env.GIT_WORK_TREE && !process.env.GIT_COMMON_DIR) {
|
|
228
|
+
let directory = realpathSync(targetDir);
|
|
229
|
+
for (;;) {
|
|
230
|
+
if (existsSync(path.join(directory, '.git')) ||
|
|
231
|
+
(existsSync(path.join(directory, 'HEAD')) &&
|
|
232
|
+
(existsSync(path.join(directory, 'objects')) || existsSync(path.join(directory, 'commondir')))))
|
|
233
|
+
break;
|
|
234
|
+
const parent = path.dirname(directory);
|
|
235
|
+
if (parent === directory)
|
|
236
|
+
return files;
|
|
237
|
+
directory = parent;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
const result = boundedSpawnSync('git', ['check-ignore', '-z', '--stdin'], 'quick', {
|
|
241
|
+
cwd: targetDir,
|
|
242
|
+
input: `${files.join('\0')}\0`,
|
|
243
|
+
encoding: 'utf8',
|
|
244
|
+
});
|
|
245
|
+
// 0 = some paths ignored, 1 = none ignored, anything else = not a repo or
|
|
246
|
+
// git unavailable, in which case nothing is filtered.
|
|
247
|
+
if (result.error || (result.status !== 0 && result.status !== 1))
|
|
248
|
+
return files;
|
|
249
|
+
const ignored = new Set((result.stdout ?? '').split('\0').filter(Boolean));
|
|
250
|
+
if (ignored.size === 0)
|
|
251
|
+
return files;
|
|
252
|
+
return files.filter((file) => !ignored.has(file));
|
|
253
|
+
}
|
|
254
|
+
catch {
|
|
255
|
+
return files;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
208
258
|
//# sourceMappingURL=gitignore.js.map
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
export const OVERRIDABLE_INDEX_GRAPHS = ['project', 'codebase'];
|
|
2
|
+
/** Shared by config validation and graph loading, including non-CLI callers. */
|
|
3
|
+
export function validateIndexGraphOverrides(overrides) {
|
|
4
|
+
if (overrides === undefined)
|
|
5
|
+
return [];
|
|
6
|
+
if (typeof overrides !== 'object' || overrides === null || Array.isArray(overrides)) {
|
|
7
|
+
return ['index.graphOverrides: must be an object mapping supported built-in graph names to overrides'];
|
|
8
|
+
}
|
|
9
|
+
const errors = [];
|
|
10
|
+
for (const [name, raw] of Object.entries(overrides)) {
|
|
11
|
+
const where = `index.graphOverrides.${name}`;
|
|
12
|
+
if (!OVERRIDABLE_INDEX_GRAPHS.includes(name)) {
|
|
13
|
+
errors.push(`${where}: unsupported built-in graph override (supported: ${OVERRIDABLE_INDEX_GRAPHS.join(', ')})`);
|
|
14
|
+
continue;
|
|
15
|
+
}
|
|
16
|
+
if (typeof raw !== 'object' || raw === null || Array.isArray(raw)) {
|
|
17
|
+
errors.push(`${where}: must be an object`);
|
|
18
|
+
continue;
|
|
19
|
+
}
|
|
20
|
+
const override = raw;
|
|
21
|
+
for (const field of Object.keys(override)) {
|
|
22
|
+
if (field !== 'scanDirs' && field !== 'extensions') {
|
|
23
|
+
errors.push(`${where}.${field}: unknown field (supported: scanDirs, extensions)`);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
for (const field of ['scanDirs', 'extensions']) {
|
|
27
|
+
const value = override[field];
|
|
28
|
+
if (value !== undefined && (!Array.isArray(value) || value.length === 0 ||
|
|
29
|
+
!value.every(item => typeof item === 'string' && item.trim().length > 0))) {
|
|
30
|
+
errors.push(`${where}.${field}: must be a non-empty array of strings`);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return errors;
|
|
35
|
+
}
|
|
36
|
+
//# sourceMappingURL=index-graph-overrides.js.map
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
export type ProjectArtifactHealthClassification =
|
|
2
|
+
| 'local'
|
|
3
|
+
| 'healthy-split-root'
|
|
4
|
+
| 'degraded-offline'
|
|
5
|
+
| 'legacy-missing-control-plane'
|
|
6
|
+
| 'duplicated-identical'
|
|
7
|
+
| 'duplicated-divergent';
|
|
8
|
+
|
|
9
|
+
export interface ProjectControlFileHealth {
|
|
10
|
+
file: string;
|
|
11
|
+
local: boolean;
|
|
12
|
+
external: boolean;
|
|
13
|
+
identical: boolean | null;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface ProjectArtifactHealth {
|
|
17
|
+
classification: ProjectArtifactHealthClassification;
|
|
18
|
+
severity: 'ok' | 'warning' | 'error';
|
|
19
|
+
external_configured: boolean;
|
|
20
|
+
source: { kind: string; key?: string; path?: string };
|
|
21
|
+
local_control_root: string;
|
|
22
|
+
artifact_root: string;
|
|
23
|
+
external_reachable: boolean;
|
|
24
|
+
control_files: ProjectControlFileHealth[];
|
|
25
|
+
missing_local_control_files: string[];
|
|
26
|
+
divergent_control_files: string[];
|
|
27
|
+
duplicated_local_corpus_files: string[];
|
|
28
|
+
divergent_local_corpus_files: string[];
|
|
29
|
+
repairable: boolean;
|
|
30
|
+
action: string | null;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export const PROJECT_CONTROL_PLANE_FILES: readonly string[];
|
|
34
|
+
export function auditProjectArtifactHealth(
|
|
35
|
+
projectDir: string,
|
|
36
|
+
env?: Record<string, string | undefined>,
|
|
37
|
+
): ProjectArtifactHealth;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export const DEFAULT_PROJECT_AIWG_DIR: '.aiwg';
|
|
2
|
+
export const AIWG_ARTIFACTS_PATH_ENV: 'AIWG_ARTIFACTS_PATH';
|
|
3
|
+
export const PROJECT_AIWG_LOCATION_FILE: '.aiwg-location';
|
|
4
|
+
|
|
5
|
+
export type ProjectArtifactEnv = Record<string, string | undefined>;
|
|
6
|
+
|
|
7
|
+
export function expandProjectArtifactPath(pathValue: string, projectDir: string): string;
|
|
8
|
+
export function parseProjectArtifactLocation(contents: string): string | null;
|
|
9
|
+
export function readProjectArtifactLocation(projectDir: string): string | null;
|
|
10
|
+
export function resolveProjectAiwgDir(projectDir: string, env?: ProjectArtifactEnv): string;
|
|
11
|
+
export function isProjectArtifactRootExternal(projectDir: string, env?: ProjectArtifactEnv): boolean;
|
|
12
|
+
export function resolveProjectAiwgDirForWrite(projectDir: string, env?: ProjectArtifactEnv): string;
|
|
13
|
+
export function resolveProjectControlDir(projectDir: string): string;
|
|
14
|
+
export function projectAiwgPath(projectDir: string, ...segments: string[]): string;
|
|
15
|
+
export function projectAiwgWritePath(projectDir: string, ...segments: string[]): string;
|
|
16
|
+
export function projectControlPath(projectDir: string, ...segments: string[]): string;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export interface UserConfigDirOptions {
|
|
2
|
+
configDir?: string;
|
|
3
|
+
env?: Record<string, string | undefined>;
|
|
4
|
+
homeDir?: string;
|
|
5
|
+
exists?: (path: string) => boolean;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function resolveUserConfigDir(options?: UserConfigDirOptions): string;
|
|
9
|
+
export function userConfigFile(name: string, options?: UserConfigDirOptions): string;
|
|
@@ -24,6 +24,7 @@ import { resolveUserConfigDir } from './user-config-dir.mjs';
|
|
|
24
24
|
*/
|
|
25
25
|
export const KNOWN_CONFIG_FILES = [
|
|
26
26
|
{ filename: 'config.yaml', description: 'Global AIWG user preferences' },
|
|
27
|
+
{ filename: 'aiwg.config', description: 'Operator rule pins and capability groups' },
|
|
27
28
|
{ filename: 'models.json', description: 'Model tier and provider overrides' },
|
|
28
29
|
{ filename: 'ops.json', description: 'Ops workspace registry' },
|
|
29
30
|
{ filename: 'mcp-servers.json', description: 'MCP server registry (single source of truth)' },
|
|
@@ -122,7 +123,7 @@ export class UserConfig {
|
|
|
122
123
|
try {
|
|
123
124
|
await access(filePath);
|
|
124
125
|
const content = await readFile(filePath, 'utf-8');
|
|
125
|
-
if (spec.filename.endsWith('.json')) {
|
|
126
|
+
if (spec.filename.endsWith('.json') || spec.filename === 'aiwg.config') {
|
|
126
127
|
result[spec.filename] = JSON.parse(content);
|
|
127
128
|
}
|
|
128
129
|
else {
|
|
@@ -12,7 +12,6 @@
|
|
|
12
12
|
* --scope user` works from any cwd.
|
|
13
13
|
*/
|
|
14
14
|
import { readFile, writeFile, mkdir } from 'node:fs/promises';
|
|
15
|
-
import { existsSync } from 'node:fs';
|
|
16
15
|
import { dirname } from 'node:path';
|
|
17
16
|
import { userScopeConfigPath } from '../cli/scope-resolver.js';
|
|
18
17
|
/**
|
|
@@ -46,28 +45,37 @@ export function emptyUserRegistry() {
|
|
|
46
45
|
return { version: '1', installed: {} };
|
|
47
46
|
}
|
|
48
47
|
/**
|
|
49
|
-
* Read the
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
* warning (the broken file is preserved so the operator can inspect it).
|
|
48
|
+
* Read the user registry while preserving whether empty means absent or
|
|
49
|
+
* unreadable. Callers making adoption/readiness claims must not collapse a
|
|
50
|
+
* corrupt or inaccessible registry into proof that an addon is missing.
|
|
53
51
|
*/
|
|
54
|
-
export async function
|
|
55
|
-
const
|
|
56
|
-
if (!existsSync(path))
|
|
57
|
-
return emptyUserRegistry();
|
|
52
|
+
export async function inspectUserRegistry() {
|
|
53
|
+
const registryFile = userRegistryPath();
|
|
58
54
|
try {
|
|
59
|
-
const raw = await readFile(
|
|
55
|
+
const raw = await readFile(registryFile, 'utf-8');
|
|
60
56
|
const parsed = JSON.parse(raw);
|
|
61
|
-
if (!parsed.installed || typeof parsed.installed !== 'object') {
|
|
62
|
-
|
|
57
|
+
if (!parsed.installed || typeof parsed.installed !== 'object' || Array.isArray(parsed.installed)) {
|
|
58
|
+
throw new Error('installed must be an object');
|
|
63
59
|
}
|
|
64
|
-
return { version: '1', installed: parsed.installed };
|
|
60
|
+
return { registry: { version: '1', installed: parsed.installed }, status: 'available' };
|
|
65
61
|
}
|
|
66
62
|
catch (err) {
|
|
67
|
-
|
|
68
|
-
|
|
63
|
+
if (err.code === 'ENOENT') {
|
|
64
|
+
return { registry: emptyUserRegistry(), status: 'absent' };
|
|
65
|
+
}
|
|
66
|
+
process.stderr.write(`[user-registry] failed to parse ${registryFile}: ${err instanceof Error ? err.message : String(err)} — using empty registry\n`);
|
|
67
|
+
return { registry: emptyUserRegistry(), status: 'unreadable' };
|
|
69
68
|
}
|
|
70
69
|
}
|
|
70
|
+
/**
|
|
71
|
+
* Read the per-user registry. Returns an empty registry when the file doesn't
|
|
72
|
+
* exist — that's the normal case before the operator's first `aiwg use --scope
|
|
73
|
+
* user`. On parse failure returns the empty registry and writes a stderr
|
|
74
|
+
* warning (the broken file is preserved so the operator can inspect it).
|
|
75
|
+
*/
|
|
76
|
+
export async function readUserRegistry() {
|
|
77
|
+
return (await inspectUserRegistry()).registry;
|
|
78
|
+
}
|
|
71
79
|
/**
|
|
72
80
|
* Persist the user registry. Creates the parent directory if missing.
|
|
73
81
|
*/
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import os from 'node:os';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
/** Host-local AIWG state, shared by CLI telemetry and maintenance leases. */
|
|
4
|
+
export function getUserStateDir(env = process.env) {
|
|
5
|
+
return path.join(env.XDG_STATE_HOME || path.join(env.HOME || os.homedir(), '.local', 'state'), 'aiwg');
|
|
6
|
+
}
|
|
7
|
+
//# sourceMappingURL=user-state.js.map
|