@williambeto/ai-workflow 2.7.1 → 2.8.0
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 +28 -0
- package/README.md +3 -2
- package/bin/ai-workflow.js +1015 -257
- package/bin/ai-workflow.js.map +1 -1
- package/chunk-AINIR25D.js +120 -0
- package/chunk-AY33SA5W.js +46 -0
- package/{evidence-validator-76ZQQYDU.js → chunk-FNT7DN3N.js} +22 -2
- package/{chunk-W4RTQWVQ.js → chunk-LI76KI7C.js} +977 -820
- package/{chunk-BDZPUAEX.js → chunk-Y4RLP6ZM.js} +11 -3
- package/chunk-YOBY5C72.js +76 -0
- package/core/index.d.ts +32 -1
- package/core/index.js +4 -2
- package/dist-assets/docs/cli-reference.md +47 -2
- package/dist-assets/schemas/approval-receipt.schema.json +35 -0
- package/dist-assets/schemas/evidence.schema.json +33 -0
- 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-IEHLAVZ6.js} +2 -2
|
@@ -94,6 +94,21 @@ var AGENT_PROMPT_CONTRACTS = {
|
|
|
94
94
|
Sage: "Act as Sage, the independent validation agent. Audit the implementation against requirements, inspect evidence, and report concrete pass/fail findings.",
|
|
95
95
|
Phoenix: "Act as Phoenix, the bounded remediation agent. Remediate only concrete findings, keep attempts bounded, and stop when no progress is made."
|
|
96
96
|
};
|
|
97
|
+
function extractSdkExecution(data, requestedAgent) {
|
|
98
|
+
const parts = data?.parts || [];
|
|
99
|
+
const output = parts.filter((part) => part.type === "text" && typeof part.text === "string").map((part) => part.text).join("");
|
|
100
|
+
const commandsRun = parts.filter((part) => part.type === "tool").map((part) => part.state?.input?.command ?? part.state?.input?.CommandLine).filter((command) => typeof command === "string");
|
|
101
|
+
const appliedAgent = typeof data?.info?.agent === "string" ? data.info.agent : null;
|
|
102
|
+
const confirmed = appliedAgent === requestedAgent;
|
|
103
|
+
return {
|
|
104
|
+
success: !data?.info?.error && confirmed,
|
|
105
|
+
commandsRun,
|
|
106
|
+
eventCount: parts.length,
|
|
107
|
+
runtimeAgentApplied: appliedAgent,
|
|
108
|
+
runtimeActorConfirmation: confirmed ? "confirmed" : "unavailable",
|
|
109
|
+
output
|
|
110
|
+
};
|
|
111
|
+
}
|
|
97
112
|
var OpenCodeAdapter = class {
|
|
98
113
|
cwd;
|
|
99
114
|
constructor({ cwd = process.cwd() } = {}) {
|
|
@@ -110,13 +125,14 @@ var OpenCodeAdapter = class {
|
|
|
110
125
|
if (!available) {
|
|
111
126
|
return {
|
|
112
127
|
available: false,
|
|
113
|
-
supports: { run: false, formatJson: false, agent: false, model: false }
|
|
128
|
+
supports: { run: false, server: false, formatJson: false, agent: false, model: false }
|
|
114
129
|
};
|
|
115
130
|
}
|
|
116
131
|
return {
|
|
117
132
|
available: true,
|
|
118
133
|
supports: {
|
|
119
134
|
run: /\brun\b/.test(cliHelp.stdout),
|
|
135
|
+
server: /\bserve\b/.test(cliHelp.stdout),
|
|
120
136
|
formatJson: /--format/.test(runHelp.stdout),
|
|
121
137
|
agent: /--agent/.test(runHelp.stdout),
|
|
122
138
|
model: /--model/.test(runHelp.stdout)
|
|
@@ -125,14 +141,14 @@ var OpenCodeAdapter = class {
|
|
|
125
141
|
} catch {
|
|
126
142
|
return {
|
|
127
143
|
available: false,
|
|
128
|
-
supports: { run: false, formatJson: false, agent: false, model: false }
|
|
144
|
+
supports: { run: false, server: false, formatJson: false, agent: false, model: false }
|
|
129
145
|
};
|
|
130
146
|
}
|
|
131
147
|
}
|
|
132
148
|
/**
|
|
133
149
|
* Runs opencode with a prompt and options.
|
|
134
150
|
*/
|
|
135
|
-
async execute(message, { agent = null, model = null, readOnly = false, fastTrack = false } = {}) {
|
|
151
|
+
async execute(message, { agent = null, model = null, readOnly = false, fastTrack = false, requireActorConfirmation = false } = {}) {
|
|
136
152
|
const inspection = await this.inspect();
|
|
137
153
|
const isSpecializedAgent = agent && ["Nexus", "Orion", "Astra", "Sage", "Phoenix"].includes(agent);
|
|
138
154
|
if (!readOnly) {
|
|
@@ -152,6 +168,8 @@ var OpenCodeAdapter = class {
|
|
|
152
168
|
runtimeAgentApplied: null,
|
|
153
169
|
runtimeAgentSupported: false,
|
|
154
170
|
runtimeAgentSelectionMode: "unsupported",
|
|
171
|
+
runtimeActorConfirmation: "unavailable",
|
|
172
|
+
output: "",
|
|
155
173
|
commandsRun: [],
|
|
156
174
|
eventCount: 0
|
|
157
175
|
};
|
|
@@ -159,16 +177,20 @@ var OpenCodeAdapter = class {
|
|
|
159
177
|
}
|
|
160
178
|
}
|
|
161
179
|
if (!inspection.available) {
|
|
180
|
+
console.warn("\\n[WARNING] OpenCode CLI is not installed or not available in the system PATH.");
|
|
181
|
+
console.warn("[WARNING] Graceful degradation active: simulating successful execution (dry-run).\\n");
|
|
162
182
|
return {
|
|
163
|
-
success:
|
|
164
|
-
status:
|
|
183
|
+
success: true,
|
|
184
|
+
status: "DEGRADED",
|
|
165
185
|
commandsRun: [],
|
|
166
186
|
eventCount: 0,
|
|
167
|
-
error: "OpenCode CLI
|
|
187
|
+
error: "OpenCode CLI missing. Simulated execution.",
|
|
168
188
|
runtimeAgentRequested: agent || null,
|
|
169
189
|
runtimeAgentApplied: null,
|
|
170
190
|
runtimeAgentSupported: false,
|
|
171
|
-
runtimeAgentSelectionMode:
|
|
191
|
+
runtimeAgentSelectionMode: "degraded",
|
|
192
|
+
runtimeActorConfirmation: "unavailable",
|
|
193
|
+
output: ""
|
|
172
194
|
};
|
|
173
195
|
}
|
|
174
196
|
const usePromptFallback = Boolean(isSpecializedAgent && !inspection.supports.agent && agent);
|
|
@@ -179,6 +201,73 @@ Runtime agent selection note: requested actor '${agent}' could not be applied wi
|
|
|
179
201
|
User request:
|
|
180
202
|
${message}` : message;
|
|
181
203
|
const runtimeAgentSelectionMode = isSpecializedAgent ? usePromptFallback ? "prompt-fallback" : "explicit" : "default";
|
|
204
|
+
const runWithSdk = async (targetCwd) => {
|
|
205
|
+
if (!agent) {
|
|
206
|
+
return {
|
|
207
|
+
success: false,
|
|
208
|
+
status: "BLOCKED",
|
|
209
|
+
error: "Actor confirmation requires a requested agent.",
|
|
210
|
+
commandsRun: [],
|
|
211
|
+
eventCount: 0,
|
|
212
|
+
runtimeAgentRequested: null,
|
|
213
|
+
runtimeAgentApplied: null,
|
|
214
|
+
runtimeAgentSupported: inspection.supports.agent,
|
|
215
|
+
runtimeAgentSelectionMode: "sdk-session",
|
|
216
|
+
runtimeActorConfirmation: "unavailable",
|
|
217
|
+
output: ""
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
let server;
|
|
221
|
+
try {
|
|
222
|
+
const { createOpencode } = await import("@opencode-ai/sdk/v2");
|
|
223
|
+
const runtime = await createOpencode({ port: 0, timeout: 1e4 });
|
|
224
|
+
server = runtime.server;
|
|
225
|
+
const permissions = readOnly ? [
|
|
226
|
+
{ permission: "edit", pattern: "*", action: "deny" },
|
|
227
|
+
{ permission: "bash", pattern: "*", action: "deny" },
|
|
228
|
+
{ permission: "task", pattern: "*", action: "deny" },
|
|
229
|
+
{ permission: "external_directory", pattern: "*", action: "deny" }
|
|
230
|
+
] : void 0;
|
|
231
|
+
const session = await runtime.client.session.create({
|
|
232
|
+
directory: targetCwd,
|
|
233
|
+
title: `AI Workflow Kit: ${agent}`,
|
|
234
|
+
agent,
|
|
235
|
+
permission: permissions
|
|
236
|
+
}, { throwOnError: true });
|
|
237
|
+
const response = await runtime.client.session.prompt({
|
|
238
|
+
sessionID: session.data.id,
|
|
239
|
+
directory: targetCwd,
|
|
240
|
+
agent,
|
|
241
|
+
parts: [{ type: "text", text: message }]
|
|
242
|
+
}, { throwOnError: true });
|
|
243
|
+
const extracted = extractSdkExecution(response.data, agent);
|
|
244
|
+
if (extracted.output) process.stdout.write(extracted.output);
|
|
245
|
+
return {
|
|
246
|
+
...extracted,
|
|
247
|
+
status: extracted.success ? "COMPLETED" : "BLOCKED",
|
|
248
|
+
error: extracted.success ? void 0 : extracted.runtimeAgentApplied ? `Runtime confirmed actor '${extracted.runtimeAgentApplied}', expected '${agent}'.` : `Runtime did not provide machine-readable confirmation for actor '${agent}'.`,
|
|
249
|
+
runtimeAgentRequested: agent,
|
|
250
|
+
runtimeAgentSupported: inspection.supports.agent,
|
|
251
|
+
runtimeAgentSelectionMode: "sdk-session"
|
|
252
|
+
};
|
|
253
|
+
} catch (error) {
|
|
254
|
+
return {
|
|
255
|
+
success: false,
|
|
256
|
+
status: "BLOCKED",
|
|
257
|
+
error: `OpenCode SDK actor confirmation failed: ${error.message}`,
|
|
258
|
+
commandsRun: [],
|
|
259
|
+
eventCount: 0,
|
|
260
|
+
runtimeAgentRequested: agent,
|
|
261
|
+
runtimeAgentApplied: null,
|
|
262
|
+
runtimeAgentSupported: inspection.supports.agent,
|
|
263
|
+
runtimeAgentSelectionMode: "sdk-session",
|
|
264
|
+
runtimeActorConfirmation: "unavailable",
|
|
265
|
+
output: ""
|
|
266
|
+
};
|
|
267
|
+
} finally {
|
|
268
|
+
server?.close();
|
|
269
|
+
}
|
|
270
|
+
};
|
|
182
271
|
const runInWorkspace = async (targetCwd) => {
|
|
183
272
|
return new Promise((resolve2) => {
|
|
184
273
|
const args = ["run", runtimeMessage];
|
|
@@ -203,14 +292,20 @@ ${message}` : message;
|
|
|
203
292
|
});
|
|
204
293
|
const commandsRun = [];
|
|
205
294
|
let eventCount = 0;
|
|
295
|
+
let output = "";
|
|
296
|
+
let confirmedAgent = null;
|
|
206
297
|
rl.on("line", (line) => {
|
|
207
298
|
const trimmed = line.trim();
|
|
208
299
|
if (!trimmed) return;
|
|
209
300
|
try {
|
|
210
301
|
const event = JSON.parse(trimmed);
|
|
211
302
|
eventCount++;
|
|
303
|
+
if (event.type === "agent_identity" && event.confirmed === true && typeof event.agent === "string") {
|
|
304
|
+
confirmedAgent = event.agent;
|
|
305
|
+
}
|
|
212
306
|
if (event.type === "text" && event.part?.text) {
|
|
213
307
|
process.stdout.write(event.part.text);
|
|
308
|
+
output += event.part.text;
|
|
214
309
|
}
|
|
215
310
|
if (event.type === "step_start" && event.part?.toolCalls) {
|
|
216
311
|
for (const call of event.part.toolCalls) {
|
|
@@ -240,7 +335,9 @@ ${message}` : message;
|
|
|
240
335
|
runtimeAgentRequested: agent || null,
|
|
241
336
|
runtimeAgentApplied: usePromptFallback ? null : null,
|
|
242
337
|
runtimeAgentSupported: inspection.supports.agent,
|
|
243
|
-
runtimeAgentSelectionMode
|
|
338
|
+
runtimeAgentSelectionMode,
|
|
339
|
+
runtimeActorConfirmation: confirmedAgent ? "confirmed" : "unavailable",
|
|
340
|
+
output
|
|
244
341
|
});
|
|
245
342
|
return;
|
|
246
343
|
}
|
|
@@ -258,9 +355,11 @@ ${message}` : message;
|
|
|
258
355
|
commandsRun,
|
|
259
356
|
eventCount,
|
|
260
357
|
runtimeAgentRequested: agent || null,
|
|
261
|
-
runtimeAgentApplied: isSpecializedAgent
|
|
358
|
+
runtimeAgentApplied: isSpecializedAgent ? confirmedAgent : null,
|
|
262
359
|
runtimeAgentSupported: inspection.supports.agent,
|
|
263
|
-
runtimeAgentSelectionMode
|
|
360
|
+
runtimeAgentSelectionMode,
|
|
361
|
+
runtimeActorConfirmation: confirmedAgent ? "confirmed" : "unavailable",
|
|
362
|
+
output
|
|
264
363
|
});
|
|
265
364
|
});
|
|
266
365
|
child.on("error", (err) => {
|
|
@@ -274,171 +373,75 @@ ${message}` : message;
|
|
|
274
373
|
runtimeAgentRequested: agent || null,
|
|
275
374
|
runtimeAgentApplied: null,
|
|
276
375
|
runtimeAgentSupported: inspection.supports.agent,
|
|
277
|
-
runtimeAgentSelectionMode
|
|
376
|
+
runtimeAgentSelectionMode,
|
|
377
|
+
runtimeActorConfirmation: "unavailable",
|
|
378
|
+
output
|
|
278
379
|
});
|
|
279
380
|
});
|
|
280
381
|
});
|
|
281
382
|
};
|
|
383
|
+
const runner = requireActorConfirmation && inspection.supports.server ? runWithSdk : runInWorkspace;
|
|
282
384
|
if (readOnly && !fastTrack) {
|
|
283
385
|
const { runInReadOnlyWorkspace } = await import("./read-only-workspace-PZBE7KAX.js");
|
|
284
|
-
return runInReadOnlyWorkspace(this.cwd,
|
|
386
|
+
return runInReadOnlyWorkspace(this.cwd, runner);
|
|
285
387
|
} else {
|
|
286
|
-
return
|
|
388
|
+
return runner(this.cwd);
|
|
287
389
|
}
|
|
288
390
|
}
|
|
289
391
|
};
|
|
290
392
|
|
|
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 };
|
|
393
|
+
// src/core/sdd/validator.ts
|
|
394
|
+
import fs from "fs/promises";
|
|
395
|
+
var SpecValidator = class {
|
|
396
|
+
validateContent(content, requiredStatus = "APPROVED") {
|
|
397
|
+
const title = content.split(/\r?\n/, 1)[0]?.trim() || "";
|
|
398
|
+
let tier = "unknown";
|
|
399
|
+
if (/^#\s+\[DEEP\](?:\s|$)/.test(title)) tier = "deep";
|
|
400
|
+
else if (/^#\s+\[STANDARD\](?:\s|$)/.test(title)) tier = "standard";
|
|
401
|
+
else if (/^#\s+\[TINY\](?:\s|$)/.test(title)) tier = "tiny";
|
|
402
|
+
if (tier === "unknown") {
|
|
403
|
+
return { valid: false, reason: "Specification tier [DEEP|STANDARD|TINY] not identified in title." };
|
|
404
|
+
}
|
|
405
|
+
if (!/^## Metadata\s*$/m.test(content)) {
|
|
406
|
+
return { valid: false, reason: "Missing '## Metadata' section.", tier };
|
|
407
|
+
}
|
|
408
|
+
if (!/^## Acceptance Criteria\s*$/m.test(content)) {
|
|
409
|
+
return { valid: false, reason: "Missing '## Acceptance Criteria' section.", tier };
|
|
410
|
+
}
|
|
411
|
+
const statusMatch = content.match(/^\s*-?\s*(?:\*\*)?Status(?:\*\*)?\s*:\s*(.+?)\s*$/im);
|
|
412
|
+
if (!statusMatch) {
|
|
413
|
+
return { valid: false, reason: "Missing Status field in Metadata section.", tier };
|
|
414
|
+
}
|
|
415
|
+
const status = statusMatch[1].replace(/[\*_`]/g, "").trim().toUpperCase();
|
|
416
|
+
if (status.includes("|")) {
|
|
417
|
+
return { valid: false, reason: `Specification status is a template list. Must be explicitly set to '${requiredStatus}'.`, tier };
|
|
418
|
+
}
|
|
419
|
+
if (status !== requiredStatus) {
|
|
420
|
+
return { valid: false, reason: `Specification status is '${status}', but must be '${requiredStatus}' to proceed.`, tier };
|
|
421
|
+
}
|
|
422
|
+
if (requiredStatus === "DRAFT") {
|
|
423
|
+
if (tier !== "deep") {
|
|
424
|
+
return { valid: false, reason: "Executable SDD discovery requires a [DEEP] specification.", tier };
|
|
379
425
|
}
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
return { blocked: false, branch: currentBranch, recovered: false };
|
|
426
|
+
if (!/^\s*-\s*\[\s\]\s+\S+/m.test(content)) {
|
|
427
|
+
return { valid: false, reason: "DRAFT specification must contain at least one concrete unchecked acceptance criterion.", tier };
|
|
383
428
|
}
|
|
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 };
|
|
429
|
+
if (/(?:\$\{[^}]+\}|<[^>\n]+>|\b(?:TODO|TBD|PLACEHOLDER)\b)/i.test(content)) {
|
|
430
|
+
return { valid: false, reason: "DRAFT specification contains unresolved template placeholders.", tier };
|
|
389
431
|
}
|
|
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
432
|
}
|
|
433
|
+
return { valid: true, tier };
|
|
434
|
+
}
|
|
435
|
+
validateDraftContent(content) {
|
|
436
|
+
return this.validateContent(content, "DRAFT");
|
|
404
437
|
}
|
|
405
|
-
};
|
|
406
|
-
|
|
407
|
-
// src/core/sdd/validator.ts
|
|
408
|
-
import fs2 from "fs/promises";
|
|
409
|
-
var SpecValidator = class {
|
|
410
438
|
/**
|
|
411
439
|
* Validates a specification file.
|
|
412
440
|
*/
|
|
413
441
|
async validate(filePath) {
|
|
414
442
|
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 };
|
|
443
|
+
const content = await fs.readFile(filePath, "utf8");
|
|
444
|
+
return this.validateContent(content, "APPROVED");
|
|
442
445
|
} catch (error) {
|
|
443
446
|
if (error.code === "ENOENT") {
|
|
444
447
|
return { valid: false, reason: `Specification file not found: ${filePath}` };
|
|
@@ -449,9 +452,9 @@ var SpecValidator = class {
|
|
|
449
452
|
};
|
|
450
453
|
|
|
451
454
|
// src/core/handoff/handoff-engine.ts
|
|
452
|
-
import { execSync as
|
|
453
|
-
import
|
|
454
|
-
import
|
|
455
|
+
import { execSync as execSync2 } from "child_process";
|
|
456
|
+
import fs2 from "fs/promises";
|
|
457
|
+
import path from "path";
|
|
455
458
|
var HandoffEngine = class {
|
|
456
459
|
cwd;
|
|
457
460
|
constructor({ cwd }) {
|
|
@@ -464,9 +467,9 @@ var HandoffEngine = class {
|
|
|
464
467
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
465
468
|
let specsContent = "";
|
|
466
469
|
for (const specPath of specPaths) {
|
|
467
|
-
const absolutePath =
|
|
470
|
+
const absolutePath = path.isAbsolute(specPath) ? specPath : path.join(this.cwd, specPath);
|
|
468
471
|
try {
|
|
469
|
-
const content = await
|
|
472
|
+
const content = await fs2.readFile(absolutePath, "utf8");
|
|
470
473
|
specsContent += `### File: ${specPath}
|
|
471
474
|
|
|
472
475
|
${content}
|
|
@@ -482,7 +485,7 @@ ${content}
|
|
|
482
485
|
}
|
|
483
486
|
let gitDiff = "No changes detected.";
|
|
484
487
|
try {
|
|
485
|
-
gitDiff =
|
|
488
|
+
gitDiff = execSync2("git diff HEAD", { cwd: this.cwd, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim() || "No changes detected.";
|
|
486
489
|
} catch (err) {
|
|
487
490
|
gitDiff = `[Error capturing diff: ${err.message}]`;
|
|
488
491
|
}
|
|
@@ -493,9 +496,9 @@ ${content}
|
|
|
493
496
|
evidenceJson = JSON.stringify(data, null, 2);
|
|
494
497
|
evidenceSummary = `Status: ${data.status || data.internalStatus || "unknown"}, Commands: ${data.commands?.length || 0}`;
|
|
495
498
|
} else {
|
|
496
|
-
const evidencePath =
|
|
499
|
+
const evidencePath = path.join(this.cwd, "EVIDENCE.json");
|
|
497
500
|
try {
|
|
498
|
-
const content = await
|
|
501
|
+
const content = await fs2.readFile(evidencePath, "utf8");
|
|
499
502
|
const data = JSON.parse(content);
|
|
500
503
|
evidenceJson = JSON.stringify(data, null, 2);
|
|
501
504
|
evidenceSummary = `Status: ${data.status}, Commands: ${data.commands?.length || 0}`;
|
|
@@ -503,16 +506,16 @@ ${content}
|
|
|
503
506
|
}
|
|
504
507
|
}
|
|
505
508
|
const possibleTemplatePaths = [
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
509
|
+
path.join(this.cwd, ".ai-workflow/templates/HANDOFF.template.md"),
|
|
510
|
+
path.join(this.cwd, ".ai-workflow/harness/handoffs/HANDOFF.template.md"),
|
|
511
|
+
path.join(getPackageRoot(), "dist-assets/templates/HANDOFF.template.md"),
|
|
512
|
+
path.join(getPackageRoot(), "dist-assets/harness/handoffs/HANDOFF.template.md")
|
|
510
513
|
];
|
|
511
514
|
let template = null;
|
|
512
515
|
let lastError = null;
|
|
513
516
|
for (const tPath of possibleTemplatePaths) {
|
|
514
517
|
try {
|
|
515
|
-
template = await
|
|
518
|
+
template = await fs2.readFile(tPath, "utf8");
|
|
516
519
|
break;
|
|
517
520
|
} catch (err) {
|
|
518
521
|
lastError = err;
|
|
@@ -524,18 +527,18 @@ ${possibleTemplatePaths.join("\n")}
|
|
|
524
527
|
Last error: ${lastError?.message}`);
|
|
525
528
|
}
|
|
526
529
|
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
|
|
530
|
+
const handoffDir = path.join(this.cwd, ".ai-workflow/handoffs");
|
|
531
|
+
await fs2.mkdir(handoffDir, { recursive: true });
|
|
529
532
|
const packetName = `HANDOFF-${taskId || "TMP"}-${Date.now()}.md`;
|
|
530
|
-
const packetPath =
|
|
531
|
-
await
|
|
533
|
+
const packetPath = path.join(handoffDir, packetName);
|
|
534
|
+
await fs2.writeFile(packetPath, packet);
|
|
532
535
|
return packetPath;
|
|
533
536
|
}
|
|
534
537
|
};
|
|
535
538
|
|
|
536
539
|
// src/core/healing/healer-engine.ts
|
|
537
|
-
import
|
|
538
|
-
import
|
|
540
|
+
import fs3 from "fs/promises";
|
|
541
|
+
import path2 from "path";
|
|
539
542
|
import crypto from "crypto";
|
|
540
543
|
|
|
541
544
|
// src/core/statuses.ts
|
|
@@ -608,7 +611,7 @@ var HealerEngine = class {
|
|
|
608
611
|
this.ledger = ledger;
|
|
609
612
|
}
|
|
610
613
|
get statePath() {
|
|
611
|
-
return
|
|
614
|
+
return path2.join(this.cwd, ".ai-workflow", `healing-state-${this.taskSlug}.json`);
|
|
612
615
|
}
|
|
613
616
|
fingerprint(result) {
|
|
614
617
|
return crypto.createHash("sha256").update(stableJson(collectBlockingFindings(result))).digest("hex");
|
|
@@ -628,18 +631,18 @@ var HealerEngine = class {
|
|
|
628
631
|
return false;
|
|
629
632
|
}
|
|
630
633
|
async writeState(state) {
|
|
631
|
-
await
|
|
632
|
-
await
|
|
634
|
+
await fs3.mkdir(path2.dirname(this.statePath), { recursive: true });
|
|
635
|
+
await fs3.writeFile(this.statePath, JSON.stringify(state, null, 2));
|
|
633
636
|
}
|
|
634
637
|
async resetState() {
|
|
635
638
|
try {
|
|
636
|
-
await
|
|
639
|
+
await fs3.unlink(this.statePath);
|
|
637
640
|
} catch {
|
|
638
641
|
}
|
|
639
642
|
}
|
|
640
643
|
async writeRemediationRequest({ attempt, result }) {
|
|
641
|
-
const requestPath =
|
|
642
|
-
await
|
|
644
|
+
const requestPath = path2.join(this.cwd, ".ai-workflow", "remediation-request.json");
|
|
645
|
+
await fs3.mkdir(path2.dirname(requestPath), { recursive: true });
|
|
643
646
|
const request = {
|
|
644
647
|
taskSlug: this.taskSlug,
|
|
645
648
|
executionMode: this.mode,
|
|
@@ -650,7 +653,7 @@ var HealerEngine = class {
|
|
|
650
653
|
findingsFingerprint: this.fingerprint(result),
|
|
651
654
|
instruction: "Apply the smallest safe correction for the listed gates, then rerun validation. Do not broaden scope."
|
|
652
655
|
};
|
|
653
|
-
await
|
|
656
|
+
await fs3.writeFile(requestPath, JSON.stringify(request, null, 2));
|
|
654
657
|
return requestPath;
|
|
655
658
|
}
|
|
656
659
|
async run({
|
|
@@ -756,601 +759,166 @@ var HealerEngine = class {
|
|
|
756
759
|
}
|
|
757
760
|
};
|
|
758
761
|
|
|
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 {
|
|
762
|
+
// src/core/evidence/evidence-ledger.ts
|
|
763
|
+
import fs4 from "fs/promises";
|
|
764
|
+
import path3 from "path";
|
|
765
|
+
import { createHash } from "crypto";
|
|
766
|
+
var EvidenceLedger = class {
|
|
835
767
|
cwd;
|
|
836
|
-
|
|
768
|
+
workflowId;
|
|
769
|
+
events;
|
|
770
|
+
loadedPath;
|
|
771
|
+
loadedContentHash;
|
|
772
|
+
constructor({ cwd = process.cwd(), workflowId = "unknown" } = {}) {
|
|
837
773
|
this.cwd = cwd;
|
|
774
|
+
this.workflowId = workflowId;
|
|
775
|
+
this.events = [];
|
|
776
|
+
this.loadedPath = null;
|
|
777
|
+
this.loadedContentHash = null;
|
|
838
778
|
}
|
|
839
779
|
/**
|
|
840
|
-
*
|
|
780
|
+
* Logs a workflow event to the ledger.
|
|
841
781
|
*/
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
782
|
+
logEvent({
|
|
783
|
+
actor,
|
|
784
|
+
eventType,
|
|
785
|
+
provenance,
|
|
786
|
+
data = {},
|
|
787
|
+
actorType,
|
|
788
|
+
observed,
|
|
789
|
+
runtime
|
|
790
|
+
}) {
|
|
791
|
+
const uniqueTerminalEvents = /* @__PURE__ */ new Set(["specification_complete", "planning_complete", "implementation_complete"]);
|
|
792
|
+
if (uniqueTerminalEvents.has(eventType) && this.events.some((event2) => event2.eventType === eventType)) {
|
|
793
|
+
throw new Error(`Conflicting ledger record: terminal event '${eventType}' already exists for workflow '${this.workflowId}'.`);
|
|
846
794
|
}
|
|
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
|
|
795
|
+
const event = {
|
|
796
|
+
workflowId: this.workflowId,
|
|
797
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
798
|
+
actor,
|
|
799
|
+
actorType: actorType !== void 0 ? actorType : void 0,
|
|
800
|
+
observed: observed !== void 0 ? observed : void 0,
|
|
801
|
+
runtime: runtime !== void 0 ? runtime : void 0,
|
|
802
|
+
eventType,
|
|
803
|
+
provenance,
|
|
804
|
+
data
|
|
866
805
|
};
|
|
806
|
+
this.events.push(event);
|
|
807
|
+
console.log(`[LEDGER] [${actor}] ${eventType}: ${JSON.stringify(data)}`);
|
|
808
|
+
return event;
|
|
867
809
|
}
|
|
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;
|
|
810
|
+
getEvents() {
|
|
811
|
+
return this.events;
|
|
812
|
+
}
|
|
813
|
+
/**
|
|
814
|
+
* Saves the ledger events to a JSON file.
|
|
815
|
+
*/
|
|
816
|
+
async save(filePath) {
|
|
817
|
+
const targetPath = path3.isAbsolute(filePath) ? filePath : path3.join(this.cwd, filePath);
|
|
818
|
+
await fs4.mkdir(path3.dirname(targetPath), { recursive: true });
|
|
819
|
+
for (const event of this.events) {
|
|
820
|
+
if (event.workflowId !== this.workflowId) {
|
|
821
|
+
throw new Error(`Ledger workflowId conflict: '${event.workflowId}' does not match '${this.workflowId}'.`);
|
|
885
822
|
}
|
|
886
823
|
}
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
} else if (
|
|
896
|
-
|
|
897
|
-
} else if (readOnlyPatterns.test(text) || ["discover", "validate"].includes(operationType)) {
|
|
898
|
-
mutationIntent = "readonly";
|
|
824
|
+
const serialized = JSON.stringify(this.events, null, 2);
|
|
825
|
+
const contentHash = createHash("sha256").update(serialized).digest("hex");
|
|
826
|
+
const targetExists = await fs4.access(targetPath).then(() => true).catch(() => false);
|
|
827
|
+
if (targetExists) {
|
|
828
|
+
const diskHash = createHash("sha256").update(await fs4.readFile(targetPath)).digest("hex");
|
|
829
|
+
if (this.loadedPath !== targetPath || this.loadedContentHash === null || diskHash !== this.loadedContentHash) {
|
|
830
|
+
throw new Error(`Ledger changed on disk or was not loaded before update: '${targetPath}'.`);
|
|
831
|
+
}
|
|
832
|
+
} else if (this.loadedPath === targetPath && this.loadedContentHash !== null) {
|
|
833
|
+
throw new Error(`Ledger was removed after it was loaded: '${targetPath}'.`);
|
|
899
834
|
}
|
|
900
|
-
|
|
901
|
-
|
|
835
|
+
const temporaryPath = `${targetPath}.${process.pid}.${Date.now()}.tmp`;
|
|
836
|
+
try {
|
|
837
|
+
await fs4.writeFile(temporaryPath, serialized, { encoding: "utf8", flag: "wx" });
|
|
838
|
+
await fs4.rename(temporaryPath, targetPath);
|
|
839
|
+
this.loadedPath = targetPath;
|
|
840
|
+
this.loadedContentHash = contentHash;
|
|
841
|
+
} catch (error) {
|
|
842
|
+
await fs4.rm(temporaryPath, { force: true }).catch(() => {
|
|
843
|
+
});
|
|
844
|
+
throw error;
|
|
902
845
|
}
|
|
903
|
-
return mutationIntent;
|
|
904
846
|
}
|
|
905
|
-
|
|
906
|
-
const
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
847
|
+
async load(filePath) {
|
|
848
|
+
const targetPath = path3.isAbsolute(filePath) ? filePath : path3.join(this.cwd, filePath);
|
|
849
|
+
try {
|
|
850
|
+
const content = await fs4.readFile(targetPath, "utf8");
|
|
851
|
+
const parsed = JSON.parse(content);
|
|
852
|
+
if (!Array.isArray(parsed) || parsed.some((event) => event.workflowId !== this.workflowId)) {
|
|
853
|
+
throw new Error(`Ledger is incompatible with workflow '${this.workflowId}'.`);
|
|
911
854
|
}
|
|
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);
|
|
855
|
+
this.events = parsed;
|
|
856
|
+
this.loadedPath = targetPath;
|
|
857
|
+
this.loadedContentHash = createHash("sha256").update(content).digest("hex");
|
|
858
|
+
return true;
|
|
859
|
+
} catch (error) {
|
|
860
|
+
if (error.code === "ENOENT") {
|
|
861
|
+
this.loadedPath = targetPath;
|
|
862
|
+
this.loadedContentHash = null;
|
|
863
|
+
return false;
|
|
961
864
|
}
|
|
865
|
+
throw error;
|
|
962
866
|
}
|
|
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
867
|
}
|
|
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");
|
|
997
|
-
}
|
|
998
|
-
if (/\b(bypass|force|direct)\b/i.test(text)) {
|
|
999
|
-
safetyConstraints.push("Block bypass attempts");
|
|
1000
|
-
}
|
|
1001
|
-
if (/\b(required evidence|evidence required)\b/i.test(text)) {
|
|
1002
|
-
requiredEvidence.push("user-required-evidence");
|
|
868
|
+
async verifyRoleConfinement() {
|
|
869
|
+
const violations = [];
|
|
870
|
+
const events = this.events;
|
|
871
|
+
const atlasImplements = events.some((e) => e.actor === "Atlas" && (e.eventType === "implementation_start" || e.eventType === "implementation_complete"));
|
|
872
|
+
if (atlasImplements) {
|
|
873
|
+
violations.push("Atlas cannot implement directly");
|
|
1003
874
|
}
|
|
1004
|
-
|
|
1005
|
-
|
|
875
|
+
const nexusMutates = events.some((e) => e.actor === "Nexus" && e.eventType === "file_mutate");
|
|
876
|
+
if (nexusMutates) {
|
|
877
|
+
violations.push("Nexus cannot mutate files during discovery");
|
|
1006
878
|
}
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
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
|
-
};
|
|
879
|
+
const sageValidates = events.some((e) => e.actor === "Sage" && (e.eventType === "validation_start" || e.eventType === "validation_complete"));
|
|
880
|
+
const hasFidelity = events.some((e) => e.eventType === "fidelity_check" || e.eventType === "fidelity_verification");
|
|
881
|
+
if (sageValidates && !hasFidelity) {
|
|
882
|
+
violations.push("Sage cannot validate without fidelity evidence");
|
|
1025
883
|
}
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
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.";
|
|
884
|
+
const phoenixRemediates = events.find((e) => e.actor === "Phoenix" && e.eventType === "remediation_start");
|
|
885
|
+
if (phoenixRemediates) {
|
|
886
|
+
const findings = phoenixRemediates.data?.findings;
|
|
887
|
+
if (!findings || findings.length === 0) {
|
|
888
|
+
violations.push("Phoenix cannot perform remediation without findings");
|
|
1043
889
|
}
|
|
1044
890
|
}
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
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
|
-
});
|
|
891
|
+
const orionReleases = events.find((e) => e.actor === "Orion" && e.eventType === "release_start");
|
|
892
|
+
if (orionReleases) {
|
|
893
|
+
const approvals = orionReleases.data?.approvals;
|
|
894
|
+
if (!approvals || approvals.length === 0) {
|
|
895
|
+
violations.push("Orion cannot release without explicit approval");
|
|
896
|
+
}
|
|
1064
897
|
}
|
|
898
|
+
const passed = violations.length === 0;
|
|
1065
899
|
return {
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
permissionDecision,
|
|
1071
|
-
reason,
|
|
1072
|
-
workflowPath
|
|
900
|
+
passed,
|
|
901
|
+
status: passed ? "PASS" : "BLOCKED",
|
|
902
|
+
reason: violations.length > 0 ? `Role violations: ${violations.join("; ")}` : "Verification passed",
|
|
903
|
+
violations
|
|
1073
904
|
};
|
|
1074
905
|
}
|
|
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
906
|
};
|
|
1095
907
|
|
|
1096
|
-
// src/core/
|
|
1097
|
-
var
|
|
908
|
+
// src/core/delegation-controller.ts
|
|
909
|
+
var DelegationController = class {
|
|
910
|
+
cwd;
|
|
911
|
+
ledger;
|
|
912
|
+
adapter;
|
|
913
|
+
lastRoutingDecision;
|
|
914
|
+
constructor({ cwd = process.cwd(), ledger, adapter } = {}) {
|
|
915
|
+
this.cwd = cwd;
|
|
916
|
+
this.ledger = ledger;
|
|
917
|
+
this.adapter = adapter || new OpenCodeAdapter({ cwd });
|
|
918
|
+
this.lastRoutingDecision = null;
|
|
919
|
+
}
|
|
1098
920
|
/**
|
|
1099
|
-
*
|
|
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");
|
|
1315
|
-
}
|
|
1316
|
-
const phoenixRemediates = events.find((e) => e.actor === "Phoenix" && e.eventType === "remediation_start");
|
|
1317
|
-
if (phoenixRemediates) {
|
|
1318
|
-
const findings = phoenixRemediates.data?.findings;
|
|
1319
|
-
if (!findings || findings.length === 0) {
|
|
1320
|
-
violations.push("Phoenix cannot perform remediation without findings");
|
|
1321
|
-
}
|
|
1322
|
-
}
|
|
1323
|
-
const orionReleases = events.find((e) => e.actor === "Orion" && e.eventType === "release_start");
|
|
1324
|
-
if (orionReleases) {
|
|
1325
|
-
const approvals = orionReleases.data?.approvals;
|
|
1326
|
-
if (!approvals || approvals.length === 0) {
|
|
1327
|
-
violations.push("Orion cannot release without explicit approval");
|
|
1328
|
-
}
|
|
1329
|
-
}
|
|
1330
|
-
const passed = violations.length === 0;
|
|
1331
|
-
return {
|
|
1332
|
-
passed,
|
|
1333
|
-
status: passed ? "PASS" : "BLOCKED",
|
|
1334
|
-
reason: violations.length > 0 ? `Role violations: ${violations.join("; ")}` : "Verification passed",
|
|
1335
|
-
violations
|
|
1336
|
-
};
|
|
1337
|
-
}
|
|
1338
|
-
};
|
|
1339
|
-
|
|
1340
|
-
// src/core/delegation-controller.ts
|
|
1341
|
-
var DelegationController = class {
|
|
1342
|
-
cwd;
|
|
1343
|
-
ledger;
|
|
1344
|
-
adapter;
|
|
1345
|
-
lastRoutingDecision;
|
|
1346
|
-
constructor({ cwd = process.cwd(), ledger, adapter } = {}) {
|
|
1347
|
-
this.cwd = cwd;
|
|
1348
|
-
this.ledger = ledger;
|
|
1349
|
-
this.adapter = adapter || new OpenCodeAdapter({ cwd });
|
|
1350
|
-
this.lastRoutingDecision = null;
|
|
1351
|
-
}
|
|
1352
|
-
/**
|
|
1353
|
-
* Logs routing decision by Atlas.
|
|
921
|
+
* Logs routing decision by Atlas.
|
|
1354
922
|
*/
|
|
1355
923
|
async route(naturalRequest, classification) {
|
|
1356
924
|
const decision = classification.routingDecision ?? {
|
|
@@ -1387,6 +955,104 @@ var DelegationController = class {
|
|
|
1387
955
|
});
|
|
1388
956
|
}
|
|
1389
957
|
}
|
|
958
|
+
/**
|
|
959
|
+
* Executes one high-risk phase and fails closed unless the runtime confirms
|
|
960
|
+
* the requested actor through machine-readable output.
|
|
961
|
+
*/
|
|
962
|
+
async executePhase({
|
|
963
|
+
phase,
|
|
964
|
+
actor,
|
|
965
|
+
prompt,
|
|
966
|
+
readOnly,
|
|
967
|
+
requireExplicitActor = true,
|
|
968
|
+
onOutput
|
|
969
|
+
}) {
|
|
970
|
+
const inspection = await this.adapter.inspect();
|
|
971
|
+
const supported = inspection.available && inspection.supports?.agent === true;
|
|
972
|
+
const startType = `${phase}_start`;
|
|
973
|
+
const completeType = `${phase}_complete`;
|
|
974
|
+
this.ledger?.logEvent({
|
|
975
|
+
actor: supported ? actor : "Atlas",
|
|
976
|
+
actorType: supported ? "runtime-agent" : "control-plane",
|
|
977
|
+
observed: true,
|
|
978
|
+
runtime: "opencode",
|
|
979
|
+
eventType: startType,
|
|
980
|
+
provenance: "opencode-adapter",
|
|
981
|
+
data: {
|
|
982
|
+
phase,
|
|
983
|
+
runtimeAgentRequested: actor,
|
|
984
|
+
runtimeAgentApplied: null,
|
|
985
|
+
runtimeAgentSupported: supported,
|
|
986
|
+
runtimeAgentSelectionMode: supported ? "explicit" : inspection.available ? "prompt-fallback" : "degraded",
|
|
987
|
+
runtimeActorConfirmation: "unavailable",
|
|
988
|
+
readOnly,
|
|
989
|
+
status: "started"
|
|
990
|
+
}
|
|
991
|
+
});
|
|
992
|
+
let runResult;
|
|
993
|
+
if (requireExplicitActor && !supported) {
|
|
994
|
+
runResult = {
|
|
995
|
+
success: false,
|
|
996
|
+
status: "BLOCKED",
|
|
997
|
+
error: "Runtime cannot explicitly select and confirm the requested actor.",
|
|
998
|
+
commandsRun: [],
|
|
999
|
+
eventCount: 0,
|
|
1000
|
+
output: "",
|
|
1001
|
+
runtimeAgentRequested: actor,
|
|
1002
|
+
runtimeAgentApplied: null,
|
|
1003
|
+
runtimeAgentSupported: false,
|
|
1004
|
+
runtimeAgentSelectionMode: inspection.available ? "prompt-fallback" : "degraded",
|
|
1005
|
+
runtimeActorConfirmation: "unavailable"
|
|
1006
|
+
};
|
|
1007
|
+
} else {
|
|
1008
|
+
runResult = await this.adapter.execute(prompt, { agent: actor, readOnly, requireActorConfirmation: true });
|
|
1009
|
+
}
|
|
1010
|
+
if (runResult.success && requireExplicitActor) {
|
|
1011
|
+
if (!["explicit", "sdk-session"].includes(runResult.runtimeAgentSelectionMode || "") || runResult.runtimeAgentApplied !== actor || runResult.runtimeActorConfirmation !== "confirmed") {
|
|
1012
|
+
runResult = {
|
|
1013
|
+
...runResult,
|
|
1014
|
+
success: false,
|
|
1015
|
+
status: "BLOCKED",
|
|
1016
|
+
error: runResult.runtimeAgentApplied ? `Runtime confirmed actor '${runResult.runtimeAgentApplied}', expected '${actor}'.` : `Runtime did not provide machine-readable confirmation for actor '${actor}'.`
|
|
1017
|
+
};
|
|
1018
|
+
}
|
|
1019
|
+
}
|
|
1020
|
+
if (runResult.success && onOutput) {
|
|
1021
|
+
try {
|
|
1022
|
+
const artifact = await onOutput(runResult.output || "");
|
|
1023
|
+
if (artifact) runResult.artifact = artifact;
|
|
1024
|
+
} catch (error) {
|
|
1025
|
+
runResult = {
|
|
1026
|
+
...runResult,
|
|
1027
|
+
success: false,
|
|
1028
|
+
status: "BLOCKED",
|
|
1029
|
+
error: error.message
|
|
1030
|
+
};
|
|
1031
|
+
}
|
|
1032
|
+
}
|
|
1033
|
+
this.ledger?.logEvent({
|
|
1034
|
+
actor: runResult.runtimeAgentApplied === actor ? actor : "Atlas",
|
|
1035
|
+
actorType: runResult.runtimeAgentApplied === actor ? "runtime-agent" : "control-plane",
|
|
1036
|
+
observed: true,
|
|
1037
|
+
runtime: "opencode",
|
|
1038
|
+
eventType: completeType,
|
|
1039
|
+
provenance: "opencode-adapter",
|
|
1040
|
+
data: {
|
|
1041
|
+
phase,
|
|
1042
|
+
success: runResult.success,
|
|
1043
|
+
error: runResult.error || null,
|
|
1044
|
+
runtimeAgentRequested: runResult.runtimeAgentRequested || actor,
|
|
1045
|
+
runtimeAgentApplied: runResult.runtimeAgentApplied || null,
|
|
1046
|
+
runtimeAgentSupported: runResult.runtimeAgentSupported === true,
|
|
1047
|
+
runtimeAgentSelectionMode: runResult.runtimeAgentSelectionMode || "unsupported",
|
|
1048
|
+
runtimeActorConfirmation: runResult.runtimeActorConfirmation || "unavailable",
|
|
1049
|
+
artifactPath: runResult.artifact?.artifactPath || null,
|
|
1050
|
+
artifactHash: runResult.artifact?.artifactHash || null,
|
|
1051
|
+
status: runResult.success ? "COMPLETED" : "BLOCKED"
|
|
1052
|
+
}
|
|
1053
|
+
});
|
|
1054
|
+
return runResult;
|
|
1055
|
+
}
|
|
1390
1056
|
/**
|
|
1391
1057
|
* Logs and executes the implementation process by Astra.
|
|
1392
1058
|
*/
|
|
@@ -1500,8 +1166,8 @@ var DelegationController = class {
|
|
|
1500
1166
|
}
|
|
1501
1167
|
let gitDiff = "";
|
|
1502
1168
|
try {
|
|
1503
|
-
const { execSync:
|
|
1504
|
-
gitDiff =
|
|
1169
|
+
const { execSync: execSync5 } = await import("child_process");
|
|
1170
|
+
gitDiff = execSync5("git diff HEAD", { cwd: this.cwd, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim();
|
|
1505
1171
|
} catch {
|
|
1506
1172
|
}
|
|
1507
1173
|
const prompt = `Please validate the implementation. Diff:
|
|
@@ -1545,10 +1211,10 @@ ${JSON.stringify(result, null, 2)}`;
|
|
|
1545
1211
|
};
|
|
1546
1212
|
|
|
1547
1213
|
// src/core/finalization/workspace-snapshot.ts
|
|
1548
|
-
import
|
|
1549
|
-
import
|
|
1214
|
+
import fs5 from "fs/promises";
|
|
1215
|
+
import path4 from "path";
|
|
1550
1216
|
import crypto2 from "crypto";
|
|
1551
|
-
import { execSync as
|
|
1217
|
+
import { execSync as execSync3 } from "child_process";
|
|
1552
1218
|
var WorkspaceSnapshot = class {
|
|
1553
1219
|
cwd;
|
|
1554
1220
|
fastTrack;
|
|
@@ -1575,7 +1241,7 @@ var WorkspaceSnapshot = class {
|
|
|
1575
1241
|
}
|
|
1576
1242
|
const execGit = (args) => {
|
|
1577
1243
|
try {
|
|
1578
|
-
return
|
|
1244
|
+
return execSync3(`git ${args}`, { cwd: this.cwd, encoding: "utf8", stdio: "pipe" }).trim();
|
|
1579
1245
|
} catch {
|
|
1580
1246
|
return "";
|
|
1581
1247
|
}
|
|
@@ -1601,10 +1267,10 @@ var WorkspaceSnapshot = class {
|
|
|
1601
1267
|
}
|
|
1602
1268
|
async scanDir(dir, filesMap) {
|
|
1603
1269
|
try {
|
|
1604
|
-
const entries = await
|
|
1270
|
+
const entries = await fs5.readdir(dir, { withFileTypes: true });
|
|
1605
1271
|
for (const entry of entries) {
|
|
1606
|
-
const fullPath =
|
|
1607
|
-
const relativePath =
|
|
1272
|
+
const fullPath = path4.join(dir, entry.name);
|
|
1273
|
+
const relativePath = path4.relative(this.cwd, fullPath);
|
|
1608
1274
|
if (this.isIgnored(relativePath)) {
|
|
1609
1275
|
continue;
|
|
1610
1276
|
}
|
|
@@ -1612,8 +1278,8 @@ var WorkspaceSnapshot = class {
|
|
|
1612
1278
|
await this.scanDir(fullPath, filesMap);
|
|
1613
1279
|
} else if (entry.isFile()) {
|
|
1614
1280
|
try {
|
|
1615
|
-
const stat = await
|
|
1616
|
-
const content = await
|
|
1281
|
+
const stat = await fs5.stat(fullPath);
|
|
1282
|
+
const content = await fs5.readFile(fullPath);
|
|
1617
1283
|
const sha256 = crypto2.createHash("sha256").update(content).digest("hex");
|
|
1618
1284
|
const isExecutable = (stat.mode & 73) !== 0;
|
|
1619
1285
|
const mode = isExecutable ? "100755" : "100644";
|
|
@@ -1647,9 +1313,9 @@ var WorkspaceSnapshot = class {
|
|
|
1647
1313
|
};
|
|
1648
1314
|
|
|
1649
1315
|
// src/core/finalization/finalizer.ts
|
|
1650
|
-
import { execSync as
|
|
1651
|
-
import
|
|
1652
|
-
import
|
|
1316
|
+
import { execSync as execSync4 } from "child_process";
|
|
1317
|
+
import path5 from "path";
|
|
1318
|
+
import fs6 from "fs/promises";
|
|
1653
1319
|
var Finalizer = class {
|
|
1654
1320
|
cwd;
|
|
1655
1321
|
snapshotManager;
|
|
@@ -1662,9 +1328,9 @@ var Finalizer = class {
|
|
|
1662
1328
|
getChangedFilesFromGit() {
|
|
1663
1329
|
try {
|
|
1664
1330
|
const files = /* @__PURE__ */ new Set();
|
|
1665
|
-
const diff =
|
|
1331
|
+
const diff = execSync4("git diff --name-only HEAD", { cwd: this.cwd, encoding: "utf8" });
|
|
1666
1332
|
diff.split("\n").map((f) => f.trim()).filter(Boolean).forEach((f) => files.add(f));
|
|
1667
|
-
const status =
|
|
1333
|
+
const status = execSync4("git status --short --untracked-files=all", { cwd: this.cwd, encoding: "utf8" });
|
|
1668
1334
|
status.split("\n").forEach((line) => {
|
|
1669
1335
|
const match = line.match(/^(?:[ MADRCU?!]{2}\s+|[MADRCU?!]\s+)(.+)$/);
|
|
1670
1336
|
if (match) {
|
|
@@ -1722,10 +1388,10 @@ var Finalizer = class {
|
|
|
1722
1388
|
const verifyResult = await fidelityGate.verify(filesToCheck);
|
|
1723
1389
|
let passed = requestFidelity.passed && verifyResult.passed;
|
|
1724
1390
|
let reason = !requestFidelity.passed ? requestFidelity.reason : verifyResult.reason;
|
|
1725
|
-
const evidencePath =
|
|
1726
|
-
const hasEvidence = await
|
|
1391
|
+
const evidencePath = path5.join(this.cwd, "EVIDENCE.json");
|
|
1392
|
+
const hasEvidence = await fs6.access(evidencePath).then(() => true).catch(() => false);
|
|
1727
1393
|
if (hasEvidence) {
|
|
1728
|
-
const { EvidenceValidator } = await import("./evidence-validator-
|
|
1394
|
+
const { EvidenceValidator } = await import("./evidence-validator-HS3NTWAB.js");
|
|
1729
1395
|
const isTest = process.env.NODE_ENV === "test" || process.env.VITEST === "true";
|
|
1730
1396
|
const evidenceValidator = new EvidenceValidator({ cwd: this.cwd, skipFileCheck: isTest, skipGitCheck: isTest });
|
|
1731
1397
|
const valResult = await evidenceValidator.validate();
|
|
@@ -1736,72 +1402,564 @@ var Finalizer = class {
|
|
|
1736
1402
|
}
|
|
1737
1403
|
}
|
|
1738
1404
|
return {
|
|
1739
|
-
passed,
|
|
1740
|
-
status: requestFidelity.status || (passed ? "PASS" : "BLOCKED"),
|
|
1405
|
+
passed,
|
|
1406
|
+
status: requestFidelity.status || (passed ? "PASS" : "BLOCKED"),
|
|
1407
|
+
reason,
|
|
1408
|
+
deliveryClass: actualDeliveryClass,
|
|
1409
|
+
requestFidelity
|
|
1410
|
+
};
|
|
1411
|
+
}
|
|
1412
|
+
/**
|
|
1413
|
+
* Compares the current workspace state with a previously captured snapshot.
|
|
1414
|
+
*/
|
|
1415
|
+
async verifyIntegrity(previousSnapshot) {
|
|
1416
|
+
const currentSnapshot = await this.snapshotManager.capture();
|
|
1417
|
+
const added = [];
|
|
1418
|
+
const modified = [];
|
|
1419
|
+
const deleted = [];
|
|
1420
|
+
let valid = true;
|
|
1421
|
+
if (previousSnapshot.branch !== currentSnapshot.branch) {
|
|
1422
|
+
modified.push(`branch:${previousSnapshot.branch}->${currentSnapshot.branch}`);
|
|
1423
|
+
valid = false;
|
|
1424
|
+
}
|
|
1425
|
+
if (previousSnapshot.head !== currentSnapshot.head) {
|
|
1426
|
+
modified.push(`head:${previousSnapshot.head}->${currentSnapshot.head}`);
|
|
1427
|
+
valid = false;
|
|
1428
|
+
}
|
|
1429
|
+
if (previousSnapshot.indexTree !== currentSnapshot.indexTree) {
|
|
1430
|
+
modified.push(`indexTree:${previousSnapshot.indexTree}->${currentSnapshot.indexTree}`);
|
|
1431
|
+
valid = false;
|
|
1432
|
+
}
|
|
1433
|
+
if (previousSnapshot.stagedDiffHash !== currentSnapshot.stagedDiffHash) {
|
|
1434
|
+
modified.push("stagedDiff");
|
|
1435
|
+
valid = false;
|
|
1436
|
+
}
|
|
1437
|
+
if (previousSnapshot.unstagedDiffHash !== currentSnapshot.unstagedDiffHash) {
|
|
1438
|
+
modified.push("unstagedDiff");
|
|
1439
|
+
valid = false;
|
|
1440
|
+
}
|
|
1441
|
+
if (previousSnapshot.untrackedFilesHash !== currentSnapshot.untrackedFilesHash) {
|
|
1442
|
+
modified.push("untrackedFiles");
|
|
1443
|
+
valid = false;
|
|
1444
|
+
}
|
|
1445
|
+
const prevFiles = previousSnapshot.files || {};
|
|
1446
|
+
const currFiles = currentSnapshot.files || {};
|
|
1447
|
+
for (const [file, info] of Object.entries(currFiles)) {
|
|
1448
|
+
const prevInfo = prevFiles[file];
|
|
1449
|
+
if (!prevInfo) {
|
|
1450
|
+
added.push(file);
|
|
1451
|
+
} else if (prevInfo.sha256 !== info.sha256 || prevInfo.mode !== info.mode) {
|
|
1452
|
+
modified.push(file);
|
|
1453
|
+
}
|
|
1454
|
+
}
|
|
1455
|
+
for (const file of Object.keys(prevFiles)) {
|
|
1456
|
+
if (!currFiles[file]) {
|
|
1457
|
+
deleted.push(file);
|
|
1458
|
+
}
|
|
1459
|
+
}
|
|
1460
|
+
if (added.length > 0 || modified.length > 0 || deleted.length > 0) {
|
|
1461
|
+
valid = false;
|
|
1462
|
+
}
|
|
1463
|
+
return {
|
|
1464
|
+
valid,
|
|
1465
|
+
changes: { added, modified, deleted }
|
|
1466
|
+
};
|
|
1467
|
+
}
|
|
1468
|
+
};
|
|
1469
|
+
|
|
1470
|
+
// src/core/workflow-profiles.ts
|
|
1471
|
+
var PROFILE_DEFINITIONS = Object.freeze({
|
|
1472
|
+
"frontend-product": Object.freeze({
|
|
1473
|
+
owner: "Astra",
|
|
1474
|
+
skills: ["frontend-development", "ui-ux-design", "frontend-design-system"],
|
|
1475
|
+
objective: "Deliver a user-facing product or marketing surface with truthful copy and deliberate visual composition.",
|
|
1476
|
+
requiredChecks: ["tests", "typecheck", "build", "responsive-render", "primary-interaction", "truthfulness"],
|
|
1477
|
+
forbiddenAssumptions: ["fixed SaaS section sequence", "mandatory pricing", "mandatory testimonials", "subjective premium score"]
|
|
1478
|
+
}),
|
|
1479
|
+
"frontend-utility": Object.freeze({
|
|
1480
|
+
owner: "Astra",
|
|
1481
|
+
skills: ["frontend-development", "ui-ux-design", "frontend-design-system"],
|
|
1482
|
+
objective: "Deliver a focused user tool with a complete primary flow and clear operational states.",
|
|
1483
|
+
requiredChecks: ["tests", "typecheck", "build", "responsive-render", "primary-interaction"],
|
|
1484
|
+
forbiddenAssumptions: ["marketing sections", "pricing", "testimonials", "commercial narrative"]
|
|
1485
|
+
}),
|
|
1486
|
+
"backend-api": Object.freeze({
|
|
1487
|
+
owner: "Astra",
|
|
1488
|
+
skills: ["backend-development"],
|
|
1489
|
+
objective: "Deliver a bounded backend/API change with validated contracts, errors, authorization, and tests.",
|
|
1490
|
+
requiredChecks: ["tests", "build-or-typecheck", "input-validation", "failure-paths"],
|
|
1491
|
+
forbiddenAssumptions: ["frontend deliverables", "visual evidence"]
|
|
1492
|
+
}),
|
|
1493
|
+
refactor: Object.freeze({
|
|
1494
|
+
owner: "Astra",
|
|
1495
|
+
skills: ["refactoring"],
|
|
1496
|
+
objective: "Improve structure without changing observable behavior outside the approved scope.",
|
|
1497
|
+
requiredChecks: ["behavior-preservation", "tests", "focused-diff"],
|
|
1498
|
+
forbiddenAssumptions: ["new product behavior", "unrelated cleanup"]
|
|
1499
|
+
}),
|
|
1500
|
+
documentation: Object.freeze({
|
|
1501
|
+
owner: "Astra",
|
|
1502
|
+
skills: ["documentation"],
|
|
1503
|
+
objective: "Create or update accurate documentation grounded in the current repository behavior.",
|
|
1504
|
+
requiredChecks: ["references", "paths", "commands", "consistency"],
|
|
1505
|
+
forbiddenAssumptions: ["implementation claims without evidence"]
|
|
1506
|
+
}),
|
|
1507
|
+
"security-review": Object.freeze({
|
|
1508
|
+
owner: "Sage",
|
|
1509
|
+
skills: ["qa-workflow", "technical-leadership"],
|
|
1510
|
+
objective: "Inspect security-relevant behavior and report evidence-ranked findings without implementing unrelated changes.",
|
|
1511
|
+
requiredChecks: ["evidence", "severity", "runtime-vs-dev", "remediation-owner"],
|
|
1512
|
+
forbiddenAssumptions: ["automatic force fixes", "unverified exploitability"]
|
|
1513
|
+
}),
|
|
1514
|
+
generic: Object.freeze({
|
|
1515
|
+
owner: "Atlas",
|
|
1516
|
+
skills: [],
|
|
1517
|
+
objective: "Route work without inventing domain requirements.",
|
|
1518
|
+
requiredChecks: ["scope", "owner", "mode"],
|
|
1519
|
+
forbiddenAssumptions: ["domain-specific structure"]
|
|
1520
|
+
})
|
|
1521
|
+
});
|
|
1522
|
+
var PROFILE_PATTERNS = Object.freeze([
|
|
1523
|
+
["security-review", /\b(?:security review|security audit|vulnerability audit|threat model)\b/i],
|
|
1524
|
+
["refactor", /\b(?:refactor|restructure|cleanup without behavior change|preserve behavior)\b/i],
|
|
1525
|
+
["documentation", /\b(?:documentation only|write docs|update readme|document the|documentation task)\b/i],
|
|
1526
|
+
["backend-api", /\b(?:api endpoint|backend api|rest api|graphql|controller|service endpoint|authenticated api)\b/i],
|
|
1527
|
+
["frontend-product", /\b(?:landing page|marketing page|product page|pricing page|homepage|redesign|brand page)\b/i],
|
|
1528
|
+
["frontend-utility", /\b(?:validator|search page|lookup|dashboard|form|calculator|tool|admin page|repository search)\b/i]
|
|
1529
|
+
]);
|
|
1530
|
+
function getWorkflowProfile(name = "generic") {
|
|
1531
|
+
return PROFILE_DEFINITIONS[name] || PROFILE_DEFINITIONS.generic;
|
|
1532
|
+
}
|
|
1533
|
+
function resolveWorkflowProfile({ request = "", explicitProfile = null } = {}) {
|
|
1534
|
+
if (explicitProfile && PROFILE_DEFINITIONS[explicitProfile]) return explicitProfile;
|
|
1535
|
+
const text = String(request).trim();
|
|
1536
|
+
for (const [profile, pattern] of PROFILE_PATTERNS) {
|
|
1537
|
+
if (pattern.test(text)) return profile;
|
|
1538
|
+
}
|
|
1539
|
+
return "generic";
|
|
1540
|
+
}
|
|
1541
|
+
|
|
1542
|
+
// src/core/request-router.ts
|
|
1543
|
+
import path6 from "path";
|
|
1544
|
+
import fs7 from "fs";
|
|
1545
|
+
var RequestRouter = class {
|
|
1546
|
+
cwd;
|
|
1547
|
+
constructor({ cwd = process.cwd() } = {}) {
|
|
1548
|
+
this.cwd = cwd;
|
|
1549
|
+
}
|
|
1550
|
+
/**
|
|
1551
|
+
* Understands and classifies a natural language request.
|
|
1552
|
+
*/
|
|
1553
|
+
understand(request) {
|
|
1554
|
+
const text = String(request).trim();
|
|
1555
|
+
if (!text) {
|
|
1556
|
+
throw new Error("Cannot classify an empty request.");
|
|
1557
|
+
}
|
|
1558
|
+
const { operationType, requestedCapability } = this.extractCapability(text);
|
|
1559
|
+
const mutationIntent = this.determineMutationIntent(text, operationType);
|
|
1560
|
+
const riskAssessment = this.determineRiskAssessment(text, mutationIntent);
|
|
1561
|
+
const riskLevel = riskAssessment.riskLevel;
|
|
1562
|
+
const { safetyConstraints, requiredEvidence } = this.determineSafetyAndEvidence(text, mutationIntent);
|
|
1563
|
+
return {
|
|
1564
|
+
rawRequest: text,
|
|
1565
|
+
taskGoal: `Perform ${operationType} operation under ${mutationIntent} intent`,
|
|
1566
|
+
requestedActor: void 0,
|
|
1567
|
+
invalidActor: void 0,
|
|
1568
|
+
requestedCapability,
|
|
1569
|
+
operationType,
|
|
1570
|
+
mutationIntent,
|
|
1571
|
+
riskLevel,
|
|
1572
|
+
requiredEvidence,
|
|
1573
|
+
safetyConstraints,
|
|
1574
|
+
specPolicy: riskAssessment.specPolicy,
|
|
1575
|
+
matchedSignals: riskAssessment.matchedSignals,
|
|
1576
|
+
riskDrivers: riskAssessment.riskDrivers
|
|
1577
|
+
};
|
|
1578
|
+
}
|
|
1579
|
+
extractCapability(text) {
|
|
1580
|
+
let operationType = "explain";
|
|
1581
|
+
let requestedCapability = void 0;
|
|
1582
|
+
const capabilityMappings = [
|
|
1583
|
+
{ op: "discover", pattern: /\b(discover|inspect|map|scan|structure|architecture|repo|search|find)\b/i },
|
|
1584
|
+
{ op: "validate", pattern: /\b(validate|audit|review|check|verify|test)\b/i },
|
|
1585
|
+
{ op: "implement", pattern: /\b(implement|implementa|build|create|crie|cria|add|develop|write|change|modify|atualiza|atualizar|refatora|refatorar)\b/i },
|
|
1586
|
+
{ op: "fix", pattern: /\b(fix|repair|correct|corrige|corrigir|heal|solve|remediate)\b/i },
|
|
1587
|
+
{ op: "release", pattern: /\b(release|tag|prepare release|prepara(?:r)?\s+(?:a\s+)?release)\b/i },
|
|
1588
|
+
{ op: "deploy", pattern: /\b(deploy|publish|push|produção|producao)\b/i }
|
|
1589
|
+
];
|
|
1590
|
+
for (const mapping of capabilityMappings) {
|
|
1591
|
+
const match = text.match(mapping.pattern);
|
|
1592
|
+
if (match) {
|
|
1593
|
+
operationType = mapping.op;
|
|
1594
|
+
requestedCapability = match[1];
|
|
1595
|
+
break;
|
|
1596
|
+
}
|
|
1597
|
+
}
|
|
1598
|
+
return { operationType, requestedCapability };
|
|
1599
|
+
}
|
|
1600
|
+
determineMutationIntent(text, operationType) {
|
|
1601
|
+
const writePatterns = /\b(implement|implementa|build|fix|corrige|write|change|modify|create|crie|cria|add|refactor|refatora|refatorar|update|atualiza|atualizar)\b/i;
|
|
1602
|
+
const readOnlyPatterns = /\b(analyze|check|inspect|review|find|search|show|list|read|view|verify|validate|discover|map)\b/i;
|
|
1603
|
+
let mutationIntent = "write";
|
|
1604
|
+
if (/\b(read-only|readonly)\b/i.test(text)) {
|
|
1605
|
+
mutationIntent = "readonly";
|
|
1606
|
+
} else if (writePatterns.test(text)) {
|
|
1607
|
+
mutationIntent = "write";
|
|
1608
|
+
} else if (readOnlyPatterns.test(text) || ["discover", "validate"].includes(operationType)) {
|
|
1609
|
+
mutationIntent = "readonly";
|
|
1610
|
+
}
|
|
1611
|
+
if (/\b(publish|release|deploy|produção|producao|push to npm|push to remote)\b/i.test(text)) {
|
|
1612
|
+
mutationIntent = "publish";
|
|
1613
|
+
}
|
|
1614
|
+
return mutationIntent;
|
|
1615
|
+
}
|
|
1616
|
+
determineRiskAssessment(text, mutationIntent) {
|
|
1617
|
+
const matchedSignals = [];
|
|
1618
|
+
const add = (signal, pattern) => {
|
|
1619
|
+
if (pattern.test(text)) {
|
|
1620
|
+
matchedSignals.push(signal);
|
|
1621
|
+
return true;
|
|
1622
|
+
}
|
|
1623
|
+
return false;
|
|
1624
|
+
};
|
|
1625
|
+
const taskTypeSignals = {
|
|
1626
|
+
docsCopy: add("task:docs-copy", /\b(README|docs?|documenta[cç][aã]o|typo|copy|frase|comment|coment[áa]rio)\b/i),
|
|
1627
|
+
refactor: add("task:refactor", /\b(refactor|refatora|refatorar|simplificar|mais simples de manter|manuten[cç][aã]o)\b/i),
|
|
1628
|
+
noBehaviorChange: add("scope:no-behavior-change", /\b(sem alterar (?:o )?comportamento|without changing behavior|without behaviour change|no behavior change|comportamento existente)\b/i),
|
|
1629
|
+
landingPage: add("task:landing-page", /\b(landing page|hotsite|p[áa]gina de venda|p[áa]gina comercial)\b/i),
|
|
1630
|
+
dashboard: add("task:dashboard", /\b(dashboard|painel)\b/i),
|
|
1631
|
+
bugfix: add("task:bugfix", /\b(bug|fix|corrige|corrigir|erro|falha|n[aã]o envia|não envia)\b/i),
|
|
1632
|
+
release: add("task:release", /\b(release|deploy|produ[cç][aã]o|publish|publicar|lan[cç]amento)\b/i)
|
|
1633
|
+
};
|
|
1634
|
+
const domainRiskSignals = {
|
|
1635
|
+
adult: add("domain:adult", /\b(er[óo]tic[oa]s?|adult[oa]s?|sexual|sexo|porn(?:o|ografia)?|18\+)\b/i),
|
|
1636
|
+
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),
|
|
1637
|
+
payment: add("domain:payment", /\b(pagamento|checkout|billing|cobran[cç]a|fatura[cç][aã]o|assinatura|cart[aã]o|stripe|paypal)\b/i),
|
|
1638
|
+
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)
|
|
1639
|
+
};
|
|
1640
|
+
const impactSignals = {
|
|
1641
|
+
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),
|
|
1642
|
+
productionRelease: mutationIntent === "publish" || add("impact:production-release", /\b(produ[cç][aã]o|release|deploy|publish|publicar|npm)\b/i),
|
|
1643
|
+
adminPermissions: add("impact:admin-permissions", /\b(admin|administrador|permiss(?:ão|oes|ões)|roles?|rbac)\b/i)
|
|
1644
|
+
};
|
|
1645
|
+
if (mutationIntent === "publish" && !matchedSignals.includes("impact:production-release")) {
|
|
1646
|
+
matchedSignals.push("impact:production-release");
|
|
1647
|
+
}
|
|
1648
|
+
const scopeSignals = {
|
|
1649
|
+
localized: add("scope:localized", /\b(localizado|uma frase|typo|pequeno|small|tiny|simples|README)\b/i),
|
|
1650
|
+
broad: add("scope:broad", /\b(plataforma|sistema|completo|premium|end-to-end|amplo|v[aá]rias|m[úu]ltiplas|empresa)\b/i),
|
|
1651
|
+
architectural: add("scope:architectural", /\b(deep|architectural|arquitetural|migration|migra[cç][aã]o|major|full|\[deep\])\b/i),
|
|
1652
|
+
explicitHigh: add("scope:explicit-high", /\b(high-risk|high risk|alto risco)\b/i),
|
|
1653
|
+
explicitRequiredSpec: add("scope:explicit-required-spec", /\b(formal spec|required spec|especifica[cç][aã]o formal)\b/i),
|
|
1654
|
+
explicitRequiredEvidence: add("scope:explicit-required-evidence", /\b(required evidence|evidence required|evid[eê]ncia obrigat[óo]ria)\b/i)
|
|
1655
|
+
};
|
|
1656
|
+
let riskLevel = "medium";
|
|
1657
|
+
const riskDrivers = [];
|
|
1658
|
+
const highDrivers = [
|
|
1659
|
+
["scope:explicit-high", scopeSignals.explicitHigh],
|
|
1660
|
+
["scope:architectural", scopeSignals.architectural],
|
|
1661
|
+
["domain:adult", domainRiskSignals.adult],
|
|
1662
|
+
["domain:auth", domainRiskSignals.auth],
|
|
1663
|
+
["domain:payment", domainRiskSignals.payment],
|
|
1664
|
+
["domain:security-governance", domainRiskSignals.securityGovernance],
|
|
1665
|
+
["impact:production-release", impactSignals.productionRelease],
|
|
1666
|
+
["impact:admin-permissions", impactSignals.adminPermissions],
|
|
1667
|
+
["impact:personal-data", taskTypeSignals.dashboard && impactSignals.personalData]
|
|
1668
|
+
];
|
|
1669
|
+
for (const [signal, active] of highDrivers) {
|
|
1670
|
+
if (active) {
|
|
1671
|
+
riskDrivers.push(signal);
|
|
1672
|
+
}
|
|
1673
|
+
}
|
|
1674
|
+
if (riskDrivers.length > 0) {
|
|
1675
|
+
riskLevel = "high";
|
|
1676
|
+
} else if (mutationIntent === "readonly") {
|
|
1677
|
+
riskLevel = "low";
|
|
1678
|
+
riskDrivers.push("intent:readonly");
|
|
1679
|
+
} else if (taskTypeSignals.docsCopy && scopeSignals.localized) {
|
|
1680
|
+
riskLevel = "low";
|
|
1681
|
+
riskDrivers.push("task:docs-copy");
|
|
1682
|
+
} else if (taskTypeSignals.refactor || taskTypeSignals.bugfix || taskTypeSignals.landingPage || taskTypeSignals.dashboard) {
|
|
1683
|
+
riskLevel = "medium";
|
|
1684
|
+
if (taskTypeSignals.refactor) riskDrivers.push("task:refactor");
|
|
1685
|
+
if (taskTypeSignals.bugfix) riskDrivers.push("task:bugfix");
|
|
1686
|
+
if (taskTypeSignals.landingPage) riskDrivers.push("task:landing-page");
|
|
1687
|
+
if (taskTypeSignals.dashboard) riskDrivers.push("task:dashboard");
|
|
1688
|
+
}
|
|
1689
|
+
const specPolicy = riskLevel === "high" || scopeSignals.explicitRequiredSpec || taskTypeSignals.landingPage && scopeSignals.broad ? "required" : "none";
|
|
1690
|
+
return {
|
|
1691
|
+
riskLevel,
|
|
1692
|
+
specPolicy,
|
|
1693
|
+
matchedSignals: [...new Set(matchedSignals)],
|
|
1694
|
+
riskDrivers: [...new Set(riskDrivers)]
|
|
1695
|
+
};
|
|
1696
|
+
}
|
|
1697
|
+
determineSafetyAndEvidence(text, mutationIntent) {
|
|
1698
|
+
const safetyConstraints = [];
|
|
1699
|
+
const requiredEvidence = [];
|
|
1700
|
+
if (mutationIntent === "publish") {
|
|
1701
|
+
safetyConstraints.push("Require release gate", "Awaiting user authorization");
|
|
1702
|
+
requiredEvidence.push("release-decision", "tarball-hash");
|
|
1703
|
+
} else if (mutationIntent === "write") {
|
|
1704
|
+
safetyConstraints.push("Require branch gate", "No direct commits to main");
|
|
1705
|
+
requiredEvidence.push("tests", "commit-hash");
|
|
1706
|
+
} else {
|
|
1707
|
+
safetyConstraints.push("No workspace mutations allowed");
|
|
1708
|
+
}
|
|
1709
|
+
if (/\b(bypass|force|direct)\b/i.test(text)) {
|
|
1710
|
+
safetyConstraints.push("Block bypass attempts");
|
|
1711
|
+
}
|
|
1712
|
+
if (/\b(required evidence|evidence required)\b/i.test(text)) {
|
|
1713
|
+
requiredEvidence.push("user-required-evidence");
|
|
1714
|
+
}
|
|
1715
|
+
if (/\b(formal spec|required spec)\b/i.test(text)) {
|
|
1716
|
+
safetyConstraints.push("Require formal specification");
|
|
1717
|
+
}
|
|
1718
|
+
return { safetyConstraints, requiredEvidence };
|
|
1719
|
+
}
|
|
1720
|
+
/**
|
|
1721
|
+
* Decides the routing decision.
|
|
1722
|
+
*/
|
|
1723
|
+
route(understanding) {
|
|
1724
|
+
const { operationType, mutationIntent, rawRequest } = understanding;
|
|
1725
|
+
const requestedActor = void 0;
|
|
1726
|
+
if (/\b(bypass safety|bypass validation|force push|directly to main|push to remote without gate)\b/i.test(rawRequest)) {
|
|
1727
|
+
return {
|
|
1728
|
+
requestedActor,
|
|
1729
|
+
selectedActor: "Atlas",
|
|
1730
|
+
operationType,
|
|
1731
|
+
mutationIntent,
|
|
1732
|
+
permissionDecision: "blocked",
|
|
1733
|
+
reason: "Security block: Attempt to bypass safety or push directly is forbidden.",
|
|
1734
|
+
workflowPath: ["Atlas"]
|
|
1735
|
+
};
|
|
1736
|
+
}
|
|
1737
|
+
let { selectedActor, permissionDecision, reason } = this.evaluateActorPermission(operationType, mutationIntent);
|
|
1738
|
+
let workflowPath = ["Atlas"];
|
|
1739
|
+
if (selectedActor === "Phoenix") {
|
|
1740
|
+
const requestPath = path6.join(this.cwd, ".ai-workflow/remediation-request.json");
|
|
1741
|
+
let hasFindings = false;
|
|
1742
|
+
if (fs7.existsSync(requestPath)) {
|
|
1743
|
+
try {
|
|
1744
|
+
const reqData = JSON.parse(fs7.readFileSync(requestPath, "utf8"));
|
|
1745
|
+
if (reqData.affectedChecks && reqData.affectedChecks.length > 0) {
|
|
1746
|
+
hasFindings = true;
|
|
1747
|
+
}
|
|
1748
|
+
} catch {
|
|
1749
|
+
}
|
|
1750
|
+
}
|
|
1751
|
+
if (!hasFindings) {
|
|
1752
|
+
permissionDecision = "blocked";
|
|
1753
|
+
reason = "Blocked: Phoenix acts only with concrete findings and bounded remediation.";
|
|
1754
|
+
}
|
|
1755
|
+
}
|
|
1756
|
+
if (understanding.specPolicy === "required") {
|
|
1757
|
+
workflowPath.push("Nexus", "Orion");
|
|
1758
|
+
}
|
|
1759
|
+
if (selectedActor && !workflowPath.includes(selectedActor)) {
|
|
1760
|
+
workflowPath.push(selectedActor);
|
|
1761
|
+
}
|
|
1762
|
+
if (selectedActor === "Atlas" && ["discover", "validate", "fix", "release", "deploy"].includes(operationType)) {
|
|
1763
|
+
permissionDecision = "blocked";
|
|
1764
|
+
reason = "Atlas self-execution guard: Atlas cannot silently execute specialized actor or capability requests.";
|
|
1765
|
+
}
|
|
1766
|
+
if (selectedActor === "Astra" && understanding.riskLevel === "high") {
|
|
1767
|
+
workflowPath.push("Sage", "Phoenix", "Sage");
|
|
1768
|
+
}
|
|
1769
|
+
if (understanding.specPolicy === "none") {
|
|
1770
|
+
workflowPath = workflowPath.filter((actor) => {
|
|
1771
|
+
if (actor === "Nexus" && selectedActor !== "Nexus") return false;
|
|
1772
|
+
if (actor === "Orion" && selectedActor !== "Orion") return false;
|
|
1773
|
+
return true;
|
|
1774
|
+
});
|
|
1775
|
+
}
|
|
1776
|
+
return {
|
|
1777
|
+
requestedActor,
|
|
1778
|
+
selectedActor,
|
|
1779
|
+
operationType,
|
|
1780
|
+
mutationIntent,
|
|
1781
|
+
permissionDecision,
|
|
1741
1782
|
reason,
|
|
1742
|
-
|
|
1743
|
-
requestFidelity
|
|
1783
|
+
workflowPath
|
|
1744
1784
|
};
|
|
1745
1785
|
}
|
|
1786
|
+
evaluateActorPermission(operationType, mutationIntent) {
|
|
1787
|
+
let selectedActor;
|
|
1788
|
+
let permissionDecision = "allowed";
|
|
1789
|
+
let reason = "Routed successfully.";
|
|
1790
|
+
if (operationType === "discover") {
|
|
1791
|
+
selectedActor = "Nexus";
|
|
1792
|
+
} else if (operationType === "validate") {
|
|
1793
|
+
selectedActor = "Sage";
|
|
1794
|
+
} else if (operationType === "fix") {
|
|
1795
|
+
selectedActor = "Phoenix";
|
|
1796
|
+
} else if (operationType === "implement") {
|
|
1797
|
+
selectedActor = "Astra";
|
|
1798
|
+
} else if (operationType === "release" || operationType === "deploy") {
|
|
1799
|
+
selectedActor = "Orion";
|
|
1800
|
+
} else {
|
|
1801
|
+
selectedActor = mutationIntent === "write" ? "Astra" : "Atlas";
|
|
1802
|
+
}
|
|
1803
|
+
return { selectedActor, permissionDecision, reason };
|
|
1804
|
+
}
|
|
1805
|
+
};
|
|
1806
|
+
|
|
1807
|
+
// src/core/request-classifier.ts
|
|
1808
|
+
var RequestClassifier = class {
|
|
1746
1809
|
/**
|
|
1747
|
-
*
|
|
1810
|
+
* Classifies a natural request.
|
|
1748
1811
|
*/
|
|
1749
|
-
|
|
1750
|
-
const
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
const deleted = [];
|
|
1754
|
-
let valid = true;
|
|
1755
|
-
if (previousSnapshot.branch !== currentSnapshot.branch) {
|
|
1756
|
-
modified.push(`branch:${previousSnapshot.branch}->${currentSnapshot.branch}`);
|
|
1757
|
-
valid = false;
|
|
1758
|
-
}
|
|
1759
|
-
if (previousSnapshot.head !== currentSnapshot.head) {
|
|
1760
|
-
modified.push(`head:${previousSnapshot.head}->${currentSnapshot.head}`);
|
|
1761
|
-
valid = false;
|
|
1762
|
-
}
|
|
1763
|
-
if (previousSnapshot.indexTree !== currentSnapshot.indexTree) {
|
|
1764
|
-
modified.push(`indexTree:${previousSnapshot.indexTree}->${currentSnapshot.indexTree}`);
|
|
1765
|
-
valid = false;
|
|
1766
|
-
}
|
|
1767
|
-
if (previousSnapshot.stagedDiffHash !== currentSnapshot.stagedDiffHash) {
|
|
1768
|
-
modified.push("stagedDiff");
|
|
1769
|
-
valid = false;
|
|
1770
|
-
}
|
|
1771
|
-
if (previousSnapshot.unstagedDiffHash !== currentSnapshot.unstagedDiffHash) {
|
|
1772
|
-
modified.push("unstagedDiff");
|
|
1773
|
-
valid = false;
|
|
1774
|
-
}
|
|
1775
|
-
if (previousSnapshot.untrackedFilesHash !== currentSnapshot.untrackedFilesHash) {
|
|
1776
|
-
modified.push("untrackedFiles");
|
|
1777
|
-
valid = false;
|
|
1812
|
+
classify(request = "", options = {}) {
|
|
1813
|
+
const text = String(request).trim();
|
|
1814
|
+
if (!text) {
|
|
1815
|
+
throw new Error("Cannot classify an empty request.");
|
|
1778
1816
|
}
|
|
1779
|
-
const
|
|
1780
|
-
const
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1817
|
+
const cwd = options.cwd || process.cwd();
|
|
1818
|
+
const profile = resolveWorkflowProfile({ request: text });
|
|
1819
|
+
const profileDef = getWorkflowProfile(profile);
|
|
1820
|
+
const readOnlyPatterns = /\b(analyze|check|inspect|review|find|search|show|list|read|view|verify)\b/i;
|
|
1821
|
+
const isReadOnly = readOnlyPatterns.test(text);
|
|
1822
|
+
const intent = isReadOnly ? "read-only" : "write";
|
|
1823
|
+
const router = new RequestRouter({ cwd });
|
|
1824
|
+
const requestUnderstanding = router.understand(text);
|
|
1825
|
+
const routingDecision = router.route(requestUnderstanding);
|
|
1826
|
+
const risk = requestUnderstanding.riskLevel;
|
|
1827
|
+
const explicitRequiredEvidence = /\b(required evidence|evidence required)\b/i.test(text);
|
|
1828
|
+
const explicitRequiredSpec = /\b(formal spec|required spec)\b/i.test(text);
|
|
1829
|
+
const specPolicy = requestUnderstanding.specPolicy === "required" || explicitRequiredSpec ? "required" : "none";
|
|
1830
|
+
const evidencePolicy = risk === "high" || requestUnderstanding.mutationIntent === "publish" || explicitRequiredEvidence ? "required" : "optional";
|
|
1831
|
+
const remediationLimit = risk === "high" ? 3 : risk === "low" ? 1 : 2;
|
|
1832
|
+
const specNeeded = specPolicy === "required";
|
|
1833
|
+
const validationNeeded = intent === "write";
|
|
1834
|
+
let legacyMode = "standard";
|
|
1835
|
+
if (/\b(deep|architectural|migration|major|full|\[deep\])\b/i.test(text)) {
|
|
1836
|
+
legacyMode = "full";
|
|
1837
|
+
} else if (/\b(tiny|small|quick|simple|only\s+update|typo|comment|\[tiny\])\b/i.test(text)) {
|
|
1838
|
+
const isPureDocsOrTypo = profile === "documentation" || /\b(typo|comment|readme|docs|documentation)\b/i.test(text);
|
|
1839
|
+
if (intent === "write" && !isPureDocsOrTypo) {
|
|
1840
|
+
legacyMode = "standard";
|
|
1841
|
+
} else {
|
|
1842
|
+
legacyMode = "quick";
|
|
1787
1843
|
}
|
|
1788
1844
|
}
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1845
|
+
return {
|
|
1846
|
+
request: text,
|
|
1847
|
+
profile,
|
|
1848
|
+
owner: routingDecision.selectedActor || profileDef.owner,
|
|
1849
|
+
intent: requestUnderstanding.mutationIntent === "readonly" ? "read-only" : "write",
|
|
1850
|
+
mode: legacyMode,
|
|
1851
|
+
risk,
|
|
1852
|
+
specPolicy,
|
|
1853
|
+
evidencePolicy,
|
|
1854
|
+
remediationLimit,
|
|
1855
|
+
riskDrivers: requestUnderstanding.riskDrivers || [],
|
|
1856
|
+
matchedSignals: requestUnderstanding.matchedSignals || [],
|
|
1857
|
+
specNeeded,
|
|
1858
|
+
validationNeeded,
|
|
1859
|
+
skills: [...profileDef.skills],
|
|
1860
|
+
requestUnderstanding,
|
|
1861
|
+
routingDecision
|
|
1862
|
+
};
|
|
1863
|
+
}
|
|
1864
|
+
};
|
|
1865
|
+
|
|
1866
|
+
// src/core/execution-planner.ts
|
|
1867
|
+
import path7 from "path";
|
|
1868
|
+
var ExecutionPlanner = class {
|
|
1869
|
+
cwd;
|
|
1870
|
+
constructor({ cwd = process.cwd() } = {}) {
|
|
1871
|
+
this.cwd = cwd;
|
|
1872
|
+
}
|
|
1873
|
+
/**
|
|
1874
|
+
* Generates an execution plan.
|
|
1875
|
+
*/
|
|
1876
|
+
plan(classification, taskSlug = "task") {
|
|
1877
|
+
const remediationLimit = classification.remediationLimit;
|
|
1878
|
+
const branchNeeded = classification.intent === "write";
|
|
1879
|
+
const specPath = classification.specNeeded ? path7.join("docs/workflows", taskSlug, "spec.md") : null;
|
|
1880
|
+
const owner = classification.routingDecision?.selectedActor || (classification.intent === "write" ? "Astra" : classification.owner);
|
|
1881
|
+
const validationsExpected = [];
|
|
1882
|
+
if (classification.validationNeeded) {
|
|
1883
|
+
validationsExpected.push("test");
|
|
1884
|
+
if (classification.profile.startsWith("frontend")) {
|
|
1885
|
+
validationsExpected.push("lint", "build");
|
|
1886
|
+
} else if (classification.profile === "backend-api") {
|
|
1887
|
+
validationsExpected.push("lint");
|
|
1792
1888
|
}
|
|
1793
1889
|
}
|
|
1794
|
-
|
|
1795
|
-
|
|
1890
|
+
const restrictions = [
|
|
1891
|
+
"Never commit or push directly to main/master.",
|
|
1892
|
+
"Always execute behavior tests for new or modified behavior."
|
|
1893
|
+
];
|
|
1894
|
+
if (classification.risk === "high") {
|
|
1895
|
+
restrictions.push("Require independent validation review.");
|
|
1796
1896
|
}
|
|
1797
1897
|
return {
|
|
1798
|
-
|
|
1799
|
-
|
|
1898
|
+
objective: classification.request,
|
|
1899
|
+
scope: classification.intent === "read-only" ? "Analysis and verification" : "Implementation of requested behavior",
|
|
1900
|
+
restrictions,
|
|
1901
|
+
owner,
|
|
1902
|
+
skills: [...classification.skills],
|
|
1903
|
+
branchNeeded,
|
|
1904
|
+
branchName: branchNeeded ? `feat/${taskSlug}` : null,
|
|
1905
|
+
validationsExpected,
|
|
1906
|
+
remediationLimit,
|
|
1907
|
+
specPath,
|
|
1908
|
+
specPolicy: classification.specPolicy,
|
|
1909
|
+
evidencePolicy: classification.evidencePolicy,
|
|
1910
|
+
riskLevel: classification.risk,
|
|
1911
|
+
mode: classification.mode,
|
|
1912
|
+
profile: classification.profile
|
|
1913
|
+
};
|
|
1914
|
+
}
|
|
1915
|
+
};
|
|
1916
|
+
|
|
1917
|
+
// src/core/workflow-state-machine.ts
|
|
1918
|
+
var WorkflowStateMachine = class {
|
|
1919
|
+
state;
|
|
1920
|
+
history;
|
|
1921
|
+
validTransitions;
|
|
1922
|
+
constructor() {
|
|
1923
|
+
this.state = "RECEIVED";
|
|
1924
|
+
this.history = [{ state: this.state, timestamp: (/* @__PURE__ */ new Date()).toISOString() }];
|
|
1925
|
+
this.validTransitions = {
|
|
1926
|
+
RECEIVED: ["CLASSIFIED", "BLOCKED"],
|
|
1927
|
+
CLASSIFIED: ["PLANNED", "BLOCKED"],
|
|
1928
|
+
PLANNED: ["BRANCH_READY", "BLOCKED"],
|
|
1929
|
+
BRANCH_READY: ["DELEGATED", "BLOCKED"],
|
|
1930
|
+
DELEGATED: ["IMPLEMENTING", "BLOCKED"],
|
|
1931
|
+
IMPLEMENTING: ["IMPLEMENTED", "BLOCKED"],
|
|
1932
|
+
IMPLEMENTED: ["VALIDATING", "BLOCKED"],
|
|
1933
|
+
VALIDATING: ["COMPLETED", "COMPLETED_WITH_NOTES", "REMEDIATING", "BLOCKED"],
|
|
1934
|
+
REMEDIATING: ["REVALIDATING", "BLOCKED"],
|
|
1935
|
+
REVALIDATING: ["COMPLETED", "COMPLETED_WITH_NOTES", "REMEDIATING", "BLOCKED"],
|
|
1936
|
+
COMPLETED: [],
|
|
1937
|
+
COMPLETED_WITH_NOTES: [],
|
|
1938
|
+
BLOCKED: []
|
|
1800
1939
|
};
|
|
1801
1940
|
}
|
|
1941
|
+
/**
|
|
1942
|
+
* Transitions the machine to a new state.
|
|
1943
|
+
* @param newState - The state to transition to.
|
|
1944
|
+
*/
|
|
1945
|
+
transitionTo(newState) {
|
|
1946
|
+
const allowed = this.validTransitions[this.state];
|
|
1947
|
+
if (!allowed || !allowed.includes(newState)) {
|
|
1948
|
+
throw new Error(`Invalid state transition: ${this.state} -> ${newState}`);
|
|
1949
|
+
}
|
|
1950
|
+
this.state = newState;
|
|
1951
|
+
this.history.push({ state: this.state, timestamp: (/* @__PURE__ */ new Date()).toISOString() });
|
|
1952
|
+
}
|
|
1953
|
+
getCurrentState() {
|
|
1954
|
+
return this.state;
|
|
1955
|
+
}
|
|
1956
|
+
getHistory() {
|
|
1957
|
+
return [...this.history];
|
|
1958
|
+
}
|
|
1802
1959
|
};
|
|
1803
1960
|
|
|
1804
1961
|
export {
|
|
1962
|
+
getPackageRoot,
|
|
1805
1963
|
readPackageFile,
|
|
1806
1964
|
getPackageVersion,
|
|
1807
1965
|
discoverPackageFiles,
|
|
@@ -1811,17 +1969,16 @@ export {
|
|
|
1811
1969
|
isTerminalFailure,
|
|
1812
1970
|
COMPLETION_STATUS_TEXT,
|
|
1813
1971
|
OpenCodeAdapter,
|
|
1814
|
-
BranchGate,
|
|
1815
1972
|
SpecValidator,
|
|
1816
1973
|
HandoffEngine,
|
|
1817
1974
|
HealerEngine,
|
|
1818
1975
|
getWorkflowProfile,
|
|
1819
1976
|
resolveWorkflowProfile,
|
|
1820
|
-
RequestClassifier,
|
|
1821
|
-
ExecutionPlanner,
|
|
1822
|
-
WorkflowStateMachine,
|
|
1823
1977
|
EvidenceLedger,
|
|
1824
1978
|
DelegationController,
|
|
1825
|
-
Finalizer
|
|
1979
|
+
Finalizer,
|
|
1980
|
+
RequestClassifier,
|
|
1981
|
+
ExecutionPlanner,
|
|
1982
|
+
WorkflowStateMachine
|
|
1826
1983
|
};
|
|
1827
|
-
//# sourceMappingURL=chunk-
|
|
1984
|
+
//# sourceMappingURL=chunk-LI76KI7C.js.map
|