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
10 changes: 10 additions & 0 deletions .agents/skills/improving-by-trial/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,16 @@ Each round runs every trial in `trials/`: 4 ESS trials (`ess-new`, a new specifi
round so the agents cannot copy the previous answer from the skills; a changed trial starts a new
baseline entry.

### Every product release is re-verified

`verified.json` names, per CLI (`aep`, `ess`, `worktree`), the release the skills were last
verified against. The daily `agentplugins-check tools` run fails with one line per CLI whose newest
release is newer. Then:

1. Run `agentplugins-check tools` and fix every command it reports.
2. Run an ESS trial round (at least `ess-full-package`) against the new release.
3. Set the CLI to the new release in `verified.json` in the same pull request.

## 7. Clean up

`trial:run` deletes the credentials it copied. When the round is released, check that no
Expand Down
3 changes: 3 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ CLIs. Serves O2 (decisions as data) and O3 (any harness).
| `plugins/<name>/` | every plugin: `b10x`, `aep`, `ess`, `worktree`, `connectors` ([structure](website/docs/structure.md)) |
| `.claude-plugin/marketplace.json`, `.agents/plugins/marketplace.json` | the two marketplace files |
| `catalog.json` | products, plugins, binaries, retired names — no versions |
| `verified.json` | per CLI, the release the skills were last verified against; `tools` fails when a newer one is out |
| `crates/b10x/` | the setup CLI; `plan.rs` is pure and fixture-tested |
| `crates/agentplugins-check/` | the gate: marketplace, catalog, concept, retired names, CLI spellings, evals; `tools` checks skills against the newest CLI releases |
| `evals/` | eval corpus ([`evals/README.md`](evals/README.md)) |
Expand All @@ -29,6 +30,8 @@ CLIs. Serves O2 (decisions as data) and O3 (any harness).
- Retired names appear only where the gate allows them (`CHANGELOG.md`, `changes/`,
`.engineering/`, `catalog.json`, `crates/b10x/`, the checker's own table).
- Anything executable is Rust.
- Every `aep`, `ess` and `worktree` release is re-verified before `verified.json` moves to it:
`agentplugins-check tools`, then an ESS trial round ([`improving-by-trial`](.agents/skills/improving-by-trial/SKILL.md)).
- Trial findings for another repository become an issue there, labelled `trial-finding` by the
bot; the skill here documents the workaround until the fix is released ([`improving-by-trial`](.agents/skills/improving-by-trial/SKILL.md)).

Expand Down
21 changes: 21 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,26 @@
# Changelog

## [0.14.11] — 2026-09-26

Keeping installs current, keeping the skills matched to each product release, and pinning a tooling
version per repository.

- `b10x check` (the session-start line) refreshes the newest releases from GitHub at most once a day,
in parallel with a short timeout, and names `/b10x:upgrade` when the installed plugins or a CLI are
older. It used to compare against a record written only by the last plan, so a machine on 0.14.7
heard nothing about 0.14.10.
- `b10x upgrade` and `b10x setup plan` read plugin versions from the newest release when the local
marketplace copy is older than it; they reported 0.14.7 as current until the apply step refreshed
the copy.
- `verified.json` records the aep, ess and worktree releases the skills were last verified against;
the daily `agentplugins-check tools` fails when a newer release exists, until the skills are
re-verified and the file is bumped.
- `b10x pin <cli> <version>` and `b10x unpin <cli>` keep a committed `b10x.toml` (`[pins]`, exact or
a minor line such as `0.32`). `init`, `upgrade`, `setup plan` and `install` install the pinned
release; `upgrade` reports newer ones and changes nothing; `b10x check` warns when the CLI on
`PATH` differs from the pin and when the skills describe a newer release. The matching `requires`
line in `ess-inputs.yaml` waits for beyond10x/ess#106.

## [0.14.10] — 2026-09-25

From round 5, the first measured trial round: seven trials on 0.14.9, all isolated. The
Expand Down
4 changes: 2 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ resolver = "2"
members = ["crates/agentplugins-check", "crates/b10x"]

[workspace.package]
version = "0.14.10"
version = "0.14.11"
edition = "2021"
rust-version = "1.85"
license = "Apache-2.0"
Expand Down
1 change: 1 addition & 0 deletions crates/agentplugins-check/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -917,6 +917,7 @@ fn check(root: &Path) -> Result<(), String> {
marketplace(root, ".agents/plugins/marketplace.json")?;
marketplace(root, ".claude-plugin/marketplace.json")?;
catalog(root)?;
tools::verified(root)?;
for (name, required) in PLUGINS {
plugin(root, name, required)?;
}
Expand Down
76 changes: 75 additions & 1 deletion crates/agentplugins-check/src/tools.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@
//! checked against its `SHA256SUMS` — and runs `<cli> <subcommands> --help` for every command a
//! code span or code block in that plugin spells. ESS's syntax example must also still validate.
//! It runs on every pull request, every `main` push and daily; a red run is fixed by a skill edit.
//!
//! It also fails when a CLI's newest release is newer than `verified.json`, the release its skills
//! were last verified against: every product release is re-verified (this check and an ESS trial
//! round) before `verified.json` moves. The offline gate only checks that file's shape.

use std::collections::BTreeSet;
use std::path::{Path, PathBuf};
Expand All @@ -21,6 +25,53 @@ const TOOLS: &[(&str, &str, &str)] = &[
/// Most subcommand words taken from one spelled command.
const DEPTH: usize = 4;

/// The file naming, per CLI, the release the skills were last verified against.
pub const VERIFIED: &str = "verified.json";

/// A key for an `x.y.z` version or tag.
fn key(version: &str) -> Option<(u64, u64, u64)> {
let mut parts = version.trim_start_matches('v').split('.');
let triple = (
parts.next()?.parse().ok()?,
parts.next()?.parse().ok()?,
parts.next()?.parse().ok()?,
);
parts.next().is_none().then_some(triple)
}

/// Offline: `verified.json` names exactly the CLIs the skills drive, each at an `x.y.z` release.
pub fn verified(root: &Path) -> Result<std::collections::BTreeMap<String, String>, String> {
let path = root.join(VERIFIED);
let text = std::fs::read_to_string(&path).map_err(|error| format!("{VERIFIED}: {error}"))?;
let map: std::collections::BTreeMap<String, String> = serde_json::from_str(&text)
.map_err(|error| format!("{VERIFIED}: an object of CLI → `x.y.z` release: {error}"))?;
let expected: BTreeSet<&str> = TOOLS.iter().map(|(_, cli, _)| *cli).collect();
let named: BTreeSet<&str> = map.keys().map(String::as_str).collect();
if named != expected {
return Err(format!(
"{VERIFIED}: names {named:?}; it must name exactly {expected:?}"
));
}
for (cli, release) in &map {
if key(release).is_none() {
return Err(format!(
"{VERIFIED}: `{cli}` is `{release}`, not an `x.y.z` release"
));
}
}
Ok(map)
}

/// The line for a CLI whose newest release is newer than the one its skills were verified against.
#[must_use]
pub fn unverified(cli: &str, newest: &str, verified: &str) -> Option<String> {
(key(newest)? > key(verified)?).then(|| {
format!(
"{cli} {newest} is newer than {VERIFIED} ({verified}): re-verify the skills (`agentplugins-check tools` and an ESS trial round), then set `{cli}` to {newest} in {VERIFIED}"
)
})
}

fn run(program: &str, arguments: &[&str]) -> Result<String, String> {
let output = Command::new(program)
.args(arguments)
Expand Down Expand Up @@ -264,9 +315,16 @@ pub fn verify(root: &Path) -> Result<(), String> {
std::env::temp_dir().join(format!("agentplugins-check-tools-{}", std::process::id()));
let _ = std::fs::remove_dir_all(&scratch);
let result = (|| {
let verified = verified(root)?;
let mut problems = Vec::new();
for (plugin, cli, repository) in TOOLS {
let (tag, binary) = fetch(cli, repository, &scratch)?;
if let Some(line) = verified
.get(*cli)
.and_then(|release| unverified(cli, &tag, release))
{
problems.push(line);
}
let mut checked = 0;
for file in markdown(&root.join("plugins").join(plugin)) {
let text = std::fs::read_to_string(&file).map_err(|error| error.to_string())?;
Expand Down Expand Up @@ -296,7 +354,7 @@ pub fn verify(root: &Path) -> Result<(), String> {
Ok(())
} else {
Err(format!(
"{} spelled command(s) the newest releases do not have:\n {}",
"{} problem(s) against the newest releases:\n {}",
problems.len(),
problems.join("\n ")
))
Expand All @@ -310,6 +368,22 @@ pub fn verify(root: &Path) -> Result<(), String> {
mod tests {
use super::*;

#[test]
fn a_newer_release_than_verified_is_named_with_the_step() {
let line = unverified("ess", "0.32.2", "0.32.1").unwrap();
assert!(line.starts_with("ess 0.32.2 is newer than verified.json (0.32.1)"));
assert!(line.contains("ESS trial round"));
assert_eq!(unverified("ess", "0.32.1", "0.32.1"), None);
assert_eq!(unverified("ess", "v0.32.0", "0.32.1"), None);
}

#[test]
fn the_committed_verified_file_has_its_shape() {
let root = Path::new(env!("CARGO_MANIFEST_DIR")).join("../..");
let map = verified(&root).unwrap();
assert_eq!(map.len(), TOOLS.len());
}

#[test]
fn commands_are_read_from_code_only() {
let text = "Run `ess specify validate --path <spec>` then prose ess binary.\n\n```console\n$ ess generate project openapi --out x\nb10x skill ess:init\n```\n`ess` alone, `ess specify|generate <verb>`\n";
Expand Down
Loading
Loading