stemedb/applications/aphoria/src/cli/mod.rs
jml 200b84751e feat: add claims search, promote, stats commands and convergence engine
Adds three new Aphoria CLI commands and supporting infrastructure for
org-pattern alignment and claim tier advancement:

- `aphoria claims search` — find claims by concept pattern, predicate,
  category, or max authority tier (works local and hosted mode)
- `aphoria claims promote` — raise a claim to a higher authority tier by
  creating a superseding claim (append-only; original marked Deprecated)
- `aphoria claims stats` — breakdown of claim counts by tier and status
  for a given concept_path + predicate pair

New modules:
- `convergence.rs` — pure engine comparing local scan observations to
  remote org claims, producing `ConvergenceSuggestion`s at read time
- `types/convergence.rs` — `ConvergenceSuggestion` type with severity
  derived from the driving claim's authority tier
- `types/promotion.rs` — `PromotionRequest` / `PromotionResult` types
- `handlers/promote.rs` — promotion handler; validates tier ordering

Remote client: adds `search_claims` and `claim_stats` methods to
`RemoteClaimStore`, wiring hosted mode for all three new commands.

API (`stemedb-api`): new `/v1/claims/search` and `/v1/claims/stats`
endpoints with DTOs, plus report formatters (JSON/Markdown/SARIF/table)
for search and stats output.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-25 08:21:37 +00:00

526 lines
14 KiB
Rust

//! CLI argument definitions for Aphoria
//!
//! This module is split into submodules to keep file sizes manageable:
//! - `extractors`: Extractor and shadow mode commands
//! - `governance`: Governance and Audit commands
//! - `lifecycle`: Lifecycle and Migration commands
//! - `patterns`: Pattern and Eval commands
//! - `scope`: Scope commands
mod claims;
mod extractors;
mod governance;
mod lifecycle;
mod patterns;
mod scope;
mod verify;
pub use claims::ClaimsCommands;
pub use extractors::ExtractorCommands;
pub use governance::{AuditCommands, GovernanceCommands};
pub use lifecycle::{LifecycleCommands, MigrationCommands};
pub use patterns::{EvalCommands, PatternCommands};
pub use scope::ScopeCommands;
pub use verify::VerifyCommands;
use std::path::PathBuf;
use clap::{Parser, Subcommand};
/// A code-level truth linter powered by Episteme.
///
/// Aphoria scans a codebase, extracts the decisions embedded in config and code,
/// and checks them against authoritative sources. It finds the places where what
/// your code *does* contradicts what the specs *say*.
#[derive(Parser)]
#[command(name = "aphoria")]
#[command(version, about, long_about = None)]
#[command(
after_help = "Examples:\n aphoria scan Scan current directory\n aphoria scan --format sarif Output for IDE integration\n aphoria scan --strict Stricter conflict thresholds\n aphoria verify run Check code against claims\n aphoria coverage Show claim density per module\n aphoria explain Onboarding summary"
)]
pub struct Cli {
/// Path to aphoria.toml configuration file
#[arg(short, long, global = true)]
pub config: Option<PathBuf>,
/// Enable verbose logging (shows internal tracing output)
#[arg(short, long, global = true)]
pub verbose: bool,
#[command(subcommand)]
pub command: Commands,
}
#[derive(Subcommand)]
pub enum Commands {
/// Scan a project for epistemic drift
Scan {
/// Path to the project root to scan
#[arg(default_value = ".")]
path: PathBuf,
/// Output format: table, json, sarif, markdown
#[arg(short, long, default_value = "table")]
format: String,
/// Exit with non-zero code if conflicts found
#[arg(long)]
exit_code: bool,
/// Use stricter thresholds (FLAG at 0.3, BLOCK at 0.5)
#[arg(long)]
strict: bool,
/// Persist claims to Episteme storage (enables diff/baseline features).
/// Without this flag, scans are ephemeral and fast.
#[arg(long)]
persist: bool,
/// Enable debug output showing conflict resolution traces.
#[arg(long)]
debug: bool,
/// Enable write-back of observations to local Episteme (requires --persist).
#[arg(long)]
sync: bool,
/// Scan only git-staged files (for pre-commit hooks).
#[arg(long)]
staged: bool,
/// Preview what would be shared with the community corpus.
#[arg(long)]
community_preview: bool,
/// Run performance benchmark with timing breakdown.
#[arg(long)]
benchmark: bool,
/// Show all extracted claims in the output
#[arg(long)]
show_claims: bool,
/// Show all observations with concept paths (for debugging extractor alignment)
#[arg(long)]
show_observations: bool,
/// Show detailed authority tier breakdown for conflicts
#[arg(long)]
explain_authority: bool,
/// Fetch remote org claims and show where your code diverges from org patterns.
/// Requires hosted mode configuration (aphoria.toml with [hosted] section).
#[arg(long)]
suggest_convergence: bool,
},
/// Manage acknowledgments (mark conflicts as intentional)
Ack {
#[command(subcommand)]
command: AckCommands,
},
/// Bless a code pattern as the authoritative standard
Bless {
/// The concept path to bless
concept_path: String,
/// The predicate (e.g., "enabled", "min_version")
#[arg(short, long)]
predicate: String,
/// The value (e.g., "true", "1.2")
#[arg(short = 'V', long)]
value: String,
/// Reason/description for this standard
#[arg(short, long)]
reason: String,
},
/// Record an intentional configuration change as a policy update
Update {
/// The concept path being updated
concept_path: String,
/// The new value for this concept
value: String,
/// Reason for the update
#[arg(short, long)]
reason: String,
},
/// Set the current scan as the baseline
Baseline,
/// Show changes since last baseline
Diff,
/// Show current scan status
Status,
/// Initialize Aphoria with authoritative corpus
Init,
/// Manage the authoritative corpus
Corpus {
#[command(subcommand)]
command: CorpusCommands,
},
/// Manage the research agent for filling corpus gaps
Research {
#[command(subcommand)]
command: ResearchCommands,
},
/// Manage federated policies (Trust Packs)
Policy {
#[command(subcommand)]
command: PolicyCommands,
},
/// Manage learned patterns and extractor promotion
Extractors {
#[command(subcommand)]
command: ExtractorCommands,
},
/// Evaluate LLM prompt effectiveness
Eval {
#[command(subcommand)]
command: EvalCommands,
},
/// Manage cross-project pattern learning
Patterns {
#[command(subcommand)]
command: PatternCommands,
},
/// Manage knowledge scopes and inheritance
Scope {
#[command(subcommand)]
command: ScopeCommands,
},
/// Manage knowledge lifecycle (deprecation, archival)
Lifecycle {
#[command(subcommand)]
command: LifecycleCommands,
},
/// Track migration progress for deprecated patterns
Migrations {
#[command(subcommand)]
command: MigrationCommands,
},
/// Manage approval workflows for pattern promotion
Governance {
#[command(subcommand)]
command: GovernanceCommands,
},
/// View and export audit trails for compliance
Audit {
#[command(subcommand)]
command: AuditCommands,
},
/// Manage human-authored claims (create, list, explain, update, supersede, deprecate)
Claims {
#[command(subcommand)]
command: ClaimsCommands,
},
/// Verify code against authored claims
Verify {
#[command(subcommand)]
command: VerifyCommands,
},
/// Show claim coverage metrics per module
Coverage {
/// Path to the project root
#[arg(default_value = ".")]
path: PathBuf,
/// Output format: table, json, markdown
#[arg(short, long, default_value = "table")]
format: String,
/// Sort modules by: name, density, unclaimed, observations
#[arg(long, default_value = "name")]
sort_by: String,
},
/// Generate a narrative explanation of this project's claims (onboarding)
Explain {
/// Path to the project root
#[arg(default_value = ".")]
path: PathBuf,
/// Write output to a file instead of stdout
#[arg(short, long)]
output: Option<PathBuf>,
/// Output format: markdown or json
#[arg(long, default_value = "markdown")]
format: String,
},
/// Generate enhanced documentation from claims + verification
Docs {
#[command(subcommand)]
command: DocsCommands,
},
/// Manage curated Trust Packs (install, list)
TrustPack {
#[command(subcommand)]
command: TrustPackCommands,
},
/// Install Claude Code skills for Aphoria workflows
InstallClaude {
/// Preview what would be installed without copying
#[arg(long)]
dry_run: bool,
/// Force reinstall even if files exist
#[arg(short, long)]
force: bool,
},
}
#[derive(Subcommand)]
pub enum TrustPackCommands {
/// Install a curated Trust Pack by name
Install {
/// Pack name (e.g., "security-hardening", "rfc-compliance", "owasp-top10")
name: String,
/// Custom registry URL (overrides built-in registry)
#[arg(long)]
registry: Option<String>,
},
/// List available curated Trust Packs
List,
}
#[derive(Subcommand)]
pub enum DocsCommands {
/// Generate a claims overview document
Generate {
/// Path to the project root
#[arg(default_value = ".")]
path: PathBuf,
/// Output path (default: stdout)
#[arg(short, long)]
output: Option<PathBuf>,
/// Output format: markdown or json
#[arg(long, default_value = "markdown")]
format: String,
},
}
#[derive(Subcommand)]
pub enum AckCommands {
/// Create a new acknowledgment for a conflict
Add {
/// The concept path to acknowledge
concept_path: String,
/// Reason for acknowledgment
#[arg(short, long)]
reason: String,
/// Optional expiry for acknowledgment (e.g., "90d" or "2026-12-31")
#[arg(long, alias = "expires-at")]
expires: Option<String>,
},
/// Export acknowledgments to .aphoria/acks.toml for version control
Export {
/// Output path (default: .aphoria/acks.toml)
#[arg(short, long)]
output: Option<PathBuf>,
},
/// Import acknowledgments from .aphoria/acks.toml
Import {
/// Input path (default: .aphoria/acks.toml)
#[arg(short, long)]
input: Option<PathBuf>,
},
}
#[derive(Subcommand)]
pub enum CorpusCommands {
/// Build the authoritative corpus from configured sources
Build {
/// Only include specific sources (comma-separated)
#[arg(long)]
only: Option<String>,
/// Run in offline mode (skip sources requiring network)
#[arg(long)]
offline: bool,
/// Clear cache before building
#[arg(long)]
clear_cache: bool,
},
/// List available corpus sources
List,
/// Import patterns from external sources to bootstrap the corpus
Import {
#[command(subcommand)]
source: ImportSource,
},
/// Export the corpus as a signed Trust Pack
ExportPack {
/// Name for the exported pack
#[arg(long)]
name: String,
/// Output path for the .pack file
#[arg(short, long)]
output: PathBuf,
/// Only include specific corpus sources (comma-separated)
#[arg(long)]
only: Option<String>,
/// Run in offline mode (skip sources requiring network)
#[arg(long)]
offline: bool,
},
/// Create a new corpus item from structured data
Create {
/// Subject path (e.g., "ml/dependencies/basicsr/torchvision")
#[arg(long)]
subject: String,
/// Predicate (e.g., "incompatible_with", "requires", "recommends")
#[arg(long)]
predicate: String,
/// Value (string, number, or boolean)
#[arg(long)]
value: String,
/// Full explanation/context for this claim
#[arg(long)]
explanation: String,
/// Authority source (GitHub URL, paper citation, docs URL)
#[arg(long)]
authority: String,
/// Category (compatibility, performance, security, architecture)
#[arg(long)]
category: String,
/// Authority tier (0=regulatory, 1=clinical, 2=observational, 3=community)
#[arg(long)]
tier: u8,
},
}
#[derive(Subcommand)]
pub enum ImportSource {
/// Import patterns from wiki markdown documentation
Wiki {
/// Path to wiki directory containing markdown files
path: PathBuf,
},
}
#[derive(Subcommand)]
pub enum ResearchCommands {
/// Run the research agent to fill corpus gaps
Run {
/// Minimum projects that must report a gap before researching
#[arg(short, long, default_value = "3")]
threshold: u32,
/// Use strict quality validation
#[arg(long)]
strict: bool,
/// Prune old gaps before researching
#[arg(long)]
prune: bool,
/// Maximum age of gaps to consider in days
#[arg(long, default_value = "90")]
max_age: u64,
},
/// Show research agent status and gap statistics
Status,
/// List gaps eligible for research
Gaps {
/// Minimum projects that must report a gap
#[arg(short, long, default_value = "1")]
threshold: u32,
/// Show only gaps ready for research (seen in 3+ projects)
#[arg(long)]
ready: bool,
},
}
#[derive(Subcommand)]
pub enum PolicyCommands {
/// Export acknowledged conflicts as a Trust Pack
Export {
/// Name of the policy pack
#[arg(long)]
name: String,
/// Output path for the pack file
#[arg(short, long)]
output: PathBuf,
},
/// Import a Trust Pack into the local Episteme
Import {
/// Path to the .pack file
file: PathBuf,
},
/// Re-sign a Trust Pack with a new key
Resign {
/// Path to the .pack file to re-sign
file: PathBuf,
/// Output path for the re-signed pack
#[arg(short, long)]
output: PathBuf,
/// Path to new signing key
#[arg(long)]
key: Option<PathBuf>,
/// Reason for re-signing (for audit trail)
#[arg(long)]
reason: Option<String>,
/// Preserve signature chain for audit trail
#[arg(long, default_value = "true")]
chain_signatures: bool,
},
}