@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,409 @@
1
+ /**
2
+ * Deterministic pre-audit reconnaissance (Q0 / first phase of every mode).
3
+ *
4
+ * Runs entirely in the orchestrator process — no model calls. Produces a
5
+ * compact Markdown report describing the target repository so later phases
6
+ * (and human reviewers) have stable ground truth.
7
+ *
8
+ * Designed to be resilient on:
9
+ * - directories without `.git`
10
+ * - directories without typical build manifests
11
+ * - very large repos (caps tree depth + entry count)
12
+ * - missing `git` binary
13
+ */
14
+
15
+ import { execFileSync } from "node:child_process";
16
+ import { existsSync, mkdirSync, readdirSync, statSync, writeFileSync } from "node:fs";
17
+ import { dirname, join, relative } from "node:path";
18
+ import { yieldToEventLoop } from "./retry.ts";
19
+
20
+ export const RECON_REPORT_PATH = "piolium/attack-surface/lite-recon.md";
21
+
22
+ export interface ReconResult {
23
+ cwd: string;
24
+ reportPath: string;
25
+ hasGit: boolean;
26
+ commit?: string;
27
+ branch?: string;
28
+ repository?: string;
29
+ historyAvailable: boolean;
30
+ languages: Record<string, number>;
31
+ manifests: string[];
32
+ totalFiles: number;
33
+ totalBytes: number;
34
+ }
35
+
36
+ export interface ReconOptions {
37
+ signal?: AbortSignal;
38
+ yieldEveryEntries?: number;
39
+ }
40
+
41
+ const MANIFEST_FILES = [
42
+ "package.json",
43
+ "go.mod",
44
+ "Cargo.toml",
45
+ "pyproject.toml",
46
+ "requirements.txt",
47
+ "Pipfile",
48
+ "Gemfile",
49
+ "composer.json",
50
+ "pom.xml",
51
+ "build.gradle",
52
+ "build.gradle.kts",
53
+ "setup.py",
54
+ "setup.cfg",
55
+ "Dockerfile",
56
+ "docker-compose.yml",
57
+ "docker-compose.yaml",
58
+ ];
59
+
60
+ const LANGUAGE_BY_EXT: Record<string, string> = {
61
+ ".ts": "TypeScript",
62
+ ".tsx": "TypeScript",
63
+ ".js": "JavaScript",
64
+ ".jsx": "JavaScript",
65
+ ".py": "Python",
66
+ ".go": "Go",
67
+ ".rs": "Rust",
68
+ ".rb": "Ruby",
69
+ ".java": "Java",
70
+ ".kt": "Kotlin",
71
+ ".swift": "Swift",
72
+ ".c": "C",
73
+ ".h": "C",
74
+ ".cpp": "C++",
75
+ ".cc": "C++",
76
+ ".hpp": "C++",
77
+ ".cs": "C#",
78
+ ".php": "PHP",
79
+ ".scala": "Scala",
80
+ ".clj": "Clojure",
81
+ ".sh": "Shell",
82
+ ".bash": "Shell",
83
+ ".zsh": "Shell",
84
+ ".sql": "SQL",
85
+ ".lua": "Lua",
86
+ ".m": "Objective-C",
87
+ ".mm": "Objective-C",
88
+ };
89
+
90
+ const SKIP_DIRS = new Set([
91
+ "node_modules",
92
+ ".git",
93
+ "vendor",
94
+ "dist",
95
+ "build",
96
+ "target",
97
+ "out",
98
+ ".next",
99
+ ".nuxt",
100
+ ".cache",
101
+ ".venv",
102
+ "venv",
103
+ "__pycache__",
104
+ ".pytest_cache",
105
+ ".mypy_cache",
106
+ ".idea",
107
+ ".vscode",
108
+ "piolium",
109
+ ]);
110
+
111
+ /** Soft caps so we never wedge on a giant repo. */
112
+ const MAX_FILES_TO_SCAN = 50_000;
113
+ const MAX_BYTES_TO_TALLY = 500 * 1024 * 1024; // 500MB
114
+ const DEFAULT_ASYNC_YIELD_EVERY_ENTRIES = 100;
115
+
116
+ function safeExec(file: string, args: string[], cwd: string): string | undefined {
117
+ try {
118
+ return execFileSync(file, args, {
119
+ cwd,
120
+ encoding: "utf8",
121
+ stdio: ["ignore", "pipe", "ignore"],
122
+ }).trim();
123
+ } catch {
124
+ return undefined;
125
+ }
126
+ }
127
+
128
+ function detectGit(cwd: string): {
129
+ hasGit: boolean;
130
+ commit?: string;
131
+ branch?: string;
132
+ historyAvailable: boolean;
133
+ remote?: string;
134
+ headLog?: string;
135
+ } {
136
+ if (!existsSync(join(cwd, ".git"))) {
137
+ return { hasGit: false, historyAvailable: false };
138
+ }
139
+ const commit = safeExec("git", ["rev-parse", "HEAD"], cwd);
140
+ const branch = safeExec("git", ["rev-parse", "--abbrev-ref", "HEAD"], cwd);
141
+ const remote = safeExec("git", ["remote", "get-url", "origin"], cwd);
142
+ const headLog = safeExec("git", ["log", "-n", "10", "--oneline", "--no-decorate"], cwd);
143
+ return {
144
+ hasGit: true,
145
+ historyAvailable: Boolean(commit),
146
+ ...(commit ? { commit } : {}),
147
+ ...(branch ? { branch } : {}),
148
+ ...(remote ? { remote } : {}),
149
+ ...(headLog ? { headLog } : {}),
150
+ };
151
+ }
152
+
153
+ function inferRepository(remote: string | undefined): string | undefined {
154
+ if (!remote) return undefined;
155
+ // Strip auth + protocol
156
+ const m = remote.match(
157
+ /(?:github\.com[:/]|gitlab\.com[:/]|bitbucket\.org[:/])([^/]+\/[^/.]+)(?:\.git)?$/,
158
+ );
159
+ if (m?.[1]) return m[1];
160
+ return undefined;
161
+ }
162
+
163
+ function walkAndTally(cwd: string): {
164
+ languages: Record<string, number>;
165
+ manifests: string[];
166
+ totalFiles: number;
167
+ totalBytes: number;
168
+ } {
169
+ const languages: Record<string, number> = {};
170
+ const manifests: string[] = [];
171
+ let totalFiles = 0;
172
+ let totalBytes = 0;
173
+
174
+ function walk(dir: string): void {
175
+ if (totalFiles >= MAX_FILES_TO_SCAN) return;
176
+ let entries: string[];
177
+ try {
178
+ entries = readdirSync(dir);
179
+ } catch {
180
+ return;
181
+ }
182
+ for (const entry of entries) {
183
+ if (totalFiles >= MAX_FILES_TO_SCAN) return;
184
+ if (SKIP_DIRS.has(entry) || entry.startsWith(".")) continue;
185
+ const full = join(dir, entry);
186
+ let st: ReturnType<typeof statSync>;
187
+ try {
188
+ st = statSync(full);
189
+ } catch {
190
+ continue;
191
+ }
192
+ if (st.isDirectory()) {
193
+ walk(full);
194
+ continue;
195
+ }
196
+ if (!st.isFile()) continue;
197
+ totalFiles++;
198
+ if (totalBytes < MAX_BYTES_TO_TALLY) totalBytes += st.size;
199
+ const lower = entry.toLowerCase();
200
+ if (MANIFEST_FILES.includes(entry)) {
201
+ manifests.push(relative(cwd, full));
202
+ } else if (lower.startsWith("dockerfile")) {
203
+ manifests.push(relative(cwd, full));
204
+ }
205
+ const dotIdx = entry.lastIndexOf(".");
206
+ if (dotIdx > 0) {
207
+ const ext = entry.slice(dotIdx).toLowerCase();
208
+ const lang = LANGUAGE_BY_EXT[ext];
209
+ if (lang) languages[lang] = (languages[lang] ?? 0) + 1;
210
+ }
211
+ }
212
+ }
213
+
214
+ walk(cwd);
215
+ return { languages, manifests, totalFiles, totalBytes };
216
+ }
217
+
218
+ async function walkAndTallyAsync(
219
+ cwd: string,
220
+ options: ReconOptions = {},
221
+ ): Promise<{
222
+ languages: Record<string, number>;
223
+ manifests: string[];
224
+ totalFiles: number;
225
+ totalBytes: number;
226
+ }> {
227
+ const languages: Record<string, number> = {};
228
+ const manifests: string[] = [];
229
+ let totalFiles = 0;
230
+ let totalBytes = 0;
231
+ let visitedEntries = 0;
232
+ const yieldEveryEntries = Math.max(
233
+ 1,
234
+ options.yieldEveryEntries ?? DEFAULT_ASYNC_YIELD_EVERY_ENTRIES,
235
+ );
236
+
237
+ async function maybeYield(): Promise<void> {
238
+ visitedEntries++;
239
+ if (visitedEntries % yieldEveryEntries === 0) {
240
+ await yieldToEventLoop(options.signal);
241
+ }
242
+ }
243
+
244
+ async function walk(dir: string): Promise<void> {
245
+ if (options.signal?.aborted) throw options.signal.reason ?? new Error("Aborted");
246
+ if (totalFiles >= MAX_FILES_TO_SCAN) return;
247
+ let entries: string[];
248
+ try {
249
+ entries = readdirSync(dir);
250
+ } catch {
251
+ return;
252
+ }
253
+ for (const entry of entries) {
254
+ await maybeYield();
255
+ if (options.signal?.aborted) throw options.signal.reason ?? new Error("Aborted");
256
+ if (totalFiles >= MAX_FILES_TO_SCAN) return;
257
+ if (SKIP_DIRS.has(entry) || entry.startsWith(".")) continue;
258
+ const full = join(dir, entry);
259
+ let st: ReturnType<typeof statSync>;
260
+ try {
261
+ st = statSync(full);
262
+ } catch {
263
+ continue;
264
+ }
265
+ if (st.isDirectory()) {
266
+ await walk(full);
267
+ continue;
268
+ }
269
+ if (!st.isFile()) continue;
270
+ totalFiles++;
271
+ if (totalBytes < MAX_BYTES_TO_TALLY) totalBytes += st.size;
272
+ const lower = entry.toLowerCase();
273
+ if (MANIFEST_FILES.includes(entry)) {
274
+ manifests.push(relative(cwd, full));
275
+ } else if (lower.startsWith("dockerfile")) {
276
+ manifests.push(relative(cwd, full));
277
+ }
278
+ const dotIdx = entry.lastIndexOf(".");
279
+ if (dotIdx > 0) {
280
+ const ext = entry.slice(dotIdx).toLowerCase();
281
+ const lang = LANGUAGE_BY_EXT[ext];
282
+ if (lang) languages[lang] = (languages[lang] ?? 0) + 1;
283
+ }
284
+ }
285
+ }
286
+
287
+ await walk(cwd);
288
+ return { languages, manifests, totalFiles, totalBytes };
289
+ }
290
+
291
+ function formatBytes(bytes: number): string {
292
+ if (bytes < 1024) return `${bytes} B`;
293
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
294
+ if (bytes < 1024 * 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
295
+ return `${(bytes / (1024 * 1024 * 1024)).toFixed(2)} GB`;
296
+ }
297
+
298
+ export function buildReconReport(cwd: string, result: ReconResult, headLog?: string): string {
299
+ const lines: string[] = [];
300
+ lines.push("# Lite Recon — Q0");
301
+ lines.push("");
302
+ lines.push(`Generated by piolium at ${new Date().toISOString()}`);
303
+ lines.push("");
304
+ lines.push("## Target");
305
+ lines.push("");
306
+ lines.push(`- Path: \`${cwd}\``);
307
+ lines.push(`- Repository: ${result.repository ?? "(unknown)"}`);
308
+ lines.push(
309
+ `- Total files (scanned): ${result.totalFiles}${result.totalFiles >= MAX_FILES_TO_SCAN ? " (capped)" : ""}`,
310
+ );
311
+ lines.push(`- Total bytes (scanned): ${formatBytes(result.totalBytes)}`);
312
+ lines.push("");
313
+ lines.push("## Git");
314
+ lines.push("");
315
+ if (result.hasGit) {
316
+ lines.push(`- Commit: ${result.commit ?? "(unknown)"}`);
317
+ lines.push(`- Branch: ${result.branch ?? "(unknown)"}`);
318
+ lines.push(`- History available: ${result.historyAvailable}`);
319
+ if (headLog) {
320
+ lines.push("");
321
+ lines.push("Recent commits:");
322
+ lines.push("");
323
+ lines.push("```");
324
+ lines.push(headLog);
325
+ lines.push("```");
326
+ }
327
+ } else {
328
+ lines.push("- Not a git repository (no `.git/`).");
329
+ lines.push(
330
+ "- Phases that depend on git history (commit archaeology, patch-bypass) will be skipped.",
331
+ );
332
+ }
333
+ lines.push("");
334
+ lines.push("## Languages");
335
+ lines.push("");
336
+ const langEntries = Object.entries(result.languages).sort((a, b) => b[1] - a[1]);
337
+ if (langEntries.length === 0) {
338
+ lines.push("(none recognized)");
339
+ } else {
340
+ for (const [lang, count] of langEntries) lines.push(`- ${lang}: ${count} file(s)`);
341
+ }
342
+ lines.push("");
343
+ lines.push("## Build / Project Manifests");
344
+ lines.push("");
345
+ if (result.manifests.length === 0) {
346
+ lines.push("(none recognized)");
347
+ } else {
348
+ for (const m of result.manifests) lines.push(`- \`${m}\``);
349
+ }
350
+ lines.push("");
351
+ return lines.join("\n");
352
+ }
353
+
354
+ export function runRecon(cwd: string): ReconResult {
355
+ const git = detectGit(cwd);
356
+ const repository = inferRepository(git.remote);
357
+ const tally = walkAndTally(cwd);
358
+
359
+ const reportPath = join(cwd, RECON_REPORT_PATH);
360
+ mkdirSync(dirname(reportPath), { recursive: true });
361
+
362
+ const result: ReconResult = {
363
+ cwd,
364
+ reportPath,
365
+ hasGit: git.hasGit,
366
+ ...(git.commit ? { commit: git.commit } : {}),
367
+ ...(git.branch ? { branch: git.branch } : {}),
368
+ ...(repository ? { repository } : {}),
369
+ historyAvailable: git.historyAvailable,
370
+ languages: tally.languages,
371
+ manifests: tally.manifests,
372
+ totalFiles: tally.totalFiles,
373
+ totalBytes: tally.totalBytes,
374
+ };
375
+
376
+ writeFileSync(reportPath, buildReconReport(cwd, result, git.headLog));
377
+ return result;
378
+ }
379
+
380
+ export async function runReconAsync(cwd: string, options: ReconOptions = {}): Promise<ReconResult> {
381
+ const git = detectGit(cwd);
382
+ await yieldToEventLoop(options.signal);
383
+ const repository = inferRepository(git.remote);
384
+ const tally = await walkAndTallyAsync(cwd, options);
385
+
386
+ const reportPath = join(cwd, RECON_REPORT_PATH);
387
+ mkdirSync(dirname(reportPath), { recursive: true });
388
+
389
+ const result: ReconResult = {
390
+ cwd,
391
+ reportPath,
392
+ hasGit: git.hasGit,
393
+ ...(git.commit ? { commit: git.commit } : {}),
394
+ ...(git.branch ? { branch: git.branch } : {}),
395
+ ...(repository ? { repository } : {}),
396
+ historyAvailable: git.historyAvailable,
397
+ languages: tally.languages,
398
+ manifests: tally.manifests,
399
+ totalFiles: tally.totalFiles,
400
+ totalBytes: tally.totalBytes,
401
+ };
402
+
403
+ writeFileSync(reportPath, buildReconReport(cwd, result, git.headLog));
404
+ return result;
405
+ }
406
+
407
+ export function reconReportPath(cwd: string): string {
408
+ return join(cwd, RECON_REPORT_PATH);
409
+ }
@@ -0,0 +1,105 @@
1
+ import { basename } from "node:path";
2
+ import { latestAudit, readAuditState } from "./audit-state.ts";
3
+ import { listDraftFindings, listFindingDirs, readFindingFrontmatter } from "./findings.ts";
4
+
5
+ type Severity = "critical" | "high" | "medium" | "low" | "info";
6
+
7
+ interface FindingCounts {
8
+ total: number;
9
+ critical: number;
10
+ high: number;
11
+ medium: number;
12
+ low: number;
13
+ info: number;
14
+ }
15
+
16
+ const SEVERITIES: Severity[] = ["critical", "high", "medium", "low", "info"];
17
+
18
+ function emptyCounts(): FindingCounts {
19
+ return {
20
+ total: 0,
21
+ critical: 0,
22
+ high: 0,
23
+ medium: 0,
24
+ low: 0,
25
+ info: 0,
26
+ };
27
+ }
28
+
29
+ function increment(counts: FindingCounts, severity: Severity): void {
30
+ counts.total += 1;
31
+ counts[severity] += 1;
32
+ }
33
+
34
+ function severityFromId(value: string): Severity {
35
+ const normalized = value.replace(/^FP-/i, "").trim();
36
+ if (/^C\d+/i.test(normalized)) return "critical";
37
+ if (/^H\d+/i.test(normalized)) return "high";
38
+ if (/^M\d+/i.test(normalized)) return "medium";
39
+ if (/^L\d+/i.test(normalized)) return "low";
40
+ if (/^I\d+/i.test(normalized)) return "info";
41
+ return "info";
42
+ }
43
+
44
+ function countFindings(cwd: string): FindingCounts {
45
+ const counts = emptyCounts();
46
+ const dirs = listFindingDirs(cwd);
47
+ if (dirs.length > 0) {
48
+ for (const dir of dirs) {
49
+ const dirName = basename(dir.path);
50
+ if (dirName.startsWith("FP-")) continue;
51
+ increment(counts, readFindingFrontmatter(dir.path)?.severity ?? severityFromId(dir.id));
52
+ }
53
+ return counts;
54
+ }
55
+
56
+ for (const draft of listDraftFindings(cwd)) {
57
+ increment(counts, draft.severity);
58
+ }
59
+ return counts;
60
+ }
61
+
62
+ function parseTime(value: string | null | undefined): number | undefined {
63
+ if (!value) return undefined;
64
+ const ms = Date.parse(value);
65
+ return Number.isFinite(ms) ? ms : undefined;
66
+ }
67
+
68
+ function formatDuration(ms: number): string {
69
+ const totalSeconds = Math.max(0, Math.floor(ms / 1000));
70
+ const hours = Math.floor(totalSeconds / 3600);
71
+ const minutes = Math.floor((totalSeconds % 3600) / 60);
72
+ const seconds = totalSeconds % 60;
73
+ if (hours > 0)
74
+ return `${hours}h ${String(minutes).padStart(2, "0")}m ${String(seconds).padStart(2, "0")}s`;
75
+ if (minutes > 0) return `${minutes}m ${String(seconds).padStart(2, "0")}s`;
76
+ return `${seconds}s`;
77
+ }
78
+
79
+ function severitySummary(counts: FindingCounts): string {
80
+ return SEVERITIES.map((severity) => `${severity} ${counts[severity]}`).join(" | ");
81
+ }
82
+
83
+ export function buildAuditResultStatsLines(
84
+ cwd: string,
85
+ auditId?: string,
86
+ options: { nowMs?: number } = {},
87
+ ): string[] {
88
+ const state = readAuditState(cwd).state;
89
+ const audit = auditId
90
+ ? state?.audits.find((candidate) => candidate.audit_id === auditId)
91
+ : state
92
+ ? latestAudit(state)
93
+ : undefined;
94
+ const startedAt = parseTime(audit?.started_at);
95
+ const completedAt = parseTime(audit?.completed_at) ?? options.nowMs ?? Date.now();
96
+ const duration = startedAt === undefined ? "unknown" : formatDuration(completedAt - startedAt);
97
+ const counts = countFindings(cwd);
98
+
99
+ return [
100
+ "Stats:",
101
+ ` Audit duration: ${duration}`,
102
+ ` Total findings: ${counts.total}`,
103
+ ` Severity: ${severitySummary(counts)}`,
104
+ ];
105
+ }
@@ -0,0 +1,120 @@
1
+ export interface RetryOptions {
2
+ maxRetries: number;
3
+ backoffBaseMs: number;
4
+ backoffMaxMs: number;
5
+ signal?: AbortSignal;
6
+ onRetry?: (info: RetryInfo) => void | Promise<void>;
7
+ shouldRetry?: (err: unknown, info: RetryInfo) => boolean | Promise<boolean>;
8
+ }
9
+
10
+ export interface RetryInfo {
11
+ attempt: number;
12
+ maxAttempts: number;
13
+ nextAttempt: number;
14
+ backoffMs: number;
15
+ nextRetryAt: string;
16
+ errorMessage: string;
17
+ }
18
+
19
+ function readEnv(name: string): string | undefined {
20
+ const primary = process.env[name];
21
+ if (primary !== undefined && primary.trim() !== "") return primary;
22
+ if (!name.startsWith("PIOLIUM_")) return primary;
23
+ const legacy = process.env[`ARCHON_${name.slice("PIOLIUM_".length)}`];
24
+ return legacy;
25
+ }
26
+
27
+ export function readPositiveIntEnv(name: string, fallback: number): number {
28
+ const raw = readEnv(name);
29
+ if (raw === undefined || raw.trim() === "") return fallback;
30
+ const parsed = Number.parseInt(raw, 10);
31
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
32
+ }
33
+
34
+ export function readNonNegativeIntEnv(name: string, fallback: number): number {
35
+ const raw = readEnv(name);
36
+ if (raw === undefined || raw.trim() === "") return fallback;
37
+ const parsed = Number.parseInt(raw, 10);
38
+ return Number.isFinite(parsed) && parsed >= 0 ? parsed : fallback;
39
+ }
40
+
41
+ export function errorMessage(err: unknown): string {
42
+ return err instanceof Error ? err.message : typeof err === "string" ? err : "Unknown error";
43
+ }
44
+
45
+ export function retryBackoffMs(attempt: number, baseMs: number, maxMs: number): number {
46
+ const exponent = Math.max(0, attempt - 1);
47
+ const raw = baseMs * 2 ** exponent;
48
+ return Math.min(maxMs, raw);
49
+ }
50
+
51
+ export async function sleep(ms: number, signal: AbortSignal | undefined): Promise<void> {
52
+ if (ms <= 0) return;
53
+ if (signal?.aborted) throw signal.reason ?? new Error("Aborted");
54
+ await new Promise<void>((resolve, reject) => {
55
+ const cleanup = () => {
56
+ clearTimeout(timeout);
57
+ signal?.removeEventListener("abort", onAbort);
58
+ };
59
+ const timeout = setTimeout(() => {
60
+ cleanup();
61
+ resolve();
62
+ }, ms);
63
+ const onAbort = () => {
64
+ cleanup();
65
+ reject(signal?.reason ?? new Error("Aborted"));
66
+ };
67
+ signal?.addEventListener("abort", onAbort, { once: true });
68
+ });
69
+ }
70
+
71
+ export async function yieldToEventLoop(signal?: AbortSignal): Promise<void> {
72
+ if (signal?.aborted) throw signal.reason ?? new Error("Aborted");
73
+ await new Promise<void>((resolve, reject) => {
74
+ const cleanup = () => {
75
+ clearTimeout(timeout);
76
+ signal?.removeEventListener("abort", onAbort);
77
+ };
78
+ const timeout = setTimeout(() => {
79
+ cleanup();
80
+ resolve();
81
+ }, 0);
82
+ const onAbort = () => {
83
+ cleanup();
84
+ reject(signal?.reason ?? new Error("Aborted"));
85
+ };
86
+ signal?.addEventListener("abort", onAbort, { once: true });
87
+ });
88
+ }
89
+
90
+ export async function runWithRetry<T>(
91
+ operation: (attempt: number, maxAttempts: number) => Promise<T>,
92
+ options: RetryOptions,
93
+ ): Promise<T> {
94
+ const maxRetries = Math.max(0, options.maxRetries);
95
+ const maxAttempts = maxRetries + 1;
96
+
97
+ for (let attempt = 1; attempt <= maxAttempts; attempt++) {
98
+ try {
99
+ return await operation(attempt, maxAttempts);
100
+ } catch (err) {
101
+ if (options.signal?.aborted || attempt >= maxAttempts) throw err;
102
+
103
+ const backoffMs = retryBackoffMs(attempt, options.backoffBaseMs, options.backoffMaxMs);
104
+ const info: RetryInfo = {
105
+ attempt,
106
+ maxAttempts,
107
+ nextAttempt: attempt + 1,
108
+ backoffMs,
109
+ nextRetryAt: new Date(Date.now() + backoffMs).toISOString(),
110
+ errorMessage: errorMessage(err),
111
+ };
112
+
113
+ if (options.shouldRetry && !(await options.shouldRetry(err, info))) throw err;
114
+ await options.onRetry?.(info);
115
+ await sleep(backoffMs, options.signal);
116
+ }
117
+ }
118
+
119
+ throw new Error("Retry loop exhausted unexpectedly.");
120
+ }