@williambeto/ai-workflow 2.4.9 → 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 (104) 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/npm-consumer-quickstart.md +70 -109
  25. package/dist-assets/docs/policies/01-BRANCH_GATE.md +1 -1
  26. package/dist-assets/docs/policies/06-FINAL_EVIDENCE_CONTRACT.md +2 -2
  27. package/dist-assets/docs/policies/08-PRODUCT_TRUTHFULNESS_AND_PROJECT_DOCS.md +1 -1
  28. package/dist-assets/docs/policies/10-BEHAVIORAL_CONTRACT_HARDENING.md +1 -1
  29. package/dist-assets/docs/policies/ORCHESTRATION_PROTOCOL.md +8 -9
  30. package/dist-assets/docs/policies/PROCEDURE_DELIVERY_ARTIFACTS.md +4 -10
  31. package/dist-assets/docs/policies/SKILLS_COMMON_GOVERNANCE.md +1 -1
  32. package/dist-assets/docs/profiles/README.md +2 -2
  33. package/dist-assets/docs/specs/runtime-operational-contract.md +12 -12
  34. package/dist-assets/docs/troubleshooting-guide.md +2 -2
  35. package/dist-assets/docs/visual-validation-guide.md +3 -3
  36. package/dist-assets/schemas/evidence.schema.json +109 -0
  37. package/dist-assets/templates/ANTIGRAVITY.md.template +7 -5
  38. package/dist-assets/templates/CLAUDE.md.template +7 -5
  39. package/dist-assets/templates/CODEX.md.template +7 -5
  40. package/evidence-validator-76ZQQYDU.js +6661 -0
  41. package/package.json +7 -64
  42. package/{src/core/workspace/read-only-workspace.js → read-only-workspace-PZBE7KAX.js} +14 -31
  43. package/{src/commands/validate.js → validate-Q2NLLQ5G.js} +30 -22
  44. package/AGENTS.md +0 -34
  45. package/CHANGELOG.md +0 -259
  46. package/LICENSE +0 -21
  47. package/PUBLISH_MANIFEST.json +0 -34
  48. package/README.md +0 -129
  49. package/dist-assets/templates/.geminiignore.template +0 -8
  50. package/dist-assets/templates/GEMINI.md.template +0 -20
  51. package/docs/compatibility/provider-usage.md +0 -46
  52. package/docs/compatibility/runtime-matrix.md +0 -31
  53. package/docs/getting-started/DESKTOP_PROMPT.md +0 -52
  54. package/docs/getting-started/authoring-custom-skills.md +0 -80
  55. package/docs/getting-started/quickstart.md +0 -117
  56. package/docs/getting-started/troubleshooting.md +0 -123
  57. package/docs/getting-started/upgrading-to-v2.md +0 -62
  58. package/src/adapters/index.js +0 -4
  59. package/src/adapters/platforms/antigravity.js +0 -382
  60. package/src/adapters/platforms/claude.js +0 -126
  61. package/src/adapters/platforms/codex.js +0 -113
  62. package/src/adapters/platforms/gemini.js +0 -245
  63. package/src/cli.js +0 -155
  64. package/src/commands/clean.js +0 -106
  65. package/src/commands/collect-evidence.js +0 -25
  66. package/src/commands/doctor.js +0 -202
  67. package/src/commands/execute.js +0 -382
  68. package/src/commands/init.js +0 -359
  69. package/src/commands/run.js +0 -112
  70. package/src/core/backup.js +0 -65
  71. package/src/core/completion-contract.js +0 -35
  72. package/src/core/delegation-controller.js +0 -193
  73. package/src/core/evidence/evidence-ledger.js +0 -98
  74. package/src/core/execution-planner.js +0 -62
  75. package/src/core/filesystem.js +0 -141
  76. package/src/core/finalization/finalizer.js +0 -165
  77. package/src/core/finalization/workspace-snapshot.js +0 -110
  78. package/src/core/gates/branch-gate.js +0 -134
  79. package/src/core/gates/merge-gate.js +0 -74
  80. package/src/core/handoff/handoff-engine.js +0 -104
  81. package/src/core/healing/cli-remediation-executor.js +0 -153
  82. package/src/core/healing/healer-engine.js +0 -212
  83. package/src/core/healing/runtime-remediation-executor.js +0 -136
  84. package/src/core/identity.js +0 -43
  85. package/src/core/install-plan.js +0 -34
  86. package/src/core/opencode-merge.js +0 -150
  87. package/src/core/package-assets.js +0 -141
  88. package/src/core/request-classifier.js +0 -73
  89. package/src/core/request-router.js +0 -289
  90. package/src/core/runtime/opencode-adapter.js +0 -202
  91. package/src/core/sdd/validator.js +0 -67
  92. package/src/core/statuses.js +0 -29
  93. package/src/core/symlink-layout.js +0 -93
  94. package/src/core/templates.js +0 -229
  95. package/src/core/validation/artifact-fidelity-gate.js +0 -445
  96. package/src/core/validation/canonical-finalization.js +0 -43
  97. package/src/core/validation/delivery-decision-engine.js +0 -344
  98. package/src/core/validation/evidence-collector.js +0 -245
  99. package/src/core/validation/quality-guard.js +0 -251
  100. package/src/core/validation/stack-detector.js +0 -65
  101. package/src/core/validation/validation-planner.js +0 -134
  102. package/src/core/validation/visual-verifier.js +0 -175
  103. package/src/core/workflow-profiles.js +0 -107
  104. 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
- }