@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,697 @@
1
+ /**
2
+ * Confirm mode (`/piolium-confirm`).
3
+ *
4
+ * Verification pass over an already-completed audit (command-defs/confirm.md,
5
+ * archon-audit @ 2026-05-16). Seven phases:
6
+ *
7
+ * V1 findings inventory (env-profiler surveys & classifies findings by
8
+ * exploitability: network / local / non-exploitable)
9
+ * V1.5 intent cross-check (context-reviewer cross-references findings against
10
+ * repo-local SECURITY.md / threat models; annotates
11
+ * `Documented-Intent` — annotate-only, never gates
12
+ * V4/V5, never overrides Severity-Final;
13
+ * skip-and-continue on failure)
14
+ * V2 env discovery (env-profiler probes target environment)
15
+ * V3 provisioner (env-builder sets up exploit targets)
16
+ * V4 PoC execution (poc-runner runs PoCs, class-routed)
17
+ * V5 test fallback (test-locator maps PoCs onto tests when runtime
18
+ * is unavailable)
19
+ * V6 reporter (confirm-writer writes piolium/confirmation-report.md)
20
+ *
21
+ * Upstream folds cleanup/redaction into a post-V6 EXIT trap rather than a
22
+ * tracked phase, so piolium runs `cleanupConfirmArtifacts` as an
23
+ * always-run post-pass after V6 (no separate V7 phase).
24
+ *
25
+ * MVP simplification: V1-V6 are one agent run each. No real Docker/VM
26
+ * provisioning is built into the orchestrator; the env-builder agent's own
27
+ * prompt covers that and writes
28
+ * `piolium/confirm-workspace/env-connection.json`.
29
+ */
30
+
31
+ import {
32
+ existsSync,
33
+ mkdirSync,
34
+ readFileSync,
35
+ readdirSync,
36
+ renameSync,
37
+ statSync,
38
+ writeFileSync,
39
+ } from "node:fs";
40
+ import { basename, extname, join } from "node:path";
41
+ import type { AgentRuntimeModel } from "../agent-runner.ts";
42
+ import { loadAgents } from "../agents.ts";
43
+ import {
44
+ type AuditRunState,
45
+ applyPhaseStatus,
46
+ initAudit,
47
+ latestAudit,
48
+ markAuditStatus,
49
+ readAuditState,
50
+ } from "../audit-state.ts";
51
+ import { runReconAsync } from "../recon.ts";
52
+ import { type PhaseUiHooks, runAgentPhase } from "./phase-runner.ts";
53
+
54
+ export interface RunConfirmOptions {
55
+ cwd: string;
56
+ signal?: AbortSignal;
57
+ ui?: PhaseUiHooks;
58
+ forceFresh?: boolean;
59
+ agentRuntime?: AgentRuntimeModel;
60
+ /** Optional remote URL (e.g. https://target.example.com) — bypasses local provisioning. */
61
+ target?: string;
62
+ }
63
+
64
+ export interface RunConfirmResult {
65
+ auditId: string;
66
+ status: "complete" | "failed";
67
+ phases: Record<string, "complete" | "failed" | "skipped">;
68
+ }
69
+
70
+ export const CONFIRM_WORKSPACE = "piolium/confirm-workspace";
71
+ export const CONFIRM_REPORT = "piolium/confirmation-report.md";
72
+ const WORK = CONFIRM_WORKSPACE;
73
+ const REPORT = CONFIRM_REPORT;
74
+ export const POC_RESULTS = `${WORK}/poc-results.json`;
75
+ export const INTENT_CORPUS = `${WORK}/intent-corpus.json`;
76
+ const FP_RENAMES = `${WORK}/false-positive-renames.json`;
77
+ export const CLEANUP_SUMMARY = `${WORK}/cleanup-summary.json`;
78
+ const MAX_REDACTABLE_BYTES = 5 * 1024 * 1024;
79
+
80
+ export const CONFIRM_AGENT_PHASES = ["V1", "V1.5", "V2", "V3", "V4", "V5", "V6"] as const;
81
+
82
+ const TEXT_EXTENSIONS = new Set([
83
+ ".csv",
84
+ ".curl",
85
+ ".env",
86
+ ".err",
87
+ ".html",
88
+ ".http",
89
+ ".java",
90
+ ".js",
91
+ ".json",
92
+ ".jsonl",
93
+ ".jsx",
94
+ ".log",
95
+ ".md",
96
+ ".out",
97
+ ".php",
98
+ ".py",
99
+ ".rb",
100
+ ".rs",
101
+ ".sh",
102
+ ".stderr",
103
+ ".stdout",
104
+ ".ts",
105
+ ".tsx",
106
+ ".tsv",
107
+ ".txt",
108
+ ".xml",
109
+ ".yaml",
110
+ ".yml",
111
+ ]);
112
+ const TEXT_BASENAMES = new Set([".env", "Dockerfile", "Makefile"]);
113
+
114
+ const SECRET_KEY_NAMES =
115
+ "api[_-]?key|apikey|secret(?:[_-]?key)?|secretaccesskey|access[_-]?key(?:[_-]?id)?|accesskeyid|token|access[_-]?token|accesstoken|refresh[_-]?token|refreshtoken|password|passwd|pwd|client[_-]?secret|clientsecret|private[_-]?key|privatekey|session[_-]?id|sessionid|database[_-]?url|db[_-]?url|redis[_-]?url|mongo(?:db)?[_-]?url|mysql[_-]?url|postgres(?:ql)?[_-]?url|dsn";
116
+ const SECRET_VALUE_RE = new RegExp(
117
+ `((?:["']?\\b(?:${SECRET_KEY_NAMES})\\b["']?\\s*[:=]\\s*)(["']?))([^"'\\s,}\\]]{8,})(\\2)`,
118
+ "gi",
119
+ );
120
+ const SECRET_QUERY_RE = new RegExp(`([?&](?:${SECRET_KEY_NAMES}|key)=)([^&#\\s]+)`, "gi");
121
+
122
+ function exists(cwd: string, rel: string): boolean {
123
+ return existsSync(join(cwd, rel));
124
+ }
125
+
126
+ export function ensureConfirmWorkdir(cwd: string): void {
127
+ mkdirSync(join(cwd, WORK), { recursive: true });
128
+ }
129
+
130
+ export function findingDirsWithReports(cwd: string): string[] {
131
+ const root = join(cwd, "piolium", "findings");
132
+ if (!existsSync(root)) return [];
133
+ return readdirSync(root)
134
+ .map((entry) => join(root, entry))
135
+ .filter((path) => {
136
+ try {
137
+ return statSync(path).isDirectory() && existsSync(join(path, "report.md"));
138
+ } catch {
139
+ return false;
140
+ }
141
+ })
142
+ .sort();
143
+ }
144
+
145
+ function pickResume(cwd: string, force: boolean): AuditRunState | undefined {
146
+ if (force) return undefined;
147
+ const state = readAuditState(cwd).state;
148
+ const audit = state ? latestAudit(state) : undefined;
149
+ if (!audit) return undefined;
150
+ if (audit.mode !== "confirm") return undefined;
151
+ if (audit.status === "complete") return undefined;
152
+ return audit;
153
+ }
154
+
155
+ const CONFIRMATION_STANDARD = [
156
+ "Confirmation standard (strict):",
157
+ "- Prefer live exploit execution over assertion-only review.",
158
+ "- A confirmed finding needs observable proof: exact command, request/response, before/after state, or stack trace.",
159
+ "- Write evidence under each finding's `evidence/` directory; include enough detail for replay.",
160
+ "- Do not mark confirmed from code plausibility alone.",
161
+ "- Mark `Confirm-Status: false-positive` only when real execution or a targeted reproducer proves the claimed exploit path is blocked, unreachable, or contradicted by code/runtime behavior.",
162
+ "- If evidence is incomplete, use `blocked`, `inconclusive`, or `unconfirmed` instead of false-positive.",
163
+ ].join("\n");
164
+
165
+ export function buildConfirmTask(phase: string, target: string | undefined): string {
166
+ const targetLine = target
167
+ ? `Target is REMOTE: ${target}. Skip local Docker/VM provisioning; treat the URL as already-running.`
168
+ : "Target is LOCAL — provision via Docker/VM if necessary.";
169
+ switch (phase) {
170
+ case "V1":
171
+ return [
172
+ "You are running V1 (Findings Inventory) of /piolium-confirm.",
173
+ "Read every `report.md` under `piolium/findings/` and treat it as the source of truth.",
174
+ "Extract: id, slug, severity, vulnerability class, title, PoC script path, Protocol, Auth-Required, and existing Confirm-* fields.",
175
+ "Classify each finding as `network-exploitable`, `local-exploitable`, or `non-exploitable`. When unsure, default to network-exploitable so V4 gets a chance.",
176
+ `Write \`${WORK}/findings-inventory.json\` with totals by severity/class and one object per finding.`,
177
+ targetLine,
178
+ CONFIRMATION_STANDARD,
179
+ ].join("\n\n");
180
+ case "V1.5":
181
+ return [
182
+ "You are running V1.5 (Intent Cross-Check) of /piolium-confirm.",
183
+ "Build an intent corpus from repo-local security documentation (SECURITY.md, README security sections, docs/security/, threat-model files, inline `#security:` / `// security:` pragmas).",
184
+ "Produce two lists per the context-reviewer spec: `intentional_behaviors[]` (project-declared safe-by-design behaviors) and `acknowledged_risks[]` (project-acknowledged risks that may still appear as findings).",
185
+ `Write the structured corpus to \`${INTENT_CORPUS}\`.`,
186
+ "Then cross-check every finding in `piolium/findings/`:",
187
+ " - If a finding overlaps an `intentional_behavior`, append `Documented-Intent: matched` to its `draft.md` frontmatter (or `report.md` if no draft).",
188
+ " - If a finding partially overlaps (same class, different code path), append `Documented-Intent: partial`.",
189
+ " - Otherwise, append `Documented-Intent: no-match`.",
190
+ "This phase is annotate-only — NEVER overwrite Severity-Final, never short-circuit later V-phases, and never skip PoC for a `matched` finding. The annotation is a hint for the reporter (V6), not a verdict.",
191
+ "Cold-verifier-style independence is not required here; reading prior verdicts and severity is allowed.",
192
+ targetLine,
193
+ CONFIRMATION_STANDARD,
194
+ ].join("\n\n");
195
+ case "V2":
196
+ return [
197
+ "You are running V2 (Environment Discovery) of /piolium-confirm.",
198
+ "Detect exact startup strategies, framework, ports, required env vars, datastores, migrations, seed data, and test framework.",
199
+ "If auth is present, write `piolium/confirm-workspace/auth-spec.json` describing how to create low-privileged and privileged test identities.",
200
+ `Write \`${WORK}/env-strategies.json\` ranked from most reliable to fallback.`,
201
+ targetLine,
202
+ CONFIRMATION_STANDARD,
203
+ ].join("\n\n");
204
+ case "V3":
205
+ return [
206
+ "You are running V3 (Environment Provisioning) of /piolium-confirm.",
207
+ "Stand up the target if local using env-strategies.json. Prefer the repo's own docker-compose, Makefile, package scripts, or test server.",
208
+ "Seed test identities from auth-spec.json if present and write usable credentials/tokens to env-connection.json.",
209
+ "Write `piolium/confirm-workspace/env-connection.json` with `{status, base_url, test_identities?, cleanup_cmd, session}`.",
210
+ "On failure, write `piolium/confirm-workspace/healthcheck-failure.log` with the last relevant app/container logs and exit cleanly.",
211
+ targetLine,
212
+ CONFIRMATION_STANDARD,
213
+ ].join("\n\n");
214
+ case "V4":
215
+ return [
216
+ "You are running V4 (PoC Execution) of /piolium-confirm.",
217
+ "Read findings-inventory.json and env-connection.json. Skip non-exploitable findings as `Confirm-Status: analytical-only`; route local-only findings to V5.",
218
+ "Before per-finding execution, run one reachability check against base_url with a 5s timeout; if unreachable, mark queued network findings `blocked` and record the reason.",
219
+ "For every network-exploitable finding with a PoC, execute the real PoC against the target. Use a 30s timeout per variant, max 2 variants.",
220
+ "Capture exact command, relevant env, HTTP request/response or stdout/stderr, and observable before/after state to `<finding-dir>/evidence/confirmed-<timestamp>.log`.",
221
+ "Parse structured PoC output if present: final JSON line `{status,evidence,notes}`.",
222
+ "Update each `report.md` with `Confirm-Status: confirmed-live | failed | blocked | analytical-only | false-positive` and `Confirm-Evidence:` pointing at the evidence file.",
223
+ `Write aggregate results to \`${POC_RESULTS}\`.`,
224
+ CONFIRMATION_STANDARD,
225
+ ].join("\n\n");
226
+ case "V5":
227
+ return [
228
+ "You are running V5 (Test Mapper) of /piolium-confirm.",
229
+ "For findings whose live PoC did not confirm, had no PoC, or are local-exploitable, generate the smallest reproducer test in the existing test framework.",
230
+ "Actually run the test with a 60s cap (pytest timeout, jest --testTimeout, go test -timeout, etc.).",
231
+ "Keep reproducer files/evidence under each finding dir and write command/output logs under `evidence/`.",
232
+ "Update `report.md`: `Confirm-Status: confirmed-test | failed | blocked | false-positive` and `Confirm-Evidence:`.",
233
+ "Only mark `false-positive` when the reproducer proves the claimed vulnerable path is unreachable, patched, protected, or based on an invalid assumption.",
234
+ `Write \`${WORK}/test-mapping.json\` with per-finding verdicts and evidence pointers.`,
235
+ CONFIRMATION_STANDARD,
236
+ ].join("\n\n");
237
+ case "V6":
238
+ return [
239
+ "You are running V6 (Confirmation Report) of /piolium-confirm.",
240
+ "Read `piolium/findings/`, including any directories renamed with `FP-` after V5.",
241
+ `Compose \`${REPORT}\` with: confirmed-live, confirmed-test, analytical-only, blocked, inconclusive/unconfirmed, and false-positive counts.`,
242
+ "Include one line per finding with status, evidence pointer, and reproduction command summary.",
243
+ "Create a dedicated false-positive section listing every `FP-*` directory and the evidence that disproved it.",
244
+ "Include environment setup notes, target URL/base_url, cleanup result, and methodology.",
245
+ ].join("\n\n");
246
+ default:
247
+ return "Unknown V phase.";
248
+ }
249
+ }
250
+
251
+ export function confirmGateFor(phase: string, cwd: string): () => boolean {
252
+ switch (phase) {
253
+ case "V1":
254
+ return () => exists(cwd, `${WORK}/findings-inventory.json`);
255
+ case "V1.5":
256
+ return () => exists(cwd, INTENT_CORPUS);
257
+ case "V2":
258
+ return () => exists(cwd, `${WORK}/env-strategies.json`);
259
+ case "V3":
260
+ return () =>
261
+ exists(cwd, `${WORK}/env-connection.json`) || exists(cwd, `${WORK}/healthcheck-failure.log`);
262
+ case "V4":
263
+ return () => exists(cwd, POC_RESULTS);
264
+ case "V5":
265
+ return () => exists(cwd, `${WORK}/test-mapping.json`);
266
+ case "V6":
267
+ return () => exists(cwd, REPORT);
268
+ default:
269
+ return () => true;
270
+ }
271
+ }
272
+
273
+ export function writeRemoteConnection(cwd: string, target: string): void {
274
+ ensureConfirmWorkdir(cwd);
275
+ writeFileSync(
276
+ join(cwd, `${WORK}/env-connection.json`),
277
+ `${JSON.stringify(
278
+ {
279
+ status: "remote",
280
+ base_url: target,
281
+ method_used: "remote-target",
282
+ healthcheck_passed: null,
283
+ cleanup_cmd: null,
284
+ },
285
+ null,
286
+ "\t",
287
+ )}\n`,
288
+ );
289
+ }
290
+
291
+ function reportMarksFalsePositive(text: string): boolean {
292
+ return (
293
+ /^(?:Confirm-Status|Confirmation|Confirm-Verdict|Verdict)\s*:\s*(?:false[-_ ]positive|fp)\b/im.test(
294
+ text,
295
+ ) || /"confirm_status"\s*:\s*"false[-_ ]positive"/i.test(text)
296
+ );
297
+ }
298
+
299
+ function uniqueDest(root: string, name: string): string {
300
+ let candidate = join(root, name);
301
+ let suffix = 2;
302
+ while (existsSync(candidate)) {
303
+ candidate = join(root, `${name}-${suffix}`);
304
+ suffix++;
305
+ }
306
+ return candidate;
307
+ }
308
+
309
+ export function renameFalsePositiveFindings(cwd: string): string[] {
310
+ const root = join(cwd, "piolium", "findings");
311
+ if (!existsSync(root)) return [];
312
+ const renames: string[] = [];
313
+ for (const entry of readdirSync(root).sort()) {
314
+ if (entry.startsWith("FP-")) continue;
315
+ const dir = join(root, entry);
316
+ try {
317
+ if (!statSync(dir).isDirectory()) continue;
318
+ const reportPath = join(dir, "report.md");
319
+ if (!existsSync(reportPath)) continue;
320
+ if (!reportMarksFalsePositive(readFileSync(reportPath, "utf8"))) continue;
321
+ const destName = `FP-${entry}`;
322
+ const dest = uniqueDest(root, destName);
323
+ renameSync(dir, dest);
324
+ renames.push(`${entry} -> ${basename(dest)}`);
325
+ } catch {
326
+ // Keep confirmation moving; V6 will still report available evidence.
327
+ }
328
+ }
329
+ ensureConfirmWorkdir(cwd);
330
+ writeFileSync(
331
+ join(cwd, FP_RENAMES),
332
+ `${JSON.stringify({ renamed_at: new Date().toISOString(), renames }, null, "\t")}\n`,
333
+ );
334
+ return renames;
335
+ }
336
+
337
+ export interface ConfirmCleanupResult {
338
+ summaryPath: string;
339
+ checkedFindingDirs: string[];
340
+ createdEvidenceDirs: string[];
341
+ formatIssues: string[];
342
+ falsePositiveRenames: string[];
343
+ redactedFiles: Array<{ path: string; replacements: Record<string, number> }>;
344
+ skippedFiles: Array<{ path: string; reason: string }>;
345
+ }
346
+
347
+ function increment(counts: Record<string, number>, key: string, by = 1): void {
348
+ counts[key] = (counts[key] ?? 0) + by;
349
+ }
350
+
351
+ function replaceStatic(
352
+ text: string,
353
+ counts: Record<string, number>,
354
+ key: string,
355
+ pattern: RegExp,
356
+ replacement: string,
357
+ ): string {
358
+ pattern.lastIndex = 0;
359
+ const matches = text.match(pattern);
360
+ if (!matches || matches.length === 0) return text;
361
+ increment(counts, key, matches.length);
362
+ pattern.lastIndex = 0;
363
+ return text.replace(pattern, replacement);
364
+ }
365
+
366
+ export function redactSecrets(text: string): {
367
+ text: string;
368
+ replacements: Record<string, number>;
369
+ } {
370
+ const replacements: Record<string, number> = {};
371
+ let out = text;
372
+
373
+ out = replaceStatic(
374
+ out,
375
+ replacements,
376
+ "private-key",
377
+ /-----BEGIN (?:RSA |DSA |EC |OPENSSH |PGP )?PRIVATE KEY-----[\s\S]*?-----END (?:RSA |DSA |EC |OPENSSH |PGP )?PRIVATE KEY-----/g,
378
+ "[REDACTED:private-key]",
379
+ );
380
+ out = replaceStatic(
381
+ out,
382
+ replacements,
383
+ "aws-access-key",
384
+ /\b(?:AKIA|ASIA)[0-9A-Z]{16}\b/g,
385
+ "[REDACTED:aws-access-key]",
386
+ );
387
+ out = replaceStatic(
388
+ out,
389
+ replacements,
390
+ "github-token",
391
+ /\b(?:(?:ghp|gho|ghu|ghs|ghr)_[A-Za-z0-9_]{20,255}|github_pat_[A-Za-z0-9_]{20,255})\b/g,
392
+ "[REDACTED:github-token]",
393
+ );
394
+ out = replaceStatic(
395
+ out,
396
+ replacements,
397
+ "openai-token",
398
+ /\bsk-(?:proj-)?[A-Za-z0-9_-]{20,}\b/g,
399
+ "[REDACTED:openai-token]",
400
+ );
401
+ out = replaceStatic(
402
+ out,
403
+ replacements,
404
+ "slack-token",
405
+ /\bxox[baprs]-[A-Za-z0-9-]{10,}\b/g,
406
+ "[REDACTED:slack-token]",
407
+ );
408
+ out = replaceStatic(
409
+ out,
410
+ replacements,
411
+ "jwt",
412
+ /\beyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\b/g,
413
+ "[REDACTED:jwt]",
414
+ );
415
+
416
+ out = out.replace(
417
+ /(["']?Authorization["']?\s*:\s*["']?Bearer\s+)([^"'\s,}\]]+)/gi,
418
+ (_match: string, prefix: string) => {
419
+ increment(replacements, "authorization-bearer");
420
+ return `${prefix}[REDACTED:bearer]`;
421
+ },
422
+ );
423
+ out = out.replace(
424
+ /(Authorization:\s*Bearer\s+)([^\s`"']+)/gi,
425
+ (_match: string, prefix: string) => {
426
+ increment(replacements, "authorization-bearer");
427
+ return `${prefix}[REDACTED:bearer]`;
428
+ },
429
+ );
430
+ out = out.replace(
431
+ /(Authorization:\s*Basic\s+)([A-Za-z0-9+/=]+)/gi,
432
+ (_match: string, prefix: string) => {
433
+ increment(replacements, "authorization-basic");
434
+ return `${prefix}[REDACTED:basic]`;
435
+ },
436
+ );
437
+ out = out.replace(/((?:Cookie|Set-Cookie):\s*)[^\r\n]+/gi, (_match: string, prefix: string) => {
438
+ increment(replacements, "cookie");
439
+ return `${prefix}[REDACTED:cookie]`;
440
+ });
441
+ out = out.replace(/(\bhttps?:\/\/)([^/\s:@]+):([^/\s@]+)@/gi, (_match: string, prefix: string) => {
442
+ increment(replacements, "url-userinfo");
443
+ return `${prefix}[REDACTED:userinfo]@`;
444
+ });
445
+ out = out.replace(SECRET_QUERY_RE, (_match: string, prefix: string) => {
446
+ increment(replacements, "secret-query-param");
447
+ return `${prefix}[REDACTED:secret]`;
448
+ });
449
+ out = out.replace(
450
+ SECRET_VALUE_RE,
451
+ (_match: string, prefix: string, _quote: string, _value: string, suffix: string) => {
452
+ increment(replacements, "secret-key-value");
453
+ return `${prefix}[REDACTED:secret]${suffix}`;
454
+ },
455
+ );
456
+
457
+ return { text: out, replacements };
458
+ }
459
+
460
+ function isTextCandidate(path: string): boolean {
461
+ const name = basename(path);
462
+ return TEXT_BASENAMES.has(name) || TEXT_EXTENSIONS.has(extname(name).toLowerCase());
463
+ }
464
+
465
+ function collectArtifactFiles(
466
+ cwd: string,
467
+ rel: string,
468
+ out: string[],
469
+ skipped: ConfirmCleanupResult["skippedFiles"],
470
+ ): void {
471
+ const abs = join(cwd, rel);
472
+ if (!existsSync(abs)) return;
473
+ const stat = statSync(abs);
474
+ if (stat.isDirectory()) {
475
+ for (const entry of readdirSync(abs).sort())
476
+ collectArtifactFiles(cwd, `${rel}/${entry}`, out, skipped);
477
+ return;
478
+ }
479
+ if (!stat.isFile()) return;
480
+ if (stat.size > MAX_REDACTABLE_BYTES) {
481
+ skipped.push({ path: rel, reason: `larger than ${MAX_REDACTABLE_BYTES} bytes` });
482
+ return;
483
+ }
484
+ if (!isTextCandidate(abs)) {
485
+ skipped.push({ path: rel, reason: "not a known text artifact extension" });
486
+ return;
487
+ }
488
+ out.push(rel);
489
+ }
490
+
491
+ function redactArtifactFile(
492
+ cwd: string,
493
+ rel: string,
494
+ skipped: ConfirmCleanupResult["skippedFiles"],
495
+ ): ConfirmCleanupResult["redactedFiles"][number] | undefined {
496
+ const abs = join(cwd, rel);
497
+ const raw = readFileSync(abs);
498
+ if (raw.includes(0)) {
499
+ skipped.push({ path: rel, reason: "appears to be binary" });
500
+ return undefined;
501
+ }
502
+ const original = raw.toString("utf8");
503
+ const redacted = redactSecrets(original);
504
+ if (redacted.text === original) return undefined;
505
+ writeFileSync(abs, redacted.text);
506
+ return { path: rel, replacements: redacted.replacements };
507
+ }
508
+
509
+ function normalizeFindingLayout(
510
+ cwd: string,
511
+ ): Pick<ConfirmCleanupResult, "checkedFindingDirs" | "createdEvidenceDirs" | "formatIssues"> {
512
+ const root = join(cwd, "piolium", "findings");
513
+ const checkedFindingDirs: string[] = [];
514
+ const createdEvidenceDirs: string[] = [];
515
+ const formatIssues: string[] = [];
516
+ if (!existsSync(root)) {
517
+ formatIssues.push("missing piolium/findings/");
518
+ return { checkedFindingDirs, createdEvidenceDirs, formatIssues };
519
+ }
520
+ for (const entry of readdirSync(root).sort()) {
521
+ const dir = join(root, entry);
522
+ if (!statSync(dir).isDirectory()) {
523
+ formatIssues.push(`non-directory entry under piolium/findings/: ${entry}`);
524
+ continue;
525
+ }
526
+ checkedFindingDirs.push(entry);
527
+ if (!/^(?:FP-)?[A-Za-z0-9]+(?:-\d+)?-[A-Za-z0-9][A-Za-z0-9._-]*$/.test(entry)) {
528
+ formatIssues.push(`finding directory name is non-standard: ${entry}`);
529
+ }
530
+ const report = join(dir, "report.md");
531
+ if (!existsSync(report)) {
532
+ formatIssues.push(`missing report.md: piolium/findings/${entry}/report.md`);
533
+ } else if (statSync(report).size === 0) {
534
+ formatIssues.push(`empty report.md: piolium/findings/${entry}/report.md`);
535
+ }
536
+ const evidence = join(dir, "evidence");
537
+ if (!existsSync(evidence)) {
538
+ mkdirSync(evidence, { recursive: true });
539
+ createdEvidenceDirs.push(`piolium/findings/${entry}/evidence`);
540
+ } else if (!statSync(evidence).isDirectory()) {
541
+ formatIssues.push(`evidence path is not a directory: piolium/findings/${entry}/evidence`);
542
+ }
543
+ }
544
+ return { checkedFindingDirs, createdEvidenceDirs, formatIssues };
545
+ }
546
+
547
+ export function cleanupConfirmArtifacts(cwd: string): ConfirmCleanupResult {
548
+ ensureConfirmWorkdir(cwd);
549
+ const falsePositiveRenames = renameFalsePositiveFindings(cwd);
550
+ const layout = normalizeFindingLayout(cwd);
551
+ const skippedFiles: ConfirmCleanupResult["skippedFiles"] = [];
552
+ const candidates: string[] = [];
553
+ for (const rel of ["piolium/findings", REPORT, WORK]) {
554
+ collectArtifactFiles(cwd, rel, candidates, skippedFiles);
555
+ }
556
+ const redactedFiles = candidates
557
+ .map((rel) => redactArtifactFile(cwd, rel, skippedFiles))
558
+ .filter((item): item is ConfirmCleanupResult["redactedFiles"][number] => item !== undefined);
559
+
560
+ const result: ConfirmCleanupResult = {
561
+ summaryPath: CLEANUP_SUMMARY,
562
+ ...layout,
563
+ falsePositiveRenames,
564
+ redactedFiles,
565
+ skippedFiles,
566
+ };
567
+ writeFileSync(join(cwd, CLEANUP_SUMMARY), `${JSON.stringify(result, null, "\t")}\n`);
568
+ return result;
569
+ }
570
+
571
+ export async function runConfirmAudit(opts: RunConfirmOptions): Promise<RunConfirmResult> {
572
+ const { cwd, signal, ui } = opts;
573
+ if (findingDirsWithReports(cwd).length === 0) {
574
+ throw new Error("No findings to confirm. Expected `piolium/findings/*/report.md`.");
575
+ }
576
+ ensureConfirmWorkdir(cwd);
577
+
578
+ ui?.setStatus?.("piolium-confirm", "● preparing recon");
579
+ const recon = await runReconAsync(cwd, { signal });
580
+ let audit = pickResume(cwd, opts.forceFresh ?? false);
581
+ if (!audit) {
582
+ audit = await initAudit(cwd, {
583
+ mode: "confirm",
584
+ ...(recon.commit ? { commit: recon.commit } : { commit: null }),
585
+ ...(recon.branch ? { branch: recon.branch } : { branch: "nogit" }),
586
+ ...(recon.repository ? { repository: recon.repository } : {}),
587
+ history_available: recon.historyAvailable,
588
+ agent_sdk: "pi",
589
+ });
590
+ }
591
+
592
+ const { agents } = loadAgents({ cwd });
593
+ const phaseAgents: Record<(typeof CONFIRM_AGENT_PHASES)[number], ReturnType<typeof agents.get>> = {
594
+ V1: agents.get("env-profiler"),
595
+ "V1.5": agents.get("context-reviewer"),
596
+ V2: agents.get("env-profiler"),
597
+ V3: agents.get("env-builder"),
598
+ V4: agents.get("poc-runner"),
599
+ V5: agents.get("test-locator"),
600
+ V6: agents.get("confirm-writer"),
601
+ };
602
+
603
+ let failed = false;
604
+
605
+ for (const name of CONFIRM_AGENT_PHASES) {
606
+ if (opts.target && (name === "V2" || name === "V3")) {
607
+ writeRemoteConnection(cwd, opts.target);
608
+ await applyPhaseStatus(cwd, audit, name, {
609
+ status: "skipped",
610
+ error: "Remote target supplied; local environment discovery/provisioning skipped.",
611
+ });
612
+ continue;
613
+ }
614
+ if (opts.target && name === "V5") {
615
+ await applyPhaseStatus(cwd, audit, name, {
616
+ status: "skipped",
617
+ error: "Remote target supplied; local test fallback skipped.",
618
+ });
619
+ continue;
620
+ }
621
+ if (name === "V6") {
622
+ const renames = renameFalsePositiveFindings(cwd);
623
+ if (renames.length > 0) {
624
+ ui?.notify?.(
625
+ `Renamed ${renames.length} false-positive finding folder(s) with FP- prefix.`,
626
+ "warning",
627
+ );
628
+ }
629
+ }
630
+ try {
631
+ await runAgentPhase({
632
+ cwd,
633
+ audit,
634
+ phaseName: name,
635
+ statusKey: "piolium-confirm",
636
+ statusLabel: `● ${name}`,
637
+ agent: phaseAgents[name],
638
+ missingAgentMessage: `agent missing for ${name}`,
639
+ task: buildConfirmTask(name, opts.target),
640
+ gate: confirmGateFor(name, cwd),
641
+ mode: "confirm",
642
+ ui,
643
+ agentRuntime: opts.agentRuntime,
644
+ ...(signal ? { signal } : {}),
645
+ });
646
+ } catch {
647
+ if (name === "V1.5") {
648
+ // Intent Cross-Check is annotate-only and best-effort: it never
649
+ // gates V4/V5 and never fails the confirmation run.
650
+ await applyPhaseStatus(cwd, audit, name, {
651
+ status: "failed",
652
+ error: "policy: skip-and-continue",
653
+ });
654
+ ui?.notify?.("V1.5 Intent Cross-Check skipped (skip-and-continue).", "warning");
655
+ continue;
656
+ }
657
+ failed = true;
658
+ if (name === "V1" || name === "V6") break;
659
+ ui?.notify?.(
660
+ `Confirm phase ${name} failed; continuing to collect/report remaining evidence.`,
661
+ "warning",
662
+ );
663
+ }
664
+ }
665
+
666
+ // Cleanup/redaction is folded into a post-V6 pass (upstream EXIT trap),
667
+ // not a tracked phase. Always run it, regardless of upstream failures.
668
+ try {
669
+ const cleanup = cleanupConfirmArtifacts(cwd);
670
+ if (cleanup.formatIssues.length > 0) {
671
+ ui?.notify?.(
672
+ `Cleanup completed with ${cleanup.formatIssues.length} final-folder format warning(s).`,
673
+ "warning",
674
+ );
675
+ }
676
+ } catch (err) {
677
+ ui?.notify?.(
678
+ `Post-confirm cleanup failed: ${err instanceof Error ? err.message : String(err)}`,
679
+ "warning",
680
+ );
681
+ }
682
+
683
+ await markAuditStatus(cwd, audit.audit_id, failed ? "failed" : "complete");
684
+ const fresh =
685
+ readAuditState(cwd).state?.audits.find((a) => a.audit_id === audit.audit_id) ?? audit;
686
+ const phases: Record<string, "complete" | "failed" | "skipped"> = {};
687
+ for (const [name, p] of Object.entries(fresh.phases)) {
688
+ if (p.status === "complete" || p.status === "failed" || p.status === "skipped") {
689
+ phases[name] = p.status;
690
+ }
691
+ }
692
+ ui?.notify?.(
693
+ failed ? "Confirm pass failed." : "Confirm pass complete.",
694
+ failed ? "error" : "info",
695
+ );
696
+ return { auditId: audit.audit_id, status: failed ? "failed" : "complete", phases };
697
+ }