sentinelayer-cli 0.8.11 → 0.9.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.
Files changed (38) hide show
  1. package/package.json +10 -5
  2. package/src/agents/devtestbot/config/definition.js +100 -0
  3. package/src/agents/devtestbot/config/system-prompt.js +92 -0
  4. package/src/agents/devtestbot/index.js +9 -0
  5. package/src/agents/devtestbot/runner.js +769 -0
  6. package/src/agents/devtestbot/tool.js +707 -0
  7. package/src/agents/jules/stream.js +2 -12
  8. package/src/audit/orchestrator.js +471 -114
  9. package/src/audit/persona-loop.js +1342 -0
  10. package/src/audit/registry.js +58 -2
  11. package/src/commands/audit.js +42 -1
  12. package/src/commands/legacy-args.js +32 -1
  13. package/src/commands/omargate.js +4 -0
  14. package/src/commands/session.js +417 -89
  15. package/src/commands/swarm.js +11 -2
  16. package/src/cost/history.js +41 -21
  17. package/src/events/schema.js +27 -1
  18. package/src/guide/generator.js +14 -0
  19. package/src/legacy-cli.js +110 -18
  20. package/src/prompt/generator.js +4 -16
  21. package/src/review/ai-review.js +95 -6
  22. package/src/review/dd-report-email-client.js +148 -0
  23. package/src/review/investor-dd-devtestbot.js +599 -0
  24. package/src/review/investor-dd-orchestrator.js +135 -3
  25. package/src/review/omargate-cache.js +285 -0
  26. package/src/review/omargate-orchestrator.js +605 -4
  27. package/src/review/persona-prompts.js +34 -1
  28. package/src/review/report.js +189 -4
  29. package/src/session/coordination-guidance.js +48 -0
  30. package/src/session/daemon.js +3 -2
  31. package/src/session/listener.js +236 -0
  32. package/src/session/senti-naming.js +36 -0
  33. package/src/session/setup-guides.js +3 -15
  34. package/src/session/store.js +54 -5
  35. package/src/session/sync.js +23 -0
  36. package/src/spec/generator.js +8 -10
  37. package/src/swarm/registry.js +20 -0
  38. package/src/swarm/runtime.js +139 -1
@@ -3,8 +3,10 @@ import fsp from "node:fs/promises";
3
3
  import path from "node:path";
4
4
 
5
5
  import { resolveOutputRoot } from "../config/service.js";
6
+ import { createAgentEvent } from "../events/schema.js";
6
7
  import { resolveCodebaseIngest } from "../ingest/engine.js";
7
8
  import { runDeterministicReviewPipeline } from "../review/local-review.js";
9
+ import { loadOmarGateDeterministicCache } from "../review/omargate-cache.js";
8
10
  import {
9
11
  renderArchitectureSpecialistMarkdown,
10
12
  runArchitectureSpecialist,
@@ -42,6 +44,15 @@ import {
42
44
  buildSharedMemoryCorpus,
43
45
  queryHybridRetriever,
44
46
  } from "../memory/retrieval.js";
47
+ import { runPersonaAgenticLoop } from "./persona-loop.js";
48
+
49
+ const AUDIT_ORCHESTRATOR_AGENT = Object.freeze({
50
+ id: "audit-orchestrator",
51
+ persona: "Audit Orchestrator",
52
+ shortName: "Audit",
53
+ color: "cyan",
54
+ avatar: "A",
55
+ });
45
56
 
46
57
  function normalizeString(value) {
47
58
  return String(value || "").trim();
@@ -84,6 +95,34 @@ function severitySummary(findings = []) {
84
95
  return summary;
85
96
  }
86
97
 
98
+ function emitAuditEvent(onEvent, runId, event, payload = {}) {
99
+ if (!onEvent) return;
100
+ onEvent(createAgentEvent({
101
+ event,
102
+ agent: AUDIT_ORCHESTRATOR_AGENT,
103
+ payload,
104
+ runId,
105
+ }));
106
+ }
107
+
108
+ function emitProgressShadow(onEvent, runId, phase, message, extra = {}) {
109
+ emitAuditEvent(onEvent, runId, "progress", {
110
+ phase,
111
+ message,
112
+ shadowFor: extra.shadowFor || phase,
113
+ ...extra,
114
+ });
115
+ }
116
+
117
+ function emitAuditLifecycleEvent(onEvent, runId, event, payload = {}, shadowMessage = "") {
118
+ emitAuditEvent(onEvent, runId, event, payload);
119
+ if (shadowMessage) {
120
+ emitProgressShadow(onEvent, runId, payload.phase || event, shadowMessage, {
121
+ shadowFor: event,
122
+ });
123
+ }
124
+ }
125
+
87
126
  function routeFindingToAgentId(finding = {}) {
88
127
  const file = normalizeString(finding.file).toLowerCase();
89
128
  const message = normalizeString(finding.message).toLowerCase();
@@ -142,6 +181,11 @@ function computeConfidenceFloor(base, findingCount) {
142
181
  return Math.max(0.5, Math.min(0.99, normalizedFloor - damping));
143
182
  }
144
183
 
184
+ function normalizeIsolationMode(value) {
185
+ const normalized = normalizeString(value).toLowerCase();
186
+ return normalized === "relaxed" ? "relaxed" : "strict";
187
+ }
188
+
145
189
  async function runWithConcurrency(items, maxParallel, worker) {
146
190
  const results = [];
147
191
  const queue = [...items];
@@ -182,6 +226,9 @@ Run ID: ${report.runId}
182
226
  Target: ${report.targetPath}
183
227
  Max parallel: ${report.maxParallel}
184
228
  Dry run: ${report.dryRun ? "yes" : "no"}
229
+ Persona isolation: ${report.isolation || "strict"}
230
+ Seed from deterministic: ${report.seedFromDeterministic === false ? "no" : "yes"}
231
+ OmarGate reuse: ${report.omargateReuse?.used ? `yes (${report.omargateReuse.runId})` : report.omargateReuse?.requested ? `requested ${report.omargateReuse.requested} (${report.omargateReuse.reason || "unavailable"})` : "no"}
185
232
 
186
233
  Summary:
187
234
  - Findings: P0=${report.summary.P0} P1=${report.summary.P1} P2=${report.summary.P2} P3=${report.summary.P3}
@@ -209,6 +256,7 @@ Ingest:
209
256
  Deterministic baseline:
210
257
  - Run ID: ${report.deterministicBaseline.runId || "n/a"}
211
258
  - Report: ${report.deterministicBaseline.reportPath || "n/a"}
259
+ - Reused OmarGate run: ${report.omargateReuse?.used ? report.omargateReuse.runId : "n/a"}
212
260
  - Summary: P0=${report.deterministicBaseline.summary.P0} P1=${report.deterministicBaseline.summary.P1} P2=${report.deterministicBaseline.summary.P2} P3=${report.deterministicBaseline.summary.P3}
213
261
 
214
262
  Agent outcomes:
@@ -216,6 +264,110 @@ ${agentLines || "- none"}
216
264
  `;
217
265
  }
218
266
 
267
+ async function buildSpecialistSeed({
268
+ agent,
269
+ deterministicBaseline,
270
+ ingest,
271
+ agentsDirectory,
272
+ }) {
273
+ let findings = [];
274
+ let summary = severitySummary(findings);
275
+ let confidence = computeConfidenceFloor(agent.confidenceFloor, findings.length);
276
+ let specialistReportPath = "";
277
+
278
+ if (agent.id === "security") {
279
+ const securitySpecialist = runSecuritySpecialist({
280
+ findings: deterministicBaseline.findings,
281
+ });
282
+ findings = securitySpecialist.findings;
283
+ summary = securitySpecialist.summary;
284
+ confidence = securitySpecialist.confidence;
285
+ specialistReportPath = path.join(agentsDirectory, "SECURITY_AGENT_REPORT.md");
286
+ await fsp.writeFile(
287
+ specialistReportPath,
288
+ `${renderSecuritySpecialistMarkdown(securitySpecialist).trim()}\n`,
289
+ "utf-8"
290
+ );
291
+ } else if (agent.id === "architecture") {
292
+ const architectureSpecialist = runArchitectureSpecialist({
293
+ findings: deterministicBaseline.findings,
294
+ ingest,
295
+ });
296
+ findings = architectureSpecialist.findings;
297
+ summary = architectureSpecialist.summary;
298
+ confidence = architectureSpecialist.confidence;
299
+ specialistReportPath = path.join(agentsDirectory, "ARCHITECTURE_AGENT_REPORT.md");
300
+ await fsp.writeFile(
301
+ specialistReportPath,
302
+ `${renderArchitectureSpecialistMarkdown(architectureSpecialist).trim()}\n`,
303
+ "utf-8"
304
+ );
305
+ } else if (agent.id === "testing") {
306
+ const testingSpecialist = runTestingSpecialist({
307
+ findings: deterministicBaseline.findings,
308
+ ingest,
309
+ });
310
+ findings = testingSpecialist.findings;
311
+ summary = testingSpecialist.summary;
312
+ confidence = testingSpecialist.confidence;
313
+ specialistReportPath = path.join(agentsDirectory, "TESTING_AGENT_REPORT.md");
314
+ await fsp.writeFile(
315
+ specialistReportPath,
316
+ `${renderTestingSpecialistMarkdown(testingSpecialist).trim()}\n`,
317
+ "utf-8"
318
+ );
319
+ } else if (agent.id === "performance") {
320
+ const performanceSpecialist = runPerformanceSpecialist({
321
+ findings: deterministicBaseline.findings,
322
+ ingest,
323
+ });
324
+ findings = performanceSpecialist.findings;
325
+ summary = performanceSpecialist.summary;
326
+ confidence = performanceSpecialist.confidence;
327
+ specialistReportPath = path.join(agentsDirectory, "PERFORMANCE_AGENT_REPORT.md");
328
+ await fsp.writeFile(
329
+ specialistReportPath,
330
+ `${renderPerformanceSpecialistMarkdown(performanceSpecialist).trim()}\n`,
331
+ "utf-8"
332
+ );
333
+ } else if (agent.id === "compliance") {
334
+ const complianceSpecialist = runComplianceSpecialist({
335
+ findings: deterministicBaseline.findings,
336
+ ingest,
337
+ });
338
+ findings = complianceSpecialist.findings;
339
+ summary = complianceSpecialist.summary;
340
+ confidence = complianceSpecialist.confidence;
341
+ specialistReportPath = path.join(agentsDirectory, "COMPLIANCE_AGENT_REPORT.md");
342
+ await fsp.writeFile(
343
+ specialistReportPath,
344
+ `${renderComplianceSpecialistMarkdown(complianceSpecialist).trim()}\n`,
345
+ "utf-8"
346
+ );
347
+ } else if (agent.id === "documentation") {
348
+ const documentationSpecialist = runDocumentationSpecialist({
349
+ findings: deterministicBaseline.findings,
350
+ ingest,
351
+ });
352
+ findings = documentationSpecialist.findings;
353
+ summary = documentationSpecialist.summary;
354
+ confidence = documentationSpecialist.confidence;
355
+ specialistReportPath = path.join(agentsDirectory, "DOCUMENTATION_AGENT_REPORT.md");
356
+ await fsp.writeFile(
357
+ specialistReportPath,
358
+ `${renderDocumentationSpecialistMarkdown(documentationSpecialist).trim()}\n`,
359
+ "utf-8"
360
+ );
361
+ }
362
+
363
+ return {
364
+ findings,
365
+ summary,
366
+ confidence,
367
+ specialistReportPath,
368
+ };
369
+ }
370
+
219
371
  export async function runAuditOrchestrator({
220
372
  targetPath,
221
373
  agents = [],
@@ -223,14 +375,24 @@ export async function runAuditOrchestrator({
223
375
  outputDir = "",
224
376
  dryRun = false,
225
377
  refreshIngest = false,
378
+ provider = null,
379
+ onEvent = null,
380
+ clientFactory = null,
381
+ isolation = "strict",
382
+ seedFromDeterministic = true,
383
+ reuseOmarGate = "",
226
384
  } = {}) {
227
385
  const normalizedTargetPath = path.resolve(String(targetPath || "."));
386
+ const isolationMode = normalizeIsolationMode(isolation);
387
+ const useDeterministicSeed = seedFromDeterministic !== false;
388
+ const requestedOmarGateReuse = normalizeString(reuseOmarGate);
228
389
  const outputRoot = await resolveOutputRoot({
229
390
  cwd: normalizedTargetPath,
230
391
  outputDirOverride: outputDir,
231
392
  env: process.env,
232
393
  });
233
394
  const runId = `audit-${formatTimestampToken()}-${randomUUID().slice(0, 8)}`;
395
+ const startedAt = Date.now();
234
396
  const runDirectory = path.join(outputRoot, "audits", runId);
235
397
  const agentsDirectory = path.join(runDirectory, "agents");
236
398
  await fsp.mkdir(agentsDirectory, { recursive: true });
@@ -238,13 +400,55 @@ export async function runAuditOrchestrator({
238
400
  runId,
239
401
  scope: "audit-orchestrator",
240
402
  });
403
+ const selectedAgentIds = agents.map((agent) => agent.id);
404
+ const normalizedMaxParallel = Math.max(1, Math.floor(Number(maxParallel || 1)));
405
+
406
+ emitAuditLifecycleEvent(
407
+ onEvent,
408
+ runId,
409
+ "orchestrator_start",
410
+ {
411
+ runId,
412
+ targetPath: normalizedTargetPath,
413
+ selectedAgents: selectedAgentIds,
414
+ agentCount: selectedAgentIds.length,
415
+ maxParallel: normalizedMaxParallel,
416
+ dryRun: Boolean(dryRun),
417
+ isolation: isolationMode,
418
+ seedFromDeterministic: useDeterministicSeed,
419
+ reuseOmarGate: requestedOmarGateReuse || "",
420
+ },
421
+ `Audit orchestrator starting with ${selectedAgentIds.length} agent(s).`
422
+ );
241
423
 
424
+ const ingestStartedAt = Date.now();
425
+ emitAuditLifecycleEvent(
426
+ onEvent,
427
+ runId,
428
+ "phase_start",
429
+ { phase: "ingest", targetPath: normalizedTargetPath, refresh: Boolean(refreshIngest) },
430
+ "Starting codebase ingest."
431
+ );
242
432
  const ingestResolution = await resolveCodebaseIngest({
243
433
  rootPath: normalizedTargetPath,
244
434
  outputDir,
245
435
  refresh: Boolean(refreshIngest),
246
436
  });
247
437
  const ingest = ingestResolution.ingest;
438
+ emitAuditLifecycleEvent(
439
+ onEvent,
440
+ runId,
441
+ "phase_complete",
442
+ {
443
+ phase: "ingest",
444
+ durationMs: Math.max(0, Date.now() - ingestStartedAt),
445
+ filesScanned: Number(ingest?.summary?.filesScanned || 0),
446
+ totalLoc: Number(ingest?.summary?.totalLoc || 0),
447
+ refreshed: Boolean(ingestResolution.refreshed),
448
+ stale: Boolean(ingestResolution.stale),
449
+ },
450
+ `Ingest complete: ${Number(ingest?.summary?.filesScanned || 0)} files.`
451
+ );
248
452
 
249
453
  let deterministicBaseline = {
250
454
  runId: "",
@@ -253,25 +457,113 @@ export async function runAuditOrchestrator({
253
457
  summary: { P0: 0, P1: 0, P2: 0, P3: 0, blocking: false },
254
458
  findings: [],
255
459
  };
460
+ let omargateReuse = {
461
+ requested: requestedOmarGateReuse,
462
+ used: false,
463
+ runId: "",
464
+ deterministicRunId: "",
465
+ artifactPath: "",
466
+ reason: requestedOmarGateReuse ? (dryRun ? "dry_run" : "not_found") : "not_requested",
467
+ };
468
+ const baselineStartedAt = Date.now();
469
+ emitAuditLifecycleEvent(
470
+ onEvent,
471
+ runId,
472
+ "phase_start",
473
+ {
474
+ phase: "deterministic_baseline",
475
+ skipped: Boolean(dryRun),
476
+ reuseRequested: Boolean(requestedOmarGateReuse),
477
+ requestedOmarGateRunId: requestedOmarGateReuse,
478
+ },
479
+ dryRun
480
+ ? "Skipping deterministic baseline for dry run."
481
+ : requestedOmarGateReuse
482
+ ? "Resolving OmarGate deterministic baseline reuse."
483
+ : "Starting deterministic baseline."
484
+ );
256
485
  if (!dryRun) {
257
- const deterministic = await runDeterministicReviewPipeline({
258
- targetPath: normalizedTargetPath,
259
- mode: "full",
260
- outputDir,
486
+ let reusedBaseline = null;
487
+ if (requestedOmarGateReuse) {
488
+ const reused = await loadOmarGateDeterministicCache({
489
+ targetPath: normalizedTargetPath,
490
+ outputDir,
491
+ runIdOrLatest: requestedOmarGateReuse,
492
+ });
493
+ if (reused.found) {
494
+ const cache = reused.cache || {};
495
+ reusedBaseline = {
496
+ runId: cache.deterministicRunId || cache.runId || reused.runId,
497
+ reportPath: cache.source?.reportPath || "",
498
+ reportJsonPath: cache.artifacts?.jsonPath || "",
499
+ summary: cache.summary || severitySummary(cache.findings || []),
500
+ findings: Array.isArray(cache.findings) ? cache.findings : [],
501
+ reusedFromOmarGate: true,
502
+ reusedOmarGateRunId: reused.runId,
503
+ reuseArtifactPath: reused.artifactPath,
504
+ };
505
+ omargateReuse = {
506
+ requested: requestedOmarGateReuse,
507
+ used: true,
508
+ runId: reused.runId,
509
+ deterministicRunId: cache.deterministicRunId || "",
510
+ artifactPath: reused.artifactPath,
511
+ reason: "",
512
+ };
513
+ } else {
514
+ omargateReuse = {
515
+ requested: requestedOmarGateReuse,
516
+ used: false,
517
+ runId: "",
518
+ deterministicRunId: "",
519
+ artifactPath: "",
520
+ reason: reused.reason || "not_found",
521
+ };
522
+ }
523
+ }
524
+ if (reusedBaseline) {
525
+ deterministicBaseline = reusedBaseline;
526
+ } else {
527
+ const deterministic = await runDeterministicReviewPipeline({
528
+ targetPath: normalizedTargetPath,
529
+ mode: "full",
530
+ outputDir,
531
+ });
532
+ deterministicBaseline = {
533
+ runId: deterministic.runId,
534
+ reportPath: deterministic.artifacts.markdownPath,
535
+ reportJsonPath: deterministic.artifacts.jsonPath,
536
+ summary: deterministic.summary,
537
+ findings: deterministic.findings,
538
+ };
539
+ }
540
+ }
541
+ emitAuditLifecycleEvent(
542
+ onEvent,
543
+ runId,
544
+ "phase_complete",
545
+ {
546
+ phase: "deterministic_baseline",
547
+ skipped: Boolean(dryRun),
548
+ reused: Boolean(omargateReuse.used),
549
+ omargateReuse,
550
+ durationMs: Math.max(0, Date.now() - baselineStartedAt),
551
+ findingCount: deterministicBaseline.findings.length,
552
+ summary: deterministicBaseline.summary,
553
+ },
554
+ dryRun
555
+ ? "Deterministic baseline skipped."
556
+ : omargateReuse.used
557
+ ? `Reused OmarGate deterministic baseline ${omargateReuse.runId}: ${deterministicBaseline.findings.length} finding(s).`
558
+ : `Deterministic baseline complete: ${deterministicBaseline.findings.length} finding(s).`
559
+ );
560
+ if (useDeterministicSeed) {
561
+ appendBlackboardFindings(blackboard, {
562
+ agentId: "omar",
563
+ findings: deterministicBaseline.findings,
564
+ source: omargateReuse.used ? "omargate-reuse" : "deterministic-baseline",
261
565
  });
262
- deterministicBaseline = {
263
- runId: deterministic.runId,
264
- reportPath: deterministic.artifacts.markdownPath,
265
- reportJsonPath: deterministic.artifacts.jsonPath,
266
- summary: deterministic.summary,
267
- findings: deterministic.findings,
268
- };
269
566
  }
270
- appendBlackboardFindings(blackboard, {
271
- agentId: "omar",
272
- findings: deterministicBaseline.findings,
273
- source: "deterministic-baseline",
274
- });
275
567
  const memoryProvider = resolveMemoryProvider(process.env);
276
568
  const memoryApiEndpoint = normalizeString(process.env.SENTINELAYER_MEMORY_API_ENDPOINT);
277
569
  const memoryApiKey = normalizeString(
@@ -288,16 +580,39 @@ export async function runAuditOrchestrator({
288
580
  const sharedMemoryQueries = [];
289
581
 
290
582
  const routeBuckets = new Map();
291
- for (const finding of deterministicBaseline.findings) {
292
- const bucketId = routeFindingToAgentId(finding);
293
- const existing = routeBuckets.get(bucketId) || [];
294
- existing.push(finding);
295
- routeBuckets.set(bucketId, existing);
583
+ if (useDeterministicSeed) {
584
+ for (const finding of deterministicBaseline.findings) {
585
+ const bucketId = routeFindingToAgentId(finding);
586
+ const existing = routeBuckets.get(bucketId) || [];
587
+ existing.push(finding);
588
+ routeBuckets.set(bucketId, existing);
589
+ }
296
590
  }
297
591
 
298
- const startedAt = Date.now();
299
- const agentResults = await runWithConcurrency(agents, maxParallel, async (agent) => {
592
+ const dispatchStartedAt = Date.now();
593
+ emitAuditLifecycleEvent(
594
+ onEvent,
595
+ runId,
596
+ "phase_start",
597
+ { phase: "dispatch", agentCount: agents.length, maxParallel: normalizedMaxParallel },
598
+ `Dispatching ${agents.length} audit persona(s).`
599
+ );
600
+ const agentResults = await runWithConcurrency(agents, normalizedMaxParallel, async (agent) => {
300
601
  const agentStart = Date.now();
602
+ emitAuditLifecycleEvent(
603
+ onEvent,
604
+ runId,
605
+ "dispatch",
606
+ {
607
+ phase: "dispatch",
608
+ agentId: agent.id,
609
+ persona: agent.persona,
610
+ domain: agent.domain,
611
+ isolation: isolationMode,
612
+ seedFromDeterministic: useDeterministicSeed,
613
+ },
614
+ `Dispatching ${agent.id} persona.`
615
+ );
301
616
  const sharedContext = queryBlackboard(blackboard, {
302
617
  query: `${agent.id} ${agent.domain} ${agent.persona}`,
303
618
  agentId: agent.id,
@@ -321,96 +636,60 @@ export async function runAuditOrchestrator({
321
636
  resultCount: Array.isArray(hybridContext.results) ? hybridContext.results.length : 0,
322
637
  apiError: hybridContext.apiError || "",
323
638
  });
324
- let findings = routeBuckets.get(agent.id) || [];
325
- let summary = severitySummary(findings);
326
- let confidence = computeConfidenceFloor(agent.confidenceFloor, findings.length);
327
- let specialistReportPath = "";
328
-
329
- if (agent.id === "security") {
330
- const securitySpecialist = runSecuritySpecialist({
331
- findings: deterministicBaseline.findings,
332
- });
333
- findings = securitySpecialist.findings;
334
- summary = securitySpecialist.summary;
335
- confidence = securitySpecialist.confidence;
336
- specialistReportPath = path.join(agentsDirectory, "SECURITY_AGENT_REPORT.md");
337
- await fsp.writeFile(
338
- specialistReportPath,
339
- `${renderSecuritySpecialistMarkdown(securitySpecialist).trim()}\n`,
340
- "utf-8"
341
- );
342
- } else if (agent.id === "architecture") {
343
- const architectureSpecialist = runArchitectureSpecialist({
344
- findings: deterministicBaseline.findings,
345
- ingest,
346
- });
347
- findings = architectureSpecialist.findings;
348
- summary = architectureSpecialist.summary;
349
- confidence = architectureSpecialist.confidence;
350
- specialistReportPath = path.join(agentsDirectory, "ARCHITECTURE_AGENT_REPORT.md");
351
- await fsp.writeFile(
352
- specialistReportPath,
353
- `${renderArchitectureSpecialistMarkdown(architectureSpecialist).trim()}\n`,
354
- "utf-8"
355
- );
356
- } else if (agent.id === "testing") {
357
- const testingSpecialist = runTestingSpecialist({
358
- findings: deterministicBaseline.findings,
359
- ingest,
360
- });
361
- findings = testingSpecialist.findings;
362
- summary = testingSpecialist.summary;
363
- confidence = testingSpecialist.confidence;
364
- specialistReportPath = path.join(agentsDirectory, "TESTING_AGENT_REPORT.md");
365
- await fsp.writeFile(
366
- specialistReportPath,
367
- `${renderTestingSpecialistMarkdown(testingSpecialist).trim()}\n`,
368
- "utf-8"
369
- );
370
- } else if (agent.id === "performance") {
371
- const performanceSpecialist = runPerformanceSpecialist({
372
- findings: deterministicBaseline.findings,
373
- ingest,
374
- });
375
- findings = performanceSpecialist.findings;
376
- summary = performanceSpecialist.summary;
377
- confidence = performanceSpecialist.confidence;
378
- specialistReportPath = path.join(agentsDirectory, "PERFORMANCE_AGENT_REPORT.md");
379
- await fsp.writeFile(
380
- specialistReportPath,
381
- `${renderPerformanceSpecialistMarkdown(performanceSpecialist).trim()}\n`,
382
- "utf-8"
383
- );
384
- } else if (agent.id === "compliance") {
385
- const complianceSpecialist = runComplianceSpecialist({
386
- findings: deterministicBaseline.findings,
387
- ingest,
388
- });
389
- findings = complianceSpecialist.findings;
390
- summary = complianceSpecialist.summary;
391
- confidence = complianceSpecialist.confidence;
392
- specialistReportPath = path.join(agentsDirectory, "COMPLIANCE_AGENT_REPORT.md");
393
- await fsp.writeFile(
394
- specialistReportPath,
395
- `${renderComplianceSpecialistMarkdown(complianceSpecialist).trim()}\n`,
396
- "utf-8"
397
- );
398
- } else if (agent.id === "documentation") {
399
- const documentationSpecialist = runDocumentationSpecialist({
400
- findings: deterministicBaseline.findings,
639
+ const personaDeterministicBaseline = useDeterministicSeed
640
+ ? deterministicBaseline
641
+ : { ...deterministicBaseline, findings: [] };
642
+ const routedFindings = useDeterministicSeed ? routeBuckets.get(agent.id) || [] : [];
643
+ const specialistSeed = useDeterministicSeed
644
+ ? await buildSpecialistSeed({
645
+ agent,
646
+ deterministicBaseline,
647
+ ingest,
648
+ agentsDirectory,
649
+ })
650
+ : {
651
+ findings: [],
652
+ summary: severitySummary([]),
653
+ confidence: computeConfidenceFloor(agent.confidenceFloor, 0),
654
+ specialistReportPath: "",
655
+ };
656
+ let findings = specialistSeed.findings.length > 0 ? specialistSeed.findings : routedFindings;
657
+ let summary = specialistSeed.findings.length > 0
658
+ ? specialistSeed.summary
659
+ : severitySummary(findings);
660
+ let confidence = specialistSeed.findings.length > 0
661
+ ? specialistSeed.confidence
662
+ : computeConfidenceFloor(agent.confidenceFloor, findings.length);
663
+ let specialistReportPath = specialistSeed.specialistReportPath;
664
+ let agenticReport = null;
665
+
666
+ if (agent.id !== "frontend") {
667
+ agenticReport = await runPersonaAgenticLoop({
668
+ agent,
669
+ rootPath: normalizedTargetPath,
401
670
  ingest,
671
+ deterministicBaseline: personaDeterministicBaseline,
672
+ seedFindings: findings,
673
+ sharedContext,
674
+ hybridContext,
675
+ artifactDir: agentsDirectory,
676
+ provider,
677
+ onEvent,
678
+ clientFactory,
679
+ dryRun: Boolean(dryRun),
680
+ isolation: isolationMode,
402
681
  });
403
- findings = documentationSpecialist.findings;
404
- summary = documentationSpecialist.summary;
405
- confidence = documentationSpecialist.confidence;
406
- specialistReportPath = path.join(agentsDirectory, "DOCUMENTATION_AGENT_REPORT.md");
407
- await fsp.writeFile(
408
- specialistReportPath,
409
- `${renderDocumentationSpecialistMarkdown(documentationSpecialist).trim()}\n`,
410
- "utf-8"
411
- );
682
+ findings = agenticReport.findings;
683
+ summary = agenticReport.summary;
684
+ confidence = agenticReport.confidence;
412
685
  }
413
686
 
687
+ const agentStatus = summary.blocking
688
+ ? "escalate"
689
+ : agenticReport && !["completed", "dry_run"].includes(agenticReport.status)
690
+ ? agenticReport.status
691
+ : "ok";
692
+
414
693
  const result = {
415
694
  agentId: agent.id,
416
695
  persona: agent.persona,
@@ -418,17 +697,34 @@ export async function runAuditOrchestrator({
418
697
  permissionMode: agent.permissionMode,
419
698
  maxTurns: agent.maxTurns,
420
699
  confidenceFloor: agent.confidenceFloor,
700
+ isolation: isolationMode,
701
+ seedFromDeterministic: useDeterministicSeed,
702
+ routedSeedFindingCount: routedFindings.length,
703
+ specialistSeedFindingCount: specialistSeed.findings.length,
421
704
  confidence,
422
705
  findingCount: findings.length,
423
706
  summary,
424
707
  findings: findings.slice(0, 120),
425
- status: summary.blocking ? "escalate" : "ok",
708
+ status: agentStatus,
426
709
  startedAt: new Date(agentStart).toISOString(),
427
710
  completedAt: new Date().toISOString(),
428
711
  durationMs: Math.max(0, Date.now() - agentStart),
429
712
  escalationTargets: agent.escalationTargets || [],
430
713
  evidenceRequirements: agent.evidenceRequirements || [],
431
714
  specialistReportPath,
715
+ agenticRunId: agenticReport?.runId || "",
716
+ agenticStatus: agenticReport?.status || (agent.id === "frontend" ? "preserved-frontend-flow" : ""),
717
+ agenticMessageHistoryLength: Number(agenticReport?.messageHistoryLength || 0),
718
+ swarm: agenticReport?.swarm || null,
719
+ usage: agenticReport?.usage || {
720
+ costUsd: 0,
721
+ outputTokens: 0,
722
+ toolCalls: 0,
723
+ durationMs: Math.max(0, Date.now() - agentStart),
724
+ filesRead: [],
725
+ },
726
+ grantedTools: agenticReport?.grantedTools || agent.tools || [],
727
+ availableTools: agenticReport?.availableTools || [],
432
728
  sharedContextEntryCount: sharedContext.entries.length,
433
729
  sharedContextPreview: sharedContext.entries.slice(0, 5).map((entry) => ({
434
730
  entryId: entry.entryId,
@@ -451,8 +747,8 @@ export async function runAuditOrchestrator({
451
747
  appendBlackboardFindings(blackboard, {
452
748
  agentId: agent.id,
453
749
  findings,
454
- source: "specialist-agent",
455
- note: `${agent.id} specialist finding`,
750
+ source: agenticReport ? "persona-agentic-loop" : "specialist-agent",
751
+ note: `${agent.id} persona finding`,
456
752
  confidence,
457
753
  });
458
754
  const agentPath = path.join(agentsDirectory, `${agent.id}.json`);
@@ -463,7 +759,35 @@ export async function runAuditOrchestrator({
463
759
  };
464
760
  });
465
761
  agentResults.sort((left, right) => left.agentId.localeCompare(right.agentId));
762
+ emitAuditLifecycleEvent(
763
+ onEvent,
764
+ runId,
765
+ "phase_complete",
766
+ {
767
+ phase: "dispatch",
768
+ durationMs: Math.max(0, Date.now() - dispatchStartedAt),
769
+ agentCount: agentResults.length,
770
+ statuses: agentResults.reduce((acc, result) => {
771
+ const status = normalizeString(result.status) || "unknown";
772
+ acc[status] = (acc[status] || 0) + 1;
773
+ return acc;
774
+ }, {}),
775
+ },
776
+ `Dispatch complete: ${agentResults.length} persona result(s).`
777
+ );
466
778
 
779
+ const reconcileStartedAt = Date.now();
780
+ const candidateFindingCount = agentResults.reduce(
781
+ (count, agent) => count + (Array.isArray(agent.findings) ? agent.findings.length : 0),
782
+ 0
783
+ );
784
+ emitAuditLifecycleEvent(
785
+ onEvent,
786
+ runId,
787
+ "reconcile_start",
788
+ { phase: "reconcile", agentCount: agentResults.length, candidateFindingCount },
789
+ `Reconciling ${candidateFindingCount} candidate finding(s).`
790
+ );
467
791
  const uniqueFindings = [];
468
792
  const seen = new Set();
469
793
  for (const agent of agentResults) {
@@ -479,6 +803,20 @@ export async function runAuditOrchestrator({
479
803
  }
480
804
  }
481
805
  const summary = severitySummary(uniqueFindings);
806
+ emitAuditLifecycleEvent(
807
+ onEvent,
808
+ runId,
809
+ "reconcile_complete",
810
+ {
811
+ phase: "reconcile",
812
+ durationMs: Math.max(0, Date.now() - reconcileStartedAt),
813
+ candidateFindingCount,
814
+ findingCount: uniqueFindings.length,
815
+ dedupedCount: Math.max(0, candidateFindingCount - uniqueFindings.length),
816
+ summary,
817
+ },
818
+ `Reconcile complete: ${uniqueFindings.length} unique finding(s).`
819
+ );
482
820
  const sharedMemoryArtifact = await writeBlackboardArtifact(blackboard, {
483
821
  outputRoot,
484
822
  });
@@ -494,7 +832,9 @@ export async function runAuditOrchestrator({
494
832
  outputRoot,
495
833
  runDirectory,
496
834
  dryRun: Boolean(dryRun),
497
- maxParallel: Math.max(1, Math.floor(Number(maxParallel || 1))),
835
+ maxParallel: normalizedMaxParallel,
836
+ isolation: isolationMode,
837
+ seedFromDeterministic: useDeterministicSeed,
498
838
  durationMs: Math.max(0, Date.now() - startedAt),
499
839
  ingest: {
500
840
  summary: ingest.summary,
@@ -513,6 +853,7 @@ export async function runAuditOrchestrator({
513
853
  },
514
854
  },
515
855
  deterministicBaseline,
856
+ omargateReuse,
516
857
  sharedMemory: {
517
858
  enabled: true,
518
859
  artifactPath: sharedMemoryArtifact.artifactPath,
@@ -533,7 +874,7 @@ export async function runAuditOrchestrator({
533
874
  historyRunDocumentCount: sharedMemoryCorpus.historyRunDocumentCount,
534
875
  },
535
876
  },
536
- selectedAgents: agents.map((agent) => agent.id),
877
+ selectedAgents: selectedAgentIds,
537
878
  agentResults,
538
879
  summary,
539
880
  };
@@ -546,6 +887,22 @@ export async function runAuditOrchestrator({
546
887
  report,
547
888
  runDirectory,
548
889
  });
890
+ emitAuditLifecycleEvent(
891
+ onEvent,
892
+ runId,
893
+ "orchestrator_complete",
894
+ {
895
+ runId,
896
+ targetPath: normalizedTargetPath,
897
+ durationMs: Math.max(0, Date.now() - startedAt),
898
+ agentCount: agentResults.length,
899
+ summary,
900
+ reportJsonPath,
901
+ reportMarkdownPath,
902
+ ddPackageManifestPath: ddPackage.manifestPath || "",
903
+ },
904
+ `Audit orchestrator complete: P0=${summary.P0} P1=${summary.P1} P2=${summary.P2} P3=${summary.P3}.`
905
+ );
549
906
 
550
907
  return {
551
908
  ...report,