@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.
@@ -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: false,
164
- status: isSpecializedAgent ? "BLOCKED" : "FAILED",
183
+ success: true,
184
+ status: "DEGRADED",
165
185
  commandsRun: [],
166
186
  eventCount: 0,
167
- error: "OpenCode CLI is not installed or not available in the system PATH.",
187
+ error: "OpenCode CLI missing. Simulated execution.",
168
188
  runtimeAgentRequested: agent || null,
169
189
  runtimeAgentApplied: null,
170
190
  runtimeAgentSupported: false,
171
- runtimeAgentSelectionMode: isSpecializedAgent ? "explicit" : "default"
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 && !usePromptFallback ? agent : null,
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, runInWorkspace);
386
+ return runInReadOnlyWorkspace(this.cwd, runner);
285
387
  } else {
286
- return runInWorkspace(this.cwd);
388
+ return runner(this.cwd);
287
389
  }
288
390
  }
289
391
  };
290
392
 
291
- // src/core/gates/branch-gate.ts
292
- import { execSync as execSync2 } from "child_process";
293
- import fs from "fs";
294
- import path from "path";
295
- function slugify(value = "task") {
296
- return String(value).toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 48) || "task";
297
- }
298
- var BranchGate = class {
299
- protectedBranches;
300
- logPath;
301
- cwd;
302
- constructor({ protectedBranches = ["main", "master"], memoryDir, cwd = process.cwd() } = {}) {
303
- this.protectedBranches = protectedBranches;
304
- this.logPath = memoryDir ? path.join(memoryDir, "GATE_ALERTS.log") : null;
305
- this.cwd = cwd;
306
- }
307
- run(command) {
308
- return execSync2(command, {
309
- cwd: this.cwd,
310
- encoding: "utf8",
311
- stdio: ["ignore", "pipe", "pipe"]
312
- }).trim();
313
- }
314
- log(event) {
315
- if (!this.logPath) return;
316
- const timestamp = (/* @__PURE__ */ new Date()).toISOString();
317
- const logEntry = `[${timestamp}] ${event}
318
- `;
319
- try {
320
- if (!fs.existsSync(path.dirname(this.logPath))) fs.mkdirSync(path.dirname(this.logPath), { recursive: true });
321
- fs.appendFileSync(this.logPath, logEntry);
322
- } catch (error) {
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
- const isProtected = this.protectedBranches.includes(currentBranch);
381
- if (!isProtected) {
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
- const dirty = this.getDirtyState();
385
- if (!dirty.clean) {
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 fs2.readFile(filePath, "utf8");
416
- let tier = "unknown";
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 execSync3 } from "child_process";
453
- import fs3 from "fs/promises";
454
- import path2 from "path";
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 = path2.isAbsolute(specPath) ? specPath : path2.join(this.cwd, specPath);
470
+ const absolutePath = path.isAbsolute(specPath) ? specPath : path.join(this.cwd, specPath);
468
471
  try {
469
- const content = await fs3.readFile(absolutePath, "utf8");
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 = execSync3("git diff HEAD", { cwd: this.cwd, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim() || "No changes detected.";
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 = path2.join(this.cwd, "EVIDENCE.json");
499
+ const evidencePath = path.join(this.cwd, "EVIDENCE.json");
497
500
  try {
498
- const content = await fs3.readFile(evidencePath, "utf8");
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
- path2.join(this.cwd, ".ai-workflow/templates/HANDOFF.template.md"),
507
- path2.join(this.cwd, ".ai-workflow/harness/handoffs/HANDOFF.template.md"),
508
- path2.join(getPackageRoot(), "dist-assets/templates/HANDOFF.template.md"),
509
- path2.join(getPackageRoot(), "dist-assets/harness/handoffs/HANDOFF.template.md")
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 fs3.readFile(tPath, "utf8");
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 = path2.join(this.cwd, ".ai-workflow/handoffs");
528
- await fs3.mkdir(handoffDir, { recursive: true });
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 = path2.join(handoffDir, packetName);
531
- await fs3.writeFile(packetPath, packet);
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 fs4 from "fs/promises";
538
- import path3 from "path";
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 path3.join(this.cwd, ".ai-workflow", `healing-state-${this.taskSlug}.json`);
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 fs4.mkdir(path3.dirname(this.statePath), { recursive: true });
632
- await fs4.writeFile(this.statePath, JSON.stringify(state, null, 2));
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 fs4.unlink(this.statePath);
639
+ await fs3.unlink(this.statePath);
637
640
  } catch {
638
641
  }
639
642
  }
640
643
  async writeRemediationRequest({ attempt, result }) {
641
- const requestPath = path3.join(this.cwd, ".ai-workflow", "remediation-request.json");
642
- await fs4.mkdir(path3.dirname(requestPath), { recursive: true });
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 fs4.writeFile(requestPath, JSON.stringify(request, null, 2));
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/workflow-profiles.ts
760
- var PROFILE_DEFINITIONS = Object.freeze({
761
- "frontend-product": Object.freeze({
762
- owner: "Astra",
763
- skills: ["frontend-development", "ui-ux-design", "frontend-design-system"],
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
- constructor({ cwd = process.cwd() } = {}) {
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
- * Understands and classifies a natural language request.
780
+ * Logs a workflow event to the ledger.
841
781
  */
842
- understand(request) {
843
- const text = String(request).trim();
844
- if (!text) {
845
- throw new Error("Cannot classify an empty request.");
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 { operationType, requestedCapability } = this.extractCapability(text);
848
- const mutationIntent = this.determineMutationIntent(text, operationType);
849
- const riskAssessment = this.determineRiskAssessment(text, mutationIntent);
850
- const riskLevel = riskAssessment.riskLevel;
851
- const { safetyConstraints, requiredEvidence } = this.determineSafetyAndEvidence(text, mutationIntent);
852
- return {
853
- rawRequest: text,
854
- taskGoal: `Perform ${operationType} operation under ${mutationIntent} intent`,
855
- requestedActor: void 0,
856
- invalidActor: void 0,
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
- extractCapability(text) {
869
- let operationType = "explain";
870
- let requestedCapability = void 0;
871
- const capabilityMappings = [
872
- { op: "discover", pattern: /\b(discover|inspect|map|scan|structure|architecture|repo|search|find)\b/i },
873
- { op: "validate", pattern: /\b(validate|audit|review|check|verify|test)\b/i },
874
- { op: "implement", pattern: /\b(implement|implementa|build|create|crie|cria|add|develop|write|change|modify|atualiza|atualizar|refatora|refatorar)\b/i },
875
- { op: "fix", pattern: /\b(fix|repair|correct|corrige|corrigir|heal|solve|remediate)\b/i },
876
- { op: "release", pattern: /\b(release|tag|prepare release|prepara(?:r)?\s+(?:a\s+)?release)\b/i },
877
- { op: "deploy", pattern: /\b(deploy|publish|push|produção|producao)\b/i }
878
- ];
879
- for (const mapping of capabilityMappings) {
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
- return { operationType, requestedCapability };
888
- }
889
- determineMutationIntent(text, operationType) {
890
- const writePatterns = /\b(implement|implementa|build|fix|corrige|write|change|modify|create|crie|cria|add|refactor|refatora|refatorar|update|atualiza|atualizar)\b/i;
891
- const readOnlyPatterns = /\b(analyze|check|inspect|review|find|search|show|list|read|view|verify|validate|discover|map)\b/i;
892
- let mutationIntent = "write";
893
- if (/\b(read-only|readonly)\b/i.test(text)) {
894
- mutationIntent = "readonly";
895
- } else if (writePatterns.test(text)) {
896
- mutationIntent = "write";
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
- if (/\b(publish|release|deploy|produção|producao|push to npm|push to remote)\b/i.test(text)) {
901
- mutationIntent = "publish";
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
- determineRiskAssessment(text, mutationIntent) {
906
- const matchedSignals = [];
907
- const add = (signal, pattern) => {
908
- if (pattern.test(text)) {
909
- matchedSignals.push(signal);
910
- return true;
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
- return false;
913
- };
914
- const taskTypeSignals = {
915
- docsCopy: add("task:docs-copy", /\b(README|docs?|documenta[cç][aã]o|typo|copy|frase|comment|coment[áa]rio)\b/i),
916
- refactor: add("task:refactor", /\b(refactor|refatora|refatorar|simplificar|mais simples de manter|manuten[cç][aã]o)\b/i),
917
- noBehaviorChange: add("scope:no-behavior-change", /\b(sem alterar (?:o )?comportamento|without changing behavior|without behaviour change|no behavior change|comportamento existente)\b/i),
918
- landingPage: add("task:landing-page", /\b(landing page|hotsite|p[áa]gina de venda|p[áa]gina comercial)\b/i),
919
- dashboard: add("task:dashboard", /\b(dashboard|painel)\b/i),
920
- bugfix: add("task:bugfix", /\b(bug|fix|corrige|corrigir|erro|falha|n[aã]o envia|não envia)\b/i),
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
- determineSafetyAndEvidence(text, mutationIntent) {
987
- const safetyConstraints = [];
988
- const requiredEvidence = [];
989
- if (mutationIntent === "publish") {
990
- safetyConstraints.push("Require release gate", "Awaiting user authorization");
991
- requiredEvidence.push("release-decision", "tarball-hash");
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
- if (/\b(formal spec|required spec)\b/i.test(text)) {
1005
- safetyConstraints.push("Require formal specification");
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
- return { safetyConstraints, requiredEvidence };
1008
- }
1009
- /**
1010
- * Decides the routing decision.
1011
- */
1012
- route(understanding) {
1013
- const { operationType, mutationIntent, rawRequest } = understanding;
1014
- const requestedActor = void 0;
1015
- if (/\b(bypass safety|bypass validation|force push|directly to main|push to remote without gate)\b/i.test(rawRequest)) {
1016
- return {
1017
- requestedActor,
1018
- selectedActor: "Atlas",
1019
- operationType,
1020
- mutationIntent,
1021
- permissionDecision: "blocked",
1022
- reason: "Security block: Attempt to bypass safety or push directly is forbidden.",
1023
- workflowPath: ["Atlas"]
1024
- };
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
- let { selectedActor, permissionDecision, reason } = this.evaluateActorPermission(operationType, mutationIntent);
1027
- let workflowPath = ["Atlas"];
1028
- if (selectedActor === "Phoenix") {
1029
- const requestPath = path4.join(this.cwd, ".ai-workflow/remediation-request.json");
1030
- let hasFindings = false;
1031
- if (fs5.existsSync(requestPath)) {
1032
- try {
1033
- const reqData = JSON.parse(fs5.readFileSync(requestPath, "utf8"));
1034
- if (reqData.affectedChecks && reqData.affectedChecks.length > 0) {
1035
- hasFindings = true;
1036
- }
1037
- } catch {
1038
- }
1039
- }
1040
- if (!hasFindings) {
1041
- permissionDecision = "blocked";
1042
- reason = "Blocked: Phoenix acts only with concrete findings and bounded remediation.";
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
- if (understanding.specPolicy === "required") {
1046
- workflowPath.push("Nexus", "Orion");
1047
- }
1048
- if (selectedActor && !workflowPath.includes(selectedActor)) {
1049
- workflowPath.push(selectedActor);
1050
- }
1051
- if (selectedActor === "Atlas" && ["discover", "validate", "fix", "release", "deploy"].includes(operationType)) {
1052
- permissionDecision = "blocked";
1053
- reason = "Atlas self-execution guard: Atlas cannot silently execute specialized actor or capability requests.";
1054
- }
1055
- if (selectedActor === "Astra" && understanding.riskLevel === "high") {
1056
- workflowPath.push("Sage", "Phoenix", "Sage");
1057
- }
1058
- if (understanding.specPolicy === "none") {
1059
- workflowPath = workflowPath.filter((actor) => {
1060
- if (actor === "Nexus" && selectedActor !== "Nexus") return false;
1061
- if (actor === "Orion" && selectedActor !== "Orion") return false;
1062
- return true;
1063
- });
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
- requestedActor,
1067
- selectedActor,
1068
- operationType,
1069
- mutationIntent,
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/request-classifier.ts
1097
- var RequestClassifier = class {
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
- * Classifies a natural request.
1100
- */
1101
- classify(request = "", options = {}) {
1102
- const text = String(request).trim();
1103
- if (!text) {
1104
- throw new Error("Cannot classify an empty request.");
1105
- }
1106
- const cwd = options.cwd || process.cwd();
1107
- const profile = resolveWorkflowProfile({ request: text });
1108
- const profileDef = getWorkflowProfile(profile);
1109
- const readOnlyPatterns = /\b(analyze|check|inspect|review|find|search|show|list|read|view|verify)\b/i;
1110
- const isReadOnly = readOnlyPatterns.test(text);
1111
- const intent = isReadOnly ? "read-only" : "write";
1112
- const router = new RequestRouter({ cwd });
1113
- const requestUnderstanding = router.understand(text);
1114
- const routingDecision = router.route(requestUnderstanding);
1115
- const risk = requestUnderstanding.riskLevel;
1116
- const explicitRequiredEvidence = /\b(required evidence|evidence required)\b/i.test(text);
1117
- const explicitRequiredSpec = /\b(formal spec|required spec)\b/i.test(text);
1118
- const specPolicy = requestUnderstanding.specPolicy === "required" || explicitRequiredSpec ? "required" : "none";
1119
- const evidencePolicy = risk === "high" || requestUnderstanding.mutationIntent === "publish" || explicitRequiredEvidence ? "required" : "optional";
1120
- const remediationLimit = risk === "high" ? 3 : risk === "low" ? 1 : 2;
1121
- const specNeeded = specPolicy === "required";
1122
- const validationNeeded = intent === "write";
1123
- let legacyMode = "standard";
1124
- if (/\b(deep|architectural|migration|major|full|\[deep\])\b/i.test(text)) {
1125
- legacyMode = "full";
1126
- } else if (/\b(tiny|small|quick|simple|only\s+update|typo|comment|\[tiny\])\b/i.test(text)) {
1127
- const isPureDocsOrTypo = profile === "documentation" || /\b(typo|comment|readme|docs|documentation)\b/i.test(text);
1128
- if (intent === "write" && !isPureDocsOrTypo) {
1129
- legacyMode = "standard";
1130
- } else {
1131
- legacyMode = "quick";
1132
- }
1133
- }
1134
- return {
1135
- request: text,
1136
- profile,
1137
- owner: routingDecision.selectedActor || profileDef.owner,
1138
- intent: requestUnderstanding.mutationIntent === "readonly" ? "read-only" : "write",
1139
- mode: legacyMode,
1140
- risk,
1141
- specPolicy,
1142
- evidencePolicy,
1143
- remediationLimit,
1144
- riskDrivers: requestUnderstanding.riskDrivers || [],
1145
- matchedSignals: requestUnderstanding.matchedSignals || [],
1146
- specNeeded,
1147
- validationNeeded,
1148
- skills: [...profileDef.skills],
1149
- requestUnderstanding,
1150
- routingDecision
1151
- };
1152
- }
1153
- };
1154
-
1155
- // src/core/execution-planner.ts
1156
- import path5 from "path";
1157
- var ExecutionPlanner = class {
1158
- cwd;
1159
- constructor({ cwd = process.cwd() } = {}) {
1160
- this.cwd = cwd;
1161
- }
1162
- /**
1163
- * Generates an execution plan.
1164
- */
1165
- plan(classification, taskSlug = "task") {
1166
- const remediationLimit = classification.remediationLimit;
1167
- const branchNeeded = classification.intent === "write";
1168
- const specPath = classification.specNeeded ? path5.join("docs/workflows", taskSlug, "spec.md") : null;
1169
- const owner = classification.routingDecision?.selectedActor || (classification.intent === "write" ? "Astra" : classification.owner);
1170
- const validationsExpected = [];
1171
- if (classification.validationNeeded) {
1172
- validationsExpected.push("test");
1173
- if (classification.profile.startsWith("frontend")) {
1174
- validationsExpected.push("lint", "build");
1175
- } else if (classification.profile === "backend-api") {
1176
- validationsExpected.push("lint");
1177
- }
1178
- }
1179
- const restrictions = [
1180
- "Never commit or push directly to main/master.",
1181
- "Always execute behavior tests for new or modified behavior."
1182
- ];
1183
- if (classification.risk === "high") {
1184
- restrictions.push("Require independent validation review.");
1185
- }
1186
- return {
1187
- objective: classification.request,
1188
- scope: classification.intent === "read-only" ? "Analysis and verification" : "Implementation of requested behavior",
1189
- restrictions,
1190
- owner,
1191
- skills: [...classification.skills],
1192
- branchNeeded,
1193
- branchName: branchNeeded ? `feat/${taskSlug}` : null,
1194
- validationsExpected,
1195
- remediationLimit,
1196
- specPath,
1197
- specPolicy: classification.specPolicy,
1198
- evidencePolicy: classification.evidencePolicy,
1199
- riskLevel: classification.risk,
1200
- mode: classification.mode,
1201
- profile: classification.profile
1202
- };
1203
- }
1204
- };
1205
-
1206
- // src/core/workflow-state-machine.ts
1207
- var WorkflowStateMachine = class {
1208
- state;
1209
- history;
1210
- validTransitions;
1211
- constructor() {
1212
- this.state = "RECEIVED";
1213
- this.history = [{ state: this.state, timestamp: (/* @__PURE__ */ new Date()).toISOString() }];
1214
- this.validTransitions = {
1215
- RECEIVED: ["CLASSIFIED", "BLOCKED"],
1216
- CLASSIFIED: ["PLANNED", "BLOCKED"],
1217
- PLANNED: ["BRANCH_READY", "BLOCKED"],
1218
- BRANCH_READY: ["DELEGATED", "BLOCKED"],
1219
- DELEGATED: ["IMPLEMENTING", "BLOCKED"],
1220
- IMPLEMENTING: ["IMPLEMENTED", "BLOCKED"],
1221
- IMPLEMENTED: ["VALIDATING", "BLOCKED"],
1222
- VALIDATING: ["COMPLETED", "COMPLETED_WITH_NOTES", "REMEDIATING", "BLOCKED"],
1223
- REMEDIATING: ["REVALIDATING", "BLOCKED"],
1224
- REVALIDATING: ["COMPLETED", "COMPLETED_WITH_NOTES", "REMEDIATING", "BLOCKED"],
1225
- COMPLETED: [],
1226
- COMPLETED_WITH_NOTES: [],
1227
- BLOCKED: []
1228
- };
1229
- }
1230
- /**
1231
- * Transitions the machine to a new state.
1232
- * @param newState - The state to transition to.
1233
- */
1234
- transitionTo(newState) {
1235
- const allowed = this.validTransitions[this.state];
1236
- if (!allowed || !allowed.includes(newState)) {
1237
- throw new Error(`Invalid state transition: ${this.state} -> ${newState}`);
1238
- }
1239
- this.state = newState;
1240
- this.history.push({ state: this.state, timestamp: (/* @__PURE__ */ new Date()).toISOString() });
1241
- }
1242
- getCurrentState() {
1243
- return this.state;
1244
- }
1245
- getHistory() {
1246
- return [...this.history];
1247
- }
1248
- };
1249
-
1250
- // src/core/evidence/evidence-ledger.ts
1251
- import fs6 from "fs/promises";
1252
- import path6 from "path";
1253
- var EvidenceLedger = class {
1254
- cwd;
1255
- workflowId;
1256
- events;
1257
- constructor({ cwd = process.cwd(), workflowId = "unknown" } = {}) {
1258
- this.cwd = cwd;
1259
- this.workflowId = workflowId;
1260
- this.events = [];
1261
- }
1262
- /**
1263
- * Logs a workflow event to the ledger.
1264
- */
1265
- logEvent({
1266
- actor,
1267
- eventType,
1268
- provenance,
1269
- data = {},
1270
- actorType,
1271
- observed,
1272
- runtime
1273
- }) {
1274
- const event = {
1275
- workflowId: this.workflowId,
1276
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1277
- actor,
1278
- actorType: actorType !== void 0 ? actorType : void 0,
1279
- observed: observed !== void 0 ? observed : void 0,
1280
- runtime: runtime !== void 0 ? runtime : void 0,
1281
- eventType,
1282
- provenance,
1283
- data
1284
- };
1285
- this.events.push(event);
1286
- console.log(`[LEDGER] [${actor}] ${eventType}: ${JSON.stringify(data)}`);
1287
- return event;
1288
- }
1289
- getEvents() {
1290
- return this.events;
1291
- }
1292
- /**
1293
- * Saves the ledger events to a JSON file.
1294
- */
1295
- async save(filePath) {
1296
- const targetPath = path6.isAbsolute(filePath) ? filePath : path6.join(this.cwd, filePath);
1297
- await fs6.mkdir(path6.dirname(targetPath), { recursive: true });
1298
- await fs6.writeFile(targetPath, JSON.stringify(this.events, null, 2), "utf8");
1299
- }
1300
- async verifyRoleConfinement() {
1301
- const violations = [];
1302
- const events = this.events;
1303
- const atlasImplements = events.some((e) => e.actor === "Atlas" && (e.eventType === "implementation_start" || e.eventType === "implementation_complete"));
1304
- if (atlasImplements) {
1305
- violations.push("Atlas cannot implement directly");
1306
- }
1307
- const nexusMutates = events.some((e) => e.actor === "Nexus" && e.eventType === "file_mutate");
1308
- if (nexusMutates) {
1309
- violations.push("Nexus cannot mutate files during discovery");
1310
- }
1311
- const sageValidates = events.some((e) => e.actor === "Sage" && (e.eventType === "validation_start" || e.eventType === "validation_complete"));
1312
- const hasFidelity = events.some((e) => e.eventType === "fidelity_check" || e.eventType === "fidelity_verification");
1313
- if (sageValidates && !hasFidelity) {
1314
- violations.push("Sage cannot validate without fidelity evidence");
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: execSync6 } = await import("child_process");
1504
- gitDiff = execSync6("git diff HEAD", { cwd: this.cwd, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim();
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 fs7 from "fs/promises";
1549
- import path7 from "path";
1214
+ import fs5 from "fs/promises";
1215
+ import path4 from "path";
1550
1216
  import crypto2 from "crypto";
1551
- import { execSync as execSync4 } from "child_process";
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 execSync4(`git ${args}`, { cwd: this.cwd, encoding: "utf8", stdio: "pipe" }).trim();
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 fs7.readdir(dir, { withFileTypes: true });
1270
+ const entries = await fs5.readdir(dir, { withFileTypes: true });
1605
1271
  for (const entry of entries) {
1606
- const fullPath = path7.join(dir, entry.name);
1607
- const relativePath = path7.relative(this.cwd, fullPath);
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 fs7.stat(fullPath);
1616
- const content = await fs7.readFile(fullPath);
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 execSync5 } from "child_process";
1651
- import path8 from "path";
1652
- import fs8 from "fs/promises";
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 = execSync5("git diff --name-only HEAD", { cwd: this.cwd, encoding: "utf8" });
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 = execSync5("git status --short --untracked-files=all", { cwd: this.cwd, encoding: "utf8" });
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 = path8.join(this.cwd, "EVIDENCE.json");
1726
- const hasEvidence = await fs8.access(evidencePath).then(() => true).catch(() => false);
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-76ZQQYDU.js");
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
- deliveryClass: actualDeliveryClass,
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
- * Compares the current workspace state with a previously captured snapshot.
1810
+ * Classifies a natural request.
1748
1811
  */
1749
- async verifyIntegrity(previousSnapshot) {
1750
- const currentSnapshot = await this.snapshotManager.capture();
1751
- const added = [];
1752
- const modified = [];
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 prevFiles = previousSnapshot.files || {};
1780
- const currFiles = currentSnapshot.files || {};
1781
- for (const [file, info] of Object.entries(currFiles)) {
1782
- const prevInfo = prevFiles[file];
1783
- if (!prevInfo) {
1784
- added.push(file);
1785
- } else if (prevInfo.sha256 !== info.sha256 || prevInfo.mode !== info.mode) {
1786
- modified.push(file);
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
- for (const file of Object.keys(prevFiles)) {
1790
- if (!currFiles[file]) {
1791
- deleted.push(file);
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
- if (added.length > 0 || modified.length > 0 || deleted.length > 0) {
1795
- valid = false;
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
- valid,
1799
- changes: { added, modified, deleted }
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-W4RTQWVQ.js.map
1984
+ //# sourceMappingURL=chunk-LI76KI7C.js.map