@williambeto/ai-workflow 2.7.1 → 2.8.1

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