@vigolium/piolium 0.0.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.
Files changed (271) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +117 -0
  3. package/agents/access-auditor.md +300 -0
  4. package/agents/assumption-breaker.md +154 -0
  5. package/agents/attack-designer.md +116 -0
  6. package/agents/code-scanner.md +139 -0
  7. package/agents/concurrency-auditor.md +238 -0
  8. package/agents/confirm-writer.md +257 -0
  9. package/agents/context-reviewer.md +274 -0
  10. package/agents/cross-verifier.md +165 -0
  11. package/agents/cve-scout.md +381 -0
  12. package/agents/env-builder.md +282 -0
  13. package/agents/env-profiler.md +205 -0
  14. package/agents/evidence-collector.md +140 -0
  15. package/agents/finding-grader.md +142 -0
  16. package/agents/finding-writer.md +148 -0
  17. package/agents/flow-tracer.md +106 -0
  18. package/agents/goal-backtracer.md +146 -0
  19. package/agents/history-miner.md +467 -0
  20. package/agents/independent-verifier.md +118 -0
  21. package/agents/intent-mapper.md +183 -0
  22. package/agents/longshot-collector.md +128 -0
  23. package/agents/longshot-prober.md +126 -0
  24. package/agents/patch-auditor.md +73 -0
  25. package/agents/poc-author.md +124 -0
  26. package/agents/poc-runner.md +194 -0
  27. package/agents/probe-lead.md +269 -0
  28. package/agents/red-challenger.md +101 -0
  29. package/agents/report-composer.md +208 -0
  30. package/agents/review-adjudicator.md +216 -0
  31. package/agents/spec-auditor.md +155 -0
  32. package/agents/taint-tracer.md +265 -0
  33. package/agents/test-locator.md +209 -0
  34. package/agents/threat-modeler.md +132 -0
  35. package/agents/variant-scanner.md +108 -0
  36. package/agents/variant-spotter.md +110 -0
  37. package/bin/piolium.mjs +376 -0
  38. package/extensions/piolium/_vendor/yaml.bundle.d.mts +6 -0
  39. package/extensions/piolium/_vendor/yaml.bundle.mjs +139 -0
  40. package/extensions/piolium/agent-runner.ts +322 -0
  41. package/extensions/piolium/agents.ts +266 -0
  42. package/extensions/piolium/audit-state.ts +522 -0
  43. package/extensions/piolium/bundled-resources.ts +97 -0
  44. package/extensions/piolium/candidate-scan.ts +966 -0
  45. package/extensions/piolium/command-target.ts +177 -0
  46. package/extensions/piolium/console-stream.ts +57 -0
  47. package/extensions/piolium/export-results.ts +380 -0
  48. package/extensions/piolium/findings.ts +448 -0
  49. package/extensions/piolium/heartbeat.ts +182 -0
  50. package/extensions/piolium/help.ts +234 -0
  51. package/extensions/piolium/index.ts +1865 -0
  52. package/extensions/piolium/longshot.ts +530 -0
  53. package/extensions/piolium/matcher-suggestions.ts +196 -0
  54. package/extensions/piolium/matcher-utils.ts +83 -0
  55. package/extensions/piolium/modes/balanced.ts +750 -0
  56. package/extensions/piolium/modes/confirm-bootstrap.ts +186 -0
  57. package/extensions/piolium/modes/confirm.ts +697 -0
  58. package/extensions/piolium/modes/deep.ts +917 -0
  59. package/extensions/piolium/modes/diff.ts +177 -0
  60. package/extensions/piolium/modes/lite.ts +540 -0
  61. package/extensions/piolium/modes/longshot.ts +595 -0
  62. package/extensions/piolium/modes/merge.ts +204 -0
  63. package/extensions/piolium/modes/phase-runner.ts +267 -0
  64. package/extensions/piolium/modes/reinvest.ts +546 -0
  65. package/extensions/piolium/modes/revisit.ts +279 -0
  66. package/extensions/piolium/modes.ts +48 -0
  67. package/extensions/piolium/phase-labels.ts +123 -0
  68. package/extensions/piolium/phase-status-strip.ts +92 -0
  69. package/extensions/piolium/prompt-prefix-editor.ts +39 -0
  70. package/extensions/piolium/providers/anthropic-vertex.ts +836 -0
  71. package/extensions/piolium/recon.ts +409 -0
  72. package/extensions/piolium/result-stats.ts +105 -0
  73. package/extensions/piolium/retry.ts +120 -0
  74. package/extensions/piolium/scheduler.ts +212 -0
  75. package/extensions/piolium/secrets.ts +368 -0
  76. package/extensions/piolium/tools/web-tools.ts +148 -0
  77. package/package.json +77 -0
  78. package/skills/agentic-actions-auditor/SKILL.md +327 -0
  79. package/skills/agentic-actions-auditor/references/action-profiles.md +186 -0
  80. package/skills/agentic-actions-auditor/references/cross-file-resolution.md +209 -0
  81. package/skills/agentic-actions-auditor/references/foundations.md +94 -0
  82. package/skills/agentic-actions-auditor/references/vector-a-env-var-intermediary.md +77 -0
  83. package/skills/agentic-actions-auditor/references/vector-b-direct-expression-injection.md +83 -0
  84. package/skills/agentic-actions-auditor/references/vector-c-cli-data-fetch.md +83 -0
  85. package/skills/agentic-actions-auditor/references/vector-d-pr-target-checkout.md +88 -0
  86. package/skills/agentic-actions-auditor/references/vector-e-error-log-injection.md +88 -0
  87. package/skills/agentic-actions-auditor/references/vector-f-subshell-expansion.md +82 -0
  88. package/skills/agentic-actions-auditor/references/vector-g-eval-of-ai-output.md +91 -0
  89. package/skills/agentic-actions-auditor/references/vector-h-dangerous-sandbox-configs.md +102 -0
  90. package/skills/agentic-actions-auditor/references/vector-i-wildcard-allowlists.md +88 -0
  91. package/skills/audit/SKILL.md +562 -0
  92. package/skills/audit/assets/icon.svg +7 -0
  93. package/skills/audit/hooks/scripts/validate_phase_output.py +550 -0
  94. package/skills/audit/references/adversarial-review.md +148 -0
  95. package/skills/audit/references/architecture-aware-sast.md +306 -0
  96. package/skills/audit/references/audit-workflow.md +737 -0
  97. package/skills/audit/references/chamber-protocol.md +384 -0
  98. package/skills/audit/references/creative-attack-modes.md +221 -0
  99. package/skills/audit/references/deep-analysis.md +273 -0
  100. package/skills/audit/references/domain-attack-playbooks.md +1129 -0
  101. package/skills/audit/references/knowledge-base-template.md +513 -0
  102. package/skills/audit/references/real-env-validation.md +191 -0
  103. package/skills/audit/references/report-templates.md +417 -0
  104. package/skills/audit/references/triage-and-prereqs.md +134 -0
  105. package/skills/audit/scripts/consolidate_drafts.py +554 -0
  106. package/skills/audit/scripts/partition_findings.py +152 -0
  107. package/skills/audit/scripts/rg-hotspots.sh +121 -0
  108. package/skills/audit/scripts/stamp_file_state.py +349 -0
  109. package/skills/code-reviewer/SKILL.md +65 -0
  110. package/skills/codeql/SKILL.md +281 -0
  111. package/skills/codeql/references/build-fixes.md +90 -0
  112. package/skills/codeql/references/diagnostic-query-templates.md +339 -0
  113. package/skills/codeql/references/extension-yaml-format.md +209 -0
  114. package/skills/codeql/references/important-only-suite.md +153 -0
  115. package/skills/codeql/references/language-details.md +207 -0
  116. package/skills/codeql/references/macos-arm64e-workaround.md +179 -0
  117. package/skills/codeql/references/performance-tuning.md +111 -0
  118. package/skills/codeql/references/quality-assessment.md +172 -0
  119. package/skills/codeql/references/ruleset-catalog.md +63 -0
  120. package/skills/codeql/references/run-all-suite.md +92 -0
  121. package/skills/codeql/references/sarif-processing.md +79 -0
  122. package/skills/codeql/references/threat-models.md +51 -0
  123. package/skills/codeql/workflows/build-database.md +280 -0
  124. package/skills/codeql/workflows/create-data-extensions.md +261 -0
  125. package/skills/codeql/workflows/run-analysis.md +301 -0
  126. package/skills/differential-review/SKILL.md +220 -0
  127. package/skills/differential-review/adversarial.md +203 -0
  128. package/skills/differential-review/methodology.md +234 -0
  129. package/skills/differential-review/patterns.md +300 -0
  130. package/skills/differential-review/reporting.md +369 -0
  131. package/skills/fp-check/SKILL.md +125 -0
  132. package/skills/fp-check/references/bug-class-verification.md +114 -0
  133. package/skills/fp-check/references/deep-verification.md +143 -0
  134. package/skills/fp-check/references/evidence-templates.md +91 -0
  135. package/skills/fp-check/references/false-positive-patterns.md +115 -0
  136. package/skills/fp-check/references/gate-reviews.md +27 -0
  137. package/skills/fp-check/references/standard-verification.md +78 -0
  138. package/skills/insecure-defaults/SKILL.md +117 -0
  139. package/skills/insecure-defaults/references/examples.md +409 -0
  140. package/skills/last30days/SKILL.md +444 -0
  141. package/skills/sarif-parsing/SKILL.md +483 -0
  142. package/skills/sarif-parsing/resources/jq-queries.md +162 -0
  143. package/skills/sarif-parsing/resources/sarif_helpers.py +331 -0
  144. package/skills/security-threat-model/LICENSE.txt +201 -0
  145. package/skills/security-threat-model/SKILL.md +81 -0
  146. package/skills/security-threat-model/agents/openai.yaml +4 -0
  147. package/skills/security-threat-model/references/prompt-template.md +255 -0
  148. package/skills/security-threat-model/references/security-controls-and-assets.md +32 -0
  149. package/skills/semgrep/SKILL.md +212 -0
  150. package/skills/semgrep/references/rulesets.md +162 -0
  151. package/skills/semgrep/references/scan-modes.md +110 -0
  152. package/skills/semgrep/references/scanner-task-prompt.md +140 -0
  153. package/skills/semgrep/scripts/merge_sarif.py +203 -0
  154. package/skills/semgrep/workflows/scan-workflow.md +311 -0
  155. package/skills/semgrep-rule-creator/SKILL.md +168 -0
  156. package/skills/semgrep-rule-creator/references/quick-reference.md +202 -0
  157. package/skills/semgrep-rule-creator/references/workflow.md +240 -0
  158. package/skills/semgrep-rule-variant-creator/SKILL.md +205 -0
  159. package/skills/semgrep-rule-variant-creator/references/applicability-analysis.md +250 -0
  160. package/skills/semgrep-rule-variant-creator/references/language-syntax-guide.md +324 -0
  161. package/skills/semgrep-rule-variant-creator/references/workflow.md +518 -0
  162. package/skills/sharp-edges/SKILL.md +292 -0
  163. package/skills/sharp-edges/references/auth-patterns.md +252 -0
  164. package/skills/sharp-edges/references/case-studies.md +274 -0
  165. package/skills/sharp-edges/references/config-patterns.md +333 -0
  166. package/skills/sharp-edges/references/crypto-apis.md +190 -0
  167. package/skills/sharp-edges/references/lang-c.md +205 -0
  168. package/skills/sharp-edges/references/lang-csharp.md +285 -0
  169. package/skills/sharp-edges/references/lang-go.md +270 -0
  170. package/skills/sharp-edges/references/lang-java.md +263 -0
  171. package/skills/sharp-edges/references/lang-javascript.md +269 -0
  172. package/skills/sharp-edges/references/lang-kotlin.md +265 -0
  173. package/skills/sharp-edges/references/lang-php.md +245 -0
  174. package/skills/sharp-edges/references/lang-python.md +274 -0
  175. package/skills/sharp-edges/references/lang-ruby.md +273 -0
  176. package/skills/sharp-edges/references/lang-rust.md +272 -0
  177. package/skills/sharp-edges/references/lang-swift.md +287 -0
  178. package/skills/sharp-edges/references/language-specific.md +588 -0
  179. package/skills/spec-to-code-compliance/SKILL.md +357 -0
  180. package/skills/spec-to-code-compliance/resources/COMPLETENESS_CHECKLIST.md +69 -0
  181. package/skills/spec-to-code-compliance/resources/IR_EXAMPLES.md +417 -0
  182. package/skills/spec-to-code-compliance/resources/OUTPUT_REQUIREMENTS.md +105 -0
  183. package/skills/supply-chain-risk-auditor/SKILL.md +67 -0
  184. package/skills/supply-chain-risk-auditor/resources/results-template.md +41 -0
  185. package/skills/variant-analysis/METHODOLOGY.md +327 -0
  186. package/skills/variant-analysis/SKILL.md +142 -0
  187. package/skills/variant-analysis/resources/codeql/cpp.ql +119 -0
  188. package/skills/variant-analysis/resources/codeql/go.ql +69 -0
  189. package/skills/variant-analysis/resources/codeql/java.ql +71 -0
  190. package/skills/variant-analysis/resources/codeql/javascript.ql +63 -0
  191. package/skills/variant-analysis/resources/codeql/python.ql +80 -0
  192. package/skills/variant-analysis/resources/semgrep/cpp.yaml +98 -0
  193. package/skills/variant-analysis/resources/semgrep/go.yaml +63 -0
  194. package/skills/variant-analysis/resources/semgrep/java.yaml +61 -0
  195. package/skills/variant-analysis/resources/semgrep/javascript.yaml +60 -0
  196. package/skills/variant-analysis/resources/semgrep/python.yaml +72 -0
  197. package/skills/variant-analysis/resources/variant-report-template.md +75 -0
  198. package/skills/vuln-report/SKILL.md +137 -0
  199. package/skills/vuln-report/agents/openai.yaml +4 -0
  200. package/skills/vuln-report/references/report-template.md +135 -0
  201. package/skills/wooyun-legacy/SKILL.md +367 -0
  202. package/skills/wooyun-legacy/references/bank-penetration.md +222 -0
  203. package/skills/wooyun-legacy/references/checklists/command-execution-checklist.md +119 -0
  204. package/skills/wooyun-legacy/references/checklists/csrf-checklist.md +74 -0
  205. package/skills/wooyun-legacy/references/checklists/file-upload-checklist.md +108 -0
  206. package/skills/wooyun-legacy/references/checklists/info-disclosure-checklist.md +114 -0
  207. package/skills/wooyun-legacy/references/checklists/logic-flaws-checklist.md +95 -0
  208. package/skills/wooyun-legacy/references/checklists/misconfig-checklist.md +124 -0
  209. package/skills/wooyun-legacy/references/checklists/path-traversal-checklist.md +87 -0
  210. package/skills/wooyun-legacy/references/checklists/rce-checklist.md +93 -0
  211. package/skills/wooyun-legacy/references/checklists/sql-injection-checklist.md +97 -0
  212. package/skills/wooyun-legacy/references/checklists/ssrf-checklist.md +99 -0
  213. package/skills/wooyun-legacy/references/checklists/unauthorized-access-checklist.md +89 -0
  214. package/skills/wooyun-legacy/references/checklists/weak-password-checklist.md +115 -0
  215. package/skills/wooyun-legacy/references/checklists/xss-checklist.md +103 -0
  216. package/skills/wooyun-legacy/references/checklists/xxe-checklist.md +130 -0
  217. package/skills/wooyun-legacy/references/info-disclosure.md +975 -0
  218. package/skills/wooyun-legacy/references/logic-flaws.md +721 -0
  219. package/skills/wooyun-legacy/references/path-traversal.md +1191 -0
  220. package/skills/wooyun-legacy/references/telecom-penetration.md +156 -0
  221. package/skills/wooyun-legacy/references/unauthorized-access.md +980 -0
  222. package/skills/wooyun-legacy/references/xss.md +746 -0
  223. package/skills/zeroize-audit/SKILL.md +371 -0
  224. package/skills/zeroize-audit/configs/c.yaml +21 -0
  225. package/skills/zeroize-audit/configs/default.yaml +128 -0
  226. package/skills/zeroize-audit/configs/rust.yaml +83 -0
  227. package/skills/zeroize-audit/prompts/report_template.md +238 -0
  228. package/skills/zeroize-audit/prompts/system.md +163 -0
  229. package/skills/zeroize-audit/prompts/task.md +97 -0
  230. package/skills/zeroize-audit/references/compile-commands.md +231 -0
  231. package/skills/zeroize-audit/references/detection-strategy.md +191 -0
  232. package/skills/zeroize-audit/references/ir-analysis.md +252 -0
  233. package/skills/zeroize-audit/references/mcp-analysis.md +221 -0
  234. package/skills/zeroize-audit/references/poc-generation.md +470 -0
  235. package/skills/zeroize-audit/references/rust-zeroization-patterns.md +867 -0
  236. package/skills/zeroize-audit/schemas/input.json +83 -0
  237. package/skills/zeroize-audit/schemas/output.json +140 -0
  238. package/skills/zeroize-audit/tools/analyze_asm.sh +202 -0
  239. package/skills/zeroize-audit/tools/analyze_cfg.py +381 -0
  240. package/skills/zeroize-audit/tools/analyze_heap.sh +211 -0
  241. package/skills/zeroize-audit/tools/analyze_ir_semantic.py +429 -0
  242. package/skills/zeroize-audit/tools/diff_ir.sh +135 -0
  243. package/skills/zeroize-audit/tools/diff_rust_mir.sh +189 -0
  244. package/skills/zeroize-audit/tools/emit_asm.sh +67 -0
  245. package/skills/zeroize-audit/tools/emit_ir.sh +77 -0
  246. package/skills/zeroize-audit/tools/emit_rust_asm.sh +178 -0
  247. package/skills/zeroize-audit/tools/emit_rust_ir.sh +150 -0
  248. package/skills/zeroize-audit/tools/emit_rust_mir.sh +158 -0
  249. package/skills/zeroize-audit/tools/extract_compile_flags.py +284 -0
  250. package/skills/zeroize-audit/tools/generate_poc.py +1329 -0
  251. package/skills/zeroize-audit/tools/mcp/apply_confidence_gates.py +113 -0
  252. package/skills/zeroize-audit/tools/mcp/check_mcp.sh +68 -0
  253. package/skills/zeroize-audit/tools/mcp/normalize_mcp_evidence.py +125 -0
  254. package/skills/zeroize-audit/tools/scripts/check_llvm_patterns.py +481 -0
  255. package/skills/zeroize-audit/tools/scripts/check_mir_patterns.py +554 -0
  256. package/skills/zeroize-audit/tools/scripts/check_rust_asm.py +424 -0
  257. package/skills/zeroize-audit/tools/scripts/check_rust_asm_aarch64.py +300 -0
  258. package/skills/zeroize-audit/tools/scripts/check_rust_asm_x86.py +283 -0
  259. package/skills/zeroize-audit/tools/scripts/find_dangerous_apis.py +375 -0
  260. package/skills/zeroize-audit/tools/scripts/semantic_audit.py +923 -0
  261. package/skills/zeroize-audit/tools/track_dataflow.sh +196 -0
  262. package/skills/zeroize-audit/tools/validate_rust_toolchain.sh +298 -0
  263. package/skills/zeroize-audit/workflows/phase-0-preflight.md +150 -0
  264. package/skills/zeroize-audit/workflows/phase-1-source-analysis.md +144 -0
  265. package/skills/zeroize-audit/workflows/phase-2-compiler-analysis.md +139 -0
  266. package/skills/zeroize-audit/workflows/phase-3-interim-report.md +46 -0
  267. package/skills/zeroize-audit/workflows/phase-4-poc-generation.md +46 -0
  268. package/skills/zeroize-audit/workflows/phase-5-poc-validation.md +136 -0
  269. package/skills/zeroize-audit/workflows/phase-6-final-report.md +44 -0
  270. package/skills/zeroize-audit/workflows/phase-7-test-generation.md +42 -0
  271. package/themes/piolium-srcery.json +94 -0
@@ -0,0 +1,917 @@
1
+ /**
2
+ * Deep mode orchestrator (`/piolium-deep`).
3
+ *
4
+ * Pipeline (command-defs/deep.md, archon-audit @ 2026-05-16):
5
+ *
6
+ * [D1 Intel Sweep CVE ‖ D2 Intel Sweep History] (D2 skipped no-git)
7
+ * → D3 Patch Audit (skipped no-git)
8
+ * → D4 Threat Model
9
+ * → [D5 Code Scan ‖ D7 Access Audit] (wave A, cap 2)
10
+ * → D6 Deep Probe (wave B)
11
+ * → D8 Taint Trace
12
+ * → D9 Review Panel + inline FP/triage tail
13
+ * → D10 Intent Reconciliation (skip-and-continue)
14
+ * → D11 Variant Sweep
15
+ * → D12 PoC Authoring (per finding)
16
+ * → D13 Finding Finalize (per finding, BOTH buckets)
17
+ * → D14 Report Compose (+ inline cleanup/file-state)
18
+ *
19
+ * MVP simplifications (deliberate, relative to upstream):
20
+ * - D6 deep probe runs as a single probe-lead session that folds the
21
+ * reasoner roles inline instead of N orchestrated teams.
22
+ * - D9 chamber is one review-adjudicator session per run with the FP /
23
+ * cold-verify / triage tail folded into its prompt.
24
+ * - D12/D13 iterate findings under the global concurrency cap.
25
+ *
26
+ * No-git mode: D2 and D3 transition to `skipped`; the audit records
27
+ * `history_available: false`.
28
+ *
29
+ * Single-phase mode: pass `only: ["D6"]` to rerun a specific phase. Its
30
+ * prerequisites must already be `complete`/`skipped`.
31
+ */
32
+
33
+ import { existsSync, mkdirSync, readdirSync, rmSync, statSync, writeFileSync } from "node:fs";
34
+ import { join } from "node:path";
35
+ import type { AgentRuntimeModel } from "../agent-runner.ts";
36
+ import { type AgentDefinition, loadAgents } from "../agents.ts";
37
+ import {
38
+ type AuditRunState,
39
+ applyPhaseStatus,
40
+ initAudit,
41
+ latestAudit,
42
+ markAuditStatus,
43
+ readAuditState,
44
+ } from "../audit-state.ts";
45
+ import { runCandidateScanAsync } from "../candidate-scan.ts";
46
+ import {
47
+ type FindingDir,
48
+ consolidateDrafts,
49
+ listAllFindingDirs,
50
+ listFindingDirs,
51
+ partitionFindings,
52
+ } from "../findings.ts";
53
+ import { runReconAsync } from "../recon.ts";
54
+ import { readPositiveIntEnv } from "../retry.ts";
55
+ import { Scheduler } from "../scheduler.ts";
56
+ import { type PhaseUiHooks, runAgentPhase } from "./phase-runner.ts";
57
+
58
+ export type DeepUiHooks = PhaseUiHooks;
59
+
60
+ export interface RunDeepOptions {
61
+ cwd: string;
62
+ signal?: AbortSignal;
63
+ ui?: DeepUiHooks;
64
+ forceFresh?: boolean;
65
+ agentRuntime?: AgentRuntimeModel;
66
+ /** Restrict execution to a subset of phase ids (e.g. ["D6"]). */
67
+ only?: string[];
68
+ }
69
+
70
+ export interface RunDeepResult {
71
+ auditId: string;
72
+ status: "complete" | "failed";
73
+ phases: Record<string, "complete" | "failed" | "skipped">;
74
+ }
75
+
76
+ const ATTACK_SURFACE_DIR = "piolium/attack-surface";
77
+ const KB_REPORT = `${ATTACK_SURFACE_DIR}/knowledge-base-report.md`;
78
+ const ADVISORY_SUMMARY = `${ATTACK_SURFACE_DIR}/advisory-summary.md`;
79
+ const COMMIT_RECON = `${ATTACK_SURFACE_DIR}/commit-recon-report.md`;
80
+ const PATCH_BYPASS_SUMMARY = `${ATTACK_SURFACE_DIR}/patch-bypass-summary.md`;
81
+ const ATTACK_SURFACE_ARCHITECTURE = `${ATTACK_SURFACE_DIR}/architecture-entrypoints.md`;
82
+ const SAST_REPORT = `${ATTACK_SURFACE_DIR}/source-sink-flows-all-severities.md`;
83
+ const AUTHZ_MATRIX = `${ATTACK_SURFACE_DIR}/public-routes-authz-matrix.md`;
84
+ const MANUAL_ATTACK_SURFACE = `${ATTACK_SURFACE_DIR}/manual-attack-surface-inventory.md`;
85
+ const PROBE_SUMMARY = `${ATTACK_SURFACE_DIR}/deep-probe-summary.md`;
86
+ const CROSS_SERVICE = `${ATTACK_SURFACE_DIR}/cross-service-edges.json`;
87
+ const CROSS_SERVICE_REPORT = `${ATTACK_SURFACE_DIR}/cross-service-edges.md`;
88
+ const CHAMBER_INDEX = "piolium/chamber-workspace/index.md";
89
+ const INTENT_RECONCILIATION = `${ATTACK_SURFACE_DIR}/intent-reconciliation.md`;
90
+ const VARIANT_SUMMARY = `${ATTACK_SURFACE_DIR}/variant-summary.md`;
91
+ const CONSOLIDATION_MANIFEST = `${ATTACK_SURFACE_DIR}/deep-consolidation-manifest.json`;
92
+ const FINAL_REPORT = "piolium/final-audit-report.md";
93
+ const DEEP_CLEANUP_SUMMARY = `${ATTACK_SURFACE_DIR}/deep-cleanup-summary.json`;
94
+ const DEEP_TRANSIENT_PATHS = [
95
+ "piolium/tmp",
96
+ "piolium/chamber-workspace",
97
+ "piolium/probe-workspace",
98
+ "piolium/adversarial-reviews",
99
+ "piolium/bypass-analysis",
100
+ "piolium/codeql-artifacts",
101
+ "piolium/codeql-queries",
102
+ "piolium/semgrep-rules",
103
+ "piolium/agentic-actions-res",
104
+ "piolium/confirm-workspace",
105
+ "piolium/codeql-res",
106
+ "piolium/semgrep-res",
107
+ "piolium/real-env-evidence",
108
+ "piolium/raw",
109
+ "piolium/file-records",
110
+ "piolium/findings-draft",
111
+ "piolium/attack-surface/raw",
112
+ "piolium/attack-pattern-registry.json",
113
+ "piolium/authz-coverage-gaps.md",
114
+ "piolium/merged-results.sarif",
115
+ ];
116
+ const PER_FINDING_MAX_RETRIES = readPositiveIntEnv("PIOLIUM_PER_FINDING_MAX_RETRIES", 10);
117
+ const PER_FINDING_ATTEMPT_TIMEOUT_MS = readPositiveIntEnv(
118
+ "PIOLIUM_PER_FINDING_ATTEMPT_TIMEOUT_MS",
119
+ 30 * 60 * 1000,
120
+ );
121
+ const PER_FINDING_BACKOFF_BASE_MS = readPositiveIntEnv("PIOLIUM_PER_FINDING_BACKOFF_BASE_MS", 5000);
122
+ const PER_FINDING_BACKOFF_MAX_MS = readPositiveIntEnv(
123
+ "PIOLIUM_PER_FINDING_BACKOFF_MAX_MS",
124
+ 120_000,
125
+ );
126
+
127
+ function exists(cwd: string, rel: string): boolean {
128
+ return existsSync(join(cwd, rel));
129
+ }
130
+
131
+ function ensureAttackSurfaceDir(cwd: string): void {
132
+ mkdirSync(join(cwd, ATTACK_SURFACE_DIR), { recursive: true });
133
+ }
134
+
135
+ function findingHasPoc(path: string): boolean {
136
+ try {
137
+ return (
138
+ readdirSync(path).some((f) => f.startsWith("poc.")) ||
139
+ existsSync(join(path, "poc.theoretical.md"))
140
+ );
141
+ } catch {
142
+ return false;
143
+ }
144
+ }
145
+
146
+ function findingHasReport(path: string): boolean {
147
+ try {
148
+ const report = join(path, "report.md");
149
+ return existsSync(report) && statSync(report).size > 500;
150
+ } catch {
151
+ return false;
152
+ }
153
+ }
154
+
155
+ function perFindingGate(phase: "D12" | "D13", path: string): boolean {
156
+ return phase === "D12" ? findingHasPoc(path) : findingHasReport(path);
157
+ }
158
+
159
+ function retryBackoffMs(attempt: number): number {
160
+ const exponent = Math.max(0, attempt - 1);
161
+ const raw = PER_FINDING_BACKOFF_BASE_MS * 2 ** exponent;
162
+ return Math.min(PER_FINDING_BACKOFF_MAX_MS, raw);
163
+ }
164
+
165
+ async function sleep(ms: number, signal: AbortSignal | undefined): Promise<void> {
166
+ if (ms <= 0) return;
167
+ if (signal?.aborted) throw signal.reason ?? new Error("Aborted");
168
+ await new Promise<void>((resolve, reject) => {
169
+ const cleanup = () => {
170
+ clearTimeout(timeout);
171
+ signal?.removeEventListener("abort", onAbort);
172
+ };
173
+ const timeout = setTimeout(() => {
174
+ cleanup();
175
+ resolve();
176
+ }, ms);
177
+ const onAbort = () => {
178
+ cleanup();
179
+ reject(signal?.reason ?? new Error("Aborted"));
180
+ };
181
+ signal?.addEventListener("abort", onAbort, { once: true });
182
+ });
183
+ }
184
+
185
+ function errorMessage(err: unknown): string {
186
+ return err instanceof Error ? err.message : typeof err === "string" ? err : "Unknown error";
187
+ }
188
+
189
+ const PREREQS: Record<string, string[]> = {
190
+ D1: [],
191
+ D2: [],
192
+ D3: ["D1", "D2"],
193
+ D4: ["D3"],
194
+ D5: ["D4"],
195
+ D6: ["D4"],
196
+ D7: ["D4"],
197
+ D8: ["D5", "D6"],
198
+ D9: ["D6", "D7", "D8"],
199
+ D10: ["D9"],
200
+ D11: ["D10"],
201
+ D12: ["D11"],
202
+ D13: ["D12"],
203
+ D14: ["D13"],
204
+ };
205
+
206
+ function pickResume(
207
+ cwd: string,
208
+ force: boolean,
209
+ allowComplete: boolean,
210
+ ): AuditRunState | undefined {
211
+ if (force) return undefined;
212
+ const state = readAuditState(cwd).state;
213
+ const audit = state ? latestAudit(state) : undefined;
214
+ if (!audit) return undefined;
215
+ if (audit.mode !== "deep") return undefined;
216
+ if (audit.status === "complete" && !allowComplete) return undefined;
217
+ return audit;
218
+ }
219
+
220
+ function shouldRun(name: string, only: string[] | undefined): boolean {
221
+ if (!only) return true;
222
+ return only.includes(name);
223
+ }
224
+
225
+ function ensurePrereqs(audit: AuditRunState, name: string): void {
226
+ const prereqs = PREREQS[name] ?? [];
227
+ for (const prereq of prereqs) {
228
+ const status = audit.phases[prereq]?.status;
229
+ if (status !== "complete" && status !== "skipped") {
230
+ throw new Error(
231
+ `Cannot run ${name}: prerequisite ${prereq} is "${status ?? "missing"}". Resume or rerun upstream phases first.`,
232
+ );
233
+ }
234
+ }
235
+ }
236
+
237
+ function buildTask(phase: string, cwd: string, hasGit: boolean): string {
238
+ switch (phase) {
239
+ case "D1":
240
+ return [
241
+ "You are running Phase D1 (Intel Sweep — CVE) of /piolium-deep.",
242
+ `Build \`${ADVISORY_SUMMARY}\` covering CVE/GHSA/OSV advisories, dependency intel, and architecture hints. Use bash + WebFetch (run \`curl\` against advisory APIs; WebSearch may be unavailable — use bash+curl instead).`,
243
+ `Scope: cve-scout only. Target repository: ${cwd}.`,
244
+ ].join("\n\n");
245
+ case "D2":
246
+ return [
247
+ "You are running Phase D2 (Intel Sweep — History) of /piolium-deep.",
248
+ hasGit
249
+ ? `Sweep git history for security-relevant commits. Set \`MAX_COMMITS="\${PIOLIUM_COMMIT_SCAN_LIMIT:-500}"\` and \`MAX_AGE="\${PIOLIUM_COMMIT_SCAN_SINCE:-60 days ago}"\`; every \`git log\` must include \`-n "$MAX_COMMITS" --since="$MAX_AGE"\`. Categorise HIGH-risk commits (silent fixes, dangerous patterns, reverted fixes). Write \`${COMMIT_RECON}\` with categorised commits feeding D3.`
250
+ : `(no-git target — return immediately with a short note in \`${COMMIT_RECON}\` saying D2 is skipped: history_available=false.)`,
251
+ ].join("\n\n");
252
+ case "D3":
253
+ return [
254
+ "You are running Phase D3 (Patch Audit) of /piolium-deep.",
255
+ hasGit
256
+ ? `Read \`${ADVISORY_SUMMARY}\` and \`${COMMIT_RECON}\`. For each known CVE/GHSA patch and each HIGH-risk undisclosed commit, check whether the fix is bypassable today (alternate entry points, config gates, parser differentials). Write \`${PATCH_BYPASS_SUMMARY}\` with: patches reviewed, bypass attempts, conclusions, and drafts \`piolium/findings-draft/d3-NNN-<slug>.md\`.`
257
+ : `(no-git target — write a short \`${PATCH_BYPASS_SUMMARY}\` note saying D3 is skipped because there is no local patch history.)`,
258
+ ].join("\n\n");
259
+ case "D4":
260
+ return [
261
+ "You are running Phase D4 (Threat Model) of /piolium-deep.",
262
+ `Build the deep KB at \`${KB_REPORT}\`. Use the security-threat-model skill if available. If \`piolium/INFO.md\` exists, treat it as authoritative for the sections it covers.`,
263
+ "Sections: Project Type, Trust Boundaries, DFD slices, CFD slices, Framework Contracts and Hidden Control Channels, Domain Attack Modes (apply sharp-edges, wooyun-legacy, insecure-defaults, last30days as applicable), Phase D5 CodeQL Extraction Targets, Coverage Gaps.",
264
+ `Also write \`${ATTACK_SURFACE_ARCHITECTURE}\` with a reusable inventory of entry points, public routes/URLs, attacker-controlled sources, high-value sinks, and key source files.`,
265
+ "This KB drives every later phase — be thorough.",
266
+ ].join("\n\n");
267
+ case "D5":
268
+ return [
269
+ "You are running Phase D5 (Code Scan) of /piolium-deep.",
270
+ "Use codeql + semgrep skills if available (custom queries/rules + structural extraction allowed in deep); otherwise fall back to grep + read.",
271
+ "Read `piolium/attack-surface/candidates-summary.md` and `piolium/attack-surface/candidates.jsonl` first; prioritize precise/high-score candidate files before expanding coverage.",
272
+ "Pay special attention to `hidden-control-channel` candidates: headers or framework/proxy context affecting auth, routing, tenant selection, middleware, runtime mode, debug/admin/preview behavior, or cache keys.",
273
+ "Required artifacts:",
274
+ ` - \`${SAST_REPORT}\``,
275
+ " - draft findings `piolium/findings-draft/d5-NNN-<slug>.md`",
276
+ "Then run an inline SAST enrichment pass: classify each candidate for security relevance (trust-boundary crossing / attacker-controlled input) and write a `## SAST Enrichment` section into the KB. Drop likely-correctness / environment-only findings.",
277
+ "Each draft frontmatter MUST include id (d5-NNN), phase (D5), slug, severity. Cap drafts at 30.",
278
+ ].join("\n\n");
279
+ case "D6":
280
+ return [
281
+ "You are running Phase D6 (Deep Probe) of /piolium-deep.",
282
+ "Single-team probe in MVP — strategist + backward/contradiction reasoners + evidence harvester, all inline.",
283
+ "Steps:",
284
+ ` 1. Read \`${KB_REPORT}\`, \`${ATTACK_SURFACE_DIR}/candidates-summary.md\`, plus D4/D5 artifacts. Pick the highest-impact slices.`,
285
+ ` 2. Write \`${MANUAL_ATTACK_SURFACE}\` with public routes/URLs, attacker sources, sinks, source files, hidden control channels, exploit-relevant paths, and an inline Code Anatomy.`,
286
+ " 3. Generate hypotheses (Pre-Mortem + Abductive + TRIZ/Game-Theory reasoning).",
287
+ " 4. Verify with read/grep/bash — file:line evidence required; apply causal challenge (intervention/counterfactual/confounder) before any INVALIDATED verdict.",
288
+ " 5. Write drafts `piolium/findings-draft/d6-NNN-<slug>.md`.",
289
+ ` 6. Write \`${PROBE_SUMMARY}\`.`,
290
+ ].join("\n\n");
291
+ case "D7":
292
+ return [
293
+ "You are running Phase D7 (Access Audit) of /piolium-deep.",
294
+ `Read \`${KB_REPORT}\` and \`${ATTACK_SURFACE_ARCHITECTURE}\` if present. Enumerate every route/handler/consumer; build \`${AUTHZ_MATRIX}\` with rows: public route/URL/operation × roles, expected vs actual checks, including middleware/proxy-derived identity and hidden control channels. File anomalies as drafts \`piolium/findings-draft/d7-NNN-<slug>.md\`. Coordinate with D6 — check the probe summary before filing to avoid duplicates.`,
295
+ ].join("\n\n");
296
+ case "D8":
297
+ return [
298
+ "You are running Phase D8 (Taint Trace) of /piolium-deep.",
299
+ `Read \`${KB_REPORT}\` Architecture/DFD sections, the D6 probe summary, and \`${AUTHZ_MATRIX}\` if present.`,
300
+ `If the target is a single-service repo, write \`${CROSS_SERVICE}\` containing \`{"single_service": true}\` and stop with a clean no-op note.`,
301
+ `Otherwise: stitch inter-component flows (HTTP/RPC/queues/DB writes), catch sanitization gaps and write-driven injection, and produce \`${CROSS_SERVICE}\` (machine-readable) plus \`${CROSS_SERVICE_REPORT}\` (human-readable). Drafts go to \`piolium/findings-draft/d8-NNN-<slug>.md\`.`,
302
+ ].join("\n\n");
303
+ case "D9":
304
+ return [
305
+ "You are running Phase D9 (Review Panel + FP/Triage tail) of /piolium-deep.",
306
+ `Read \`${ATTACK_SURFACE_DIR}/\` artifacts as the shared attack-surface index.`,
307
+ "Group draft findings (d3-, d5-, d6-, d7-, d8-) into clusters by attack class. For each cluster, run an inline chamber: Synthesizer (you, final verdict) + Ideator (challenge with attack scenarios; also cover state/concurrency and spec-compliance classes since they have no dedicated phase) + Devil's Advocate (try to reject). Chain/extend pre-seeded drafts rather than regenerating.",
308
+ "Do not delete weak drafts. Mark rejected drafts with frontmatter `status: rejected-fp` and `rejection_reason: <short reason>`.",
309
+ "FP tail (inline): (1) fp-check every surviving VALID draft; (2) cold re-verification for CRITICAL survivors only; (3) triage sweep — write `Triage-Priority` (P0/P1/P2/skip), `Triage-Exploitability`, `Triage-Impact`, `Triage-Reasoning` into every surviving draft's frontmatter. `skip` is reversible (routed to the theoretical bucket later).",
310
+ "Survivors get copied to `piolium/findings-draft/d9-NNN-<slug>.md` with `status: valid`, normalised severity, and Triage-* preserved.",
311
+ "Write per-cluster transcripts under `piolium/chamber-workspace/<cluster-id>/debate.md` and an index at `piolium/chamber-workspace/index.md`.",
312
+ ].join("\n\n");
313
+ case "D10":
314
+ return [
315
+ "You are running Phase D10 (Intent Reconciliation) of /piolium-deep — AUDIT CONTRACT.",
316
+ "Runs after the D9 FP/triage tail and BEFORE variant/PoC effort. Reconcile each surviving finding against documented intentional design / exposed feature / explicitly in-scope risk.",
317
+ `Inputs: \`piolium/findings-draft/d9-*.md\` (every \`status: valid\` draft), \`${KB_REPORT}\` (Architecture Model / Domain Attack Research / Known False-Positive Sources), and \`piolium/INFO.md\` if present.`,
318
+ "For each VALID draft: bounded read of ONLY the cited file:line, reconcile against documented intent, write `Intent-Verdict` / `Intent-Source` / `Intent-Quote` into the draft frontmatter. For strongly-intentional or documented-feature findings, overwrite `Triage-Priority: skip` with a `Triage-Reasoning: context-reviewer: …` note. Do NOT touch `status` or `severity`.",
319
+ `Write the corpus to \`piolium/attack-surface/intent-corpus.json\`, per-finding verdicts to \`piolium/attack-surface/intent-verdicts.json\`, and the report to \`${INTENT_RECONCILIATION}\`. Best-effort: never suppress a finding outright; always write at least a minimal intent-reconciliation.md.`,
320
+ ].join("\n\n");
321
+ case "D11":
322
+ return [
323
+ "You are running Phase D11 (Variant Sweep) of /piolium-deep.",
324
+ `For each surviving finding whose draft is NOT \`Triage-Priority: skip\`, search the codebase and \`${ATTACK_SURFACE_DIR}/\` for similar routes, sources, sinks, and flow patterns. Use the variant-analysis skill if available. Write new drafts \`piolium/findings-draft/d11-NNN-<slug>.md\`. Write \`${VARIANT_SUMMARY}\`.`,
325
+ ].join("\n\n");
326
+ case "D12":
327
+ return [
328
+ "You are running Phase D12 (PoC Authoring) of /piolium-deep — this prompt is for a SINGLE finding.",
329
+ "Read draft.md, build a minimal PoC, write `<finding-dir>/poc.{py|sh|js|rb|go}`, evidence to `<finding-dir>/evidence/`, and `PoC-Status: executed` (or `not-executed` with reason) into draft.md frontmatter. If runtime exploitation isn't possible, write `<finding-dir>/poc.theoretical.md` explaining the chain and set `PoC-Status: not-executed`.",
330
+ ].join("\n\n");
331
+ case "D13":
332
+ return [
333
+ "You are running Phase D13 (Finding Finalize) of /piolium-deep — this prompt is for a SINGLE finding (may be in the confirmed OR theoretical bucket).",
334
+ "Use the vuln-report skill if available. Produce `<finding-dir>/report.md` (>500 bytes) with Summary, Details, Root Cause, Proof of concept & Evidence, Impact, Remediation. Theoretical-bucket findings get the same report; their PoC section states the no-PoC reason.",
335
+ ].join("\n\n");
336
+ case "D14":
337
+ return [
338
+ "You are running Phase D14 (Report Compose) of /piolium-deep.",
339
+ "Verify every directory under `piolium/findings/` AND `piolium/findings-theoretical/` has `report.md` >500 bytes. If any are missing, fail with a clear error.",
340
+ `Compose \`piolium/final-audit-report.md\`: Executive Summary, Findings by Severity — confirmed only (with links), Theoretical / Unconfirmed Findings (kept out of the Summary-of-Findings table), Intent Reconciliation summary (surface \`${INTENT_RECONCILIATION}\` if present), Attack Surface Summary (linking \`${ATTACK_SURFACE_DIR}/\` artifacts), Coverage Gaps, Methodology Notes. If history_available is false, add an Executive Summary note that D2/D3 git-history phases were skipped.`,
341
+ ].join("\n\n");
342
+ default:
343
+ return `Phase ${phase} task description not implemented.`;
344
+ }
345
+ }
346
+
347
+ function gateFor(phase: string, cwd: string): () => boolean {
348
+ switch (phase) {
349
+ case "D1":
350
+ return () => exists(cwd, ADVISORY_SUMMARY);
351
+ case "D2":
352
+ return () => exists(cwd, COMMIT_RECON);
353
+ case "D3":
354
+ return () => exists(cwd, PATCH_BYPASS_SUMMARY);
355
+ case "D4":
356
+ return () => exists(cwd, KB_REPORT) && exists(cwd, ATTACK_SURFACE_ARCHITECTURE);
357
+ case "D5":
358
+ return () => exists(cwd, SAST_REPORT);
359
+ case "D6":
360
+ return () => exists(cwd, MANUAL_ATTACK_SURFACE) && exists(cwd, PROBE_SUMMARY);
361
+ case "D7":
362
+ return () => exists(cwd, AUTHZ_MATRIX);
363
+ case "D8":
364
+ return () => exists(cwd, CROSS_SERVICE);
365
+ case "D9":
366
+ return () => exists(cwd, CHAMBER_INDEX);
367
+ case "D10":
368
+ return () => exists(cwd, INTENT_RECONCILIATION);
369
+ case "D11":
370
+ return () => exists(cwd, VARIANT_SUMMARY);
371
+ case "D12":
372
+ return () => {
373
+ const dirs = listFindingDirs(cwd);
374
+ return dirs.length === 0 || dirs.every((d) => findingHasPoc(d.path));
375
+ };
376
+ case "D13":
377
+ return () => {
378
+ const dirs = listAllFindingDirs(cwd);
379
+ return dirs.length === 0 || dirs.every((d) => findingHasReport(d.path));
380
+ };
381
+ case "D14":
382
+ return () => exists(cwd, FINAL_REPORT);
383
+ default:
384
+ return () => true;
385
+ }
386
+ }
387
+
388
+ interface PhaseSpec {
389
+ name: string;
390
+ agent: AgentDefinition | undefined;
391
+ missingMessage: string;
392
+ statusLabel: string;
393
+ }
394
+
395
+ async function runOne(
396
+ cwd: string,
397
+ audit: AuditRunState,
398
+ spec: PhaseSpec,
399
+ hasGit: boolean,
400
+ signal: AbortSignal | undefined,
401
+ ui: DeepUiHooks | undefined,
402
+ agentRuntime?: AgentRuntimeModel,
403
+ ): Promise<void> {
404
+ await runAgentPhase({
405
+ cwd,
406
+ audit,
407
+ phaseName: spec.name,
408
+ statusKey: "piolium-deep",
409
+ statusLabel: spec.statusLabel,
410
+ agent: spec.agent,
411
+ missingAgentMessage: spec.missingMessage,
412
+ task: buildTask(spec.name, cwd, hasGit),
413
+ gate: gateFor(spec.name, cwd),
414
+ mode: "deep",
415
+ ui,
416
+ agentRuntime,
417
+ ...(signal ? { signal } : {}),
418
+ });
419
+ }
420
+
421
+ async function runFanout3(
422
+ cwd: string,
423
+ audit: AuditRunState,
424
+ specs: PhaseSpec[],
425
+ hasGit: boolean,
426
+ signal: AbortSignal | undefined,
427
+ ui: DeepUiHooks | undefined,
428
+ agentRuntime?: AgentRuntimeModel,
429
+ ): Promise<{ failed: boolean }> {
430
+ const scheduler = new Scheduler({ maxConcurrent: 3, ...(signal ? { signal } : {}) });
431
+ const settled = await Promise.allSettled(
432
+ specs.map((s) =>
433
+ scheduler.enqueue({
434
+ id: s.name,
435
+ run: (sig) => runOne(cwd, audit, s, hasGit, sig, ui, agentRuntime),
436
+ }),
437
+ ),
438
+ );
439
+ scheduler.dispose();
440
+ return { failed: settled.some((r) => r.status === "rejected") };
441
+ }
442
+
443
+ async function runPerFinding(
444
+ cwd: string,
445
+ audit: AuditRunState,
446
+ phase: "D12" | "D13",
447
+ agent: AgentDefinition | undefined,
448
+ dirs: FindingDir[],
449
+ signal: AbortSignal | undefined,
450
+ ui: DeepUiHooks | undefined,
451
+ agentRuntime?: AgentRuntimeModel,
452
+ ): Promise<{ failed: boolean }> {
453
+ if (!agent) {
454
+ await applyPhaseStatus(cwd, audit, phase, {
455
+ status: "failed",
456
+ error: `agent missing for ${phase}`,
457
+ });
458
+ return { failed: true };
459
+ }
460
+ await applyPhaseStatus(cwd, audit, phase, { status: "in_progress" });
461
+ if (dirs.length === 0) {
462
+ await applyPhaseStatus(cwd, audit, phase, { status: "skipped" });
463
+ return { failed: false };
464
+ }
465
+ const maxAttempts = PER_FINDING_MAX_RETRIES + 1;
466
+ const scheduler = new Scheduler({ maxConcurrent: 3, ...(signal ? { signal } : {}) });
467
+ const settled = await Promise.allSettled(
468
+ dirs.map((d) =>
469
+ scheduler.enqueue({
470
+ id: `${phase}:${d.id}`,
471
+ run: async (sig) => {
472
+ const phaseName = `${phase}:${d.id}`;
473
+ const gate = () => perFindingGate(phase, d.path);
474
+ if (gate()) {
475
+ await applyPhaseStatus(cwd, audit, phaseName, {
476
+ status: "complete",
477
+ attempt: audit.phases[phaseName]?.attempt ?? 0,
478
+ max_attempts: maxAttempts,
479
+ retry_backoff_ms: null,
480
+ next_retry_at: null,
481
+ last_error: null,
482
+ });
483
+ return;
484
+ }
485
+
486
+ let lastError = "Unknown error";
487
+ for (let attempt = 1; attempt <= maxAttempts; attempt++) {
488
+ try {
489
+ await applyPhaseStatus(cwd, audit, phaseName, {
490
+ status: "in_progress",
491
+ attempt,
492
+ max_attempts: maxAttempts,
493
+ retry_backoff_ms: null,
494
+ next_retry_at: null,
495
+ error: `Attempt ${attempt}/${maxAttempts} running.`,
496
+ });
497
+ await runAgentPhase({
498
+ cwd,
499
+ audit,
500
+ phaseName,
501
+ statusKey: "piolium-deep",
502
+ statusLabel: `● ${phase} ${d.id} (${attempt}/${maxAttempts})`,
503
+ agent,
504
+ missingAgentMessage: `agent missing for ${phase}`,
505
+ task: `${buildTask(phase, cwd, true)}\n\nFinding directory: ${d.path}\nSlug: ${d.slug}`,
506
+ gate,
507
+ mode: "deep",
508
+ ui,
509
+ agentRuntime,
510
+ timeoutMs: PER_FINDING_ATTEMPT_TIMEOUT_MS,
511
+ maxRetries: 0,
512
+ ...(sig ? { signal: sig } : {}),
513
+ });
514
+ return;
515
+ } catch (err) {
516
+ lastError = errorMessage(err);
517
+ if (gate()) {
518
+ await applyPhaseStatus(cwd, audit, phaseName, {
519
+ status: "complete",
520
+ attempt,
521
+ max_attempts: maxAttempts,
522
+ retry_backoff_ms: null,
523
+ next_retry_at: null,
524
+ last_error: null,
525
+ });
526
+ return;
527
+ }
528
+
529
+ if (attempt >= maxAttempts) {
530
+ await applyPhaseStatus(cwd, audit, phaseName, {
531
+ status: "failed",
532
+ error: `Failed after ${PER_FINDING_MAX_RETRIES} retries: ${lastError}`,
533
+ attempt,
534
+ max_attempts: maxAttempts,
535
+ retry_backoff_ms: null,
536
+ next_retry_at: null,
537
+ last_error: lastError,
538
+ });
539
+ throw err;
540
+ }
541
+
542
+ const backoffMs = retryBackoffMs(attempt);
543
+ const nextRetryAt = new Date(Date.now() + backoffMs).toISOString();
544
+ await applyPhaseStatus(cwd, audit, phaseName, {
545
+ status: "in_progress",
546
+ error: `Attempt ${attempt}/${maxAttempts} failed: ${lastError}. Retrying at ${nextRetryAt}.`,
547
+ attempt,
548
+ max_attempts: maxAttempts,
549
+ retry_backoff_ms: backoffMs,
550
+ next_retry_at: nextRetryAt,
551
+ last_error: lastError,
552
+ });
553
+ await sleep(backoffMs, sig);
554
+ }
555
+ }
556
+ },
557
+ }),
558
+ ),
559
+ );
560
+ scheduler.dispose();
561
+ const failed = settled.some((r) => r.status === "rejected");
562
+ await applyPhaseStatus(cwd, audit, phase, {
563
+ status: failed ? "failed" : "complete",
564
+ ...(failed ? { error: `Some per-finding ${phase} runs failed.` } : {}),
565
+ });
566
+ return { failed };
567
+ }
568
+
569
+ export interface DeepTransientCleanupResult {
570
+ summaryPath: string;
571
+ removed: string[];
572
+ missing: string[];
573
+ retained: string[];
574
+ }
575
+
576
+ export function cleanupDeepTransientArtifacts(cwd: string): DeepTransientCleanupResult {
577
+ ensureAttackSurfaceDir(cwd);
578
+ const removed: string[] = [];
579
+ const missing: string[] = [];
580
+ for (const rel of DEEP_TRANSIENT_PATHS) {
581
+ const abs = join(cwd, rel);
582
+ if (!existsSync(abs)) {
583
+ missing.push(rel);
584
+ continue;
585
+ }
586
+ rmSync(abs, { recursive: true, force: true });
587
+ removed.push(rel);
588
+ }
589
+ const result: DeepTransientCleanupResult = {
590
+ summaryPath: DEEP_CLEANUP_SUMMARY,
591
+ removed,
592
+ missing,
593
+ retained: [
594
+ "piolium/attack-surface/",
595
+ "piolium/findings/",
596
+ "piolium/findings-theoretical/",
597
+ "piolium/final-audit-report.md",
598
+ "piolium/audit-state.json",
599
+ ],
600
+ };
601
+ writeFileSync(join(cwd, DEEP_CLEANUP_SUMMARY), `${JSON.stringify(result, null, "\t")}\n`);
602
+ return result;
603
+ }
604
+
605
+ /** Post-D14 finalize: file-state stamp note + transient cleanup, folded inline. */
606
+ function finalizeDeep(cwd: string): void {
607
+ cleanupDeepTransientArtifacts(cwd);
608
+ }
609
+
610
+ export async function runDeepAudit(opts: RunDeepOptions): Promise<RunDeepResult> {
611
+ const { cwd, signal, ui } = opts;
612
+ ui?.setStatus?.("piolium-deep", "● preparing recon");
613
+ const recon = await runReconAsync(cwd, { signal });
614
+ ensureAttackSurfaceDir(cwd);
615
+ ui?.setStatus?.("piolium-deep", "● scanning candidate files");
616
+ const candidateScan = await runCandidateScanAsync(cwd, { signal });
617
+ ui?.notify?.(
618
+ `Candidate scan: ${candidateScan.candidateCount} match(es) across ${candidateScan.candidateFiles} file(s).`,
619
+ "info",
620
+ );
621
+ let audit = pickResume(cwd, opts.forceFresh ?? false, Boolean(opts.only?.length));
622
+ if (!audit) {
623
+ audit = await initAudit(cwd, {
624
+ mode: "deep",
625
+ ...(recon.commit ? { commit: recon.commit } : { commit: null }),
626
+ ...(recon.branch ? { branch: recon.branch } : { branch: "nogit" }),
627
+ ...(recon.repository ? { repository: recon.repository } : {}),
628
+ history_available: recon.historyAvailable,
629
+ agent_sdk: "pi",
630
+ });
631
+ }
632
+
633
+ const { agents } = loadAgents({ cwd });
634
+ const specs: Record<string, PhaseSpec> = {
635
+ D1: {
636
+ name: "D1",
637
+ agent: agents.get("cve-scout"),
638
+ missingMessage: "cve-scout missing",
639
+ statusLabel: "● D1 intel-cve",
640
+ },
641
+ D2: {
642
+ name: "D2",
643
+ agent: agents.get("history-miner"),
644
+ missingMessage: "history-miner missing",
645
+ statusLabel: "● D2 intel-history",
646
+ },
647
+ D3: {
648
+ name: "D3",
649
+ agent: agents.get("patch-auditor"),
650
+ missingMessage: "patch-auditor missing",
651
+ statusLabel: "● D3 patch-audit",
652
+ },
653
+ D4: {
654
+ name: "D4",
655
+ agent: agents.get("threat-modeler"),
656
+ missingMessage: "threat-modeler missing",
657
+ statusLabel: "● D4 threat-model",
658
+ },
659
+ D5: {
660
+ name: "D5",
661
+ agent: agents.get("code-scanner"),
662
+ missingMessage: "code-scanner missing",
663
+ statusLabel: "● D5 code-scan",
664
+ },
665
+ D6: {
666
+ name: "D6",
667
+ agent: agents.get("probe-lead"),
668
+ missingMessage: "probe-lead missing",
669
+ statusLabel: "● D6 deep-probe",
670
+ },
671
+ D7: {
672
+ name: "D7",
673
+ agent: agents.get("access-auditor"),
674
+ missingMessage: "access-auditor missing",
675
+ statusLabel: "● D7 access-audit",
676
+ },
677
+ D8: {
678
+ name: "D8",
679
+ agent: agents.get("taint-tracer"),
680
+ missingMessage: "taint-tracer missing",
681
+ statusLabel: "● D8 taint-trace",
682
+ },
683
+ D9: {
684
+ name: "D9",
685
+ agent: agents.get("review-adjudicator"),
686
+ missingMessage: "review-adjudicator missing",
687
+ statusLabel: "● D9 review-panel",
688
+ },
689
+ D10: {
690
+ name: "D10",
691
+ agent: agents.get("context-reviewer"),
692
+ missingMessage: "context-reviewer missing",
693
+ statusLabel: "● D10 intent-reconcile",
694
+ },
695
+ D11: {
696
+ name: "D11",
697
+ agent: agents.get("variant-scanner"),
698
+ missingMessage: "variant-scanner missing",
699
+ statusLabel: "● D11 variant-sweep",
700
+ },
701
+ D12: {
702
+ name: "D12",
703
+ agent: agents.get("poc-author"),
704
+ missingMessage: "poc-author missing",
705
+ statusLabel: "● D12 poc-author",
706
+ },
707
+ D13: {
708
+ name: "D13",
709
+ agent: agents.get("finding-writer"),
710
+ missingMessage: "finding-writer missing",
711
+ statusLabel: "● D13 finding-finalize",
712
+ },
713
+ D14: {
714
+ name: "D14",
715
+ agent: agents.get("report-composer"),
716
+ missingMessage: "report-composer missing",
717
+ statusLabel: "● D14 report-compose",
718
+ },
719
+ };
720
+
721
+ const spec = (name: keyof typeof specs): PhaseSpec => {
722
+ const s = specs[name];
723
+ if (!s) throw new Error(`Internal: phase spec ${name} missing`);
724
+ return s;
725
+ };
726
+
727
+ let failed = false;
728
+ const want = (name: string) => shouldRun(name, opts.only);
729
+
730
+ const runSequential = async (name: string, fn: () => Promise<void>) => {
731
+ if (!want(name)) return;
732
+ ensurePrereqs(audit, name);
733
+ try {
734
+ await fn();
735
+ } catch {
736
+ failed = true;
737
+ throw new Error(`Phase ${name} failed`);
738
+ }
739
+ };
740
+
741
+ const skipNoGit = async (name: "D2" | "D3") => {
742
+ if (!want(name)) return;
743
+ ensurePrereqs(audit, name);
744
+ if (!recon.historyAvailable) {
745
+ await applyPhaseStatus(cwd, audit, name, {
746
+ status: "skipped",
747
+ error: "no git history available",
748
+ });
749
+ return;
750
+ }
751
+ try {
752
+ await runOne(cwd, audit, spec(name), true, signal, ui, opts.agentRuntime);
753
+ } catch {
754
+ failed = true;
755
+ }
756
+ };
757
+
758
+ try {
759
+ // [D1 ‖ D2] — D2 skipped when no git.
760
+ if (want("D1") || want("D2")) {
761
+ const wave: PhaseSpec[] = [];
762
+ if (want("D1")) {
763
+ ensurePrereqs(audit, "D1");
764
+ wave.push(spec("D1"));
765
+ }
766
+ if (want("D2") && recon.historyAvailable) {
767
+ ensurePrereqs(audit, "D2");
768
+ wave.push(spec("D2"));
769
+ } else {
770
+ await skipNoGit("D2");
771
+ }
772
+ if (wave.length > 0) {
773
+ const r = await runFanout3(
774
+ cwd,
775
+ audit,
776
+ wave,
777
+ recon.historyAvailable,
778
+ signal,
779
+ ui,
780
+ opts.agentRuntime,
781
+ );
782
+ if (r.failed) failed = true;
783
+ }
784
+ }
785
+
786
+ if (!failed) await skipNoGit("D3");
787
+
788
+ if (!failed) {
789
+ await runSequential("D4", () =>
790
+ runOne(cwd, audit, spec("D4"), recon.historyAvailable, signal, ui, opts.agentRuntime),
791
+ );
792
+
793
+ // Wave A: [D5 ‖ D7] (cap 2, within burst cap).
794
+ if (!failed && (want("D5") || want("D7"))) {
795
+ const waveA = [spec("D5"), spec("D7")].filter((s) => want(s.name));
796
+ for (const s of waveA) ensurePrereqs(audit, s.name);
797
+ const r = await runFanout3(
798
+ cwd,
799
+ audit,
800
+ waveA,
801
+ recon.historyAvailable,
802
+ signal,
803
+ ui,
804
+ opts.agentRuntime,
805
+ );
806
+ if (r.failed) failed = true;
807
+ }
808
+
809
+ // Wave B: D6 (own wave — a probe team uses the full burst cap).
810
+ if (!failed) {
811
+ await runSequential("D6", () =>
812
+ runOne(cwd, audit, spec("D6"), recon.historyAvailable, signal, ui, opts.agentRuntime),
813
+ );
814
+ }
815
+
816
+ if (!failed) {
817
+ await runSequential("D8", () =>
818
+ runOne(cwd, audit, spec("D8"), recon.historyAvailable, signal, ui, opts.agentRuntime),
819
+ );
820
+ await runSequential("D9", () =>
821
+ runOne(cwd, audit, spec("D9"), recon.historyAvailable, signal, ui, opts.agentRuntime),
822
+ );
823
+
824
+ // D10 Intent Reconciliation — skip-and-continue.
825
+ if (want("D10")) {
826
+ ensurePrereqs(audit, "D10");
827
+ try {
828
+ await runOne(cwd, audit, spec("D10"), recon.historyAvailable, signal, ui, opts.agentRuntime);
829
+ } catch {
830
+ await applyPhaseStatus(cwd, audit, "D10", {
831
+ status: "failed",
832
+ error: "policy: skip-and-continue",
833
+ });
834
+ ui?.notify?.("D10 Intent Reconciliation skipped (skip-and-continue).", "warning");
835
+ }
836
+ }
837
+
838
+ // Consolidate chamber-survived (d9-) + variant (d11-) drafts into
839
+ // severity-prefixed finding dirs, then route triage-skip ones to
840
+ // the theoretical bucket.
841
+ const consolidate = () => {
842
+ const consolidation = consolidateDrafts(cwd, ["d9-", "d11-"]);
843
+ writeFileSync(
844
+ join(cwd, CONSOLIDATION_MANIFEST),
845
+ `${JSON.stringify(
846
+ {
847
+ generated_at: new Date().toISOString(),
848
+ source_prefixes: ["d9-", "d11-"],
849
+ promoted: consolidation.promoted,
850
+ dropped: consolidation.dropped,
851
+ },
852
+ null,
853
+ "\t",
854
+ )}\n`,
855
+ );
856
+ partitionFindings(cwd);
857
+ };
858
+ consolidate();
859
+
860
+ await runSequential("D11", () =>
861
+ runOne(cwd, audit, spec("D11"), recon.historyAvailable, signal, ui, opts.agentRuntime),
862
+ );
863
+ consolidate();
864
+
865
+ if (want("D12")) {
866
+ ensurePrereqs(audit, "D12");
867
+ const r = await runPerFinding(
868
+ cwd,
869
+ audit,
870
+ "D12",
871
+ spec("D12").agent,
872
+ listFindingDirs(cwd),
873
+ signal,
874
+ ui,
875
+ opts.agentRuntime,
876
+ );
877
+ if (r.failed) failed = true;
878
+ if (!failed) partitionFindings(cwd);
879
+ }
880
+ if (!failed && want("D13")) {
881
+ ensurePrereqs(audit, "D13");
882
+ const r = await runPerFinding(
883
+ cwd,
884
+ audit,
885
+ "D13",
886
+ spec("D13").agent,
887
+ listAllFindingDirs(cwd),
888
+ signal,
889
+ ui,
890
+ opts.agentRuntime,
891
+ );
892
+ if (r.failed) failed = true;
893
+ }
894
+ if (!failed && want("D14")) {
895
+ await runSequential("D14", () =>
896
+ runOne(cwd, audit, spec("D14"), recon.historyAvailable, signal, ui, opts.agentRuntime),
897
+ );
898
+ if (!failed) finalizeDeep(cwd);
899
+ }
900
+ }
901
+ }
902
+ } catch {
903
+ failed = true;
904
+ }
905
+
906
+ await markAuditStatus(cwd, audit.audit_id, failed ? "failed" : "complete");
907
+ const fresh =
908
+ readAuditState(cwd).state?.audits.find((a) => a.audit_id === audit.audit_id) ?? audit;
909
+ const phases: Record<string, "complete" | "failed" | "skipped"> = {};
910
+ for (const [name, p] of Object.entries(fresh.phases)) {
911
+ if (p.status === "complete" || p.status === "failed" || p.status === "skipped") {
912
+ phases[name] = p.status;
913
+ }
914
+ }
915
+ ui?.notify?.(failed ? "Deep audit failed." : "Deep audit complete.", failed ? "error" : "info");
916
+ return { auditId: audit.audit_id, status: failed ? "failed" : "complete", phases };
917
+ }