@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.
- package/LICENSE +21 -0
- package/README.md +117 -0
- package/agents/access-auditor.md +300 -0
- package/agents/assumption-breaker.md +154 -0
- package/agents/attack-designer.md +116 -0
- package/agents/code-scanner.md +139 -0
- package/agents/concurrency-auditor.md +238 -0
- package/agents/confirm-writer.md +257 -0
- package/agents/context-reviewer.md +274 -0
- package/agents/cross-verifier.md +165 -0
- package/agents/cve-scout.md +381 -0
- package/agents/env-builder.md +282 -0
- package/agents/env-profiler.md +205 -0
- package/agents/evidence-collector.md +140 -0
- package/agents/finding-grader.md +142 -0
- package/agents/finding-writer.md +148 -0
- package/agents/flow-tracer.md +106 -0
- package/agents/goal-backtracer.md +146 -0
- package/agents/history-miner.md +467 -0
- package/agents/independent-verifier.md +118 -0
- package/agents/intent-mapper.md +183 -0
- package/agents/longshot-collector.md +128 -0
- package/agents/longshot-prober.md +126 -0
- package/agents/patch-auditor.md +73 -0
- package/agents/poc-author.md +124 -0
- package/agents/poc-runner.md +194 -0
- package/agents/probe-lead.md +269 -0
- package/agents/red-challenger.md +101 -0
- package/agents/report-composer.md +208 -0
- package/agents/review-adjudicator.md +216 -0
- package/agents/spec-auditor.md +155 -0
- package/agents/taint-tracer.md +265 -0
- package/agents/test-locator.md +209 -0
- package/agents/threat-modeler.md +132 -0
- package/agents/variant-scanner.md +108 -0
- package/agents/variant-spotter.md +110 -0
- package/bin/piolium.mjs +376 -0
- package/extensions/piolium/_vendor/yaml.bundle.d.mts +6 -0
- package/extensions/piolium/_vendor/yaml.bundle.mjs +139 -0
- package/extensions/piolium/agent-runner.ts +322 -0
- package/extensions/piolium/agents.ts +266 -0
- package/extensions/piolium/audit-state.ts +522 -0
- package/extensions/piolium/bundled-resources.ts +97 -0
- package/extensions/piolium/candidate-scan.ts +966 -0
- package/extensions/piolium/command-target.ts +177 -0
- package/extensions/piolium/console-stream.ts +57 -0
- package/extensions/piolium/export-results.ts +380 -0
- package/extensions/piolium/findings.ts +448 -0
- package/extensions/piolium/heartbeat.ts +182 -0
- package/extensions/piolium/help.ts +234 -0
- package/extensions/piolium/index.ts +1865 -0
- package/extensions/piolium/longshot.ts +530 -0
- package/extensions/piolium/matcher-suggestions.ts +196 -0
- package/extensions/piolium/matcher-utils.ts +83 -0
- package/extensions/piolium/modes/balanced.ts +750 -0
- package/extensions/piolium/modes/confirm-bootstrap.ts +186 -0
- package/extensions/piolium/modes/confirm.ts +697 -0
- package/extensions/piolium/modes/deep.ts +917 -0
- package/extensions/piolium/modes/diff.ts +177 -0
- package/extensions/piolium/modes/lite.ts +540 -0
- package/extensions/piolium/modes/longshot.ts +595 -0
- package/extensions/piolium/modes/merge.ts +204 -0
- package/extensions/piolium/modes/phase-runner.ts +267 -0
- package/extensions/piolium/modes/reinvest.ts +546 -0
- package/extensions/piolium/modes/revisit.ts +279 -0
- package/extensions/piolium/modes.ts +48 -0
- package/extensions/piolium/phase-labels.ts +123 -0
- package/extensions/piolium/phase-status-strip.ts +92 -0
- package/extensions/piolium/prompt-prefix-editor.ts +39 -0
- package/extensions/piolium/providers/anthropic-vertex.ts +836 -0
- package/extensions/piolium/recon.ts +409 -0
- package/extensions/piolium/result-stats.ts +105 -0
- package/extensions/piolium/retry.ts +120 -0
- package/extensions/piolium/scheduler.ts +212 -0
- package/extensions/piolium/secrets.ts +368 -0
- package/extensions/piolium/tools/web-tools.ts +148 -0
- package/package.json +77 -0
- package/skills/agentic-actions-auditor/SKILL.md +327 -0
- package/skills/agentic-actions-auditor/references/action-profiles.md +186 -0
- package/skills/agentic-actions-auditor/references/cross-file-resolution.md +209 -0
- package/skills/agentic-actions-auditor/references/foundations.md +94 -0
- package/skills/agentic-actions-auditor/references/vector-a-env-var-intermediary.md +77 -0
- package/skills/agentic-actions-auditor/references/vector-b-direct-expression-injection.md +83 -0
- package/skills/agentic-actions-auditor/references/vector-c-cli-data-fetch.md +83 -0
- package/skills/agentic-actions-auditor/references/vector-d-pr-target-checkout.md +88 -0
- package/skills/agentic-actions-auditor/references/vector-e-error-log-injection.md +88 -0
- package/skills/agentic-actions-auditor/references/vector-f-subshell-expansion.md +82 -0
- package/skills/agentic-actions-auditor/references/vector-g-eval-of-ai-output.md +91 -0
- package/skills/agentic-actions-auditor/references/vector-h-dangerous-sandbox-configs.md +102 -0
- package/skills/agentic-actions-auditor/references/vector-i-wildcard-allowlists.md +88 -0
- package/skills/audit/SKILL.md +562 -0
- package/skills/audit/assets/icon.svg +7 -0
- package/skills/audit/hooks/scripts/validate_phase_output.py +550 -0
- package/skills/audit/references/adversarial-review.md +148 -0
- package/skills/audit/references/architecture-aware-sast.md +306 -0
- package/skills/audit/references/audit-workflow.md +737 -0
- package/skills/audit/references/chamber-protocol.md +384 -0
- package/skills/audit/references/creative-attack-modes.md +221 -0
- package/skills/audit/references/deep-analysis.md +273 -0
- package/skills/audit/references/domain-attack-playbooks.md +1129 -0
- package/skills/audit/references/knowledge-base-template.md +513 -0
- package/skills/audit/references/real-env-validation.md +191 -0
- package/skills/audit/references/report-templates.md +417 -0
- package/skills/audit/references/triage-and-prereqs.md +134 -0
- package/skills/audit/scripts/consolidate_drafts.py +554 -0
- package/skills/audit/scripts/partition_findings.py +152 -0
- package/skills/audit/scripts/rg-hotspots.sh +121 -0
- package/skills/audit/scripts/stamp_file_state.py +349 -0
- package/skills/code-reviewer/SKILL.md +65 -0
- package/skills/codeql/SKILL.md +281 -0
- package/skills/codeql/references/build-fixes.md +90 -0
- package/skills/codeql/references/diagnostic-query-templates.md +339 -0
- package/skills/codeql/references/extension-yaml-format.md +209 -0
- package/skills/codeql/references/important-only-suite.md +153 -0
- package/skills/codeql/references/language-details.md +207 -0
- package/skills/codeql/references/macos-arm64e-workaround.md +179 -0
- package/skills/codeql/references/performance-tuning.md +111 -0
- package/skills/codeql/references/quality-assessment.md +172 -0
- package/skills/codeql/references/ruleset-catalog.md +63 -0
- package/skills/codeql/references/run-all-suite.md +92 -0
- package/skills/codeql/references/sarif-processing.md +79 -0
- package/skills/codeql/references/threat-models.md +51 -0
- package/skills/codeql/workflows/build-database.md +280 -0
- package/skills/codeql/workflows/create-data-extensions.md +261 -0
- package/skills/codeql/workflows/run-analysis.md +301 -0
- package/skills/differential-review/SKILL.md +220 -0
- package/skills/differential-review/adversarial.md +203 -0
- package/skills/differential-review/methodology.md +234 -0
- package/skills/differential-review/patterns.md +300 -0
- package/skills/differential-review/reporting.md +369 -0
- package/skills/fp-check/SKILL.md +125 -0
- package/skills/fp-check/references/bug-class-verification.md +114 -0
- package/skills/fp-check/references/deep-verification.md +143 -0
- package/skills/fp-check/references/evidence-templates.md +91 -0
- package/skills/fp-check/references/false-positive-patterns.md +115 -0
- package/skills/fp-check/references/gate-reviews.md +27 -0
- package/skills/fp-check/references/standard-verification.md +78 -0
- package/skills/insecure-defaults/SKILL.md +117 -0
- package/skills/insecure-defaults/references/examples.md +409 -0
- package/skills/last30days/SKILL.md +444 -0
- package/skills/sarif-parsing/SKILL.md +483 -0
- package/skills/sarif-parsing/resources/jq-queries.md +162 -0
- package/skills/sarif-parsing/resources/sarif_helpers.py +331 -0
- package/skills/security-threat-model/LICENSE.txt +201 -0
- package/skills/security-threat-model/SKILL.md +81 -0
- package/skills/security-threat-model/agents/openai.yaml +4 -0
- package/skills/security-threat-model/references/prompt-template.md +255 -0
- package/skills/security-threat-model/references/security-controls-and-assets.md +32 -0
- package/skills/semgrep/SKILL.md +212 -0
- package/skills/semgrep/references/rulesets.md +162 -0
- package/skills/semgrep/references/scan-modes.md +110 -0
- package/skills/semgrep/references/scanner-task-prompt.md +140 -0
- package/skills/semgrep/scripts/merge_sarif.py +203 -0
- package/skills/semgrep/workflows/scan-workflow.md +311 -0
- package/skills/semgrep-rule-creator/SKILL.md +168 -0
- package/skills/semgrep-rule-creator/references/quick-reference.md +202 -0
- package/skills/semgrep-rule-creator/references/workflow.md +240 -0
- package/skills/semgrep-rule-variant-creator/SKILL.md +205 -0
- package/skills/semgrep-rule-variant-creator/references/applicability-analysis.md +250 -0
- package/skills/semgrep-rule-variant-creator/references/language-syntax-guide.md +324 -0
- package/skills/semgrep-rule-variant-creator/references/workflow.md +518 -0
- package/skills/sharp-edges/SKILL.md +292 -0
- package/skills/sharp-edges/references/auth-patterns.md +252 -0
- package/skills/sharp-edges/references/case-studies.md +274 -0
- package/skills/sharp-edges/references/config-patterns.md +333 -0
- package/skills/sharp-edges/references/crypto-apis.md +190 -0
- package/skills/sharp-edges/references/lang-c.md +205 -0
- package/skills/sharp-edges/references/lang-csharp.md +285 -0
- package/skills/sharp-edges/references/lang-go.md +270 -0
- package/skills/sharp-edges/references/lang-java.md +263 -0
- package/skills/sharp-edges/references/lang-javascript.md +269 -0
- package/skills/sharp-edges/references/lang-kotlin.md +265 -0
- package/skills/sharp-edges/references/lang-php.md +245 -0
- package/skills/sharp-edges/references/lang-python.md +274 -0
- package/skills/sharp-edges/references/lang-ruby.md +273 -0
- package/skills/sharp-edges/references/lang-rust.md +272 -0
- package/skills/sharp-edges/references/lang-swift.md +287 -0
- package/skills/sharp-edges/references/language-specific.md +588 -0
- package/skills/spec-to-code-compliance/SKILL.md +357 -0
- package/skills/spec-to-code-compliance/resources/COMPLETENESS_CHECKLIST.md +69 -0
- package/skills/spec-to-code-compliance/resources/IR_EXAMPLES.md +417 -0
- package/skills/spec-to-code-compliance/resources/OUTPUT_REQUIREMENTS.md +105 -0
- package/skills/supply-chain-risk-auditor/SKILL.md +67 -0
- package/skills/supply-chain-risk-auditor/resources/results-template.md +41 -0
- package/skills/variant-analysis/METHODOLOGY.md +327 -0
- package/skills/variant-analysis/SKILL.md +142 -0
- package/skills/variant-analysis/resources/codeql/cpp.ql +119 -0
- package/skills/variant-analysis/resources/codeql/go.ql +69 -0
- package/skills/variant-analysis/resources/codeql/java.ql +71 -0
- package/skills/variant-analysis/resources/codeql/javascript.ql +63 -0
- package/skills/variant-analysis/resources/codeql/python.ql +80 -0
- package/skills/variant-analysis/resources/semgrep/cpp.yaml +98 -0
- package/skills/variant-analysis/resources/semgrep/go.yaml +63 -0
- package/skills/variant-analysis/resources/semgrep/java.yaml +61 -0
- package/skills/variant-analysis/resources/semgrep/javascript.yaml +60 -0
- package/skills/variant-analysis/resources/semgrep/python.yaml +72 -0
- package/skills/variant-analysis/resources/variant-report-template.md +75 -0
- package/skills/vuln-report/SKILL.md +137 -0
- package/skills/vuln-report/agents/openai.yaml +4 -0
- package/skills/vuln-report/references/report-template.md +135 -0
- package/skills/wooyun-legacy/SKILL.md +367 -0
- package/skills/wooyun-legacy/references/bank-penetration.md +222 -0
- package/skills/wooyun-legacy/references/checklists/command-execution-checklist.md +119 -0
- package/skills/wooyun-legacy/references/checklists/csrf-checklist.md +74 -0
- package/skills/wooyun-legacy/references/checklists/file-upload-checklist.md +108 -0
- package/skills/wooyun-legacy/references/checklists/info-disclosure-checklist.md +114 -0
- package/skills/wooyun-legacy/references/checklists/logic-flaws-checklist.md +95 -0
- package/skills/wooyun-legacy/references/checklists/misconfig-checklist.md +124 -0
- package/skills/wooyun-legacy/references/checklists/path-traversal-checklist.md +87 -0
- package/skills/wooyun-legacy/references/checklists/rce-checklist.md +93 -0
- package/skills/wooyun-legacy/references/checklists/sql-injection-checklist.md +97 -0
- package/skills/wooyun-legacy/references/checklists/ssrf-checklist.md +99 -0
- package/skills/wooyun-legacy/references/checklists/unauthorized-access-checklist.md +89 -0
- package/skills/wooyun-legacy/references/checklists/weak-password-checklist.md +115 -0
- package/skills/wooyun-legacy/references/checklists/xss-checklist.md +103 -0
- package/skills/wooyun-legacy/references/checklists/xxe-checklist.md +130 -0
- package/skills/wooyun-legacy/references/info-disclosure.md +975 -0
- package/skills/wooyun-legacy/references/logic-flaws.md +721 -0
- package/skills/wooyun-legacy/references/path-traversal.md +1191 -0
- package/skills/wooyun-legacy/references/telecom-penetration.md +156 -0
- package/skills/wooyun-legacy/references/unauthorized-access.md +980 -0
- package/skills/wooyun-legacy/references/xss.md +746 -0
- package/skills/zeroize-audit/SKILL.md +371 -0
- package/skills/zeroize-audit/configs/c.yaml +21 -0
- package/skills/zeroize-audit/configs/default.yaml +128 -0
- package/skills/zeroize-audit/configs/rust.yaml +83 -0
- package/skills/zeroize-audit/prompts/report_template.md +238 -0
- package/skills/zeroize-audit/prompts/system.md +163 -0
- package/skills/zeroize-audit/prompts/task.md +97 -0
- package/skills/zeroize-audit/references/compile-commands.md +231 -0
- package/skills/zeroize-audit/references/detection-strategy.md +191 -0
- package/skills/zeroize-audit/references/ir-analysis.md +252 -0
- package/skills/zeroize-audit/references/mcp-analysis.md +221 -0
- package/skills/zeroize-audit/references/poc-generation.md +470 -0
- package/skills/zeroize-audit/references/rust-zeroization-patterns.md +867 -0
- package/skills/zeroize-audit/schemas/input.json +83 -0
- package/skills/zeroize-audit/schemas/output.json +140 -0
- package/skills/zeroize-audit/tools/analyze_asm.sh +202 -0
- package/skills/zeroize-audit/tools/analyze_cfg.py +381 -0
- package/skills/zeroize-audit/tools/analyze_heap.sh +211 -0
- package/skills/zeroize-audit/tools/analyze_ir_semantic.py +429 -0
- package/skills/zeroize-audit/tools/diff_ir.sh +135 -0
- package/skills/zeroize-audit/tools/diff_rust_mir.sh +189 -0
- package/skills/zeroize-audit/tools/emit_asm.sh +67 -0
- package/skills/zeroize-audit/tools/emit_ir.sh +77 -0
- package/skills/zeroize-audit/tools/emit_rust_asm.sh +178 -0
- package/skills/zeroize-audit/tools/emit_rust_ir.sh +150 -0
- package/skills/zeroize-audit/tools/emit_rust_mir.sh +158 -0
- package/skills/zeroize-audit/tools/extract_compile_flags.py +284 -0
- package/skills/zeroize-audit/tools/generate_poc.py +1329 -0
- package/skills/zeroize-audit/tools/mcp/apply_confidence_gates.py +113 -0
- package/skills/zeroize-audit/tools/mcp/check_mcp.sh +68 -0
- package/skills/zeroize-audit/tools/mcp/normalize_mcp_evidence.py +125 -0
- package/skills/zeroize-audit/tools/scripts/check_llvm_patterns.py +481 -0
- package/skills/zeroize-audit/tools/scripts/check_mir_patterns.py +554 -0
- package/skills/zeroize-audit/tools/scripts/check_rust_asm.py +424 -0
- package/skills/zeroize-audit/tools/scripts/check_rust_asm_aarch64.py +300 -0
- package/skills/zeroize-audit/tools/scripts/check_rust_asm_x86.py +283 -0
- package/skills/zeroize-audit/tools/scripts/find_dangerous_apis.py +375 -0
- package/skills/zeroize-audit/tools/scripts/semantic_audit.py +923 -0
- package/skills/zeroize-audit/tools/track_dataflow.sh +196 -0
- package/skills/zeroize-audit/tools/validate_rust_toolchain.sh +298 -0
- package/skills/zeroize-audit/workflows/phase-0-preflight.md +150 -0
- package/skills/zeroize-audit/workflows/phase-1-source-analysis.md +144 -0
- package/skills/zeroize-audit/workflows/phase-2-compiler-analysis.md +139 -0
- package/skills/zeroize-audit/workflows/phase-3-interim-report.md +46 -0
- package/skills/zeroize-audit/workflows/phase-4-poc-generation.md +46 -0
- package/skills/zeroize-audit/workflows/phase-5-poc-validation.md +136 -0
- package/skills/zeroize-audit/workflows/phase-6-final-report.md +44 -0
- package/skills/zeroize-audit/workflows/phase-7-test-generation.md +42 -0
- package/themes/piolium-srcery.json +94 -0
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Route confirmed vs theoretical findings AFTER poc-author has run.
|
|
4
|
+
|
|
5
|
+
`consolidate_drafts.py` puts every actionable finding in
|
|
6
|
+
`<archon_dir>/findings/<ID>-<slug>/`. poc-author then attempts a PoC for
|
|
7
|
+
each and writes `PoC-Status: executed | theoretical | blocked` back into the
|
|
8
|
+
finding's `draft.md`. This script reads that field and demotes any finding
|
|
9
|
+
that did NOT reach `PoC-Status: executed` into
|
|
10
|
+
`<archon_dir>/findings-theoretical/<ID>-<slug>/` (same directory shape, IDs
|
|
11
|
+
unchanged so cross-references stay stable).
|
|
12
|
+
|
|
13
|
+
Bucket contract:
|
|
14
|
+
findings/ -> confirmed, PoC executed (the actionable bucket)
|
|
15
|
+
findings-theoretical/ -> no PoC / theoretical / blocked, plus the
|
|
16
|
+
triage-skipped findings consolidate_drafts.py
|
|
17
|
+
already placed there.
|
|
18
|
+
|
|
19
|
+
The script is idempotent: re-running it never moves an already-`executed`
|
|
20
|
+
finding, and a re-demoted finding overwrites any stale same-ID directory in
|
|
21
|
+
the theoretical bucket. It is a no-op for modes that never build PoCs
|
|
22
|
+
(nothing is `executed`, but those modes simply don't invoke this script).
|
|
23
|
+
|
|
24
|
+
Usage:
|
|
25
|
+
partition_findings.py [archon_dir]
|
|
26
|
+
|
|
27
|
+
archon_dir defaults to "archon". Exit codes:
|
|
28
|
+
0 success (including "nothing to move")
|
|
29
|
+
2 usage error / archon_dir missing
|
|
30
|
+
3 I/O error during partition
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
import json
|
|
34
|
+
import os
|
|
35
|
+
import re
|
|
36
|
+
import shutil
|
|
37
|
+
import sys
|
|
38
|
+
from pathlib import Path
|
|
39
|
+
|
|
40
|
+
FINDING_DIR_RE = re.compile(r"^[CHM]\d+-")
|
|
41
|
+
# Same shape as consolidate_drafts.py's KV_RE, intentionally duplicated:
|
|
42
|
+
# every vendored script under skills/audit/scripts/ is standalone (copied
|
|
43
|
+
# verbatim at install time, no cross-script imports). Keep the two in sync.
|
|
44
|
+
KV_RE = re.compile(r"^([A-Za-z][A-Za-z0-9 _-]*):\s*(.*)$")
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def read_poc_status(draft_path: Path) -> str:
|
|
48
|
+
"""Return the lowercased PoC-Status value from a finding draft, or "" if
|
|
49
|
+
absent.
|
|
50
|
+
|
|
51
|
+
poc-author writes `PoC-Status:` back into an already-materialised
|
|
52
|
+
`draft.md` *after* consolidation, so it does not always land inside the
|
|
53
|
+
strict leading `Key: Value` block (which terminates at the first blank
|
|
54
|
+
line or `## ` heading). Demoting a genuinely confirmed finding because
|
|
55
|
+
the field sat one line outside that block is the costly failure
|
|
56
|
+
direction, so scan the WHOLE file for a `PoC-Status:` line and take the
|
|
57
|
+
last occurrence (re-runs append an updated value). This matches how
|
|
58
|
+
finding-writer and merge mode read the field loosely.
|
|
59
|
+
"""
|
|
60
|
+
if not draft_path.is_file():
|
|
61
|
+
return ""
|
|
62
|
+
found = ""
|
|
63
|
+
try:
|
|
64
|
+
with draft_path.open() as f:
|
|
65
|
+
for line in f:
|
|
66
|
+
m = KV_RE.match(line.rstrip("\n"))
|
|
67
|
+
if m and m.group(1).strip().lower() == "poc-status":
|
|
68
|
+
found = m.group(2).strip().lower()
|
|
69
|
+
except OSError:
|
|
70
|
+
pass
|
|
71
|
+
return found
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def move_into(src: Path, dest_dir: Path) -> Path:
|
|
75
|
+
"""Move `src` directory into `dest_dir`, replacing any same-named target
|
|
76
|
+
so the operation is idempotent. Returns the destination path.
|
|
77
|
+
"""
|
|
78
|
+
dest_dir.mkdir(parents=True, exist_ok=True)
|
|
79
|
+
target = dest_dir / src.name
|
|
80
|
+
if target.exists():
|
|
81
|
+
shutil.rmtree(target)
|
|
82
|
+
shutil.move(str(src), str(target))
|
|
83
|
+
return target
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def partition(archon_dir: Path) -> int:
|
|
87
|
+
findings_dir = archon_dir / "findings"
|
|
88
|
+
theoretical_dir = archon_dir / "findings-theoretical"
|
|
89
|
+
|
|
90
|
+
kept: list[str] = []
|
|
91
|
+
moved: list[dict] = []
|
|
92
|
+
|
|
93
|
+
if findings_dir.is_dir():
|
|
94
|
+
for entry in sorted(os.listdir(findings_dir)):
|
|
95
|
+
folder = findings_dir / entry
|
|
96
|
+
if not folder.is_dir() or not FINDING_DIR_RE.match(entry):
|
|
97
|
+
continue
|
|
98
|
+
status = read_poc_status(folder / "draft.md")
|
|
99
|
+
if status == "executed":
|
|
100
|
+
kept.append(entry)
|
|
101
|
+
continue
|
|
102
|
+
dest = move_into(folder, theoretical_dir)
|
|
103
|
+
moved.append(
|
|
104
|
+
{
|
|
105
|
+
"id": entry.split("-", 1)[0],
|
|
106
|
+
"dir": entry,
|
|
107
|
+
"poc_status": status or "missing",
|
|
108
|
+
"to": str(dest),
|
|
109
|
+
}
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
manifest = {
|
|
113
|
+
"archon_dir": str(archon_dir),
|
|
114
|
+
"kept": kept,
|
|
115
|
+
"moved": moved,
|
|
116
|
+
"counts": {"kept": len(kept), "moved": len(moved)},
|
|
117
|
+
}
|
|
118
|
+
(archon_dir / "findings-draft").mkdir(parents=True, exist_ok=True)
|
|
119
|
+
(archon_dir / "findings-draft" / "partition-manifest.json").write_text(
|
|
120
|
+
json.dumps(manifest, indent=2) + "\n"
|
|
121
|
+
)
|
|
122
|
+
print(json.dumps(manifest, indent=2))
|
|
123
|
+
print(
|
|
124
|
+
f"partition: {len(kept)} confirmed stay in findings/, "
|
|
125
|
+
f"{len(moved)} demoted to findings-theoretical/ "
|
|
126
|
+
f"(no PoC / theoretical / blocked)",
|
|
127
|
+
file=sys.stderr,
|
|
128
|
+
)
|
|
129
|
+
return 0
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def main() -> None:
|
|
133
|
+
argv = sys.argv[1:]
|
|
134
|
+
if argv and argv[0] in ("-h", "--help"):
|
|
135
|
+
print(__doc__)
|
|
136
|
+
sys.exit(0)
|
|
137
|
+
if len(argv) > 1:
|
|
138
|
+
print("usage: partition_findings.py [archon_dir]", file=sys.stderr)
|
|
139
|
+
sys.exit(2)
|
|
140
|
+
archon_dir = Path(argv[0]) if argv else Path("archon")
|
|
141
|
+
if not archon_dir.is_dir():
|
|
142
|
+
print(f"error: archon dir not found: {archon_dir}", file=sys.stderr)
|
|
143
|
+
sys.exit(2)
|
|
144
|
+
try:
|
|
145
|
+
sys.exit(partition(archon_dir))
|
|
146
|
+
except OSError as e:
|
|
147
|
+
print(f"error: I/O failure during partition: {e}", file=sys.stderr)
|
|
148
|
+
sys.exit(3)
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
if __name__ == "__main__":
|
|
152
|
+
main()
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
# Fast, noisy-first security hotspot discovery.
|
|
5
|
+
# Goal: find review starting points (sinks and risky APIs), not prove vulns.
|
|
6
|
+
#
|
|
7
|
+
# Usage:
|
|
8
|
+
# scripts/rg-hotspots.sh # scan repo from cwd
|
|
9
|
+
# scripts/rg-hotspots.sh path/ # scan a subdir
|
|
10
|
+
#
|
|
11
|
+
# Tips:
|
|
12
|
+
# - Pipe into a pager: `... | less -R`
|
|
13
|
+
# - Narrow scope for signal: `... api/` or `... src/auth/`
|
|
14
|
+
|
|
15
|
+
root="${1:-.}"
|
|
16
|
+
|
|
17
|
+
if ! command -v rg >/dev/null 2>&1; then
|
|
18
|
+
echo "error: rg (ripgrep) not found in PATH" >&2
|
|
19
|
+
exit 2
|
|
20
|
+
fi
|
|
21
|
+
|
|
22
|
+
rg_base=(
|
|
23
|
+
--no-heading
|
|
24
|
+
--line-number
|
|
25
|
+
--hidden
|
|
26
|
+
--follow
|
|
27
|
+
--smart-case
|
|
28
|
+
--glob '!.git/**'
|
|
29
|
+
--glob '!**/node_modules/**'
|
|
30
|
+
--glob '!**/dist/**'
|
|
31
|
+
--glob '!**/build/**'
|
|
32
|
+
--glob '!**/target/**'
|
|
33
|
+
--glob '!**/.next/**'
|
|
34
|
+
--glob '!**/vendor/**'
|
|
35
|
+
--glob '!**/.venv/**'
|
|
36
|
+
--glob '!**/venv/**'
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
section() {
|
|
40
|
+
echo
|
|
41
|
+
echo "== $1"
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
run() {
|
|
45
|
+
local label="$1"
|
|
46
|
+
shift
|
|
47
|
+
section "$label"
|
|
48
|
+
# `|| true` because some sections will have no matches (not an error).
|
|
49
|
+
rg "${rg_base[@]}" "$@" "$root" || true
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
section "RG Hotspots"
|
|
53
|
+
echo "root: $root"
|
|
54
|
+
|
|
55
|
+
run "Command Execution / Dangerous Eval" \
|
|
56
|
+
-e 'Runtime\.getRuntime\(\)\.exec' \
|
|
57
|
+
-e 'ProcessBuilder\s*\(' \
|
|
58
|
+
-e '\bexec(ute)?\s*\(' \
|
|
59
|
+
-e '\bsystem\s*\(' \
|
|
60
|
+
-e '\bpopen\s*\(' \
|
|
61
|
+
-e 'child_process\.(exec|execSync|spawn|spawnSync)\b' \
|
|
62
|
+
-e '\bos\.system\b' \
|
|
63
|
+
-e '\bsubprocess\.(run|Popen|call|check_output)\b' \
|
|
64
|
+
-e '\beval\s*\(' \
|
|
65
|
+
-e '\bFunction\s*\(' \
|
|
66
|
+
-e 'vm\.run(In(New)?Context|InThisContext)\b'
|
|
67
|
+
|
|
68
|
+
run "Deserialization / Template Injection Primitives" \
|
|
69
|
+
-e '\bpickle\.loads\b' \
|
|
70
|
+
-e '\byaml\.load\s*\(' \
|
|
71
|
+
-e '\bObjectInputStream\b' \
|
|
72
|
+
-e '\breadObject\s*\(' \
|
|
73
|
+
-e '\bunserialize\s*\(' \
|
|
74
|
+
-e '\bMarshal\.load\b' \
|
|
75
|
+
-e '\bERB\.new\b' \
|
|
76
|
+
-e '\bnew\s+Template\b' \
|
|
77
|
+
-e '\bMustache\.' \
|
|
78
|
+
-e '\bHandlebars\.' \
|
|
79
|
+
-e '\bEJS\b'
|
|
80
|
+
|
|
81
|
+
run "SSRF / URL Fetching / HTTP Clients (review call sites)" \
|
|
82
|
+
-e '\brequests\.(get|post|put|delete|head|patch)\b' \
|
|
83
|
+
-e '\burllib\.(request|parse)\b' \
|
|
84
|
+
-e '\bhttpx\.(get|post|Client)\b' \
|
|
85
|
+
-e '\baxios\.' \
|
|
86
|
+
-e '\bfetch\s*\(' \
|
|
87
|
+
-e '\bgot\s*\(' \
|
|
88
|
+
-e '\bnew\s+URL\s*\(' \
|
|
89
|
+
-e '\bnet/http\b' \
|
|
90
|
+
-e '\bhttp\.NewRequest\b' \
|
|
91
|
+
-e '\bhttp\.Client\b'
|
|
92
|
+
|
|
93
|
+
run "SQL / Query Construction (review interpolation/concatenation)" \
|
|
94
|
+
-e '\bSELECT\b|\bINSERT\b|\bUPDATE\b|\bDELETE\b' \
|
|
95
|
+
-e '\bquery(Raw)?\s*\(' \
|
|
96
|
+
-e '\bexecute\s*\(' \
|
|
97
|
+
-e '\bprepare\s*\('
|
|
98
|
+
|
|
99
|
+
run "File & Path Handling (review traversal/allowlists)" \
|
|
100
|
+
-e '\bopen\s*\(' \
|
|
101
|
+
-e '\bos\.path\.join\b|\bpath\.join\b|\bfilepath\.Join\b' \
|
|
102
|
+
-e '\bread(File|FileSync)\s*\(' \
|
|
103
|
+
-e '\bwrite(File|FileSync)\s*\(' \
|
|
104
|
+
-e '\bcreate(Read|Write)Stream\s*\(' \
|
|
105
|
+
-e '\bSendFile\b|\bsendFile\s*\('
|
|
106
|
+
|
|
107
|
+
run "AuthN/AuthZ Signals (review enforcement points)" \
|
|
108
|
+
-e '\bauthori[sz]e\b' \
|
|
109
|
+
-e '\bpermission(s)?\b' \
|
|
110
|
+
-e '\brole(s)?\b' \
|
|
111
|
+
-e '\bisAdmin\b|\badminOnly\b' \
|
|
112
|
+
-e '\bRBAC\b|\bABAC\b'
|
|
113
|
+
|
|
114
|
+
run "Secrets (high false positives; prioritize obvious tokens/keys)" \
|
|
115
|
+
-e 'AKIA[0-9A-Z]{16}' \
|
|
116
|
+
-e 'ASIA[0-9A-Z]{16}' \
|
|
117
|
+
-e '-----BEGIN (RSA|EC|OPENSSH) PRIVATE KEY-----' \
|
|
118
|
+
-e 'xox[baprs]-[0-9A-Za-z-]+' \
|
|
119
|
+
-e 'ghp_[0-9A-Za-z]{30,}' \
|
|
120
|
+
-e 'AIza[0-9A-Za-z\-_]{35}' \
|
|
121
|
+
-e '\b(api[_-]?key|secret|token|passwd|password)\b\s*[:=]\s*["'\''][^"'\'']{8,}["'\'']'
|
|
@@ -0,0 +1,349 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Walk the target repository, hash every source file, and write
|
|
4
|
+
archon/file-state.json — a per-file lattice that records which audit IDs
|
|
5
|
+
have touched which file. Run this as a side-effect of the final phase of
|
|
6
|
+
any audit (balanced Phase B8, deep Phase D13) so the next audit can compute
|
|
7
|
+
an incremental scope (changed/new/deleted files) against the prior state.
|
|
8
|
+
|
|
9
|
+
The state file is additive across runs: a re-run merges the new audit_id
|
|
10
|
+
into each file's `last_audits[]` rather than overwriting.
|
|
11
|
+
|
|
12
|
+
Usage:
|
|
13
|
+
stamp_file_state.py [--target <path>] [--archon-dir <path>] [--audit-id <id>] [--phases <n,n,...>]
|
|
14
|
+
|
|
15
|
+
Defaults:
|
|
16
|
+
--target cwd
|
|
17
|
+
--archon-dir <target>/archon
|
|
18
|
+
--audit-id read from <archon-dir>/audit-state.json's last entry
|
|
19
|
+
--phases all integer keys from the last audit's `phases` map
|
|
20
|
+
|
|
21
|
+
Excludes:
|
|
22
|
+
Standard noise (.git/, node_modules/, vendor/, __pycache__/, dist/,
|
|
23
|
+
build/, .venv/, target/, .archon-merge-staging-*/) and the archon/
|
|
24
|
+
directory itself. Only files smaller than DEFAULT_MAX_BYTES (~512KB)
|
|
25
|
+
are hashed; larger files get a `large_file: true` marker without a
|
|
26
|
+
hash so the next audit still sees that they exist.
|
|
27
|
+
|
|
28
|
+
Exit codes:
|
|
29
|
+
0 success
|
|
30
|
+
1 no audit_id available (no prior audit-state.json and none provided)
|
|
31
|
+
2 usage error / target missing
|
|
32
|
+
3 I/O failure during walk or write
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
from __future__ import annotations
|
|
36
|
+
|
|
37
|
+
import argparse
|
|
38
|
+
import hashlib
|
|
39
|
+
import json
|
|
40
|
+
import os
|
|
41
|
+
import sys
|
|
42
|
+
from datetime import datetime, timezone
|
|
43
|
+
from pathlib import Path
|
|
44
|
+
from typing import Optional
|
|
45
|
+
|
|
46
|
+
DEFAULT_MAX_BYTES = 512 * 1024 # 512 KB cap per file before we skip hashing
|
|
47
|
+
|
|
48
|
+
EXCLUDED_DIR_NAMES = {
|
|
49
|
+
".git",
|
|
50
|
+
".hg",
|
|
51
|
+
".svn",
|
|
52
|
+
"node_modules",
|
|
53
|
+
"vendor",
|
|
54
|
+
"__pycache__",
|
|
55
|
+
".venv",
|
|
56
|
+
"venv",
|
|
57
|
+
".tox",
|
|
58
|
+
"dist",
|
|
59
|
+
"build",
|
|
60
|
+
"target",
|
|
61
|
+
".next",
|
|
62
|
+
".nuxt",
|
|
63
|
+
".cache",
|
|
64
|
+
".pytest_cache",
|
|
65
|
+
".mypy_cache",
|
|
66
|
+
".ruff_cache",
|
|
67
|
+
".gradle",
|
|
68
|
+
".idea",
|
|
69
|
+
".vscode",
|
|
70
|
+
"archon",
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
EXCLUDED_DIR_PREFIXES = (".archon-merge-staging-", "bak-archon-")
|
|
74
|
+
|
|
75
|
+
# Hashed only — extensions are intentionally broad. If a file has no
|
|
76
|
+
# extension we still hash it as long as it's not obviously a binary blob
|
|
77
|
+
# (we sniff the first chunk for null bytes).
|
|
78
|
+
TEXT_HINT_EXTENSIONS = {
|
|
79
|
+
# source
|
|
80
|
+
".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs",
|
|
81
|
+
".py", ".rb", ".go", ".rs", ".java", ".kt", ".scala",
|
|
82
|
+
".c", ".cc", ".cpp", ".h", ".hpp", ".m", ".mm",
|
|
83
|
+
".cs", ".fs", ".vb", ".swift", ".dart",
|
|
84
|
+
".php", ".pl", ".pm", ".lua", ".r", ".jl", ".ex", ".exs",
|
|
85
|
+
".erl", ".hrl", ".clj", ".cljs", ".elm", ".purs",
|
|
86
|
+
".sh", ".bash", ".zsh", ".fish", ".ps1", ".bat", ".cmd",
|
|
87
|
+
".sql", ".graphql", ".gql", ".proto", ".thrift", ".avsc",
|
|
88
|
+
# config / infra
|
|
89
|
+
".json", ".yaml", ".yml", ".toml", ".ini", ".env", ".conf",
|
|
90
|
+
".tf", ".hcl", ".tfvars", ".dockerfile",
|
|
91
|
+
# docs
|
|
92
|
+
".md", ".mdx", ".rst", ".txt",
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def is_excluded_dir(name: str) -> bool:
|
|
97
|
+
if name in EXCLUDED_DIR_NAMES:
|
|
98
|
+
return True
|
|
99
|
+
return any(name.startswith(p) for p in EXCLUDED_DIR_PREFIXES)
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def looks_like_text(path: Path, sniff_bytes: int = 4096) -> bool:
|
|
103
|
+
"""Cheap binary sniff. We hash anything that has a text-y extension or
|
|
104
|
+
that doesn't trip the null-byte heuristic in its first chunk."""
|
|
105
|
+
if path.suffix.lower() in TEXT_HINT_EXTENSIONS:
|
|
106
|
+
return True
|
|
107
|
+
# No extension or unfamiliar extension — sniff content.
|
|
108
|
+
try:
|
|
109
|
+
with path.open("rb") as f:
|
|
110
|
+
chunk = f.read(sniff_bytes)
|
|
111
|
+
except OSError:
|
|
112
|
+
return False
|
|
113
|
+
if not chunk:
|
|
114
|
+
return True # empty file is fine
|
|
115
|
+
return b"\x00" not in chunk
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def hash_file(path: Path) -> str:
|
|
119
|
+
h = hashlib.sha256()
|
|
120
|
+
with path.open("rb") as f:
|
|
121
|
+
for buf in iter(lambda: f.read(64 * 1024), b""):
|
|
122
|
+
h.update(buf)
|
|
123
|
+
return h.hexdigest()
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def walk_target(target: Path) -> list[Path]:
|
|
127
|
+
out: list[Path] = []
|
|
128
|
+
target = target.resolve()
|
|
129
|
+
for root, dirs, files in os.walk(target):
|
|
130
|
+
# Prune in place — modifying `dirs` skips them entirely.
|
|
131
|
+
dirs[:] = [d for d in dirs if not is_excluded_dir(d)]
|
|
132
|
+
root_path = Path(root)
|
|
133
|
+
for name in files:
|
|
134
|
+
full = root_path / name
|
|
135
|
+
if not full.is_file():
|
|
136
|
+
continue
|
|
137
|
+
try:
|
|
138
|
+
if full.is_symlink():
|
|
139
|
+
continue
|
|
140
|
+
except OSError:
|
|
141
|
+
continue
|
|
142
|
+
out.append(full)
|
|
143
|
+
return sorted(out)
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def load_prior(state_path: Path) -> dict:
|
|
147
|
+
if not state_path.is_file():
|
|
148
|
+
return {"audits": [], "files": {}}
|
|
149
|
+
try:
|
|
150
|
+
return json.loads(state_path.read_text())
|
|
151
|
+
except (OSError, json.JSONDecodeError):
|
|
152
|
+
return {"audits": [], "files": {}}
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def detect_audit_id(archon_dir: Path) -> Optional[tuple[str, list[int]]]:
|
|
156
|
+
"""Pull the most recent audit's id + phase keys from audit-state.json.
|
|
157
|
+
Returns (audit_id, phase_numbers) or None if the file is unreadable.
|
|
158
|
+
"""
|
|
159
|
+
audit_state = archon_dir / "audit-state.json"
|
|
160
|
+
if not audit_state.is_file():
|
|
161
|
+
return None
|
|
162
|
+
try:
|
|
163
|
+
data = json.loads(audit_state.read_text())
|
|
164
|
+
except (OSError, json.JSONDecodeError):
|
|
165
|
+
return None
|
|
166
|
+
audits = data.get("audits") or []
|
|
167
|
+
if not audits:
|
|
168
|
+
return None
|
|
169
|
+
last = audits[-1]
|
|
170
|
+
audit_id = (last.get("audit_id") or "").strip()
|
|
171
|
+
if not audit_id:
|
|
172
|
+
return None
|
|
173
|
+
phase_map = last.get("phases") or {}
|
|
174
|
+
phases: list[int] = []
|
|
175
|
+
for key in phase_map.keys():
|
|
176
|
+
try:
|
|
177
|
+
phases.append(int(key))
|
|
178
|
+
except (TypeError, ValueError):
|
|
179
|
+
continue
|
|
180
|
+
phases.sort()
|
|
181
|
+
return audit_id, phases
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
def stamp(
|
|
185
|
+
target: Path,
|
|
186
|
+
archon_dir: Path,
|
|
187
|
+
audit_id: str,
|
|
188
|
+
phases: list[int],
|
|
189
|
+
max_bytes: int = DEFAULT_MAX_BYTES,
|
|
190
|
+
) -> dict:
|
|
191
|
+
state_path = archon_dir / "file-state.json"
|
|
192
|
+
state = load_prior(state_path)
|
|
193
|
+
files: dict = state.get("files") or {}
|
|
194
|
+
audits_seen: list = state.get("audits") or []
|
|
195
|
+
|
|
196
|
+
target = target.resolve()
|
|
197
|
+
paths = walk_target(target)
|
|
198
|
+
seen_rel: set[str] = set()
|
|
199
|
+
|
|
200
|
+
for full in paths:
|
|
201
|
+
try:
|
|
202
|
+
rel = str(full.relative_to(target))
|
|
203
|
+
except ValueError:
|
|
204
|
+
continue
|
|
205
|
+
seen_rel.add(rel)
|
|
206
|
+
|
|
207
|
+
try:
|
|
208
|
+
stat = full.stat()
|
|
209
|
+
except OSError:
|
|
210
|
+
continue
|
|
211
|
+
|
|
212
|
+
prior = files.get(rel) or {}
|
|
213
|
+
large = stat.st_size > max_bytes
|
|
214
|
+
text = looks_like_text(full) if not large else False
|
|
215
|
+
|
|
216
|
+
record: dict = {
|
|
217
|
+
"size": stat.st_size,
|
|
218
|
+
"last_audit_id": audit_id,
|
|
219
|
+
}
|
|
220
|
+
prior_audits = list(prior.get("last_audits") or [])
|
|
221
|
+
if audit_id not in prior_audits:
|
|
222
|
+
prior_audits.append(audit_id)
|
|
223
|
+
record["last_audits"] = prior_audits[-10:] # cap history per file
|
|
224
|
+
|
|
225
|
+
prior_phases = list(prior.get("last_phases") or [])
|
|
226
|
+
merged_phases = sorted(set(prior_phases) | set(phases))
|
|
227
|
+
record["last_phases"] = merged_phases
|
|
228
|
+
|
|
229
|
+
if large:
|
|
230
|
+
record["large_file"] = True
|
|
231
|
+
# Preserve any prior hash so a follow-up tool can still detect
|
|
232
|
+
# truncation back below the cap.
|
|
233
|
+
if "hash" in prior:
|
|
234
|
+
record["hash"] = prior["hash"]
|
|
235
|
+
elif not text:
|
|
236
|
+
record["binary"] = True
|
|
237
|
+
if "hash" in prior:
|
|
238
|
+
record["hash"] = prior["hash"]
|
|
239
|
+
else:
|
|
240
|
+
try:
|
|
241
|
+
record["hash"] = hash_file(full)
|
|
242
|
+
except OSError as exc:
|
|
243
|
+
record["hash_error"] = str(exc)
|
|
244
|
+
|
|
245
|
+
files[rel] = record
|
|
246
|
+
|
|
247
|
+
# Mark deleted files (present in prior state, missing from this walk).
|
|
248
|
+
for rel, prior in list(files.items()):
|
|
249
|
+
if rel in seen_rel:
|
|
250
|
+
continue
|
|
251
|
+
prior["deleted_in_audit"] = audit_id
|
|
252
|
+
files[rel] = prior
|
|
253
|
+
|
|
254
|
+
if audit_id not in audits_seen:
|
|
255
|
+
audits_seen.append(audit_id)
|
|
256
|
+
|
|
257
|
+
state.update(
|
|
258
|
+
{
|
|
259
|
+
"schema_version": 1,
|
|
260
|
+
"audit_id": audit_id,
|
|
261
|
+
"audits": audits_seen[-25:],
|
|
262
|
+
"stamped_at": datetime.now(timezone.utc).isoformat(),
|
|
263
|
+
"target": str(target),
|
|
264
|
+
"files": files,
|
|
265
|
+
}
|
|
266
|
+
)
|
|
267
|
+
|
|
268
|
+
state_path.parent.mkdir(parents=True, exist_ok=True)
|
|
269
|
+
state_path.write_text(json.dumps(state, indent=2, sort_keys=True) + "\n")
|
|
270
|
+
|
|
271
|
+
counts = {
|
|
272
|
+
"tracked": len(files),
|
|
273
|
+
"with_hash": sum(1 for r in files.values() if r.get("hash")),
|
|
274
|
+
"large_skipped": sum(1 for r in files.values() if r.get("large_file")),
|
|
275
|
+
"binary_skipped": sum(1 for r in files.values() if r.get("binary")),
|
|
276
|
+
"deleted": sum(1 for r in files.values() if r.get("deleted_in_audit")),
|
|
277
|
+
}
|
|
278
|
+
return counts
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
def parse_phases_arg(raw: str) -> list[int]:
|
|
282
|
+
if not raw:
|
|
283
|
+
return []
|
|
284
|
+
out: list[int] = []
|
|
285
|
+
for piece in raw.split(","):
|
|
286
|
+
piece = piece.strip()
|
|
287
|
+
if not piece:
|
|
288
|
+
continue
|
|
289
|
+
try:
|
|
290
|
+
out.append(int(piece))
|
|
291
|
+
except ValueError:
|
|
292
|
+
print(f"warning: ignoring non-integer phase {piece!r}", file=sys.stderr)
|
|
293
|
+
return sorted(set(out))
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
def main() -> None:
|
|
297
|
+
parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
|
|
298
|
+
parser.add_argument("--target", default=".", help="Target repository path (default: cwd)")
|
|
299
|
+
parser.add_argument("--archon-dir", default=None, help="Archon data dir (default: <target>/archon)")
|
|
300
|
+
parser.add_argument("--audit-id", default=None, help="Override the audit id to stamp (default: read from audit-state.json)")
|
|
301
|
+
parser.add_argument("--phases", default=None, help="Comma-separated phase numbers to mark on each file (default: all phases from current audit)")
|
|
302
|
+
parser.add_argument("--max-bytes", type=int, default=DEFAULT_MAX_BYTES, help="Skip hashing for files larger than this many bytes")
|
|
303
|
+
args = parser.parse_args()
|
|
304
|
+
|
|
305
|
+
target = Path(args.target).resolve()
|
|
306
|
+
if not target.is_dir():
|
|
307
|
+
print(f"error: target is not a directory: {target}", file=sys.stderr)
|
|
308
|
+
sys.exit(2)
|
|
309
|
+
|
|
310
|
+
archon_dir = Path(args.archon_dir) if args.archon_dir else target / "archon"
|
|
311
|
+
archon_dir.mkdir(parents=True, exist_ok=True)
|
|
312
|
+
|
|
313
|
+
audit_id = args.audit_id
|
|
314
|
+
phases: list[int] = parse_phases_arg(args.phases or "")
|
|
315
|
+
|
|
316
|
+
if not audit_id or not phases:
|
|
317
|
+
detected = detect_audit_id(archon_dir)
|
|
318
|
+
if detected is None and not audit_id:
|
|
319
|
+
print(
|
|
320
|
+
"error: no audit_id provided and audit-state.json is unreadable",
|
|
321
|
+
file=sys.stderr,
|
|
322
|
+
)
|
|
323
|
+
sys.exit(1)
|
|
324
|
+
if detected is not None:
|
|
325
|
+
det_id, det_phases = detected
|
|
326
|
+
if not audit_id:
|
|
327
|
+
audit_id = det_id
|
|
328
|
+
if not phases:
|
|
329
|
+
phases = det_phases
|
|
330
|
+
|
|
331
|
+
try:
|
|
332
|
+
counts = stamp(target, archon_dir, audit_id, phases, max_bytes=args.max_bytes)
|
|
333
|
+
except OSError as exc:
|
|
334
|
+
print(f"error: I/O failure during stamp: {exc}", file=sys.stderr)
|
|
335
|
+
sys.exit(3)
|
|
336
|
+
|
|
337
|
+
state_path = archon_dir / "file-state.json"
|
|
338
|
+
print(
|
|
339
|
+
f"file-state stamped at {state_path}: "
|
|
340
|
+
f"{counts['tracked']} tracked "
|
|
341
|
+
f"({counts['with_hash']} hashed, "
|
|
342
|
+
f"{counts['large_skipped']} large-skipped, "
|
|
343
|
+
f"{counts['binary_skipped']} binary-skipped, "
|
|
344
|
+
f"{counts['deleted']} marked deleted)"
|
|
345
|
+
)
|
|
346
|
+
|
|
347
|
+
|
|
348
|
+
if __name__ == "__main__":
|
|
349
|
+
main()
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: code-reviewer
|
|
3
|
+
description:
|
|
4
|
+
Use this skill to review code. It supports both local changes (staged or working tree)
|
|
5
|
+
and remote Pull Requests (by ID or URL). It focuses on correctness, maintainability,
|
|
6
|
+
and adherence to project standards.
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Code Reviewer
|
|
10
|
+
|
|
11
|
+
This skill guides the agent in conducting professional and thorough code reviews for both local development and remote Pull Requests.
|
|
12
|
+
|
|
13
|
+
## Workflow
|
|
14
|
+
|
|
15
|
+
### 1. Determine Review Target
|
|
16
|
+
* **Remote PR**: If the user provides a PR number or URL (e.g., "Review PR #123"), target that remote PR.
|
|
17
|
+
* **Local Changes**: If no specific PR is mentioned, or if the user asks to "review my changes", target the current local file system states (staged and unstaged changes).
|
|
18
|
+
|
|
19
|
+
### 2. Preparation
|
|
20
|
+
|
|
21
|
+
#### For Remote PRs:
|
|
22
|
+
1. **Checkout**: Use the GitHub CLI to checkout the PR.
|
|
23
|
+
```bash
|
|
24
|
+
gh pr checkout <PR_NUMBER>
|
|
25
|
+
```
|
|
26
|
+
2. **Preflight**: Execute the project's standard verification suite to catch automated failures early.
|
|
27
|
+
```bash
|
|
28
|
+
npm run preflight
|
|
29
|
+
```
|
|
30
|
+
3. **Context**: Read the PR description and any existing comments to understand the goal and history.
|
|
31
|
+
|
|
32
|
+
#### For Local Changes:
|
|
33
|
+
1. **Identify Changes**:
|
|
34
|
+
* Check status: `git status`
|
|
35
|
+
* Read diffs: `git diff` (working tree) and/or `git diff --staged` (staged).
|
|
36
|
+
2. **Preflight (Optional)**: If the changes are substantial, ask the user if they want to run `npm run preflight` before reviewing.
|
|
37
|
+
|
|
38
|
+
### 3. In-Depth Analysis
|
|
39
|
+
Analyze the code changes based on the following pillars:
|
|
40
|
+
|
|
41
|
+
* **Correctness**: Does the code achieve its stated purpose without bugs or logical errors?
|
|
42
|
+
* **Maintainability**: Is the code clean, well-structured, and easy to understand and modify in the future? Consider factors like code clarity, modularity, and adherence to established design patterns.
|
|
43
|
+
* **Readability**: Is the code well-commented (where necessary) and consistently formatted according to our project's coding style guidelines?
|
|
44
|
+
* **Efficiency**: Are there any obvious performance bottlenecks or resource inefficiencies introduced by the changes?
|
|
45
|
+
* **Security**: Are there any potential security vulnerabilities or insecure coding practices?
|
|
46
|
+
* **Edge Cases and Error Handling**: Does the code appropriately handle edge cases and potential errors?
|
|
47
|
+
* **Testability**: Is the new or modified code adequately covered by tests (even if preflight checks pass)? Suggest additional test cases that would improve coverage or robustness.
|
|
48
|
+
|
|
49
|
+
### 4. Provide Feedback
|
|
50
|
+
|
|
51
|
+
#### Structure
|
|
52
|
+
* **Summary**: A high-level overview of the review.
|
|
53
|
+
* **Findings**:
|
|
54
|
+
* **Critical**: Bugs, security issues, or breaking changes.
|
|
55
|
+
* **Improvements**: Suggestions for better code quality or performance.
|
|
56
|
+
* **Nitpicks**: Formatting or minor style issues (optional).
|
|
57
|
+
* **Conclusion**: Clear recommendation (Approved / Request Changes).
|
|
58
|
+
|
|
59
|
+
#### Tone
|
|
60
|
+
* Be constructive, professional, and friendly.
|
|
61
|
+
* Explain *why* a change is requested.
|
|
62
|
+
* For approvals, acknowledge the specific value of the contribution.
|
|
63
|
+
|
|
64
|
+
### 5. Cleanup (Remote PRs only)
|
|
65
|
+
* After the review, ask the user if they want to switch back to the default branch (e.g., `main` or `master`).
|