@williambeto/ai-workflow 2.3.0 → 2.3.2
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/AGENTS.md +27 -0
- package/CHANGELOG.md +30 -0
- package/README.md +14 -10
- package/dist-assets/agents/astra.md +74 -45
- package/dist-assets/agents/atlas.md +110 -152
- package/dist-assets/agents/nexus.md +64 -19
- package/dist-assets/agents/orion.md +72 -27
- package/dist-assets/agents/phoenix.md +64 -19
- package/dist-assets/agents/sage.md +67 -36
- package/dist-assets/commands/atlas.md +66 -6
- package/dist-assets/commands/audit.md +62 -4
- package/dist-assets/commands/deploy.md +66 -5
- package/dist-assets/commands/discover.md +72 -4
- package/dist-assets/commands/implement.md +66 -18
- package/dist-assets/commands/optimize-tokens.md +60 -4
- package/dist-assets/commands/plan.md +64 -4
- package/dist-assets/commands/release.md +76 -5
- package/dist-assets/commands/run.md +62 -16
- package/dist-assets/commands/spec-create.md +66 -4
- package/dist-assets/commands/spec-implement.md +68 -4
- package/dist-assets/commands/spec-review.md +63 -4
- package/dist-assets/commands/update-memory.md +62 -4
- package/dist-assets/commands/validate.md +70 -6
- package/dist-assets/skills/architecture/SKILL.md +62 -7
- package/dist-assets/skills/backend-development/SKILL.md +62 -7
- package/dist-assets/skills/deployment/SKILL.md +62 -7
- package/dist-assets/skills/design-principles/SKILL.md +59 -7
- package/dist-assets/skills/documentation/SKILL.md +61 -7
- package/dist-assets/skills/frontend-development/SKILL.md +62 -7
- package/dist-assets/skills/full-stack-development/SKILL.md +62 -7
- package/dist-assets/skills/optimize-tokens/SKILL.md +61 -7
- package/dist-assets/skills/pr-workflow/SKILL.md +65 -7
- package/dist-assets/skills/product-discovery/SKILL.md +81 -7
- package/dist-assets/skills/product-planning/SKILL.md +62 -7
- package/dist-assets/skills/project-memory/SKILL.md +55 -22
- package/dist-assets/skills/prompt-engineer/SKILL.md +59 -7
- package/dist-assets/skills/qa-workflow/SKILL.md +72 -7
- package/dist-assets/skills/refactoring/SKILL.md +68 -7
- package/dist-assets/skills/release-workflow/SKILL.md +72 -7
- package/dist-assets/skills/spec-driven-development/SKILL.md +75 -7
- package/dist-assets/skills/technical-leadership/SKILL.md +61 -7
- package/dist-assets/skills/ui-ux-design/SKILL.md +60 -7
- package/docs/compatibility/provider-usage.md +46 -0
- package/docs/compatibility/runtime-matrix.md +31 -0
- package/docs/getting-started/DESKTOP_PROMPT.md +52 -0
- package/docs/getting-started/quickstart.md +17 -0
- package/docs/getting-started/upgrading-to-v2.md +62 -0
- package/package.json +10 -3
- package/src/commands/execute.js +10 -1
- package/src/core/delegation-controller.js +72 -13
- package/src/core/execution-planner.js +2 -3
- package/src/core/request-classifier.js +15 -5
- package/src/core/request-router.js +289 -0
- package/src/core/runtime/opencode-adapter.js +38 -5
- package/docs/releases/v2.3.0-release-decision.md +0 -35
package/src/commands/execute.js
CHANGED
|
@@ -125,12 +125,21 @@ export async function runExecute({ cwd, naturalRequest, taskSlug: taskSlugOverri
|
|
|
125
125
|
|
|
126
126
|
// 1. Classification
|
|
127
127
|
const classifier = new RequestClassifier();
|
|
128
|
-
const classification = classifier.classify(naturalRequest);
|
|
128
|
+
const classification = classifier.classify(naturalRequest, { cwd });
|
|
129
129
|
console.log(`[CLASSIFIED] Intent: ${classification.intent}, Mode: ${classification.mode}, Profile: ${classification.profile}`);
|
|
130
130
|
stateMachine.transitionTo("CLASSIFIED");
|
|
131
131
|
|
|
132
132
|
await delegationController.route(naturalRequest, classification);
|
|
133
133
|
|
|
134
|
+
const routingDecision = classification.routingDecision || {};
|
|
135
|
+
if (routingDecision.permissionDecision === "blocked") {
|
|
136
|
+
stateMachine.transitionTo("BLOCKED");
|
|
137
|
+
throw new Error(`[ROUTE BLOCKED] ${routingDecision.reason}`);
|
|
138
|
+
}
|
|
139
|
+
if (routingDecision.permissionDecision === "rerouted") {
|
|
140
|
+
console.log(`[REROUTED] ${routingDecision.reason}`);
|
|
141
|
+
}
|
|
142
|
+
|
|
134
143
|
// 2. Planning
|
|
135
144
|
const planner = new ExecutionPlanner({ cwd });
|
|
136
145
|
plan = planner.plan(classification, taskSlug);
|
|
@@ -14,6 +14,9 @@ export class DelegationController {
|
|
|
14
14
|
* Logs routing decision by Atlas.
|
|
15
15
|
*/
|
|
16
16
|
async route(naturalRequest, classification) {
|
|
17
|
+
const decision = classification.routingDecision || {};
|
|
18
|
+
this.lastRoutingDecision = decision;
|
|
19
|
+
|
|
17
20
|
if (this.ledger) {
|
|
18
21
|
this.ledger.logEvent({
|
|
19
22
|
actor: "Atlas",
|
|
@@ -23,8 +26,19 @@ export class DelegationController {
|
|
|
23
26
|
provenance: "request-classifier",
|
|
24
27
|
data: {
|
|
25
28
|
request: naturalRequest,
|
|
29
|
+
requestedActor: decision.requestedActor || null,
|
|
30
|
+
selectedActor: decision.selectedActor || null,
|
|
31
|
+
operationType: decision.operationType || null,
|
|
32
|
+
mutationIntent: decision.mutationIntent || null,
|
|
33
|
+
permissionDecision: decision.permissionDecision || null,
|
|
34
|
+
reason: decision.reason || null,
|
|
35
|
+
workflowPath: decision.workflowPath || [],
|
|
26
36
|
classification,
|
|
27
|
-
event: "routing.completed"
|
|
37
|
+
event: "routing.completed",
|
|
38
|
+
runtimeAgentRequested: null,
|
|
39
|
+
runtimeAgentApplied: null,
|
|
40
|
+
runtimeAgentSupported: false,
|
|
41
|
+
status: decision.permissionDecision === "blocked" ? "BLOCKED" : "COMPLETED"
|
|
28
42
|
}
|
|
29
43
|
});
|
|
30
44
|
}
|
|
@@ -34,10 +48,16 @@ export class DelegationController {
|
|
|
34
48
|
* Logs and executes the implementation process by Astra.
|
|
35
49
|
*/
|
|
36
50
|
async implement(promptMsg, owner, options = {}) {
|
|
51
|
+
const decision = this.lastRoutingDecision || {};
|
|
52
|
+
const inspection = await this.adapter.inspect();
|
|
53
|
+
const isSpecialized = ["Nexus", "Orion", "Astra", "Sage", "Phoenix"].includes(owner);
|
|
54
|
+
const supportsAgent = inspection.supports?.agent;
|
|
55
|
+
const canApply = !isSpecialized || supportsAgent;
|
|
56
|
+
|
|
37
57
|
if (this.ledger) {
|
|
38
58
|
this.ledger.logEvent({
|
|
39
|
-
actor: owner,
|
|
40
|
-
actorType: "runtime-agent",
|
|
59
|
+
actor: canApply ? owner : "Atlas",
|
|
60
|
+
actorType: canApply ? "runtime-agent" : "control-plane",
|
|
41
61
|
observed: true,
|
|
42
62
|
runtime: "opencode",
|
|
43
63
|
eventType: "implementation_start",
|
|
@@ -45,7 +65,15 @@ export class DelegationController {
|
|
|
45
65
|
data: {
|
|
46
66
|
agent: owner,
|
|
47
67
|
prompt: promptMsg,
|
|
48
|
-
event: "implementation.started"
|
|
68
|
+
event: "implementation.started",
|
|
69
|
+
requestedActor: decision.requestedActor || null,
|
|
70
|
+
selectedActor: decision.selectedActor || null,
|
|
71
|
+
runtimeAgentRequested: owner,
|
|
72
|
+
runtimeAgentApplied: null,
|
|
73
|
+
runtimeAgentSupported: supportsAgent || false,
|
|
74
|
+
permissionDecision: decision.permissionDecision || null,
|
|
75
|
+
reason: decision.reason || null,
|
|
76
|
+
status: "started"
|
|
49
77
|
}
|
|
50
78
|
});
|
|
51
79
|
}
|
|
@@ -53,9 +81,10 @@ export class DelegationController {
|
|
|
53
81
|
const runResult = await this.adapter.execute(promptMsg, { agent: owner, ...options });
|
|
54
82
|
|
|
55
83
|
if (this.ledger) {
|
|
84
|
+
const isApplied = runResult.runtimeAgentApplied === owner;
|
|
56
85
|
this.ledger.logEvent({
|
|
57
|
-
actor: owner,
|
|
58
|
-
actorType: "runtime-agent",
|
|
86
|
+
actor: isApplied ? owner : "Atlas",
|
|
87
|
+
actorType: isApplied ? "runtime-agent" : "control-plane",
|
|
59
88
|
observed: true,
|
|
60
89
|
runtime: "opencode",
|
|
61
90
|
eventType: "implementation_complete",
|
|
@@ -65,7 +94,15 @@ export class DelegationController {
|
|
|
65
94
|
commandsRun: runResult.commandsRun || [],
|
|
66
95
|
eventCount: runResult.eventCount || 0,
|
|
67
96
|
error: runResult.error,
|
|
68
|
-
event: "implementation.completed"
|
|
97
|
+
event: "implementation.completed",
|
|
98
|
+
requestedActor: decision.requestedActor || null,
|
|
99
|
+
selectedActor: decision.selectedActor || null,
|
|
100
|
+
runtimeAgentRequested: runResult.runtimeAgentRequested || owner || null,
|
|
101
|
+
runtimeAgentApplied: runResult.runtimeAgentApplied || null,
|
|
102
|
+
runtimeAgentSupported: runResult.runtimeAgentSupported !== undefined ? runResult.runtimeAgentSupported : (supportsAgent || false),
|
|
103
|
+
permissionDecision: decision.permissionDecision || null,
|
|
104
|
+
reason: runResult.error || decision.reason || null,
|
|
105
|
+
status: runResult.status || (runResult.success ? "COMPLETED" : "FAILED")
|
|
69
106
|
}
|
|
70
107
|
});
|
|
71
108
|
}
|
|
@@ -80,17 +117,30 @@ export class DelegationController {
|
|
|
80
117
|
const result = await verifyFn();
|
|
81
118
|
|
|
82
119
|
if (mode === "full") {
|
|
120
|
+
const decision = this.lastRoutingDecision || {};
|
|
121
|
+
const inspection = await this.adapter.inspect();
|
|
122
|
+
const supportsAgent = inspection.supports?.agent;
|
|
123
|
+
const canApply = supportsAgent;
|
|
124
|
+
|
|
83
125
|
if (this.ledger) {
|
|
84
126
|
this.ledger.logEvent({
|
|
85
|
-
actor: "Sage",
|
|
86
|
-
actorType: "auditor",
|
|
127
|
+
actor: canApply ? "Sage" : "Atlas",
|
|
128
|
+
actorType: canApply ? "auditor" : "control-plane",
|
|
87
129
|
observed: true,
|
|
88
130
|
runtime: "opencode",
|
|
89
131
|
eventType: "validation_start",
|
|
90
132
|
provenance: "opencode-adapter",
|
|
91
133
|
data: {
|
|
92
134
|
agent: "Sage",
|
|
93
|
-
event: "validation.started"
|
|
135
|
+
event: "validation.started",
|
|
136
|
+
requestedActor: decision.requestedActor || null,
|
|
137
|
+
selectedActor: decision.selectedActor || null,
|
|
138
|
+
runtimeAgentRequested: "Sage",
|
|
139
|
+
runtimeAgentApplied: null,
|
|
140
|
+
runtimeAgentSupported: supportsAgent || false,
|
|
141
|
+
permissionDecision: decision.permissionDecision || null,
|
|
142
|
+
reason: decision.reason || null,
|
|
143
|
+
status: "started"
|
|
94
144
|
}
|
|
95
145
|
});
|
|
96
146
|
}
|
|
@@ -107,9 +157,10 @@ export class DelegationController {
|
|
|
107
157
|
const runResult = await this.adapter.execute(prompt, { agent: "Sage" });
|
|
108
158
|
|
|
109
159
|
if (this.ledger) {
|
|
160
|
+
const isApplied = runResult.runtimeAgentApplied === "Sage";
|
|
110
161
|
this.ledger.logEvent({
|
|
111
|
-
actor: "Sage",
|
|
112
|
-
actorType: "auditor",
|
|
162
|
+
actor: isApplied ? "Sage" : "Atlas",
|
|
163
|
+
actorType: isApplied ? "auditor" : "control-plane",
|
|
113
164
|
observed: true,
|
|
114
165
|
runtime: "opencode",
|
|
115
166
|
eventType: "validation_complete",
|
|
@@ -119,7 +170,15 @@ export class DelegationController {
|
|
|
119
170
|
commandsRun: runResult.commandsRun || [],
|
|
120
171
|
eventCount: runResult.eventCount || 0,
|
|
121
172
|
error: runResult.error,
|
|
122
|
-
event: "validation.completed"
|
|
173
|
+
event: "validation.completed",
|
|
174
|
+
requestedActor: decision.requestedActor || null,
|
|
175
|
+
selectedActor: decision.selectedActor || null,
|
|
176
|
+
runtimeAgentRequested: runResult.runtimeAgentRequested || "Sage",
|
|
177
|
+
runtimeAgentApplied: runResult.runtimeAgentApplied || null,
|
|
178
|
+
runtimeAgentSupported: runResult.runtimeAgentSupported !== undefined ? runResult.runtimeAgentSupported : (supportsAgent || false),
|
|
179
|
+
permissionDecision: decision.permissionDecision || null,
|
|
180
|
+
reason: runResult.error || decision.reason || null,
|
|
181
|
+
status: runResult.status || (runResult.success ? "COMPLETED" : "FAILED")
|
|
123
182
|
}
|
|
124
183
|
});
|
|
125
184
|
}
|
|
@@ -21,9 +21,8 @@ export class ExecutionPlanner {
|
|
|
21
21
|
? path.join("docs/workflows", taskSlug, "spec.md")
|
|
22
22
|
: null;
|
|
23
23
|
|
|
24
|
-
const owner = classification.
|
|
25
|
-
? "Astra"
|
|
26
|
-
: classification.owner;
|
|
24
|
+
const owner = classification.routingDecision?.selectedActor ||
|
|
25
|
+
(classification.intent === "write" ? "Astra" : classification.owner);
|
|
27
26
|
|
|
28
27
|
// Build default expected validations based on the resolved profile
|
|
29
28
|
const validationsExpected = [];
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { resolveWorkflowProfile, getWorkflowProfile } from "./workflow-profiles.js";
|
|
2
|
+
import { RequestRouter } from "./request-router.js";
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* RequestClassifier - Classifies natural requests into structured, testable contracts.
|
|
@@ -9,12 +10,14 @@ export class RequestClassifier {
|
|
|
9
10
|
* @param {string} request - The natural language request string.
|
|
10
11
|
* @returns {Object} Structured classification contract.
|
|
11
12
|
*/
|
|
12
|
-
classify(request = "") {
|
|
13
|
+
classify(request = "", options = {}) {
|
|
13
14
|
const text = String(request).trim();
|
|
14
15
|
if (!text) {
|
|
15
16
|
throw new Error("Cannot classify an empty request.");
|
|
16
17
|
}
|
|
17
18
|
|
|
19
|
+
const cwd = options.cwd || process.cwd();
|
|
20
|
+
|
|
18
21
|
// 1. Resolve Profile
|
|
19
22
|
const profile = resolveWorkflowProfile({ request: text });
|
|
20
23
|
const profileDef = getWorkflowProfile(profile);
|
|
@@ -43,16 +46,23 @@ export class RequestClassifier {
|
|
|
43
46
|
const specNeeded = mode === "full" && intent === "write";
|
|
44
47
|
const validationNeeded = intent === "write";
|
|
45
48
|
|
|
49
|
+
// Integrate central RequestRouter
|
|
50
|
+
const router = new RequestRouter({ cwd });
|
|
51
|
+
const requestUnderstanding = router.understand(text);
|
|
52
|
+
const routingDecision = router.route(requestUnderstanding);
|
|
53
|
+
|
|
46
54
|
return {
|
|
47
55
|
request: text,
|
|
48
56
|
profile,
|
|
49
|
-
owner: profileDef.owner,
|
|
50
|
-
intent,
|
|
57
|
+
owner: routingDecision.selectedActor || profileDef.owner,
|
|
58
|
+
intent: requestUnderstanding.mutationIntent === "readonly" ? "read-only" : "write",
|
|
51
59
|
mode,
|
|
52
|
-
risk,
|
|
60
|
+
risk: requestUnderstanding.riskLevel,
|
|
53
61
|
specNeeded,
|
|
54
62
|
validationNeeded,
|
|
55
|
-
skills: [...profileDef.skills]
|
|
63
|
+
skills: [...profileDef.skills],
|
|
64
|
+
requestUnderstanding,
|
|
65
|
+
routingDecision
|
|
56
66
|
};
|
|
57
67
|
}
|
|
58
68
|
}
|
|
@@ -0,0 +1,289 @@
|
|
|
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
|
+
}
|
|
@@ -51,12 +51,33 @@ export class OpenCodeAdapter {
|
|
|
51
51
|
*/
|
|
52
52
|
async execute(message, { agent = null, model = null, readOnly = false } = {}) {
|
|
53
53
|
const inspection = await this.inspect();
|
|
54
|
+
const isSpecializedAgent = agent && ["Nexus", "Orion", "Astra", "Sage", "Phoenix"].includes(agent);
|
|
55
|
+
|
|
54
56
|
if (!inspection.available) {
|
|
55
57
|
return {
|
|
56
58
|
success: false,
|
|
59
|
+
status: isSpecializedAgent ? "BLOCKED" : "FAILED",
|
|
57
60
|
commandsRun: [],
|
|
58
61
|
eventCount: 0,
|
|
59
|
-
error: "OpenCode CLI is not installed or not available in the system PATH."
|
|
62
|
+
error: "OpenCode CLI is not installed or not available in the system PATH.",
|
|
63
|
+
runtimeAgentRequested: agent || null,
|
|
64
|
+
runtimeAgentApplied: null,
|
|
65
|
+
runtimeAgentSupported: false,
|
|
66
|
+
runtimeAgentSelectionMode: isSpecializedAgent ? "explicit" : "default"
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (isSpecializedAgent && !inspection.supports.agent) {
|
|
71
|
+
return {
|
|
72
|
+
success: false,
|
|
73
|
+
status: "BLOCKED",
|
|
74
|
+
error: `Requested runtime actor '${agent}' cannot be applied because this OpenCode runtime does not support --agent.`,
|
|
75
|
+
runtimeAgentRequested: agent,
|
|
76
|
+
runtimeAgentApplied: null,
|
|
77
|
+
runtimeAgentSupported: false,
|
|
78
|
+
runtimeAgentSelectionMode: "unsupported",
|
|
79
|
+
commandsRun: [],
|
|
80
|
+
eventCount: 0
|
|
60
81
|
};
|
|
61
82
|
}
|
|
62
83
|
|
|
@@ -66,7 +87,7 @@ export class OpenCodeAdapter {
|
|
|
66
87
|
if (inspection.supports.formatJson) {
|
|
67
88
|
args.push("--format", "json");
|
|
68
89
|
}
|
|
69
|
-
if (
|
|
90
|
+
if (isSpecializedAgent && inspection.supports.agent) {
|
|
70
91
|
args.push("--agent", agent);
|
|
71
92
|
}
|
|
72
93
|
if (model && inspection.supports.model) {
|
|
@@ -125,7 +146,11 @@ export class OpenCodeAdapter {
|
|
|
125
146
|
success: false,
|
|
126
147
|
commandsRun,
|
|
127
148
|
eventCount,
|
|
128
|
-
error: `Read-only confinement violation: tool '${name}' or command attempted write/modify operation during read-only task
|
|
149
|
+
error: `Read-only confinement violation: tool '${name}' or command attempted write/modify operation during read-only task.`,
|
|
150
|
+
runtimeAgentRequested: agent || null,
|
|
151
|
+
runtimeAgentApplied: null,
|
|
152
|
+
runtimeAgentSupported: inspection.supports.agent,
|
|
153
|
+
runtimeAgentSelectionMode: isSpecializedAgent ? "explicit" : "default"
|
|
129
154
|
});
|
|
130
155
|
return;
|
|
131
156
|
}
|
|
@@ -143,7 +168,11 @@ export class OpenCodeAdapter {
|
|
|
143
168
|
resolve({
|
|
144
169
|
success: code === 0,
|
|
145
170
|
commandsRun,
|
|
146
|
-
eventCount
|
|
171
|
+
eventCount,
|
|
172
|
+
runtimeAgentRequested: agent || null,
|
|
173
|
+
runtimeAgentApplied: isSpecializedAgent ? agent : null,
|
|
174
|
+
runtimeAgentSupported: inspection.supports.agent,
|
|
175
|
+
runtimeAgentSelectionMode: isSpecializedAgent ? "explicit" : "default"
|
|
147
176
|
});
|
|
148
177
|
});
|
|
149
178
|
|
|
@@ -153,7 +182,11 @@ export class OpenCodeAdapter {
|
|
|
153
182
|
success: false,
|
|
154
183
|
commandsRun,
|
|
155
184
|
eventCount: 0,
|
|
156
|
-
error: err.message
|
|
185
|
+
error: err.message,
|
|
186
|
+
runtimeAgentRequested: agent || null,
|
|
187
|
+
runtimeAgentApplied: null,
|
|
188
|
+
runtimeAgentSupported: inspection.supports.agent,
|
|
189
|
+
runtimeAgentSelectionMode: isSpecializedAgent ? "explicit" : "default"
|
|
157
190
|
});
|
|
158
191
|
});
|
|
159
192
|
});
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
# AI Workflow Kit 2.3.0 Release Decision
|
|
2
|
-
|
|
3
|
-
**Status:** Finalized
|
|
4
|
-
**npm publish:** Pending (Awaiting user authorization)
|
|
5
|
-
**Published dist-tag:** `latest`
|
|
6
|
-
|
|
7
|
-
## Identity
|
|
8
|
-
|
|
9
|
-
- **Package:** `@williambeto/ai-workflow`
|
|
10
|
-
- **Version:** `2.3.0`
|
|
11
|
-
- **Commit:** `c16d06889ed4957840b69d61df43063908c3b220`
|
|
12
|
-
- **Tarball SHA256:** `24b1cb412bb4503add8d8962568cdc5ea819e207e6dd0d0e3d0f9d8502c063ce`
|
|
13
|
-
- **Previous public line:** `2.2.7`
|
|
14
|
-
|
|
15
|
-
## Validation evidence
|
|
16
|
-
|
|
17
|
-
- **Unit tests:** PASS — 142 tests passed
|
|
18
|
-
- **E2E tests:** PASS — 18 tests passed (including the new read-only safety validation and Sage/Phoenix/Astra loop verification)
|
|
19
|
-
- **Release-readiness validation:** PASS — 17 required files checked
|
|
20
|
-
- **Full validation:** PASS — 18/18 checks completed successfully (JSON, Cross-reference, Artifact safety, External link, Documentation consistency, Workflow state, Schema, Skill, Canonical policy reference, Privacy publication gate, UI evidence gate, Delegation, Behavioral contract, Token economy, Upgrade regression, Visual regression, Release readiness, Repository structure)
|
|
21
|
-
- **Safe pack:** PASS — `@williambeto/ai-workflow@2.3.0`, 310 files, zero root-level tarballs left behind
|
|
22
|
-
- **Fresh consumer install:** PASS (verified via packed consumer runner in a sandbox)
|
|
23
|
-
- **CLI help/init/doctor:** PASS (verified via packed consumer runner in a sandbox)
|
|
24
|
-
- **Real OpenCode Smoke Test:** PASS (integrated directly into the release verification)
|
|
25
|
-
- **Open Blocker/High findings:** None
|
|
26
|
-
|
|
27
|
-
## Accepted limitations
|
|
28
|
-
|
|
29
|
-
- See [known-limitations.md](../internal/known-limitations.md).
|
|
30
|
-
|
|
31
|
-
## Decision
|
|
32
|
-
|
|
33
|
-
Decision finalized for version `2.3.0`. The release includes the isolated read-only workspace runner, corrected snapshot comparisons, bypassed physical files write during read-only executions, real OpenCode smoke test integration in release verification, and Sage validation agent coordination in full mode.
|
|
34
|
-
|
|
35
|
-
**Approved by:** José Willams (Human User)
|