@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,546 @@
1
+ // Reinvest mode (`/piolium-reinvest`). Cross-agent re-verification of
2
+ // CRITICAL/HIGH findings produced by a prior audit (command-defs/reinvest.md,
3
+ // archon-audit @ 2026-05-16). Phase 1 enumerate → phase 2 cross-verifier
4
+ // fan-out (cap 3) → phase 3 consensus summary. Existing report.md /
5
+ // poc / evidence / prior audits[] entries are immutable; only
6
+ // piolium/reinvest-report.md is produced.
7
+
8
+ import { randomUUID } from "node:crypto";
9
+ import { existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from "node:fs";
10
+ import { join, relative } from "node:path";
11
+ import { type AgentRuntimeModel, runAgent } from "../agent-runner.ts";
12
+ import { type AgentDefinition, loadAgents } from "../agents.ts";
13
+ import {
14
+ type AuditRunState,
15
+ applyPhaseStatus,
16
+ initAudit,
17
+ latestAudit,
18
+ markAuditStatus,
19
+ readAuditState,
20
+ } from "../audit-state.ts";
21
+ import { type FindingDir, listFindingDirs } from "../findings.ts";
22
+ import { errorMessage } from "../retry.ts";
23
+ import { Scheduler } from "../scheduler.ts";
24
+ import type { PhaseUiHooks } from "./phase-runner.ts";
25
+
26
+ export interface RunReinvestOptions {
27
+ cwd: string;
28
+ signal?: AbortSignal;
29
+ ui?: PhaseUiHooks;
30
+ forceFresh?: boolean;
31
+ agentRuntime?: AgentRuntimeModel;
32
+ /**
33
+ * Optional comma-separated ID allow-list (e.g. `["C1", "H1", "H3"]`). When
34
+ * empty, every C-prefixed and H-prefixed finding under `piolium/findings/`
35
+ * is reinvested. MEDIUM/LOW are always excluded — too numerous to justify
36
+ * a mass second-pass at this verifier cost.
37
+ */
38
+ scope?: readonly string[];
39
+ }
40
+
41
+ export interface RunReinvestResult {
42
+ auditId: string;
43
+ status: "complete" | "failed";
44
+ phases: Record<string, "complete" | "failed" | "skipped">;
45
+ reportPath: string;
46
+ reinvestedCount: number;
47
+ flippedCount: number;
48
+ uncertainCount: number;
49
+ }
50
+
51
+ export const REINVEST_WORKSPACE = "piolium/reinvest-workspace";
52
+ export const REINVEST_SCOPE_FILE = `${REINVEST_WORKSPACE}/scope.json`;
53
+ export const REINVEST_REPORT = "piolium/reinvest-report.md";
54
+ const REINVEST_BURST_CAP = 3;
55
+ const STATUS_KEY = "piolium-reinvest";
56
+
57
+ interface ReinvestScopeEntry {
58
+ id: string;
59
+ slug: string;
60
+ dir: string;
61
+ wave: number;
62
+ }
63
+
64
+ interface ReinvestScopeFile {
65
+ parent_audit_id: string | null;
66
+ baseline_agent_sdk: string | null;
67
+ baseline_model: string | null;
68
+ current_agent_sdk: string;
69
+ scope: ReinvestScopeEntry[];
70
+ }
71
+
72
+ function isCritOrHigh(id: string): boolean {
73
+ return /^[CH][0-9]+$/i.test(id);
74
+ }
75
+
76
+ function nextWaveNumber(findingDir: string): number {
77
+ const re = /^wave-(\d+)-verdict\.md$/;
78
+ let max = 1;
79
+ for (const entry of readdirSync(findingDir)) {
80
+ const raw = re.exec(entry)?.[1];
81
+ if (!raw) continue;
82
+ const n = Number.parseInt(raw, 10);
83
+ if (n > max) max = n;
84
+ }
85
+ return max + 1;
86
+ }
87
+
88
+ function pickResume(cwd: string, force: boolean): AuditRunState | undefined {
89
+ if (force) return undefined;
90
+ const state = readAuditState(cwd).state;
91
+ const audit = state ? latestAudit(state) : undefined;
92
+ if (!audit) return undefined;
93
+ if (audit.mode !== "reinvest") return undefined;
94
+ if (audit.status === "complete") return undefined;
95
+ return audit;
96
+ }
97
+
98
+ function setStatus(ui: PhaseUiHooks | undefined, text?: string): void {
99
+ ui?.setStatus?.(STATUS_KEY, text);
100
+ }
101
+
102
+ function ensureWorkdir(cwd: string): void {
103
+ mkdirSync(join(cwd, REINVEST_WORKSPACE), { recursive: true });
104
+ }
105
+
106
+ /**
107
+ * Phase 1 — Enumerate. Build the reinvest scope. Returns the list of in-scope
108
+ * finding directories with their assigned wave numbers. Writes
109
+ * `piolium/reinvest-workspace/scope.json` for downstream phases to read.
110
+ */
111
+ function runI1(
112
+ cwd: string,
113
+ audit: AuditRunState,
114
+ opts: RunReinvestOptions,
115
+ priorAudit: AuditRunState | undefined,
116
+ critHigh: FindingDir[],
117
+ ): ReinvestScopeFile {
118
+ const scope =
119
+ opts.scope && opts.scope.length > 0
120
+ ? critHigh.filter((d) =>
121
+ opts.scope?.some((requested) => requested.toUpperCase() === d.id.toUpperCase()),
122
+ )
123
+ : critHigh;
124
+
125
+ const scopeFile: ReinvestScopeFile = {
126
+ parent_audit_id: priorAudit?.audit_id ?? null,
127
+ baseline_agent_sdk: priorAudit?.agent_sdk ?? null,
128
+ baseline_model: priorAudit?.model ?? null,
129
+ current_agent_sdk: audit.agent_sdk ?? "pi",
130
+ scope: scope.map((d) => ({
131
+ id: d.id,
132
+ slug: d.slug,
133
+ dir: d.path,
134
+ wave: nextWaveNumber(d.path),
135
+ })),
136
+ };
137
+
138
+ ensureWorkdir(cwd);
139
+ writeFileSync(join(cwd, REINVEST_SCOPE_FILE), `${JSON.stringify(scopeFile, null, "\t")}\n`);
140
+ return scopeFile;
141
+ }
142
+
143
+ function buildVerifierTask(
144
+ cwd: string,
145
+ entry: ReinvestScopeEntry,
146
+ scope: ReinvestScopeFile,
147
+ totalInScope: number,
148
+ rank: number,
149
+ ): string {
150
+ const relDir = relative(cwd, entry.dir);
151
+ const baseline = scope.baseline_agent_sdk
152
+ ? `${scope.baseline_agent_sdk}${scope.baseline_model ? `/${scope.baseline_model}` : ""}`
153
+ : "(unknown prior agent)";
154
+ return [
155
+ `Cross-agent reinvest of \`${relDir}\`.`,
156
+ `This is finding ${rank} of ${totalInScope} in the current reinvest wave.`,
157
+ `Assign wave number ${entry.wave} (filename: wave-${entry.wave}-verdict.md).`,
158
+ `You are running under \`${scope.current_agent_sdk}\` — the original verdict came from \`${baseline}\`.`,
159
+ "",
160
+ "Procedure:",
161
+ " 1. Read this finding's `report.md` and the contents of `evidence/`.",
162
+ " 2. Restate the claim independently in your own words (do not paraphrase report.md's `Trace:` block).",
163
+ " 3. Trace from source code, not from the report's quoted snippets.",
164
+ " 4. Look for protections that may invalidate the claim (input validation, capability checks, framework defaults, etc.).",
165
+ " 5. If a runnable `poc.*` exists and is safely runnable in your environment, attempt reproduction.",
166
+ " 6. ONLY AFTER forming your own view, read any prior `wave-*-verdict.md` files and explicitly acknowledge agreement or disagreement.",
167
+ "",
168
+ `Write \`${relDir}/wave-${entry.wave}-verdict.md\` with:`,
169
+ " - Verdict: CONFIRMED | DISPROVED | UNCERTAIN",
170
+ ` - Wave-${entry.wave}-Agent: ${scope.current_agent_sdk}`,
171
+ " - Independent-Trace: a fresh trace from source to sink",
172
+ " - Protections-Considered: list each protection examined and why it does/doesn't invalidate the claim",
173
+ " - PoC-Attempt: command + outcome (or 'not attempted: reason')",
174
+ " - Agreement-With-Prior-Waves: explicit per-wave agreement/disagreement notes",
175
+ "",
176
+ `Also append \`Wave-${entry.wave}-Verdict:\` and \`Wave-${entry.wave}-Agent:\` lines to this finding's \`draft.md\` frontmatter (if draft.md exists).`,
177
+ "",
178
+ "DO NOT modify report.md, poc.*, evidence/, or any other finding directory. The original audit's artefacts are immutable in reinvest mode.",
179
+ ].join("\n");
180
+ }
181
+
182
+ /**
183
+ * Phase 2 — Fan-out cross-verifier across the scope (burst cap 3). Each task gets
184
+ * one retry on failure; persistent failure is recorded in the audit-state
185
+ * artifacts and surfaced in the phase 3 report's "Failed Reinvests" section.
186
+ */
187
+ async function runI2(
188
+ cwd: string,
189
+ audit: AuditRunState,
190
+ verifier: AgentDefinition | undefined,
191
+ scope: ReinvestScopeFile,
192
+ opts: RunReinvestOptions,
193
+ ): Promise<{ completed: number; failed: string[] }> {
194
+ if (scope.scope.length === 0) {
195
+ await applyPhaseStatus(cwd, audit, "2", {
196
+ status: "skipped",
197
+ error: "No CRIT/HIGH findings in scope; nothing to verify.",
198
+ });
199
+ return { completed: 0, failed: [] };
200
+ }
201
+ if (!verifier) {
202
+ await applyPhaseStatus(cwd, audit, "2", {
203
+ status: "failed",
204
+ error: "cross-verifier agent not found in bundled agents/.",
205
+ });
206
+ throw new Error("cross-verifier agent missing");
207
+ }
208
+
209
+ await applyPhaseStatus(cwd, audit, "2", { status: "in_progress" });
210
+ setStatus(opts.ui, `● 2 verifying ${scope.scope.length} finding(s)`);
211
+
212
+ const scheduler = new Scheduler({
213
+ maxConcurrent: REINVEST_BURST_CAP,
214
+ ...(opts.signal ? { signal: opts.signal } : {}),
215
+ });
216
+ const auditIdSafe = audit.audit_id.replace(/[:.]/g, "-");
217
+ const total = scope.scope.length;
218
+ let completed = 0;
219
+ const failed: string[] = [];
220
+
221
+ const updateStatus = () => {
222
+ setStatus(
223
+ opts.ui,
224
+ `● 2 verifying (${completed}/${total} done${failed.length > 0 ? `, ${failed.length} failed` : ""})`,
225
+ );
226
+ };
227
+ updateStatus();
228
+
229
+ const tasks = scope.scope.map((entry, idx) => ({
230
+ id: `reinvest-${entry.id}`,
231
+ run: async (signal: AbortSignal) => {
232
+ const verdictPath = join(entry.dir, `wave-${entry.wave}-verdict.md`);
233
+ for (let attempt = 1; attempt <= 2; attempt++) {
234
+ const runId = `i2-${auditIdSafe}-${entry.id.toLowerCase()}-a${attempt}-${randomUUID().slice(0, 8)}`;
235
+ try {
236
+ await runAgent({
237
+ agent: verifier,
238
+ task: buildVerifierTask(cwd, entry, scope, total, idx + 1),
239
+ runId,
240
+ runtime: {
241
+ cwd,
242
+ mode: "reinvest",
243
+ phase: "2",
244
+ outputPaths: [entry.dir],
245
+ notes: [
246
+ `Finding: ${entry.id}-${entry.slug}`,
247
+ `Wave: ${entry.wave}`,
248
+ `Burst cap: ${REINVEST_BURST_CAP}`,
249
+ `Baseline agent: ${scope.baseline_agent_sdk ?? "(unknown)"}`,
250
+ ],
251
+ },
252
+ ...(opts.agentRuntime ? opts.agentRuntime : {}),
253
+ signal,
254
+ onEvent: (event) => opts.ui?.onAgentEvent?.("2", event),
255
+ });
256
+ if (!existsSync(verdictPath)) {
257
+ throw new Error(`cross-verifier did not write ${verdictPath}`);
258
+ }
259
+ completed++;
260
+ updateStatus();
261
+ return;
262
+ } catch (err) {
263
+ if (attempt === 1) {
264
+ opts.ui?.notify?.(
265
+ `Reinvest ${entry.id} attempt ${attempt} failed: ${errorMessage(err)}. Retrying.`,
266
+ "warning",
267
+ );
268
+ continue;
269
+ }
270
+ failed.push(`${entry.id}: ${errorMessage(err)}`);
271
+ updateStatus();
272
+ return;
273
+ }
274
+ }
275
+ },
276
+ }));
277
+
278
+ await scheduler.runBatch(tasks);
279
+ scheduler.dispose();
280
+
281
+ if (failed.length === total) {
282
+ await applyPhaseStatus(cwd, audit, "2", {
283
+ status: "failed",
284
+ error: `All ${total} cross-verifier tasks failed.`,
285
+ });
286
+ } else {
287
+ await applyPhaseStatus(cwd, audit, "2", {
288
+ status: "complete",
289
+ ...(failed.length > 0
290
+ ? { error: `${failed.length}/${total} cross-verifiers failed; see report.` }
291
+ : {}),
292
+ });
293
+ }
294
+ setStatus(opts.ui, undefined);
295
+ return { completed, failed };
296
+ }
297
+
298
+ type WaveVerdict = "CONFIRMED" | "DISPROVED" | "UNCERTAIN" | "UNKNOWN";
299
+
300
+ function parseVerdict(text: string): WaveVerdict {
301
+ const m = /^\s*(?:Verdict|verdict)\s*:\s*([A-Z]+)/m.exec(text);
302
+ if (!m) return "UNKNOWN";
303
+ const v = (m[1] ?? "").toUpperCase();
304
+ if (v === "CONFIRMED" || v === "DISPROVED" || v === "UNCERTAIN") return v;
305
+ return "UNKNOWN";
306
+ }
307
+
308
+ interface FindingConsensus {
309
+ id: string;
310
+ slug: string;
311
+ verdictsByWave: Array<{ wave: number; verdict: WaveVerdict }>;
312
+ consensus: "stable-confirmed" | "flipped-disproved" | "mixed-uncertain" | "no-verdicts";
313
+ }
314
+
315
+ function classifyConsensus(verdicts: WaveVerdict[]): FindingConsensus["consensus"] {
316
+ if (verdicts.length === 0) return "no-verdicts";
317
+ if (verdicts.includes("DISPROVED")) return "flipped-disproved";
318
+ if (verdicts.includes("UNCERTAIN") || verdicts.includes("UNKNOWN")) return "mixed-uncertain";
319
+ if (verdicts.every((v) => v === "CONFIRMED")) return "stable-confirmed";
320
+ return "mixed-uncertain";
321
+ }
322
+
323
+ function readFindingConsensus(entry: ReinvestScopeEntry): FindingConsensus {
324
+ const re = /^wave-(\d+)-verdict\.md$/;
325
+ const verdictsByWave: FindingConsensus["verdictsByWave"] = [];
326
+ for (const file of readdirSync(entry.dir)) {
327
+ const m = re.exec(file);
328
+ const raw = m?.[1];
329
+ if (!raw) continue;
330
+ const text = readFileSync(join(entry.dir, file), "utf8");
331
+ verdictsByWave.push({ wave: Number.parseInt(raw, 10), verdict: parseVerdict(text) });
332
+ }
333
+ verdictsByWave.sort((a, b) => a.wave - b.wave);
334
+ return {
335
+ id: entry.id,
336
+ slug: entry.slug,
337
+ verdictsByWave,
338
+ consensus: classifyConsensus(verdictsByWave.map((v) => v.verdict)),
339
+ };
340
+ }
341
+
342
+ /**
343
+ * Phase 3 — Walk every reinvested finding, read its wave verdicts, compute the
344
+ * consensus, and write the delta report. Pure local logic.
345
+ */
346
+ function runI3(
347
+ cwd: string,
348
+ audit: AuditRunState,
349
+ scope: ReinvestScopeFile,
350
+ failedFindings: string[],
351
+ ): { reportPath: string; reinvested: number; flipped: number; uncertain: number } {
352
+ const consensusList = scope.scope.map(readFindingConsensus);
353
+
354
+ const flipped = consensusList.filter((c) => c.consensus === "flipped-disproved");
355
+ const uncertain = consensusList.filter((c) => c.consensus === "mixed-uncertain");
356
+ const stable = consensusList.filter((c) => c.consensus === "stable-confirmed");
357
+
358
+ const baseline = scope.baseline_agent_sdk
359
+ ? `${scope.baseline_agent_sdk}${scope.baseline_model ? ` / ${scope.baseline_model}` : ""}`
360
+ : "(unknown — no prior audit recorded an agent_sdk)";
361
+
362
+ const lines: string[] = [
363
+ "# Cross-Agent Reinvest Report",
364
+ "",
365
+ `**Reinvest audit_id:** ${audit.audit_id}`,
366
+ `**Parent audit:** ${scope.parent_audit_id ?? "(none)"} (${baseline})`,
367
+ `**Reinvest agent:** ${scope.current_agent_sdk}`,
368
+ `**Findings reinvested:** ${consensusList.length}`,
369
+ `**Stable confirmed:** ${stable.length}`,
370
+ `**Flipped to disproved:** ${flipped.length}`,
371
+ `**Mixed / uncertain:** ${uncertain.length}`,
372
+ "",
373
+ "## Consensus",
374
+ "",
375
+ "| ID | Slug | Waves | Verdicts | Consensus |",
376
+ "| --- | --- | --- | --- | --- |",
377
+ ];
378
+ for (const c of consensusList) {
379
+ const waves = c.verdictsByWave.map((v) => v.wave).join(", ") || "—";
380
+ const verdicts = c.verdictsByWave.map((v) => v.verdict).join(" → ") || "—";
381
+ lines.push(`| ${c.id} | ${c.slug} | ${waves} | ${verdicts} | ${c.consensus} |`);
382
+ }
383
+ lines.push("");
384
+
385
+ if (flipped.length > 0) {
386
+ lines.push("## Findings That Flipped to DISPROVED", "");
387
+ for (const c of flipped) {
388
+ lines.push(
389
+ `- **${c.id} ${c.slug}** — verdicts: ${c.verdictsByWave.map((v) => `wave ${v.wave}=${v.verdict}`).join(", ")}.`,
390
+ );
391
+ }
392
+ lines.push("");
393
+ }
394
+ if (uncertain.length > 0) {
395
+ lines.push("## Findings That Remain Uncertain", "");
396
+ for (const c of uncertain) {
397
+ lines.push(
398
+ `- **${c.id} ${c.slug}** — verdicts: ${c.verdictsByWave.map((v) => `wave ${v.wave}=${v.verdict}`).join(", ")}.`,
399
+ );
400
+ }
401
+ lines.push("");
402
+ }
403
+ if (failedFindings.length > 0) {
404
+ lines.push("## Failed Reinvests", "");
405
+ for (const msg of failedFindings) {
406
+ lines.push(`- ${msg}`);
407
+ }
408
+ lines.push("");
409
+ }
410
+
411
+ const path = join(cwd, REINVEST_REPORT);
412
+ writeFileSync(path, `${lines.join("\n").trimEnd()}\n`);
413
+ return {
414
+ reportPath: path,
415
+ reinvested: consensusList.length,
416
+ flipped: flipped.length,
417
+ uncertain: uncertain.length,
418
+ };
419
+ }
420
+
421
+ export async function runReinvestAudit(opts: RunReinvestOptions): Promise<RunReinvestResult> {
422
+ const { cwd, ui } = opts;
423
+
424
+ // Preflight: an existing audit state is required for `parent_audit_id`.
425
+ const stateResult = readAuditState(cwd);
426
+ if (!stateResult.exists || !stateResult.state) {
427
+ throw new Error("Reinvest requires a prior audit. Run /piolium-deep or /piolium-balanced first.");
428
+ }
429
+
430
+ // Baseline = most recent non-reinvest audit; if there are only reinvests
431
+ // recorded, fall back to the freshest entry so we still record a parent.
432
+ const priorAudit =
433
+ [...stateResult.state.audits]
434
+ .sort((a, b) => (a.started_at < b.started_at ? 1 : -1))
435
+ .find((a) => a.mode !== "reinvest") ?? latestAudit(stateResult.state);
436
+
437
+ const critHigh = listFindingDirs(cwd).filter((d) => d.hasReport && isCritOrHigh(d.id));
438
+ if (critHigh.length === 0) {
439
+ throw new Error(
440
+ "No CRITICAL or HIGH finding directories under piolium/findings/. Nothing to reinvest.",
441
+ );
442
+ }
443
+
444
+ ensureWorkdir(cwd);
445
+ let audit = pickResume(cwd, opts.forceFresh ?? false);
446
+ if (!audit) {
447
+ audit = await initAudit(cwd, {
448
+ mode: "reinvest",
449
+ ...(priorAudit?.commit !== undefined ? { commit: priorAudit.commit } : {}),
450
+ ...(priorAudit?.branch !== undefined ? { branch: priorAudit.branch } : {}),
451
+ ...(priorAudit?.repository !== undefined ? { repository: priorAudit.repository } : {}),
452
+ ...(priorAudit?.history_available !== undefined
453
+ ? { history_available: priorAudit.history_available }
454
+ : {}),
455
+ agent_sdk: "pi",
456
+ });
457
+ }
458
+
459
+ const { agents } = loadAgents({ cwd });
460
+ const verifier = agents.get("cross-verifier");
461
+
462
+ setStatus(ui, "● 1 enumerating CRIT/HIGH findings");
463
+ await applyPhaseStatus(cwd, audit, "1", { status: "in_progress" });
464
+ let scope: ReinvestScopeFile;
465
+ try {
466
+ scope = runI1(cwd, audit, opts, priorAudit, critHigh);
467
+ await applyPhaseStatus(cwd, audit, "1", {
468
+ status: "complete",
469
+ artifacts: [REINVEST_SCOPE_FILE],
470
+ });
471
+ } catch (err) {
472
+ await applyPhaseStatus(cwd, audit, "1", {
473
+ status: "failed",
474
+ error: errorMessage(err),
475
+ });
476
+ await markAuditStatus(cwd, audit.audit_id, "failed");
477
+ throw err;
478
+ }
479
+
480
+ if (scope.scope.length === 0) {
481
+ ui?.notify?.(
482
+ opts.scope && opts.scope.length > 0
483
+ ? `Reinvest scope (${opts.scope.join(", ")}) matched no CRIT/HIGH findings.`
484
+ : "No CRITICAL or HIGH findings to reinvest.",
485
+ "info",
486
+ );
487
+ }
488
+
489
+ let failedFindings: string[] = [];
490
+ let failedI2 = false;
491
+ try {
492
+ const result = await runI2(cwd, audit, verifier, scope, opts);
493
+ failedFindings = result.failed;
494
+ } catch (err) {
495
+ failedI2 = true;
496
+ ui?.notify?.(`Phase 2 fan-out aborted: ${errorMessage(err)}`, "error");
497
+ }
498
+
499
+ let reportInfo: { reportPath: string; reinvested: number; flipped: number; uncertain: number };
500
+ setStatus(ui, "● 3 computing consensus");
501
+ await applyPhaseStatus(cwd, audit, "3", { status: "in_progress" });
502
+ try {
503
+ reportInfo = runI3(cwd, audit, scope, failedFindings);
504
+ await applyPhaseStatus(cwd, audit, "3", {
505
+ status: "complete",
506
+ artifacts: [REINVEST_REPORT],
507
+ });
508
+ } catch (err) {
509
+ await applyPhaseStatus(cwd, audit, "3", {
510
+ status: "failed",
511
+ error: errorMessage(err),
512
+ });
513
+ await markAuditStatus(cwd, audit.audit_id, "failed");
514
+ throw err;
515
+ }
516
+ setStatus(ui, undefined);
517
+
518
+ const auditFailed = failedI2 || failedFindings.length > 0;
519
+ await markAuditStatus(cwd, audit.audit_id, auditFailed ? "failed" : "complete");
520
+
521
+ const fresh =
522
+ readAuditState(cwd).state?.audits.find((a) => a.audit_id === audit.audit_id) ?? audit;
523
+ const phases: Record<string, "complete" | "failed" | "skipped"> = {};
524
+ for (const [name, p] of Object.entries(fresh.phases)) {
525
+ if (p.status === "complete" || p.status === "failed" || p.status === "skipped") {
526
+ phases[name] = p.status;
527
+ }
528
+ }
529
+
530
+ ui?.notify?.(
531
+ auditFailed
532
+ ? `Reinvest completed with ${failedFindings.length} failed cross-verifier(s); see ${REINVEST_REPORT}.`
533
+ : `Reinvest complete. Report: ${REINVEST_REPORT}.`,
534
+ auditFailed ? "warning" : "info",
535
+ );
536
+
537
+ return {
538
+ auditId: audit.audit_id,
539
+ status: auditFailed ? "failed" : "complete",
540
+ phases,
541
+ reportPath: reportInfo.reportPath,
542
+ reinvestedCount: reportInfo.reinvested,
543
+ flippedCount: reportInfo.flipped,
544
+ uncertainCount: reportInfo.uncertain,
545
+ };
546
+ }