@williambeto/ai-workflow 2.7.1 → 2.8.1
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/CHANGELOG.md +69 -0
- package/README.md +3 -2
- package/bin/ai-workflow.js +1203 -301
- package/bin/ai-workflow.js.map +1 -1
- package/{chunk-W4RTQWVQ.js → chunk-4FI5ODAM.js} +879 -759
- package/chunk-AINIR25D.js +120 -0
- package/chunk-AY33SA5W.js +46 -0
- package/{evidence-validator-76ZQQYDU.js → chunk-FNT7DN3N.js} +22 -2
- package/{chunk-XW747GIG.js → chunk-H7GIKXFO.js} +339 -14
- package/{chunk-BDZPUAEX.js → chunk-LD7EHAU2.js} +352 -39
- package/chunk-YOBY5C72.js +76 -0
- package/core/index.d.ts +51 -1
- package/core/index.js +5 -3
- package/dist-assets/agents/atlas.md +25 -6
- package/dist-assets/commands/atlas.md +7 -2
- package/dist-assets/docs/QUICKSTART.md +10 -1
- package/dist-assets/docs/cli-reference.md +47 -2
- package/dist-assets/docs/compatibility/provider-usage.md +16 -1
- package/dist-assets/docs/compatibility/runtime-matrix.md +13 -2
- package/dist-assets/docs/policies/06-FINAL_EVIDENCE_CONTRACT.md +11 -1
- package/dist-assets/docs/policies/SKILLS_COMMON_GOVERNANCE.md +16 -0
- package/dist-assets/schemas/README.md +4 -0
- package/dist-assets/schemas/approval-receipt.schema.json +35 -0
- package/dist-assets/schemas/evidence.schema.json +93 -1
- package/evidence-validator-HS3NTWAB.js +8 -0
- package/package.json +17 -2
- package/skill-4MEGJ3DO.js +211 -0
- package/skill-frontmatter-linter-FMJADOK4.js +14 -0
- package/{validate-A46WUBVZ.js → validate-F3ZH63LI.js} +3 -3
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import {
|
|
2
2
|
ArtifactFidelityGate,
|
|
3
|
-
DeliveryDecisionEngine
|
|
4
|
-
|
|
3
|
+
DeliveryDecisionEngine,
|
|
4
|
+
RequestRouter
|
|
5
|
+
} from "./chunk-H7GIKXFO.js";
|
|
5
6
|
|
|
6
7
|
// src/core/package-assets.ts
|
|
7
8
|
import { fileURLToPath } from "url";
|
|
@@ -94,6 +95,145 @@ var AGENT_PROMPT_CONTRACTS = {
|
|
|
94
95
|
Sage: "Act as Sage, the independent validation agent. Audit the implementation against requirements, inspect evidence, and report concrete pass/fail findings.",
|
|
95
96
|
Phoenix: "Act as Phoenix, the bounded remediation agent. Remediate only concrete findings, keep attempts bounded, and stop when no progress is made."
|
|
96
97
|
};
|
|
98
|
+
function buildPromptFallback(agent, message, orchestratedChild = false) {
|
|
99
|
+
if (agent === "Astra") {
|
|
100
|
+
return [
|
|
101
|
+
"You are Atlas, the AI Workflow Kit coordinator running under Atlas permissions.",
|
|
102
|
+
"Delegate the implementation exactly once to the configured Astra agent using the task tool. Do not edit workspace files yourself and do not pretend to be Astra.",
|
|
103
|
+
orchestratedChild ? "The parent execute process already owns branch safety, validation, evidence, and finalization. Do not run git branch/status, collect-evidence, handoff, or another execute workflow." : "Keep coordination minimal and let Astra own every requested workspace mutation.",
|
|
104
|
+
"Ask Astra to make the smallest scoped change and run only proportional validation: docs integrity for docs, a focused behavior test for simple code, and tests plus build for normal React work. Screenshots, README changes, Sage, and Phoenix are conditional, not default.",
|
|
105
|
+
"Return Astra's observed result without adding a second implementation or finalization pass.",
|
|
106
|
+
"",
|
|
107
|
+
"User request:",
|
|
108
|
+
message
|
|
109
|
+
].join("\n");
|
|
110
|
+
}
|
|
111
|
+
return `${AGENT_PROMPT_CONTRACTS[agent]}
|
|
112
|
+
|
|
113
|
+
Runtime agent selection note: requested actor '${agent}' could not be applied with --agent because this OpenCode runtime does not support it. Continue under this bounded prompt contract and preserve all AI Workflow Kit safety rules.
|
|
114
|
+
|
|
115
|
+
User request:
|
|
116
|
+
${message}`;
|
|
117
|
+
}
|
|
118
|
+
function extractSdkExecution(data, requestedAgent) {
|
|
119
|
+
const parts = data?.parts || [];
|
|
120
|
+
const output = parts.filter((part) => part.type === "text" && typeof part.text === "string").map((part) => part.text).join("");
|
|
121
|
+
const commandsRun = parts.filter((part) => part.type === "tool").map((part) => part.state?.input?.command ?? part.state?.input?.CommandLine).filter((command) => typeof command === "string");
|
|
122
|
+
const commandEvents = commandsRun.map((command) => ({
|
|
123
|
+
command,
|
|
124
|
+
tool: "sdk-tool",
|
|
125
|
+
status: "UNKNOWN",
|
|
126
|
+
exitCode: null,
|
|
127
|
+
source: "sdk"
|
|
128
|
+
}));
|
|
129
|
+
const appliedAgent = typeof data?.info?.agent === "string" ? data.info.agent : null;
|
|
130
|
+
const confirmed = appliedAgent === requestedAgent;
|
|
131
|
+
return {
|
|
132
|
+
success: !data?.info?.error && confirmed,
|
|
133
|
+
commandsRun,
|
|
134
|
+
commandEvents,
|
|
135
|
+
eventCount: parts.length,
|
|
136
|
+
runtimeAgentApplied: appliedAgent,
|
|
137
|
+
runtimeActorConfirmation: confirmed ? "confirmed" : "unavailable",
|
|
138
|
+
output
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
function extractCommandEvents(event) {
|
|
142
|
+
const calls = Array.isArray(event?.part?.toolCalls) ? event.part.toolCalls : event?.type === "tool" || event?.type === "tool_use" || event?.type === "tool_result" ? [event.part || event] : [];
|
|
143
|
+
return calls.flatMap((call) => {
|
|
144
|
+
const input = call?.args || call?.input || call?.state?.input || call?.part?.state?.input || {};
|
|
145
|
+
const command = input.command ?? input.CommandLine ?? call?.command;
|
|
146
|
+
if (typeof command !== "string" || !command.trim()) return [];
|
|
147
|
+
const rawExit = call?.exitCode ?? call?.output?.exitCode ?? call?.state?.metadata?.exitCode ?? event?.exitCode;
|
|
148
|
+
const exitCode = typeof rawExit === "number" ? rawExit : null;
|
|
149
|
+
return [{
|
|
150
|
+
command: command.trim(),
|
|
151
|
+
tool: String(call?.name || call?.tool || event?.tool || "command"),
|
|
152
|
+
status: exitCode === null ? "UNKNOWN" : exitCode === 0 ? "PASS" : "FAIL",
|
|
153
|
+
exitCode,
|
|
154
|
+
source: "json-event"
|
|
155
|
+
}];
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
function extractDelegatedAgents(event) {
|
|
159
|
+
const calls = Array.isArray(event?.part?.toolCalls) ? event.part.toolCalls : ["tool", "tool_use"].includes(event?.type) ? [event?.part || event] : [];
|
|
160
|
+
const delegatedAgents = [];
|
|
161
|
+
for (const call of calls) {
|
|
162
|
+
const tool = String(call?.name || call?.tool || event?.tool || "").toLowerCase();
|
|
163
|
+
if (tool !== "task") continue;
|
|
164
|
+
const input = call?.args || call?.input || call?.state?.input || call?.part?.state?.input || {};
|
|
165
|
+
const delegated = input.subagent_type ?? input.agent ?? input.subagent ?? input.agentName;
|
|
166
|
+
if (typeof delegated === "string" && delegated.trim()) delegatedAgents.push(delegated.trim());
|
|
167
|
+
}
|
|
168
|
+
return delegatedAgents;
|
|
169
|
+
}
|
|
170
|
+
function countAstraCompletionMarkers(value) {
|
|
171
|
+
return [...String(value || "").matchAll(/(?:✓|✔).*\bAstra Agent\b/gi)].length;
|
|
172
|
+
}
|
|
173
|
+
var MUTATING_GIT_COMMANDS = /* @__PURE__ */ new Set([
|
|
174
|
+
"add",
|
|
175
|
+
"am",
|
|
176
|
+
"apply",
|
|
177
|
+
"bisect",
|
|
178
|
+
"checkout",
|
|
179
|
+
"cherry-pick",
|
|
180
|
+
"clean",
|
|
181
|
+
"clone",
|
|
182
|
+
"commit",
|
|
183
|
+
"fetch",
|
|
184
|
+
"gc",
|
|
185
|
+
"init",
|
|
186
|
+
"merge",
|
|
187
|
+
"mv",
|
|
188
|
+
"notes",
|
|
189
|
+
"pull",
|
|
190
|
+
"push",
|
|
191
|
+
"rebase",
|
|
192
|
+
"reset",
|
|
193
|
+
"restore",
|
|
194
|
+
"revert",
|
|
195
|
+
"rm",
|
|
196
|
+
"stash",
|
|
197
|
+
"switch",
|
|
198
|
+
"update-index"
|
|
199
|
+
]);
|
|
200
|
+
function gitCommandIsDestructive(command) {
|
|
201
|
+
for (const match of command.matchAll(/\bgit\b([^;&|]*)/gi)) {
|
|
202
|
+
const tokens = match[1].trim().split(/\s+/).filter(Boolean);
|
|
203
|
+
while (tokens[0]?.startsWith("-")) {
|
|
204
|
+
const option = tokens.shift();
|
|
205
|
+
if (["-C", "--git-dir", "--work-tree", "--namespace"].includes(option || "")) tokens.shift();
|
|
206
|
+
}
|
|
207
|
+
const subcommand = tokens.shift()?.toLowerCase();
|
|
208
|
+
if (!subcommand) continue;
|
|
209
|
+
if (MUTATING_GIT_COMMANDS.has(subcommand)) return true;
|
|
210
|
+
if (subcommand === "branch") {
|
|
211
|
+
if (tokens.length === 0) continue;
|
|
212
|
+
const mutatingBranchFlag = tokens.some(
|
|
213
|
+
(token) => /^(?:-[dDmMcC]|--(?:delete|move|copy|edit-description|set-upstream-to|unset-upstream))(?:=|$)/.test(token)
|
|
214
|
+
);
|
|
215
|
+
if (mutatingBranchFlag) return true;
|
|
216
|
+
const first = tokens[0];
|
|
217
|
+
const readOnlyBranchFlag = /^(?:--(?:show-current|list|all|remotes|contains|no-contains|merged|no-merged|points-at|format|sort|column|no-column|color|no-color)(?:=|$)|-[larv](?:$|v$))/.test(first);
|
|
218
|
+
if (!readOnlyBranchFlag) return true;
|
|
219
|
+
}
|
|
220
|
+
if (subcommand === "tag") {
|
|
221
|
+
if (tokens.length === 0) continue;
|
|
222
|
+
const first = tokens[0];
|
|
223
|
+
if (!/^(?:--(?:list|contains|no-contains|merged|no-merged|points-at|format|sort)(?:=|$)|-[ln])/.test(first)) return true;
|
|
224
|
+
}
|
|
225
|
+
if (subcommand === "remote" && tokens.length > 0 && !/^(?:-v|--verbose|show|get-url)$/.test(tokens[0])) return true;
|
|
226
|
+
if (subcommand === "worktree" && tokens[0] !== "list") return true;
|
|
227
|
+
}
|
|
228
|
+
return false;
|
|
229
|
+
}
|
|
230
|
+
function isDestructiveCommand(command) {
|
|
231
|
+
if (/(^|[^<])(?:>>|>)/.test(command)) return true;
|
|
232
|
+
if (/\b(?:touch|rm|mkdir|cp|mv|chmod|chown|tee)\b/.test(command)) return true;
|
|
233
|
+
if (/\bsed\b[^;&|]*(?:\s-i[^\s]*|\s--in-place(?:=|\s|$))/.test(command)) return true;
|
|
234
|
+
if (/\bawk\b[^;&|]*\s-i\s*inplace\b/.test(command)) return true;
|
|
235
|
+
return gitCommandIsDestructive(command);
|
|
236
|
+
}
|
|
97
237
|
var OpenCodeAdapter = class {
|
|
98
238
|
cwd;
|
|
99
239
|
constructor({ cwd = process.cwd() } = {}) {
|
|
@@ -110,13 +250,14 @@ var OpenCodeAdapter = class {
|
|
|
110
250
|
if (!available) {
|
|
111
251
|
return {
|
|
112
252
|
available: false,
|
|
113
|
-
supports: { run: false, formatJson: false, agent: false, model: false }
|
|
253
|
+
supports: { run: false, server: false, formatJson: false, agent: false, model: false }
|
|
114
254
|
};
|
|
115
255
|
}
|
|
116
256
|
return {
|
|
117
257
|
available: true,
|
|
118
258
|
supports: {
|
|
119
259
|
run: /\brun\b/.test(cliHelp.stdout),
|
|
260
|
+
server: /\bserve\b/.test(cliHelp.stdout),
|
|
120
261
|
formatJson: /--format/.test(runHelp.stdout),
|
|
121
262
|
agent: /--agent/.test(runHelp.stdout),
|
|
122
263
|
model: /--model/.test(runHelp.stdout)
|
|
@@ -125,14 +266,14 @@ var OpenCodeAdapter = class {
|
|
|
125
266
|
} catch {
|
|
126
267
|
return {
|
|
127
268
|
available: false,
|
|
128
|
-
supports: { run: false, formatJson: false, agent: false, model: false }
|
|
269
|
+
supports: { run: false, server: false, formatJson: false, agent: false, model: false }
|
|
129
270
|
};
|
|
130
271
|
}
|
|
131
272
|
}
|
|
132
273
|
/**
|
|
133
274
|
* Runs opencode with a prompt and options.
|
|
134
275
|
*/
|
|
135
|
-
async execute(message, { agent = null, model = null, readOnly = false, fastTrack = false } = {}) {
|
|
276
|
+
async execute(message, { agent = null, model = null, readOnly = false, fastTrack = false, requireActorConfirmation = false, orchestratedChild = false } = {}) {
|
|
136
277
|
const inspection = await this.inspect();
|
|
137
278
|
const isSpecializedAgent = agent && ["Nexus", "Orion", "Astra", "Sage", "Phoenix"].includes(agent);
|
|
138
279
|
if (!readOnly) {
|
|
@@ -152,6 +293,8 @@ var OpenCodeAdapter = class {
|
|
|
152
293
|
runtimeAgentApplied: null,
|
|
153
294
|
runtimeAgentSupported: false,
|
|
154
295
|
runtimeAgentSelectionMode: "unsupported",
|
|
296
|
+
runtimeActorConfirmation: "unavailable",
|
|
297
|
+
output: "",
|
|
155
298
|
commandsRun: [],
|
|
156
299
|
eventCount: 0
|
|
157
300
|
};
|
|
@@ -159,26 +302,103 @@ var OpenCodeAdapter = class {
|
|
|
159
302
|
}
|
|
160
303
|
}
|
|
161
304
|
if (!inspection.available) {
|
|
305
|
+
console.warn("\\n[WARNING] OpenCode CLI is not installed or not available in the system PATH.");
|
|
306
|
+
console.warn("[WARNING] Graceful degradation active: simulating successful execution (dry-run).\\n");
|
|
162
307
|
return {
|
|
163
|
-
success:
|
|
164
|
-
status:
|
|
308
|
+
success: true,
|
|
309
|
+
status: "DEGRADED",
|
|
165
310
|
commandsRun: [],
|
|
166
311
|
eventCount: 0,
|
|
167
|
-
error: "OpenCode CLI
|
|
312
|
+
error: "OpenCode CLI missing. Simulated execution.",
|
|
168
313
|
runtimeAgentRequested: agent || null,
|
|
169
314
|
runtimeAgentApplied: null,
|
|
170
315
|
runtimeAgentSupported: false,
|
|
171
|
-
runtimeAgentSelectionMode:
|
|
316
|
+
runtimeAgentSelectionMode: "degraded",
|
|
317
|
+
runtimeActorConfirmation: "unavailable",
|
|
318
|
+
output: ""
|
|
172
319
|
};
|
|
173
320
|
}
|
|
174
321
|
const usePromptFallback = Boolean(isSpecializedAgent && !inspection.supports.agent && agent);
|
|
175
|
-
const
|
|
322
|
+
const agentMessage = usePromptFallback ? buildPromptFallback(agent, message, orchestratedChild) : message;
|
|
323
|
+
const runtimeMessage = orchestratedChild ? `${agentMessage}
|
|
176
324
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
325
|
+
AIWK_ORCHESTRATED_CHILD: The parent execute process exclusively owns collect-evidence, handoff, and finalization. Do not invoke those operations or start another execute workflow.` : agentMessage;
|
|
326
|
+
const runtimeAgentSelectionMode = isSpecializedAgent ? usePromptFallback && agent === "Astra" ? "atlas-delegation-fallback" : usePromptFallback ? "prompt-fallback" : "explicit" : "default";
|
|
327
|
+
const runWithSdk = async (targetCwd) => {
|
|
328
|
+
if (!agent) {
|
|
329
|
+
return {
|
|
330
|
+
success: false,
|
|
331
|
+
status: "BLOCKED",
|
|
332
|
+
error: "Actor confirmation requires a requested agent.",
|
|
333
|
+
commandsRun: [],
|
|
334
|
+
eventCount: 0,
|
|
335
|
+
runtimeAgentRequested: null,
|
|
336
|
+
runtimeAgentApplied: null,
|
|
337
|
+
runtimeAgentSupported: inspection.supports.agent,
|
|
338
|
+
runtimeAgentSelectionMode: "sdk-session",
|
|
339
|
+
runtimeActorConfirmation: "unavailable",
|
|
340
|
+
output: ""
|
|
341
|
+
};
|
|
342
|
+
}
|
|
343
|
+
let server;
|
|
344
|
+
try {
|
|
345
|
+
const { createOpencode } = await import("@opencode-ai/sdk/v2");
|
|
346
|
+
const previousFinalizationOwner = process.env.AI_WORKFLOW_FINALIZATION_OWNER;
|
|
347
|
+
if (orchestratedChild) process.env.AI_WORKFLOW_FINALIZATION_OWNER = "execute";
|
|
348
|
+
let runtime;
|
|
349
|
+
try {
|
|
350
|
+
runtime = await createOpencode({ port: 0, timeout: 1e4 });
|
|
351
|
+
} finally {
|
|
352
|
+
if (previousFinalizationOwner === void 0) delete process.env.AI_WORKFLOW_FINALIZATION_OWNER;
|
|
353
|
+
else process.env.AI_WORKFLOW_FINALIZATION_OWNER = previousFinalizationOwner;
|
|
354
|
+
}
|
|
355
|
+
server = runtime.server;
|
|
356
|
+
const permissions = readOnly ? [
|
|
357
|
+
{ permission: "edit", pattern: "*", action: "deny" },
|
|
358
|
+
{ permission: "bash", pattern: "*", action: "deny" },
|
|
359
|
+
{ permission: "task", pattern: "*", action: "deny" },
|
|
360
|
+
{ permission: "external_directory", pattern: "*", action: "deny" }
|
|
361
|
+
] : void 0;
|
|
362
|
+
const session = await runtime.client.session.create({
|
|
363
|
+
directory: targetCwd,
|
|
364
|
+
title: `AI Workflow Kit: ${agent}`,
|
|
365
|
+
agent,
|
|
366
|
+
permission: permissions
|
|
367
|
+
}, { throwOnError: true });
|
|
368
|
+
const response = await runtime.client.session.prompt({
|
|
369
|
+
sessionID: session.data.id,
|
|
370
|
+
directory: targetCwd,
|
|
371
|
+
agent,
|
|
372
|
+
parts: [{ type: "text", text: runtimeMessage }]
|
|
373
|
+
}, { throwOnError: true });
|
|
374
|
+
const extracted = extractSdkExecution(response.data, agent);
|
|
375
|
+
if (extracted.output) process.stdout.write(extracted.output);
|
|
376
|
+
return {
|
|
377
|
+
...extracted,
|
|
378
|
+
status: extracted.success ? "COMPLETED" : "BLOCKED",
|
|
379
|
+
error: extracted.success ? void 0 : extracted.runtimeAgentApplied ? `Runtime confirmed actor '${extracted.runtimeAgentApplied}', expected '${agent}'.` : `Runtime did not provide machine-readable confirmation for actor '${agent}'.`,
|
|
380
|
+
runtimeAgentRequested: agent,
|
|
381
|
+
runtimeAgentSupported: inspection.supports.agent,
|
|
382
|
+
runtimeAgentSelectionMode: "sdk-session"
|
|
383
|
+
};
|
|
384
|
+
} catch (error) {
|
|
385
|
+
return {
|
|
386
|
+
success: false,
|
|
387
|
+
status: "BLOCKED",
|
|
388
|
+
error: `OpenCode SDK actor confirmation failed: ${error.message}`,
|
|
389
|
+
commandsRun: [],
|
|
390
|
+
eventCount: 0,
|
|
391
|
+
runtimeAgentRequested: agent,
|
|
392
|
+
runtimeAgentApplied: null,
|
|
393
|
+
runtimeAgentSupported: inspection.supports.agent,
|
|
394
|
+
runtimeAgentSelectionMode: "sdk-session",
|
|
395
|
+
runtimeActorConfirmation: "unavailable",
|
|
396
|
+
output: ""
|
|
397
|
+
};
|
|
398
|
+
} finally {
|
|
399
|
+
server?.close();
|
|
400
|
+
}
|
|
401
|
+
};
|
|
182
402
|
const runInWorkspace = async (targetCwd) => {
|
|
183
403
|
return new Promise((resolve2) => {
|
|
184
404
|
const args = ["run", runtimeMessage];
|
|
@@ -194,6 +414,7 @@ ${message}` : message;
|
|
|
194
414
|
console.log(`[RUNTIME] Delegating to OpenCode: opencode ${args.map((a) => a.includes(" ") ? `"${a}"` : a).join(" ")}`);
|
|
195
415
|
const child = spawn("opencode", args, {
|
|
196
416
|
cwd: targetCwd,
|
|
417
|
+
env: orchestratedChild ? { ...process.env, AI_WORKFLOW_FINALIZATION_OWNER: "execute" } : process.env,
|
|
197
418
|
stdio: ["ignore", "pipe", "inherit"]
|
|
198
419
|
// Inherit stderr to show warnings directly
|
|
199
420
|
});
|
|
@@ -202,29 +423,65 @@ ${message}` : message;
|
|
|
202
423
|
terminal: false
|
|
203
424
|
});
|
|
204
425
|
const commandsRun = [];
|
|
426
|
+
const commandEvents = [];
|
|
205
427
|
let eventCount = 0;
|
|
428
|
+
let output = "";
|
|
429
|
+
let confirmedAgent = null;
|
|
430
|
+
let delegatedAgent = null;
|
|
431
|
+
let astraTaskInvocations = 0;
|
|
432
|
+
let astraCompletionMarkers = 0;
|
|
206
433
|
rl.on("line", (line) => {
|
|
207
434
|
const trimmed = line.trim();
|
|
208
435
|
if (!trimmed) return;
|
|
209
436
|
try {
|
|
210
437
|
const event = JSON.parse(trimmed);
|
|
211
438
|
eventCount++;
|
|
439
|
+
const observedDelegations = extractDelegatedAgents(event);
|
|
440
|
+
astraTaskInvocations += observedDelegations.filter((value) => value === "Astra").length;
|
|
441
|
+
if (observedDelegations.includes("Astra")) delegatedAgent = "Astra";
|
|
442
|
+
for (const observation of extractCommandEvents(event)) {
|
|
443
|
+
commandEvents.push(observation);
|
|
444
|
+
if (!commandsRun.includes(observation.command)) commandsRun.push(observation.command);
|
|
445
|
+
}
|
|
446
|
+
if (readOnly) {
|
|
447
|
+
const unsafe = commandEvents.find((item) => isDestructiveCommand(item.command));
|
|
448
|
+
if (unsafe) {
|
|
449
|
+
child.kill();
|
|
450
|
+
resolve2({
|
|
451
|
+
success: false,
|
|
452
|
+
commandsRun,
|
|
453
|
+
commandEvents,
|
|
454
|
+
eventCount,
|
|
455
|
+
error: `Read-only confinement violation: command '${unsafe.command}' attempted write/modify operation during read-only task.`,
|
|
456
|
+
runtimeAgentRequested: agent || null,
|
|
457
|
+
runtimeAgentApplied: null,
|
|
458
|
+
runtimeAgentSupported: inspection.supports.agent,
|
|
459
|
+
runtimeAgentSelectionMode,
|
|
460
|
+
runtimeActorConfirmation: confirmedAgent ? "confirmed" : "unavailable",
|
|
461
|
+
output
|
|
462
|
+
});
|
|
463
|
+
return;
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
if (event.type === "agent_identity" && event.confirmed === true && typeof event.agent === "string") {
|
|
467
|
+
confirmedAgent = event.agent;
|
|
468
|
+
}
|
|
212
469
|
if (event.type === "text" && event.part?.text) {
|
|
213
470
|
process.stdout.write(event.part.text);
|
|
471
|
+
output += event.part.text;
|
|
472
|
+
const markers = countAstraCompletionMarkers(event.part.text);
|
|
473
|
+
astraCompletionMarkers += markers;
|
|
474
|
+
if (markers > 0) delegatedAgent = "Astra";
|
|
214
475
|
}
|
|
215
476
|
if (event.type === "step_start" && event.part?.toolCalls) {
|
|
216
477
|
for (const call of event.part.toolCalls) {
|
|
217
|
-
if (call.name === "run_command" && call.args?.CommandLine) {
|
|
218
|
-
commandsRun.push(call.args.CommandLine);
|
|
219
|
-
}
|
|
220
478
|
if (readOnly) {
|
|
221
479
|
const name = call.name || "";
|
|
222
480
|
const isWriteTool = name.startsWith("write_") || name.includes("write") || name.includes("replace") || name.includes("edit") || name.includes("delete") || name.includes("remove");
|
|
223
481
|
let isDestructiveGit = false;
|
|
224
482
|
if (name === "run_command" && call.args?.CommandLine) {
|
|
225
483
|
const cmd = call.args.CommandLine;
|
|
226
|
-
|
|
227
|
-
if (destPattern.test(cmd)) {
|
|
484
|
+
if (isDestructiveCommand(cmd)) {
|
|
228
485
|
isDestructiveGit = true;
|
|
229
486
|
}
|
|
230
487
|
}
|
|
@@ -240,7 +497,9 @@ ${message}` : message;
|
|
|
240
497
|
runtimeAgentRequested: agent || null,
|
|
241
498
|
runtimeAgentApplied: usePromptFallback ? null : null,
|
|
242
499
|
runtimeAgentSupported: inspection.supports.agent,
|
|
243
|
-
runtimeAgentSelectionMode
|
|
500
|
+
runtimeAgentSelectionMode,
|
|
501
|
+
runtimeActorConfirmation: confirmedAgent ? "confirmed" : "unavailable",
|
|
502
|
+
output
|
|
244
503
|
});
|
|
245
504
|
return;
|
|
246
505
|
}
|
|
@@ -249,18 +508,39 @@ ${message}` : message;
|
|
|
249
508
|
}
|
|
250
509
|
} catch {
|
|
251
510
|
console.log(trimmed);
|
|
511
|
+
output += `${trimmed}
|
|
512
|
+
`;
|
|
513
|
+
const shellCommand = trimmed.match(/^\$\s+(.+)$/)?.[1];
|
|
514
|
+
if (shellCommand) {
|
|
515
|
+
const observation = { command: shellCommand, tool: "shell", status: "UNKNOWN", exitCode: null, source: "text-output" };
|
|
516
|
+
commandEvents.push(observation);
|
|
517
|
+
if (!commandsRun.includes(shellCommand)) commandsRun.push(shellCommand);
|
|
518
|
+
}
|
|
519
|
+
const markers = usePromptFallback && agent === "Astra" ? countAstraCompletionMarkers(trimmed) : 0;
|
|
520
|
+
if (markers > 0) {
|
|
521
|
+
astraCompletionMarkers += markers;
|
|
522
|
+
delegatedAgent = "Astra";
|
|
523
|
+
}
|
|
252
524
|
}
|
|
253
525
|
});
|
|
254
526
|
child.on("close", (code) => {
|
|
255
527
|
console.log("\n[RUNTIME] OpenCode finished execution.");
|
|
528
|
+
const fallbackDelegationRequired = usePromptFallback && agent === "Astra";
|
|
529
|
+
const observedAstraDelegations = astraTaskInvocations > 0 ? astraTaskInvocations : astraCompletionMarkers;
|
|
530
|
+
const fallbackDelegationConfirmed = !fallbackDelegationRequired || delegatedAgent === "Astra" && observedAstraDelegations === 1;
|
|
531
|
+
const appliedAgent = fallbackDelegationRequired ? delegatedAgent : isSpecializedAgent ? confirmedAgent : null;
|
|
256
532
|
resolve2({
|
|
257
|
-
success: code === 0,
|
|
533
|
+
success: code === 0 && fallbackDelegationConfirmed,
|
|
258
534
|
commandsRun,
|
|
535
|
+
commandEvents,
|
|
259
536
|
eventCount,
|
|
537
|
+
error: code === 0 && !fallbackDelegationConfirmed ? `OpenCode fallback requires exactly one observed Atlas-to-Astra delegation; observed ${observedAstraDelegations}.` : void 0,
|
|
260
538
|
runtimeAgentRequested: agent || null,
|
|
261
|
-
runtimeAgentApplied:
|
|
539
|
+
runtimeAgentApplied: appliedAgent,
|
|
262
540
|
runtimeAgentSupported: inspection.supports.agent,
|
|
263
|
-
runtimeAgentSelectionMode
|
|
541
|
+
runtimeAgentSelectionMode,
|
|
542
|
+
runtimeActorConfirmation: appliedAgent ? "confirmed" : "unavailable",
|
|
543
|
+
output
|
|
264
544
|
});
|
|
265
545
|
});
|
|
266
546
|
child.on("error", (err) => {
|
|
@@ -269,176 +549,81 @@ ${message}` : message;
|
|
|
269
549
|
resolve2({
|
|
270
550
|
success: false,
|
|
271
551
|
commandsRun,
|
|
552
|
+
commandEvents,
|
|
272
553
|
eventCount: 0,
|
|
273
554
|
error: err.message,
|
|
274
555
|
runtimeAgentRequested: agent || null,
|
|
275
556
|
runtimeAgentApplied: null,
|
|
276
557
|
runtimeAgentSupported: inspection.supports.agent,
|
|
277
|
-
runtimeAgentSelectionMode
|
|
558
|
+
runtimeAgentSelectionMode,
|
|
559
|
+
runtimeActorConfirmation: "unavailable",
|
|
560
|
+
output
|
|
278
561
|
});
|
|
279
562
|
});
|
|
280
563
|
});
|
|
281
564
|
};
|
|
565
|
+
const runner = requireActorConfirmation && inspection.supports.server ? runWithSdk : runInWorkspace;
|
|
282
566
|
if (readOnly && !fastTrack) {
|
|
283
567
|
const { runInReadOnlyWorkspace } = await import("./read-only-workspace-PZBE7KAX.js");
|
|
284
|
-
return runInReadOnlyWorkspace(this.cwd,
|
|
568
|
+
return runInReadOnlyWorkspace(this.cwd, runner);
|
|
285
569
|
} else {
|
|
286
|
-
return
|
|
570
|
+
return runner(this.cwd);
|
|
287
571
|
}
|
|
288
572
|
}
|
|
289
573
|
};
|
|
290
574
|
|
|
291
|
-
// src/core/
|
|
292
|
-
import
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
console.error(`Failed to write to gate log: ${error.message}`);
|
|
324
|
-
}
|
|
325
|
-
}
|
|
326
|
-
getDirtyState() {
|
|
327
|
-
const lines = this.run("git status --short").split("\n").filter(Boolean);
|
|
328
|
-
const tracked = lines.filter((line) => !line.startsWith("??"));
|
|
329
|
-
const untracked = lines.filter((line) => line.startsWith("??"));
|
|
330
|
-
return { clean: lines.length === 0, tracked, untracked, lines };
|
|
331
|
-
}
|
|
332
|
-
createScopedBranch(taskSlug) {
|
|
333
|
-
const base = `feat/${slugify(taskSlug)}`;
|
|
334
|
-
let candidate = base;
|
|
335
|
-
let suffix = 2;
|
|
336
|
-
while (true) {
|
|
337
|
-
try {
|
|
338
|
-
this.run(`git show-ref --verify --quiet refs/heads/${candidate}`);
|
|
339
|
-
candidate = `${base}-${suffix++}`;
|
|
340
|
-
} catch {
|
|
341
|
-
break;
|
|
342
|
-
}
|
|
343
|
-
}
|
|
344
|
-
this.run(`git switch -c ${candidate}`);
|
|
345
|
-
return candidate;
|
|
346
|
-
}
|
|
347
|
-
getCurrentBranch() {
|
|
348
|
-
try {
|
|
349
|
-
return this.run("git branch --show-current") || this.run("git symbolic-ref --short HEAD") || "unknown";
|
|
350
|
-
} catch {
|
|
351
|
-
try {
|
|
352
|
-
return this.run("git symbolic-ref --short HEAD") || "unknown";
|
|
353
|
-
} catch {
|
|
354
|
-
return "unknown";
|
|
355
|
-
}
|
|
356
|
-
}
|
|
357
|
-
}
|
|
358
|
-
/**
|
|
359
|
-
* Strictly verifies branch safety without any override bypasses.
|
|
360
|
-
*/
|
|
361
|
-
check(overrideIgnored = "", { taskSlug = "implementation", readOnly = false } = {}) {
|
|
362
|
-
if (readOnly) {
|
|
363
|
-
const currentBranch = this.getCurrentBranch();
|
|
364
|
-
return { blocked: false, branch: currentBranch, recovered: false, readOnly: true };
|
|
365
|
-
}
|
|
366
|
-
try {
|
|
367
|
-
try {
|
|
368
|
-
this.run("git rev-parse --is-inside-work-tree");
|
|
369
|
-
} catch (e) {
|
|
370
|
-
const reason = "Git is unavailable or not inside a Git repository. Implementation work is blocked.";
|
|
371
|
-
this.log(`BLOCKED: ${reason}`);
|
|
372
|
-
return { blocked: true, branch: "unknown", reason };
|
|
373
|
-
}
|
|
374
|
-
const currentBranch = this.getCurrentBranch();
|
|
375
|
-
if (!currentBranch || currentBranch === "unknown" || currentBranch.trim() === "") {
|
|
376
|
-
const reason = "Could not determine current Git branch name. Implementation work is blocked.";
|
|
377
|
-
this.log(`BLOCKED: ${reason}`);
|
|
378
|
-
return { blocked: true, branch: "unknown", reason };
|
|
575
|
+
// src/core/sdd/validator.ts
|
|
576
|
+
import fs from "fs/promises";
|
|
577
|
+
var SpecValidator = class {
|
|
578
|
+
validateContent(content, requiredStatus = "APPROVED") {
|
|
579
|
+
const title = content.split(/\r?\n/, 1)[0]?.trim() || "";
|
|
580
|
+
let tier = "unknown";
|
|
581
|
+
if (/^#\s+\[DEEP\](?:\s|$)/.test(title)) tier = "deep";
|
|
582
|
+
else if (/^#\s+\[STANDARD\](?:\s|$)/.test(title)) tier = "standard";
|
|
583
|
+
else if (/^#\s+\[TINY\](?:\s|$)/.test(title)) tier = "tiny";
|
|
584
|
+
if (tier === "unknown") {
|
|
585
|
+
return { valid: false, reason: "Specification tier [DEEP|STANDARD|TINY] not identified in title." };
|
|
586
|
+
}
|
|
587
|
+
if (!/^## Metadata\s*$/m.test(content)) {
|
|
588
|
+
return { valid: false, reason: "Missing '## Metadata' section.", tier };
|
|
589
|
+
}
|
|
590
|
+
if (!/^## Acceptance Criteria\s*$/m.test(content)) {
|
|
591
|
+
return { valid: false, reason: "Missing '## Acceptance Criteria' section.", tier };
|
|
592
|
+
}
|
|
593
|
+
const statusMatch = content.match(/^\s*-?\s*(?:\*\*)?Status(?:\*\*)?\s*:\s*(.+?)\s*$/im);
|
|
594
|
+
if (!statusMatch) {
|
|
595
|
+
return { valid: false, reason: "Missing Status field in Metadata section.", tier };
|
|
596
|
+
}
|
|
597
|
+
const status = statusMatch[1].replace(/[\*_`]/g, "").trim().toUpperCase();
|
|
598
|
+
if (status.includes("|")) {
|
|
599
|
+
return { valid: false, reason: `Specification status is a template list. Must be explicitly set to '${requiredStatus}'.`, tier };
|
|
600
|
+
}
|
|
601
|
+
if (status !== requiredStatus) {
|
|
602
|
+
return { valid: false, reason: `Specification status is '${status}', but must be '${requiredStatus}' to proceed.`, tier };
|
|
603
|
+
}
|
|
604
|
+
if (requiredStatus === "DRAFT") {
|
|
605
|
+
if (tier !== "deep") {
|
|
606
|
+
return { valid: false, reason: "Executable SDD discovery requires a [DEEP] specification.", tier };
|
|
379
607
|
}
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
return { blocked: false, branch: currentBranch, recovered: false };
|
|
608
|
+
if (!/^\s*-\s*\[\s\]\s+\S+/m.test(content)) {
|
|
609
|
+
return { valid: false, reason: "DRAFT specification must contain at least one concrete unchecked acceptance criterion.", tier };
|
|
383
610
|
}
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
const reason = `Direct writes to protected branch '${currentBranch}' are prohibited and the branch is dirty (has changes).`;
|
|
387
|
-
this.log(`BLOCKED: ${reason}`);
|
|
388
|
-
return { blocked: true, branch: currentBranch, reason, dirtyState: dirty };
|
|
611
|
+
if (/(?:\$\{[^}]+\}|<[^>\n]+>|\b(?:TODO|TBD|PLACEHOLDER)\b)/i.test(content)) {
|
|
612
|
+
return { valid: false, reason: "DRAFT specification contains unresolved template placeholders.", tier };
|
|
389
613
|
}
|
|
390
|
-
const recoveredBranch = this.createScopedBranch(taskSlug);
|
|
391
|
-
this.log(`AUTO-RECOVERED '${currentBranch}' -> '${recoveredBranch}'`);
|
|
392
|
-
return {
|
|
393
|
-
blocked: false,
|
|
394
|
-
branch: recoveredBranch,
|
|
395
|
-
branchBefore: currentBranch,
|
|
396
|
-
recovered: true,
|
|
397
|
-
dirtyState: dirty
|
|
398
|
-
};
|
|
399
|
-
} catch (error) {
|
|
400
|
-
const reason = `Git command failure on branch gate check: ${error.message}`;
|
|
401
|
-
this.log(`BLOCKED: ${reason}`);
|
|
402
|
-
return { blocked: true, branch: "unknown", error: error.message, reason, recovered: false };
|
|
403
614
|
}
|
|
615
|
+
return { valid: true, tier };
|
|
616
|
+
}
|
|
617
|
+
validateDraftContent(content) {
|
|
618
|
+
return this.validateContent(content, "DRAFT");
|
|
404
619
|
}
|
|
405
|
-
};
|
|
406
|
-
|
|
407
|
-
// src/core/sdd/validator.ts
|
|
408
|
-
import fs2 from "fs/promises";
|
|
409
|
-
var SpecValidator = class {
|
|
410
620
|
/**
|
|
411
621
|
* Validates a specification file.
|
|
412
622
|
*/
|
|
413
623
|
async validate(filePath) {
|
|
414
624
|
try {
|
|
415
|
-
const content = await
|
|
416
|
-
|
|
417
|
-
if (content.includes("[DEEP]")) tier = "deep";
|
|
418
|
-
else if (content.includes("[STANDARD]")) tier = "standard";
|
|
419
|
-
else if (content.includes("[TINY]")) tier = "tiny";
|
|
420
|
-
if (tier === "unknown") {
|
|
421
|
-
return { valid: false, reason: "Specification tier [DEEP|STANDARD|TINY] not identified in title." };
|
|
422
|
-
}
|
|
423
|
-
if (!content.includes("## Metadata")) {
|
|
424
|
-
return { valid: false, reason: "Missing '## Metadata' section.", tier };
|
|
425
|
-
}
|
|
426
|
-
if (!content.includes("## Acceptance Criteria")) {
|
|
427
|
-
return { valid: false, reason: "Missing '## Acceptance Criteria' section.", tier };
|
|
428
|
-
}
|
|
429
|
-
const lines = content.split("\n");
|
|
430
|
-
const statusLine = lines.find((l) => l.includes("Status:"));
|
|
431
|
-
if (!statusLine) {
|
|
432
|
-
return { valid: false, reason: "Missing Status field in Metadata section.", tier };
|
|
433
|
-
}
|
|
434
|
-
const statusPart = statusLine.split(":")[1].replace(/[\*_]/g, "").trim().toUpperCase();
|
|
435
|
-
if (statusPart.includes("|")) {
|
|
436
|
-
return { valid: false, reason: "Specification status is a template list. Must be explicitly set to 'APPROVED'.", tier };
|
|
437
|
-
}
|
|
438
|
-
if (statusPart !== "APPROVED") {
|
|
439
|
-
return { valid: false, reason: `Specification status is '${statusPart}', but must be 'APPROVED' to proceed.`, tier };
|
|
440
|
-
}
|
|
441
|
-
return { valid: true, tier };
|
|
625
|
+
const content = await fs.readFile(filePath, "utf8");
|
|
626
|
+
return this.validateContent(content, "APPROVED");
|
|
442
627
|
} catch (error) {
|
|
443
628
|
if (error.code === "ENOENT") {
|
|
444
629
|
return { valid: false, reason: `Specification file not found: ${filePath}` };
|
|
@@ -449,9 +634,9 @@ var SpecValidator = class {
|
|
|
449
634
|
};
|
|
450
635
|
|
|
451
636
|
// src/core/handoff/handoff-engine.ts
|
|
452
|
-
import { execSync as
|
|
453
|
-
import
|
|
454
|
-
import
|
|
637
|
+
import { execSync as execSync2 } from "child_process";
|
|
638
|
+
import fs2 from "fs/promises";
|
|
639
|
+
import path from "path";
|
|
455
640
|
var HandoffEngine = class {
|
|
456
641
|
cwd;
|
|
457
642
|
constructor({ cwd }) {
|
|
@@ -464,9 +649,9 @@ var HandoffEngine = class {
|
|
|
464
649
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
465
650
|
let specsContent = "";
|
|
466
651
|
for (const specPath of specPaths) {
|
|
467
|
-
const absolutePath =
|
|
652
|
+
const absolutePath = path.isAbsolute(specPath) ? specPath : path.join(this.cwd, specPath);
|
|
468
653
|
try {
|
|
469
|
-
const content = await
|
|
654
|
+
const content = await fs2.readFile(absolutePath, "utf8");
|
|
470
655
|
specsContent += `### File: ${specPath}
|
|
471
656
|
|
|
472
657
|
${content}
|
|
@@ -482,7 +667,7 @@ ${content}
|
|
|
482
667
|
}
|
|
483
668
|
let gitDiff = "No changes detected.";
|
|
484
669
|
try {
|
|
485
|
-
gitDiff =
|
|
670
|
+
gitDiff = execSync2("git diff HEAD", { cwd: this.cwd, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim() || "No changes detected.";
|
|
486
671
|
} catch (err) {
|
|
487
672
|
gitDiff = `[Error capturing diff: ${err.message}]`;
|
|
488
673
|
}
|
|
@@ -493,9 +678,9 @@ ${content}
|
|
|
493
678
|
evidenceJson = JSON.stringify(data, null, 2);
|
|
494
679
|
evidenceSummary = `Status: ${data.status || data.internalStatus || "unknown"}, Commands: ${data.commands?.length || 0}`;
|
|
495
680
|
} else {
|
|
496
|
-
const evidencePath =
|
|
681
|
+
const evidencePath = path.join(this.cwd, "EVIDENCE.json");
|
|
497
682
|
try {
|
|
498
|
-
const content = await
|
|
683
|
+
const content = await fs2.readFile(evidencePath, "utf8");
|
|
499
684
|
const data = JSON.parse(content);
|
|
500
685
|
evidenceJson = JSON.stringify(data, null, 2);
|
|
501
686
|
evidenceSummary = `Status: ${data.status}, Commands: ${data.commands?.length || 0}`;
|
|
@@ -503,16 +688,16 @@ ${content}
|
|
|
503
688
|
}
|
|
504
689
|
}
|
|
505
690
|
const possibleTemplatePaths = [
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
691
|
+
path.join(this.cwd, ".ai-workflow/templates/HANDOFF.template.md"),
|
|
692
|
+
path.join(this.cwd, ".ai-workflow/harness/handoffs/HANDOFF.template.md"),
|
|
693
|
+
path.join(getPackageRoot(), "dist-assets/templates/HANDOFF.template.md"),
|
|
694
|
+
path.join(getPackageRoot(), "dist-assets/harness/handoffs/HANDOFF.template.md")
|
|
510
695
|
];
|
|
511
696
|
let template = null;
|
|
512
697
|
let lastError = null;
|
|
513
698
|
for (const tPath of possibleTemplatePaths) {
|
|
514
699
|
try {
|
|
515
|
-
template = await
|
|
700
|
+
template = await fs2.readFile(tPath, "utf8");
|
|
516
701
|
break;
|
|
517
702
|
} catch (err) {
|
|
518
703
|
lastError = err;
|
|
@@ -524,18 +709,18 @@ ${possibleTemplatePaths.join("\n")}
|
|
|
524
709
|
Last error: ${lastError?.message}`);
|
|
525
710
|
}
|
|
526
711
|
const packet = template.replace("${TASK_ID}", taskId || "unknown").replace("${TIMESTAMP}", timestamp).replace("${AUTHOR}", author || "AI Workflow Kit").replace("${STATUS}", status || "IN_PROGRESS").replace("${SPECS_CONTENT}", specsContent || "No specs provided.").replace("${EVIDENCE_SUMMARY}", evidenceSummary).replace("${EVIDENCE_JSON}", evidenceJson).replace("${GIT_DIFF}", gitDiff).replace("${NEXT_ACTIONS}", nextActions || "Awaiting manager assignment.");
|
|
527
|
-
const handoffDir =
|
|
528
|
-
await
|
|
712
|
+
const handoffDir = path.join(this.cwd, ".ai-workflow/handoffs");
|
|
713
|
+
await fs2.mkdir(handoffDir, { recursive: true });
|
|
529
714
|
const packetName = `HANDOFF-${taskId || "TMP"}-${Date.now()}.md`;
|
|
530
|
-
const packetPath =
|
|
531
|
-
await
|
|
715
|
+
const packetPath = path.join(handoffDir, packetName);
|
|
716
|
+
await fs2.writeFile(packetPath, packet);
|
|
532
717
|
return packetPath;
|
|
533
718
|
}
|
|
534
719
|
};
|
|
535
720
|
|
|
536
721
|
// src/core/healing/healer-engine.ts
|
|
537
|
-
import
|
|
538
|
-
import
|
|
722
|
+
import fs3 from "fs/promises";
|
|
723
|
+
import path2 from "path";
|
|
539
724
|
import crypto from "crypto";
|
|
540
725
|
|
|
541
726
|
// src/core/statuses.ts
|
|
@@ -608,7 +793,7 @@ var HealerEngine = class {
|
|
|
608
793
|
this.ledger = ledger;
|
|
609
794
|
}
|
|
610
795
|
get statePath() {
|
|
611
|
-
return
|
|
796
|
+
return path2.join(this.cwd, ".ai-workflow", `healing-state-${this.taskSlug}.json`);
|
|
612
797
|
}
|
|
613
798
|
fingerprint(result) {
|
|
614
799
|
return crypto.createHash("sha256").update(stableJson(collectBlockingFindings(result))).digest("hex");
|
|
@@ -628,18 +813,18 @@ var HealerEngine = class {
|
|
|
628
813
|
return false;
|
|
629
814
|
}
|
|
630
815
|
async writeState(state) {
|
|
631
|
-
await
|
|
632
|
-
await
|
|
816
|
+
await fs3.mkdir(path2.dirname(this.statePath), { recursive: true });
|
|
817
|
+
await fs3.writeFile(this.statePath, JSON.stringify(state, null, 2));
|
|
633
818
|
}
|
|
634
819
|
async resetState() {
|
|
635
820
|
try {
|
|
636
|
-
await
|
|
821
|
+
await fs3.unlink(this.statePath);
|
|
637
822
|
} catch {
|
|
638
823
|
}
|
|
639
824
|
}
|
|
640
825
|
async writeRemediationRequest({ attempt, result }) {
|
|
641
|
-
const requestPath =
|
|
642
|
-
await
|
|
826
|
+
const requestPath = path2.join(this.cwd, ".ai-workflow", "remediation-request.json");
|
|
827
|
+
await fs3.mkdir(path2.dirname(requestPath), { recursive: true });
|
|
643
828
|
const request = {
|
|
644
829
|
taskSlug: this.taskSlug,
|
|
645
830
|
executionMode: this.mode,
|
|
@@ -650,7 +835,7 @@ var HealerEngine = class {
|
|
|
650
835
|
findingsFingerprint: this.fingerprint(result),
|
|
651
836
|
instruction: "Apply the smallest safe correction for the listed gates, then rerun validation. Do not broaden scope."
|
|
652
837
|
};
|
|
653
|
-
await
|
|
838
|
+
await fs3.writeFile(requestPath, JSON.stringify(request, null, 2));
|
|
654
839
|
return requestPath;
|
|
655
840
|
}
|
|
656
841
|
async run({
|
|
@@ -756,562 +941,127 @@ var HealerEngine = class {
|
|
|
756
941
|
}
|
|
757
942
|
};
|
|
758
943
|
|
|
759
|
-
// src/core/
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
objective: "Deliver a user-facing product or marketing surface with truthful copy and deliberate visual composition.",
|
|
765
|
-
requiredChecks: ["tests", "typecheck", "build", "responsive-render", "primary-interaction", "truthfulness"],
|
|
766
|
-
forbiddenAssumptions: ["fixed SaaS section sequence", "mandatory pricing", "mandatory testimonials", "subjective premium score"]
|
|
767
|
-
}),
|
|
768
|
-
"frontend-utility": Object.freeze({
|
|
769
|
-
owner: "Astra",
|
|
770
|
-
skills: ["frontend-development", "ui-ux-design", "frontend-design-system"],
|
|
771
|
-
objective: "Deliver a focused user tool with a complete primary flow and clear operational states.",
|
|
772
|
-
requiredChecks: ["tests", "typecheck", "build", "responsive-render", "primary-interaction"],
|
|
773
|
-
forbiddenAssumptions: ["marketing sections", "pricing", "testimonials", "commercial narrative"]
|
|
774
|
-
}),
|
|
775
|
-
"backend-api": Object.freeze({
|
|
776
|
-
owner: "Astra",
|
|
777
|
-
skills: ["backend-development"],
|
|
778
|
-
objective: "Deliver a bounded backend/API change with validated contracts, errors, authorization, and tests.",
|
|
779
|
-
requiredChecks: ["tests", "build-or-typecheck", "input-validation", "failure-paths"],
|
|
780
|
-
forbiddenAssumptions: ["frontend deliverables", "visual evidence"]
|
|
781
|
-
}),
|
|
782
|
-
refactor: Object.freeze({
|
|
783
|
-
owner: "Astra",
|
|
784
|
-
skills: ["refactoring"],
|
|
785
|
-
objective: "Improve structure without changing observable behavior outside the approved scope.",
|
|
786
|
-
requiredChecks: ["behavior-preservation", "tests", "focused-diff"],
|
|
787
|
-
forbiddenAssumptions: ["new product behavior", "unrelated cleanup"]
|
|
788
|
-
}),
|
|
789
|
-
documentation: Object.freeze({
|
|
790
|
-
owner: "Astra",
|
|
791
|
-
skills: ["documentation"],
|
|
792
|
-
objective: "Create or update accurate documentation grounded in the current repository behavior.",
|
|
793
|
-
requiredChecks: ["references", "paths", "commands", "consistency"],
|
|
794
|
-
forbiddenAssumptions: ["implementation claims without evidence"]
|
|
795
|
-
}),
|
|
796
|
-
"security-review": Object.freeze({
|
|
797
|
-
owner: "Sage",
|
|
798
|
-
skills: ["qa-workflow", "technical-leadership"],
|
|
799
|
-
objective: "Inspect security-relevant behavior and report evidence-ranked findings without implementing unrelated changes.",
|
|
800
|
-
requiredChecks: ["evidence", "severity", "runtime-vs-dev", "remediation-owner"],
|
|
801
|
-
forbiddenAssumptions: ["automatic force fixes", "unverified exploitability"]
|
|
802
|
-
}),
|
|
803
|
-
generic: Object.freeze({
|
|
804
|
-
owner: "Atlas",
|
|
805
|
-
skills: [],
|
|
806
|
-
objective: "Route work without inventing domain requirements.",
|
|
807
|
-
requiredChecks: ["scope", "owner", "mode"],
|
|
808
|
-
forbiddenAssumptions: ["domain-specific structure"]
|
|
809
|
-
})
|
|
810
|
-
});
|
|
811
|
-
var PROFILE_PATTERNS = Object.freeze([
|
|
812
|
-
["security-review", /\b(?:security review|security audit|vulnerability audit|threat model)\b/i],
|
|
813
|
-
["refactor", /\b(?:refactor|restructure|cleanup without behavior change|preserve behavior)\b/i],
|
|
814
|
-
["documentation", /\b(?:documentation only|write docs|update readme|document the|documentation task)\b/i],
|
|
815
|
-
["backend-api", /\b(?:api endpoint|backend api|rest api|graphql|controller|service endpoint|authenticated api)\b/i],
|
|
816
|
-
["frontend-product", /\b(?:landing page|marketing page|product page|pricing page|homepage|redesign|brand page)\b/i],
|
|
817
|
-
["frontend-utility", /\b(?:validator|search page|lookup|dashboard|form|calculator|tool|admin page|repository search)\b/i]
|
|
818
|
-
]);
|
|
819
|
-
function getWorkflowProfile(name = "generic") {
|
|
820
|
-
return PROFILE_DEFINITIONS[name] || PROFILE_DEFINITIONS.generic;
|
|
821
|
-
}
|
|
822
|
-
function resolveWorkflowProfile({ request = "", explicitProfile = null } = {}) {
|
|
823
|
-
if (explicitProfile && PROFILE_DEFINITIONS[explicitProfile]) return explicitProfile;
|
|
824
|
-
const text = String(request).trim();
|
|
825
|
-
for (const [profile, pattern] of PROFILE_PATTERNS) {
|
|
826
|
-
if (pattern.test(text)) return profile;
|
|
827
|
-
}
|
|
828
|
-
return "generic";
|
|
829
|
-
}
|
|
830
|
-
|
|
831
|
-
// src/core/request-router.ts
|
|
832
|
-
import path4 from "path";
|
|
833
|
-
import fs5 from "fs";
|
|
834
|
-
var RequestRouter = class {
|
|
944
|
+
// src/core/evidence/evidence-ledger.ts
|
|
945
|
+
import fs4 from "fs/promises";
|
|
946
|
+
import path3 from "path";
|
|
947
|
+
import { createHash } from "crypto";
|
|
948
|
+
var EvidenceLedger = class {
|
|
835
949
|
cwd;
|
|
836
|
-
|
|
950
|
+
workflowId;
|
|
951
|
+
events;
|
|
952
|
+
loadedPath;
|
|
953
|
+
loadedContentHash;
|
|
954
|
+
constructor({ cwd = process.cwd(), workflowId = "unknown" } = {}) {
|
|
837
955
|
this.cwd = cwd;
|
|
956
|
+
this.workflowId = workflowId;
|
|
957
|
+
this.events = [];
|
|
958
|
+
this.loadedPath = null;
|
|
959
|
+
this.loadedContentHash = null;
|
|
838
960
|
}
|
|
839
961
|
/**
|
|
840
|
-
*
|
|
962
|
+
* Logs a workflow event to the ledger.
|
|
841
963
|
*/
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
964
|
+
logEvent({
|
|
965
|
+
actor,
|
|
966
|
+
eventType,
|
|
967
|
+
provenance,
|
|
968
|
+
data = {},
|
|
969
|
+
actorType,
|
|
970
|
+
observed,
|
|
971
|
+
runtime
|
|
972
|
+
}) {
|
|
973
|
+
const uniqueTerminalEvents = /* @__PURE__ */ new Set(["specification_complete", "planning_complete", "implementation_complete", "finalization_completed"]);
|
|
974
|
+
if (uniqueTerminalEvents.has(eventType) && this.events.some((event2) => event2.eventType === eventType)) {
|
|
975
|
+
throw new Error(`Conflicting ledger record: terminal event '${eventType}' already exists for workflow '${this.workflowId}'.`);
|
|
846
976
|
}
|
|
847
|
-
const
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
requestedCapability,
|
|
858
|
-
operationType,
|
|
859
|
-
mutationIntent,
|
|
860
|
-
riskLevel,
|
|
861
|
-
requiredEvidence,
|
|
862
|
-
safetyConstraints,
|
|
863
|
-
specPolicy: riskAssessment.specPolicy,
|
|
864
|
-
matchedSignals: riskAssessment.matchedSignals,
|
|
865
|
-
riskDrivers: riskAssessment.riskDrivers
|
|
977
|
+
const event = {
|
|
978
|
+
workflowId: this.workflowId,
|
|
979
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
980
|
+
actor,
|
|
981
|
+
actorType: actorType !== void 0 ? actorType : void 0,
|
|
982
|
+
observed: observed !== void 0 ? observed : void 0,
|
|
983
|
+
runtime: runtime !== void 0 ? runtime : void 0,
|
|
984
|
+
eventType,
|
|
985
|
+
provenance,
|
|
986
|
+
data
|
|
866
987
|
};
|
|
988
|
+
this.events.push(event);
|
|
989
|
+
console.log(`[LEDGER] [${actor}] ${eventType}: ${JSON.stringify(data)}`);
|
|
990
|
+
return event;
|
|
867
991
|
}
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
const match = text.match(mapping.pattern);
|
|
881
|
-
if (match) {
|
|
882
|
-
operationType = mapping.op;
|
|
883
|
-
requestedCapability = match[1];
|
|
884
|
-
break;
|
|
992
|
+
getEvents() {
|
|
993
|
+
return this.events;
|
|
994
|
+
}
|
|
995
|
+
/**
|
|
996
|
+
* Saves the ledger events to a JSON file.
|
|
997
|
+
*/
|
|
998
|
+
async save(filePath) {
|
|
999
|
+
const targetPath = path3.isAbsolute(filePath) ? filePath : path3.join(this.cwd, filePath);
|
|
1000
|
+
await fs4.mkdir(path3.dirname(targetPath), { recursive: true });
|
|
1001
|
+
for (const event of this.events) {
|
|
1002
|
+
if (event.workflowId !== this.workflowId) {
|
|
1003
|
+
throw new Error(`Ledger workflowId conflict: '${event.workflowId}' does not match '${this.workflowId}'.`);
|
|
885
1004
|
}
|
|
886
1005
|
}
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
} else if (
|
|
896
|
-
|
|
897
|
-
}
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
1006
|
+
const serialized = JSON.stringify(this.events, null, 2);
|
|
1007
|
+
const contentHash = createHash("sha256").update(serialized).digest("hex");
|
|
1008
|
+
const targetExists = await fs4.access(targetPath).then(() => true).catch(() => false);
|
|
1009
|
+
if (targetExists) {
|
|
1010
|
+
const diskHash = createHash("sha256").update(await fs4.readFile(targetPath)).digest("hex");
|
|
1011
|
+
if (this.loadedPath !== targetPath || this.loadedContentHash === null || diskHash !== this.loadedContentHash) {
|
|
1012
|
+
throw new Error(`Ledger changed on disk or was not loaded before update: '${targetPath}'.`);
|
|
1013
|
+
}
|
|
1014
|
+
} else if (this.loadedPath === targetPath && this.loadedContentHash !== null) {
|
|
1015
|
+
throw new Error(`Ledger was removed after it was loaded: '${targetPath}'.`);
|
|
1016
|
+
}
|
|
1017
|
+
const temporaryPath = `${targetPath}.${process.pid}.${Date.now()}.tmp`;
|
|
1018
|
+
try {
|
|
1019
|
+
await fs4.writeFile(temporaryPath, serialized, { encoding: "utf8", flag: "wx" });
|
|
1020
|
+
await fs4.rename(temporaryPath, targetPath);
|
|
1021
|
+
this.loadedPath = targetPath;
|
|
1022
|
+
this.loadedContentHash = contentHash;
|
|
1023
|
+
} catch (error) {
|
|
1024
|
+
await fs4.rm(temporaryPath, { force: true }).catch(() => {
|
|
1025
|
+
});
|
|
1026
|
+
throw error;
|
|
1027
|
+
}
|
|
904
1028
|
}
|
|
905
|
-
|
|
906
|
-
const
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
1029
|
+
async load(filePath) {
|
|
1030
|
+
const targetPath = path3.isAbsolute(filePath) ? filePath : path3.join(this.cwd, filePath);
|
|
1031
|
+
try {
|
|
1032
|
+
const content = await fs4.readFile(targetPath, "utf8");
|
|
1033
|
+
const parsed = JSON.parse(content);
|
|
1034
|
+
if (!Array.isArray(parsed) || parsed.some((event) => event.workflowId !== this.workflowId)) {
|
|
1035
|
+
throw new Error(`Ledger is incompatible with workflow '${this.workflowId}'.`);
|
|
911
1036
|
}
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
release: add("task:release", /\b(release|deploy|produ[cç][aã]o|publish|publicar|lan[cç]amento)\b/i)
|
|
922
|
-
};
|
|
923
|
-
const domainRiskSignals = {
|
|
924
|
-
adult: add("domain:adult", /\b(er[óo]tic[oa]s?|adult[oa]s?|sexual|sexo|porn(?:o|ografia)?|18\+)\b/i),
|
|
925
|
-
auth: add("domain:auth", /\b(login|auth|autentica[cç][aã]o|autoriza[cç][aã]o|permiss(?:ão|oes|ões)|admin|administrador|roles?|rbac)\b/i),
|
|
926
|
-
payment: add("domain:payment", /\b(pagamento|checkout|billing|cobran[cç]a|fatura[cç][aã]o|assinatura|cart[aã]o|stripe|paypal)\b/i),
|
|
927
|
-
securityGovernance: add("domain:security-governance", /\b(SECURITY\.md|seguran[cç]a|security policy|pol[ií]tica de seguran[cç]a|governance|governan[cç]a|compliance|branch safety)\b/i)
|
|
928
|
-
};
|
|
929
|
-
const impactSignals = {
|
|
930
|
-
personalData: add("impact:personal-data", /\b(leads?|dados pessoais|personal data|crm|contatos?|contactos?|clientes?|exporta[cç][aã]o|exportar|armazenamento|armazenar|integra[cç][aã]o|integrar)\b/i),
|
|
931
|
-
productionRelease: mutationIntent === "publish" || add("impact:production-release", /\b(produ[cç][aã]o|release|deploy|publish|publicar|npm)\b/i),
|
|
932
|
-
adminPermissions: add("impact:admin-permissions", /\b(admin|administrador|permiss(?:ão|oes|ões)|roles?|rbac)\b/i)
|
|
933
|
-
};
|
|
934
|
-
if (mutationIntent === "publish" && !matchedSignals.includes("impact:production-release")) {
|
|
935
|
-
matchedSignals.push("impact:production-release");
|
|
936
|
-
}
|
|
937
|
-
const scopeSignals = {
|
|
938
|
-
localized: add("scope:localized", /\b(localizado|uma frase|typo|pequeno|small|tiny|simples|README)\b/i),
|
|
939
|
-
broad: add("scope:broad", /\b(plataforma|sistema|completo|premium|end-to-end|amplo|v[aá]rias|m[úu]ltiplas|empresa)\b/i),
|
|
940
|
-
architectural: add("scope:architectural", /\b(deep|architectural|arquitetural|migration|migra[cç][aã]o|major|full|\[deep\])\b/i),
|
|
941
|
-
explicitHigh: add("scope:explicit-high", /\b(high-risk|high risk|alto risco)\b/i),
|
|
942
|
-
explicitRequiredSpec: add("scope:explicit-required-spec", /\b(formal spec|required spec|especifica[cç][aã]o formal)\b/i),
|
|
943
|
-
explicitRequiredEvidence: add("scope:explicit-required-evidence", /\b(required evidence|evidence required|evid[eê]ncia obrigat[óo]ria)\b/i)
|
|
944
|
-
};
|
|
945
|
-
let riskLevel = "medium";
|
|
946
|
-
const riskDrivers = [];
|
|
947
|
-
const highDrivers = [
|
|
948
|
-
["scope:explicit-high", scopeSignals.explicitHigh],
|
|
949
|
-
["scope:architectural", scopeSignals.architectural],
|
|
950
|
-
["domain:adult", domainRiskSignals.adult],
|
|
951
|
-
["domain:auth", domainRiskSignals.auth],
|
|
952
|
-
["domain:payment", domainRiskSignals.payment],
|
|
953
|
-
["domain:security-governance", domainRiskSignals.securityGovernance],
|
|
954
|
-
["impact:production-release", impactSignals.productionRelease],
|
|
955
|
-
["impact:admin-permissions", impactSignals.adminPermissions],
|
|
956
|
-
["impact:personal-data", taskTypeSignals.dashboard && impactSignals.personalData]
|
|
957
|
-
];
|
|
958
|
-
for (const [signal, active] of highDrivers) {
|
|
959
|
-
if (active) {
|
|
960
|
-
riskDrivers.push(signal);
|
|
1037
|
+
this.events = parsed;
|
|
1038
|
+
this.loadedPath = targetPath;
|
|
1039
|
+
this.loadedContentHash = createHash("sha256").update(content).digest("hex");
|
|
1040
|
+
return true;
|
|
1041
|
+
} catch (error) {
|
|
1042
|
+
if (error.code === "ENOENT") {
|
|
1043
|
+
this.loadedPath = targetPath;
|
|
1044
|
+
this.loadedContentHash = null;
|
|
1045
|
+
return false;
|
|
961
1046
|
}
|
|
1047
|
+
throw error;
|
|
962
1048
|
}
|
|
963
|
-
if (riskDrivers.length > 0) {
|
|
964
|
-
riskLevel = "high";
|
|
965
|
-
} else if (mutationIntent === "readonly") {
|
|
966
|
-
riskLevel = "low";
|
|
967
|
-
riskDrivers.push("intent:readonly");
|
|
968
|
-
} else if (taskTypeSignals.docsCopy && scopeSignals.localized) {
|
|
969
|
-
riskLevel = "low";
|
|
970
|
-
riskDrivers.push("task:docs-copy");
|
|
971
|
-
} else if (taskTypeSignals.refactor || taskTypeSignals.bugfix || taskTypeSignals.landingPage || taskTypeSignals.dashboard) {
|
|
972
|
-
riskLevel = "medium";
|
|
973
|
-
if (taskTypeSignals.refactor) riskDrivers.push("task:refactor");
|
|
974
|
-
if (taskTypeSignals.bugfix) riskDrivers.push("task:bugfix");
|
|
975
|
-
if (taskTypeSignals.landingPage) riskDrivers.push("task:landing-page");
|
|
976
|
-
if (taskTypeSignals.dashboard) riskDrivers.push("task:dashboard");
|
|
977
|
-
}
|
|
978
|
-
const specPolicy = riskLevel === "high" || scopeSignals.explicitRequiredSpec || taskTypeSignals.landingPage && scopeSignals.broad ? "required" : "none";
|
|
979
|
-
return {
|
|
980
|
-
riskLevel,
|
|
981
|
-
specPolicy,
|
|
982
|
-
matchedSignals: [...new Set(matchedSignals)],
|
|
983
|
-
riskDrivers: [...new Set(riskDrivers)]
|
|
984
|
-
};
|
|
985
1049
|
}
|
|
986
|
-
|
|
987
|
-
const
|
|
988
|
-
const
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
} else if (mutationIntent === "write") {
|
|
993
|
-
safetyConstraints.push("Require branch gate", "No direct commits to main");
|
|
994
|
-
requiredEvidence.push("tests", "commit-hash");
|
|
995
|
-
} else {
|
|
996
|
-
safetyConstraints.push("No workspace mutations allowed");
|
|
1050
|
+
async verifyRoleConfinement() {
|
|
1051
|
+
const violations = [];
|
|
1052
|
+
const events = this.events;
|
|
1053
|
+
const atlasImplements = events.some((e) => e.actor === "Atlas" && (e.eventType === "implementation_start" || e.eventType === "implementation_complete"));
|
|
1054
|
+
if (atlasImplements) {
|
|
1055
|
+
violations.push("Atlas cannot implement directly");
|
|
997
1056
|
}
|
|
998
|
-
|
|
999
|
-
|
|
1057
|
+
const nexusMutates = events.some((e) => e.actor === "Nexus" && e.eventType === "file_mutate");
|
|
1058
|
+
if (nexusMutates) {
|
|
1059
|
+
violations.push("Nexus cannot mutate files during discovery");
|
|
1000
1060
|
}
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
safetyConstraints.push("Require formal specification");
|
|
1006
|
-
}
|
|
1007
|
-
return { safetyConstraints, requiredEvidence };
|
|
1008
|
-
}
|
|
1009
|
-
/**
|
|
1010
|
-
* Decides the routing decision.
|
|
1011
|
-
*/
|
|
1012
|
-
route(understanding) {
|
|
1013
|
-
const { operationType, mutationIntent, rawRequest } = understanding;
|
|
1014
|
-
const requestedActor = void 0;
|
|
1015
|
-
if (/\b(bypass safety|bypass validation|force push|directly to main|push to remote without gate)\b/i.test(rawRequest)) {
|
|
1016
|
-
return {
|
|
1017
|
-
requestedActor,
|
|
1018
|
-
selectedActor: "Atlas",
|
|
1019
|
-
operationType,
|
|
1020
|
-
mutationIntent,
|
|
1021
|
-
permissionDecision: "blocked",
|
|
1022
|
-
reason: "Security block: Attempt to bypass safety or push directly is forbidden.",
|
|
1023
|
-
workflowPath: ["Atlas"]
|
|
1024
|
-
};
|
|
1025
|
-
}
|
|
1026
|
-
let { selectedActor, permissionDecision, reason } = this.evaluateActorPermission(operationType, mutationIntent);
|
|
1027
|
-
let workflowPath = ["Atlas"];
|
|
1028
|
-
if (selectedActor === "Phoenix") {
|
|
1029
|
-
const requestPath = path4.join(this.cwd, ".ai-workflow/remediation-request.json");
|
|
1030
|
-
let hasFindings = false;
|
|
1031
|
-
if (fs5.existsSync(requestPath)) {
|
|
1032
|
-
try {
|
|
1033
|
-
const reqData = JSON.parse(fs5.readFileSync(requestPath, "utf8"));
|
|
1034
|
-
if (reqData.affectedChecks && reqData.affectedChecks.length > 0) {
|
|
1035
|
-
hasFindings = true;
|
|
1036
|
-
}
|
|
1037
|
-
} catch {
|
|
1038
|
-
}
|
|
1039
|
-
}
|
|
1040
|
-
if (!hasFindings) {
|
|
1041
|
-
permissionDecision = "blocked";
|
|
1042
|
-
reason = "Blocked: Phoenix acts only with concrete findings and bounded remediation.";
|
|
1043
|
-
}
|
|
1044
|
-
}
|
|
1045
|
-
if (understanding.specPolicy === "required") {
|
|
1046
|
-
workflowPath.push("Nexus", "Orion");
|
|
1047
|
-
}
|
|
1048
|
-
if (selectedActor && !workflowPath.includes(selectedActor)) {
|
|
1049
|
-
workflowPath.push(selectedActor);
|
|
1050
|
-
}
|
|
1051
|
-
if (selectedActor === "Atlas" && ["discover", "validate", "fix", "release", "deploy"].includes(operationType)) {
|
|
1052
|
-
permissionDecision = "blocked";
|
|
1053
|
-
reason = "Atlas self-execution guard: Atlas cannot silently execute specialized actor or capability requests.";
|
|
1054
|
-
}
|
|
1055
|
-
if (selectedActor === "Astra" && understanding.riskLevel === "high") {
|
|
1056
|
-
workflowPath.push("Sage", "Phoenix", "Sage");
|
|
1057
|
-
}
|
|
1058
|
-
if (understanding.specPolicy === "none") {
|
|
1059
|
-
workflowPath = workflowPath.filter((actor) => {
|
|
1060
|
-
if (actor === "Nexus" && selectedActor !== "Nexus") return false;
|
|
1061
|
-
if (actor === "Orion" && selectedActor !== "Orion") return false;
|
|
1062
|
-
return true;
|
|
1063
|
-
});
|
|
1064
|
-
}
|
|
1065
|
-
return {
|
|
1066
|
-
requestedActor,
|
|
1067
|
-
selectedActor,
|
|
1068
|
-
operationType,
|
|
1069
|
-
mutationIntent,
|
|
1070
|
-
permissionDecision,
|
|
1071
|
-
reason,
|
|
1072
|
-
workflowPath
|
|
1073
|
-
};
|
|
1074
|
-
}
|
|
1075
|
-
evaluateActorPermission(operationType, mutationIntent) {
|
|
1076
|
-
let selectedActor;
|
|
1077
|
-
let permissionDecision = "allowed";
|
|
1078
|
-
let reason = "Routed successfully.";
|
|
1079
|
-
if (operationType === "discover") {
|
|
1080
|
-
selectedActor = "Nexus";
|
|
1081
|
-
} else if (operationType === "validate") {
|
|
1082
|
-
selectedActor = "Sage";
|
|
1083
|
-
} else if (operationType === "fix") {
|
|
1084
|
-
selectedActor = "Phoenix";
|
|
1085
|
-
} else if (operationType === "implement") {
|
|
1086
|
-
selectedActor = "Astra";
|
|
1087
|
-
} else if (operationType === "release" || operationType === "deploy") {
|
|
1088
|
-
selectedActor = "Orion";
|
|
1089
|
-
} else {
|
|
1090
|
-
selectedActor = mutationIntent === "write" ? "Astra" : "Atlas";
|
|
1091
|
-
}
|
|
1092
|
-
return { selectedActor, permissionDecision, reason };
|
|
1093
|
-
}
|
|
1094
|
-
};
|
|
1095
|
-
|
|
1096
|
-
// src/core/request-classifier.ts
|
|
1097
|
-
var RequestClassifier = class {
|
|
1098
|
-
/**
|
|
1099
|
-
* Classifies a natural request.
|
|
1100
|
-
*/
|
|
1101
|
-
classify(request = "", options = {}) {
|
|
1102
|
-
const text = String(request).trim();
|
|
1103
|
-
if (!text) {
|
|
1104
|
-
throw new Error("Cannot classify an empty request.");
|
|
1105
|
-
}
|
|
1106
|
-
const cwd = options.cwd || process.cwd();
|
|
1107
|
-
const profile = resolveWorkflowProfile({ request: text });
|
|
1108
|
-
const profileDef = getWorkflowProfile(profile);
|
|
1109
|
-
const readOnlyPatterns = /\b(analyze|check|inspect|review|find|search|show|list|read|view|verify)\b/i;
|
|
1110
|
-
const isReadOnly = readOnlyPatterns.test(text);
|
|
1111
|
-
const intent = isReadOnly ? "read-only" : "write";
|
|
1112
|
-
const router = new RequestRouter({ cwd });
|
|
1113
|
-
const requestUnderstanding = router.understand(text);
|
|
1114
|
-
const routingDecision = router.route(requestUnderstanding);
|
|
1115
|
-
const risk = requestUnderstanding.riskLevel;
|
|
1116
|
-
const explicitRequiredEvidence = /\b(required evidence|evidence required)\b/i.test(text);
|
|
1117
|
-
const explicitRequiredSpec = /\b(formal spec|required spec)\b/i.test(text);
|
|
1118
|
-
const specPolicy = requestUnderstanding.specPolicy === "required" || explicitRequiredSpec ? "required" : "none";
|
|
1119
|
-
const evidencePolicy = risk === "high" || requestUnderstanding.mutationIntent === "publish" || explicitRequiredEvidence ? "required" : "optional";
|
|
1120
|
-
const remediationLimit = risk === "high" ? 3 : risk === "low" ? 1 : 2;
|
|
1121
|
-
const specNeeded = specPolicy === "required";
|
|
1122
|
-
const validationNeeded = intent === "write";
|
|
1123
|
-
let legacyMode = "standard";
|
|
1124
|
-
if (/\b(deep|architectural|migration|major|full|\[deep\])\b/i.test(text)) {
|
|
1125
|
-
legacyMode = "full";
|
|
1126
|
-
} else if (/\b(tiny|small|quick|simple|only\s+update|typo|comment|\[tiny\])\b/i.test(text)) {
|
|
1127
|
-
const isPureDocsOrTypo = profile === "documentation" || /\b(typo|comment|readme|docs|documentation)\b/i.test(text);
|
|
1128
|
-
if (intent === "write" && !isPureDocsOrTypo) {
|
|
1129
|
-
legacyMode = "standard";
|
|
1130
|
-
} else {
|
|
1131
|
-
legacyMode = "quick";
|
|
1132
|
-
}
|
|
1133
|
-
}
|
|
1134
|
-
return {
|
|
1135
|
-
request: text,
|
|
1136
|
-
profile,
|
|
1137
|
-
owner: routingDecision.selectedActor || profileDef.owner,
|
|
1138
|
-
intent: requestUnderstanding.mutationIntent === "readonly" ? "read-only" : "write",
|
|
1139
|
-
mode: legacyMode,
|
|
1140
|
-
risk,
|
|
1141
|
-
specPolicy,
|
|
1142
|
-
evidencePolicy,
|
|
1143
|
-
remediationLimit,
|
|
1144
|
-
riskDrivers: requestUnderstanding.riskDrivers || [],
|
|
1145
|
-
matchedSignals: requestUnderstanding.matchedSignals || [],
|
|
1146
|
-
specNeeded,
|
|
1147
|
-
validationNeeded,
|
|
1148
|
-
skills: [...profileDef.skills],
|
|
1149
|
-
requestUnderstanding,
|
|
1150
|
-
routingDecision
|
|
1151
|
-
};
|
|
1152
|
-
}
|
|
1153
|
-
};
|
|
1154
|
-
|
|
1155
|
-
// src/core/execution-planner.ts
|
|
1156
|
-
import path5 from "path";
|
|
1157
|
-
var ExecutionPlanner = class {
|
|
1158
|
-
cwd;
|
|
1159
|
-
constructor({ cwd = process.cwd() } = {}) {
|
|
1160
|
-
this.cwd = cwd;
|
|
1161
|
-
}
|
|
1162
|
-
/**
|
|
1163
|
-
* Generates an execution plan.
|
|
1164
|
-
*/
|
|
1165
|
-
plan(classification, taskSlug = "task") {
|
|
1166
|
-
const remediationLimit = classification.remediationLimit;
|
|
1167
|
-
const branchNeeded = classification.intent === "write";
|
|
1168
|
-
const specPath = classification.specNeeded ? path5.join("docs/workflows", taskSlug, "spec.md") : null;
|
|
1169
|
-
const owner = classification.routingDecision?.selectedActor || (classification.intent === "write" ? "Astra" : classification.owner);
|
|
1170
|
-
const validationsExpected = [];
|
|
1171
|
-
if (classification.validationNeeded) {
|
|
1172
|
-
validationsExpected.push("test");
|
|
1173
|
-
if (classification.profile.startsWith("frontend")) {
|
|
1174
|
-
validationsExpected.push("lint", "build");
|
|
1175
|
-
} else if (classification.profile === "backend-api") {
|
|
1176
|
-
validationsExpected.push("lint");
|
|
1177
|
-
}
|
|
1178
|
-
}
|
|
1179
|
-
const restrictions = [
|
|
1180
|
-
"Never commit or push directly to main/master.",
|
|
1181
|
-
"Always execute behavior tests for new or modified behavior."
|
|
1182
|
-
];
|
|
1183
|
-
if (classification.risk === "high") {
|
|
1184
|
-
restrictions.push("Require independent validation review.");
|
|
1185
|
-
}
|
|
1186
|
-
return {
|
|
1187
|
-
objective: classification.request,
|
|
1188
|
-
scope: classification.intent === "read-only" ? "Analysis and verification" : "Implementation of requested behavior",
|
|
1189
|
-
restrictions,
|
|
1190
|
-
owner,
|
|
1191
|
-
skills: [...classification.skills],
|
|
1192
|
-
branchNeeded,
|
|
1193
|
-
branchName: branchNeeded ? `feat/${taskSlug}` : null,
|
|
1194
|
-
validationsExpected,
|
|
1195
|
-
remediationLimit,
|
|
1196
|
-
specPath,
|
|
1197
|
-
specPolicy: classification.specPolicy,
|
|
1198
|
-
evidencePolicy: classification.evidencePolicy,
|
|
1199
|
-
riskLevel: classification.risk,
|
|
1200
|
-
mode: classification.mode,
|
|
1201
|
-
profile: classification.profile
|
|
1202
|
-
};
|
|
1203
|
-
}
|
|
1204
|
-
};
|
|
1205
|
-
|
|
1206
|
-
// src/core/workflow-state-machine.ts
|
|
1207
|
-
var WorkflowStateMachine = class {
|
|
1208
|
-
state;
|
|
1209
|
-
history;
|
|
1210
|
-
validTransitions;
|
|
1211
|
-
constructor() {
|
|
1212
|
-
this.state = "RECEIVED";
|
|
1213
|
-
this.history = [{ state: this.state, timestamp: (/* @__PURE__ */ new Date()).toISOString() }];
|
|
1214
|
-
this.validTransitions = {
|
|
1215
|
-
RECEIVED: ["CLASSIFIED", "BLOCKED"],
|
|
1216
|
-
CLASSIFIED: ["PLANNED", "BLOCKED"],
|
|
1217
|
-
PLANNED: ["BRANCH_READY", "BLOCKED"],
|
|
1218
|
-
BRANCH_READY: ["DELEGATED", "BLOCKED"],
|
|
1219
|
-
DELEGATED: ["IMPLEMENTING", "BLOCKED"],
|
|
1220
|
-
IMPLEMENTING: ["IMPLEMENTED", "BLOCKED"],
|
|
1221
|
-
IMPLEMENTED: ["VALIDATING", "BLOCKED"],
|
|
1222
|
-
VALIDATING: ["COMPLETED", "COMPLETED_WITH_NOTES", "REMEDIATING", "BLOCKED"],
|
|
1223
|
-
REMEDIATING: ["REVALIDATING", "BLOCKED"],
|
|
1224
|
-
REVALIDATING: ["COMPLETED", "COMPLETED_WITH_NOTES", "REMEDIATING", "BLOCKED"],
|
|
1225
|
-
COMPLETED: [],
|
|
1226
|
-
COMPLETED_WITH_NOTES: [],
|
|
1227
|
-
BLOCKED: []
|
|
1228
|
-
};
|
|
1229
|
-
}
|
|
1230
|
-
/**
|
|
1231
|
-
* Transitions the machine to a new state.
|
|
1232
|
-
* @param newState - The state to transition to.
|
|
1233
|
-
*/
|
|
1234
|
-
transitionTo(newState) {
|
|
1235
|
-
const allowed = this.validTransitions[this.state];
|
|
1236
|
-
if (!allowed || !allowed.includes(newState)) {
|
|
1237
|
-
throw new Error(`Invalid state transition: ${this.state} -> ${newState}`);
|
|
1238
|
-
}
|
|
1239
|
-
this.state = newState;
|
|
1240
|
-
this.history.push({ state: this.state, timestamp: (/* @__PURE__ */ new Date()).toISOString() });
|
|
1241
|
-
}
|
|
1242
|
-
getCurrentState() {
|
|
1243
|
-
return this.state;
|
|
1244
|
-
}
|
|
1245
|
-
getHistory() {
|
|
1246
|
-
return [...this.history];
|
|
1247
|
-
}
|
|
1248
|
-
};
|
|
1249
|
-
|
|
1250
|
-
// src/core/evidence/evidence-ledger.ts
|
|
1251
|
-
import fs6 from "fs/promises";
|
|
1252
|
-
import path6 from "path";
|
|
1253
|
-
var EvidenceLedger = class {
|
|
1254
|
-
cwd;
|
|
1255
|
-
workflowId;
|
|
1256
|
-
events;
|
|
1257
|
-
constructor({ cwd = process.cwd(), workflowId = "unknown" } = {}) {
|
|
1258
|
-
this.cwd = cwd;
|
|
1259
|
-
this.workflowId = workflowId;
|
|
1260
|
-
this.events = [];
|
|
1261
|
-
}
|
|
1262
|
-
/**
|
|
1263
|
-
* Logs a workflow event to the ledger.
|
|
1264
|
-
*/
|
|
1265
|
-
logEvent({
|
|
1266
|
-
actor,
|
|
1267
|
-
eventType,
|
|
1268
|
-
provenance,
|
|
1269
|
-
data = {},
|
|
1270
|
-
actorType,
|
|
1271
|
-
observed,
|
|
1272
|
-
runtime
|
|
1273
|
-
}) {
|
|
1274
|
-
const event = {
|
|
1275
|
-
workflowId: this.workflowId,
|
|
1276
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1277
|
-
actor,
|
|
1278
|
-
actorType: actorType !== void 0 ? actorType : void 0,
|
|
1279
|
-
observed: observed !== void 0 ? observed : void 0,
|
|
1280
|
-
runtime: runtime !== void 0 ? runtime : void 0,
|
|
1281
|
-
eventType,
|
|
1282
|
-
provenance,
|
|
1283
|
-
data
|
|
1284
|
-
};
|
|
1285
|
-
this.events.push(event);
|
|
1286
|
-
console.log(`[LEDGER] [${actor}] ${eventType}: ${JSON.stringify(data)}`);
|
|
1287
|
-
return event;
|
|
1288
|
-
}
|
|
1289
|
-
getEvents() {
|
|
1290
|
-
return this.events;
|
|
1291
|
-
}
|
|
1292
|
-
/**
|
|
1293
|
-
* Saves the ledger events to a JSON file.
|
|
1294
|
-
*/
|
|
1295
|
-
async save(filePath) {
|
|
1296
|
-
const targetPath = path6.isAbsolute(filePath) ? filePath : path6.join(this.cwd, filePath);
|
|
1297
|
-
await fs6.mkdir(path6.dirname(targetPath), { recursive: true });
|
|
1298
|
-
await fs6.writeFile(targetPath, JSON.stringify(this.events, null, 2), "utf8");
|
|
1299
|
-
}
|
|
1300
|
-
async verifyRoleConfinement() {
|
|
1301
|
-
const violations = [];
|
|
1302
|
-
const events = this.events;
|
|
1303
|
-
const atlasImplements = events.some((e) => e.actor === "Atlas" && (e.eventType === "implementation_start" || e.eventType === "implementation_complete"));
|
|
1304
|
-
if (atlasImplements) {
|
|
1305
|
-
violations.push("Atlas cannot implement directly");
|
|
1306
|
-
}
|
|
1307
|
-
const nexusMutates = events.some((e) => e.actor === "Nexus" && e.eventType === "file_mutate");
|
|
1308
|
-
if (nexusMutates) {
|
|
1309
|
-
violations.push("Nexus cannot mutate files during discovery");
|
|
1310
|
-
}
|
|
1311
|
-
const sageValidates = events.some((e) => e.actor === "Sage" && (e.eventType === "validation_start" || e.eventType === "validation_complete"));
|
|
1312
|
-
const hasFidelity = events.some((e) => e.eventType === "fidelity_check" || e.eventType === "fidelity_verification");
|
|
1313
|
-
if (sageValidates && !hasFidelity) {
|
|
1314
|
-
violations.push("Sage cannot validate without fidelity evidence");
|
|
1061
|
+
const sageValidates = events.some((e) => e.actor === "Sage" && (e.eventType === "validation_start" || e.eventType === "validation_complete"));
|
|
1062
|
+
const hasFidelity = events.some((e) => e.eventType === "fidelity_check" || e.eventType === "fidelity_verification");
|
|
1063
|
+
if (sageValidates && !hasFidelity) {
|
|
1064
|
+
violations.push("Sage cannot validate without fidelity evidence");
|
|
1315
1065
|
}
|
|
1316
1066
|
const phoenixRemediates = events.find((e) => e.actor === "Phoenix" && e.eventType === "remediation_start");
|
|
1317
1067
|
if (phoenixRemediates) {
|
|
@@ -1387,6 +1137,106 @@ var DelegationController = class {
|
|
|
1387
1137
|
});
|
|
1388
1138
|
}
|
|
1389
1139
|
}
|
|
1140
|
+
/**
|
|
1141
|
+
* Executes one high-risk phase and fails closed unless the runtime confirms
|
|
1142
|
+
* the requested actor through machine-readable output.
|
|
1143
|
+
*/
|
|
1144
|
+
async executePhase({
|
|
1145
|
+
phase,
|
|
1146
|
+
actor,
|
|
1147
|
+
prompt,
|
|
1148
|
+
readOnly,
|
|
1149
|
+
requireExplicitActor = true,
|
|
1150
|
+
onOutput
|
|
1151
|
+
}) {
|
|
1152
|
+
const inspection = await this.adapter.inspect();
|
|
1153
|
+
const supported = inspection.available && inspection.supports?.agent === true;
|
|
1154
|
+
const startType = `${phase}_start`;
|
|
1155
|
+
const completeType = `${phase}_complete`;
|
|
1156
|
+
this.ledger?.logEvent({
|
|
1157
|
+
actor: supported ? actor : "Atlas",
|
|
1158
|
+
actorType: supported ? "runtime-agent" : "control-plane",
|
|
1159
|
+
observed: true,
|
|
1160
|
+
runtime: "opencode",
|
|
1161
|
+
eventType: startType,
|
|
1162
|
+
provenance: "opencode-adapter",
|
|
1163
|
+
data: {
|
|
1164
|
+
phase,
|
|
1165
|
+
runtimeAgentRequested: actor,
|
|
1166
|
+
runtimeAgentApplied: null,
|
|
1167
|
+
runtimeAgentSupported: supported,
|
|
1168
|
+
runtimeAgentSelectionMode: supported ? "explicit" : inspection.available ? "prompt-fallback" : "degraded",
|
|
1169
|
+
runtimeActorConfirmation: "unavailable",
|
|
1170
|
+
readOnly,
|
|
1171
|
+
status: "started"
|
|
1172
|
+
}
|
|
1173
|
+
});
|
|
1174
|
+
let runResult;
|
|
1175
|
+
if (requireExplicitActor && !supported) {
|
|
1176
|
+
runResult = {
|
|
1177
|
+
success: false,
|
|
1178
|
+
status: "BLOCKED",
|
|
1179
|
+
error: "Runtime cannot explicitly select and confirm the requested actor.",
|
|
1180
|
+
commandsRun: [],
|
|
1181
|
+
eventCount: 0,
|
|
1182
|
+
output: "",
|
|
1183
|
+
runtimeAgentRequested: actor,
|
|
1184
|
+
runtimeAgentApplied: null,
|
|
1185
|
+
runtimeAgentSupported: false,
|
|
1186
|
+
runtimeAgentSelectionMode: inspection.available ? "prompt-fallback" : "degraded",
|
|
1187
|
+
runtimeActorConfirmation: "unavailable"
|
|
1188
|
+
};
|
|
1189
|
+
} else {
|
|
1190
|
+
runResult = await this.adapter.execute(prompt, { agent: actor, readOnly, requireActorConfirmation: true, orchestratedChild: true });
|
|
1191
|
+
}
|
|
1192
|
+
if (runResult.success && requireExplicitActor) {
|
|
1193
|
+
if (!["explicit", "sdk-session"].includes(runResult.runtimeAgentSelectionMode || "") || runResult.runtimeAgentApplied !== actor || runResult.runtimeActorConfirmation !== "confirmed") {
|
|
1194
|
+
runResult = {
|
|
1195
|
+
...runResult,
|
|
1196
|
+
success: false,
|
|
1197
|
+
status: "BLOCKED",
|
|
1198
|
+
error: runResult.runtimeAgentApplied ? `Runtime confirmed actor '${runResult.runtimeAgentApplied}', expected '${actor}'.` : `Runtime did not provide machine-readable confirmation for actor '${actor}'.`
|
|
1199
|
+
};
|
|
1200
|
+
}
|
|
1201
|
+
}
|
|
1202
|
+
if (runResult.success && onOutput) {
|
|
1203
|
+
try {
|
|
1204
|
+
const artifact = await onOutput(runResult.output || "");
|
|
1205
|
+
if (artifact) runResult.artifact = artifact;
|
|
1206
|
+
} catch (error) {
|
|
1207
|
+
runResult = {
|
|
1208
|
+
...runResult,
|
|
1209
|
+
success: false,
|
|
1210
|
+
status: "BLOCKED",
|
|
1211
|
+
error: error.message
|
|
1212
|
+
};
|
|
1213
|
+
}
|
|
1214
|
+
}
|
|
1215
|
+
this.ledger?.logEvent({
|
|
1216
|
+
actor: runResult.runtimeAgentApplied === actor ? actor : "Atlas",
|
|
1217
|
+
actorType: runResult.runtimeAgentApplied === actor ? "runtime-agent" : "control-plane",
|
|
1218
|
+
observed: true,
|
|
1219
|
+
runtime: "opencode",
|
|
1220
|
+
eventType: completeType,
|
|
1221
|
+
provenance: "opencode-adapter",
|
|
1222
|
+
data: {
|
|
1223
|
+
phase,
|
|
1224
|
+
success: runResult.success,
|
|
1225
|
+
error: runResult.error || null,
|
|
1226
|
+
runtimeAgentRequested: runResult.runtimeAgentRequested || actor,
|
|
1227
|
+
runtimeAgentApplied: runResult.runtimeAgentApplied || null,
|
|
1228
|
+
runtimeAgentSupported: runResult.runtimeAgentSupported === true,
|
|
1229
|
+
runtimeAgentSelectionMode: runResult.runtimeAgentSelectionMode || "unsupported",
|
|
1230
|
+
runtimeActorConfirmation: runResult.runtimeActorConfirmation || "unavailable",
|
|
1231
|
+
artifactPath: runResult.artifact?.artifactPath || null,
|
|
1232
|
+
artifactHash: runResult.artifact?.artifactHash || null,
|
|
1233
|
+
commandsRun: runResult.commandsRun || [],
|
|
1234
|
+
commandEvents: runResult.commandEvents || [],
|
|
1235
|
+
status: runResult.success ? "COMPLETED" : "BLOCKED"
|
|
1236
|
+
}
|
|
1237
|
+
});
|
|
1238
|
+
return runResult;
|
|
1239
|
+
}
|
|
1390
1240
|
/**
|
|
1391
1241
|
* Logs and executes the implementation process by Astra.
|
|
1392
1242
|
*/
|
|
@@ -1394,6 +1244,9 @@ var DelegationController = class {
|
|
|
1394
1244
|
const decision = this.lastRoutingDecision ?? {
|
|
1395
1245
|
operationType: "explain",
|
|
1396
1246
|
mutationIntent: "readonly",
|
|
1247
|
+
deliveryMode: "answer-only",
|
|
1248
|
+
mutationOwner: null,
|
|
1249
|
+
capabilityRoles: [],
|
|
1397
1250
|
permissionDecision: "allowed",
|
|
1398
1251
|
reason: "no routing decision",
|
|
1399
1252
|
workflowPath: []
|
|
@@ -1440,6 +1293,7 @@ var DelegationController = class {
|
|
|
1440
1293
|
data: {
|
|
1441
1294
|
success: runResult.success,
|
|
1442
1295
|
commandsRun: runResult.commandsRun || [],
|
|
1296
|
+
commandEvents: runResult.commandEvents || [],
|
|
1443
1297
|
eventCount: runResult.eventCount || 0,
|
|
1444
1298
|
error: runResult.error,
|
|
1445
1299
|
event: "implementation.completed",
|
|
@@ -1467,6 +1321,9 @@ var DelegationController = class {
|
|
|
1467
1321
|
const decision = this.lastRoutingDecision ?? {
|
|
1468
1322
|
operationType: "explain",
|
|
1469
1323
|
mutationIntent: "readonly",
|
|
1324
|
+
deliveryMode: "answer-only",
|
|
1325
|
+
mutationOwner: null,
|
|
1326
|
+
capabilityRoles: [],
|
|
1470
1327
|
permissionDecision: "allowed",
|
|
1471
1328
|
reason: "no routing decision",
|
|
1472
1329
|
workflowPath: []
|
|
@@ -1500,8 +1357,8 @@ var DelegationController = class {
|
|
|
1500
1357
|
}
|
|
1501
1358
|
let gitDiff = "";
|
|
1502
1359
|
try {
|
|
1503
|
-
const { execSync:
|
|
1504
|
-
gitDiff =
|
|
1360
|
+
const { execSync: execSync5 } = await import("child_process");
|
|
1361
|
+
gitDiff = execSync5("git diff HEAD", { cwd: this.cwd, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim();
|
|
1505
1362
|
} catch {
|
|
1506
1363
|
}
|
|
1507
1364
|
const prompt = `Please validate the implementation. Diff:
|
|
@@ -1545,10 +1402,10 @@ ${JSON.stringify(result, null, 2)}`;
|
|
|
1545
1402
|
};
|
|
1546
1403
|
|
|
1547
1404
|
// src/core/finalization/workspace-snapshot.ts
|
|
1548
|
-
import
|
|
1549
|
-
import
|
|
1405
|
+
import fs5 from "fs/promises";
|
|
1406
|
+
import path4 from "path";
|
|
1550
1407
|
import crypto2 from "crypto";
|
|
1551
|
-
import { execSync as
|
|
1408
|
+
import { execFileSync, execSync as execSync3 } from "child_process";
|
|
1552
1409
|
var WorkspaceSnapshot = class {
|
|
1553
1410
|
cwd;
|
|
1554
1411
|
fastTrack;
|
|
@@ -1571,11 +1428,16 @@ var WorkspaceSnapshot = class {
|
|
|
1571
1428
|
const files = {};
|
|
1572
1429
|
const isTest = process.env.NODE_ENV === "test" || process.env.VITEST === "true";
|
|
1573
1430
|
if (!this.fastTrack || isTest) {
|
|
1574
|
-
|
|
1431
|
+
const gitVisibleFiles = this.listGitVisibleFiles();
|
|
1432
|
+
if (gitVisibleFiles) {
|
|
1433
|
+
await this.scanFiles(gitVisibleFiles, files);
|
|
1434
|
+
} else {
|
|
1435
|
+
await this.scanDir(this.cwd, files);
|
|
1436
|
+
}
|
|
1575
1437
|
}
|
|
1576
1438
|
const execGit = (args) => {
|
|
1577
1439
|
try {
|
|
1578
|
-
return
|
|
1440
|
+
return execSync3(`git ${args}`, { cwd: this.cwd, encoding: "utf8", stdio: "pipe" }).trim();
|
|
1579
1441
|
} catch {
|
|
1580
1442
|
return "";
|
|
1581
1443
|
}
|
|
@@ -1599,12 +1461,46 @@ var WorkspaceSnapshot = class {
|
|
|
1599
1461
|
untrackedFilesHash: hashString(untrackedFiles)
|
|
1600
1462
|
};
|
|
1601
1463
|
}
|
|
1464
|
+
listGitVisibleFiles() {
|
|
1465
|
+
try {
|
|
1466
|
+
execFileSync("git", ["rev-parse", "--is-inside-work-tree"], {
|
|
1467
|
+
cwd: this.cwd,
|
|
1468
|
+
encoding: "utf8",
|
|
1469
|
+
stdio: "pipe"
|
|
1470
|
+
});
|
|
1471
|
+
const output = execFileSync("git", ["ls-files", "-z", "--cached", "--others", "--exclude-standard"], {
|
|
1472
|
+
cwd: this.cwd,
|
|
1473
|
+
encoding: "utf8",
|
|
1474
|
+
stdio: "pipe"
|
|
1475
|
+
});
|
|
1476
|
+
return [...new Set(output.split("\0").filter(Boolean))];
|
|
1477
|
+
} catch {
|
|
1478
|
+
return null;
|
|
1479
|
+
}
|
|
1480
|
+
}
|
|
1481
|
+
async scanFiles(relativePaths, filesMap) {
|
|
1482
|
+
for (const relativePath of relativePaths) {
|
|
1483
|
+
const normalizedPath = relativePath.replaceAll("\\", "/");
|
|
1484
|
+
if (this.isIgnored(normalizedPath)) continue;
|
|
1485
|
+
try {
|
|
1486
|
+
const fullPath = path4.join(this.cwd, relativePath);
|
|
1487
|
+
const stat = await fs5.stat(fullPath);
|
|
1488
|
+
if (!stat.isFile()) continue;
|
|
1489
|
+
const content = await fs5.readFile(fullPath);
|
|
1490
|
+
const sha256 = crypto2.createHash("sha256").update(content).digest("hex");
|
|
1491
|
+
const isExecutable = (stat.mode & 73) !== 0;
|
|
1492
|
+
const mode = isExecutable ? "100755" : "100644";
|
|
1493
|
+
filesMap[normalizedPath] = { sha256, mode };
|
|
1494
|
+
} catch {
|
|
1495
|
+
}
|
|
1496
|
+
}
|
|
1497
|
+
}
|
|
1602
1498
|
async scanDir(dir, filesMap) {
|
|
1603
1499
|
try {
|
|
1604
|
-
const entries = await
|
|
1500
|
+
const entries = await fs5.readdir(dir, { withFileTypes: true });
|
|
1605
1501
|
for (const entry of entries) {
|
|
1606
|
-
const fullPath =
|
|
1607
|
-
const relativePath =
|
|
1502
|
+
const fullPath = path4.join(dir, entry.name);
|
|
1503
|
+
const relativePath = path4.relative(this.cwd, fullPath);
|
|
1608
1504
|
if (this.isIgnored(relativePath)) {
|
|
1609
1505
|
continue;
|
|
1610
1506
|
}
|
|
@@ -1612,8 +1508,8 @@ var WorkspaceSnapshot = class {
|
|
|
1612
1508
|
await this.scanDir(fullPath, filesMap);
|
|
1613
1509
|
} else if (entry.isFile()) {
|
|
1614
1510
|
try {
|
|
1615
|
-
const stat = await
|
|
1616
|
-
const content = await
|
|
1511
|
+
const stat = await fs5.stat(fullPath);
|
|
1512
|
+
const content = await fs5.readFile(fullPath);
|
|
1617
1513
|
const sha256 = crypto2.createHash("sha256").update(content).digest("hex");
|
|
1618
1514
|
const isExecutable = (stat.mode & 73) !== 0;
|
|
1619
1515
|
const mode = isExecutable ? "100755" : "100644";
|
|
@@ -1647,9 +1543,9 @@ var WorkspaceSnapshot = class {
|
|
|
1647
1543
|
};
|
|
1648
1544
|
|
|
1649
1545
|
// src/core/finalization/finalizer.ts
|
|
1650
|
-
import { execSync as
|
|
1651
|
-
import
|
|
1652
|
-
import
|
|
1546
|
+
import { execSync as execSync4 } from "child_process";
|
|
1547
|
+
import path5 from "path";
|
|
1548
|
+
import fs6 from "fs/promises";
|
|
1653
1549
|
var Finalizer = class {
|
|
1654
1550
|
cwd;
|
|
1655
1551
|
snapshotManager;
|
|
@@ -1662,9 +1558,9 @@ var Finalizer = class {
|
|
|
1662
1558
|
getChangedFilesFromGit() {
|
|
1663
1559
|
try {
|
|
1664
1560
|
const files = /* @__PURE__ */ new Set();
|
|
1665
|
-
const diff =
|
|
1561
|
+
const diff = execSync4("git diff --name-only HEAD", { cwd: this.cwd, encoding: "utf8" });
|
|
1666
1562
|
diff.split("\n").map((f) => f.trim()).filter(Boolean).forEach((f) => files.add(f));
|
|
1667
|
-
const status =
|
|
1563
|
+
const status = execSync4("git status --short --untracked-files=all", { cwd: this.cwd, encoding: "utf8" });
|
|
1668
1564
|
status.split("\n").forEach((line) => {
|
|
1669
1565
|
const match = line.match(/^(?:[ MADRCU?!]{2}\s+|[MADRCU?!]\s+)(.+)$/);
|
|
1670
1566
|
if (match) {
|
|
@@ -1722,10 +1618,10 @@ var Finalizer = class {
|
|
|
1722
1618
|
const verifyResult = await fidelityGate.verify(filesToCheck);
|
|
1723
1619
|
let passed = requestFidelity.passed && verifyResult.passed;
|
|
1724
1620
|
let reason = !requestFidelity.passed ? requestFidelity.reason : verifyResult.reason;
|
|
1725
|
-
const evidencePath =
|
|
1726
|
-
const hasEvidence = await
|
|
1621
|
+
const evidencePath = path5.join(this.cwd, "EVIDENCE.json");
|
|
1622
|
+
const hasEvidence = await fs6.access(evidencePath).then(() => true).catch(() => false);
|
|
1727
1623
|
if (hasEvidence) {
|
|
1728
|
-
const { EvidenceValidator } = await import("./evidence-validator-
|
|
1624
|
+
const { EvidenceValidator } = await import("./evidence-validator-HS3NTWAB.js");
|
|
1729
1625
|
const isTest = process.env.NODE_ENV === "test" || process.env.VITEST === "true";
|
|
1730
1626
|
const evidenceValidator = new EvidenceValidator({ cwd: this.cwd, skipFileCheck: isTest, skipGitCheck: isTest });
|
|
1731
1627
|
const valResult = await evidenceValidator.validate();
|
|
@@ -1801,7 +1697,232 @@ var Finalizer = class {
|
|
|
1801
1697
|
}
|
|
1802
1698
|
};
|
|
1803
1699
|
|
|
1700
|
+
// src/core/workflow-profiles.ts
|
|
1701
|
+
var PROFILE_DEFINITIONS = Object.freeze({
|
|
1702
|
+
"frontend-product": Object.freeze({
|
|
1703
|
+
owner: "Astra",
|
|
1704
|
+
skills: ["frontend-development", "ui-ux-design", "frontend-design-system"],
|
|
1705
|
+
objective: "Deliver a user-facing product or marketing surface with truthful copy and deliberate visual composition.",
|
|
1706
|
+
requiredChecks: ["tests", "typecheck", "build", "responsive-render", "primary-interaction", "truthfulness"],
|
|
1707
|
+
forbiddenAssumptions: ["fixed SaaS section sequence", "mandatory pricing", "mandatory testimonials", "subjective premium score"]
|
|
1708
|
+
}),
|
|
1709
|
+
"frontend-utility": Object.freeze({
|
|
1710
|
+
owner: "Astra",
|
|
1711
|
+
skills: ["frontend-development", "ui-ux-design", "frontend-design-system"],
|
|
1712
|
+
objective: "Deliver a focused user tool with a complete primary flow and clear operational states.",
|
|
1713
|
+
requiredChecks: ["tests", "typecheck", "build", "responsive-render", "primary-interaction"],
|
|
1714
|
+
forbiddenAssumptions: ["marketing sections", "pricing", "testimonials", "commercial narrative"]
|
|
1715
|
+
}),
|
|
1716
|
+
"backend-api": Object.freeze({
|
|
1717
|
+
owner: "Astra",
|
|
1718
|
+
skills: ["backend-development"],
|
|
1719
|
+
objective: "Deliver a bounded backend/API change with validated contracts, errors, authorization, and tests.",
|
|
1720
|
+
requiredChecks: ["tests", "build-or-typecheck", "input-validation", "failure-paths"],
|
|
1721
|
+
forbiddenAssumptions: ["frontend deliverables", "visual evidence"]
|
|
1722
|
+
}),
|
|
1723
|
+
refactor: Object.freeze({
|
|
1724
|
+
owner: "Astra",
|
|
1725
|
+
skills: ["refactoring"],
|
|
1726
|
+
objective: "Improve structure without changing observable behavior outside the approved scope.",
|
|
1727
|
+
requiredChecks: ["behavior-preservation", "tests", "focused-diff"],
|
|
1728
|
+
forbiddenAssumptions: ["new product behavior", "unrelated cleanup"]
|
|
1729
|
+
}),
|
|
1730
|
+
documentation: Object.freeze({
|
|
1731
|
+
owner: "Astra",
|
|
1732
|
+
skills: ["documentation"],
|
|
1733
|
+
objective: "Create or update accurate documentation grounded in the current repository behavior.",
|
|
1734
|
+
requiredChecks: ["references", "paths", "commands", "consistency"],
|
|
1735
|
+
forbiddenAssumptions: ["implementation claims without evidence"]
|
|
1736
|
+
}),
|
|
1737
|
+
"security-review": Object.freeze({
|
|
1738
|
+
owner: "Sage",
|
|
1739
|
+
skills: ["qa-workflow", "technical-leadership"],
|
|
1740
|
+
objective: "Inspect security-relevant behavior and report evidence-ranked findings without implementing unrelated changes.",
|
|
1741
|
+
requiredChecks: ["evidence", "severity", "runtime-vs-dev", "remediation-owner"],
|
|
1742
|
+
forbiddenAssumptions: ["automatic force fixes", "unverified exploitability"]
|
|
1743
|
+
}),
|
|
1744
|
+
generic: Object.freeze({
|
|
1745
|
+
owner: "Atlas",
|
|
1746
|
+
skills: [],
|
|
1747
|
+
objective: "Route work without inventing domain requirements.",
|
|
1748
|
+
requiredChecks: ["scope", "owner", "mode"],
|
|
1749
|
+
forbiddenAssumptions: ["domain-specific structure"]
|
|
1750
|
+
})
|
|
1751
|
+
});
|
|
1752
|
+
var PROFILE_PATTERNS = Object.freeze([
|
|
1753
|
+
["security-review", /\b(?:security review|security audit|vulnerability audit|threat model)\b/i],
|
|
1754
|
+
["refactor", /\b(?:refactor|restructure|cleanup without behavior change|preserve behavior)\b/i],
|
|
1755
|
+
["documentation", /\b(?:documentation only|write docs|update readme|document the|documentation task)\b/i],
|
|
1756
|
+
["backend-api", /\b(?:api endpoint|backend api|rest api|graphql|controller|service endpoint|authenticated api)\b/i],
|
|
1757
|
+
["frontend-product", /\b(?:landing page|marketing page|product page|pricing page|homepage|redesign|brand page)\b/i],
|
|
1758
|
+
["frontend-utility", /\b(?:validator|search page|lookup|dashboard|form|calculator|tool|admin page|repository search)\b/i]
|
|
1759
|
+
]);
|
|
1760
|
+
function getWorkflowProfile(name = "generic") {
|
|
1761
|
+
return PROFILE_DEFINITIONS[name] || PROFILE_DEFINITIONS.generic;
|
|
1762
|
+
}
|
|
1763
|
+
function resolveWorkflowProfile({ request = "", explicitProfile = null } = {}) {
|
|
1764
|
+
if (explicitProfile && PROFILE_DEFINITIONS[explicitProfile]) return explicitProfile;
|
|
1765
|
+
const text = String(request).trim();
|
|
1766
|
+
for (const [profile, pattern] of PROFILE_PATTERNS) {
|
|
1767
|
+
if (pattern.test(text)) return profile;
|
|
1768
|
+
}
|
|
1769
|
+
return "generic";
|
|
1770
|
+
}
|
|
1771
|
+
|
|
1772
|
+
// src/core/request-classifier.ts
|
|
1773
|
+
var RequestClassifier = class {
|
|
1774
|
+
/**
|
|
1775
|
+
* Classifies a natural request.
|
|
1776
|
+
*/
|
|
1777
|
+
classify(request = "", options = {}) {
|
|
1778
|
+
const text = String(request).trim();
|
|
1779
|
+
if (!text) {
|
|
1780
|
+
throw new Error("Cannot classify an empty request.");
|
|
1781
|
+
}
|
|
1782
|
+
const cwd = options.cwd || process.cwd();
|
|
1783
|
+
const profile = resolveWorkflowProfile({ request: text });
|
|
1784
|
+
const profileDef = getWorkflowProfile(profile);
|
|
1785
|
+
const router = new RequestRouter({ cwd });
|
|
1786
|
+
const requestUnderstanding = router.understand(text);
|
|
1787
|
+
const routingDecision = router.route(requestUnderstanding);
|
|
1788
|
+
const intent = requestUnderstanding.mutationIntent === "readonly" || requestUnderstanding.deliveryMode === "answer-only" ? "read-only" : "write";
|
|
1789
|
+
const risk = requestUnderstanding.riskLevel;
|
|
1790
|
+
const explicitRequiredEvidence = /\b(required evidence|evidence required)\b/i.test(text);
|
|
1791
|
+
const explicitRequiredSpec = /\b(formal spec|required spec)\b/i.test(text);
|
|
1792
|
+
const specPolicy = requestUnderstanding.specPolicy === "required" || explicitRequiredSpec ? "required" : "none";
|
|
1793
|
+
const evidencePolicy = risk === "high" || requestUnderstanding.mutationIntent === "publish" || explicitRequiredEvidence ? "required" : "optional";
|
|
1794
|
+
const remediationLimit = risk === "high" ? 3 : risk === "low" ? 1 : 2;
|
|
1795
|
+
const specNeeded = specPolicy === "required";
|
|
1796
|
+
const validationNeeded = intent === "write";
|
|
1797
|
+
let legacyMode = "standard";
|
|
1798
|
+
if (/\b(deep|architectural|migration|major|full|\[deep\])\b/i.test(text)) {
|
|
1799
|
+
legacyMode = "full";
|
|
1800
|
+
} else if (/\b(tiny|small|quick|simple|only\s+update|typo|comment|\[tiny\])\b/i.test(text)) {
|
|
1801
|
+
const isPureDocsOrTypo = profile === "documentation" || /\b(typo|comment|readme|docs|documentation)\b/i.test(text);
|
|
1802
|
+
if (intent === "write" && !isPureDocsOrTypo) {
|
|
1803
|
+
legacyMode = "standard";
|
|
1804
|
+
} else {
|
|
1805
|
+
legacyMode = "quick";
|
|
1806
|
+
}
|
|
1807
|
+
}
|
|
1808
|
+
return {
|
|
1809
|
+
request: text,
|
|
1810
|
+
profile,
|
|
1811
|
+
owner: routingDecision.selectedActor || profileDef.owner,
|
|
1812
|
+
intent,
|
|
1813
|
+
mode: legacyMode,
|
|
1814
|
+
risk,
|
|
1815
|
+
specPolicy,
|
|
1816
|
+
evidencePolicy,
|
|
1817
|
+
remediationLimit,
|
|
1818
|
+
riskDrivers: requestUnderstanding.riskDrivers || [],
|
|
1819
|
+
matchedSignals: requestUnderstanding.matchedSignals || [],
|
|
1820
|
+
specNeeded,
|
|
1821
|
+
validationNeeded,
|
|
1822
|
+
skills: [...profileDef.skills],
|
|
1823
|
+
requestUnderstanding,
|
|
1824
|
+
routingDecision
|
|
1825
|
+
};
|
|
1826
|
+
}
|
|
1827
|
+
};
|
|
1828
|
+
|
|
1829
|
+
// src/core/execution-planner.ts
|
|
1830
|
+
import path6 from "path";
|
|
1831
|
+
var ExecutionPlanner = class {
|
|
1832
|
+
cwd;
|
|
1833
|
+
constructor({ cwd = process.cwd() } = {}) {
|
|
1834
|
+
this.cwd = cwd;
|
|
1835
|
+
}
|
|
1836
|
+
/**
|
|
1837
|
+
* Generates an execution plan.
|
|
1838
|
+
*/
|
|
1839
|
+
plan(classification, taskSlug = "task") {
|
|
1840
|
+
const remediationLimit = classification.remediationLimit;
|
|
1841
|
+
const branchNeeded = classification.intent === "write";
|
|
1842
|
+
const specPath = classification.specNeeded ? path6.join("docs/workflows", taskSlug, "spec.md") : null;
|
|
1843
|
+
const owner = classification.routingDecision?.mutationOwner === "Astra" ? "Astra" : classification.routingDecision?.selectedActor || (classification.intent === "write" ? "Astra" : classification.owner);
|
|
1844
|
+
const validationsExpected = [];
|
|
1845
|
+
if (classification.validationNeeded) {
|
|
1846
|
+
validationsExpected.push("test");
|
|
1847
|
+
if (classification.profile.startsWith("frontend")) {
|
|
1848
|
+
validationsExpected.push("lint", "build");
|
|
1849
|
+
} else if (classification.profile === "backend-api") {
|
|
1850
|
+
validationsExpected.push("lint");
|
|
1851
|
+
}
|
|
1852
|
+
}
|
|
1853
|
+
const restrictions = [
|
|
1854
|
+
"Never commit or push directly to main/master.",
|
|
1855
|
+
"Always execute behavior tests for new or modified behavior."
|
|
1856
|
+
];
|
|
1857
|
+
if (classification.risk === "high") {
|
|
1858
|
+
restrictions.push("Require independent validation review.");
|
|
1859
|
+
}
|
|
1860
|
+
return {
|
|
1861
|
+
objective: classification.request,
|
|
1862
|
+
scope: classification.intent === "read-only" ? "Analysis and verification" : "Implementation of requested behavior",
|
|
1863
|
+
restrictions,
|
|
1864
|
+
owner,
|
|
1865
|
+
skills: [...classification.skills],
|
|
1866
|
+
branchNeeded,
|
|
1867
|
+
branchName: branchNeeded ? `feat/${taskSlug}` : null,
|
|
1868
|
+
validationsExpected,
|
|
1869
|
+
remediationLimit,
|
|
1870
|
+
specPath,
|
|
1871
|
+
specPolicy: classification.specPolicy,
|
|
1872
|
+
evidencePolicy: classification.evidencePolicy,
|
|
1873
|
+
riskLevel: classification.risk,
|
|
1874
|
+
mode: classification.mode,
|
|
1875
|
+
profile: classification.profile
|
|
1876
|
+
};
|
|
1877
|
+
}
|
|
1878
|
+
};
|
|
1879
|
+
|
|
1880
|
+
// src/core/workflow-state-machine.ts
|
|
1881
|
+
var WorkflowStateMachine = class {
|
|
1882
|
+
state;
|
|
1883
|
+
history;
|
|
1884
|
+
validTransitions;
|
|
1885
|
+
constructor() {
|
|
1886
|
+
this.state = "RECEIVED";
|
|
1887
|
+
this.history = [{ state: this.state, timestamp: (/* @__PURE__ */ new Date()).toISOString() }];
|
|
1888
|
+
this.validTransitions = {
|
|
1889
|
+
RECEIVED: ["CLASSIFIED", "BLOCKED"],
|
|
1890
|
+
CLASSIFIED: ["PLANNED", "BLOCKED"],
|
|
1891
|
+
PLANNED: ["BRANCH_READY", "BLOCKED"],
|
|
1892
|
+
BRANCH_READY: ["DELEGATED", "BLOCKED"],
|
|
1893
|
+
DELEGATED: ["IMPLEMENTING", "BLOCKED"],
|
|
1894
|
+
IMPLEMENTING: ["IMPLEMENTED", "BLOCKED"],
|
|
1895
|
+
IMPLEMENTED: ["VALIDATING", "BLOCKED"],
|
|
1896
|
+
VALIDATING: ["COMPLETED", "COMPLETED_WITH_NOTES", "REMEDIATING", "BLOCKED"],
|
|
1897
|
+
REMEDIATING: ["REVALIDATING", "BLOCKED"],
|
|
1898
|
+
REVALIDATING: ["COMPLETED", "COMPLETED_WITH_NOTES", "REMEDIATING", "BLOCKED"],
|
|
1899
|
+
COMPLETED: [],
|
|
1900
|
+
COMPLETED_WITH_NOTES: [],
|
|
1901
|
+
BLOCKED: []
|
|
1902
|
+
};
|
|
1903
|
+
}
|
|
1904
|
+
/**
|
|
1905
|
+
* Transitions the machine to a new state.
|
|
1906
|
+
* @param newState - The state to transition to.
|
|
1907
|
+
*/
|
|
1908
|
+
transitionTo(newState) {
|
|
1909
|
+
const allowed = this.validTransitions[this.state];
|
|
1910
|
+
if (!allowed || !allowed.includes(newState)) {
|
|
1911
|
+
throw new Error(`Invalid state transition: ${this.state} -> ${newState}`);
|
|
1912
|
+
}
|
|
1913
|
+
this.state = newState;
|
|
1914
|
+
this.history.push({ state: this.state, timestamp: (/* @__PURE__ */ new Date()).toISOString() });
|
|
1915
|
+
}
|
|
1916
|
+
getCurrentState() {
|
|
1917
|
+
return this.state;
|
|
1918
|
+
}
|
|
1919
|
+
getHistory() {
|
|
1920
|
+
return [...this.history];
|
|
1921
|
+
}
|
|
1922
|
+
};
|
|
1923
|
+
|
|
1804
1924
|
export {
|
|
1925
|
+
getPackageRoot,
|
|
1805
1926
|
readPackageFile,
|
|
1806
1927
|
getPackageVersion,
|
|
1807
1928
|
discoverPackageFiles,
|
|
@@ -1811,17 +1932,16 @@ export {
|
|
|
1811
1932
|
isTerminalFailure,
|
|
1812
1933
|
COMPLETION_STATUS_TEXT,
|
|
1813
1934
|
OpenCodeAdapter,
|
|
1814
|
-
BranchGate,
|
|
1815
1935
|
SpecValidator,
|
|
1816
1936
|
HandoffEngine,
|
|
1817
1937
|
HealerEngine,
|
|
1818
1938
|
getWorkflowProfile,
|
|
1819
1939
|
resolveWorkflowProfile,
|
|
1820
|
-
RequestClassifier,
|
|
1821
|
-
ExecutionPlanner,
|
|
1822
|
-
WorkflowStateMachine,
|
|
1823
1940
|
EvidenceLedger,
|
|
1824
1941
|
DelegationController,
|
|
1825
|
-
Finalizer
|
|
1942
|
+
Finalizer,
|
|
1943
|
+
RequestClassifier,
|
|
1944
|
+
ExecutionPlanner,
|
|
1945
|
+
WorkflowStateMachine
|
|
1826
1946
|
};
|
|
1827
|
-
//# sourceMappingURL=chunk-
|
|
1947
|
+
//# sourceMappingURL=chunk-4FI5ODAM.js.map
|