@williambeto/ai-workflow 2.4.8 → 2.6.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (122) hide show
  1. package/bin/ai-workflow.d.ts +3 -0
  2. package/bin/ai-workflow.js +2669 -1
  3. package/bin/ai-workflow.js.map +1 -0
  4. package/chunk-2AOV2ATY.js +749 -0
  5. package/chunk-5WRI5ZAA.js +31 -0
  6. package/chunk-6EMG6FAO.js +734 -0
  7. package/chunk-UHLZX2EG.js +1827 -0
  8. package/core/index.d.ts +440 -0
  9. package/core/index.js +32 -0
  10. package/core/index.js.map +1 -0
  11. package/dist-assets/AGENTS.md +23 -6
  12. package/dist-assets/agents/atlas.md +8 -8
  13. package/dist-assets/agents/nexus.md +4 -4
  14. package/dist-assets/agents/orion.md +1 -1
  15. package/dist-assets/agents/phoenix.md +1 -1
  16. package/dist-assets/agents/sage.md +1 -1
  17. package/dist-assets/commands/implement.md +1 -1
  18. package/dist-assets/docs/QUICKSTART.md +8 -8
  19. package/dist-assets/docs/cli-reference.md +7 -7
  20. package/dist-assets/docs/compatibility/provider-usage.md +1 -9
  21. package/dist-assets/docs/compatibility/runtime-matrix.md +5 -6
  22. package/dist-assets/docs/consumer-onboarding.md +2 -2
  23. package/dist-assets/docs/design-patterns-policy.md +1 -1
  24. package/dist-assets/docs/full-documentation.md +0 -2
  25. package/dist-assets/docs/npm-consumer-quickstart.md +70 -109
  26. package/dist-assets/docs/policies/01-BRANCH_GATE.md +1 -1
  27. package/dist-assets/docs/policies/06-FINAL_EVIDENCE_CONTRACT.md +2 -2
  28. package/dist-assets/docs/policies/08-PRODUCT_TRUTHFULNESS_AND_PROJECT_DOCS.md +1 -1
  29. package/dist-assets/docs/policies/10-BEHAVIORAL_CONTRACT_HARDENING.md +1 -1
  30. package/dist-assets/docs/policies/ORCHESTRATION_PROTOCOL.md +8 -9
  31. package/dist-assets/docs/policies/PROCEDURE_DELIVERY_ARTIFACTS.md +4 -10
  32. package/dist-assets/docs/policies/SKILLS_COMMON_GOVERNANCE.md +1 -1
  33. package/dist-assets/docs/profiles/README.md +2 -2
  34. package/dist-assets/docs/specs/runtime-operational-contract.md +12 -12
  35. package/dist-assets/docs/troubleshooting-guide.md +2 -2
  36. package/dist-assets/docs/visual-validation-guide.md +65 -3
  37. package/dist-assets/prompts/01-create-requirement.md +2 -2
  38. package/dist-assets/prompts/02-create-spec.md +2 -2
  39. package/dist-assets/prompts/03-create-tech-plan.md +4 -4
  40. package/dist-assets/prompts/04-breakdown-prs.md +4 -4
  41. package/dist-assets/prompts/05-implement-pr.md +5 -5
  42. package/dist-assets/prompts/06-review-and-fix.md +3 -3
  43. package/dist-assets/prompts/07-apply-design.md +2 -2
  44. package/dist-assets/prompts/08-validate.md +5 -5
  45. package/dist-assets/prompts/09-deploy.md +5 -5
  46. package/dist-assets/prompts/commands/implement.md +1 -1
  47. package/dist-assets/prompts/commands/tech-plan.md +2 -2
  48. package/dist-assets/prompts/commands/validate.md +2 -2
  49. package/dist-assets/runbooks/deploy-checklist.md +5 -5
  50. package/dist-assets/runbooks/how-to-use-skills.md +10 -0
  51. package/dist-assets/runbooks/validation-checklist.md +5 -5
  52. package/dist-assets/schemas/evidence.schema.json +109 -0
  53. package/dist-assets/templates/ANTIGRAVITY.md.template +7 -5
  54. package/dist-assets/templates/CLAUDE.md.template +7 -5
  55. package/dist-assets/templates/CODEX.md.template +7 -5
  56. package/evidence-validator-76ZQQYDU.js +6661 -0
  57. package/package.json +7 -65
  58. package/{src/core/workspace/read-only-workspace.js → read-only-workspace-PZBE7KAX.js} +14 -31
  59. package/{src/commands/validate.js → validate-Q2NLLQ5G.js} +30 -22
  60. package/AGENTS.md +0 -34
  61. package/CHANGELOG.md +0 -237
  62. package/LICENSE +0 -21
  63. package/PUBLISH_MANIFEST.json +0 -34
  64. package/README.md +0 -129
  65. package/dist-assets/runbooks/use-linear-for-operational-planning.md +0 -45
  66. package/dist-assets/runbooks/use-napkin-project-memory.md +0 -33
  67. package/dist-assets/templates/.geminiignore.template +0 -8
  68. package/dist-assets/templates/GEMINI.md.template +0 -20
  69. package/docs/compatibility/provider-usage.md +0 -46
  70. package/docs/compatibility/runtime-matrix.md +0 -31
  71. package/docs/getting-started/DESKTOP_PROMPT.md +0 -52
  72. package/docs/getting-started/quickstart.md +0 -117
  73. package/docs/getting-started/troubleshooting.md +0 -123
  74. package/docs/getting-started/upgrading-to-v2.md +0 -62
  75. package/read_only_safety_verification.md +0 -48
  76. package/src/adapters/index.js +0 -4
  77. package/src/adapters/platforms/antigravity.js +0 -382
  78. package/src/adapters/platforms/claude.js +0 -126
  79. package/src/adapters/platforms/codex.js +0 -113
  80. package/src/adapters/platforms/gemini.js +0 -245
  81. package/src/cli.js +0 -155
  82. package/src/commands/clean.js +0 -106
  83. package/src/commands/collect-evidence.js +0 -25
  84. package/src/commands/doctor.js +0 -202
  85. package/src/commands/execute.js +0 -382
  86. package/src/commands/init.js +0 -359
  87. package/src/commands/run.js +0 -112
  88. package/src/core/backup.js +0 -65
  89. package/src/core/completion-contract.js +0 -35
  90. package/src/core/delegation-controller.js +0 -193
  91. package/src/core/evidence/evidence-ledger.js +0 -98
  92. package/src/core/execution-planner.js +0 -62
  93. package/src/core/filesystem.js +0 -141
  94. package/src/core/finalization/finalizer.js +0 -165
  95. package/src/core/finalization/workspace-snapshot.js +0 -110
  96. package/src/core/gates/branch-gate.js +0 -134
  97. package/src/core/gates/merge-gate.js +0 -74
  98. package/src/core/handoff/handoff-engine.js +0 -104
  99. package/src/core/healing/cli-remediation-executor.js +0 -153
  100. package/src/core/healing/healer-engine.js +0 -212
  101. package/src/core/healing/runtime-remediation-executor.js +0 -136
  102. package/src/core/identity.js +0 -43
  103. package/src/core/install-plan.js +0 -34
  104. package/src/core/opencode-merge.js +0 -150
  105. package/src/core/package-assets.js +0 -141
  106. package/src/core/request-classifier.js +0 -73
  107. package/src/core/request-router.js +0 -289
  108. package/src/core/runtime/opencode-adapter.js +0 -202
  109. package/src/core/sdd/validator.js +0 -67
  110. package/src/core/statuses.js +0 -29
  111. package/src/core/symlink-layout.js +0 -93
  112. package/src/core/templates.js +0 -229
  113. package/src/core/validation/artifact-fidelity-gate.js +0 -445
  114. package/src/core/validation/canonical-finalization.js +0 -43
  115. package/src/core/validation/delivery-decision-engine.js +0 -344
  116. package/src/core/validation/evidence-collector.js +0 -245
  117. package/src/core/validation/quality-guard.js +0 -251
  118. package/src/core/validation/stack-detector.js +0 -65
  119. package/src/core/validation/validation-planner.js +0 -134
  120. package/src/core/validation/visual-verifier.js +0 -175
  121. package/src/core/workflow-profiles.js +0 -107
  122. package/src/core/workflow-state-machine.js +0 -46
@@ -1,141 +0,0 @@
1
- /**
2
- * Package asset resolution for @williambeto/ai-workflow.
3
- *
4
- * Resolves the installed package root and reads real skill/agent/command
5
- * files so `init` copies them to the target project instead of generating stubs.
6
- */
7
- import { fileURLToPath } from "node:url";
8
- import { dirname, resolve } from "node:path";
9
- import { readFileSync, readdirSync, statSync } from "node:fs";
10
-
11
- let cachedRoot = null;
12
-
13
- /**
14
- * Return the absolute path to the installed package root
15
- * (the directory containing `.agents/skills/`).
16
- *
17
- * It dynamically searches for the package.json belonging to @williambeto/ai-workflow
18
- * by climbing up from the current module location.
19
- */
20
- export function getPackageRoot() {
21
- if (cachedRoot) return cachedRoot;
22
- let currentDir = dirname(fileURLToPath(import.meta.url));
23
-
24
- while (true) {
25
- const pkgPath = resolve(currentDir, "package.json");
26
- try {
27
- const pkg = JSON.parse(readFileSync(pkgPath, "utf8"));
28
- if (pkg.name === "@williambeto/ai-workflow") {
29
- cachedRoot = currentDir;
30
- return cachedRoot;
31
- }
32
- } catch {
33
- // package.json doesn't exist or is invalid, keep climbing
34
- }
35
-
36
- const parentDir = dirname(currentDir);
37
- if (parentDir === currentDir) {
38
- break; // Reached filesystem root
39
- }
40
- currentDir = parentDir;
41
- }
42
-
43
- throw new Error("Failed to resolve @williambeto/ai-workflow package root physically.");
44
- }
45
-
46
- /**
47
- * Read a file relative to the package root.
48
- * Returns null if the file does not exist.
49
- */
50
- export function readPackageFile(relativePath) {
51
- try {
52
- const fullPath = resolve(getPackageRoot(), relativePath);
53
- return readFileSync(fullPath, "utf8");
54
- } catch {
55
- return null;
56
- }
57
- }
58
-
59
- /**
60
- * Resolve the current kit version from package metadata.
61
- * Returns null if package.json cannot be read/parsed.
62
- */
63
- export function getPackageVersion() {
64
- try {
65
- const raw = readPackageFile("package.json");
66
- if (!raw) return null;
67
- const parsed = JSON.parse(raw);
68
- return typeof parsed.version === "string" ? parsed.version : null;
69
- } catch {
70
- return null;
71
- }
72
- }
73
-
74
- /**
75
- * Discover ALL files under a package-relative directory.
76
- * Returns a flat map of { relativePath: content }.
77
- * Skips non-existent directories silently.
78
- */
79
- export function discoverPackageFiles(baseRelativePath) {
80
- const files = {};
81
- const basePath = resolve(getPackageRoot(), baseRelativePath);
82
-
83
- try {
84
- if (!statSync(basePath).isDirectory()) {
85
- return files;
86
- }
87
- } catch {
88
- return files;
89
- }
90
-
91
- function walk(dir, relativePrefix) {
92
- const entries = readdirSync(dir, { withFileTypes: true });
93
- for (const entry of entries) {
94
- const fullPath = resolve(dir, entry.name);
95
- const relativePath = relativePrefix
96
- ? `${relativePrefix}/${entry.name}`
97
- : entry.name;
98
-
99
- if (entry.isDirectory()) {
100
- walk(fullPath, relativePath);
101
- } else if (entry.isFile()) {
102
- try {
103
- files[relativePath] = readFileSync(fullPath, "utf8");
104
- } catch {
105
- // skip unreadable files
106
- }
107
- }
108
- }
109
- }
110
-
111
- walk(basePath, baseRelativePath);
112
- return files;
113
- }
114
-
115
- /**
116
- * Read the SKILL.md for a given skill name, plus any reference files
117
- * found under a directory.
118
- * Returns a map of { relativePath: content }.
119
- */
120
- export function getFullSkillFiles(skillName) {
121
- const skillRelPath = `dist-assets/skills/${skillName}`;
122
- const files = discoverPackageFiles(skillRelPath);
123
- return files;
124
- }
125
-
126
- /**
127
- * Read the agent .md file for a given agent name.
128
- * Returns the content string or null.
129
- */
130
- export function getFullAgentContent(agentName) {
131
- return readPackageFile(`dist-assets/agents/${agentName}.md`);
132
- }
133
-
134
- /**
135
- * Read the command .md file for a given command name.
136
- * Returns the content string or null.
137
- */
138
- export function getFullCommandContent(commandName) {
139
- return readPackageFile(`dist-assets/commands/${commandName}.md`);
140
- }
141
-
@@ -1,73 +0,0 @@
1
- import { resolveWorkflowProfile, getWorkflowProfile } from "./workflow-profiles.js";
2
- import { RequestRouter } from "./request-router.js";
3
-
4
- /**
5
- * RequestClassifier - Classifies natural requests into structured, testable contracts.
6
- */
7
- export class RequestClassifier {
8
- /**
9
- * Classifies a natural request.
10
- * @param {string} request - The natural language request string.
11
- * @returns {Object} Structured classification contract.
12
- */
13
- classify(request = "", options = {}) {
14
- const text = String(request).trim();
15
- if (!text) {
16
- throw new Error("Cannot classify an empty request.");
17
- }
18
-
19
- const cwd = options.cwd || process.cwd();
20
-
21
- // 1. Resolve Profile
22
- const profile = resolveWorkflowProfile({ request: text });
23
- const profileDef = getWorkflowProfile(profile);
24
-
25
- // 2. Classify Intent
26
- const readOnlyPatterns = /\b(analyze|check|inspect|review|find|search|show|list|read|view|verify)\b/i;
27
- const isReadOnly = readOnlyPatterns.test(text);
28
- const intent = isReadOnly ? "read-only" : "write";
29
-
30
- // 3. Classify Mode
31
- let mode = "standard";
32
- if (/\b(deep|architectural|migration|major|full|\[deep\])\b/i.test(text)) {
33
- mode = "full";
34
- } else if (/\b(tiny|small|quick|simple|only\s+update|typo|comment|\[tiny\])\b/i.test(text)) {
35
- const isPureDocsOrTypo = profile === "documentation" || /\b(typo|comment|readme|docs|documentation)\b/i.test(text);
36
- if (intent === "write" && !isPureDocsOrTypo) {
37
- mode = "standard";
38
- } else {
39
- mode = "quick";
40
- }
41
- }
42
-
43
- // 4. Classify Risk
44
- let risk = "medium";
45
- if (mode === "full" || profile === "security-review") {
46
- risk = "high";
47
- } else if (mode === "quick" || intent === "read-only") {
48
- risk = "low";
49
- }
50
-
51
- const specNeeded = mode === "full" && intent === "write";
52
- const validationNeeded = intent === "write";
53
-
54
- // Integrate central RequestRouter
55
- const router = new RequestRouter({ cwd });
56
- const requestUnderstanding = router.understand(text);
57
- const routingDecision = router.route(requestUnderstanding);
58
-
59
- return {
60
- request: text,
61
- profile,
62
- owner: routingDecision.selectedActor || profileDef.owner,
63
- intent: requestUnderstanding.mutationIntent === "readonly" ? "read-only" : "write",
64
- mode,
65
- risk: requestUnderstanding.riskLevel,
66
- specNeeded,
67
- validationNeeded,
68
- skills: [...profileDef.skills],
69
- requestUnderstanding,
70
- routingDecision
71
- };
72
- }
73
- }
@@ -1,289 +0,0 @@
1
- import path from "node:path";
2
- import fs from "node:fs";
3
-
4
- export class RequestRouter {
5
- constructor({ cwd = process.cwd() } = {}) {
6
- this.cwd = cwd;
7
- this.validActors = ["Atlas", "Nexus", "Orion", "Astra", "Sage", "Phoenix"];
8
- }
9
-
10
- /**
11
- * Understands and classifies a natural language request.
12
- * @param {string} request
13
- * @returns {Object} RequestUnderstanding
14
- */
15
- understand(request) {
16
- const text = String(request).trim();
17
- if (!text) {
18
- throw new Error("Cannot classify an empty request.");
19
- }
20
-
21
- // 1. Extract requested actor
22
- let requestedActor = undefined;
23
- let invalidActor = undefined;
24
-
25
- // Direct addressing/reference pattern e.g., "Nexus, discover...", "ask Sage to..."
26
- const askMatch = text.match(/\b(?:ask|use|tell|run|agent|actor)\s+([a-zA-Z]+)\b/i);
27
- const addressMatch = text.match(/\b([a-zA-Z]+)\s*(?:,)/);
28
-
29
- let candidate = null;
30
- if (askMatch) {
31
- candidate = askMatch[1];
32
- } else if (addressMatch) {
33
- candidate = addressMatch[1];
34
- } else {
35
- // General match for known actors
36
- const knownMatch = text.match(/\b(Atlas|Nexus|Orion|Astra|Sage|Phoenix)\b/i);
37
- if (knownMatch) {
38
- candidate = knownMatch[1];
39
- }
40
- }
41
-
42
- if (candidate) {
43
- const normalized = candidate.charAt(0).toUpperCase() + candidate.slice(1).toLowerCase();
44
- if (this.validActors.includes(normalized)) {
45
- requestedActor = normalized;
46
- } else {
47
- invalidActor = candidate;
48
- }
49
- }
50
-
51
- // 2. Extract operationType and requestedCapability
52
- let operationType = "explain"; // default
53
- let requestedCapability = undefined;
54
-
55
- const capabilityMappings = [
56
- { op: "discover", pattern: /\b(discover|inspect|map|scan|structure|architecture|repo|search|find)\b/i },
57
- { op: "validate", pattern: /\b(validate|audit|review|check|verify|test)\b/i },
58
- { op: "implement", pattern: /\b(implement|build|create|add|develop|write|change|modify)\b/i },
59
- { op: "fix", pattern: /\b(fix|repair|correct|heal|solve|remediate)\b/i },
60
- { op: "release", pattern: /\b(release|tag|prepare release)\b/i },
61
- { op: "deploy", pattern: /\b(deploy|publish|push)\b/i }
62
- ];
63
-
64
- for (const mapping of capabilityMappings) {
65
- const match = text.match(mapping.pattern);
66
- if (match) {
67
- operationType = mapping.op;
68
- requestedCapability = match[1];
69
- break;
70
- }
71
- }
72
-
73
- // 3. Determine mutationIntent
74
- const writePatterns = /\b(implement|build|fix|write|change|modify|create|add|refactor|update)\b/i;
75
- const readOnlyPatterns = /\b(analyze|check|inspect|review|find|search|show|list|read|view|verify|validate|discover|map)\b/i;
76
-
77
- let mutationIntent = "write"; // default
78
- if (/\b(read-only|readonly)\b/i.test(text)) {
79
- mutationIntent = "readonly";
80
- } else if (writePatterns.test(text)) {
81
- mutationIntent = "write";
82
- } else if (readOnlyPatterns.test(text) || ["discover", "validate"].includes(operationType)) {
83
- mutationIntent = "readonly";
84
- }
85
-
86
- if (/\b(publish|release|deploy|push to npm|push to remote)\b/i.test(text)) {
87
- mutationIntent = "publish";
88
- }
89
-
90
- // Determine Mode (needed for riskLevel mapping)
91
- let mode = "standard";
92
- if (/\b(deep|architectural|migration|major|full|\[deep\])\b/i.test(text)) {
93
- mode = "full";
94
- } else if (/\b(tiny|small|quick|simple|only\s+update|typo|comment|\[tiny\])\b/i.test(text)) {
95
- mode = "quick";
96
- }
97
-
98
- // 4. Determine riskLevel
99
- let riskLevel = "medium";
100
- if (mode === "full" || mutationIntent === "publish" || /\b(bypass|force|direct)\b/i.test(text)) {
101
- riskLevel = "high";
102
- } else if (mode === "quick" || mutationIntent === "readonly") {
103
- riskLevel = "low";
104
- }
105
-
106
- // 5. safetyConstraints and requiredEvidence
107
- const safetyConstraints = [];
108
- const requiredEvidence = [];
109
-
110
- if (mutationIntent === "publish") {
111
- safetyConstraints.push("Require release gate", "Awaiting user authorization");
112
- requiredEvidence.push("release-decision", "tarball-hash");
113
- } else if (mutationIntent === "write") {
114
- safetyConstraints.push("Require branch gate", "No direct commits to main");
115
- requiredEvidence.push("tests", "commit-hash");
116
- } else {
117
- safetyConstraints.push("No workspace mutations allowed");
118
- }
119
-
120
- if (/\b(bypass|force|direct)\b/i.test(text)) {
121
- safetyConstraints.push("Block bypass attempts");
122
- }
123
-
124
- return {
125
- rawRequest: text,
126
- taskGoal: `Perform ${operationType} operation under ${mutationIntent} intent`,
127
- requestedActor,
128
- invalidActor,
129
- requestedCapability,
130
- operationType,
131
- mutationIntent,
132
- riskLevel,
133
- requiredEvidence,
134
- safetyConstraints
135
- };
136
- }
137
-
138
- /**
139
- * Decides the routing decision.
140
- * @param {Object} understanding - RequestUnderstanding
141
- * @returns {Object} RoutingDecision
142
- */
143
- route(understanding) {
144
- const { requestedActor, invalidActor, operationType, mutationIntent, rawRequest } = understanding;
145
-
146
- // 1. Check for bypass safety attempts
147
- if (/\b(bypass safety|bypass validation|force push|directly to main|push to remote without gate)\b/i.test(rawRequest)) {
148
- return {
149
- requestedActor,
150
- selectedActor: "Atlas",
151
- operationType,
152
- mutationIntent,
153
- permissionDecision: "blocked",
154
- reason: "Security block: Attempt to bypass safety or push directly is forbidden.",
155
- workflowPath: ["Atlas"]
156
- };
157
- }
158
-
159
- // 2. Check for invalid/ambiguous actor names in critical actions
160
- if (invalidActor) {
161
- if (["release", "deploy", "fix", "implement"].includes(operationType) || mutationIntent === "publish") {
162
- return {
163
- requestedActor: undefined,
164
- selectedActor: "Atlas",
165
- operationType,
166
- mutationIntent,
167
- permissionDecision: "blocked",
168
- reason: `Security block: Request mentions invalid/unrecognized actor '${invalidActor}' for a critical operation.`,
169
- workflowPath: ["Atlas"]
170
- };
171
- }
172
- }
173
-
174
- // 3. Permission matrix check
175
- const PERMISSION_MATRIX = {
176
- Atlas: {
177
- allowedOperations: ["explain"],
178
- allowedMutations: ["readonly"]
179
- },
180
- Nexus: {
181
- allowedOperations: ["discover", "explain", "plan"],
182
- allowedMutations: ["readonly", "write"]
183
- },
184
- Orion: {
185
- allowedOperations: ["plan", "release", "deploy"],
186
- allowedMutations: ["readonly", "write", "publish"]
187
- },
188
- Astra: {
189
- allowedOperations: ["implement", "fix", "explain"],
190
- allowedMutations: ["readonly", "write"]
191
- },
192
- Sage: {
193
- allowedOperations: ["validate", "explain"],
194
- allowedMutations: ["readonly"]
195
- },
196
- Phoenix: {
197
- allowedOperations: ["fix"],
198
- allowedMutations: ["readonly", "write"]
199
- }
200
- };
201
-
202
- let selectedActor = undefined;
203
- let permissionDecision = "allowed";
204
- let reason = "Routed successfully.";
205
- let workflowPath = ["Atlas"];
206
-
207
- if (requestedActor) {
208
- const perms = PERMISSION_MATRIX[requestedActor];
209
- const isOpAllowed = perms.allowedOperations.includes(operationType);
210
- const isMutationAllowed = perms.allowedMutations.includes(mutationIntent);
211
-
212
- if (isOpAllowed && isMutationAllowed) {
213
- selectedActor = requestedActor;
214
- } else {
215
- // Valid actor but unauthorized operation or mutation
216
- if (mutationIntent === "publish") {
217
- selectedActor = "Orion";
218
- permissionDecision = "rerouted";
219
- reason = `Rerouted: Requested actor '${requestedActor}' is not authorized to publish. Redirecting to Orion.`;
220
- } else {
221
- selectedActor = requestedActor;
222
- permissionDecision = "blocked";
223
- reason = `Blocked: Requested actor '${requestedActor}' does not have permission for operation '${operationType}' or mutation '${mutationIntent}'.`;
224
- }
225
- }
226
- } else {
227
- // Infer actor from operation, risk, capability
228
- if (operationType === "discover") {
229
- selectedActor = "Nexus";
230
- } else if (operationType === "validate") {
231
- selectedActor = "Sage";
232
- } else if (operationType === "fix") {
233
- selectedActor = "Phoenix";
234
- } else if (operationType === "implement") {
235
- selectedActor = "Astra";
236
- } else if (operationType === "release" || operationType === "deploy") {
237
- selectedActor = "Orion";
238
- } else {
239
- // Fallback for general explain/plan
240
- selectedActor = mutationIntent === "write" ? "Astra" : "Atlas";
241
- }
242
- }
243
-
244
- // Phoenix findings check: A fix request requires findings before Phoenix acts
245
- if (selectedActor === "Phoenix") {
246
- const requestPath = path.join(this.cwd, ".ai-workflow/remediation-request.json");
247
- let hasFindings = false;
248
- if (fs.existsSync(requestPath)) {
249
- try {
250
- const reqData = JSON.parse(fs.readFileSync(requestPath, "utf8"));
251
- if (reqData.affectedChecks && reqData.affectedChecks.length > 0) {
252
- hasFindings = true;
253
- }
254
- } catch {
255
- // ignore
256
- }
257
- }
258
- if (!hasFindings) {
259
- permissionDecision = "blocked";
260
- reason = "Blocked: Phoenix acts only with concrete findings and bounded remediation.";
261
- }
262
- }
263
-
264
- if (selectedActor && !workflowPath.includes(selectedActor)) {
265
- workflowPath.push(selectedActor);
266
- }
267
-
268
- // Atlas self-execution guard
269
- if (selectedActor === "Atlas" && (requestedActor || ["discover", "validate", "fix", "release", "deploy"].includes(operationType))) {
270
- permissionDecision = "blocked";
271
- reason = "Atlas self-execution guard: Atlas cannot silently execute specialized actor or capability requests.";
272
- }
273
-
274
- // Sage and Phoenix revalidation loop path in full mode:
275
- if (selectedActor === "Astra" && understanding.riskLevel === "high") {
276
- workflowPath.push("Sage", "Phoenix", "Sage");
277
- }
278
-
279
- return {
280
- requestedActor,
281
- selectedActor,
282
- operationType,
283
- mutationIntent,
284
- permissionDecision,
285
- reason,
286
- workflowPath
287
- };
288
- }
289
- }