#![forbid(unsafe_code)]
pub mod lang;
pub mod runtime;
pub mod symbols;
pub use runtime::{crash_marker, SmokeInvocation};
pub use symbols::{defined_symbols, expected_symbols};
use perspt_sdk::{
AgentDomainPackage, CorrectionDirection, DomainDetection, DomainId, DomainScope,
EnergyComponent, EnergyModel, ResidualClass, ResidualEvent, ResidualSchema, ResidualWeight,
StabilityClaim, WorkspaceSnapshot,
};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CodingLanguage {
Rust,
Python,
TypeScript,
}
#[derive(Debug, Clone, Default)]
pub struct CodingDomain;
impl CodingDomain {
pub fn new() -> Self {
Self
}
fn classes() -> Vec<ResidualClass> {
vec![
ResidualClass::Syntax,
ResidualClass::Type,
ResidualClass::Build,
ResidualClass::TestFailure,
ResidualClass::Lint,
ResidualClass::Format,
ResidualClass::Runtime,
ResidualClass::Dependency,
ResidualClass::Manifest,
ResidualClass::ImportGraph,
ResidualClass::SymbolMismatch,
ResidualClass::InterfaceMismatch,
ResidualClass::OwnershipViolation,
ResidualClass::Policy,
ResidualClass::Regression,
ResidualClass::SensorUnavailable,
ResidualClass::ToolFailure,
ResidualClass::SheafInconsistency,
]
}
}
impl AgentDomainPackage for CodingDomain {
fn domain_id(&self) -> DomainId {
DomainId::new("coding")
}
fn detect(&self, workspace: &WorkspaceSnapshot) -> DomainDetection {
let mut evidence = Vec::new();
for marker in ["Cargo.toml", "pyproject.toml", "package.json", "go.mod"] {
if workspace.has_file_named(marker) {
evidence.push(format!("found {marker}"));
}
}
let activated = !evidence.is_empty();
DomainDetection {
domain: self.domain_id(),
activated,
confidence: if activated { 0.95 } else { 0.0 },
evidence,
}
}
fn residual_schema(&self, _scope: &DomainScope) -> ResidualSchema {
ResidualSchema::new(Self::classes())
}
fn energy_model(&self, scope: &DomainScope) -> EnergyModel {
use EnergyComponent::*;
use ResidualClass::*;
let weights = vec![
ResidualWeight::new(Syntax, Syn, 4.0).with_hard_threshold(0.0),
ResidualWeight::new(Type, Syn, 3.0).with_hard_threshold(0.0),
ResidualWeight::new(Build, Syn, 3.0).with_hard_threshold(0.0),
ResidualWeight::new(ImportGraph, Str, 2.0),
ResidualWeight::new(SymbolMismatch, Str, 2.0),
ResidualWeight::new(InterfaceMismatch, Str, 2.5),
ResidualWeight::new(OwnershipViolation, Str, 2.0),
ResidualWeight::new(Policy, Str, 1.0),
ResidualWeight::new(Dependency, Str, 1.5),
ResidualWeight::new(Manifest, Str, 1.5),
ResidualWeight::new(Lint, Str, 0.5),
ResidualWeight::new(Format, Str, 0.25),
ResidualWeight::new(TestFailure, Log, 2.0),
ResidualWeight::new(Runtime, Log, 2.0),
ResidualWeight::new(Regression, Log, 3.0),
ResidualWeight::new(SensorUnavailable, Boot, 1.0),
ResidualWeight::new(ToolFailure, Boot, 1.0),
ResidualWeight::new(SheafInconsistency, Sheaf, 2.0),
];
let mut model = EnergyModel::new("coding", 0.5).with_correction_budget(4);
model.residual_weights = weights;
model.energy_tolerance = 0.0;
model.stability_claim = Some(StabilityClaim::not_claimed(format!(
"coding scope: {}",
scope.label
)));
model
}
fn correction_directions(&self, residuals: &[ResidualEvent]) -> Vec<CorrectionDirection> {
let mut directions = Vec::new();
for r in residuals {
if let Some(d) = correction_for(r) {
directions.push(d);
}
}
directions
}
}
fn correction_for(residual: &ResidualEvent) -> Option<CorrectionDirection> {
match residual.class {
ResidualClass::ImportGraph => {
let symbol = residual
.affected_symbols
.first()
.map(|s| s.name.clone())
.unwrap_or_else(|| "the missing item".to_string());
Some(
CorrectionDirection::new(
ResidualClass::ImportGraph,
format!(
"resolve the unresolved import for `{symbol}`: add the missing `use` path \
or declare the missing `mod`, do not regenerate unrelated code"
),
)
.with_paths(residual.affected_paths.clone())
.with_rationale(
"unresolved imports are structural; the fix is an import/module \
declaration, not a behavioral rewrite",
),
)
}
ResidualClass::Type => Some(
CorrectionDirection::new(
ResidualClass::Type,
"reconcile the type mismatch at the reported span; adjust the expression or the \
declared signature, keeping the public interface stable",
)
.with_paths(residual.affected_paths.clone()),
),
ResidualClass::Dependency | ResidualClass::Manifest => Some(
CorrectionDirection::new(
residual.class,
"repair the dependency/manifest: add or pin the missing crate/package and sync \
the lockfile through an approved dependency-mutation effect",
)
.with_paths(residual.affected_paths.clone()),
),
ResidualClass::TestFailure => Some(
CorrectionDirection::new(
ResidualClass::TestFailure,
"address the failing test by fixing the implementation it attributes to; do not \
weaken or delete the assertion",
)
.with_paths(residual.affected_paths.clone()),
),
ResidualClass::SensorUnavailable | ResidualClass::ToolFailure => None,
_ => None,
}
}
#[cfg(test)]
mod tests {
use super::*;
use perspt_sdk::{score_candidate, IndependenceRoute, ResidualSeverity, SensorRef, SymbolRef};
fn lsp_import_residual() -> ResidualEvent {
let mut r = ResidualEvent::new(
"n1",
0,
ResidualClass::ImportGraph,
ResidualSeverity::Error,
1.0,
SensorRef::new("rust-analyzer", IndependenceRoute::Lsp),
)
.unwrap();
r.affected_symbols = vec![SymbolRef {
name: "Bar".into(),
container: Some("crate::foo".into()),
}];
r.affected_paths = vec!["src/main.rs".into()];
r
}
#[test]
fn detects_coding_domain_from_cargo_toml() {
let domain = CodingDomain::new();
let ws = WorkspaceSnapshot::new("/repo", vec!["Cargo.toml".into(), "src/main.rs".into()]);
let detection = domain.detect(&ws);
assert!(detection.activated);
assert_eq!(detection.domain, DomainId::new("coding"));
}
#[test]
fn energy_model_validates_and_is_measured_only() {
let domain = CodingDomain::new();
let model = domain.energy_model(&DomainScope::default());
assert!(model.validate().is_ok());
let claim = model.stability_claim.unwrap();
assert!(
!claim.claims_floor(),
"coding domain must remain NotClaimed"
);
}
#[test]
fn rust_unresolved_import_yields_import_direction_not_retry() {
let domain = CodingDomain::new();
let directions = domain.correction_directions(&[lsp_import_residual()]);
assert_eq!(directions.len(), 1);
assert_eq!(directions[0].addresses, ResidualClass::ImportGraph);
assert!(directions[0].instruction.contains("Bar"));
assert!(directions[0].instruction.contains("use"));
}
#[test]
fn degraded_sensor_has_no_correction_direction() {
let domain = CodingDomain::new();
let r = ResidualEvent::new(
"n1",
0,
ResidualClass::SensorUnavailable,
ResidualSeverity::Blocking,
1.0,
SensorRef::new("cargo", IndependenceRoute::DeterministicTool),
)
.unwrap();
assert!(domain.correction_directions(&[r]).is_empty());
}
#[test]
fn coding_energy_model_scores_real_residuals() {
let domain = CodingDomain::new();
let model = domain.energy_model(&DomainScope::default());
let score = score_candidate(&model, &[lsp_import_residual()]).unwrap();
assert_eq!(score.total, 2.0);
assert_eq!(score.components.v_str, 2.0);
}
}