@aiwg/cli 2026.10.5 → 2026.10.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/agentic/code/addons/composition-engine/lib/runtime.mjs +1401 -0
- package/agentic/code/addons/composition-engine/lib/validator.mjs +513 -0
- package/agentic/code/addons/composition-engine/schemas/flow-graph.schema.json +372 -0
- package/agentic/code/providers/model-capabilities.v1.json +74 -50
- package/bin/aiwg.mjs +7 -3
- package/dist/src/a2a/agent-card.js +11 -0
- package/dist/src/api/index.d.ts +1 -0
- package/dist/src/api/index.js +1 -0
- package/dist/src/artifacts/browser-export.js +1 -0
- package/dist/src/artifacts/citation-parser.js +8 -1
- package/dist/src/artifacts/cli.js +57 -6
- package/dist/src/artifacts/fortemi-core-query-adapter.js +2 -0
- package/dist/src/artifacts/fortemi-core-sync.js +26 -8
- package/dist/src/artifacts/fortemi-shard-contract/2.2.0/contract.json +103 -0
- package/dist/src/artifacts/fortemi-shard-contract/2.2.0/full-v1-embedding-config.schema.json +212 -0
- package/dist/src/artifacts/fortemi-shard-contract/2.2.0/full-v1-embedding-set.schema.json +240 -0
- package/dist/src/artifacts/fortemi-shard-contract/2.2.0/full-v1-manifest.schema.json +451 -0
- package/dist/src/artifacts/fortemi-shard-export.js +284 -0
- package/dist/src/artifacts/graph-query.js +18 -8
- package/dist/src/artifacts/index-adapters.js +339 -0
- package/dist/src/artifacts/index-builder.js +143 -14
- package/dist/src/artifacts/index-files.js +15 -4
- package/dist/src/artifacts/index-preparation.js +2 -0
- package/dist/src/artifacts/index-reader.js +44 -0
- package/dist/src/artifacts/index-refresh.js +114 -55
- package/dist/src/artifacts/index-status.js +20 -5
- package/dist/src/artifacts/memory-maintenance.js +503 -0
- package/dist/src/artifacts/prebuilt-relations.js +3 -1
- package/dist/src/artifacts/query-engine.js +263 -108
- package/dist/src/artifacts/relations-builder.js +2 -2
- package/dist/src/artifacts/source-graph.js +2 -2
- package/dist/src/artifacts/types.js +9 -1
- package/dist/src/auth/client.js +5 -1
- package/dist/src/auth/credential-store.js +5 -1
- package/dist/src/auth/sensitive-input.js +151 -0
- package/dist/src/channel/manager.mjs +3 -0
- package/dist/src/cli/handlers/agentcard.js +47 -25
- package/dist/src/cli/handlers/auth.js +22 -2
- package/dist/src/cli/handlers/decision.js +22 -3
- package/dist/src/cli/handlers/effect.js +4 -84
- package/dist/src/cli/handlers/guided-setup.js +470 -0
- package/dist/src/cli/handlers/help.js +2 -2
- package/dist/src/cli/handlers/housekeeper.js +1110 -0
- package/dist/src/cli/handlers/index.js +4 -2
- package/dist/src/cli/handlers/init.js +41 -6
- package/dist/src/cli/handlers/install.js +6 -0
- package/dist/src/cli/handlers/local-executor.js +5 -3
- package/dist/src/cli/handlers/marketplace.js +15 -4
- package/dist/src/cli/handlers/mc.js +509 -53
- package/dist/src/cli/handlers/models.js +32 -2
- package/dist/src/cli/handlers/ralph-launcher.js +76 -4
- package/dist/src/cli/handlers/rules.js +1 -1
- package/dist/src/cli/handlers/script-runner.js +73 -2
- package/dist/src/cli/handlers/serve.js +5 -3
- package/dist/src/cli/handlers/sessions.js +1 -0
- package/dist/src/cli/handlers/setup.js +432 -1
- package/dist/src/cli/handlers/skill-lint.js +1 -1
- package/dist/src/cli/handlers/subcommands.js +118 -8
- package/dist/src/cli/handlers/use.js +335 -107
- package/dist/src/cli/handlers/workspace.js +3 -1
- package/dist/src/cli/prompt-utils.js +81 -0
- package/dist/src/cli/router.js +16 -1
- package/dist/src/config/aiwg-config.js +42 -0
- package/dist/src/config/cli.js +7 -3
- package/dist/src/config/decision-config.js +14 -1
- package/dist/src/config/gitignore.js +4 -0
- package/dist/src/config/native-sandbox-warnings.js +25 -0
- package/dist/src/config/workspace.js +2 -1
- package/dist/src/contributors/validation.js +6 -5
- package/dist/src/decision/adapters/jev.js +20 -31
- package/dist/src/decision/admission.js +7 -3
- package/dist/src/decision/ask.js +6 -1
- package/dist/src/decision/benchmark.js +482 -0
- package/dist/src/decision/ledger.js +22 -3
- package/dist/src/decision/pricing.js +147 -0
- package/dist/src/decision/qualification/gate-evidence.js +23 -18
- package/dist/src/decision/qualification/load-staged.js +576 -0
- package/dist/src/decision/qualification/load.js +109 -33
- package/dist/src/decision/qualification/runner.js +12 -5
- package/dist/src/decision/review/service.js +6 -4
- package/dist/src/decision/review/validate.js +23 -2
- package/dist/src/decision/savings.js +8 -3
- package/dist/src/discovery/index.js +492 -0
- package/dist/src/discovery/interview.js +238 -0
- package/dist/src/discovery/plan-apply.js +567 -0
- package/dist/src/discovery/plan-store.js +171 -0
- package/dist/src/discovery/plan-types.js +2 -0
- package/dist/src/discovery/plan-validation.js +139 -0
- package/dist/src/discovery/types.js +2 -0
- package/dist/src/effects/host.js +91 -0
- package/dist/src/extensions/commands/definitions.js +49 -9
- package/dist/src/extensions/manifest.js +71 -44
- package/dist/src/extensions/project-local-discovery.js +1 -1
- package/dist/src/extensions/project-local-gitignore.js +2 -0
- package/dist/src/extensions/project-quickref.js +18 -17
- package/dist/src/extensions/validation.js +32 -29
- package/dist/src/housekeeper/daemon-config.js +148 -0
- package/dist/src/housekeeper/ipc.js +84 -0
- package/dist/src/housekeeper/lifecycle.js +148 -0
- package/dist/src/housekeeper/platform.js +35 -0
- package/dist/src/housekeeper/status.js +70 -0
- package/dist/src/housekeeper/units.js +114 -0
- package/dist/src/issues/cli.js +16 -5
- package/dist/src/issues/live.js +44 -12
- package/dist/src/jobs/flow.js +22 -21
- package/dist/src/lint/rule-activation.js +20 -5
- package/dist/src/lint/runner.js +1 -1
- package/dist/src/marketplace/exchange.js +18 -6
- package/dist/src/marketplace/provenance.js +3 -2
- package/dist/src/mcp/adapters/codex-runtime.js +46 -76
- package/dist/src/mcp/cli.mjs +22 -96
- package/dist/src/mcp/config-file.mjs +6 -2
- package/dist/src/mcp/config-layers.mjs +23 -7
- package/dist/src/mcp/grok-build-config.mjs +2 -1
- package/dist/src/mcp/muse-native-config.mjs +2 -1
- package/dist/src/mcp/profiles.js +18 -31
- package/dist/src/mcp/profiles.mjs +18 -32
- package/dist/src/mcp/registry.js +7 -27
- package/dist/src/mcp/registry.mjs +7 -28
- package/dist/src/mcp/server.mjs +34 -1
- package/dist/src/mcp/tools/agentic-sandbox.mjs +8 -7
- package/dist/src/mcp/tools/command-run.mjs +3 -2
- package/dist/src/mcp/tools/decision.mjs +49 -48
- package/dist/src/mcp/tools/discovery.mjs +46 -21
- package/dist/src/mcp/tools/orchestration.mjs +22 -22
- package/dist/src/mcp/tools/subsystems.mjs +9 -8
- package/dist/src/mcp/tools/worker-invoke.mjs +30 -6
- package/dist/src/mcp/tools/writer-profiles.mjs +1 -1
- package/dist/src/memory/audit.js +719 -0
- package/dist/src/memory/canonical-context.js +392 -13
- package/dist/src/memory/canonical-maintenance.js +95 -0
- package/dist/src/memory/cli-result.js +22 -0
- package/dist/src/memory/cli.js +176 -0
- package/dist/src/memory/context-pack.js +57 -6
- package/dist/src/memory/cost.js +512 -0
- package/dist/src/memory/firewall-classification.d.mts +7 -0
- package/dist/src/memory/firewall-classification.mjs +32 -0
- package/dist/src/memory/index.js +8 -0
- package/dist/src/memory/line-maintenance.js +68 -0
- package/dist/src/memory/maintenance-cli.js +286 -0
- package/dist/src/memory/maintenance-contract.js +136 -0
- package/dist/src/memory/maintenance-index.js +18 -0
- package/dist/src/memory/maintenance-owner.js +2 -0
- package/dist/src/memory/maintenance-record.js +138 -0
- package/dist/src/memory/maintenance.js +254 -0
- package/dist/src/memory/project-registry.js +2 -1
- package/dist/src/memory/provider-memory-adapter.js +154 -0
- package/dist/src/memory/recommend-cli.js +83 -0
- package/dist/src/memory/recommend.js +269 -0
- package/dist/src/memory/scheduled.js +81 -0
- package/dist/src/memory/taxonomy.js +44 -0
- package/dist/src/mission-protocol/codecs.js +19 -9
- package/dist/src/mission-protocol/worker-status.js +91 -1
- package/dist/src/models/config-loader.js +11 -1
- package/dist/src/models/model-capabilities.v1.json +74 -50
- package/dist/src/models/provider-policy.js +125 -62
- package/dist/src/models/resolver.js +45 -8
- package/dist/src/models/worker-installation.d.mts +8 -1
- package/dist/src/models/worker-installation.mjs +231 -38
- package/dist/src/models/wrapper-route.js +1 -1
- package/dist/src/output-modes/registry.js +8 -7
- package/dist/src/packages/adapters/git.js +99 -23
- package/dist/src/packages/adapters/gitea.js +8 -27
- package/dist/src/packages/adapters/github.js +7 -25
- package/dist/src/packages/registry.js +4 -4
- package/dist/src/plugin/metadata-validator.js +1 -1
- package/dist/src/process/cli-command.d.mts +5 -0
- package/dist/src/process/cli-command.mjs +42 -0
- package/dist/src/providers/deploy-version.d.mts +1 -0
- package/dist/src/providers/deploy-version.mjs +17 -0
- package/dist/src/providers/provider-definitions.js +19 -16
- package/dist/src/providers/skill-ownership.d.mts +11 -2
- package/dist/src/providers/skill-ownership.mjs +35 -17
- package/dist/src/rules/activation.d.mts +11 -2
- package/dist/src/rules/activation.mjs +82 -6
- package/dist/src/rules/inventory.js +1 -1
- package/dist/src/rules/rule-frontmatter.schema.json +2 -0
- package/dist/src/schema/zod-validation.d.mts +6 -0
- package/dist/src/schema/zod-validation.mjs +50 -0
- package/dist/src/serve/executor-registry.js +297 -20
- package/dist/src/serve/executor-token.d.mts +1 -0
- package/dist/src/serve/executor-token.mjs +8 -0
- package/dist/src/serve/shared-host-scheduler.js +1102 -31
- package/dist/src/serve/worker-invocation.js +124 -15
- package/dist/src/sessions/adapters/claude.js +19 -18
- package/dist/src/sessions/adapters/codex.js +10 -9
- package/dist/src/sessions/adapters/copilot.js +14 -13
- package/dist/src/sessions/adapters/cursor.js +19 -18
- package/dist/src/sessions/adapters/deepseek-harness.js +9 -8
- package/dist/src/sessions/adapters/factory.js +11 -10
- package/dist/src/sessions/adapters/generic.js +32 -23
- package/dist/src/sessions/adapters/grokbot.js +35 -35
- package/dist/src/sessions/adapters/hermes.js +20 -19
- package/dist/src/sessions/adapters/muse.js +50 -49
- package/dist/src/sessions/adapters/omp.js +5 -4
- package/dist/src/sessions/adapters/openclaw.js +30 -29
- package/dist/src/sessions/adapters/opencode.js +26 -26
- package/dist/src/sessions/adapters/openhuman.js +24 -23
- package/dist/src/sessions/adapters/pi.js +5 -4
- package/dist/src/sessions/adapters/windsurf.js +8 -8
- package/dist/src/sessions/analytics.js +10 -9
- package/dist/src/sessions/candidates.js +7 -6
- package/dist/src/sessions/contracts.js +63 -53
- package/dist/src/sessions/output-registration.js +11 -10
- package/dist/src/sessions/workspace-discovery.js +3 -4
- package/dist/src/smiths/context-pipeline/aiwg-md.js +18 -1
- package/dist/src/smiths/context-pipeline/claude-hook.js +46 -10
- package/dist/src/smiths/context-pipeline/context-file-safety.js +330 -9
- package/dist/src/smiths/context-pipeline/finalization.js +3 -2
- package/dist/src/smiths/context-pipeline/generator.js +7 -19
- package/dist/src/smiths/context-pipeline/legacy-inject.js +3 -10
- package/dist/src/smiths/context-pipeline/managed-hook.js +5 -5
- package/dist/src/smiths/context-pipeline/parallelism-section.js +72 -5
- package/dist/src/smiths/context-pipeline/workspace-context.js +110 -22
- package/dist/src/tracker/project-inputs.js +2 -0
- package/dist/src/update/checker.d.mts +4 -0
- package/dist/src/update/checker.mjs +14 -0
- package/dist/src/update/notifier.mjs +2 -4
- package/dist/src/writing/exemplar-selection.js +6 -6
- package/dist/src/writing/voice-evaluation.js +24 -23
- package/dist/src/writing/writer-migration.js +14 -13
- package/dist/src/writing/writer-profile.js +19 -18
- package/dist/src/writing/writing-brief.js +18 -17
- package/dist/src/writing/writing-receipt.js +47 -46
- package/package.json +5 -2
- package/schemas/decision/DecisionReview.schema.json +78 -1
- package/schemas/mission-v1.schema.json +3 -0
- package/tools/agents/deploy-agents.mjs +67 -39
- package/tools/agents/providers/antigravity.mjs +6 -1
- package/tools/agents/providers/base.mjs +24 -14
- package/tools/agents/providers/claude.mjs +6 -3
- package/tools/agents/providers/codex.mjs +54 -57
- package/tools/agents/providers/context-write.mjs +28 -0
- package/tools/agents/providers/copilot.mjs +13 -4
- package/tools/agents/providers/cursor.mjs +1 -1
- package/tools/agents/providers/deepseek-harness.mjs +1 -1
- package/tools/agents/providers/factory.mjs +7 -1
- package/tools/agents/providers/grok-build.mjs +18 -2
- package/tools/agents/providers/grokbot.mjs +1 -1
- package/tools/agents/providers/hermes.mjs +12 -15
- package/tools/agents/providers/muse-hooks.mjs +21 -14
- package/tools/agents/providers/muse.mjs +7 -2
- package/tools/agents/providers/omp.mjs +31 -5
- package/tools/agents/providers/opencode.mjs +14 -5
- package/tools/agents/providers/openhuman.mjs +1 -1
- package/tools/agents/providers/pi.mjs +1 -1
- package/tools/agents/providers/provider-prepare-coordinator.mjs +29 -9
- package/tools/agents/providers/rule-delivery.mjs +289 -80
- package/tools/agents/providers/warp.mjs +4 -1
- package/tools/agents/providers/windsurf.mjs +13 -3
- package/tools/cli/workspace-status.mjs +5 -0
- package/tools/commands/deploy-prompts-codex.mjs +11 -8
- package/tools/ralph-external/lib/codex-adapter.mjs +6 -2
- package/tools/ralph-external/lib/consecutive-failure-circuit-breaker.mjs +26 -0
- package/tools/ralph-external/lib/launcher-recovery-evidence.mjs +7 -1
- package/tools/ralph-external/lib/muse-adapter.mjs +12 -2
- package/tools/ralph-external/lib/recovery-probe.mjs +14 -4
- package/tools/ralph-external/lib/worker-health.mjs +1 -1
- package/tools/ralph-external/lib/worker-isolation.mjs +65 -9
- package/tools/ralph-external/process-monitor.mjs +49 -8
- package/tools/ralph-external/session-launcher.mjs +405 -35
- package/tools/skills/deploy-skills-codex.mjs +11 -7
- package/vscode-extension/schemas/aiwg.config.v1.json +11 -1
|
@@ -0,0 +1,567 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Guided setup plan review and apply (epic #3449 slice A4).
|
|
3
|
+
*
|
|
4
|
+
* A3 records answers and builds `actions[]` without writing anything outside
|
|
5
|
+
* the plan file. This module owns the read-only review of a saved plan and
|
|
6
|
+
* the apply engine that executes `remainingPlanActions` through injected
|
|
7
|
+
* owners. The module itself writes nothing: persistence (`save`) and every
|
|
8
|
+
* side effect (config writers, `use` deployment, registry, prompts) arrive
|
|
9
|
+
* through the `PlanReviewIO` / `PlanApplyIO` boundaries so dry runs and unit
|
|
10
|
+
* tests can prove zero-write behavior.
|
|
11
|
+
*
|
|
12
|
+
* Credential values are resolved only at apply time and are never stored on
|
|
13
|
+
* the plan, in receipts, or in rendered output. Only the reference
|
|
14
|
+
* (`env:NAME`, `file:PATH`, `store:NAME`, `stdin`) is ever rendered or persisted.
|
|
15
|
+
*/
|
|
16
|
+
import { relative, resolve, sep, isAbsolute, join } from 'node:path';
|
|
17
|
+
import { resolveSensitiveInput } from '../auth/sensitive-input.js';
|
|
18
|
+
import { revalidateSetupPlan, remainingPlanActions } from './plan-store.js';
|
|
19
|
+
import { questionId } from './interview.js';
|
|
20
|
+
/** Apply order for plan actions. Dependencies flow config -> verify -> deploy -> register. */
|
|
21
|
+
export const PLAN_APPLY_KIND_ORDER = [
|
|
22
|
+
'configure-provider',
|
|
23
|
+
'verify-provider',
|
|
24
|
+
'write-project-config',
|
|
25
|
+
'deploy-framework',
|
|
26
|
+
'register-project',
|
|
27
|
+
'record-decline',
|
|
28
|
+
'configure-jev',
|
|
29
|
+
'configure-worker-dispatch',
|
|
30
|
+
];
|
|
31
|
+
/**
|
|
32
|
+
* Optional actions report truthfully on failure but never block core setup.
|
|
33
|
+
* Integrations (framework deploys) are optional: a failing deploy must not
|
|
34
|
+
* fake full success and must not prevent config/registry writes.
|
|
35
|
+
*/
|
|
36
|
+
export const OPTIONAL_PLAN_ACTION_KINDS = new Set(['deploy-framework', 'configure-jev', 'configure-worker-dispatch']);
|
|
37
|
+
export function isOptionalPlanAction(kind) {
|
|
38
|
+
return OPTIONAL_PLAN_ACTION_KINDS.has(kind);
|
|
39
|
+
}
|
|
40
|
+
export function sortPlanActionsForApply(actions) {
|
|
41
|
+
const rank = (kind) => {
|
|
42
|
+
const index = PLAN_APPLY_KIND_ORDER.indexOf(kind);
|
|
43
|
+
return index < 0 ? PLAN_APPLY_KIND_ORDER.length : index;
|
|
44
|
+
};
|
|
45
|
+
return [...actions].sort((a, b) => rank(a.kind) - rank(b.kind) || a.id.localeCompare(b.id));
|
|
46
|
+
}
|
|
47
|
+
/** Filesystem-backed review IO. Reads only; used by the CLI (never by dry-run tests). */
|
|
48
|
+
export function defaultPlanReviewIO() {
|
|
49
|
+
return {
|
|
50
|
+
async exists(path) {
|
|
51
|
+
const { stat } = await import('node:fs/promises');
|
|
52
|
+
try {
|
|
53
|
+
await stat(path);
|
|
54
|
+
return true;
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
},
|
|
60
|
+
async installMode() {
|
|
61
|
+
try {
|
|
62
|
+
const { inspectInstallation } = await import('../installation/manager.mjs');
|
|
63
|
+
const status = inspectInstallation({ createIfMissing: false });
|
|
64
|
+
const method = status.identity?.method ?? status.actualMethod ?? 'unknown';
|
|
65
|
+
return {
|
|
66
|
+
devMode: status.identity?.runMode === 'development' || method === 'source',
|
|
67
|
+
method,
|
|
68
|
+
root: status.frameworkRoot ?? '',
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
return { devMode: false, method: 'unknown', root: '' };
|
|
73
|
+
}
|
|
74
|
+
},
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
/** The credential-source answer recorded for a workspace/provider pair, if any. */
|
|
78
|
+
export function credentialReferenceFor(plan, workspace, provider) {
|
|
79
|
+
const projectRoot = projectRootForWorkspace(plan, workspace) ?? workspace;
|
|
80
|
+
const value = plan.answers[questionId('locations', 'credential-source', projectRoot, provider)]?.value;
|
|
81
|
+
return isCredentialReference(value) ? value : undefined;
|
|
82
|
+
}
|
|
83
|
+
function isCredentialReference(value) {
|
|
84
|
+
return !!value && typeof value === 'object' && !Array.isArray(value) &&
|
|
85
|
+
value.kind === 'credential-ref';
|
|
86
|
+
}
|
|
87
|
+
/** The inventory project owning a workspace (workspaces may be selected package roots). */
|
|
88
|
+
export function projectRootForWorkspace(plan, workspace) {
|
|
89
|
+
// Selected packages also have their own discovery entry for fact checks,
|
|
90
|
+
// while their interview answers belong to the selecting monorepo root.
|
|
91
|
+
const selectedFrom = plan.inventory.projects.find(project => project.packageRoots.includes(workspace) &&
|
|
92
|
+
plan.answers[questionId('targets', 'root', project.root)]?.value === workspace);
|
|
93
|
+
if (selectedFrom)
|
|
94
|
+
return selectedFrom.root;
|
|
95
|
+
if (plan.inventory.projects.some(project => project.root === workspace))
|
|
96
|
+
return workspace;
|
|
97
|
+
return plan.inventory.projects.find(project => project.packageRoots.includes(workspace))?.root;
|
|
98
|
+
}
|
|
99
|
+
function answerFor(plan, workspace, block, topic, provider) {
|
|
100
|
+
const projectRoot = projectRootForWorkspace(plan, workspace) ?? workspace;
|
|
101
|
+
return plan.answers[questionId(block, topic, projectRoot, provider)]?.value;
|
|
102
|
+
}
|
|
103
|
+
function configPathOf(action) {
|
|
104
|
+
return action.dependsOn.find(fact => fact.kind === 'path')?.path;
|
|
105
|
+
}
|
|
106
|
+
/** Provider config path from the sibling configure-provider action, if the plan recorded one. */
|
|
107
|
+
export function configPathFor(plan, workspace, provider) {
|
|
108
|
+
return plan.actions.find(action => action.workspace === workspace && action.provider === provider &&
|
|
109
|
+
action.kind === 'configure-provider')?.dependsOn.find(fact => fact.kind === 'path')?.path;
|
|
110
|
+
}
|
|
111
|
+
/** Effective destination bucket for one action. */
|
|
112
|
+
export function destinationOf(action) {
|
|
113
|
+
switch (action.kind) {
|
|
114
|
+
case 'configure-provider':
|
|
115
|
+
return action.scope === 'user'
|
|
116
|
+
? `user settings (${action.provider} user config)`
|
|
117
|
+
: `per-workspace ${action.workspace} (${action.provider} config)`;
|
|
118
|
+
case 'verify-provider':
|
|
119
|
+
return `verification only (${action.provider} in ${action.workspace}; no writes)`;
|
|
120
|
+
case 'write-project-config':
|
|
121
|
+
return `per-workspace ${action.workspace} (.aiwg/aiwg.config)`;
|
|
122
|
+
case 'deploy-framework':
|
|
123
|
+
return `integration (framework deployment for ${action.provider} in ${action.workspace})`;
|
|
124
|
+
case 'register-project':
|
|
125
|
+
return `integration (project registry entry for ${action.workspace})`;
|
|
126
|
+
case 'configure-jev':
|
|
127
|
+
return 'user settings (Jev credential writer via aiwg decision setup jev)';
|
|
128
|
+
case 'configure-worker-dispatch':
|
|
129
|
+
return `integration (direct workers in ${action.workspace})`;
|
|
130
|
+
case 'record-decline':
|
|
131
|
+
return `per-workspace ${action.workspace} (decline marker)`;
|
|
132
|
+
default:
|
|
133
|
+
return `per-workspace ${action.workspace} (${action.kind})`;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
/** One-line diff description for one action. Reads answers only; never touches disk. */
|
|
137
|
+
export function diffOfAction(plan, action) {
|
|
138
|
+
switch (action.kind) {
|
|
139
|
+
case 'configure-provider': {
|
|
140
|
+
const path = configPathOf(action);
|
|
141
|
+
const reference = credentialReferenceFor(plan, action.workspace, action.provider);
|
|
142
|
+
// Hyphenated on purpose: the plan redaction gate rejects
|
|
143
|
+
// `credential <value>` phrasing anywhere in persisted content.
|
|
144
|
+
// configure-provider writes nothing: an existing file is preserved and
|
|
145
|
+
// a missing one is owned by deploy-framework (`aiwg use`).
|
|
146
|
+
const credential = reference ? `credential-ref ${reference.source}:${reference.name}` : 'no credential-reference recorded';
|
|
147
|
+
return path
|
|
148
|
+
? `preserve ${path} if present, no writes (${credential}); provider files owned by deploy-framework`
|
|
149
|
+
: `no writes for ${action.provider} config (${credential}); provider files owned by deploy-framework`;
|
|
150
|
+
}
|
|
151
|
+
case 'verify-provider':
|
|
152
|
+
return `read back ${action.provider} installation and config; no writes, no secrets`;
|
|
153
|
+
case 'write-project-config': {
|
|
154
|
+
const providers = plan.actions
|
|
155
|
+
.filter(other => other.workspace === action.workspace && other.kind === 'configure-provider')
|
|
156
|
+
.map(other => other.provider);
|
|
157
|
+
return `merge providers [${providers.join(', ') || 'none'}] and policy answers into ${action.workspace}/.aiwg/aiwg.config`;
|
|
158
|
+
}
|
|
159
|
+
case 'deploy-framework': {
|
|
160
|
+
const intent = answerFor(plan, action.workspace, 'intent', 'use');
|
|
161
|
+
return `deploy ${typeof intent === 'string' ? intent : 'suggested'} framework for ${action.provider} via the aiwg use path`;
|
|
162
|
+
}
|
|
163
|
+
case 'register-project':
|
|
164
|
+
return `register ${action.workspace} in the project memory registry`;
|
|
165
|
+
case 'configure-jev':
|
|
166
|
+
return 'configure Jev through aiwg decision setup jev; API input via stdin at apply only';
|
|
167
|
+
case 'configure-worker-dispatch':
|
|
168
|
+
return 'enable direct dispatch and qualify with aiwg doctor --probe-workers --qualify; installed CLIs are not ready until qualified';
|
|
169
|
+
case 'record-decline':
|
|
170
|
+
return `record explicit decline in ${action.workspace} config (no activation)`;
|
|
171
|
+
default:
|
|
172
|
+
return `apply ${action.kind} for ${action.provider} in ${action.workspace}`;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Split revalidation changes into blocking vs advisory for one action.
|
|
177
|
+
*
|
|
178
|
+
* `verify-provider` is a read-only qualification: it re-observes the tool
|
|
179
|
+
* from fresh inventory at apply time (the owner refuses when the tool is
|
|
180
|
+
* not installed). An `unobserved` version (unknown at plan and apply time,
|
|
181
|
+
* the normal presence-only discovery case) therefore does not invalidate
|
|
182
|
+
* the check — it rides along as an advisory note. Every other reason, and
|
|
183
|
+
* every other action kind, stays blocking: writes are never applied on
|
|
184
|
+
* stale facts.
|
|
185
|
+
*/
|
|
186
|
+
function splitStale(action, stale) {
|
|
187
|
+
if (!stale)
|
|
188
|
+
return { blocking: undefined, advisory: [] };
|
|
189
|
+
if (action.kind !== 'verify-provider' && action.kind !== 'configure-provider')
|
|
190
|
+
return { blocking: stale, advisory: [] };
|
|
191
|
+
if (action.kind === 'configure-provider') {
|
|
192
|
+
// configure-provider writes nothing, so the recorded provider path is
|
|
193
|
+
// informational only: the owner re-checks the filesystem at apply time
|
|
194
|
+
// (preserving an existing file, deferring a missing one to
|
|
195
|
+
// deploy-framework). Path-fact changes therefore never block it, while
|
|
196
|
+
// tool/layout facts still do.
|
|
197
|
+
const blocking = stale.changes.filter(change => change.fact.kind !== 'path');
|
|
198
|
+
return { blocking: blocking.length > 0 ? { ...stale, changes: blocking } : undefined, advisory: [] };
|
|
199
|
+
}
|
|
200
|
+
const isAdvisory = (change) => change.reason === 'unobserved' &&
|
|
201
|
+
change.fact.kind === 'tool' && change.fact.field === 'version' && change.before === null && change.after === null;
|
|
202
|
+
const blocking = stale.changes.filter(change => !isAdvisory(change));
|
|
203
|
+
const advisory = stale.changes.filter(isAdvisory);
|
|
204
|
+
return { blocking: blocking.length > 0 ? { ...stale, changes: blocking } : undefined, advisory };
|
|
205
|
+
}
|
|
206
|
+
function isUnder(root, candidate) {
|
|
207
|
+
if (!root || !candidate)
|
|
208
|
+
return false;
|
|
209
|
+
const rel = relative(resolve(root), resolve(candidate));
|
|
210
|
+
return rel === '' || (!rel.startsWith('..') && !rel.startsWith(sep) && !isAbsolute(rel));
|
|
211
|
+
}
|
|
212
|
+
async function detectConflicts(action, install) {
|
|
213
|
+
const conflicts = [];
|
|
214
|
+
// configure-provider needs no conflict gate: it writes nothing. An existing
|
|
215
|
+
// provider file is preserved (applied, left untouched) and a missing one is
|
|
216
|
+
// owned by deploy-framework, which `PLAN_APPLY_KIND_ORDER` runs after every
|
|
217
|
+
// configure-provider. The owner re-checks the filesystem at apply time.
|
|
218
|
+
if (action.kind === 'deploy-framework' && install.devMode && isUnder(install.root, action.workspace)) {
|
|
219
|
+
conflicts.push({
|
|
220
|
+
actionId: action.id,
|
|
221
|
+
workspace: action.workspace,
|
|
222
|
+
reason: 'dev-install-preserved',
|
|
223
|
+
message: `Dev-mode/source install preserved: ${action.workspace} lives under the ${install.method} install root ${install.root}; deployment refuses to overwrite it.`,
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
return conflicts;
|
|
227
|
+
}
|
|
228
|
+
/** Saving a plan in the default store itself creates the project's .aiwg directory. */
|
|
229
|
+
async function revalidateForApply(plan, fresh, io) {
|
|
230
|
+
const stale = revalidateSetupPlan(plan, fresh);
|
|
231
|
+
for (const entry of stale) {
|
|
232
|
+
const action = plan.actions.find(action => action.id === entry.actionId);
|
|
233
|
+
if (action.kind !== 'register-project')
|
|
234
|
+
continue;
|
|
235
|
+
const ownDirectory = join(action.workspace, '.aiwg');
|
|
236
|
+
if (!entry.changes.some(change => change.fact.kind === 'path' && change.fact.path === ownDirectory &&
|
|
237
|
+
change.before === false && change.after === true))
|
|
238
|
+
continue;
|
|
239
|
+
if (await io.exists(join(ownDirectory, 'setup', 'plans', `${plan.id}.json`))) {
|
|
240
|
+
entry.changes = entry.changes.filter(change => !(change.fact.kind === 'path' && change.fact.path === ownDirectory &&
|
|
241
|
+
change.before === false && change.after === true));
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
return stale.filter(entry => entry.changes.length > 0);
|
|
245
|
+
}
|
|
246
|
+
/** Read-only review: revalidate facts, compute diffs, destinations, conflicts, and readiness. Writes nothing. */
|
|
247
|
+
export async function reviewGuidedPlan(plan, fresh, io, now) {
|
|
248
|
+
const stale = await revalidateForApply(plan, fresh, io);
|
|
249
|
+
const staleByAction = new Map(stale.map(entry => [entry.actionId, entry]));
|
|
250
|
+
const appliedIds = new Set();
|
|
251
|
+
for (const receipt of plan.receipts) {
|
|
252
|
+
for (const [id, result] of Object.entries(receipt.actions)) {
|
|
253
|
+
if (result.status === 'applied')
|
|
254
|
+
appliedIds.add(id);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
const install = await io.installMode();
|
|
258
|
+
const previews = [];
|
|
259
|
+
for (const action of plan.actions) {
|
|
260
|
+
const { blocking: actionStale, advisory } = splitStale(action, staleByAction.get(action.id));
|
|
261
|
+
const conflicts = await detectConflicts(action, install);
|
|
262
|
+
const reference = action.kind === 'configure-provider' || action.kind === 'configure-jev'
|
|
263
|
+
? credentialReferenceFor(plan, action.workspace, action.provider)
|
|
264
|
+
: undefined;
|
|
265
|
+
const needsCredential = (action.kind === 'configure-provider' || (action.kind === 'configure-jev' &&
|
|
266
|
+
answerFor(plan, action.workspace, 'capabilities', 'jev') !== 'remove')) && !reference;
|
|
267
|
+
const status = appliedIds.has(action.id)
|
|
268
|
+
? 'applied'
|
|
269
|
+
: actionStale
|
|
270
|
+
? 'stale'
|
|
271
|
+
: conflicts.length > 0
|
|
272
|
+
? 'conflict'
|
|
273
|
+
: needsCredential
|
|
274
|
+
? 'needs-credential'
|
|
275
|
+
: 'ready';
|
|
276
|
+
previews.push({
|
|
277
|
+
id: action.id,
|
|
278
|
+
kind: action.kind,
|
|
279
|
+
provider: action.provider,
|
|
280
|
+
workspace: action.workspace,
|
|
281
|
+
scope: action.scope,
|
|
282
|
+
optional: isOptionalPlanAction(action.kind),
|
|
283
|
+
destination: destinationOf(action),
|
|
284
|
+
diff: status === 'applied' ? `already applied '${action.id}'` : diffOfAction(plan, action),
|
|
285
|
+
status,
|
|
286
|
+
conflicts,
|
|
287
|
+
stale: actionStale,
|
|
288
|
+
notes: advisory.map(change => `${describeFact(change.fact)} is unobserved (unknown at plan and apply time); the read-only check re-observes it from fresh inventory.`),
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
const readiness = [];
|
|
292
|
+
const byPair = new Map();
|
|
293
|
+
for (const preview of previews) {
|
|
294
|
+
const key = `${preview.workspace}\0${preview.provider}`;
|
|
295
|
+
byPair.set(key, [...(byPair.get(key) ?? []), preview]);
|
|
296
|
+
}
|
|
297
|
+
for (const [key, group] of byPair) {
|
|
298
|
+
const [workspace, provider] = key.split('\0');
|
|
299
|
+
const axis = (area, members) => {
|
|
300
|
+
const stuck = members.filter(preview => preview.status === 'stale' || preview.status === 'conflict' || preview.status === 'needs-credential');
|
|
301
|
+
readiness.push({
|
|
302
|
+
area,
|
|
303
|
+
workspace,
|
|
304
|
+
provider,
|
|
305
|
+
ready: stuck.length === 0,
|
|
306
|
+
detail: stuck.length === 0
|
|
307
|
+
? `${area} ready (${members.filter(member => member.status === 'applied' || member.status === 'ready').length}/${members.length} actions ready or applied)`
|
|
308
|
+
: `${area} blocked: ${stuck.map(member => `'${member.id}' is ${member.status}`).join(', ')}`,
|
|
309
|
+
});
|
|
310
|
+
};
|
|
311
|
+
const kinds = (list) => group.filter(preview => list.includes(preview.kind));
|
|
312
|
+
// Axes with no member actions report ready with a zero denominator.
|
|
313
|
+
axis('workspace', kinds(['write-project-config', 'register-project', 'record-decline']));
|
|
314
|
+
axis('provider', kinds(['configure-provider', 'verify-provider']));
|
|
315
|
+
axis('integration', kinds(['deploy-framework', 'configure-jev', 'configure-worker-dispatch']));
|
|
316
|
+
if (group.some(preview => preview.kind === 'configure-worker-dispatch')) {
|
|
317
|
+
const entry = readiness.at(-1);
|
|
318
|
+
entry.ready = false;
|
|
319
|
+
entry.detail = group.some(preview => preview.kind === 'configure-worker-dispatch' && preview.status === 'applied')
|
|
320
|
+
? 'Qualification recorded at apply; current launch readiness has not been checked.'
|
|
321
|
+
: 'Workers installed; qualification pending. Apply runs doctor --probe-workers --qualify.';
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
const deferred = plan.unresolvedChoices.filter(choice => choice.deferredAt).map(choice => choice.questionId);
|
|
325
|
+
const unanswered = plan.unresolvedChoices
|
|
326
|
+
.filter(choice => !Object.hasOwn(plan.answers, choice.questionId))
|
|
327
|
+
.map(choice => choice.questionId);
|
|
328
|
+
return {
|
|
329
|
+
schema: 'aiwg.setup.review.v1',
|
|
330
|
+
planId: plan.id,
|
|
331
|
+
generatedAt: now ?? new Date().toISOString(),
|
|
332
|
+
installMode: install.devMode ? `dev (${install.method})` : install.method,
|
|
333
|
+
actions: previews,
|
|
334
|
+
readiness,
|
|
335
|
+
deferred,
|
|
336
|
+
unanswered,
|
|
337
|
+
stale,
|
|
338
|
+
conflicts: previews.flatMap(preview => preview.conflicts),
|
|
339
|
+
};
|
|
340
|
+
}
|
|
341
|
+
/** Render the human-readable review: per workspace and provider. */
|
|
342
|
+
export function renderHumanReview(review) {
|
|
343
|
+
const lines = [`Guided setup plan '${review.planId}' (install: ${review.installMode})`, ''];
|
|
344
|
+
const byWorkspace = new Map();
|
|
345
|
+
for (const action of review.actions)
|
|
346
|
+
byWorkspace.set(action.workspace, [...(byWorkspace.get(action.workspace) ?? []), action]);
|
|
347
|
+
for (const [workspace, actions] of [...byWorkspace].sort(([a], [b]) => a.localeCompare(b))) {
|
|
348
|
+
lines.push(`workspace ${workspace}`);
|
|
349
|
+
const byProvider = new Map();
|
|
350
|
+
for (const action of actions)
|
|
351
|
+
byProvider.set(action.provider, [...(byProvider.get(action.provider) ?? []), action]);
|
|
352
|
+
for (const [provider, group] of [...byProvider].sort(([a], [b]) => a.localeCompare(b))) {
|
|
353
|
+
lines.push(` provider ${provider}`);
|
|
354
|
+
for (const preview of group) {
|
|
355
|
+
const flag = preview.optional ? ' (optional)' : '';
|
|
356
|
+
lines.push(` [${preview.status}] ${preview.id} (${preview.kind})${flag}`);
|
|
357
|
+
lines.push(` destination: ${preview.destination}`);
|
|
358
|
+
lines.push(` diff: ${preview.diff}`);
|
|
359
|
+
for (const conflict of preview.conflicts)
|
|
360
|
+
lines.push(` conflict: ${conflict.message}`);
|
|
361
|
+
for (const change of preview.stale?.changes ?? []) {
|
|
362
|
+
lines.push(` stale: ${describeFact(change.fact)} is ${change.reason}; re-run discovery and the setup interview before applying.`);
|
|
363
|
+
}
|
|
364
|
+
for (const note of preview.notes)
|
|
365
|
+
lines.push(` note: ${note}`);
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
lines.push('');
|
|
370
|
+
lines.push('readiness');
|
|
371
|
+
for (const entry of review.readiness) {
|
|
372
|
+
lines.push(` [${entry.ready ? 'ready' : 'blocked'}] ${entry.area} ${entry.workspace}${entry.provider === 'shared' ? '' : ` / ${entry.provider}`}: ${entry.detail}`);
|
|
373
|
+
}
|
|
374
|
+
if (review.deferred.length > 0) {
|
|
375
|
+
lines.push('');
|
|
376
|
+
lines.push(`deferred questions (asked first next session): ${review.deferred.join(', ')}`);
|
|
377
|
+
}
|
|
378
|
+
if (review.unanswered.length > 0) {
|
|
379
|
+
lines.push(`unanswered questions: ${review.unanswered.join(', ')}`);
|
|
380
|
+
}
|
|
381
|
+
return lines.join('\n');
|
|
382
|
+
}
|
|
383
|
+
function describeFact(fact) {
|
|
384
|
+
if (fact.kind === 'tool')
|
|
385
|
+
return `tool ${fact.executable}.${fact.field}`;
|
|
386
|
+
if (fact.kind === 'project')
|
|
387
|
+
return `project ${fact.root}.${fact.field}`;
|
|
388
|
+
return `path ${fact.path}`;
|
|
389
|
+
}
|
|
390
|
+
function formatFactValue(value) {
|
|
391
|
+
if (value === undefined)
|
|
392
|
+
return 'unknown';
|
|
393
|
+
return JSON.stringify(value) ?? 'unknown';
|
|
394
|
+
}
|
|
395
|
+
/** Resolve a credential reference to its value. The value must stay in memory only. */
|
|
396
|
+
export async function resolveCredential(reference, provider, io) {
|
|
397
|
+
try {
|
|
398
|
+
const terminal = reference.source === 'stdin-at-apply' && io.promptCredential !== undefined;
|
|
399
|
+
const value = await resolveSensitiveInput({
|
|
400
|
+
prefix: 'credential',
|
|
401
|
+
...(reference.source === 'env' ? { env: reference.name } : {}),
|
|
402
|
+
...(reference.source === 'file' ? { file: reference.name } : {}),
|
|
403
|
+
...(reference.source === 'store' ? { store: reference.name } : {}),
|
|
404
|
+
...(reference.source === 'stdin-at-apply' ? terminal ? { interactive: true } : { stdin: true } : {}),
|
|
405
|
+
}, {
|
|
406
|
+
readEnv: io.readEnv,
|
|
407
|
+
readFile: io.readCredentialFile,
|
|
408
|
+
...(io.readCredentialStore ? { readStore: async (name) => (await io.readCredentialStore(name)) ?? null } : {}),
|
|
409
|
+
// An absent reader explicitly refuses headless stdin; never access the
|
|
410
|
+
// process stream behind an injected apply boundary.
|
|
411
|
+
readStdin: io.readStdinCredential ?? (async () => ''),
|
|
412
|
+
isTTY: terminal,
|
|
413
|
+
...(io.promptCredential ? { prompt: async () => (await io.promptCredential(`Enter ${provider} API input (kept out of the setup plan): `)) ?? '' } : {}),
|
|
414
|
+
});
|
|
415
|
+
return { ok: true, value };
|
|
416
|
+
}
|
|
417
|
+
catch {
|
|
418
|
+
// Source failures can contain arbitrary external text. Receipts persist
|
|
419
|
+
// only the locator and supported alternatives, never the raw failure.
|
|
420
|
+
const source = reference.source === 'stdin-at-apply' ? 'Stdin-at-apply' : `Credential-source ${reference.source}:${reference.name}`;
|
|
421
|
+
return { ok: false, message: `${source} for ${provider} is unavailable; use --credential ${provider}=stdin|file:PATH|store:NAME|env:NAME (stdin prompts at a terminal).` };
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
function receiptFor(plan, action, status, message, now) {
|
|
425
|
+
const next = { ...plan, updatedAt: now, receipts: plan.receipts.map(receipt => ({ ...receipt, actions: { ...receipt.actions } })) };
|
|
426
|
+
let workspace = next.receipts.find(receipt => receipt.workspace === action.workspace);
|
|
427
|
+
if (!workspace) {
|
|
428
|
+
workspace = { workspace: action.workspace, actions: {} };
|
|
429
|
+
next.receipts.push(workspace);
|
|
430
|
+
}
|
|
431
|
+
workspace.actions[action.id] = { status, startedAt: now, completedAt: now, ...(message ? { message } : {}) };
|
|
432
|
+
return next;
|
|
433
|
+
}
|
|
434
|
+
/** Execute an owner without persistence: save failures must abort, never become failed-action receipts. */
|
|
435
|
+
async function executePlanAction(plan, action, io, advisoryNote) {
|
|
436
|
+
if (action.kind === 'configure-jev') {
|
|
437
|
+
// Never persist owner output or exceptions: either can contain API bytes.
|
|
438
|
+
try {
|
|
439
|
+
const recordedOptions = answerFor(plan, action.workspace, 'capabilities', 'jev-options', 'jev');
|
|
440
|
+
const remove = answerFor(plan, action.workspace, 'capabilities', 'jev') === 'remove';
|
|
441
|
+
const options = recordedOptions && typeof recordedOptions === 'object' && !Array.isArray(recordedOptions)
|
|
442
|
+
? { ...recordedOptions, remove } : { remove };
|
|
443
|
+
let token;
|
|
444
|
+
let reference;
|
|
445
|
+
if (!remove) {
|
|
446
|
+
reference = credentialReferenceFor(plan, action.workspace, 'jev');
|
|
447
|
+
if (!reference) {
|
|
448
|
+
return { status: 'skipped', message: 'Jev setup deferred: choose --credential jev=stdin|file:PATH|store:NAME|env:NAME.' };
|
|
449
|
+
}
|
|
450
|
+
const resolved = await resolveCredential(reference, 'jev', io);
|
|
451
|
+
if (!resolved.ok) {
|
|
452
|
+
return { status: 'skipped', message: 'Jev setup deferred: API input unavailable; core setup can proceed.' };
|
|
453
|
+
}
|
|
454
|
+
token = resolved.value;
|
|
455
|
+
}
|
|
456
|
+
if (!io.owners.configureJev)
|
|
457
|
+
throw new Error('Missing Jev owner');
|
|
458
|
+
await io.owners.configureJev({ action, token, options, credential: reference });
|
|
459
|
+
return { status: 'applied', message: remove ? 'Removed Jev setup through its existing owner.' : 'Configured Jev through its existing owner.' };
|
|
460
|
+
}
|
|
461
|
+
catch {
|
|
462
|
+
return { status: 'failed', message: 'Jev setup failed; retry the existing decision setup command.' };
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
if (action.kind === 'configure-worker-dispatch') {
|
|
466
|
+
try {
|
|
467
|
+
if (!io.owners.configureWorkerDispatch)
|
|
468
|
+
throw new Error('Missing worker owner');
|
|
469
|
+
await io.owners.configureWorkerDispatch({ action });
|
|
470
|
+
return { status: 'applied', message: 'Enabled direct workers; doctor qualification succeeded. Launch rechecks availability.' };
|
|
471
|
+
}
|
|
472
|
+
catch {
|
|
473
|
+
return { status: 'failed', message: 'Worker qualification incomplete; installed CLIs remain unqualified. Retry aiwg doctor --probe-workers --qualify.' };
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
if (action.kind === 'configure-provider') {
|
|
477
|
+
const reference = credentialReferenceFor(plan, action.workspace, action.provider);
|
|
478
|
+
if (!reference) {
|
|
479
|
+
return { status: 'skipped', message: `Refused '${action.id}': no credential-reference recorded for ${action.provider}; re-answer locations.credential-source first.` };
|
|
480
|
+
}
|
|
481
|
+
const resolved = await resolveCredential(reference, action.provider, io);
|
|
482
|
+
if (!resolved.ok) {
|
|
483
|
+
return { status: 'skipped', message: `Refused '${action.id}': ${resolved.message}` };
|
|
484
|
+
}
|
|
485
|
+
// The secret value stays in this scope only: owners receive the
|
|
486
|
+
// reference shape plus a presence bit, never the value.
|
|
487
|
+
try {
|
|
488
|
+
const message = await io.owners.configureProvider({ action, configPath: configPathOf(action),
|
|
489
|
+
credential: reference, credentialPresent: true });
|
|
490
|
+
return { status: 'applied', message };
|
|
491
|
+
}
|
|
492
|
+
catch (error) {
|
|
493
|
+
return { status: 'failed', message: error instanceof Error ? error.message : String(error) };
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
try {
|
|
497
|
+
const message = action.kind === 'verify-provider'
|
|
498
|
+
? await io.owners.verifyProvider({ action })
|
|
499
|
+
: action.kind === 'write-project-config'
|
|
500
|
+
? await io.owners.writeProjectConfig({ action })
|
|
501
|
+
: action.kind === 'deploy-framework'
|
|
502
|
+
? await io.owners.deployFramework({ action, intent: answerFor(plan, action.workspace, 'intent', 'use') })
|
|
503
|
+
: action.kind === 'register-project'
|
|
504
|
+
? await io.owners.registerProject({ action })
|
|
505
|
+
: action.kind === 'record-decline' ? await io.owners.recordDecline({ action })
|
|
506
|
+
: await Promise.reject(new Error('Unsupported setup action kind.'));
|
|
507
|
+
return { status: 'applied', message: `${message}${advisoryNote}` };
|
|
508
|
+
}
|
|
509
|
+
catch (error) {
|
|
510
|
+
return { status: 'failed', message: error instanceof Error ? error.message : String(error) };
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
/**
|
|
514
|
+
* Apply remaining actions in dependency order through the injected owners.
|
|
515
|
+
* Stale/unobserved facts are refused (re-ask or re-run discovery), never
|
|
516
|
+
* applied blindly. Failed and skipped actions stay eligible for a later run:
|
|
517
|
+
* only `applied` receipts leave `remainingPlanActions`.
|
|
518
|
+
*/
|
|
519
|
+
export async function applyGuidedPlan(plan, fresh, io) {
|
|
520
|
+
const staleByAction = new Map((await revalidateForApply(plan, fresh, io)).map(entry => [entry.actionId, entry]));
|
|
521
|
+
const install = await io.installMode();
|
|
522
|
+
let current = plan;
|
|
523
|
+
const results = [];
|
|
524
|
+
for (const action of sortPlanActionsForApply(remainingPlanActions(plan))) {
|
|
525
|
+
const record = async (status, message) => {
|
|
526
|
+
results.push({ actionId: action.id, kind: action.kind, workspace: action.workspace,
|
|
527
|
+
provider: action.provider, optional: isOptionalPlanAction(action.kind), status, message });
|
|
528
|
+
current = receiptFor(current, action, status, message, io.now());
|
|
529
|
+
await io.save(current);
|
|
530
|
+
};
|
|
531
|
+
const { blocking: stale, advisory } = splitStale(action, staleByAction.get(action.id));
|
|
532
|
+
const conflicts = await detectConflicts(action, install);
|
|
533
|
+
if (conflicts.length > 0) {
|
|
534
|
+
await record('skipped', conflicts[0].message);
|
|
535
|
+
continue;
|
|
536
|
+
}
|
|
537
|
+
if (stale) {
|
|
538
|
+
const first = stale.changes[0];
|
|
539
|
+
await record('skipped', `Refused '${action.id}': ${describeFact(first.fact)} is ${first.reason} (was ${formatFactValue(first.before)}, now ${formatFactValue(first.after)}); re-run discovery and the setup interview before applying.`);
|
|
540
|
+
continue;
|
|
541
|
+
}
|
|
542
|
+
const advisoryNote = advisory.length > 0
|
|
543
|
+
? ` (note: ${advisory.map(change => `${describeFact(change.fact)} unobserved; qualified from fresh inventory`).join(', ')})`
|
|
544
|
+
: '';
|
|
545
|
+
const outcome = await executePlanAction(current, action, io, advisoryNote);
|
|
546
|
+
await record(outcome.status, outcome.message);
|
|
547
|
+
}
|
|
548
|
+
const core = results.filter(result => !result.optional);
|
|
549
|
+
const coreBad = core.filter(result => result.status !== 'applied');
|
|
550
|
+
const optionalBad = results.filter(result => result.optional && result.status !== 'applied');
|
|
551
|
+
const classification = coreBad.length > 0 ? 'failure' : optionalBad.length > 0 ? 'partial' : 'success';
|
|
552
|
+
const summary = classification === 'success'
|
|
553
|
+
? `Applied ${results.length} action(s) from plan '${plan.id}'.`
|
|
554
|
+
: classification === 'partial'
|
|
555
|
+
? `Partial success: core setup applied, ${optionalBad.length} optional action(s) did not: ${optionalBad.map(result => `'${result.actionId}' ${result.status} (${result.message})`).join('; ')}.`
|
|
556
|
+
: `Setup incomplete: ${coreBad.length} core action(s) did not apply: ${coreBad.map(result => `'${result.actionId}' ${result.status} (${result.message})`).join('; ')}.`;
|
|
557
|
+
return { plan: current, result: {
|
|
558
|
+
schema: 'aiwg.setup.apply.v1',
|
|
559
|
+
planId: plan.id,
|
|
560
|
+
generatedAt: io.now(),
|
|
561
|
+
results,
|
|
562
|
+
classification,
|
|
563
|
+
exitCode: classification === 'success' ? 0 : 1,
|
|
564
|
+
message: summary,
|
|
565
|
+
} };
|
|
566
|
+
}
|
|
567
|
+
//# sourceMappingURL=plan-apply.js.map
|