Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletions .trinity/seals/loop_TriLoopLesson.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
{
"built_by": "t27c-bootstrap@0.5.2+0fba674ef",
"closure": [
{
"spec": "specs/claw/text.t27",
"spec_hash": "sha256:968755ec1917f766eb2cdd0ab4c0c48aef028a0e7d69e191911695e925b286ad"
},
{
"spec": "specs/tri/lab/receipt.t27",
"spec_hash": "sha256:00d9eec8d7be84dde596c9990b3a5574de24aff9f7a94579fc620eb585712441"
}
],
"config": "gen=c,rust,verilog,zig;tests=zig",
"gen_hash_c": "sha256:b43f8e5932e50e39962700c70d21c804ecac1296da76238bc38a6d908d1442be",
"gen_hash_rust": "sha256:5b03509aa928f0f6ae57f16317efdbab2ce35120e04299cc7c68ab01c7628a95",
"gen_hash_verilog": "sha256:3d1754b8cb60953c8f360d30ccef8482f51efa3e495137efeab74f35945fd6c0",
"gen_hash_zig": "sha256:3351777f5fc3125f092cf3882414ee7a46582ca37ea287cecbe72c77b27f58e0",
"module": "TriLoopLesson",
"ring": 12,
"seal_schema": 2,
"sealed_at": "2026-10-19T16:23:39Z",
"sealed_by": "t27c-bootstrap@0.5.2",
"spec_hash": "sha256:9964c79e37666e3673d2d32b4d98bb0a4b2d9639f5ff3a9d8372f4f4fa1cadcb",
"spec_path": "specs/tri/loop/lesson.t27",
"tests": {
"failed": 0,
"failing": [],
"forced": false,
"passed": 21,
"total": 21,
"vacuous": 0
},
"toolchain": {
"test_runner": "sha256:8512b80da533b79289110fca4a65f3336abd7a059ca4b72ea595d5e33ff8ad10",
"zig": "0.16.0"
}
}
67 changes: 0 additions & 67 deletions bootstrap/src/suite.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1025,73 +1025,6 @@ fn cmd_verilog_no_double_percent(repo: &Path, rel: &str) -> anyhow::Result<()> {
)
}

/// W647: the OUTPUT stratum. T57 was a defect that is well-formed Rust,
/// well-formed Verilog, compiles and runs in both, and is wrong only when a
/// human reads what it printed. Static checks stratify -- shape, type, output --
/// and every gate built in this session lives in the first two.
///
/// This runs `iverilog` + `vvp` on the generated testbench and reads the
/// printed lines, asserting each `[TEST]`/`[BENCH]` line is well formed: no
/// unconsumed format directive, and a recognised verdict token.
///
/// **Bounded and named, not total** (T55): running the corpus through a
/// simulator costs 10-20 minutes against the gate's 5, so this walks the specs
/// passed to it and the caller decides which. Its coverage claim is exactly
/// "the specs it was given", stated rather than implied.
fn cmd_simulated_output_wellformed(repo: &Path, rel: &str) -> anyhow::Result<()> {
let exe_ok = |p: &str| {
Command::new(p)
.arg("-V")
.stdout(std::process::Stdio::null())
.stderr(std::process::Stdio::null())
.status()
.map(|s| s.success())
.unwrap_or(false)
};
if !exe_ok("iverilog") {
// T51: a phase that did not run must not be reported as one that ran
// clean. The caller prints SKIPPED for this.
return Ok(());
}
let src = fs::read_to_string(repo.join(rel))
.with_context(|| format!("reading {}", rel))?;
let v = match crate::compiler::Compiler::compile_verilog_for_simulation(&src) {
Err(_) => return Ok(()),
Ok(v) => v,
};
let dir = std::env::temp_dir().join(format!("t27_outstratum_{}", std::process::id()));
fs::create_dir_all(&dir)?;
let vpath = dir.join("tb.v");
let opath = dir.join("tb.out");
fs::write(&vpath, &v)?;
let build = Command::new("iverilog")
.args(["-g2012", "-o"])
.arg(&opath)
.arg(&vpath)
.output()?;
if !build.status.success() {
// iverilog rejection is the Icarus phase's business, not this one.
return Ok(());
}
let run = Command::new("vvp").arg(&opath).output()?;
let text = String::from_utf8_lossy(&run.stdout);
let mut bad: Vec<String> = Vec::new();
for line in text.lines() {
if !(line.contains("[TEST]") || line.contains("[BENCH]")) {
continue;
}
// An unconsumed directive means the value was never substituted.
if line.contains('%') {
bad.push(format!("unformatted directive survives to output: {}", line.trim()));
}
}
let _ = fs::remove_dir_all(&dir);
if bad.is_empty() {
return Ok(());
}
anyhow::bail!("simulated output is malformed: {}", bad.join("; "))
}

fn cmd_parse(repo: &Path, rel: &str) -> anyhow::Result<()> {
let exe = t27c_exe()?;
let st = Command::new(&exe)
Expand Down
38 changes: 38 additions & 0 deletions cli/tri/src/loopclaim.rs
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,44 @@ fn claim(name: &str, release: bool, who: bool) -> Result<()> {
}
}

// `tri learn add` (#11060): every decision is specs/tri/loop/lesson.t27 (t27c gen-rust); this reads the skill's file, asks it, writes what it says.
#[path = "../../../gen/rust/tri/loop/lesson.rs"] #[allow(dead_code, unused_parens)]
mod lesson;
#[derive(Subcommand)]
pub enum LearnCmd {
/// Add one lesson, as the line `- <date> <lesson> (<ref>)`, to the Traps section of a skill's SKILL.md (a Traps section is made at the end of the file when it has none), unless the section already says it: then it prints `duplicate` and changes nothing. It prints the file and the line. It refuses an empty lesson, one over 400 bytes or with a line break, one that looks like a secret, a bad date or ref, and a skill that is no directory under .claude/skills/. Rules: specs/tri/loop/lesson.t27.
Add {
/// The skill: a directory name under .claude/skills/.
#[arg(long)] skill: String,
/// One line, at most 400 bytes.
lesson: String,
/// YYYY-MM-DD; today (UTC) when absent.
#[arg(long)] date: Option<String>,
/// Where it was learned, one word such as #11060; none when absent.
#[arg(long = "ref", default_value = "", value_name = "REF")] cite: String,
},
}

pub fn learn_run(cmd: &LearnCmd) -> Result<()> {
let LearnCmd::Add { skill, lesson: said, date, cite } = cmd;
let st = |s: &str| -> &'static str { s.to_string().leak() };
let dir = crate::find_trinity_root()?.join(".claude/skills").join(skill);
let is_dir = lesson::skill_ok(st(skill)) && std::fs::symlink_metadata(&dir).map_or(false, |m| m.is_dir());
let md = ["SKILL.md", "skill.md"].iter().map(|n| dir.join(n)).find(|p| is_dir && p.is_file());
let text = md.as_ref().map(std::fs::read_to_string).transpose()?.unwrap_or_default();
let today = chrono::Utc::now().format("%Y-%m-%d").to_string();
let (f, l, d, c) = (st(&text), st(said), st(date.as_ref().unwrap_or(&today)), st(cite));
let o = lesson::decide(st(skill), is_dir, f, l, d, c);
if lesson::refused(o) { anyhow::bail!("refused: {}", lesson::say(o)); }
if o == lesson::O_DUPLICATE { println!("{}", lesson::say(o)); return Ok(()); }
let path = md.context("the skill has no SKILL.md or skill.md")?;
let (mut buf, at) = (vec![0u8; 1024], lesson::patch_at(f));
let n = lesson::patch(f, l, d, c, &mut buf);
std::fs::write(&path, format!("{}{}{}", &f[..at], String::from_utf8_lossy(&buf[..n]), &f[at..]))?;
println!("{}:{} {}", path.display(), lesson::row_line(f), lesson::say(o));
Ok(())
}

#[cfg(test)]
mod tests {
#[test]
Expand Down
3 changes: 3 additions & 0 deletions cli/tri/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -453,6 +453,8 @@ enum Commands {
T27oSweep { paths: Vec<String>, #[arg(long, default_value = t27o_ledger::SWEEP_HERE)] repo: String, #[arg(long, default_value_t = t27o_ledger::SWEEP_MAX_BYTES)] max_bytes: usize, #[arg(long)] cache: Option<String>, #[arg(long, default_value_t = t27o_ledger::SWEEP_TOP)] top: usize },
/// Land a lane branch (default: the current one): merge master, then tests with no vacuous pass, seals, types ratchet, dupe subset, push, pull request, auto-merge; the first failing gate stops it with a named code (CONFLICT, TESTS_NOT_CLEAN, SEAL_NOT_CLEAN, TYPES_NOT_CLEAN, DUPE_NOT_SUBSET, PUSH_NOT_CONFIRMED) and a second run prints ALREADY_LANDED; --plan prints the steps and runs none, --check stops before the commit; T27C names the t27c program (default t27c on PATH) (rules: specs/tri/land/land.t27, #11068).
Land { branch: Option<String>, #[arg(long, default_value_t = 0)] issue: u64, #[arg(long)] plan: bool, #[arg(long)] check: bool },
/// Add one lesson to the Traps section of a skill unless the section already says it; `tri learn add --help` has the refusals (rules: specs/tri/loop/lesson.t27, #11060).
Learn { #[command(subcommand)] action: loopclaim::LearnCmd },
}

#[derive(Subcommand)]
Expand Down Expand Up @@ -1373,6 +1375,7 @@ fn main() -> Result<()> {
Commands::T27o { file, name, oracle, check, t27c, emit_cell } => std::process::exit(cmd_t27o(file, name.as_deref(), *oracle, *check, t27c.as_deref(), *emit_cell)? as i32),
Commands::T27oSweep { paths, repo, max_bytes, cache, top } => std::process::exit(cmd_t27o_sweep(paths, repo, *max_bytes, cache.as_deref(), *top)? as i32),
Commands::Land { branch, issue, plan, check } => std::process::exit(cmd_land(branch.as_deref(), *issue, *plan, *check)? as i32),
Commands::Learn { action } => loopclaim::learn_run(action)?,
}

Ok(())
Expand Down
Loading
Loading