@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,279 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Revisit mode (`/piolium-revisit`).
|
|
3
|
+
*
|
|
4
|
+
* Second-or-Nth pass over a completed audit, anti-anchored, reusing durable
|
|
5
|
+
* `piolium/attack-surface/` context (command-defs/revisit.md, archon-audit
|
|
6
|
+
* @ 2026-05-16). Ten phases (byte-identical upstream ids 0–9):
|
|
7
|
+
*
|
|
8
|
+
* 0 Intent Cartography (intent-mapper; soft signal)
|
|
9
|
+
* 1 Deep Probe (fresh, anti-anchored) (probe-lead)
|
|
10
|
+
* 2 Enrichment Re-classify (inline, no agent)
|
|
11
|
+
* 3 Review Chambers (anti-anchored) (review-adjudicator)
|
|
12
|
+
* 4 FP Check (independent-verifier)
|
|
13
|
+
* 5 Variant Analysis (new findings) (variant-scanner)
|
|
14
|
+
* 6 Variant Analysis (round-1 C/H) (variant-scanner)
|
|
15
|
+
* 7 PoC Construction (new findings) (poc-author)
|
|
16
|
+
* 8 Finding Finalization (finding-writer)
|
|
17
|
+
* 9 Final Report Regeneration (report-composer)
|
|
18
|
+
*
|
|
19
|
+
* MVP: each phase is a single agent run with anti-anchoring instructions
|
|
20
|
+
* folded into the task prompt. Phase 0's failure is non-blocking.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
import { existsSync, mkdirSync, readFileSync } from "node:fs";
|
|
24
|
+
import { join } from "node:path";
|
|
25
|
+
import type { AgentRuntimeModel } from "../agent-runner.ts";
|
|
26
|
+
import { loadAgents } from "../agents.ts";
|
|
27
|
+
import {
|
|
28
|
+
type AuditRunState,
|
|
29
|
+
applyPhaseStatus,
|
|
30
|
+
initAudit,
|
|
31
|
+
latestAudit,
|
|
32
|
+
markAuditStatus,
|
|
33
|
+
readAuditState,
|
|
34
|
+
} from "../audit-state.ts";
|
|
35
|
+
import { runCandidateScanAsync } from "../candidate-scan.ts";
|
|
36
|
+
import { listFindingDirs, promoteDraftsByPrefix } from "../findings.ts";
|
|
37
|
+
import { runReconAsync } from "../recon.ts";
|
|
38
|
+
import { type PhaseUiHooks, runAgentPhase } from "./phase-runner.ts";
|
|
39
|
+
|
|
40
|
+
export interface RunRevisitOptions {
|
|
41
|
+
cwd: string;
|
|
42
|
+
signal?: AbortSignal;
|
|
43
|
+
ui?: PhaseUiHooks;
|
|
44
|
+
forceFresh?: boolean;
|
|
45
|
+
agentRuntime?: AgentRuntimeModel;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export interface RunRevisitResult {
|
|
49
|
+
auditId: string;
|
|
50
|
+
status: "complete" | "failed";
|
|
51
|
+
phases: Record<string, "complete" | "failed" | "skipped">;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const ATTACK_SURFACE_DIR = "piolium/attack-surface";
|
|
55
|
+
const REVISIT_TMP_DIR = "piolium/tmp/piolium/revisit";
|
|
56
|
+
export const REVISIT_INTENT_CORPUS = `${ATTACK_SURFACE_DIR}/intent-corpus.json`;
|
|
57
|
+
export const REVISIT_ATTACK_SURFACE_INVENTORY = `${ATTACK_SURFACE_DIR}/revisit-attack-surface-inventory.md`;
|
|
58
|
+
export const REVISIT_PROBE_SUMMARY = `${ATTACK_SURFACE_DIR}/revisit-probe-summary.md`;
|
|
59
|
+
export const REVISIT_CHAMBER_SUMMARY = `${ATTACK_SURFACE_DIR}/revisit-chamber-summary.md`;
|
|
60
|
+
|
|
61
|
+
const REVISIT_PHASES = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"] as const;
|
|
62
|
+
type RevisitPhase = (typeof REVISIT_PHASES)[number];
|
|
63
|
+
|
|
64
|
+
function exists(cwd: string, rel: string): boolean {
|
|
65
|
+
return existsSync(join(cwd, rel));
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function pickResume(cwd: string, force: boolean): AuditRunState | undefined {
|
|
69
|
+
if (force) return undefined;
|
|
70
|
+
const state = readAuditState(cwd).state;
|
|
71
|
+
const audit = state ? latestAudit(state) : undefined;
|
|
72
|
+
if (!audit) return undefined;
|
|
73
|
+
if (audit.mode !== "revisit") return undefined;
|
|
74
|
+
if (audit.status === "complete") return undefined;
|
|
75
|
+
return audit;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const ANTI_ANCHOR_PREAMBLE = [
|
|
79
|
+
"REVISIT MODE — anti-anchoring rules:",
|
|
80
|
+
" - Do NOT reuse prior conclusions. Treat existing findings/ as a NEGATIVE LIST: do not re-surface them.",
|
|
81
|
+
" - Re-derive hypotheses from primary sources (code, advisories, KB) — don't copy from prior reports.",
|
|
82
|
+
" - Severity should be re-assessed independently.",
|
|
83
|
+
].join("\n");
|
|
84
|
+
|
|
85
|
+
function priorFindingNegatives(cwd: string): string {
|
|
86
|
+
const dirs = listFindingDirs(cwd);
|
|
87
|
+
if (dirs.length === 0) return "(no prior findings recorded)";
|
|
88
|
+
return dirs.map((d) => ` - ${d.id} ${d.slug}`).join("\n");
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function buildTask(phase: RevisitPhase, cwd: string): string {
|
|
92
|
+
const negatives = priorFindingNegatives(cwd);
|
|
93
|
+
switch (phase) {
|
|
94
|
+
case "0":
|
|
95
|
+
return [
|
|
96
|
+
"Run Phase 0 (Intent Cartography) of /piolium-revisit.",
|
|
97
|
+
"Scan the target repository for documented security intent: SECURITY.md, README security sections, docs/security/, threat-model files, inline `#security:` / `// security:` pragmas.",
|
|
98
|
+
"Produce a structured corpus with two lists: `intentional_behaviors[]` (project-declared safe-by-design behaviors) and `acknowledged_risks[]` (vuln classes the project explicitly considers in scope).",
|
|
99
|
+
`Output: \`${REVISIT_INTENT_CORPUS}\`. Always overwrite — rebuilt per revisit round so doc changes between rounds are reflected.`,
|
|
100
|
+
"Build the corpus only — no findings cross-check (unlike confirm V1.5).",
|
|
101
|
+
"If the repo has no security docs, write an empty `{intentional_behaviors: [], acknowledged_risks: []}` corpus.",
|
|
102
|
+
"The corpus is a SOFT priority hint — never a gate.",
|
|
103
|
+
].join("\n");
|
|
104
|
+
case "1":
|
|
105
|
+
return [
|
|
106
|
+
ANTI_ANCHOR_PREAMBLE,
|
|
107
|
+
"",
|
|
108
|
+
"Run Phase 1 (Deep Probe — fresh teams, anti-anchored) for /piolium-revisit.",
|
|
109
|
+
"Negative list (do NOT re-surface):",
|
|
110
|
+
negatives,
|
|
111
|
+
`Read the durable prior attack-surface corpus from \`${ATTACK_SURFACE_DIR}/\` (candidates-summary.md, candidates.jsonl, KB, route/authz matrix, source/sink flows, probe summary, cross-service edges).`,
|
|
112
|
+
`Write updated reusable attack-surface inventory to \`${REVISIT_ATTACK_SURFACE_INVENTORY}\`.`,
|
|
113
|
+
`Write the probe execution summary to \`${REVISIT_PROBE_SUMMARY}\`.`,
|
|
114
|
+
`Transient workspace, if needed: ${REVISIT_TMP_DIR}/r1/.`,
|
|
115
|
+
"Drafts: piolium/findings-draft/r1-NNN-<slug>.md",
|
|
116
|
+
].join("\n");
|
|
117
|
+
case "2":
|
|
118
|
+
return "Phase 2 (Enrichment Re-classify) runs inline (no agent).";
|
|
119
|
+
case "3":
|
|
120
|
+
return [
|
|
121
|
+
ANTI_ANCHOR_PREAMBLE,
|
|
122
|
+
"Run Phase 3 (Review Chambers — fresh, anti-anchored) for /piolium-revisit.",
|
|
123
|
+
"Negative list:",
|
|
124
|
+
negatives,
|
|
125
|
+
`Read \`${ATTACK_SURFACE_DIR}/\` first so chamber decisions reference current candidate files, routes, sources, sinks, and trust boundaries.`,
|
|
126
|
+
`Transient workspace: ${REVISIT_TMP_DIR}/r3/`,
|
|
127
|
+
`Durable chamber summary: ${REVISIT_CHAMBER_SUMMARY}`,
|
|
128
|
+
"Surviving drafts: piolium/findings-draft/r3-NNN-<slug>.md",
|
|
129
|
+
].join("\n");
|
|
130
|
+
case "4":
|
|
131
|
+
return [
|
|
132
|
+
ANTI_ANCHOR_PREAMBLE,
|
|
133
|
+
"Run Phase 4 (FP Check) on every revisit-stage draft. Cold re-verification of CRITICAL/HIGH survivors; reject anything weakly grounded.",
|
|
134
|
+
].join("\n");
|
|
135
|
+
case "5":
|
|
136
|
+
return [
|
|
137
|
+
ANTI_ANCHOR_PREAMBLE,
|
|
138
|
+
"Run Phase 5 (Variant Analysis — new round findings). Drafts: piolium/findings-draft/r5-NNN-<slug>.md",
|
|
139
|
+
].join("\n");
|
|
140
|
+
case "6":
|
|
141
|
+
return [
|
|
142
|
+
ANTI_ANCHOR_PREAMBLE,
|
|
143
|
+
"Run Phase 6 (Variant Analysis on Round-1 CRIT/HIGH). Re-hunt variants of the highest-severity round-1 findings with fresh priors. Drafts: piolium/findings-draft/r6-NNN-<slug>.md",
|
|
144
|
+
].join("\n");
|
|
145
|
+
case "7":
|
|
146
|
+
return [
|
|
147
|
+
ANTI_ANCHOR_PREAMBLE,
|
|
148
|
+
"Run Phase 7 (PoC Construction — new findings only). Build PoCs and evidence for revisit-stage findings.",
|
|
149
|
+
].join("\n");
|
|
150
|
+
case "8":
|
|
151
|
+
return [
|
|
152
|
+
ANTI_ANCHOR_PREAMBLE,
|
|
153
|
+
"Run Phase 8 (Finding Finalization) on revisit-stage findings — author report.md per finding.",
|
|
154
|
+
].join("\n");
|
|
155
|
+
case "9":
|
|
156
|
+
return [
|
|
157
|
+
ANTI_ANCHOR_PREAMBLE,
|
|
158
|
+
"Run Phase 9 (Final Report Regeneration). Merge round-1 + round-N findings into piolium/final-audit-report.md and append a `## Discoveries by Round` section describing what each round added.",
|
|
159
|
+
].join("\n");
|
|
160
|
+
default:
|
|
161
|
+
return `Unknown revisit phase: ${phase}`;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function gateFor(phase: RevisitPhase, cwd: string): () => boolean {
|
|
166
|
+
switch (phase) {
|
|
167
|
+
case "0":
|
|
168
|
+
return () => exists(cwd, REVISIT_INTENT_CORPUS);
|
|
169
|
+
case "1":
|
|
170
|
+
return () => exists(cwd, REVISIT_PROBE_SUMMARY);
|
|
171
|
+
case "3":
|
|
172
|
+
return () => exists(cwd, REVISIT_CHAMBER_SUMMARY);
|
|
173
|
+
case "9":
|
|
174
|
+
return () => {
|
|
175
|
+
const path = join(cwd, "piolium/final-audit-report.md");
|
|
176
|
+
if (!existsSync(path)) return false;
|
|
177
|
+
return readFileSync(path, "utf8").includes("Discoveries by Round");
|
|
178
|
+
};
|
|
179
|
+
default:
|
|
180
|
+
return () => true;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
export async function runRevisitAudit(opts: RunRevisitOptions): Promise<RunRevisitResult> {
|
|
185
|
+
const { cwd, signal, ui } = opts;
|
|
186
|
+
ui?.setStatus?.("piolium-revisit", "● preparing recon");
|
|
187
|
+
const recon = await runReconAsync(cwd, { signal });
|
|
188
|
+
mkdirSync(join(cwd, ATTACK_SURFACE_DIR), { recursive: true });
|
|
189
|
+
ui?.setStatus?.("piolium-revisit", "● scanning candidate files");
|
|
190
|
+
const candidateScan = await runCandidateScanAsync(cwd, { signal });
|
|
191
|
+
ui?.notify?.(
|
|
192
|
+
`Candidate scan: ${candidateScan.candidateCount} match(es) across ${candidateScan.candidateFiles} file(s).`,
|
|
193
|
+
"info",
|
|
194
|
+
);
|
|
195
|
+
mkdirSync(join(cwd, REVISIT_TMP_DIR), { recursive: true });
|
|
196
|
+
let audit = pickResume(cwd, opts.forceFresh ?? false);
|
|
197
|
+
if (!audit) {
|
|
198
|
+
audit = await initAudit(cwd, {
|
|
199
|
+
mode: "revisit",
|
|
200
|
+
...(recon.commit ? { commit: recon.commit } : { commit: null }),
|
|
201
|
+
...(recon.branch ? { branch: recon.branch } : { branch: "nogit" }),
|
|
202
|
+
...(recon.repository ? { repository: recon.repository } : {}),
|
|
203
|
+
history_available: recon.historyAvailable,
|
|
204
|
+
agent_sdk: "pi",
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const { agents } = loadAgents({ cwd });
|
|
209
|
+
const phaseAgents: Record<RevisitPhase, ReturnType<typeof agents.get>> = {
|
|
210
|
+
"0": agents.get("intent-mapper"),
|
|
211
|
+
"1": agents.get("probe-lead"),
|
|
212
|
+
"2": undefined,
|
|
213
|
+
"3": agents.get("review-adjudicator"),
|
|
214
|
+
"4": agents.get("independent-verifier"),
|
|
215
|
+
"5": agents.get("variant-scanner"),
|
|
216
|
+
"6": agents.get("variant-scanner"),
|
|
217
|
+
"7": agents.get("poc-author"),
|
|
218
|
+
"8": agents.get("finding-writer"),
|
|
219
|
+
"9": agents.get("report-composer"),
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
let failed = false;
|
|
223
|
+
for (const name of REVISIT_PHASES) {
|
|
224
|
+
// Phase 2 (Enrichment Re-classify) has no upstream agent — it is a
|
|
225
|
+
// deterministic inline reclassification step. Mark it complete.
|
|
226
|
+
if (name === "2") {
|
|
227
|
+
await applyPhaseStatus(cwd, audit, name, { status: "complete" });
|
|
228
|
+
continue;
|
|
229
|
+
}
|
|
230
|
+
try {
|
|
231
|
+
await runAgentPhase({
|
|
232
|
+
cwd,
|
|
233
|
+
audit,
|
|
234
|
+
phaseName: name,
|
|
235
|
+
statusKey: "piolium-revisit",
|
|
236
|
+
statusLabel: `● R${name}`,
|
|
237
|
+
agent: phaseAgents[name],
|
|
238
|
+
missingAgentMessage: `agent missing for revisit phase ${name}`,
|
|
239
|
+
task: buildTask(name, cwd),
|
|
240
|
+
gate: gateFor(name, cwd),
|
|
241
|
+
mode: "revisit",
|
|
242
|
+
ui,
|
|
243
|
+
agentRuntime: opts.agentRuntime,
|
|
244
|
+
...(signal ? { signal } : {}),
|
|
245
|
+
});
|
|
246
|
+
// Promote drafts surfaced in chamber + variant rounds.
|
|
247
|
+
if (name === "3") promoteDraftsByPrefix(cwd, "r3-");
|
|
248
|
+
if (name === "5") promoteDraftsByPrefix(cwd, "r5-");
|
|
249
|
+
if (name === "6") promoteDraftsByPrefix(cwd, "r6-");
|
|
250
|
+
} catch {
|
|
251
|
+
// Phase 0 (intent cartography) is a soft signal — non-blocking.
|
|
252
|
+
if (name === "0") {
|
|
253
|
+
ui?.notify?.(
|
|
254
|
+
"Revisit phase 0 (intent cartography) failed; continuing without the corpus.",
|
|
255
|
+
"warning",
|
|
256
|
+
);
|
|
257
|
+
await applyPhaseStatus(cwd, audit, name, {
|
|
258
|
+
status: "failed",
|
|
259
|
+
error: "policy: skip-and-continue",
|
|
260
|
+
});
|
|
261
|
+
continue;
|
|
262
|
+
}
|
|
263
|
+
failed = true;
|
|
264
|
+
break;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
await markAuditStatus(cwd, audit.audit_id, failed ? "failed" : "complete");
|
|
269
|
+
const fresh =
|
|
270
|
+
readAuditState(cwd).state?.audits.find((a) => a.audit_id === audit.audit_id) ?? audit;
|
|
271
|
+
const phases: Record<string, "complete" | "failed" | "skipped"> = {};
|
|
272
|
+
for (const [name, p] of Object.entries(fresh.phases)) {
|
|
273
|
+
if (p.status === "complete" || p.status === "failed" || p.status === "skipped") {
|
|
274
|
+
phases[name] = p.status;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
ui?.notify?.(failed ? "Revisit failed." : "Revisit complete.", failed ? "error" : "info");
|
|
278
|
+
return { auditId: audit.audit_id, status: failed ? "failed" : "complete", phases };
|
|
279
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mode → phase list registry.
|
|
3
|
+
*
|
|
4
|
+
* Phase keys are byte-identical to upstream archon-audit's
|
|
5
|
+
* `command-defs/*.md` frontmatter `phases[].id`, so `audit-state.json`
|
|
6
|
+
* files written by piolium stay schema-compatible with files produced by
|
|
7
|
+
* the upstream Claude/Codex archon-audit plugins. User-facing output renders
|
|
8
|
+
* these keys as ordered stage labels via phase-labels.ts (which is
|
|
9
|
+
* mode-aware because revisit/longshot/reinvest/diff reuse bare numeric ids).
|
|
10
|
+
*
|
|
11
|
+
* Synced from archon-audit @ 2026-05-16 (commits e40313f / 0ebff33 /
|
|
12
|
+
* c7ab09c): the upstream refactor renumbered every mode and renamed every
|
|
13
|
+
* sub-agent. piolium's older private phase keys (the L#, P#, Q#, V7 family)
|
|
14
|
+
* and their tail cleanup phases were dropped to match upstream exactly;
|
|
15
|
+
* upstream folds
|
|
16
|
+
* file-state stamping + post-audit cleanup inline into each mode's final
|
|
17
|
+
* phase.
|
|
18
|
+
*
|
|
19
|
+
* Source citations (command-defs/*.md):
|
|
20
|
+
* - lite: L1 recon → [L2 secrets ‖ L3 fast SAST]
|
|
21
|
+
* - balanced: B1 → B2 → [B3 ‖ B4] → B5 → B6 intent → B7 PoC → B8 → B9
|
|
22
|
+
* - deep: [D1 ‖ D2] → D3 → D4 → [D5 ‖ D7] → D6 → D8 → D9 → D10 intent
|
|
23
|
+
* → D11 → D12 → D13 → D14
|
|
24
|
+
* - confirm: V1 → V1.5 intent → V2 → V3 → V4 → V5 → V6
|
|
25
|
+
* - revisit: 0 intent → 1 → 2 → 3 → 4 → 5 → 6 → 7 → 8 → 9
|
|
26
|
+
* - merge: M1 → M2 → M3 → M4 → M5 → M6 → M7 (workspace-only)
|
|
27
|
+
* - longshot: 1 enumerate → 2 hunt fan-out → 3 aggregate
|
|
28
|
+
* - reinvest: 1 enumerate → 2 cross-verifier fan-out → 3 consensus
|
|
29
|
+
* - diff: 1 diff-based phase selection (re-runs deep phases)
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
import type { AuditMode } from "./audit-state.ts";
|
|
33
|
+
|
|
34
|
+
export const MODE_PHASES: Record<AuditMode, readonly string[]> = {
|
|
35
|
+
lite: ["L1", "L2", "L3"],
|
|
36
|
+
balanced: ["B1", "B2", "B3", "B4", "B5", "B6", "B7", "B8", "B9"],
|
|
37
|
+
deep: ["D1", "D2", "D3", "D4", "D5", "D6", "D7", "D8", "D9", "D10", "D11", "D12", "D13", "D14"],
|
|
38
|
+
diff: ["1"],
|
|
39
|
+
confirm: ["V1", "V1.5", "V2", "V3", "V4", "V5", "V6"],
|
|
40
|
+
revisit: ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"],
|
|
41
|
+
merge: ["M1", "M2", "M3", "M4", "M5", "M6", "M7"],
|
|
42
|
+
longshot: ["1", "2", "3"],
|
|
43
|
+
reinvest: ["1", "2", "3"],
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
export function phasesFor(mode: AuditMode): readonly string[] {
|
|
47
|
+
return MODE_PHASES[mode];
|
|
48
|
+
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-mode phase display labels.
|
|
3
|
+
*
|
|
4
|
+
* Phase keys are byte-identical to upstream archon-audit's
|
|
5
|
+
* `command-defs/*.md` frontmatter `phases[].id` so `audit-state.json` files
|
|
6
|
+
* stay schema-compatible with the upstream Claude/Codex plugins. Several
|
|
7
|
+
* modes (revisit, longshot, reinvest, diff) reuse bare numeric ids
|
|
8
|
+
* (`"1"`, `"2"`, …), so labels MUST be looked up per mode — a flat map
|
|
9
|
+
* would collide (`longshot:"1"` = "Enumerate Targets" vs
|
|
10
|
+
* `reinvest:"1"` = "Enumerate Findings").
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import type { AuditMode } from "./audit-state.ts";
|
|
14
|
+
|
|
15
|
+
const PHASE_LABELS: Record<AuditMode, Record<string, string>> = {
|
|
16
|
+
lite: {
|
|
17
|
+
L1: "Recon Sweep",
|
|
18
|
+
L2: "Secrets Scan",
|
|
19
|
+
L3: "Fast Code Scan",
|
|
20
|
+
},
|
|
21
|
+
balanced: {
|
|
22
|
+
B1: "Intel Sweep",
|
|
23
|
+
B2: "Threat Model",
|
|
24
|
+
B3: "Code Scan",
|
|
25
|
+
B4: "Targeted Probe",
|
|
26
|
+
B5: "Review Panel",
|
|
27
|
+
B6: "Intent Reconciliation",
|
|
28
|
+
B7: "PoC Authoring",
|
|
29
|
+
B8: "Finding Finalize",
|
|
30
|
+
B9: "Report Compose",
|
|
31
|
+
},
|
|
32
|
+
deep: {
|
|
33
|
+
D1: "Intel Sweep (CVE)",
|
|
34
|
+
D2: "Intel Sweep (History)",
|
|
35
|
+
D3: "Patch Audit",
|
|
36
|
+
D4: "Threat Model",
|
|
37
|
+
D5: "Code Scan",
|
|
38
|
+
D6: "Deep Probe",
|
|
39
|
+
D7: "Access Audit",
|
|
40
|
+
D8: "Taint Trace",
|
|
41
|
+
D9: "Review Panel",
|
|
42
|
+
D10: "Intent Reconciliation",
|
|
43
|
+
D11: "Variant Sweep",
|
|
44
|
+
D12: "PoC Authoring",
|
|
45
|
+
D13: "Finding Finalize",
|
|
46
|
+
D14: "Report Compose",
|
|
47
|
+
},
|
|
48
|
+
confirm: {
|
|
49
|
+
V1: "Findings Inventory",
|
|
50
|
+
"V1.5": "Intent Cross-Check",
|
|
51
|
+
V2: "Environment Discovery",
|
|
52
|
+
V3: "Environment Provisioning",
|
|
53
|
+
V4: "PoC Execution",
|
|
54
|
+
V5: "Test-Based Fallback",
|
|
55
|
+
V6: "Confirmation Report",
|
|
56
|
+
},
|
|
57
|
+
revisit: {
|
|
58
|
+
"0": "Intent Cartography",
|
|
59
|
+
"1": "Deep Probe (fresh, anti-anchored)",
|
|
60
|
+
"2": "Enrichment Re-classify",
|
|
61
|
+
"3": "Review Chambers (fresh, anti-anchored)",
|
|
62
|
+
"4": "FP Check",
|
|
63
|
+
"5": "Variant Analysis (new findings)",
|
|
64
|
+
"6": "Variant Analysis (round-1 CRIT/HIGH)",
|
|
65
|
+
"7": "PoC Construction (new findings)",
|
|
66
|
+
"8": "Finding Finalization",
|
|
67
|
+
"9": "Final Report Regeneration",
|
|
68
|
+
},
|
|
69
|
+
merge: {
|
|
70
|
+
M1: "Validate Every Finding",
|
|
71
|
+
M2: "Semantic Dedup by Root Cause",
|
|
72
|
+
M3: "Auto-Fix Safe Issues",
|
|
73
|
+
M4: "Quarantine Unfixable",
|
|
74
|
+
M5: "Renumber + Rebuild References",
|
|
75
|
+
M6: "Regenerate Summaries",
|
|
76
|
+
M7: "Cleanup + Merge Report",
|
|
77
|
+
},
|
|
78
|
+
longshot: {
|
|
79
|
+
"1": "Enumerate Targets",
|
|
80
|
+
"2": "Hunt (file-by-file fan-out)",
|
|
81
|
+
"3": "Aggregate & Deduplicate",
|
|
82
|
+
},
|
|
83
|
+
reinvest: {
|
|
84
|
+
"1": "Enumerate Findings",
|
|
85
|
+
"2": "Fan Out Wave-Verifier",
|
|
86
|
+
"3": "Consensus Summary",
|
|
87
|
+
},
|
|
88
|
+
diff: {
|
|
89
|
+
"1": "Diff-based Phase Selection",
|
|
90
|
+
},
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
function basePhaseId(phase: string): string {
|
|
94
|
+
return phase.split(":")[0] ?? phase;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function orderNumber(index: number, total: number, minWidth = 1): string {
|
|
98
|
+
const width = Math.max(minWidth, total >= 10 ? 2 : 1);
|
|
99
|
+
return String(index + 1).padStart(width, "0");
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export function phaseLabel(mode: AuditMode, phase: string): string {
|
|
103
|
+
const map = PHASE_LABELS[mode] ?? {};
|
|
104
|
+
return map[phase] ?? map[basePhaseId(phase)] ?? "Phase";
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export function formatPhaseListLabel(
|
|
108
|
+
mode: AuditMode,
|
|
109
|
+
phase: string,
|
|
110
|
+
index: number,
|
|
111
|
+
total: number,
|
|
112
|
+
): string {
|
|
113
|
+
return `${orderNumber(index, total)}. ${phaseLabel(mode, phase)}`;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export function formatPhaseDetailLabel(
|
|
117
|
+
mode: AuditMode,
|
|
118
|
+
phase: string,
|
|
119
|
+
index: number,
|
|
120
|
+
total: number,
|
|
121
|
+
): string {
|
|
122
|
+
return `${formatPhaseListLabel(mode, phase, index, total)} (${phase})`;
|
|
123
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import type { AuditMode, PhaseState, PhaseStatus } from "./audit-state.ts";
|
|
2
|
+
import { formatPhaseListLabel } from "./phase-labels.ts";
|
|
3
|
+
|
|
4
|
+
export interface PhaseStatusTheme {
|
|
5
|
+
fg(color: string, text: string): string;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
function escapeRegExp(value: string): string {
|
|
9
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Find the first phase token present in a status string. The matcher is
|
|
14
|
+
* built from the mode's actual phase list (longest id first so `D14` wins
|
|
15
|
+
* over `D1`, `V1.5` over `V1`), which keeps it correct for the bare numeric
|
|
16
|
+
* ids used by revisit/longshot/reinvest/diff without a hardcoded prefix
|
|
17
|
+
* regex.
|
|
18
|
+
*/
|
|
19
|
+
export function extractStatusPhase(
|
|
20
|
+
text: string | undefined,
|
|
21
|
+
phases: readonly string[],
|
|
22
|
+
): string | undefined {
|
|
23
|
+
if (!text || phases.length === 0) return undefined;
|
|
24
|
+
const ordered = [...phases].sort((a, b) => b.length - a.length).map(escapeRegExp);
|
|
25
|
+
const matcher = new RegExp(`(?<![\\w.])(${ordered.join("|")})(?![\\w.])`, "g");
|
|
26
|
+
for (const match of text.matchAll(matcher)) {
|
|
27
|
+
const phase = match[1];
|
|
28
|
+
if (phase && phases.includes(phase)) return phase;
|
|
29
|
+
}
|
|
30
|
+
return undefined;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function renderPhaseStatusStrip(
|
|
34
|
+
mode: AuditMode,
|
|
35
|
+
phases: readonly string[],
|
|
36
|
+
phaseStates: Record<string, PhaseState>,
|
|
37
|
+
currentPhase: string | undefined,
|
|
38
|
+
theme: PhaseStatusTheme,
|
|
39
|
+
): string {
|
|
40
|
+
void mode;
|
|
41
|
+
return phases
|
|
42
|
+
.map((phase) => {
|
|
43
|
+
const status = phaseStates[phase]?.status ?? "pending";
|
|
44
|
+
return formatPhaseToken(phase, status, currentPhase === phase, theme);
|
|
45
|
+
})
|
|
46
|
+
.join(" ");
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function renderPhaseStatusList(
|
|
50
|
+
mode: AuditMode,
|
|
51
|
+
phases: readonly string[],
|
|
52
|
+
phaseStates: Record<string, PhaseState>,
|
|
53
|
+
currentPhase: string | undefined,
|
|
54
|
+
theme: PhaseStatusTheme,
|
|
55
|
+
): string[] {
|
|
56
|
+
return phases.map((phase, index) => {
|
|
57
|
+
const status = phaseStates[phase]?.status ?? "pending";
|
|
58
|
+
const marker = phaseMarker(status, currentPhase === phase);
|
|
59
|
+
const label = formatPhaseListLabel(mode, phase, index, phases.length);
|
|
60
|
+
const text = `• ${marker} ${label}`;
|
|
61
|
+
return theme.fg(phaseColor(status, currentPhase === phase), text);
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function formatPhaseToken(
|
|
66
|
+
phase: string,
|
|
67
|
+
status: PhaseStatus,
|
|
68
|
+
isCurrent: boolean,
|
|
69
|
+
theme: PhaseStatusTheme,
|
|
70
|
+
): string {
|
|
71
|
+
if (status === "complete") return theme.fg("success", `✓${phase}`);
|
|
72
|
+
if (status === "failed") return theme.fg("error", `✗${phase}`);
|
|
73
|
+
if (status === "skipped") return theme.fg("warning", `↷${phase}`);
|
|
74
|
+
if (status === "in_progress" || isCurrent) return theme.fg("accent", `●${phase}`);
|
|
75
|
+
return theme.fg("dim", `·${phase}`);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function phaseMarker(status: PhaseStatus, isCurrent: boolean): string {
|
|
79
|
+
if (status === "complete") return "✓";
|
|
80
|
+
if (status === "failed") return "✗";
|
|
81
|
+
if (status === "skipped") return "↷";
|
|
82
|
+
if (status === "in_progress" || isCurrent) return "●";
|
|
83
|
+
return "·";
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function phaseColor(status: PhaseStatus, isCurrent: boolean): string {
|
|
87
|
+
if (status === "complete") return "success";
|
|
88
|
+
if (status === "failed") return "error";
|
|
89
|
+
if (status === "skipped") return "warning";
|
|
90
|
+
if (status === "in_progress" || isCurrent) return "accent";
|
|
91
|
+
return "dim";
|
|
92
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { CustomEditor, type KeybindingsManager, type Theme } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import { type EditorTheme, type TUI, truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
|
|
3
|
+
|
|
4
|
+
export const ARCHON_SRCERY_THEME_NAME = "piolium-srcery";
|
|
5
|
+
export const ARCHON_PROMPT_PREFIX = "piolium-audit ▶ ";
|
|
6
|
+
export const ARCHON_PROMPT_PREFIX_GREEN = "\x1b[38;2;152;188;55m";
|
|
7
|
+
const RESET_FOREGROUND = "\x1b[39m";
|
|
8
|
+
|
|
9
|
+
export function shouldUseArchonPromptPrefix(theme: Pick<Theme, "name">): boolean {
|
|
10
|
+
return theme.name === ARCHON_SRCERY_THEME_NAME;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export class ArchonPromptPrefixEditor extends CustomEditor {
|
|
14
|
+
private readonly prefixWidth = visibleWidth(ARCHON_PROMPT_PREFIX);
|
|
15
|
+
|
|
16
|
+
constructor(tui: TUI, theme: EditorTheme, keybindings: KeybindingsManager) {
|
|
17
|
+
super(tui, theme, keybindings, { paddingX: visibleWidth(ARCHON_PROMPT_PREFIX) });
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
override render(width: number): string[] {
|
|
21
|
+
const lines = super.render(width);
|
|
22
|
+
if (lines.length < 3) return lines;
|
|
23
|
+
|
|
24
|
+
const renderedPrefixWidth = Math.min(this.prefixWidth, Math.max(0, Math.floor((width - 1) / 2)));
|
|
25
|
+
if (renderedPrefixWidth <= 0) return lines;
|
|
26
|
+
|
|
27
|
+
const plainPrefix = truncateToWidth(ARCHON_PROMPT_PREFIX, renderedPrefixWidth, "");
|
|
28
|
+
if (!plainPrefix) return lines;
|
|
29
|
+
|
|
30
|
+
const expectedPadding = " ".repeat(renderedPrefixWidth);
|
|
31
|
+
const promptLine = lines[1] ?? "";
|
|
32
|
+
const styledPrefix = `${ARCHON_PROMPT_PREFIX_GREEN}${plainPrefix}${RESET_FOREGROUND}`;
|
|
33
|
+
lines[1] = promptLine.startsWith(expectedPadding)
|
|
34
|
+
? `${styledPrefix}${promptLine.slice(renderedPrefixWidth)}`
|
|
35
|
+
: truncateToWidth(`${styledPrefix}${promptLine}`, width);
|
|
36
|
+
|
|
37
|
+
return lines;
|
|
38
|
+
}
|
|
39
|
+
}
|