sentinelayer-cli 0.1.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 (124) hide show
  1. package/README.md +996 -0
  2. package/bin/create-sentinelayer.js +5 -0
  3. package/bin/sentinelayer-cli.js +5 -0
  4. package/bin/sl.js +5 -0
  5. package/package.json +54 -0
  6. package/src/agents/jules/config/definition.js +209 -0
  7. package/src/agents/jules/config/system-prompt.js +175 -0
  8. package/src/agents/jules/error-intake.js +51 -0
  9. package/src/agents/jules/fix-cycle.js +377 -0
  10. package/src/agents/jules/loop.js +367 -0
  11. package/src/agents/jules/pulse.js +319 -0
  12. package/src/agents/jules/stream.js +186 -0
  13. package/src/agents/jules/swarm/file-scanner.js +74 -0
  14. package/src/agents/jules/swarm/index.js +11 -0
  15. package/src/agents/jules/swarm/orchestrator.js +362 -0
  16. package/src/agents/jules/swarm/pattern-hunter.js +123 -0
  17. package/src/agents/jules/swarm/sub-agent.js +308 -0
  18. package/src/agents/jules/tools/auth-audit.js +222 -0
  19. package/src/agents/jules/tools/dispatch.js +327 -0
  20. package/src/agents/jules/tools/file-edit.js +180 -0
  21. package/src/agents/jules/tools/file-read.js +100 -0
  22. package/src/agents/jules/tools/frontend-analyze.js +570 -0
  23. package/src/agents/jules/tools/glob.js +168 -0
  24. package/src/agents/jules/tools/grep.js +228 -0
  25. package/src/agents/jules/tools/index.js +29 -0
  26. package/src/agents/jules/tools/path-guards.js +161 -0
  27. package/src/agents/jules/tools/runtime-audit.js +409 -0
  28. package/src/agents/jules/tools/shell.js +383 -0
  29. package/src/ai/aidenid.js +945 -0
  30. package/src/ai/client.js +508 -0
  31. package/src/ai/domain-target-store.js +268 -0
  32. package/src/ai/identity-store.js +270 -0
  33. package/src/ai/site-store.js +145 -0
  34. package/src/audit/agents/architecture.js +180 -0
  35. package/src/audit/agents/compliance.js +179 -0
  36. package/src/audit/agents/documentation.js +165 -0
  37. package/src/audit/agents/performance.js +145 -0
  38. package/src/audit/agents/security.js +215 -0
  39. package/src/audit/agents/testing.js +172 -0
  40. package/src/audit/orchestrator.js +557 -0
  41. package/src/audit/package.js +204 -0
  42. package/src/audit/registry.js +284 -0
  43. package/src/audit/replay.js +103 -0
  44. package/src/auth/http.js +113 -0
  45. package/src/auth/service.js +848 -0
  46. package/src/auth/session-store.js +345 -0
  47. package/src/cli.js +244 -0
  48. package/src/commands/ai/identity-lifecycle.js +1337 -0
  49. package/src/commands/ai/provision-governance.js +1246 -0
  50. package/src/commands/ai/shared.js +147 -0
  51. package/src/commands/ai.js +11 -0
  52. package/src/commands/apply.js +19 -0
  53. package/src/commands/audit.js +1147 -0
  54. package/src/commands/auth.js +366 -0
  55. package/src/commands/chat.js +191 -0
  56. package/src/commands/config.js +184 -0
  57. package/src/commands/cost.js +311 -0
  58. package/src/commands/daemon/core.js +850 -0
  59. package/src/commands/daemon/extended.js +1048 -0
  60. package/src/commands/daemon/shared.js +213 -0
  61. package/src/commands/daemon.js +11 -0
  62. package/src/commands/guide.js +174 -0
  63. package/src/commands/ingest.js +58 -0
  64. package/src/commands/init.js +55 -0
  65. package/src/commands/legacy-args.js +30 -0
  66. package/src/commands/mcp.js +404 -0
  67. package/src/commands/omargate.js +21 -0
  68. package/src/commands/persona.js +27 -0
  69. package/src/commands/plugin.js +260 -0
  70. package/src/commands/policy.js +132 -0
  71. package/src/commands/prompt.js +238 -0
  72. package/src/commands/review.js +704 -0
  73. package/src/commands/scan.js +788 -0
  74. package/src/commands/spec.js +716 -0
  75. package/src/commands/swarm.js +651 -0
  76. package/src/commands/telemetry.js +202 -0
  77. package/src/commands/watch.js +510 -0
  78. package/src/config/agent-dictionary.js +182 -0
  79. package/src/config/io.js +56 -0
  80. package/src/config/paths.js +18 -0
  81. package/src/config/schema.js +55 -0
  82. package/src/config/service.js +184 -0
  83. package/src/cost/budget.js +235 -0
  84. package/src/cost/history.js +188 -0
  85. package/src/cost/tracker.js +171 -0
  86. package/src/daemon/artifact-lineage.js +534 -0
  87. package/src/daemon/assignment-ledger.js +770 -0
  88. package/src/daemon/ast-parser-layer.js +258 -0
  89. package/src/daemon/budget-governor.js +633 -0
  90. package/src/daemon/callgraph-overlay.js +646 -0
  91. package/src/daemon/error-worker.js +626 -0
  92. package/src/daemon/hybrid-mapper.js +929 -0
  93. package/src/daemon/jira-lifecycle.js +632 -0
  94. package/src/daemon/operator-control.js +657 -0
  95. package/src/daemon/reliability-lane.js +471 -0
  96. package/src/daemon/watchdog.js +971 -0
  97. package/src/guide/generator.js +316 -0
  98. package/src/ingest/engine.js +918 -0
  99. package/src/legacy-cli.js +2435 -0
  100. package/src/mcp/registry.js +695 -0
  101. package/src/memory/blackboard.js +301 -0
  102. package/src/memory/retrieval.js +581 -0
  103. package/src/plugin/manifest.js +553 -0
  104. package/src/policy/packs.js +144 -0
  105. package/src/prompt/generator.js +106 -0
  106. package/src/review/ai-review.js +669 -0
  107. package/src/review/local-review.js +1284 -0
  108. package/src/review/replay.js +235 -0
  109. package/src/review/report.js +664 -0
  110. package/src/review/spec-binding.js +487 -0
  111. package/src/scan/generator.js +351 -0
  112. package/src/spec/generator.js +519 -0
  113. package/src/spec/regenerate.js +237 -0
  114. package/src/spec/templates.js +91 -0
  115. package/src/swarm/dashboard.js +247 -0
  116. package/src/swarm/factory.js +363 -0
  117. package/src/swarm/pentest.js +934 -0
  118. package/src/swarm/registry.js +419 -0
  119. package/src/swarm/report.js +158 -0
  120. package/src/swarm/runtime.js +576 -0
  121. package/src/swarm/scenario-dsl.js +272 -0
  122. package/src/telemetry/ledger.js +302 -0
  123. package/src/ui/markdown.js +220 -0
  124. package/src/ui/progress.js +100 -0
@@ -0,0 +1,557 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import fsp from "node:fs/promises";
3
+ import path from "node:path";
4
+
5
+ import { resolveOutputRoot } from "../config/service.js";
6
+ import { resolveCodebaseIngest } from "../ingest/engine.js";
7
+ import { runDeterministicReviewPipeline } from "../review/local-review.js";
8
+ import {
9
+ renderArchitectureSpecialistMarkdown,
10
+ runArchitectureSpecialist,
11
+ } from "./agents/architecture.js";
12
+ import {
13
+ renderComplianceSpecialistMarkdown,
14
+ runComplianceSpecialist,
15
+ } from "./agents/compliance.js";
16
+ import {
17
+ renderDocumentationSpecialistMarkdown,
18
+ runDocumentationSpecialist,
19
+ } from "./agents/documentation.js";
20
+ import {
21
+ renderPerformanceSpecialistMarkdown,
22
+ runPerformanceSpecialist,
23
+ } from "./agents/performance.js";
24
+ import {
25
+ renderSecuritySpecialistMarkdown,
26
+ runSecuritySpecialist,
27
+ } from "./agents/security.js";
28
+ import {
29
+ renderTestingSpecialistMarkdown,
30
+ runTestingSpecialist,
31
+ } from "./agents/testing.js";
32
+ import { writeDdPackage } from "./package.js";
33
+ import {
34
+ appendBlackboardFindings,
35
+ createBlackboard,
36
+ queryBlackboard,
37
+ summarizeBlackboard,
38
+ writeBlackboardArtifact,
39
+ } from "../memory/blackboard.js";
40
+ import {
41
+ buildDocumentsFromBlackboardEntries,
42
+ buildSharedMemoryCorpus,
43
+ queryHybridRetriever,
44
+ } from "../memory/retrieval.js";
45
+
46
+ function normalizeString(value) {
47
+ return String(value || "").trim();
48
+ }
49
+
50
+ function toPosixPath(value) {
51
+ return String(value || "").replace(/\\/g, "/");
52
+ }
53
+
54
+ function formatTimestampToken() {
55
+ const now = new Date();
56
+ const pad = (value) => String(value).padStart(2, "0");
57
+ return `${now.getUTCFullYear()}${pad(now.getUTCMonth() + 1)}${pad(now.getUTCDate())}-${pad(
58
+ now.getUTCHours()
59
+ )}${pad(now.getUTCMinutes())}${pad(now.getUTCSeconds())}`;
60
+ }
61
+
62
+ function resolveMemoryProvider(env = process.env) {
63
+ const provider = normalizeString(env.SENTINELAYER_MEMORY_PROVIDER).toLowerCase();
64
+ if (provider === "api" || provider === "auto" || provider === "local") {
65
+ return provider;
66
+ }
67
+ return "local";
68
+ }
69
+
70
+ function severitySummary(findings = []) {
71
+ const summary = {
72
+ P0: 0,
73
+ P1: 0,
74
+ P2: 0,
75
+ P3: 0,
76
+ };
77
+ for (const finding of findings) {
78
+ const severity = normalizeString(finding.severity).toUpperCase();
79
+ if (Object.prototype.hasOwnProperty.call(summary, severity)) {
80
+ summary[severity] += 1;
81
+ }
82
+ }
83
+ summary.blocking = summary.P0 > 0 || summary.P1 > 0;
84
+ return summary;
85
+ }
86
+
87
+ function routeFindingToAgentId(finding = {}) {
88
+ const file = normalizeString(finding.file).toLowerCase();
89
+ const message = normalizeString(finding.message).toLowerCase();
90
+ const layer = normalizeString(finding.layer).toLowerCase();
91
+ const combined = `${file} ${message} ${layer}`;
92
+
93
+ if (/token|secret|credential|auth|tls|cors|xss|sql|jwt|injection/.test(combined)) {
94
+ return "security";
95
+ }
96
+ if (/test|coverage|lint|typecheck|static analysis/.test(combined)) {
97
+ return "testing";
98
+ }
99
+ if (/query|loop|n\+1|performance|latency|runtime/.test(combined)) {
100
+ return "performance";
101
+ }
102
+ if (/compliance|soc2|hipaa|gdpr|privacy|pii|control|evidence|retention/.test(combined)) {
103
+ return "compliance";
104
+ }
105
+ if (/spec|documentation|docs\//.test(combined)) {
106
+ return "documentation";
107
+ }
108
+ if (/workflow|release|deploy|npm publish|ci/.test(combined)) {
109
+ return "release";
110
+ }
111
+ if (/terraform|infra|kubernetes|ecs|aws/.test(combined)) {
112
+ return "infrastructure";
113
+ }
114
+ if (/observability|telemetry|trace|metrics|log/.test(combined)) {
115
+ return "observability";
116
+ }
117
+ if (/frontend|tsx|jsx|component|react/.test(combined)) {
118
+ return "frontend";
119
+ }
120
+ if (/database|sql|migration|schema|data layer/.test(combined)) {
121
+ return "data-layer";
122
+ }
123
+ if (/dependency|supply|package-lock|lockfile/.test(combined)) {
124
+ return "supply-chain";
125
+ }
126
+ if (/ai|model|prompt|budget/.test(combined)) {
127
+ return "ai-governance";
128
+ }
129
+ if (/reliability|timeout|retry|fallback/.test(combined)) {
130
+ return "reliability";
131
+ }
132
+ return "architecture";
133
+ }
134
+
135
+ function computeConfidenceFloor(base, findingCount) {
136
+ const floor = Number(base || 0);
137
+ const normalizedFloor = Number.isFinite(floor) ? Math.max(0, Math.min(1, floor)) : 0.7;
138
+ if (findingCount <= 0) {
139
+ return Math.min(0.99, normalizedFloor + 0.05);
140
+ }
141
+ const damping = Math.min(0.2, findingCount * 0.01);
142
+ return Math.max(0.5, Math.min(0.99, normalizedFloor - damping));
143
+ }
144
+
145
+ async function runWithConcurrency(items, maxParallel, worker) {
146
+ const results = [];
147
+ const queue = [...items];
148
+ const limit = Math.max(1, Math.floor(Number(maxParallel || 1)));
149
+ const runners = [];
150
+ for (let index = 0; index < limit; index += 1) {
151
+ runners.push(
152
+ (async () => {
153
+ while (queue.length > 0) {
154
+ const nextItem = queue.shift();
155
+ if (!nextItem) {
156
+ continue;
157
+ }
158
+ const value = await worker(nextItem);
159
+ results.push(value);
160
+ }
161
+ })()
162
+ );
163
+ }
164
+ await Promise.all(runners);
165
+ return results;
166
+ }
167
+
168
+ function buildAuditMarkdown(report = {}) {
169
+ const agentLines = (report.agentResults || [])
170
+ .map(
171
+ (agent) =>
172
+ `- ${agent.agentId} (${agent.persona}, ${agent.domain}) findings=${agent.findingCount} confidence=${(
173
+ agent.confidence * 100
174
+ ).toFixed(0)}% status=${agent.status}`
175
+ )
176
+ .join("\n");
177
+
178
+ return `# AUDIT_REPORT
179
+
180
+ Generated: ${report.generatedAt}
181
+ Run ID: ${report.runId}
182
+ Target: ${report.targetPath}
183
+ Max parallel: ${report.maxParallel}
184
+ Dry run: ${report.dryRun ? "yes" : "no"}
185
+
186
+ Summary:
187
+ - Findings: P0=${report.summary.P0} P1=${report.summary.P1} P2=${report.summary.P2} P3=${report.summary.P3}
188
+ - Blocking: ${report.summary.blocking ? "yes" : "no"}
189
+ - Agents: ${report.agentResults.length}
190
+
191
+ Shared memory:
192
+ - Enabled: ${report.sharedMemory?.enabled ? "yes" : "no"}
193
+ - Entries: ${report.sharedMemory?.entryCount || 0}
194
+ - Queries: ${report.sharedMemory?.queryCount || 0}
195
+ - Corpus docs: ${report.sharedMemory?.corpusDocumentCount || 0}
196
+ - Retrieval provider: ${report.sharedMemory?.retrieval?.providerRequested || "local"}
197
+ - Providers used: ${(report.sharedMemory?.retrieval?.providersUsed || []).join(", ") || "local"}
198
+ - Artifact: ${report.sharedMemory?.artifactPath || "n/a"}
199
+
200
+ Ingest:
201
+ - Files scanned: ${report.ingest.summary.filesScanned}
202
+ - LOC: ${report.ingest.summary.totalLoc}
203
+ - Frameworks: ${report.ingest.frameworks.join(", ") || "none"}
204
+ - Risk surfaces: ${report.ingest.riskSurfaces.join(", ") || "none"}
205
+ - Refresh: ${report.ingest.refresh?.refreshed ? "yes" : "no"}
206
+ - Stale: ${report.ingest.refresh?.stale ? "yes" : "no"}
207
+ - Refresh reasons: ${(report.ingest.refresh?.reasons || []).join(", ") || "none"}
208
+
209
+ Deterministic baseline:
210
+ - Run ID: ${report.deterministicBaseline.runId || "n/a"}
211
+ - Report: ${report.deterministicBaseline.reportPath || "n/a"}
212
+ - Summary: P0=${report.deterministicBaseline.summary.P0} P1=${report.deterministicBaseline.summary.P1} P2=${report.deterministicBaseline.summary.P2} P3=${report.deterministicBaseline.summary.P3}
213
+
214
+ Agent outcomes:
215
+ ${agentLines || "- none"}
216
+ `;
217
+ }
218
+
219
+ export async function runAuditOrchestrator({
220
+ targetPath,
221
+ agents = [],
222
+ maxParallel = 3,
223
+ outputDir = "",
224
+ dryRun = false,
225
+ refreshIngest = false,
226
+ } = {}) {
227
+ const normalizedTargetPath = path.resolve(String(targetPath || "."));
228
+ const outputRoot = await resolveOutputRoot({
229
+ cwd: normalizedTargetPath,
230
+ outputDirOverride: outputDir,
231
+ env: process.env,
232
+ });
233
+ const runId = `audit-${formatTimestampToken()}-${randomUUID().slice(0, 8)}`;
234
+ const runDirectory = path.join(outputRoot, "audits", runId);
235
+ const agentsDirectory = path.join(runDirectory, "agents");
236
+ await fsp.mkdir(agentsDirectory, { recursive: true });
237
+ const blackboard = createBlackboard({
238
+ runId,
239
+ scope: "audit-orchestrator",
240
+ });
241
+
242
+ const ingestResolution = await resolveCodebaseIngest({
243
+ rootPath: normalizedTargetPath,
244
+ outputDir,
245
+ refresh: Boolean(refreshIngest),
246
+ });
247
+ const ingest = ingestResolution.ingest;
248
+
249
+ let deterministicBaseline = {
250
+ runId: "",
251
+ reportPath: "",
252
+ reportJsonPath: "",
253
+ summary: { P0: 0, P1: 0, P2: 0, P3: 0, blocking: false },
254
+ findings: [],
255
+ };
256
+ if (!dryRun) {
257
+ const deterministic = await runDeterministicReviewPipeline({
258
+ targetPath: normalizedTargetPath,
259
+ mode: "full",
260
+ outputDir,
261
+ });
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
+ }
270
+ appendBlackboardFindings(blackboard, {
271
+ agentId: "omar",
272
+ findings: deterministicBaseline.findings,
273
+ source: "deterministic-baseline",
274
+ });
275
+ const memoryProvider = resolveMemoryProvider(process.env);
276
+ const memoryApiEndpoint = normalizeString(process.env.SENTINELAYER_MEMORY_API_ENDPOINT);
277
+ const memoryApiKey = normalizeString(
278
+ process.env.SENTINELAYER_MEMORY_API_KEY ||
279
+ process.env.SENTINELAYER_TOKEN ||
280
+ process.env.SENTINELAYER_API_TOKEN
281
+ );
282
+ const sharedMemoryCorpus = await buildSharedMemoryCorpus({
283
+ outputRoot,
284
+ targetPath: normalizedTargetPath,
285
+ ingest,
286
+ excludeRunId: runId,
287
+ });
288
+ const sharedMemoryQueries = [];
289
+
290
+ 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);
296
+ }
297
+
298
+ const startedAt = Date.now();
299
+ const agentResults = await runWithConcurrency(agents, maxParallel, async (agent) => {
300
+ const agentStart = Date.now();
301
+ const sharedContext = queryBlackboard(blackboard, {
302
+ query: `${agent.id} ${agent.domain} ${agent.persona}`,
303
+ agentId: agent.id,
304
+ limit: 24,
305
+ });
306
+ const hybridContext = await queryHybridRetriever({
307
+ query: `${agent.id} ${agent.domain} ${agent.persona}`,
308
+ documents: [
309
+ ...buildDocumentsFromBlackboardEntries(blackboard.entries),
310
+ ...sharedMemoryCorpus.documents,
311
+ ],
312
+ limit: 24,
313
+ provider: memoryProvider,
314
+ apiEndpoint: memoryApiEndpoint,
315
+ apiKey: memoryApiKey,
316
+ });
317
+ sharedMemoryQueries.push({
318
+ agentId: agent.id,
319
+ providerUsed: hybridContext.providerUsed,
320
+ apiFallback: Boolean(hybridContext.apiFallback),
321
+ resultCount: Array.isArray(hybridContext.results) ? hybridContext.results.length : 0,
322
+ apiError: hybridContext.apiError || "",
323
+ });
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,
401
+ ingest,
402
+ });
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
+ );
412
+ }
413
+
414
+ const result = {
415
+ agentId: agent.id,
416
+ persona: agent.persona,
417
+ domain: agent.domain,
418
+ permissionMode: agent.permissionMode,
419
+ maxTurns: agent.maxTurns,
420
+ confidenceFloor: agent.confidenceFloor,
421
+ confidence,
422
+ findingCount: findings.length,
423
+ summary,
424
+ findings: findings.slice(0, 120),
425
+ status: summary.blocking ? "escalate" : "ok",
426
+ startedAt: new Date(agentStart).toISOString(),
427
+ completedAt: new Date().toISOString(),
428
+ durationMs: Math.max(0, Date.now() - agentStart),
429
+ escalationTargets: agent.escalationTargets || [],
430
+ evidenceRequirements: agent.evidenceRequirements || [],
431
+ specialistReportPath,
432
+ sharedContextEntryCount: sharedContext.entries.length,
433
+ sharedContextPreview: sharedContext.entries.slice(0, 5).map((entry) => ({
434
+ entryId: entry.entryId,
435
+ severity: entry.severity,
436
+ file: entry.file,
437
+ line: entry.line,
438
+ message: entry.message,
439
+ })),
440
+ hybridContextProvider: hybridContext.providerUsed,
441
+ hybridContextApiFallback: Boolean(hybridContext.apiFallback),
442
+ hybridContextEntryCount: Array.isArray(hybridContext.results) ? hybridContext.results.length : 0,
443
+ hybridContextPreview: (hybridContext.results || []).slice(0, 5).map((entry) => ({
444
+ documentId: entry.documentId || "",
445
+ sourceType: entry.sourceType || "",
446
+ severity: entry.severity || "P3",
447
+ score: Number(entry.score || 0),
448
+ snippet: normalizeString(entry.snippet || ""),
449
+ })),
450
+ };
451
+ appendBlackboardFindings(blackboard, {
452
+ agentId: agent.id,
453
+ findings,
454
+ source: "specialist-agent",
455
+ note: `${agent.id} specialist finding`,
456
+ confidence,
457
+ });
458
+ const agentPath = path.join(agentsDirectory, `${agent.id}.json`);
459
+ await fsp.writeFile(agentPath, `${JSON.stringify(result, null, 2)}\n`, "utf-8");
460
+ return {
461
+ ...result,
462
+ artifactPath: agentPath,
463
+ };
464
+ });
465
+ agentResults.sort((left, right) => left.agentId.localeCompare(right.agentId));
466
+
467
+ const uniqueFindings = [];
468
+ const seen = new Set();
469
+ for (const agent of agentResults) {
470
+ for (const finding of agent.findings || []) {
471
+ const key = `${toPosixPath(finding.file)}:${finding.line}:${normalizeString(
472
+ finding.message
473
+ ).toLowerCase()}`;
474
+ if (seen.has(key)) {
475
+ continue;
476
+ }
477
+ seen.add(key);
478
+ uniqueFindings.push(finding);
479
+ }
480
+ }
481
+ const summary = severitySummary(uniqueFindings);
482
+ const sharedMemoryArtifact = await writeBlackboardArtifact(blackboard, {
483
+ outputRoot,
484
+ });
485
+ const sharedMemorySummary = summarizeBlackboard(blackboard);
486
+ sharedMemoryQueries.sort((left, right) => left.agentId.localeCompare(right.agentId));
487
+ const providersUsed = Array.from(new Set(sharedMemoryQueries.map((item) => item.providerUsed))).sort();
488
+
489
+ const report = {
490
+ schemaVersion: "1.0.0",
491
+ generatedAt: new Date().toISOString(),
492
+ runId,
493
+ targetPath: normalizedTargetPath,
494
+ outputRoot,
495
+ runDirectory,
496
+ dryRun: Boolean(dryRun),
497
+ maxParallel: Math.max(1, Math.floor(Number(maxParallel || 1))),
498
+ durationMs: Math.max(0, Date.now() - startedAt),
499
+ ingest: {
500
+ summary: ingest.summary,
501
+ frameworks: Array.isArray(ingest.frameworks) ? ingest.frameworks : [],
502
+ riskSurfaces: Array.isArray(ingest.riskSurfaces)
503
+ ? ingest.riskSurfaces.map((item) => item.surface)
504
+ : [],
505
+ refresh: {
506
+ outputPath: ingestResolution.outputPath,
507
+ refreshed: ingestResolution.refreshed,
508
+ stale: ingestResolution.stale,
509
+ reasons: ingestResolution.reasons,
510
+ refreshedBecause: ingestResolution.refreshedBecause,
511
+ lastCommitAt: ingestResolution.lastCommitAt,
512
+ contentHash: ingestResolution.fingerprint?.contentHash || "",
513
+ },
514
+ },
515
+ deterministicBaseline,
516
+ sharedMemory: {
517
+ enabled: true,
518
+ artifactPath: sharedMemoryArtifact.artifactPath,
519
+ entryCount: sharedMemorySummary.entryCount,
520
+ queryCount: sharedMemorySummary.queryCount,
521
+ severity: sharedMemorySummary.severity,
522
+ createdAt: sharedMemorySummary.createdAt,
523
+ updatedAt: sharedMemorySummary.updatedAt,
524
+ corpusDocumentCount: sharedMemoryCorpus.documents.length,
525
+ corpusSourceCounts: sharedMemoryCorpus.sourceCounts,
526
+ retrieval: {
527
+ providerRequested: memoryProvider,
528
+ apiDelegationEnabled: Boolean(memoryApiEndpoint),
529
+ providersUsed,
530
+ queryCount: sharedMemoryQueries.length,
531
+ queries: sharedMemoryQueries,
532
+ hasSpecDocument: sharedMemoryCorpus.hasSpecDocument,
533
+ historyRunDocumentCount: sharedMemoryCorpus.historyRunDocumentCount,
534
+ },
535
+ },
536
+ selectedAgents: agents.map((agent) => agent.id),
537
+ agentResults,
538
+ summary,
539
+ };
540
+
541
+ const reportJsonPath = path.join(runDirectory, "AUDIT_REPORT.json");
542
+ const reportMarkdownPath = path.join(runDirectory, "AUDIT_REPORT.md");
543
+ await fsp.writeFile(reportJsonPath, `${JSON.stringify(report, null, 2)}\n`, "utf-8");
544
+ await fsp.writeFile(reportMarkdownPath, `${buildAuditMarkdown(report).trim()}\n`, "utf-8");
545
+ const ddPackage = await writeDdPackage({
546
+ report,
547
+ runDirectory,
548
+ });
549
+
550
+ return {
551
+ ...report,
552
+ reportJsonPath,
553
+ reportMarkdownPath,
554
+ ddPackage,
555
+ };
556
+ }
557
+