From afb93e297ec556b9fb85b40f747c443945b4cfd2 Mon Sep 17 00:00:00 2001 From: Alex Kesling Date: Wed, 6 May 2026 09:42:19 -0400 Subject: [PATCH 01/26] docs: spec for `qualifier agents` agent-bootstrap subcommand Brainstorm output: a new `agents` subcommand prints a self-contained guide so an AI coding agent in a user's repo can bootstrap into productive use of qualifier without a separately-installed skill or prompt bundle. Pages are hand-written markdown embedded via include_str!(), discovered through a small registry, and surfaced under a new "For AI agents:" group at the top of `qualifier --help`. --- ...6-05-06-qualifier-agents-command-design.md | 202 ++++++++++++++++++ 1 file changed, 202 insertions(+) create mode 100644 docs/superpowers/specs/2026-05-06-qualifier-agents-command-design.md diff --git a/docs/superpowers/specs/2026-05-06-qualifier-agents-command-design.md b/docs/superpowers/specs/2026-05-06-qualifier-agents-command-design.md new file mode 100644 index 0000000..bf47ead --- /dev/null +++ b/docs/superpowers/specs/2026-05-06-qualifier-agents-command-design.md @@ -0,0 +1,202 @@ +# `qualifier agents` — agent-bootstrap subcommand + +## Goal + +Let an AI coding agent working in a user's repo bootstrap into productive use of `qualifier` without the user installing a separate skill, prompt, or doc bundle. The agent reads `qualifier --help`, sees a clearly agent-targeted entry, and runs `qualifier agents` to get a self-contained guide. The guide covers concepts, workflows, and a per-subcommand reference, all bundled with the binary. + +## Non-goals + +- Replacing or competing with `--help`. Clap-generated help stays for humans. +- Auto-generating content from clap definitions. Pages are hand-written prose, including syntax sections. +- Multi-format rendering. Output is plain UTF-8 markdown source — no ANSI, no pager, no HTML. +- Online updates. The guide ships frozen with the installed binary version. An agent gets exactly the doc that matches the tool it can call. + +## Naming + +Subcommand is `agents` (plural). Rationale: matches the established `AGENTS.md` convention that coding agents already pattern-match on, and reads as "instructions for agents" the way `man` reads as "manual." Help description carries the imperative meaning: `Self-contained guide for AI coding agents (start here)`. + +The existing `AGENTS.md` at the repo root is contributor-facing (instructions for an agent helping develop qualifier). The new `agents` subcommand is consumer-facing (instructions for an agent using qualifier as a tool). The two coexist; the spec does not modify `AGENTS.md`. + +## Module layout + +``` +src/cli/commands/agents/ + mod.rs // clap Args, run(), dispatch, registry + pages/ + _overview.md // printed by bare `qualifier agents` + concepts.md + workflows.md + pitfalls.md + record.md + reply.md + resolve.md + emit.md + show.md + ls.md + praise.md + review.md + compact.md +``` + +`haiku` is intentionally omitted — it's a flavor command with no agent-relevant usage guidance. The page set covers exactly the subcommands an agent should learn. + +`pages/` holds pure markdown content. `mod.rs` owns dispatch and the registry. The leading underscore on `_overview.md` keeps the filename out of the topic namespace and signals "not a topic — it's the index." + +## Registry + +```rust +struct Page { + name: &'static str, + summary: &'static str, + body: &'static str, +} + +const PAGES: &[Page] = &[ + Page { name: "concepts", summary: "Annotation model, kinds, supersession, IDs, .qual layout", + body: include_str!("pages/concepts.md") }, + Page { name: "workflows", summary: "Worked recipes for common tasks", + body: include_str!("pages/workflows.md") }, + Page { name: "pitfalls", summary: "Common mistakes agents make with qualifier", + body: include_str!("pages/pitfalls.md") }, + Page { name: "record", summary: "Record a new annotation", + body: include_str!("pages/record.md") }, + // … one entry per remaining subcommand +]; +``` + +The registry is the only place that needs editing when a topic is added or removed. The overview's `{{TOPICS}}` substitution renders `name — summary` for each entry. + +## CLI surface + +```rust +#[derive(clap::Parser)] +pub struct Args { + /// Topic name (e.g. `record`, `concepts`, `workflows`). Omit to print the orientation page. + topic: Option, +} + +pub fn run(args: Args) -> crate::Result<()>; +``` + +Behavior: + +| Invocation | Behavior | +|--------------------------------|-------------------------------------------------------------------------------------------| +| `qualifier agents` | Print rendered overview (body of `_overview.md` with `{{TOPICS}}` expanded) | +| `qualifier agents ` | Print that page's `body` verbatim to stdout | +| `qualifier agents ` | `eprintln!("qualifier agents: no such topic ''. Available: ...")` and exit code 2 | +| `qualifier agents --help` | Standard clap help (one positional arg, no flags). Untouched. | + +Why a single positional rather than nested clap subcommands: every page prints text and accepts no flags. A clap subcommand tree would add boilerplate without earning a meaningful per-topic `--help` (the page *is* the help). + +Output is stdout only; no ANSI, no pager. An agent consuming the output wants raw text. + +The error path uses exit code 2 to match clap's convention for usage errors. Since `cli::run` exits 1 on any returned `Err`, `agents::run` handles its own usage error: print the message to stderr and call `std::process::exit(2)` directly rather than returning an `Err`. + +## Help template integration + +`HELP_TEMPLATE` in `src/cli/mod.rs` gets a new group above the existing ones: + +``` +For AI agents: + agents Self-contained guide for AI coding agents (start here) + +Record observations: + ... +``` + +The group header `For AI agents:` is the discoverability signal — a model scanning grouped help output recognizes this entry as targeted at it. Description ("start here") is the imperative nudge. `agents` does not appear in any other group. + +The `Commands` enum gains: + +```rust +/// Self-contained guide for AI coding agents (start here) +Agents(commands::agents::Args), +``` + +dispatched in the `run` match like every other subcommand. + +The existing comment above `HELP_TEMPLATE` already warns to keep template and enum in sync — no new instruction needed. + +## Page content templates + +Actual prose is written during implementation; the spec defines structure and sizing. + +### `_overview.md` (~80–120 lines) + +1. Two-sentence framing: what qualifier is, what shape of artifact it produces. +2. "When to use this tool" — three bullets. +3. "When NOT to use it" — e.g., one-off scratch debugging, info that belongs in a commit message. +4. Quickstart: one minimal end-to-end example (record → show → resolve). +5. `{{TOPICS}}` index — auto-rendered from registry. +6. Pointer: "For more on any topic, run `qualifier agents `." + +### `concepts.md` (~150 lines) + +- Annotation envelope (metabox, type, subject, issuer, body) and why each matters to an agent. +- Kinds (`note`, `concern`, `bug`, `praise`, etc.) and how to choose. +- Supersession: mechanics, why chains must be acyclic, cross-subject rejection. +- `.qual` file layout: directory-level vs file-level, discovery rules, `.qualignore`. +- Issuer URIs and `issuer_type`. +- BLAKE3 content addressing — implication that changing canonical body changes the ID. + +### `workflows.md` (~150 lines) + +Five recipes, each a short narrative plus commands: + +1. Record a finding during code review. +2. Reply to an existing observation with new info. +3. Resolve a finding once it's addressed. +4. Triage stale annotations after a refactor (`review`). +5. Compact a noisy `.qual` file. + +### `pitfalls.md` (~60 lines, grows over time) + +- Forgetting `--span` when scope is narrower than the file. +- Conflating `reply` (add info) with `resolve` (close). +- Recording an annotation when a commit message would do. +- Editing `.qual` files by hand instead of using `record`/`emit`. +- Section labelled "Add new pitfalls here as we observe them." + +### Per-subcommand pages (~50–100 lines each) + +Each follows the same template: + +1. One-sentence purpose. +2. When to use it (vs adjacent commands). +3. Common invocation forms with realistic examples. +4. Flags worth knowing about, in prose (not a flag table — agents can run `qualifier --help` for the table). +5. Gotchas specific to this command. + +## Testing + +CLI integration tests live in `tests/cli_integration.rs`. Add cases: + +1. `qualifier agents` exits 0 and prints non-empty output containing the rendered topic index (assert that each registry entry's name and summary appear). +2. `qualifier agents concepts` exits 0 and prints non-empty output (smoke check). +3. `qualifier agents ` exits 0 in a parameterized loop, asserting non-empty output. Catches missing `include_str!` files and registry mismatches. +4. `qualifier agents bogus-topic` exits non-zero and stderr contains `no such topic`. +5. `qualifier --help` output contains the exact line `For AI agents:` and the `agents` row, ensuring discoverability stays wired. + +A small unit test in `agents/mod.rs` verifies that `_overview.md` contains the literal `{{TOPICS}}` sentinel — guards against accidentally removing it. + +No new test fixtures or helpers are needed. The pages are static; coverage is mostly "they render and are non-empty." + +## Out of scope / future + +- A `qualifier agents pitfalls` page that grows from observed agent failures over time. Stub at launch. +- Republishing pages to the docs site verbatim. The .md files are website-friendly; if/when desired this is a docs-site change, not a qualifier change. +- Search or fuzzy match for topic names. If `agents fop` should suggest `agents foo`, defer that until we have a topic count where guessing helps. +- Auto-derived per-subcommand syntax sections from clap. Pages stay hand-written; the small drift cost is accepted. +- Localization. English only. + +## File touch list + +- New: `src/cli/commands/agents/mod.rs` +- New: `src/cli/commands/agents/pages/*.md` (13 files: `_overview` + 3 topical + 9 per-subcommand) +- Modify: `src/cli/commands.rs` or `src/cli/commands/mod.rs` — add `pub mod agents;` +- Modify: `src/cli/mod.rs` — add `Agents` variant, add dispatch arm, update `HELP_TEMPLATE` +- Modify: `tests/cli_integration.rs` — add the test cases above +- Modify: `Cargo.toml` — bump version (per AGENTS.md "Keeping Things in Sync") +- Modify: `CHANGELOG.md` — note the new subcommand +- Modify: `README.md` — add `agents` row to the CLI Commands table if one exists From aea68de36f5780a395bc5c6691792b01d559ae9a Mon Sep 17 00:00:00 2001 From: Alex Kesling Date: Wed, 6 May 2026 09:49:51 -0400 Subject: [PATCH 02/26] docs: implementation plan for `qualifier agents` subcommand MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bite-sized TDD breakdown of the spec at docs/superpowers/specs/2026-05-06-qualifier-agents-command-design.md into 10 tasks, each with failing-test-first steps where applicable. Tasks 1–5 build infrastructure (skeleton, registry, dispatch, overview rendering, help-template wiring); Tasks 7–8 fill in real prose for the topical and per-subcommand pages; Task 9 handles version/CHANGELOG/README; Task 10 is final verification. --- .../2026-05-06-qualifier-agents-command.md | 976 ++++++++++++++++++ 1 file changed, 976 insertions(+) create mode 100644 docs/superpowers/plans/2026-05-06-qualifier-agents-command.md diff --git a/docs/superpowers/plans/2026-05-06-qualifier-agents-command.md b/docs/superpowers/plans/2026-05-06-qualifier-agents-command.md new file mode 100644 index 0000000..049daf3 --- /dev/null +++ b/docs/superpowers/plans/2026-05-06-qualifier-agents-command.md @@ -0,0 +1,976 @@ +# `qualifier agents` Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Add a `qualifier agents` subcommand that prints a self-contained guide so an AI coding agent can bootstrap into productive use of qualifier without a separately-installed skill. + +**Architecture:** New `src/cli/commands/agents/` module with a registry of pages (each loaded via `include_str!()` from `pages/*.md`). Single positional arg: bare → orientation page with `{{TOPICS}}` index substitution; named → that page's body verbatim; unknown → exit 2. New "For AI agents:" group at the top of the existing `HELP_TEMPLATE`. + +**Tech Stack:** Rust 2024, clap 4, existing CLI integration test harness (`tests/cli_integration.rs`) using `std::process::Command`. + +**Spec:** [docs/superpowers/specs/2026-05-06-qualifier-agents-command-design.md](../specs/2026-05-06-qualifier-agents-command-design.md) + +--- + +## File Structure + +**Created:** +- `src/cli/commands/agents/mod.rs` — clap `Args`, `run()`, registry, dispatch, overview rendering +- `src/cli/commands/agents/pages/_overview.md` — orientation (printed by bare `qualifier agents`) +- `src/cli/commands/agents/pages/concepts.md` — annotation model, kinds, supersession, IDs, layout +- `src/cli/commands/agents/pages/workflows.md` — five worked recipes +- `src/cli/commands/agents/pages/pitfalls.md` — common mistakes (stub-friendly) +- `src/cli/commands/agents/pages/{record,reply,resolve,emit,show,ls,praise,review,compact}.md` — per-subcommand pages (9 files) + +**Modified:** +- `src/cli/commands/mod.rs` — add `pub mod agents;` +- `src/cli/mod.rs` — add `Agents` variant, dispatch arm, update `HELP_TEMPLATE` +- `tests/cli_integration.rs` — new tests (see Task 9) +- `Cargo.toml` — bump version (e.g., 0.4.0 → 0.5.0; pick whichever next-minor matches crates.io state) +- `CHANGELOG.md` — note new subcommand under Added +- `README.md` — add `agents` to the CLI Commands tables under a new "For AI agents" sub-table + +**Boundaries:** the agents module is self-contained. Its only outward dependency is being wired into the `Commands` enum and `HELP_TEMPLATE` in `src/cli/mod.rs`. It does not touch `annotation.rs`, `qual_file.rs`, or any other domain code. + +--- + +## Task 1: Scaffold the agents module + +**Files:** +- Create: `src/cli/commands/agents/mod.rs` +- Create: `src/cli/commands/agents/pages/_overview.md` (placeholder) +- Modify: `src/cli/commands/mod.rs` +- Modify: `src/cli/mod.rs` +- Test: `tests/cli_integration.rs` + +- [ ] **Step 1: Write the failing test** + +Append to `tests/cli_integration.rs`: + +```rust +// --- qualifier agents --- + +#[test] +fn test_agents_bare_invocation_succeeds() { + let dir = tempfile::tempdir().unwrap(); + let (stdout, stderr, code) = run_qualifier(dir.path(), &["agents"]); + assert_eq!(code, 0, "agents should succeed: stderr={stderr}"); + assert!(!stdout.is_empty(), "agents should print something"); +} +``` + +- [ ] **Step 2: Run test to verify it fails** + +Run: `cargo test --test cli_integration test_agents_bare_invocation_succeeds` +Expected: FAIL with clap error like "unrecognized subcommand 'agents'". + +- [ ] **Step 3: Create the placeholder overview page** + +Create `src/cli/commands/agents/pages/_overview.md` with one line so `include_str!` succeeds: + +```markdown +qualifier agents — orientation page (placeholder) +``` + +- [ ] **Step 4: Create the agents module** + +Create `src/cli/commands/agents/mod.rs`: + +```rust +use clap::Parser; + +#[derive(Parser, Debug)] +pub struct Args { + /// Topic name (e.g. `record`, `concepts`, `workflows`). Omit to print the orientation page. + pub topic: Option, +} + +const OVERVIEW: &str = include_str!("pages/_overview.md"); + +pub fn run(args: Args) -> crate::Result<()> { + match args.topic.as_deref() { + None => { + print!("{OVERVIEW}"); + Ok(()) + } + Some(name) => { + eprintln!("qualifier agents: no such topic '{name}'. Available: (none yet)"); + std::process::exit(2); + } + } +} +``` + +- [ ] **Step 5: Register the module** + +Modify `src/cli/commands/mod.rs`. Add the line in alphabetical order: + +```rust +pub mod agents; +pub mod compact; +pub mod emit; +// ... existing entries +``` + +- [ ] **Step 6: Wire the subcommand into `Commands`** + +Modify `src/cli/mod.rs`. Add the variant inside `enum Commands`: + +```rust + /// Self-contained guide for AI coding agents (start here) + Agents(commands::agents::Args), +``` + +Place it as the first variant (above `Record`) so it sits logically with the new "For AI agents" group. + +Then add the dispatch arm inside the `match cli.command` block in `pub fn run()`: + +```rust + Commands::Agents(args) => commands::agents::run(args), +``` + +- [ ] **Step 7: Run the test to verify it passes** + +Run: `cargo test --test cli_integration test_agents_bare_invocation_succeeds` +Expected: PASS. + +- [ ] **Step 8: Run the full test suite to ensure nothing broke** + +Run: `cargo test --all-features` +Expected: all tests pass. + +- [ ] **Step 9: Commit** + +```bash +git add src/cli/commands/agents src/cli/commands/mod.rs src/cli/mod.rs tests/cli_integration.rs +git commit -m "feat(agents): scaffold qualifier agents subcommand" +``` + +--- + +## Task 2: Add the unknown-topic error path + +**Files:** +- Test: `tests/cli_integration.rs` + +- [ ] **Step 1: Write the failing test** + +Append to `tests/cli_integration.rs`: + +```rust +#[test] +fn test_agents_unknown_topic_exits_2() { + let dir = tempfile::tempdir().unwrap(); + let (_stdout, stderr, code) = run_qualifier(dir.path(), &["agents", "bogus-topic"]); + assert_eq!(code, 2, "unknown topic should exit 2: stderr={stderr}"); + assert!( + stderr.contains("no such topic"), + "stderr should explain: {stderr}" + ); + assert!( + stderr.contains("bogus-topic"), + "stderr should name the bad topic: {stderr}" + ); +} +``` + +- [ ] **Step 2: Run test to verify it passes already** + +Run: `cargo test --test cli_integration test_agents_unknown_topic_exits_2` +Expected: PASS (Task 1's `run()` already prints the error and exits 2). + +If it fails, re-check the `eprintln!` wording in `agents/mod.rs` — it must contain the literal string `no such topic`. + +- [ ] **Step 3: Commit** + +```bash +git add tests/cli_integration.rs +git commit -m "test(agents): cover unknown-topic exit path" +``` + +--- + +## Task 3: Introduce the page registry and per-topic dispatch + +**Files:** +- Modify: `src/cli/commands/agents/mod.rs` +- Create: `src/cli/commands/agents/pages/concepts.md` (placeholder) +- Create: `src/cli/commands/agents/pages/workflows.md` (placeholder) +- Create: `src/cli/commands/agents/pages/pitfalls.md` (placeholder) +- Create: `src/cli/commands/agents/pages/{record,reply,resolve,emit,show,ls,praise,review,compact}.md` (placeholders, 9 files) +- Test: `tests/cli_integration.rs` + +- [ ] **Step 1: Write the failing tests** + +Append to `tests/cli_integration.rs`: + +```rust +#[test] +fn test_agents_concepts_topic_prints_body() { + let dir = tempfile::tempdir().unwrap(); + let (stdout, stderr, code) = run_qualifier(dir.path(), &["agents", "concepts"]); + assert_eq!(code, 0, "agents concepts should succeed: stderr={stderr}"); + assert!(!stdout.is_empty(), "should print body"); +} + +#[test] +fn test_agents_all_registered_topics_render() { + // Each topic in the registry must produce non-empty stdout with exit 0. + // If you add a topic, add it here too. + let topics = [ + "concepts", "workflows", "pitfalls", + "record", "reply", "resolve", "emit", + "show", "ls", "praise", "review", "compact", + ]; + let dir = tempfile::tempdir().unwrap(); + for topic in topics { + let (stdout, stderr, code) = run_qualifier(dir.path(), &["agents", topic]); + assert_eq!(code, 0, "agents {topic} should succeed: stderr={stderr}"); + assert!(!stdout.is_empty(), "agents {topic} should print body"); + } +} +``` + +- [ ] **Step 2: Run tests to verify they fail** + +Run: `cargo test --test cli_integration test_agents_concepts_topic_prints_body test_agents_all_registered_topics_render` +Expected: FAIL — both topics are unknown. + +- [ ] **Step 3: Create placeholder markdown files** + +Create each file with a single placeholder line so `include_str!` resolves and the smoke test passes. You can use the same one-liner for all of them: + +```markdown +qualifier agents — page (placeholder; replace in Task 7/8) +``` + +Replace `` with the topic name. Files to create: + +- `src/cli/commands/agents/pages/concepts.md` +- `src/cli/commands/agents/pages/workflows.md` +- `src/cli/commands/agents/pages/pitfalls.md` +- `src/cli/commands/agents/pages/record.md` +- `src/cli/commands/agents/pages/reply.md` +- `src/cli/commands/agents/pages/resolve.md` +- `src/cli/commands/agents/pages/emit.md` +- `src/cli/commands/agents/pages/show.md` +- `src/cli/commands/agents/pages/ls.md` +- `src/cli/commands/agents/pages/praise.md` +- `src/cli/commands/agents/pages/review.md` +- `src/cli/commands/agents/pages/compact.md` + +- [ ] **Step 4: Replace `agents/mod.rs` with the registry-backed implementation** + +Replace the contents of `src/cli/commands/agents/mod.rs` with: + +```rust +use clap::Parser; + +#[derive(Parser, Debug)] +pub struct Args { + /// Topic name (e.g. `record`, `concepts`, `workflows`). Omit to print the orientation page. + pub topic: Option, +} + +struct Page { + name: &'static str, + summary: &'static str, + body: &'static str, +} + +const OVERVIEW: &str = include_str!("pages/_overview.md"); + +const PAGES: &[Page] = &[ + Page { + name: "concepts", + summary: "Annotation model, kinds, supersession, IDs, .qual layout", + body: include_str!("pages/concepts.md"), + }, + Page { + name: "workflows", + summary: "Worked recipes for common tasks", + body: include_str!("pages/workflows.md"), + }, + Page { + name: "pitfalls", + summary: "Common mistakes agents make with qualifier", + body: include_str!("pages/pitfalls.md"), + }, + Page { + name: "record", + summary: "Record a new annotation", + body: include_str!("pages/record.md"), + }, + Page { + name: "reply", + summary: "Reply to an existing record", + body: include_str!("pages/reply.md"), + }, + Page { + name: "resolve", + summary: "Resolve (close) a record", + body: include_str!("pages/resolve.md"), + }, + Page { + name: "emit", + summary: "Emit a raw record of any type", + body: include_str!("pages/emit.md"), + }, + Page { + name: "show", + summary: "Show annotations for an artifact", + body: include_str!("pages/show.md"), + }, + Page { + name: "ls", + summary: "List artifacts by kind", + body: include_str!("pages/ls.md"), + }, + Page { + name: "praise", + summary: "Show who annotated an artifact and why", + body: include_str!("pages/praise.md"), + }, + Page { + name: "review", + summary: "Check freshness of annotations against current code", + body: include_str!("pages/review.md"), + }, + Page { + name: "compact", + summary: "Compact a .qual file", + body: include_str!("pages/compact.md"), + }, +]; + +fn topic_names() -> String { + PAGES + .iter() + .map(|p| p.name) + .collect::>() + .join(", ") +} + +pub fn run(args: Args) -> crate::Result<()> { + match args.topic.as_deref() { + None => { + print!("{}", render_overview()); + Ok(()) + } + Some(name) => { + if let Some(page) = PAGES.iter().find(|p| p.name == name) { + print!("{}", page.body); + Ok(()) + } else { + eprintln!( + "qualifier agents: no such topic '{name}'. Available: {}", + topic_names() + ); + std::process::exit(2); + } + } + } +} + +fn render_overview() -> String { + // Replace the {{TOPICS}} sentinel with a `name — summary` listing. + let topics_block: String = PAGES + .iter() + .map(|p| format!("- `{}` — {}", p.name, p.summary)) + .collect::>() + .join("\n"); + OVERVIEW.replace("{{TOPICS}}", &topics_block) +} +``` + +- [ ] **Step 5: Run the per-topic tests to verify they pass** + +Run: `cargo test --test cli_integration test_agents_concepts_topic_prints_body test_agents_all_registered_topics_render` +Expected: PASS. + +- [ ] **Step 6: Re-run all earlier agents tests** + +Run: `cargo test --test cli_integration test_agents` +Expected: all four `test_agents_*` tests pass. + +- [ ] **Step 7: Commit** + +```bash +git add src/cli/commands/agents tests/cli_integration.rs +git commit -m "feat(agents): registry and per-topic dispatch with placeholder pages" +``` + +--- + +## Task 4: Implement `{{TOPICS}}` substitution in the overview + +**Files:** +- Modify: `src/cli/commands/agents/pages/_overview.md` +- Test: `tests/cli_integration.rs` + +- [ ] **Step 1: Write the failing tests** + +Append to `tests/cli_integration.rs`: + +```rust +#[test] +fn test_agents_overview_renders_topics_index() { + let dir = tempfile::tempdir().unwrap(); + let (stdout, _stderr, code) = run_qualifier(dir.path(), &["agents"]); + assert_eq!(code, 0); + // Every registered topic name should appear in the rendered overview. + for topic in [ + "concepts", "workflows", "pitfalls", + "record", "reply", "resolve", "emit", + "show", "ls", "praise", "review", "compact", + ] { + assert!( + stdout.contains(topic), + "overview should mention topic '{topic}': {stdout}" + ); + } + // The literal sentinel must not leak through. + assert!( + !stdout.contains("{{TOPICS}}"), + "sentinel should be substituted: {stdout}" + ); +} +``` + +Also add a unit test inside `src/cli/commands/agents/mod.rs` (append to the file): + +```rust +#[cfg(test)] +mod tests { + #[test] + fn overview_contains_topics_sentinel() { + // If this fails, the orientation page lost the {{TOPICS}} substitution + // anchor and the rendered overview will no longer list children. + assert!(super::OVERVIEW.contains("{{TOPICS}}")); + } +} +``` + +- [ ] **Step 2: Run the integration test to verify it fails** + +Run: `cargo test --test cli_integration test_agents_overview_renders_topics_index` +Expected: FAIL — the placeholder `_overview.md` from Task 1 doesn't contain topic names or `{{TOPICS}}`. + +- [ ] **Step 3: Replace `_overview.md` with a real (still concise) orientation that uses the sentinel** + +Replace contents of `src/cli/commands/agents/pages/_overview.md` with the following. This is the launch version; deeper content is filled in during Task 7. + +```markdown +# qualifier — guide for AI coding agents + +You are an AI coding agent in a user's repository. The `qualifier` CLI is +installed. This page tells you what qualifier is, when to use it, and how to +get more detail on any specific feature. + +## What it is + +Qualifier records *annotations* — structured, content-addressed notes about +software artifacts (files, directories, line ranges). Annotations live in +`.qual` files alongside the code, are intended to be committed to version +control, and form a durable, reviewable record of quality observations +attached to specific places in the codebase. + +## When to use it + +- You found a bug, smell, risk, or stylistic concern that survives this + edit and is worth surfacing for whoever touches the code next. Record it. +- You want to praise a piece of code so future readers know it was + intentional, not accidental. Record it. +- An earlier annotation no longer applies (the code changed, the concern + was addressed). Resolve it. + +## When NOT to use it + +- One-off scratch debugging notes. Use scratch space. +- Information that belongs in the commit message (what *this* change does + and why). Use git. +- Project-wide policy or architecture decisions. Those belong in + `docs/` or an ADR, not as an annotation on a file. + +## Quickstart + +```bash +# Record a concern about a function in src/foo.rs +qualifier record concern src/foo.rs --message "tight coupling to the cache" + +# See what's annotated on a file +qualifier show src/foo.rs + +# After fixing it, resolve the annotation by id-prefix or location +qualifier resolve src/foo.rs --message "refactored to inject the cache" +``` + +## Available topics + +Run `qualifier agents ` for any of: + +{{TOPICS}} + +## Reference + +For exact flag tables on any subcommand, run `qualifier --help`. +For the JSONL wire format and library API, see `SPEC.md` in this repo (if +present) or the published spec. +``` + +- [ ] **Step 4: Run the failing tests to verify they now pass** + +Run: `cargo test --test cli_integration test_agents_overview_renders_topics_index` +Expected: PASS. + +- [ ] **Step 5: Run the unit test** + +Run: `cargo test --lib agents::tests::overview_contains_topics_sentinel` +Expected: PASS. + +If `cargo test --lib` doesn't find the test, the agents module may not be exposed under the library crate (it's CLI-only). In that case, the unit test will be discovered when running `cargo test --all-features` or `cargo test --bin qualifier`. Either is fine; the test just needs to run somewhere. + +- [ ] **Step 6: Run the whole suite** + +Run: `cargo test --all-features` +Expected: all tests pass. + +- [ ] **Step 7: Commit** + +```bash +git add src/cli/commands/agents tests/cli_integration.rs +git commit -m "feat(agents): render overview with topic index" +``` + +--- + +## Task 5: Add the "For AI agents" group to `--help` + +**Files:** +- Modify: `src/cli/mod.rs` +- Test: `tests/cli_integration.rs` + +- [ ] **Step 1: Write the failing test** + +Append to `tests/cli_integration.rs`: + +```rust +#[test] +fn test_top_level_help_shows_agents_group() { + let dir = tempfile::tempdir().unwrap(); + let (stdout, _stderr, code) = run_qualifier(dir.path(), &["--help"]); + assert_eq!(code, 0); + assert!( + stdout.contains("For AI agents:"), + "help should show the agents group header: {stdout}" + ); + // The agents row should appear under that header, with the "start here" nudge. + assert!( + stdout.contains("agents") && stdout.contains("start here"), + "help should mention the agents subcommand: {stdout}" + ); +} +``` + +- [ ] **Step 2: Run the test to verify it fails** + +Run: `cargo test --test cli_integration test_top_level_help_shows_agents_group` +Expected: FAIL — `HELP_TEMPLATE` doesn't yet include the new group. + +- [ ] **Step 3: Update `HELP_TEMPLATE` in `src/cli/mod.rs`** + +Locate the `const HELP_TEMPLATE: &str = "..."` block. Insert a new group **above** "Record observations:" so the agents row is the first thing under the usage line: + +```rust +const HELP_TEMPLATE: &str = "\ +{about-with-newline} +{usage-heading} {usage} + +For AI agents: + agents Self-contained guide for AI coding agents (start here) + +Record observations: + record Record an annotation: `qualifier record [message]` + reply Reply to an existing record (id-prefix or location) + resolve Resolve (close) an existing record (id-prefix or location) + emit Emit a raw record of any type + +Inspect annotations: + show Show annotations for an artifact + ls List artifacts by kind + praise Show who annotated an artifact and why (alias: blame) + review Check freshness of annotations against current code + +Maintain: + compact Compact a .qual file + +Other: + haiku Print a random qualifier haiku + help Print this message or the help of the given subcommand(s) + +Run `qualifier --help` for command-specific options. + +Options: +{options} +"; +``` + +The only change is inserting the four lines `For AI agents:` … `agents Self-contained guide for AI coding agents (start here)` plus the blank line that follows. Existing groups are untouched. + +- [ ] **Step 4: Run the test to verify it passes** + +Run: `cargo test --test cli_integration test_top_level_help_shows_agents_group` +Expected: PASS. + +- [ ] **Step 5: Run all tests** + +Run: `cargo test --all-features` +Expected: all tests pass. + +- [ ] **Step 6: Commit** + +```bash +git add src/cli/mod.rs tests/cli_integration.rs +git commit -m "feat(agents): surface agents group at top of --help" +``` + +--- + +## Task 6: Format and lint check (mid-implementation gate) + +**Files:** none new + +This is a checkpoint before writing prose. The infrastructure is in place; lock it in by ensuring the toolchain is happy. + +- [ ] **Step 1: Format** + +Run: `cargo fmt` +Expected: no output (or whitespace-only diffs). + +- [ ] **Step 2: Lint** + +Run: `cargo clippy --all-targets --all-features -- -D warnings` +Expected: clean. + +- [ ] **Step 3: If clippy or fmt produced changes, commit them** + +```bash +git add -u +git commit -m "style: cargo fmt and clippy fixes for agents module" +``` + +If both produced no changes, skip the commit. + +--- + +## Task 7: Write content for the topical pages + +This task fills in the four topical pages with real prose. Each step is "write one file." Follow the spec's per-page templates exactly. + +The spec's templates are reproduced inline below so you don't need to switch documents. + +**Files:** +- Modify: `src/cli/commands/agents/pages/concepts.md` +- Modify: `src/cli/commands/agents/pages/workflows.md` +- Modify: `src/cli/commands/agents/pages/pitfalls.md` + +(`_overview.md` was finalized in Task 4; no edit needed here.) + +- [ ] **Step 1: Write `concepts.md` (~150 lines)** + +Replace the placeholder. Cover, in this order, with section headings: + +1. **The annotation envelope** — explain the metabox envelope (fields in fixed order: `metabox`, `type`, `subject`, `issuer`, `issuer_type`, `created_at`, `id`, `body`). Note: `metabox` is always `"1"`. Keep terse — agents need to recognize the shape, not memorize it. +2. **Kinds** — list the kinds (`note`, `concern`, `bug`, `praise`, plus any others present in `src/annotation.rs`'s `Kind` enum) and give one-sentence guidance per kind on when to choose it. +3. **Supersession** — explain that an annotation can supersede an earlier one (by id), chains must be acyclic, and cross-subject supersession is rejected. +4. **`.qual` file layout and discovery** — directory-level vs file-level `.qual` files, that discovery walks up looking for VCS markers, and that `.qualignore` and `.gitignore` filter what's considered. +5. **Issuer URIs and `issuer_type`** — issuer must be a URI (contain `:`); `mailto:user@example.com` is the typical agent-recordable form; `issuer_type` is one of `human`, `ai`, `tool`, `unknown`. +6. **Content addressing (BLAKE3)** — IDs are computed from a canonical form of the body; changing canonical fields changes the ID. Implication: don't hand-edit `.qual` files; use `record`/`emit`. + +Keep each section to ~20 lines. Use code blocks for example JSON when it clarifies the shape. Source of truth for any specific behavior is `SPEC.md` and `src/annotation.rs`. + +- [ ] **Step 2: Write `workflows.md` (~150 lines)** + +Replace the placeholder. Five recipes, each a 1–2 sentence motivation followed by a concrete shell example: + +1. **Record a finding during code review.** Motivation: agent reviewing a diff notices a concern. Example: `qualifier record concern src/payments/charge.rs --message "..." --span 42-58`. +2. **Reply to an existing observation with new info.** Motivation: another agent (or you, later) has more context to add to an existing annotation. Example: `qualifier reply --message "..."`. +3. **Resolve a finding once it's addressed.** Motivation: the concern is fixed. Example: `qualifier resolve --message "fixed in commit X"`. +4. **Triage stale annotations after a refactor.** Motivation: span-bound annotations may no longer point at the right code. Example: `qualifier review` and how to interpret the output. +5. **Compact a noisy `.qual` file.** Motivation: many superseded entries; collapse to a clean snapshot. Example: `qualifier compact `. + +For each recipe: 1 paragraph + 1 code block. ~25 lines per recipe. + +- [ ] **Step 3: Write `pitfalls.md` (~60 lines)** + +Replace the placeholder. Bulleted list of common agent mistakes, each with a one-sentence "why this is wrong" and a "do this instead": + +- Recording without `--span` when the concern is about a few lines, not the whole file. +- Conflating `reply` (add info) with `resolve` (close). +- Recording an annotation when a commit message would do. +- Editing `.qual` files by hand instead of using `record`/`emit`. +- Choosing `bug` when `concern` fits — kinds matter for downstream filtering. +- Using a non-URI issuer (must contain `:`). + +End with: `` + +- [ ] **Step 4: Sanity-check all three files render** + +Run: + +```bash +cargo run --bin qualifier -- agents concepts | head -5 +cargo run --bin qualifier -- agents workflows | head -5 +cargo run --bin qualifier -- agents pitfalls | head -5 +``` + +Expected: each prints meaningful first lines (the section headings you wrote), not the placeholder. + +- [ ] **Step 5: Run all tests** + +Run: `cargo test --all-features` +Expected: all tests pass (the parameterized topic test still passes; existing tests unaffected). + +- [ ] **Step 6: Commit** + +```bash +git add src/cli/commands/agents/pages +git commit -m "docs(agents): write topical pages — concepts, workflows, pitfalls" +``` + +--- + +## Task 8: Write content for the per-subcommand pages + +Each per-subcommand page follows the same template. The template is reproduced inline so you don't need to switch documents. + +**Per-page template (same for all 9 files):** + +```markdown +# qualifier + +## Purpose + + + +## When to use it + +<2–3 sentences distinguishing this command from adjacent ones — e.g., +record vs reply vs resolve, or show vs praise vs ls.> + +## Common invocations + +\`\`\`bash +# Realistic example 1 +qualifier ... + +# Realistic example 2 +qualifier ... +\`\`\` + +## Flags worth knowing + + --help`.> + +## Gotchas + +- +- +``` + +**Length target:** 50–100 lines per file. If you're under 50 you've likely under-explained "When to use it"; if over 100 you've duplicated `--help`. + +**Files:** +- Modify: `src/cli/commands/agents/pages/record.md` +- Modify: `src/cli/commands/agents/pages/reply.md` +- Modify: `src/cli/commands/agents/pages/resolve.md` +- Modify: `src/cli/commands/agents/pages/emit.md` +- Modify: `src/cli/commands/agents/pages/show.md` +- Modify: `src/cli/commands/agents/pages/ls.md` +- Modify: `src/cli/commands/agents/pages/praise.md` +- Modify: `src/cli/commands/agents/pages/review.md` +- Modify: `src/cli/commands/agents/pages/compact.md` + +Source of truth for behavior of each command: read `src/cli/commands/.rs` and run `qualifier --help`. Do not invent flags. + +- [ ] **Step 1: Write `record.md`** + +Cover, beyond the template: that this is the unified annotation-write verb (replaced per-kind verbs), `--span`, `--stdin` for batch, kind argument, location argument. + +- [ ] **Step 2: Write `reply.md`** + +Cover: target can be id-prefix or location; reply *adds* to a thread (does not close it). Differentiate from `resolve`. + +- [ ] **Step 3: Write `resolve.md`** + +Cover: target can be id-prefix or location; this *closes* a record. Differentiate from `reply`. + +- [ ] **Step 4: Write `emit.md`** + +Cover: this is a low-level passthrough for any record type — agents should generally use `record`/`reply`/`resolve` instead, but `emit` exists for emitting raw `epoch` or `dependency` records or anything the higher-level commands don't cover. Mention `--body ''`. + +- [ ] **Step 5: Write `show.md`** + +Cover: shows annotations for an artifact (threaded). Mention `--format json` for programmatic consumption. + +- [ ] **Step 6: Write `ls.md`** + +Cover: list artifacts by kind, optional `--kind` filter. + +- [ ] **Step 7: Write `praise.md`** + +Cover: shows who annotated an artifact and why; alias is `blame` (with a hint that we prefer `praise`). Mention `--vcs` if it surfaces VCS info. + +- [ ] **Step 8: Write `review.md`** + +Cover: checks freshness of span-bound annotations against current code; an agent should run this after editing files that have annotations on them. + +- [ ] **Step 9: Write `compact.md`** + +Cover: prunes superseded records and/or snapshots to an epoch record. Mention this is maintenance, not something to run constantly. + +- [ ] **Step 10: Sanity-check a couple of pages render with real content** + +Run: + +```bash +cargo run --bin qualifier -- agents record | head -10 +cargo run --bin qualifier -- agents emit | head -10 +``` + +Expected: each prints the new prose, not the placeholder. + +- [ ] **Step 11: Run all tests** + +Run: `cargo test --all-features` +Expected: all tests pass. + +- [ ] **Step 12: Format and lint** + +Run: `cargo fmt && cargo clippy --all-targets --all-features -- -D warnings` +Expected: clean. + +- [ ] **Step 13: Commit** + +```bash +git add src/cli/commands/agents/pages +git commit -m "docs(agents): write per-subcommand pages" +``` + +--- + +## Task 9: Update CHANGELOG, README, and bump version + +**Files:** +- Modify: `Cargo.toml` +- Modify: `CHANGELOG.md` +- Modify: `README.md` + +- [ ] **Step 1: Bump the crate version** + +Edit `Cargo.toml`. Choose the next minor (e.g., `0.4.0` → `0.5.0`); confirm against published versions on crates.io if unsure. The change is purely additive (no breaking changes), so a minor bump is correct. + +```toml +version = "0.5.0" +``` + +- [ ] **Step 2: Add a CHANGELOG entry** + +In `CHANGELOG.md`, add a new section above the `[0.4.0]` section, or under `Added` of an existing unreleased section if one exists: + +```markdown +## [0.5.0] — unreleased + +### Added + +- **`qualifier agents`** — self-contained guide for AI coding agents. + Bare `qualifier agents` prints an orientation page with an index of + topics; `qualifier agents ` (e.g. `concepts`, `workflows`, + `record`) drills into per-topic detail. The agent group also appears + at the top of `qualifier --help` so models reading the help text + discover the entry point on their own. +``` + +If a `[0.5.0] — unreleased` section already exists, add only the bullet under its `### Added` block. + +- [ ] **Step 3: Update README CLI tables** + +In `README.md`, find the existing "Record observations / Inspect / Maintain" CLI command tables (around line 51 onward). Add a new table **above** them: + +```markdown +### For AI agents + +| Command | Description | +|---------|-------------| +| `qualifier agents [topic]` | Self-contained guide for AI coding agents (start here) | +``` + +Keep the existing tables unchanged. + +- [ ] **Step 4: Run all tests** + +Run: `cargo test --all-features` +Expected: all tests pass. + +- [ ] **Step 5: Commit** + +```bash +git add Cargo.toml CHANGELOG.md README.md +git commit -m "chore: bump to 0.5.0 and document agents subcommand" +``` + +(Adjust the version in the commit message if you chose a different number.) + +--- + +## Task 10: Final verification + +**Files:** none new + +- [ ] **Step 1: Format** + +Run: `cargo fmt` +Expected: no diff. + +- [ ] **Step 2: Lint** + +Run: `cargo clippy --all-targets --all-features -- -D warnings` +Expected: clean. + +- [ ] **Step 3: Full test suite** + +Run: `cargo test --all-features` +Expected: all tests pass. + +- [ ] **Step 4: Manual smoke test** + +Run each in turn: + +```bash +cargo run --bin qualifier -- --help +cargo run --bin qualifier -- agents +cargo run --bin qualifier -- agents concepts +cargo run --bin qualifier -- agents record +cargo run --bin qualifier -- agents bogus 2>&1 ; echo "exit=$?" +``` + +Expected: +- `--help` shows "For AI agents:" group at the top with the `agents` row. +- `agents` prints the orientation page with a topic list (no literal `{{TOPICS}}`). +- `agents concepts` and `agents record` print real prose. +- `agents bogus` prints `qualifier agents: no such topic 'bogus'. Available: ...` to stderr and exits with `exit=2`. + +- [ ] **Step 5: If anything failed, fix and re-run from Step 1** + +- [ ] **Step 6: Final commit if there are leftover style fixups** + +```bash +git status +# If anything is staged that wasn't covered by an earlier task's commit: +git add -u +git commit -m "chore: final fmt/clippy fixups for agents subcommand" +``` + +If the working tree is clean, skip this step. From 6a9e32d056e51d07bae292401abe4b0092c7d0ed Mon Sep 17 00:00:00 2001 From: Alex Kesling Date: Wed, 6 May 2026 09:57:51 -0400 Subject: [PATCH 03/26] feat(agents): scaffold qualifier agents subcommand Adds the `qualifier agents` subcommand as a directory-style module with a bundled markdown overview page. Bare invocation prints the overview; an unrecognized topic name exits with code 2. --- src/cli/commands/agents/mod.rs | 22 ++++++++++++++++++++++ src/cli/commands/agents/pages/_overview.md | 1 + src/cli/commands/mod.rs | 1 + src/cli/mod.rs | 4 ++++ tests/cli_integration.rs | 10 ++++++++++ 5 files changed, 38 insertions(+) create mode 100644 src/cli/commands/agents/mod.rs create mode 100644 src/cli/commands/agents/pages/_overview.md diff --git a/src/cli/commands/agents/mod.rs b/src/cli/commands/agents/mod.rs new file mode 100644 index 0000000..eac35d2 --- /dev/null +++ b/src/cli/commands/agents/mod.rs @@ -0,0 +1,22 @@ +use clap::Parser; + +#[derive(Parser, Debug)] +pub struct Args { + /// Topic name (e.g. `record`, `concepts`, `workflows`). Omit to print the orientation page. + pub topic: Option, +} + +const OVERVIEW: &str = include_str!("pages/_overview.md"); + +pub fn run(args: Args) -> crate::Result<()> { + match args.topic.as_deref() { + None => { + print!("{OVERVIEW}"); + Ok(()) + } + Some(name) => { + eprintln!("qualifier agents: no such topic '{name}'. Available: (none yet)"); + std::process::exit(2); + } + } +} diff --git a/src/cli/commands/agents/pages/_overview.md b/src/cli/commands/agents/pages/_overview.md new file mode 100644 index 0000000..a386558 --- /dev/null +++ b/src/cli/commands/agents/pages/_overview.md @@ -0,0 +1 @@ +qualifier agents — orientation page (placeholder) diff --git a/src/cli/commands/mod.rs b/src/cli/commands/mod.rs index 84f2e67..5dbe398 100644 --- a/src/cli/commands/mod.rs +++ b/src/cli/commands/mod.rs @@ -1,3 +1,4 @@ +pub mod agents; pub mod compact; pub mod emit; pub mod freshness; diff --git a/src/cli/mod.rs b/src/cli/mod.rs index a8454be..85d8756 100644 --- a/src/cli/mod.rs +++ b/src/cli/mod.rs @@ -53,6 +53,9 @@ pub struct Cli { #[derive(Subcommand)] pub enum Commands { + /// Self-contained guide for AI coding agents (start here) + Agents(commands::agents::Args), + /// Record an annotation: `qualifier record [message]` Record(Box), /// Reply to an existing record (id-prefix or location) @@ -92,6 +95,7 @@ pub fn run() { } let result: crate::Result<()> = match cli.command { + Commands::Agents(args) => commands::agents::run(args), Commands::Record(args) => commands::record::run(*args), Commands::Reply(args) => commands::reply::run(args), Commands::Resolve(args) => commands::resolve::run(args), diff --git a/tests/cli_integration.rs b/tests/cli_integration.rs index be42fdb..c71d582 100644 --- a/tests/cli_integration.rs +++ b/tests/cli_integration.rs @@ -2314,3 +2314,13 @@ fn test_compact_preserves_unknown_record_type() { "snapshot should produce an epoch record:\n{after_snapshot}" ); } + +// --- qualifier agents --- + +#[test] +fn test_agents_bare_invocation_succeeds() { + let dir = tempfile::tempdir().unwrap(); + let (stdout, stderr, code) = run_qualifier(dir.path(), &["agents"]); + assert_eq!(code, 0, "agents should succeed: stderr={stderr}"); + assert!(!stdout.is_empty(), "agents should print something"); +} From 01f2ec56d142681a6912873696452a9bf5b8fcc5 Mon Sep 17 00:00:00 2001 From: Alex Kesling Date: Wed, 6 May 2026 10:06:25 -0400 Subject: [PATCH 04/26] test(agents): cover unknown-topic exit path --- tests/cli_integration.rs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/tests/cli_integration.rs b/tests/cli_integration.rs index c71d582..321a0ab 100644 --- a/tests/cli_integration.rs +++ b/tests/cli_integration.rs @@ -2324,3 +2324,18 @@ fn test_agents_bare_invocation_succeeds() { assert_eq!(code, 0, "agents should succeed: stderr={stderr}"); assert!(!stdout.is_empty(), "agents should print something"); } + +#[test] +fn test_agents_unknown_topic_exits_2() { + let dir = tempfile::tempdir().unwrap(); + let (_stdout, stderr, code) = run_qualifier(dir.path(), &["agents", "bogus-topic"]); + assert_eq!(code, 2, "unknown topic should exit 2: stderr={stderr}"); + assert!( + stderr.contains("no such topic"), + "stderr should explain: {stderr}" + ); + assert!( + stderr.contains("bogus-topic"), + "stderr should name the bad topic: {stderr}" + ); +} From 7d363503b6febed06764a89fb5743dfde256ba43 Mon Sep 17 00:00:00 2001 From: Alex Kesling Date: Wed, 6 May 2026 10:14:54 -0400 Subject: [PATCH 05/26] feat(agents): registry and per-topic dispatch with placeholder pages --- src/cli/commands/agents/mod.rs | 101 ++++++++++++++++++++- src/cli/commands/agents/pages/compact.md | 1 + src/cli/commands/agents/pages/concepts.md | 1 + src/cli/commands/agents/pages/emit.md | 1 + src/cli/commands/agents/pages/ls.md | 1 + src/cli/commands/agents/pages/pitfalls.md | 1 + src/cli/commands/agents/pages/praise.md | 1 + src/cli/commands/agents/pages/record.md | 1 + src/cli/commands/agents/pages/reply.md | 1 + src/cli/commands/agents/pages/resolve.md | 1 + src/cli/commands/agents/pages/review.md | 1 + src/cli/commands/agents/pages/show.md | 1 + src/cli/commands/agents/pages/workflows.md | 1 + tests/cli_integration.rs | 25 +++++ 14 files changed, 135 insertions(+), 3 deletions(-) create mode 100644 src/cli/commands/agents/pages/compact.md create mode 100644 src/cli/commands/agents/pages/concepts.md create mode 100644 src/cli/commands/agents/pages/emit.md create mode 100644 src/cli/commands/agents/pages/ls.md create mode 100644 src/cli/commands/agents/pages/pitfalls.md create mode 100644 src/cli/commands/agents/pages/praise.md create mode 100644 src/cli/commands/agents/pages/record.md create mode 100644 src/cli/commands/agents/pages/reply.md create mode 100644 src/cli/commands/agents/pages/resolve.md create mode 100644 src/cli/commands/agents/pages/review.md create mode 100644 src/cli/commands/agents/pages/show.md create mode 100644 src/cli/commands/agents/pages/workflows.md diff --git a/src/cli/commands/agents/mod.rs b/src/cli/commands/agents/mod.rs index eac35d2..f2078ac 100644 --- a/src/cli/commands/agents/mod.rs +++ b/src/cli/commands/agents/mod.rs @@ -6,17 +6,112 @@ pub struct Args { pub topic: Option, } +struct Page { + name: &'static str, + summary: &'static str, + body: &'static str, +} + const OVERVIEW: &str = include_str!("pages/_overview.md"); +const PAGES: &[Page] = &[ + Page { + name: "concepts", + summary: "Annotation model, kinds, supersession, IDs, .qual layout", + body: include_str!("pages/concepts.md"), + }, + Page { + name: "workflows", + summary: "Worked recipes for common tasks", + body: include_str!("pages/workflows.md"), + }, + Page { + name: "pitfalls", + summary: "Common mistakes agents make with qualifier", + body: include_str!("pages/pitfalls.md"), + }, + Page { + name: "record", + summary: "Record a new annotation", + body: include_str!("pages/record.md"), + }, + Page { + name: "reply", + summary: "Reply to an existing record", + body: include_str!("pages/reply.md"), + }, + Page { + name: "resolve", + summary: "Resolve (close) a record", + body: include_str!("pages/resolve.md"), + }, + Page { + name: "emit", + summary: "Emit a raw record of any type", + body: include_str!("pages/emit.md"), + }, + Page { + name: "show", + summary: "Show annotations for an artifact", + body: include_str!("pages/show.md"), + }, + Page { + name: "ls", + summary: "List artifacts by kind", + body: include_str!("pages/ls.md"), + }, + Page { + name: "praise", + summary: "Show who annotated an artifact and why", + body: include_str!("pages/praise.md"), + }, + Page { + name: "review", + summary: "Check freshness of annotations against current code", + body: include_str!("pages/review.md"), + }, + Page { + name: "compact", + summary: "Compact a .qual file", + body: include_str!("pages/compact.md"), + }, +]; + +fn topic_names() -> String { + PAGES + .iter() + .map(|p| p.name) + .collect::>() + .join(", ") +} + pub fn run(args: Args) -> crate::Result<()> { match args.topic.as_deref() { None => { - print!("{OVERVIEW}"); + print!("{}", render_overview()); Ok(()) } Some(name) => { - eprintln!("qualifier agents: no such topic '{name}'. Available: (none yet)"); - std::process::exit(2); + if let Some(page) = PAGES.iter().find(|p| p.name == name) { + print!("{}", page.body); + Ok(()) + } else { + eprintln!( + "qualifier agents: no such topic '{name}'. Available: {}", + topic_names() + ); + std::process::exit(2); + } } } } + +fn render_overview() -> String { + // Replace the {{TOPICS}} sentinel with a `name — summary` listing. + let topics_block: String = PAGES + .iter() + .map(|p| format!("- `{}` — {}", p.name, p.summary)) + .collect::>() + .join("\n"); + OVERVIEW.replace("{{TOPICS}}", &topics_block) +} diff --git a/src/cli/commands/agents/pages/compact.md b/src/cli/commands/agents/pages/compact.md new file mode 100644 index 0000000..c1445b2 --- /dev/null +++ b/src/cli/commands/agents/pages/compact.md @@ -0,0 +1 @@ +qualifier agents — compact page (placeholder; replace in Task 7/8) diff --git a/src/cli/commands/agents/pages/concepts.md b/src/cli/commands/agents/pages/concepts.md new file mode 100644 index 0000000..1138566 --- /dev/null +++ b/src/cli/commands/agents/pages/concepts.md @@ -0,0 +1 @@ +qualifier agents — concepts page (placeholder; replace in Task 7/8) diff --git a/src/cli/commands/agents/pages/emit.md b/src/cli/commands/agents/pages/emit.md new file mode 100644 index 0000000..bb84515 --- /dev/null +++ b/src/cli/commands/agents/pages/emit.md @@ -0,0 +1 @@ +qualifier agents — emit page (placeholder; replace in Task 7/8) diff --git a/src/cli/commands/agents/pages/ls.md b/src/cli/commands/agents/pages/ls.md new file mode 100644 index 0000000..1e2888c --- /dev/null +++ b/src/cli/commands/agents/pages/ls.md @@ -0,0 +1 @@ +qualifier agents — ls page (placeholder; replace in Task 7/8) diff --git a/src/cli/commands/agents/pages/pitfalls.md b/src/cli/commands/agents/pages/pitfalls.md new file mode 100644 index 0000000..283116e --- /dev/null +++ b/src/cli/commands/agents/pages/pitfalls.md @@ -0,0 +1 @@ +qualifier agents — pitfalls page (placeholder; replace in Task 7/8) diff --git a/src/cli/commands/agents/pages/praise.md b/src/cli/commands/agents/pages/praise.md new file mode 100644 index 0000000..518aebf --- /dev/null +++ b/src/cli/commands/agents/pages/praise.md @@ -0,0 +1 @@ +qualifier agents — praise page (placeholder; replace in Task 7/8) diff --git a/src/cli/commands/agents/pages/record.md b/src/cli/commands/agents/pages/record.md new file mode 100644 index 0000000..fbefbce --- /dev/null +++ b/src/cli/commands/agents/pages/record.md @@ -0,0 +1 @@ +qualifier agents — record page (placeholder; replace in Task 7/8) diff --git a/src/cli/commands/agents/pages/reply.md b/src/cli/commands/agents/pages/reply.md new file mode 100644 index 0000000..e3d7841 --- /dev/null +++ b/src/cli/commands/agents/pages/reply.md @@ -0,0 +1 @@ +qualifier agents — reply page (placeholder; replace in Task 7/8) diff --git a/src/cli/commands/agents/pages/resolve.md b/src/cli/commands/agents/pages/resolve.md new file mode 100644 index 0000000..d3d163f --- /dev/null +++ b/src/cli/commands/agents/pages/resolve.md @@ -0,0 +1 @@ +qualifier agents — resolve page (placeholder; replace in Task 7/8) diff --git a/src/cli/commands/agents/pages/review.md b/src/cli/commands/agents/pages/review.md new file mode 100644 index 0000000..a652b62 --- /dev/null +++ b/src/cli/commands/agents/pages/review.md @@ -0,0 +1 @@ +qualifier agents — review page (placeholder; replace in Task 7/8) diff --git a/src/cli/commands/agents/pages/show.md b/src/cli/commands/agents/pages/show.md new file mode 100644 index 0000000..17d1637 --- /dev/null +++ b/src/cli/commands/agents/pages/show.md @@ -0,0 +1 @@ +qualifier agents — show page (placeholder; replace in Task 7/8) diff --git a/src/cli/commands/agents/pages/workflows.md b/src/cli/commands/agents/pages/workflows.md new file mode 100644 index 0000000..106c553 --- /dev/null +++ b/src/cli/commands/agents/pages/workflows.md @@ -0,0 +1 @@ +qualifier agents — workflows page (placeholder; replace in Task 7/8) diff --git a/tests/cli_integration.rs b/tests/cli_integration.rs index 321a0ab..8c44982 100644 --- a/tests/cli_integration.rs +++ b/tests/cli_integration.rs @@ -2339,3 +2339,28 @@ fn test_agents_unknown_topic_exits_2() { "stderr should name the bad topic: {stderr}" ); } + +#[test] +fn test_agents_concepts_topic_prints_body() { + let dir = tempfile::tempdir().unwrap(); + let (stdout, stderr, code) = run_qualifier(dir.path(), &["agents", "concepts"]); + assert_eq!(code, 0, "agents concepts should succeed: stderr={stderr}"); + assert!(!stdout.is_empty(), "should print body"); +} + +#[test] +fn test_agents_all_registered_topics_render() { + // Each topic in the registry must produce non-empty stdout with exit 0. + // If you add a topic, add it here too. + let topics = [ + "concepts", "workflows", "pitfalls", + "record", "reply", "resolve", "emit", + "show", "ls", "praise", "review", "compact", + ]; + let dir = tempfile::tempdir().unwrap(); + for topic in topics { + let (stdout, stderr, code) = run_qualifier(dir.path(), &["agents", topic]); + assert_eq!(code, 0, "agents {topic} should succeed: stderr={stderr}"); + assert!(!stdout.is_empty(), "agents {topic} should print body"); + } +} From 6f81fab3d446f08bd79276e16cc4cb7713227538 Mon Sep 17 00:00:00 2001 From: Alex Kesling Date: Wed, 6 May 2026 10:34:01 -0400 Subject: [PATCH 06/26] test(agents): drop redundant single-topic test, parameterized covers it --- tests/cli_integration.rs | 8 -------- 1 file changed, 8 deletions(-) diff --git a/tests/cli_integration.rs b/tests/cli_integration.rs index 8c44982..975dba0 100644 --- a/tests/cli_integration.rs +++ b/tests/cli_integration.rs @@ -2340,14 +2340,6 @@ fn test_agents_unknown_topic_exits_2() { ); } -#[test] -fn test_agents_concepts_topic_prints_body() { - let dir = tempfile::tempdir().unwrap(); - let (stdout, stderr, code) = run_qualifier(dir.path(), &["agents", "concepts"]); - assert_eq!(code, 0, "agents concepts should succeed: stderr={stderr}"); - assert!(!stdout.is_empty(), "should print body"); -} - #[test] fn test_agents_all_registered_topics_render() { // Each topic in the registry must produce non-empty stdout with exit 0. From df6953078feae8580c8a0cc4a78b786864010c61 Mon Sep 17 00:00:00 2001 From: Alex Kesling Date: Wed, 6 May 2026 10:36:06 -0400 Subject: [PATCH 07/26] feat(agents): render overview with topic index Replace placeholder _overview.md with real orientation prose containing {{TOPICS}}, and add integration + unit tests to verify substitution. --- src/cli/commands/agents/mod.rs | 10 ++++ src/cli/commands/agents/pages/_overview.md | 56 +++++++++++++++++++++- tests/cli_integration.rs | 23 +++++++++ 3 files changed, 88 insertions(+), 1 deletion(-) diff --git a/src/cli/commands/agents/mod.rs b/src/cli/commands/agents/mod.rs index f2078ac..88c51e2 100644 --- a/src/cli/commands/agents/mod.rs +++ b/src/cli/commands/agents/mod.rs @@ -115,3 +115,13 @@ fn render_overview() -> String { .join("\n"); OVERVIEW.replace("{{TOPICS}}", &topics_block) } + +#[cfg(test)] +mod tests { + #[test] + fn overview_contains_topics_sentinel() { + // If this fails, the orientation page lost the {{TOPICS}} substitution + // anchor and the rendered overview will no longer list children. + assert!(super::OVERVIEW.contains("{{TOPICS}}")); + } +} diff --git a/src/cli/commands/agents/pages/_overview.md b/src/cli/commands/agents/pages/_overview.md index a386558..353d229 100644 --- a/src/cli/commands/agents/pages/_overview.md +++ b/src/cli/commands/agents/pages/_overview.md @@ -1 +1,55 @@ -qualifier agents — orientation page (placeholder) +# qualifier — guide for AI coding agents + +You are an AI coding agent in a user's repository. The `qualifier` CLI is +installed. This page tells you what qualifier is, when to use it, and how to +get more detail on any specific feature. + +## What it is + +Qualifier records *annotations* — structured, content-addressed notes about +software artifacts (files, directories, line ranges). Annotations live in +`.qual` files alongside the code, are intended to be committed to version +control, and form a durable, reviewable record of quality observations +attached to specific places in the codebase. + +## When to use it + +- You found a bug, smell, risk, or stylistic concern that survives this + edit and is worth surfacing for whoever touches the code next. Record it. +- You want to praise a piece of code so future readers know it was + intentional, not accidental. Record it. +- An earlier annotation no longer applies (the code changed, the concern + was addressed). Resolve it. + +## When NOT to use it + +- One-off scratch debugging notes. Use scratch space. +- Information that belongs in the commit message (what *this* change does + and why). Use git. +- Project-wide policy or architecture decisions. Those belong in + `docs/` or an ADR, not as an annotation on a file. + +## Quickstart + +```bash +# Record a concern about a function in src/foo.rs +qualifier record concern src/foo.rs --message "tight coupling to the cache" + +# See what's annotated on a file +qualifier show src/foo.rs + +# After fixing it, resolve the annotation by id-prefix or location +qualifier resolve src/foo.rs --message "refactored to inject the cache" +``` + +## Available topics + +Run `qualifier agents ` for any of: + +{{TOPICS}} + +## Reference + +For exact flag tables on any subcommand, run `qualifier --help`. +For the JSONL wire format and library API, see `SPEC.md` in this repo (if +present) or the published spec. diff --git a/tests/cli_integration.rs b/tests/cli_integration.rs index 975dba0..2d35ecf 100644 --- a/tests/cli_integration.rs +++ b/tests/cli_integration.rs @@ -2356,3 +2356,26 @@ fn test_agents_all_registered_topics_render() { assert!(!stdout.is_empty(), "agents {topic} should print body"); } } + +#[test] +fn test_agents_overview_renders_topics_index() { + let dir = tempfile::tempdir().unwrap(); + let (stdout, _stderr, code) = run_qualifier(dir.path(), &["agents"]); + assert_eq!(code, 0); + // Every registered topic name should appear in the rendered overview. + for topic in [ + "concepts", "workflows", "pitfalls", + "record", "reply", "resolve", "emit", + "show", "ls", "praise", "review", "compact", + ] { + assert!( + stdout.contains(topic), + "overview should mention topic '{topic}': {stdout}" + ); + } + // The literal sentinel must not leak through. + assert!( + !stdout.contains("{{TOPICS}}"), + "sentinel should be substituted: {stdout}" + ); +} From bb4a43e02ef93690e5572355d6ffa354b8eeea18 Mon Sep 17 00:00:00 2001 From: Alex Kesling Date: Wed, 6 May 2026 10:46:55 -0400 Subject: [PATCH 08/26] feat(agents): surface agents group at top of --help --- src/cli/mod.rs | 3 +++ tests/cli_integration.rs | 16 ++++++++++++++++ 2 files changed, 19 insertions(+) diff --git a/src/cli/mod.rs b/src/cli/mod.rs index 85d8756..d85980b 100644 --- a/src/cli/mod.rs +++ b/src/cli/mod.rs @@ -14,6 +14,9 @@ const HELP_TEMPLATE: &str = "\ {about-with-newline} {usage-heading} {usage} +For AI agents: + agents Self-contained guide for AI coding agents (start here) + Record observations: record Record an annotation: `qualifier record [message]` reply Reply to an existing record (id-prefix or location) diff --git a/tests/cli_integration.rs b/tests/cli_integration.rs index 2d35ecf..af0464e 100644 --- a/tests/cli_integration.rs +++ b/tests/cli_integration.rs @@ -2379,3 +2379,19 @@ fn test_agents_overview_renders_topics_index() { "sentinel should be substituted: {stdout}" ); } + +#[test] +fn test_top_level_help_shows_agents_group() { + let dir = tempfile::tempdir().unwrap(); + let (stdout, _stderr, code) = run_qualifier(dir.path(), &["--help"]); + assert_eq!(code, 0); + assert!( + stdout.contains("For AI agents:"), + "help should show the agents group header: {stdout}" + ); + // The agents row should appear under that header, with the "start here" nudge. + assert!( + stdout.contains("agents") && stdout.contains("start here"), + "help should mention the agents subcommand: {stdout}" + ); +} From 0a37d3e8c013de2f285e5931d9debc4e4975421c Mon Sep 17 00:00:00 2001 From: Alex Kesling Date: Wed, 6 May 2026 10:48:15 -0400 Subject: [PATCH 09/26] style: cargo fmt fixes for agents module --- src/cli/commands/agents/mod.rs | 6 +----- tests/cli_integration.rs | 30 ++++++++++++++++++++++++------ 2 files changed, 25 insertions(+), 11 deletions(-) diff --git a/src/cli/commands/agents/mod.rs b/src/cli/commands/agents/mod.rs index 88c51e2..9d37685 100644 --- a/src/cli/commands/agents/mod.rs +++ b/src/cli/commands/agents/mod.rs @@ -78,11 +78,7 @@ const PAGES: &[Page] = &[ ]; fn topic_names() -> String { - PAGES - .iter() - .map(|p| p.name) - .collect::>() - .join(", ") + PAGES.iter().map(|p| p.name).collect::>().join(", ") } pub fn run(args: Args) -> crate::Result<()> { diff --git a/tests/cli_integration.rs b/tests/cli_integration.rs index af0464e..6bc78e6 100644 --- a/tests/cli_integration.rs +++ b/tests/cli_integration.rs @@ -2345,9 +2345,18 @@ fn test_agents_all_registered_topics_render() { // Each topic in the registry must produce non-empty stdout with exit 0. // If you add a topic, add it here too. let topics = [ - "concepts", "workflows", "pitfalls", - "record", "reply", "resolve", "emit", - "show", "ls", "praise", "review", "compact", + "concepts", + "workflows", + "pitfalls", + "record", + "reply", + "resolve", + "emit", + "show", + "ls", + "praise", + "review", + "compact", ]; let dir = tempfile::tempdir().unwrap(); for topic in topics { @@ -2364,9 +2373,18 @@ fn test_agents_overview_renders_topics_index() { assert_eq!(code, 0); // Every registered topic name should appear in the rendered overview. for topic in [ - "concepts", "workflows", "pitfalls", - "record", "reply", "resolve", "emit", - "show", "ls", "praise", "review", "compact", + "concepts", + "workflows", + "pitfalls", + "record", + "reply", + "resolve", + "emit", + "show", + "ls", + "praise", + "review", + "compact", ] { assert!( stdout.contains(topic), From a286f3b93a6cd1d1f5f4b140a6532ea78edf8eac Mon Sep 17 00:00:00 2001 From: Alex Kesling Date: Wed, 6 May 2026 10:52:46 -0400 Subject: [PATCH 10/26] =?UTF-8?q?docs(agents):=20write=20topical=20pages?= =?UTF-8?q?=20=E2=80=94=20concepts,=20workflows,=20pitfalls?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/cli/commands/agents/pages/concepts.md | 157 ++++++++++++++++++++- src/cli/commands/agents/pages/pitfalls.md | 43 +++++- src/cli/commands/agents/pages/workflows.md | 126 ++++++++++++++++- 3 files changed, 323 insertions(+), 3 deletions(-) diff --git a/src/cli/commands/agents/pages/concepts.md b/src/cli/commands/agents/pages/concepts.md index 1138566..0a09e26 100644 --- a/src/cli/commands/agents/pages/concepts.md +++ b/src/cli/commands/agents/pages/concepts.md @@ -1 +1,156 @@ -qualifier agents — concepts page (placeholder; replace in Task 7/8) +# qualifier — key concepts + +## The annotation envelope + +Every qualifier record is a single-line JSON object using the **Metabox envelope**. +The envelope fields appear in a fixed order and are identical for every record type: + +```json +{ + "metabox": "1", + "type": "annotation", + "subject": "src/auth.rs", + "issuer": "mailto:agent@example.com", + "issuer_type": "ai", + "created_at": "2026-03-01T10:00:00Z", + "id": "", + "body": { ... } +} +``` + +Field notes: + +- `metabox` — always `"1"`. Validation rejects any other value. +- `type` — `"annotation"` (default, may be omitted in files), `"epoch"`, `"dependency"`, or any URI string for custom types. +- `subject` — the artifact being annotated. A path (`src/auth.rs`), module, build target, or package name. Opaque to qualifier. +- `issuer` — who or what wrote the record. Must be a URI (see §Issuer URIs below). +- `issuer_type` — optional; one of `human`, `ai`, `tool`, `unknown`. +- `created_at` — RFC 3339 timestamp. +- `id` — BLAKE3 hash of the canonical form (see §Content addressing). Never hand-edit. +- `body` — type-specific payload; body fields are serialized in alphabetical order. + +You do not construct this JSON by hand. Use `qualifier record`, `qualifier reply`, +`qualifier resolve`, or `qualifier emit` — they compute the correct `id` for you. + +## Kinds + +The `kind` field in an annotation body identifies the nature of the signal. +Choose the most specific kind that fits; downstream filtering and polarity depend on it. + +| Kind | When to use | +|--------------|-------------| +| `concern` | A non-blocking issue worth tracking (e.g., a code smell, a risk, a TODO that matters). | +| `blocker` | A blocking issue that must be resolved before release. | +| `fail` | The artifact does not meet a stated quality bar. | +| `pass` | The artifact meets a stated quality bar. | +| `comment` | An observation or discussion point with no polarity impact. | +| `praise` | Positive recognition — marks intentional design for future readers. | +| `suggestion` | A proposed improvement, typically paired with `--suggested-fix`. | +| `waiver` | An acknowledged issue explicitly accepted with rationale. | +| `resolve` | Closes a prior record via supersession. Prefer `qualifier resolve` instead of recording this directly. | + +Any other string is valid as a custom kind. The CLI warns if it looks like a +typo of a built-in kind (edit distance ≤ 2). + +Polarity summary: `pass`, `praise`, `waiver` are positive; `comment` and `resolve` +are neutral; `concern`, `suggestion`, `fail`, `blocker` are negative. + +## Supersession + +Records are **immutable once written**. To update or retract a signal, write a +new annotation with `supersedes` pointing to the old record's `id`. + +Rules the system enforces: + +- The superseding and superseded records **must share the same `subject`**. + Cross-subject supersession is rejected. +- Supersession chains must be **acyclic**. A → B → A is detected and rejected. +- Only the **tip** of a supersession chain is active. Superseded records are + hidden by `qualifier show`, `qualifier ls`, and `qualifier review`. +- Dangling `supersedes` references (pointing to IDs not present in the + current file set) are allowed — the referencing record stays active. + +The canonical way to close an issue is `qualifier resolve `, which writes +a `resolve`-kind annotation that supersedes the target. The original record +is no longer surfaced; the resolve record stands as a visible tombstone. + +## `.qual` file layout and discovery + +A `.qual` file is UTF-8 JSONL (one record per line). Three layouts coexist: + +| Layout | Example file | Trade-off | +|--------|-------------|-----------| +| Per-directory (recommended) | `src/.qual` | Clean tree, good merge behavior | +| Per-file | `src/parser.rs.qual` | Maximum merge isolation, noisy tree | +| Per-project | `.qual` at repo root | Simplest, but high merge contention | + +When you run `qualifier record concern src/auth.rs ...`, the CLI writes to +`src/.qual` by default (or `src/auth.rs.qual` if that file already exists). +Override with `--file `. + +**Discovery** walks from the project root (found by searching upward for +`.git`, `.hg`, `.jj`, `.pijul`, `_FOSSIL_`, or `.svn`). It collects every +file named `.qual` or ending in `.qual`. + +**Ignore rules** are applied by default: +- `.gitignore` at any level (including global gitignore and `.git/info/exclude`). +- `.qualignore` — same syntax as `.gitignore`, qualifier-specific. Place + anywhere in the tree to exclude vendored code, generated files, or examples. + +Pass `--no-ignore` to bypass all ignore rules on any discovery command +(`show`, `ls`, `compact`, `review`, `praise`). + +Hidden directories (e.g., `.git`, `.vscode`) are always skipped. Hidden +*files* like `.qual` are not skipped. + +## Issuer URIs and `issuer_type` + +The `issuer` field identifies who or what created a record. It must be a URI +— specifically, it must contain `:`. Validation rejects bare strings without a colon. + +Common forms: + +``` +mailto:agent@example.com # typical for an AI agent or human +https://ci.example.com # CI job or tool with a URL +urn:qualifier:compact # reserved for the compact command +``` + +When `--issuer` is omitted, the CLI detects the VCS user identity: +- Git: `git config user.email` → wrapped as `mailto:` +- Mercurial: `hg config ui.username` → wrapped as `mailto:` +- Fallback: `mailto:$USER@localhost` + +As an agent, always pass `--issuer "mailto:your-agent-id@example.com"` and +`--issuer-type ai` so records are traceable back to you. + +The `issuer_type` field is optional but strongly recommended: + +| Value | Use when | +|-----------|----------| +| `human` | A person wrote this record | +| `ai` | An AI agent wrote this record | +| `tool` | An automated scanner or CI job wrote this record | +| `unknown` | Origin is unclear | + +## Content addressing (BLAKE3) + +A record's `id` is the BLAKE3 hash of its **Metabox Canonical Form (MCF)**: +the record serialized with envelope fields in fixed order, body fields in +alphabetical order, all optional absent fields omitted, and `id` set to `""` +during hashing. + +Implications: + +- Identical inputs always produce the same `id`, on every machine and implementation. +- Changing any canonical field (summary, kind, span, issuer, timestamp, ...) changes the `id`. +- **Do not hand-edit `.qual` files.** Editing a field invalidates the `id`, + and `qualifier` will reject the record. Use `qualifier record`, `qualifier emit`, + or the compactor to write records. +- Span normalization is part of MCF: a span with no `end` has `end` set equal + to `start` before hashing, so `{"start":{"line":42}}` and + `{"start":{"line":42},"end":{"line":42}}` produce the same `id`. + +The `id` is a stable handle you can use to target a record with +`qualifier reply ` or `qualifier resolve `. +Four characters of prefix are the minimum; use more if the file has many records. diff --git a/src/cli/commands/agents/pages/pitfalls.md b/src/cli/commands/agents/pages/pitfalls.md index 283116e..bb66951 100644 --- a/src/cli/commands/agents/pages/pitfalls.md +++ b/src/cli/commands/agents/pages/pitfalls.md @@ -1 +1,42 @@ -qualifier agents — pitfalls page (placeholder; replace in Task 7/8) +# qualifier — common pitfalls + +- **Recording without `--span` when the concern is about specific lines.** + A concern about a particular function or block is most actionable when it + points at the exact lines. Without a span, the annotation attaches to the + whole file and `qualifier review` cannot detect drift when the code changes. + Do this instead: `qualifier record concern src/foo.rs:42:58 "..."`. + +- **Conflating `reply` (add context) with `resolve` (close the issue).** + `qualifier reply` writes a new `comment` that *references* the target — both + records remain active. `qualifier resolve` writes a `resolve` record that + *supersedes* the target, removing it from active views. Use `reply` when you + have more information to add; use `resolve` when the concern is addressed. + +- **Recording an annotation when a commit message would do.** + Annotations are for observations that survive the current change and need to + be visible to whoever touches the code next. If the information is only + relevant to *this* commit (what changed and why), put it in the commit + message instead. Annotations accumulate; don't add noise that expires + immediately. + +- **Editing `.qual` files by hand instead of using `record` or `emit`.** + Record IDs are BLAKE3 hashes of the canonical form. Editing any field + directly invalidates the `id`, and qualifier will reject the record on next + read. Always use `qualifier record`, `qualifier emit`, `qualifier reply`, + or `qualifier resolve` to write records. + +- **Choosing `bug` or another custom kind when a built-in kind fits.** + The built-in kinds (`concern`, `blocker`, `fail`, `pass`, `comment`, + `praise`, `suggestion`, `waiver`, `resolve`) carry polarity semantics used + by downstream tools for filtering and display. A custom kind like `bug` is + valid but invisible to anything that filters by polarity or standard kind. + Prefer `concern` for non-blocking bugs and `blocker` for must-fix issues; + reserve custom kinds for genuinely domain-specific signals. + +- **Using a non-URI issuer (must contain `:`).** + Validation rejects any `issuer` value that does not contain a colon. A bare + email address like `agent@example.com` will fail. Wrap it: + `--issuer "mailto:agent@example.com"`. If your agent has an HTTP identity, + use that directly: `--issuer "https://agents.example.com/review-bot"`. + + diff --git a/src/cli/commands/agents/pages/workflows.md b/src/cli/commands/agents/pages/workflows.md index 106c553..c2f6e6b 100644 --- a/src/cli/commands/agents/pages/workflows.md +++ b/src/cli/commands/agents/pages/workflows.md @@ -1 +1,125 @@ -qualifier agents — workflows page (placeholder; replace in Task 7/8) +# qualifier — common workflows + +## Record a finding during code review + +When you notice a concern, smell, or risk in code you're reviewing, record it +immediately. Use `--span` to pin it to the exact lines so it stays actionable +after future edits, and `--suggested-fix` if you know what to do. + +```bash +qualifier record concern src/auth.rs:42:58 \ + "Token validation skips expiry check when issuer is internal" \ + --suggested-fix "Check exp claim unconditionally; remove the issuer shortcut" \ + --tag security \ + --issuer "mailto:review-agent@example.com" \ + --issuer-type ai +``` + +The CLI writes to `src/.qual` (or `src/auth.rs.qual` if it exists), prints +the new record's id, and exits zero. Use `qualifier show src/auth.rs` to +verify the annotation is visible. + +## Reply to an existing observation with new info + +When a record already exists and you have additional context — a root cause, +a related finding, a status update — reply rather than recording a new +standalone annotation. A reply threads to the original so readers see the +conversation together. + +`qualifier reply` accepts either a 4+ character id-prefix or a location +(`file:line`). The default kind is `comment`; override with `--kind`. + +```bash +# The original concern has id starting with a1b2c3d4 +qualifier reply a1b2 \ + "Root cause: the issuer allow-list is populated from an env var that CI never sets" \ + --issuer "mailto:review-agent@example.com" \ + --issuer-type ai + +# Or target by location if you know the span +qualifier reply src/auth.rs:42 \ + "Root cause: the issuer allow-list is populated from an env var that CI never sets" \ + --issuer "mailto:review-agent@example.com" \ + --issuer-type ai +``` + +Both forms resolve to the most-recent active record at that location. +If multiple records share the newest timestamp, the CLI exits non-zero with +a disambiguation list showing id-prefix, kind, line, and summary. + +## Resolve a finding once it's addressed + +When the code has been fixed, close the annotation so it no longer appears +in active views. `qualifier resolve` writes a `resolve`-kind record that +supersedes the target; the original concern is removed from `qualifier show` +output but the full history remains in VCS. + +```bash +qualifier resolve a1b2 "Fixed in commit abc1234 — expiry check now unconditional" \ + --issuer "mailto:review-agent@example.com" \ + --issuer-type ai +``` + +The message is optional; it defaults to `"Resolved"`. Prefer a meaningful +message so the tombstone is useful to future readers. + +After resolving, confirm with `qualifier show src/auth.rs` — the original +concern should no longer appear (unless you pass `--all`). + +## Triage stale annotations after a refactor + +Span-addressed annotations include a `content_hash` (BLAKE3 of the spanned +lines at write time). After a refactor, those hashes may no longer match the +current file content. `qualifier review` checks this and reports each +annotation's freshness status. + +```bash +# Check all annotations in the project +qualifier review + +# Check a specific subject +qualifier review src/auth.rs + +# Machine-readable output for programmatic triage +qualifier review --format json +``` + +Possible statuses: + +- `FRESH` — the spanned lines are unchanged. No action needed. +- `DRIFTED` — the lines changed. Review whether the annotation still applies; + resolve it if addressed, or record a new annotation at the updated span. +- `MISSING` — the file or span no longer exists. The annotation is likely stale; + resolve it. + +Annotations without a span or without a `content_hash` are not checked. +Whole-file annotations and older records written without `--span` fall into +this category. + +## Compact a noisy `.qual` file + +Append-only files accumulate superseded records over time. `qualifier compact` +prunes them. With `--snapshot`, it goes further and collapses all surviving +records for each subject into a single epoch record, making the file minimal. + +```bash +# Preview without writing +qualifier compact src/auth.rs --dry-run + +# Prune superseded records only +qualifier compact src/auth.rs + +# Collapse to a single epoch per subject (smallest possible file) +qualifier compact src/auth.rs --snapshot + +# Compact every .qual file in the project +qualifier compact --all + +# Compact everything, preview first +qualifier compact --all --dry-run +``` + +Compaction is always explicit and user-initiated; it never happens silently. +Records of unrecognized types are preserved unchanged. After compaction the +file is still valid JSONL — no special reader support is needed. VCS history +retains the full pre-compaction records if you need to trace back. From 11c8435a4ceb0fd53977b37c63b83f2675a7af62 Mon Sep 17 00:00:00 2001 From: Alex Kesling Date: Wed, 6 May 2026 10:57:03 -0400 Subject: [PATCH 11/26] docs(agents): tighten topical-page accuracy after review --- src/cli/commands/agents/pages/concepts.md | 6 ++++-- src/cli/commands/agents/pages/workflows.md | 4 +++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/cli/commands/agents/pages/concepts.md b/src/cli/commands/agents/pages/concepts.md index 0a09e26..b5dc6aa 100644 --- a/src/cli/commands/agents/pages/concepts.md +++ b/src/cli/commands/agents/pages/concepts.md @@ -5,6 +5,8 @@ Every qualifier record is a single-line JSON object using the **Metabox envelope**. The envelope fields appear in a fixed order and are identical for every record type: +(formatted for readability; each record is a single line in the .qual file) + ```json { "metabox": "1", @@ -21,7 +23,7 @@ The envelope fields appear in a fixed order and are identical for every record t Field notes: - `metabox` — always `"1"`. Validation rejects any other value. -- `type` — `"annotation"` (default, may be omitted in files), `"epoch"`, `"dependency"`, or any URI string for custom types. +- `type` — `"annotation"` (default, may be omitted in files), `"epoch"`, `"dependency"`, or any non-empty string for custom types (URI recommended). - `subject` — the artifact being annotated. A path (`src/auth.rs`), module, build target, or package name. Opaque to qualifier. - `issuer` — who or what wrote the record. Must be a URI (see §Issuer URIs below). - `issuer_type` — optional; one of `human`, `ai`, `tool`, `unknown`. @@ -66,7 +68,7 @@ Rules the system enforces: Cross-subject supersession is rejected. - Supersession chains must be **acyclic**. A → B → A is detected and rejected. - Only the **tip** of a supersession chain is active. Superseded records are - hidden by `qualifier show`, `qualifier ls`, and `qualifier review`. + hidden by `qualifier show` and `qualifier ls`. - Dangling `supersedes` references (pointing to IDs not present in the current file set) are allowed — the referencing record stays active. diff --git a/src/cli/commands/agents/pages/workflows.md b/src/cli/commands/agents/pages/workflows.md index c2f6e6b..f9dbd5b 100644 --- a/src/cli/commands/agents/pages/workflows.md +++ b/src/cli/commands/agents/pages/workflows.md @@ -43,7 +43,7 @@ qualifier reply src/auth.rs:42 \ --issuer-type ai ``` -Both forms resolve to the most-recent active record at that location. +The id-prefix form matches by prefix. The location form resolves to the most-recent active record at that location. If multiple records share the newest timestamp, the CLI exits non-zero with a disambiguation list showing id-prefix, kind, line, and summary. @@ -102,6 +102,8 @@ Append-only files accumulate superseded records over time. `qualifier compact` prunes them. With `--snapshot`, it goes further and collapses all surviving records for each subject into a single epoch record, making the file minimal. +Pass the artifact (subject) name — typically the same path you used when recording — not the path to a .qual file. + ```bash # Preview without writing qualifier compact src/auth.rs --dry-run From 260d17ac79acdce02b5db5a142a70c2dfa1c303b Mon Sep 17 00:00:00 2001 From: Alex Kesling Date: Wed, 6 May 2026 11:05:22 -0400 Subject: [PATCH 12/26] docs(agents): write per-subcommand pages MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replaces one-line placeholders in all 9 per-subcommand agent pages with structured content following the Purpose / When to use it / Common invocations / Flags worth knowing / Gotchas template (57–72 lines each). All flags verified against source and --help output; removed features (score, init, graph) are not mentioned. --- src/cli/commands/agents/pages/compact.md | 69 +++++++++++++++++++++- src/cli/commands/agents/pages/emit.md | 71 ++++++++++++++++++++++- src/cli/commands/agents/pages/ls.md | 58 ++++++++++++++++++- src/cli/commands/agents/pages/praise.md | 61 +++++++++++++++++++- src/cli/commands/agents/pages/record.md | 73 +++++++++++++++++++++++- src/cli/commands/agents/pages/reply.md | 67 +++++++++++++++++++++- src/cli/commands/agents/pages/resolve.md | 64 ++++++++++++++++++++- src/cli/commands/agents/pages/review.md | 61 +++++++++++++++++++- src/cli/commands/agents/pages/show.md | 72 ++++++++++++++++++++++- 9 files changed, 587 insertions(+), 9 deletions(-) diff --git a/src/cli/commands/agents/pages/compact.md b/src/cli/commands/agents/pages/compact.md index c1445b2..2d2f7a9 100644 --- a/src/cli/commands/agents/pages/compact.md +++ b/src/cli/commands/agents/pages/compact.md @@ -1 +1,68 @@ -qualifier agents — compact page (placeholder; replace in Task 7/8) +# qualifier compact + +## Purpose + +Prune superseded records from a `.qual` file, or collapse the full history +to a single epoch record. + +## When to use it + +`.qual` files grow as annotations are added, superseded, and resolved. +`compact` is a maintenance command for trimming that growth. Run it +occasionally to keep `.qual` files readable and diff-friendly — not after +every annotation. The default prune mode removes records that have been +superseded by a later record in the same file. The `--snapshot` mode goes +further, collapsing all remaining records into a single epoch record that +preserves the final state as a summary. Snapshot is appropriate when you +want a clean break in the history (e.g., after a major release audit); +prune alone is the lighter-weight routine operation. + +## Common invocations + +```bash +# Prune superseded records for one artifact +qualifier compact src/auth.rs + +# Preview what would be pruned, without writing +qualifier compact src/auth.rs --dry-run + +# Snapshot one artifact's history to a single epoch record +qualifier compact src/auth.rs --snapshot + +# Compact every .qual file in the project +qualifier compact --all + +# Snapshot all files (use with care — removes detailed history) +qualifier compact --all --snapshot +``` + +## Flags worth knowing + +**`--dry-run`** prints what would be removed or collapsed without modifying +any files. Always run this first when operating on a shared repo so you can +review the impact before committing. + +**`--snapshot`** collapses all remaining (non-superseded) records into one +`epoch` record with `issuer: "urn:qualifier:compact"` and +`issuer_type: tool`. The original individual annotation IDs are no longer +present after snapshotting, so downstream tools cannot reference them. Use +`--dry-run` first and ensure no active ids are referenced by open +`--supersedes` or `--references` chains. + +**`--all`** discovers and compacts every `.qual` file under the project root. +Combine with `--dry-run` to audit before committing the result. + +## Gotchas + +- The argument to `compact` is the **artifact name** (e.g., `src/auth.rs`), + not the `.qual` file path. The command resolves the relevant `.qual` file + from the artifact name. +- Compacting removes superseded records permanently from the file on disk. + Ensure the `.qual` file is tracked in VCS before compacting so the history + is recoverable if needed. +- `--snapshot` is destructive to annotation identity: individual record IDs + disappear. Any external system that stored those IDs (e.g., a ticket + linked to an annotation) will have broken references. Only snapshot when + you are certain no one depends on the old IDs. +- If nothing is superseded, `compact` reports "nothing to compact" and makes + no changes, so it is safe to run idempotently. diff --git a/src/cli/commands/agents/pages/emit.md b/src/cli/commands/agents/pages/emit.md index bb84515..6798b90 100644 --- a/src/cli/commands/agents/pages/emit.md +++ b/src/cli/commands/agents/pages/emit.md @@ -1 +1,70 @@ -qualifier agents — emit page (placeholder; replace in Task 7/8) +# qualifier emit + +## Purpose + +Emit a raw record of any type, bypassing the higher-level annotation +scaffolding. + +## When to use it + +Most agents should use `record`, `reply`, and `resolve` for day-to-day +annotation work — those commands construct well-formed annotation envelopes +and validate the body against the spec. `emit` is for cases that those +commands do not cover: writing `epoch` or `dependency` records, emitting +extension types defined by a custom pipeline (e.g., `license`, +`security-advisory`, `perf-measurement`, or any custom URI type), or +forwarding a pre-built record from an external tool. The body JSON is passed +through unchanged, so the caller is responsible for correctness. When +`record_type` is `annotation`, `emit` does run standard annotation validation; +for all other types, the body is stored verbatim. + +## Common invocations + +```bash +# Emit an epoch record to checkpoint the annotation history +qualifier emit epoch src/auth.rs \ + --body '{"summary":"Reviewed at v2.3.0","refs":["git:3aba500"]}' + +# Emit a dependency record linking two artifacts +qualifier emit dependency src/auth.rs \ + --body '{"depends_on":["src/session.rs"]}' + +# Emit a custom extension record type +qualifier emit https://example.com/types/perf-measurement src/hot_path.rs \ + --body '{"p99_ms":14,"baseline_ms":12}' + +# Batch-emit pre-built records from a JSONL file +cat records.jsonl | qualifier emit --stdin +``` + +## Flags worth knowing + +**`--body ''`** is required in single-record mode. The value must be a +valid JSON object; the CLI will reject non-object values. For annotation +records the body must conform to the annotation body schema; for other types +the body is stored as-is. Quote the argument carefully — shell word splitting +on embedded spaces or braces is a common source of errors. + +**`--stdin`** reads complete JSONL records from stdin, one per line. Each +line must be a full record (envelope + body). The positional `record_type` +and `subject` arguments, when provided alongside `--stdin`, act as defaults +applied to lines that are missing those fields. + +**`--issuer`** and **`--issuer-type`** work the same as in `record`. When +emitting machine-generated records such as pipeline measurements, set +`--issuer-type tool` to distinguish them from human annotations. + +## Gotchas + +- `emit` is intentionally low-level. Prefer `record` / `reply` / `resolve` + for annotation work — they guard against common mistakes (missing `kind`, + invalid supersession, etc.) that `emit` does not catch. +- When `record_type` is `annotation`, the body must include at least `kind` + and `summary`; the command will validate and reject non-conforming bodies. + For non-annotation types, no body validation is run — a malformed body will + be stored silently. +- The subject positional is the artifact name (e.g., `src/auth.rs`), not a + `.qual` file path. Span notation in the subject is not parsed; `emit` is + the low-level shape. +- Lines starting with `//` are treated as comments and skipped in `--stdin` + mode. Blank lines are also skipped. diff --git a/src/cli/commands/agents/pages/ls.md b/src/cli/commands/agents/pages/ls.md index 1e2888c..2f392b8 100644 --- a/src/cli/commands/agents/pages/ls.md +++ b/src/cli/commands/agents/pages/ls.md @@ -1 +1,57 @@ -qualifier agents — ls page (placeholder; replace in Task 7/8) +# qualifier ls + +## Purpose + +List all artifacts that have annotations, with an optional filter by +annotation kind. + +## When to use it + +`ls` gives a project-wide inventory of annotated artifacts — useful for an +initial orientation or for finding every artifact with a given kind of +annotation. It does not show the content of the annotations; it only shows +subject names and counts. For the annotations themselves, follow up with +`qualifier show `. Compare with `show`, which requires a specific +artifact name and shows full annotation text, and `praise`, which focuses +on authorship for one artifact. + +## Common invocations + +```bash +# List all annotated artifacts in the project +qualifier ls + +# Filter to artifacts that have at least one "concern" annotation +qualifier ls --kind concern + +# Machine-readable JSON output +qualifier ls --format json +``` + +## Flags worth knowing + +**`--kind `** filters the output to artifacts that have at least one +annotation of the given kind (e.g., `concern`, `pass`, `blocker`). Only +standard and custom kind strings stored in `body.kind` are matched; envelope +`type` filtering is not available here (for that, use `qualifier show --type` +per artifact). + +**`--format json`** emits a JSON array where each element has `subject`, +`annotation_count`, and `kinds` (an array of all kind strings recorded for +that artifact, including duplicates). This is useful for scripts that need +to walk every annotated artifact or count by kind. + +**`--no-ignore`** bypasses `.gitignore` and `.qualignore` filtering when +discovering `.qual` files. Use this if you suspect an artifact is being +hidden by an ignore rule. + +## Gotchas + +- `ls` operates on `.qual` files discovered from the project root. It does + not scan source files for artifacts that lack annotations — the + `--unqualified` flag exists as a placeholder but is not yet implemented. +- Counts include all records in the `.qual` file, not just active ones. + An artifact that was annotated and then fully resolved will still appear + in `ls` output with a non-zero count. +- The output is sorted alphabetically by subject. There is no sort-by-count + option; pipe through your shell's `sort` if needed. diff --git a/src/cli/commands/agents/pages/praise.md b/src/cli/commands/agents/pages/praise.md index 518aebf..0c3f80b 100644 --- a/src/cli/commands/agents/pages/praise.md +++ b/src/cli/commands/agents/pages/praise.md @@ -1 +1,60 @@ -qualifier agents — praise page (placeholder; replace in Task 7/8) +# qualifier praise + +## Purpose + +Show who annotated an artifact and why, with authorship detail for each +active record. + +## When to use it + +`praise` is the attribution command. Where `show` presents a threaded view +of open quality signals, `praise` focuses on who left each annotation, when, +and with what issuer type — making it easy to see whether a review was done +by a human, an AI agent, or a tool. It is useful when auditing the annotation +history of a file or when you need to find the contact for an annotation +before replying. The alias `blame` also works, but the CLI will print a hint +suggesting `praise` — the format is designed to surface helpers rather than +assign fault. + +## Common invocations + +```bash +# Show attribution for all active annotations on a file +qualifier praise src/auth.rs + +# Machine-readable JSON output (includes issuer_type, span, detail) +qualifier praise src/auth.rs --format json + +# Invoke via the alias (produces a hint, then runs normally) +qualifier blame src/auth.rs + +# Use VCS blame on the underlying .qual file instead +qualifier praise src/auth.rs --vcs +``` + +## Flags worth knowing + +**`--format json`** returns a JSON object with `subject` and a `records` +array. Each entry includes `id`, `kind`, `summary`, `issuer`, +`created_at`, and optionally `issuer_type`, `detail`, `suggested_fix`, and +`span`. This is the right mode for an agent that needs to programmatically +find who to notify or which annotations came from other agents. + +**`--vcs`** delegates to the VCS `blame` / `annotate` command on the `.qual` +file itself (git or hg), showing which VCS commit last touched each line. +This is complementary to the record-based view: `--vcs` shows commit +authorship at the `.qual` file level, not the annotation-level issuer field. +It requires a supported VCS to be detected. + +## Gotchas + +- `praise` only shows **active** records — superseded and resolved + annotations are filtered out. If you need the full history, use + `qualifier show --all`. +- The `issuer` field is a URI (e.g., `mailto:alice@example.com`). The human + output strips the `mailto:` prefix and the domain to show a short name; + the JSON output always includes the full URI. +- `--vcs` requires git or hg. For other VCS systems the command exits with + an error and suggests running your VCS tool directly on the `.qual` file. +- `praise` exits with an error if no records are found for the artifact, the + same as `show`. diff --git a/src/cli/commands/agents/pages/record.md b/src/cli/commands/agents/pages/record.md index fbefbce..f190aa8 100644 --- a/src/cli/commands/agents/pages/record.md +++ b/src/cli/commands/agents/pages/record.md @@ -1 +1,72 @@ -qualifier agents — record page (placeholder; replace in Task 7/8) +# qualifier record + +## Purpose + +Record a new annotation against a source artifact. + +## When to use it + +`record` is the primary write verb — use it whenever you want to leave a +quality signal on an artifact. It replaced the earlier per-kind verbs +(`comment`, `flag`, `suggest`, `approve`, `reject`, `attest`); all of those +kinds are now values for the first argument rather than separate subcommands. +Use `reply` when you are responding to an existing annotation thread (it +sets `body.references` automatically). Use `resolve` when you are closing an +open concern. + +## Common invocations + +```bash +# Leave a concern on a file +qualifier record concern src/auth.rs "Missing rate-limit on login endpoint" + +# Annotate a specific line with extended detail +qualifier record concern src/auth.rs:42 "Null check missing" \ + --detail "The function returns early but does not reset the session token." \ + --suggested-fix "Add session.reset() before the return." + +# Approve an artifact, marking yourself as an AI issuer +qualifier record pass src/auth.rs "Auth logic reviewed" \ + --issuer "mailto:agent@ci.example.com" \ + --issuer-type ai + +# Batch-record from a JSONL file +cat findings.jsonl | qualifier record --stdin +``` + +## Flags worth knowing + +**`--span `** overrides any line range parsed from ``. When +you already know the exact span (e.g., `42:58`) and want to keep the location +clean, pass it here rather than embedding it in the location string. The CLI +auto-computes a `content_hash` so that `qualifier review` can later detect +drift. + +**`--supersedes `** marks this record as superseding a prior annotation. +Use this to update or correct an existing annotation rather than leaving both +visible — the superseded record is filtered out by `show`, `praise`, and +`review`. The value is an ID string (full or prefix of 4+ chars is not +accepted here; the full ID is expected). + +**`--issuer-type `** takes `human`, `ai`, `tool`, or `unknown`. Always +set `--issuer-type ai` when writing from an agent; this lets human reviewers +distinguish machine-generated annotations from their own. + +**`--stdin`** switches to batch mode. Each stdin line must be a JSON object +with at least `kind`, `location`, and `message` keys, plus any optional +overrides. Lines starting with `//` are ignored. Useful for emitting many +annotations in one pass. + +## Gotchas + +- All three positional arguments (``, ``, ``) are + required in non-batch mode. Missing any one of them produces a validation + error rather than a prompt. +- The issuer defaults to your VCS user email wrapped in `mailto:`. If running + in a CI environment with no git config, the fallback is + `mailto:unknown@localhost` — set `--issuer` explicitly so records are + attributable. +- Cross-subject supersession is rejected: a new record can only supersede a + record with the same subject. +- Span lines are 1-indexed. Passing `--span 0` or `--span 0:5` will store + line 0, which is outside any real file — validate your line numbers first. diff --git a/src/cli/commands/agents/pages/reply.md b/src/cli/commands/agents/pages/reply.md index e3d7841..ab8fa7e 100644 --- a/src/cli/commands/agents/pages/reply.md +++ b/src/cli/commands/agents/pages/reply.md @@ -1 +1,66 @@ -qualifier agents — reply page (placeholder; replace in Task 7/8) +# qualifier reply + +## Purpose + +Add a follow-up annotation to an existing record, continuing a thread. + +## When to use it + +Use `reply` when you want to comment on or respond to an existing annotation +without closing it. The new record is linked to the target via +`body.references` — `qualifier show` will render replies indented under their +parent. This is distinct from `resolve`, which closes an annotation by +writing a record with `kind: resolve` and `body.supersedes` pointing at the +target. If you only want to add context, a correction, or acknowledge +something that still needs action, use `reply`. If the issue has been fixed +and you want it to stop appearing in active annotation lists, use `resolve`. + +## Common invocations + +```bash +# Reply to a record by id-prefix (4+ characters required) +qualifier reply a1b2c3d4 "Confirmed — also affects the logout path" + +# Reply to the most-recent active annotation at a location +qualifier reply src/auth.rs:42 "Fixed in PR #88 but needs backport to v2" + +# Reply with a suggested fix and ai issuer-type +qualifier reply a1b2c3d4 "Here is a safer pattern" \ + --suggested-fix "Use constant-time comparison: crypto.timingSafeEqual(a, b)" \ + --issuer-type ai + +# Reply with a non-default kind +qualifier reply src/auth.rs "This is intentional per security policy" \ + --kind waiver +``` + +## Flags worth knowing + +**``** accepts either an id-prefix (at least 4 hex characters) or a +location string like `src/auth.rs` or `src/auth.rs:42`. The id-prefix form +matches any record in the project whose ID starts with those characters — if +more than one record matches, the command fails with a list of candidates so +you can narrow it. The location form resolves to the most-recent active record +at that location; if multiple records share the newest timestamp, the command +also fails with a disambiguation list. + +**`--kind `** overrides the default kind of `comment`. Any standard +kind (`concern`, `suggestion`, `waiver`, etc.) or custom string is accepted. +This lets a reply carry semantic weight — for example, a `waiver` reply is +meaningful to tools that consume the annotation graph. + +**`--format json`** prints the emitted record as JSON on stdout, which is +useful when you need to capture the new record's ID for a subsequent +`resolve` or `reply`. + +## Gotchas + +- A reply does **not** close the parent record. To close, use `resolve`. +- The `body.references` field is set automatically to the target record's + full ID — you do not pass `--references` yourself. +- Location resolution only considers **active** records (those not superseded + by a later annotation). If the record at a location has already been + resolved, the location will return "no active record" rather than the + resolved one. Use an id-prefix instead if you need to target a closed record. +- The minimum id-prefix length is 4 characters. Passing 3 or fewer produces a + validation error. diff --git a/src/cli/commands/agents/pages/resolve.md b/src/cli/commands/agents/pages/resolve.md index d3d163f..919ad0b 100644 --- a/src/cli/commands/agents/pages/resolve.md +++ b/src/cli/commands/agents/pages/resolve.md @@ -1 +1,63 @@ -qualifier agents — resolve page (placeholder; replace in Task 7/8) +# qualifier resolve + +## Purpose + +Close an existing record by emitting a `resolve`-kind annotation that +supersedes it. + +## When to use it + +Use `resolve` after you have fixed the issue described in an annotation and +want it removed from active annotation lists. Under the hood, `resolve` writes +a new annotation with `kind: resolve` and `body.supersedes` pointing at the +target — there is no deletion; the full history is preserved in the `.qual` +file. This is the right command when an issue is actually done. If you just +want to add context, a comment, or acknowledge that work remains, use `reply` +instead; `reply` does not close anything. + +## Common invocations + +```bash +# Resolve a record by id-prefix with the default message "Resolved" +qualifier resolve a1b2c3d4 + +# Resolve with a descriptive message +qualifier resolve a1b2c3d4 "Fixed in commit 3aba500 — added null check before session reset" + +# Resolve the most-recent active annotation at a location +qualifier resolve src/auth.rs:42 "Rate limit added in middleware" + +# Resolve and emit the result as JSON (to capture the new record's ID) +qualifier resolve a1b2c3d4 "Done" --format json +``` + +## Flags worth knowing + +**``** follows the same resolution rules as `reply`: an id-prefix (4+ +chars) or a location string. An id-prefix that matches more than one record, +or a location with multiple tied active records, surfaces a disambiguation +list and exits without writing. + +**`[message]`** is optional. When omitted, the resolution record's summary is +set to `"Resolved"`. Provide a message when the reason for closure carries +useful context — the message ends up in the annotation history visible to +`praise` and `show --all`. + +**`--ref `** pins a VCS commit reference (e.g., `git:3aba500`) to +the resolution record. This is useful when you want reviewers to be able to +jump to the exact commit that addressed the issue. + +**`--issuer-type ai`** should be set whenever an agent is the one closing a +record, so the resolution is attributable to a machine rather than a human. + +## Gotchas + +- `resolve` writes a *new* record with `body.supersedes`; it does not modify + or delete the original. `qualifier show` hides the resolved record from its + default view, but `qualifier show --all` still shows it. +- Cross-subject supersession is rejected: the target and the new resolve + record must share the same subject. +- If the target was already resolved (already superseded), the supersession + cycle check may reject the new record. Inspect with + `qualifier show --all ` first. +- The minimum id-prefix is 4 characters, same as `reply`. diff --git a/src/cli/commands/agents/pages/review.md b/src/cli/commands/agents/pages/review.md index a652b62..62deb6d 100644 --- a/src/cli/commands/agents/pages/review.md +++ b/src/cli/commands/agents/pages/review.md @@ -1 +1,60 @@ -qualifier agents — review page (placeholder; replace in Task 7/8) +# qualifier review + +## Purpose + +Check whether span-bound annotations still point at the same code they were +written against, reporting each annotation as fresh, drifted, or missing. + +## When to use it + +Run `review` after editing any file that has span-addressed annotations. +When `qualifier record` writes a span annotation, it computes a +`content_hash` of the referenced lines and stores it in `body.span`. Over +time, edits shift line numbers and change content. `review` re-reads each +spanned region and compares it against the stored hash, flagging annotations +whose code has moved or changed. This is not a general record-listing +command — it only operates on active, span-addressed annotations that have a +`content_hash`. Annotations without a span (or with a span but no hash) are +silently skipped. + +## Common invocations + +```bash +# Check all span-bound annotations in the project +qualifier review + +# Check only annotations on one file +qualifier review src/auth.rs + +# Machine-readable output for scripting +qualifier review --format json +``` + +## Flags worth knowing + +**`[subject]`** is optional. When supplied, only annotations whose subject +matches the given string are checked. Useful in a pre-commit or CI step to +limit the check to files that were actually changed (e.g., iterate over +`git diff --name-only` and call `qualifier review ` per path). + +**`--format json`** emits a JSON array. Each element has `subject`, +`location` (e.g., `src/auth.rs:42`), `kind`, `summary`, `status` +(`"fresh"`, `"drifted"`, or `"missing"`), and a `detail` object with +`expected`/`actual` hashes for drifted results, or a `reason` string for +missing ones. Parse this when automating annotation triage after a refactor. + +## Gotchas + +- Only annotations with **both** a span **and** a `content_hash` are + checked. Annotations recorded without a span, or recorded using an older + version of the tool before content hashing was added, are silently skipped. + The summary line ("N annotations checked") counts only the subset that had + hashes to check. +- A `"drifted"` status means the lines still exist but their content has + changed. A `"missing"` status means the file could not be read or the + line range is now out of bounds. Neither status causes a non-zero exit + code — `review` is informational. Act on drifted or missing annotations + by re-recording them with updated spans (`qualifier record --supersedes`) + or resolving them if they are no longer relevant. +- `review` does not fix annotations automatically. It is a diagnostic + tool; remediation is the caller's responsibility. diff --git a/src/cli/commands/agents/pages/show.md b/src/cli/commands/agents/pages/show.md index 17d1637..98364f8 100644 --- a/src/cli/commands/agents/pages/show.md +++ b/src/cli/commands/agents/pages/show.md @@ -1 +1,71 @@ -qualifier agents — show page (placeholder; replace in Task 7/8) +# qualifier show + +## Purpose + +Display the active annotations for a specific artifact, threaded by reply +chain. + +## When to use it + +`show` is the primary read command for a single artifact. It filters out +superseded records by default and renders replies indented under their parent, +giving a conversational view of all open concerns, comments, and suggestions +on that file. Use `ls` when you want a cross-artifact overview (what +artifacts have any annotations at all). Use `praise` when you want authorship +detail — who wrote each annotation and when, with issuer type shown. Use +`review` when you need to know whether span-bound annotations are still +pointing at the right code. + +## Common invocations + +```bash +# Show active annotations on a file +qualifier show src/auth.rs + +# Show with source context rendered inline (compiler-diagnostic style) +qualifier show src/auth.rs --pretty + +# Show all records including resolved ones +qualifier show src/auth.rs --all + +# Programmatic output — returns JSON object with a "records" array +qualifier show src/auth.rs --format json + +# Show only epoch records for an artifact +qualifier show src/auth.rs --type epoch +``` + +## Flags worth knowing + +**`--format json`** emits a JSON object `{"subject": "...", "records": [...]}`. +Each record is the full envelope and body. This is the right mode when an +agent needs to inspect annotation IDs, `body.references` chains, or span +details programmatically. + +**`--pretty`** adds source context around each span-addressed annotation, +rendered in the same style as compiler diagnostics (filename, line numbers, +and a few lines of surrounding code). Useful for human review; for agents +parsing output, `--format json --pretty` adds a `"context"` field to each +span record. + +**`--all`** disables the default filter that hides superseded records and +`resolve` tombstones. Use it when you need to audit the full annotation +history or diagnose a supersession chain. + +**`--type `** filters by envelope record type (`annotation`, `epoch`, +`dependency`, or any custom URI). This is distinct from filtering by +annotation `kind` — `--type epoch` shows epoch records; `--kind concern` +is not a flag on `show` (use `ls --kind` for kind-based filtering across +artifacts). + +## Gotchas + +- `show` exits with an error if no records are found for the artifact. An + empty result is not silent — test the exit code when scripting. +- Dependency records are hidden in human output (they are graph metadata, not + quality signals) but appear in `--format json` output. +- Resolved records are hidden by default. If an artifact appears to have no + annotations but you expect some, try `--all` to check whether they were + resolved. +- The `artifact` argument is the subject name as stored in the `.qual` file + (e.g., `src/auth.rs`), not a filesystem glob. There is no wildcard matching. From 072ddbd3062d03f5056c8efd40102eabc37c1d09 Mon Sep 17 00:00:00 2001 From: Alex Kesling Date: Wed, 6 May 2026 11:07:22 -0400 Subject: [PATCH 13/26] docs(agents): clarify --supersedes does not expand prefixes in record --- src/cli/commands/agents/pages/record.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/cli/commands/agents/pages/record.md b/src/cli/commands/agents/pages/record.md index f190aa8..431ffc8 100644 --- a/src/cli/commands/agents/pages/record.md +++ b/src/cli/commands/agents/pages/record.md @@ -45,8 +45,9 @@ drift. **`--supersedes `** marks this record as superseding a prior annotation. Use this to update or correct an existing annotation rather than leaving both visible — the superseded record is filtered out by `show`, `praise`, and -`review`. The value is an ID string (full or prefix of 4+ chars is not -accepted here; the full ID is expected). +`review`. The value is stored verbatim — no prefix expansion is performed +here. Pass the full 64-character ID. (Short prefixes are only resolved by +`reply` and `resolve`, not `record`.) **`--issuer-type `** takes `human`, `ai`, `tool`, or `unknown`. Always set `--issuer-type ai` when writing from an agent; this lets human reviewers From 628c6016a26aa3d0a8aa414fdc88572067427754 Mon Sep 17 00:00:00 2001 From: Alex Kesling Date: Wed, 6 May 2026 11:10:10 -0400 Subject: [PATCH 14/26] chore: bump to 0.5.0 and document agents subcommand --- CHANGELOG.md | 11 +++++++++++ Cargo.toml | 2 +- README.md | 12 +++++++++--- 3 files changed, 21 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e89aebd..73dca54 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,17 @@ All notable changes to this project are documented here. Format follows adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html) (with the pre-1.0 caveat that any breaking change bumps the minor version). +## [0.5.0] — unreleased + +### Added + +- **`qualifier agents`** — self-contained guide for AI coding agents. + Bare `qualifier agents` prints an orientation page with an index of + topics; `qualifier agents ` (e.g. `concepts`, `workflows`, + `record`) drills into per-topic detail. The agent group also appears + at the top of `qualifier --help` so models reading the help text + discover the entry point on their own. + ## [0.4.0] — unreleased This release is a substantial reshape: the CLI surface narrowed, scoring diff --git a/Cargo.toml b/Cargo.toml index 9c855de..f3e9c58 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "qualifier" -version = "0.4.0" +version = "0.5.0" edition = "2024" description = "Deterministic quality annotations for software artifacts" license = "MIT OR Apache-2.0" diff --git a/README.md b/README.md index 528553f..b7e6cf0 100644 --- a/README.md +++ b/README.md @@ -46,7 +46,13 @@ qualifier review src/parser.rs ## CLI -**Record observations** +### For AI agents + +| Command | Description | +|---------|-------------| +| `qualifier agents [topic]` | Self-contained guide for AI coding agents (start here) | + +### Record observations | Command | Description | |---------|-------------| @@ -60,7 +66,7 @@ qualifier review src/parser.rs is a path with an optional span (`src/foo.rs:42`, `src/foo.rs:42:58`). `` is an id-prefix (≥4 chars) or a ``. -**Inspect annotations** +### Inspect annotations | Command | Description | |---------|-------------| @@ -69,7 +75,7 @@ is a path with an optional span (`src/foo.rs:42`, `src/foo.rs:42:58`). | `qualifier praise ` | Show who annotated and why (alias: `blame`) | | `qualifier review [subject]` | Check freshness of span-bound annotations | -**Maintain** +### Maintain | Command | Description | |---------|-------------| From a05d6f2489e3c30de7ac50276e2e5bf382b5c636 Mon Sep 17 00:00:00 2001 From: Alex Kesling Date: Wed, 6 May 2026 12:21:35 -0400 Subject: [PATCH 15/26] docs: AGENTS-CLI 0.1 protocol and frontmatter migration spec MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two related artifacts: 1. AGENTS-CLI.md (repo root) — a draft protocol document for CLI tools that self-describe to AI coding agents. Tool-agnostic prose with five MUST rules (subcommand existence, bare orientation, topic dispatch, unknown-topic exit-2, --help discoverability) and four SHOULD rules. qualifier 0.5.0+ is the reference implementation. Probe and JSON output are out of scope for 0.1. 2. docs/superpowers/specs/2026-05-06-frontmatter-migration-design.md — qualifier-internal refactor that replaces the hand-coded &[Page] registry with TOML frontmatter on each page, generated at compile time by build.rs. Adds name/summary/sees_also/since fields. No user-visible CLI change. --- AGENTS-CLI.md | 80 +++++++ ...2026-05-06-frontmatter-migration-design.md | 225 ++++++++++++++++++ 2 files changed, 305 insertions(+) create mode 100644 AGENTS-CLI.md create mode 100644 docs/superpowers/specs/2026-05-06-frontmatter-migration-design.md diff --git a/AGENTS-CLI.md b/AGENTS-CLI.md new file mode 100644 index 0000000..b313e00 --- /dev/null +++ b/AGENTS-CLI.md @@ -0,0 +1,80 @@ +# AGENTS-CLI 0.1 + +A convention for command-line tools that self-describe to AI coding agents. + +> **Status:** Draft. Breaking changes are allowed until 1.0. Feedback and adoption reports welcome via issues on any conforming tool's repository. + +## Audience + +CLI tool authors who want their tool to be productively usable by an AI coding agent on first contact, with no separately-installed prompt bundle, MCP server, or external skill. + +## Motivation + +A coding agent dropped into an unfamiliar shell typically does one of two things when it encounters a CLI: relies on prior training-data knowledge of the tool, or runs ` --help` and pattern-matches. Neither path teaches the agent the *judgment* it needs — when to use the tool, when not to, what the common workflows look like, what mistakes to avoid. That judgment is what separates an agent that uses your tool well from one that strings invocations together by syntactic mimicry. + +AGENTS-CLI defines the smallest surface that lets a tool ship its own onboarding for AI agents, version-locked to the binary, discovered through the existing `--help` channel that agents already inspect. + +## Conformance + +This document uses the requirement levels from RFC 2119: **MUST**, **MUST NOT**, **SHOULD**, **SHOULD NOT**, **MAY**. + +### Required (MUST) + +A conforming tool **MUST** satisfy all of the following. + +1. **Subcommand exists.** The tool exposes a subcommand named `agents`. Examples: `qualifier agents`, `gh agents`, `cargo agents`. + +2. **Bare invocation prints an orientation page.** Running ` agents` with no further arguments prints a self-contained orientation document to standard output and exits with status 0. The orientation document MUST be sufficient on its own to teach an agent (a) what the tool is, (b) when to use it, (c) when not to use it, and (d) how to drill into per-topic detail. + +3. **Topic invocation prints the named page.** Running ` agents ` prints the page identified by `` to standard output and exits with status 0. The orientation page MUST list the available topics. + +4. **Unknown topic produces a structured error.** Running ` agents ` MUST exit with status 2, and MUST write a message to standard error of the form: + + ``` + agents: no such topic ''. Available: , , ... + ``` + + The available list MUST contain every topic name that would succeed under rule 3. + +5. **`--help` discoverability.** The output of ` --help` MUST surface the `agents` subcommand in a way that signals to a coding agent that it is the agent-targeted entry point. The conformance test is informal but operational: *an LLM scanning the help output reliably identifies `agents` as the entry point intended for AI agents.* In practice this means a labeled section header ("For AI agents:"), a banner line, or an equivalent affordance — not just an unmarked entry buried in a generic command list. + +### Recommended (SHOULD) + +A conforming tool **SHOULD** satisfy the following. These are common-sense quality bars; departing from them is allowed if there's a clear reason. + +1. **Output is markdown.** Pages SHOULD be UTF-8 markdown text. Each page SHOULD begin with a single `# ` heading naming the page (e.g., `# record`). + +2. **Page set covers concepts, workflows, pitfalls, and per-subcommand detail.** At minimum, the topic set SHOULD include: + + - A concept primer (often `concepts`) — the tool's data model, key invariants, and any vocabulary an agent must understand to use the tool well. + - Common workflows (often `workflows`) — three to five worked recipes covering the most common tasks. + - Common pitfalls (often `pitfalls`) — mistakes agents make and how to avoid them. + - One page per non-trivial subcommand, named after the subcommand. + +3. **Pages are hand-written.** Pages SHOULD be authored with judgment, not auto-generated from `--help` output. Agents already have access to `--help` for syntax; AGENTS-CLI exists to convey the *when* and *why* that flag tables cannot. + +4. **Pages are version-locked to the binary.** Pages SHOULD ship inside the binary or alongside it such that the guidance an agent receives matches the tool it can actually invoke. Network fetches at runtime are discouraged. + +### Out of scope for 0.1 + +The following are deferred to later versions of this protocol or to companion specifications. Tools MAY implement them, but doing so is not part of 0.1 conformance: + +- A discovery handshake for sweeping `$PATH` (e.g., ` agents --probe`). +- Structured (JSON) output for programmatic consumption. +- Internationalization or per-locale page sets. +- A standard format for storing pages in the source tree (each tool chooses). +- A registry of conforming tools. + +## Reference implementation + +**qualifier** (0.5.0 and later) is the reference implementation: . + +`qualifier agents`, `qualifier agents concepts`, `qualifier agents `, and `qualifier agents ` exercise every MUST in this document. The "For AI agents:" group at the top of `qualifier --help` is the discoverability mechanism for rule 5. Pages live at `src/cli/commands/agents/pages/*.md`, embedded into the binary at compile time. + +## Versioning + +AGENTS-CLI uses semantic versioning. The current version is 0.1. While the major version is 0, breaking changes are allowed between minor versions; downstream tools should expect to update conformance as the protocol stabilizes. Tools MAY indicate which version they conform to in any way they choose at 0.1 (a footer in the orientation page is the conventional choice). A standardized declaration mechanism is deferred to 1.0. + +## Acknowledgements + +The protocol distills practice from man pages (orientation + per-topic drill-down), shell help conventions (machine-discoverable invocation surface), and the more recent `AGENTS.md` repo-level convention (the audience signal). It exists because no single one of those mechanisms gives a coding agent everything it needs on first contact, and because the friction of bolting on a separate skill bundle for every CLI is incompatible with how agents actually arrive at a codebase. diff --git a/docs/superpowers/specs/2026-05-06-frontmatter-migration-design.md b/docs/superpowers/specs/2026-05-06-frontmatter-migration-design.md new file mode 100644 index 0000000..f91a686 --- /dev/null +++ b/docs/superpowers/specs/2026-05-06-frontmatter-migration-design.md @@ -0,0 +1,225 @@ +# Frontmatter migration for `qualifier agents` pages + +## Goal + +Replace the hand-coded `&[Page]` registry in `src/cli/commands/agents/mod.rs` with a build-time-generated registry derived from TOML frontmatter on each `pages/*.md` file. Adding, renaming, or describing a topic becomes a single-file edit; the Rust source tree no longer carries a duplicated topic list to keep in sync. + +## Non-goals + +- Changing the runtime CLI surface. The user-visible behavior of `qualifier agents`, `qualifier agents `, and `qualifier agents ` is unchanged. +- Rendering `sees_also` cross-links in page output. The field is parsed and stored on `Page` but not yet rendered. A future PR can wire up a "See also:" footer. +- Standardizing this storage format across tools. AGENTS-CLI 0.1 explicitly leaves source-tree storage to each tool's choice; this is qualifier's choice. +- Runtime parsing. Pages are still read at compile time via `include_str!`-equivalent mechanics — never from the filesystem at run time. + +## Frontmatter schema + +Every `pages/*.md` file begins with a TOML frontmatter block delimited by `+++` lines: + +``` ++++ +name = "record" +summary = "Record a new annotation" +sees_also = ["reply", "resolve"] +since = "0.5.0" ++++ + +# qualifier record + +(page body...) +``` + +**Fields:** + +| Field | Required | Type | Purpose | +|-------------|----------|----------------|--------------------------------------------------------------------------| +| `name` | yes | string | Topic key. Must match the filename stem (or be `_overview` for overview).| +| `summary` | yes | string | One-line description used in the topic index. | +| `sees_also` | no | array | Names of related topics. Stored on `Page`; rendering deferred. | +| `since` | no | string | Version the topic was first present (e.g., `"0.5.0"`). | + +The first `+++` line MUST be the very first line of the file. The frontmatter ends at the next line that is exactly `+++`. The body is everything after that, leading whitespace preserved (the body normally starts with a blank line before the `#` heading). + +## Filename convention + +- Filenames matching `[a-z][a-z0-9_-]*\.md` are topics. Their `name` frontmatter field MUST equal the filename stem. +- Filenames beginning with `_` are *internal* pages, excluded from the topic registry. The build emits them as named consts. Currently only `_overview.md` exists; the convention generalizes to e.g. `_about.md` or `_compat.md` later. +- The build fails if it encounters a filename that doesn't match either convention. + +## `Page` struct (new shape) + +In `src/cli/commands/agents/mod.rs`: + +```rust +pub struct Page { + pub name: &'static str, + pub summary: &'static str, + pub sees_also: &'static [&'static str], + pub since: Option<&'static str>, + pub body: &'static str, +} +``` + +The build emits `PAGES: &[Page]` and individual consts for underscore-prefixed pages. + +## `build.rs` algorithm + +A new `build.rs` at the crate root performs, at compile time: + +1. Read `cargo:rerun-if-changed=src/cli/commands/agents/pages` so edits trigger a rebuild. +2. Walk `src/cli/commands/agents/pages/*.md` (sorted lexicographically for deterministic output). +3. For each file: + 1. Read full contents as UTF-8. + 2. Confirm the first line is exactly `+++`. Locate the next `+++`-only line. + 3. Parse the lines between as TOML using the `toml` crate, deserialized via `serde::Deserialize` into a `PageMeta` struct. + 4. Capture the body (everything after the closing `+++` line). + 5. Validate: + - `name` is non-empty. + - For non-underscore filenames: `name == filename_stem`. + - For `_overview.md`: `name == "_overview"`. + - `summary` is non-empty (for non-underscore files; the overview SHOULD have a summary too but enforcement is on topic pages). +4. Detect duplicates: no two `Page`s may share the same `name`. +5. Emit `$OUT_DIR/agents_pages.rs` containing: + - `pub const OVERVIEW: &str = "";` + - `pub const PAGES: &[Page] = &[...];` populated from non-underscore files in lexicographic order. + +Build errors include the offending file path and a clear reason. Examples: + +- `src/cli/commands/agents/pages/record.md: missing required field 'summary' in frontmatter` +- `src/cli/commands/agents/pages/record.md: name 'reply' does not match filename stem 'record'` +- `src/cli/commands/agents/pages/typo.md: no '+++' frontmatter delimiter on first line` +- `pages/record.md and pages/aliased.md: duplicate name 'record'` + +## `mod.rs` changes + +The current code: + +```rust +const OVERVIEW: &str = include_str!("pages/_overview.md"); + +const PAGES: &[Page] = &[ + Page { name: "concepts", summary: "...", body: include_str!("pages/concepts.md") }, + // ... 11 more entries +]; +``` + +becomes: + +```rust +include!(concat!(env!("OUT_DIR"), "/agents_pages.rs")); +``` + +The hand-coded summaries are removed. The `Page` struct definition stays in `mod.rs` (or moves to a tiny `page.rs` sibling for clarity; either is fine). + +`render_overview()`, `topic_names()`, and `run()` keep their current shape. They consume the generated `OVERVIEW` and `PAGES` consts identically. + +## Page-file edits + +Each existing page gets a frontmatter prepend. Concrete edits: + +- `_overview.md` — add `name = "_overview"`. No other frontmatter required. +- `concepts.md`, `workflows.md`, `pitfalls.md` — add `name`, `summary`, `since = "0.5.0"`. +- `record.md`, `reply.md`, `resolve.md`, `emit.md`, `show.md`, `ls.md`, `praise.md`, `review.md`, `compact.md` — add `name`, `summary`, `since = "0.5.0"`. Optionally `sees_also` for the obviously-related pairs (`record` ↔ `reply`/`resolve`; `reply` ↔ `resolve`; etc.) — see "Suggested `sees_also` map" below. + +The summaries used in frontmatter MUST match the strings currently hand-coded in `mod.rs::PAGES` to preserve the exact `qualifier agents` orientation output. + +## Suggested `sees_also` map + +Not enforced; included so the implementer doesn't have to invent it. Skip any pairing that doesn't feel natural. + +- `record` → `["reply", "resolve", "emit"]` +- `reply` → `["resolve", "record"]` +- `resolve` → `["reply", "record"]` +- `emit` → `["record"]` +- `show` → `["ls", "praise", "review"]` +- `ls` → `["show"]` +- `praise` → `["show"]` +- `review` → `["show", "compact"]` +- `compact` → `["review"]` +- `concepts`, `workflows`, `pitfalls` → omit (`sees_also = []` or absent) + +## Dependencies added + +In `Cargo.toml`: + +```toml +[build-dependencies] +toml = "0.8" +serde = { version = "1", features = ["derive"] } +``` + +These do not affect the runtime binary — they are only used by `build.rs`. + +## Testing + +Existing tests pass without changes — the user-visible CLI behavior is identical. + +Add to `tests/cli_integration.rs`: + +```rust +#[test] +fn test_agents_orientation_summaries_match_pages() { + // Lock in the contract that the orientation page renders the topic + // index from frontmatter summaries. We assert each topic's summary + // appears in the bare-agents output. + let dir = tempfile::tempdir().unwrap(); + let (stdout, _stderr, code) = run_qualifier(dir.path(), &["agents"]); + assert_eq!(code, 0); + for needle in [ + "Annotation model, kinds, supersession", // concepts summary + "Worked recipes for common tasks", // workflows summary + "Common mistakes agents make with qualifier",// pitfalls summary + "Record a new annotation", // record summary + ] { + assert!( + stdout.contains(needle), + "orientation should include summary '{needle}': {stdout}" + ); + } +} +``` + +Add a unit test in `agents/mod.rs`: + +```rust +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn all_pages_have_non_empty_summaries() { + for page in PAGES { + assert!( + !page.summary.is_empty(), + "page '{}' has empty summary", + page.name + ); + } + } + + #[test] + fn overview_contains_topics_sentinel() { + // Existing test, preserved. + assert!(super::OVERVIEW.contains("{{TOPICS}}")); + } +} +``` + +## File touch list + +- New: `build.rs` at crate root (~80 lines) +- New: dependency lines in `Cargo.toml` `[build-dependencies]` +- Modified: `src/cli/commands/agents/mod.rs` — drop hand-coded `&[Page]`, add `include!`, expand `Page` struct +- Modified: `src/cli/commands/agents/pages/_overview.md` — add minimal frontmatter +- Modified: `src/cli/commands/agents/pages/{concepts,workflows,pitfalls,record,reply,resolve,emit,show,ls,praise,review,compact}.md` — add frontmatter (12 files) +- Modified: `tests/cli_integration.rs` — add the orientation/summary test +- Modified: `Cargo.toml` — bump version to 0.5.1 (patch — no user-visible behavior change) +- Modified: `CHANGELOG.md` — note the build-time refactor under an internal/changed section +- Untouched: README.md (no user-visible change) +- Untouched: AGENTS-CLI.md (this migration is qualifier-internal; the protocol is silent on storage) + +## Out of scope / future + +- Rendering `sees_also` as a "See also: …" footer on each page. +- A `--probe` flag for AGENTS-CLI discovery (covered separately if/when AGENTS-CLI 0.2 lands). +- A JSON output mode (deferred per AGENTS-CLI 0.1 out-of-scope list). +- Auto-checking that `since` versions are monotone or match `Cargo.toml`. From dd8d5529e04b646188e00579bfc7175faa37a3f1 Mon Sep 17 00:00:00 2001 From: Alex Kesling Date: Wed, 6 May 2026 12:30:19 -0400 Subject: [PATCH 16/26] docs: implementation plan for AGENTS-CLI cross-linking + frontmatter migration --- .../2026-05-06-agents-cli-and-frontmatter.md | 723 ++++++++++++++++++ 1 file changed, 723 insertions(+) create mode 100644 docs/superpowers/plans/2026-05-06-agents-cli-and-frontmatter.md diff --git a/docs/superpowers/plans/2026-05-06-agents-cli-and-frontmatter.md b/docs/superpowers/plans/2026-05-06-agents-cli-and-frontmatter.md new file mode 100644 index 0000000..530852a --- /dev/null +++ b/docs/superpowers/plans/2026-05-06-agents-cli-and-frontmatter.md @@ -0,0 +1,723 @@ +# AGENTS-CLI cross-linking + frontmatter migration plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Land two related changes for qualifier 0.5.1 — (a) cross-link the new AGENTS-CLI 0.1 protocol from qualifier's README and orientation page, declaring qualifier as the reference implementation; (b) migrate the agents page registry from a hand-coded `&[Page]` const to a build-time-generated registry derived from TOML frontmatter on each `pages/*.md`. + +**Architecture:** Phase A is two surgical doc edits. Phase B introduces a `build.rs` that walks `src/cli/commands/agents/pages/`, parses TOML frontmatter via `toml` + `serde`, and emits `$OUT_DIR/agents_pages.rs` containing `OVERVIEW: &str` and `PAGES: &[Page]`. `mod.rs` `include!()`s the generated file and drops the hand-coded const. + +**Tech Stack:** Rust 2024, clap 4, new build dependencies `toml = "0.8"` and `serde = { version = "1", features = ["derive"] }`. + +**Specs:** +- [AGENTS-CLI.md](../../../AGENTS-CLI.md) +- [docs/superpowers/specs/2026-05-06-frontmatter-migration-design.md](../specs/2026-05-06-frontmatter-migration-design.md) + +--- + +## File Structure + +**Created:** +- `build.rs` (crate root) — walks the pages directory at compile time, parses frontmatter, emits the registry. + +**Modified:** +- `README.md` — link to AGENTS-CLI.md near the top. +- `src/cli/commands/agents/pages/_overview.md` — add minimal frontmatter; add a conformance footer. +- `src/cli/commands/agents/pages/{concepts,workflows,pitfalls,record,reply,resolve,emit,show,ls,praise,review,compact}.md` — add TOML frontmatter (12 files). +- `src/cli/commands/agents/mod.rs` — replace hand-coded `&[Page]` with `include!()` of the generated file; expand `Page` struct with `sees_also` and `since`. +- `Cargo.toml` — add `[build-dependencies]`; bump version to 0.5.1. +- `CHANGELOG.md` — add a 0.5.1 entry. +- `tests/cli_integration.rs` — one new integration test. + +**Boundaries:** `build.rs` is the only code outside the agents module that's affected. The user-visible CLI behavior is unchanged. Existing tests pass without modification. + +--- + +## Task 1: Cross-link AGENTS-CLI from README + +**Files:** +- Modify: `README.md` + +- [ ] **Step 1: Read the current README to find the right insertion point** + +The CLI Commands section starts around line 46. The new link should sit *above* the CLI tables but *below* any existing intro/quickstart, so a reader sees "this tool implements AGENTS-CLI" before they scan the command tables. + +If there's an existing project-level intro paragraph, place the link right after it. Otherwise place it as a new paragraph just above the `## CLI` heading (or whichever heading currently introduces the command tables). + +- [ ] **Step 2: Add the link** + +Insert this paragraph at the chosen location: + +```markdown +> **For AI coding agents:** qualifier implements [AGENTS-CLI 0.1](AGENTS-CLI.md). Run `qualifier agents` for a self-contained guide. +``` + +The blockquote prefix (`> `) is intentional — it sets the tone as a sidebar / call-out rather than blending into the prose. + +- [ ] **Step 3: Verify the link target exists** + +Run: `ls AGENTS-CLI.md` +Expected: file exists at repo root (it does — committed in `a05d6f2`). + +- [ ] **Step 4: Render-check** + +Open `README.md` in any markdown previewer (or just re-read the diff). Confirm the new line reads cleanly in context and the link is a relative link (not `./AGENTS-CLI.md` or absolute). + +- [ ] **Step 5: Commit** + +```bash +git add README.md +git commit -m "docs: link AGENTS-CLI from README" +``` + +--- + +## Task 2: Conformance footer on `_overview.md` + +**Files:** +- Modify: `src/cli/commands/agents/pages/_overview.md` + +- [ ] **Step 1: Read the current `_overview.md`** + +Find the `## Reference` section near the bottom. + +- [ ] **Step 2: Append a conformance line** + +After the existing `## Reference` section content, before any final newline, add: + +```markdown + +## Protocol + +qualifier implements [AGENTS-CLI 0.1](https://github.com/empathic/qualifier/blob/main/AGENTS-CLI.md). The protocol defines the `agents` subcommand contract that this page satisfies. +``` + +The link is absolute (`https://...`) because the orientation page is read by an agent in someone else's repo — relative paths won't resolve. Use the canonical GitHub URL on the main branch. + +- [ ] **Step 3: Sanity-check render** + +Run: `cargo run --bin qualifier -- agents | tail -10` +Expected: the new "## Protocol" section appears at the bottom of the orientation output. + +- [ ] **Step 4: Run all tests to confirm nothing broke** + +Run: `cargo test --all-features` +Expected: all pass. (No tests assert on the absence of "## Protocol", so this should be clean.) + +- [ ] **Step 5: Commit** + +```bash +git add src/cli/commands/agents/pages/_overview.md +git commit -m "docs(agents): declare AGENTS-CLI 0.1 conformance in orientation page" +``` + +--- + +## Task 3: Frontmatter migration — atomic refactor + +This is the meat of the work. It lands as a single commit because the changes are interlocked: adding frontmatter to a page without a corresponding `build.rs` to strip it would break user-visible output, and shipping `build.rs` without frontmatter on the pages would cause it to fail. + +The task is large but each step is bite-sized. Follow the sequence; do not commit between steps. + +**Files:** +- Modify: `Cargo.toml` +- Create: `build.rs` +- Modify: `src/cli/commands/agents/pages/_overview.md` +- Modify: `src/cli/commands/agents/pages/{concepts,workflows,pitfalls,record,reply,resolve,emit,show,ls,praise,review,compact}.md` (12 files) +- Modify: `src/cli/commands/agents/mod.rs` +- Test: `tests/cli_integration.rs` + +- [ ] **Step 1: Add the new contract test (TDD red)** + +Append to `tests/cli_integration.rs`: + +```rust +#[test] +fn test_agents_orientation_summaries_match_pages() { + // Lock in the contract that the orientation page renders the topic + // index from frontmatter summaries (rather than hard-coded ones in + // mod.rs). Each topic's summary must appear in bare-agents output. + let dir = tempfile::tempdir().unwrap(); + let (stdout, _stderr, code) = run_qualifier(dir.path(), &["agents"]); + assert_eq!(code, 0); + for needle in [ + "Annotation model, kinds, supersession", // concepts + "Worked recipes for common tasks", // workflows + "Common mistakes agents make with qualifier", // pitfalls + "Record a new annotation", // record + ] { + assert!( + stdout.contains(needle), + "orientation should include summary '{needle}': {stdout}" + ); + } +} +``` + +This test passes today (the substrings already appear because `render_overview()` substitutes them in). It is a regression guard for the migration: if the build.rs ever fails to wire summaries through, this catches it. + +Run: `cargo test --test cli_integration test_agents_orientation_summaries_match_pages` +Expected: PASS already. (We're locking in the contract before touching the implementation.) + +- [ ] **Step 2: Bump version and add build dependencies in `Cargo.toml`** + +Find the `[package]` block and change: + +```toml +version = "0.5.0" +``` + +to: + +```toml +version = "0.5.1" +``` + +Then, **above** the existing `[features]` block (or wherever fits the file's structure), add a new `[build-dependencies]` section: + +```toml +[build-dependencies] +toml = "0.8" +serde = { version = "1", features = ["derive"] } +``` + +Run: `cargo build` +Expected: succeeds (no `build.rs` exists yet, so deps are unused but resolved). Cargo may print a warning about unused build-dependencies — that's fine; it goes away in Step 3. + +- [ ] **Step 3: Create `build.rs` at the crate root** + +Create `build.rs` with the full content below. This is the complete file — copy verbatim, don't trim. + +```rust +//! Build-time generator for the `qualifier agents` page registry. +//! +//! Walks `src/cli/commands/agents/pages/*.md`, parses TOML frontmatter +//! delimited by `+++` lines, and emits `$OUT_DIR/agents_pages.rs` +//! containing: +//! +//! - `pub const OVERVIEW: &str = "...";` +//! - `pub const PAGES: &[Page] = &[...];` +//! +//! `Page` is defined in `src/cli/commands/agents/mod.rs`. The generated +//! file is `include!`d there. + +use std::collections::HashSet; +use std::env; +use std::fs; +use std::path::PathBuf; + +use serde::Deserialize; + +const PAGES_DIR: &str = "src/cli/commands/agents/pages"; + +#[derive(Deserialize)] +struct PageMeta { + name: String, + summary: Option, + #[serde(default)] + sees_also: Vec, + since: Option, +} + +fn main() { + println!("cargo:rerun-if-changed={PAGES_DIR}"); + println!("cargo:rerun-if-changed=build.rs"); + + let mut entries: Vec<_> = fs::read_dir(PAGES_DIR) + .unwrap_or_else(|e| panic!("read {PAGES_DIR}: {e}")) + .filter_map(Result::ok) + .filter(|e| e.path().extension().and_then(|s| s.to_str()) == Some("md")) + .collect(); + entries.sort_by_key(|e| e.path()); + + let mut overview_body: Option = None; + let mut topic_entries: Vec = Vec::new(); + let mut seen_names: HashSet = HashSet::new(); + + for entry in entries { + let path = entry.path(); + let stem = path + .file_stem() + .and_then(|s| s.to_str()) + .unwrap_or_else(|| panic!("non-utf8 filename: {}", path.display())) + .to_string(); + let raw = fs::read_to_string(&path) + .unwrap_or_else(|e| panic!("read {}: {e}", path.display())); + + let after_open = raw.strip_prefix("+++\n").unwrap_or_else(|| { + panic!( + "{}: file must begin with '+++' frontmatter delimiter on its first line", + path.display() + ) + }); + let close_offset = after_open.find("\n+++\n").unwrap_or_else(|| { + panic!( + "{}: missing closing '+++' frontmatter delimiter", + path.display() + ) + }); + let frontmatter = &after_open[..close_offset]; + let body = &after_open[close_offset + "\n+++\n".len()..]; + + let meta: PageMeta = toml::from_str(frontmatter) + .unwrap_or_else(|e| panic!("{}: invalid TOML frontmatter: {e}", path.display())); + + if meta.name != stem { + panic!( + "{}: frontmatter name '{}' does not match filename stem '{}'", + path.display(), + meta.name, + stem + ); + } + if !seen_names.insert(meta.name.clone()) { + panic!("duplicate page name '{}'", meta.name); + } + + if stem.starts_with('_') { + if stem == "_overview" { + overview_body = Some(body.to_string()); + } else { + panic!( + "{}: unsupported internal page; only '_overview' is recognized", + path.display() + ); + } + } else { + let summary = meta.summary.unwrap_or_else(|| { + panic!( + "{}: topic page is missing required 'summary' field", + path.display() + ) + }); + let sees_also_lit = meta + .sees_also + .iter() + .map(|s| format!("{s:?}")) + .collect::>() + .join(", "); + let since_lit = match meta.since { + Some(v) => format!("Some({v:?})"), + None => "None".into(), + }; + topic_entries.push(format!( + " Page {{ name: {:?}, summary: {:?}, sees_also: &[{sees_also_lit}], since: {since_lit}, body: {:?} }},", + meta.name, summary, body, + )); + } + } + + let overview = overview_body.unwrap_or_else(|| { + panic!("{PAGES_DIR}/_overview.md is required but not found"); + }); + + let generated = format!( + "pub const OVERVIEW: &str = {overview:?};\n\npub const PAGES: &[Page] = &[\n{}\n];\n", + topic_entries.join("\n"), + ); + + let out_path = PathBuf::from(env::var("OUT_DIR").unwrap()).join("agents_pages.rs"); + fs::write(&out_path, generated) + .unwrap_or_else(|e| panic!("write {}: {e}", out_path.display(), )); +} +``` + +Run: `cargo build` +Expected: FAILS — every page lacks frontmatter, and `build.rs` will panic on the first file that doesn't start with `+++`. The error message will name the file. This is expected; we're about to fix it. + +- [ ] **Step 4: Add frontmatter to `_overview.md`** + +Open `src/cli/commands/agents/pages/_overview.md`. Prepend, as the very first lines of the file: + +``` ++++ +name = "_overview" ++++ + +``` + +(The trailing blank line is part of the prepend so the existing body is offset cleanly.) + +The frontmatter for `_overview.md` is intentionally minimal: it has no `summary` because it isn't in the topic registry, and no `since` because it's the orientation page that always exists. + +- [ ] **Step 5: Add frontmatter to topical pages** + +For each of `concepts.md`, `workflows.md`, `pitfalls.md`, prepend frontmatter using the existing summary strings from the current `mod.rs`. **The summary text MUST match `mod.rs::PAGES` exactly** so that orientation output is byte-identical after the migration. + +`concepts.md`: + +``` ++++ +name = "concepts" +summary = "Annotation model, kinds, supersession, IDs, .qual layout" +since = "0.5.0" ++++ + +``` + +`workflows.md`: + +``` ++++ +name = "workflows" +summary = "Worked recipes for common tasks" +since = "0.5.0" ++++ + +``` + +`pitfalls.md`: + +``` ++++ +name = "pitfalls" +summary = "Common mistakes agents make with qualifier" +since = "0.5.0" ++++ + +``` + +- [ ] **Step 6: Add frontmatter to per-subcommand pages** + +For each of the 9 per-subcommand pages, prepend frontmatter with the matching summary. The exact summaries to use come from the current hand-coded `PAGES` array in `src/cli/commands/agents/mod.rs`. Read the file once and copy each summary verbatim. + +`record.md`: + +``` ++++ +name = "record" +summary = "Record a new annotation" +sees_also = ["reply", "resolve", "emit"] +since = "0.5.0" ++++ + +``` + +`reply.md`: + +``` ++++ +name = "reply" +summary = "Reply to an existing record" +sees_also = ["resolve", "record"] +since = "0.5.0" ++++ + +``` + +`resolve.md`: + +``` ++++ +name = "resolve" +summary = "Resolve (close) a record" +sees_also = ["reply", "record"] +since = "0.5.0" ++++ + +``` + +`emit.md`: + +``` ++++ +name = "emit" +summary = "Emit a raw record of any type" +sees_also = ["record"] +since = "0.5.0" ++++ + +``` + +`show.md`: + +``` ++++ +name = "show" +summary = "Show annotations for an artifact" +sees_also = ["ls", "praise", "review"] +since = "0.5.0" ++++ + +``` + +`ls.md`: + +``` ++++ +name = "ls" +summary = "List artifacts by kind" +sees_also = ["show"] +since = "0.5.0" ++++ + +``` + +`praise.md`: + +``` ++++ +name = "praise" +summary = "Show who annotated an artifact and why" +sees_also = ["show"] +since = "0.5.0" ++++ + +``` + +`review.md`: + +``` ++++ +name = "review" +summary = "Check freshness of annotations against current code" +sees_also = ["show", "compact"] +since = "0.5.0" ++++ + +``` + +`compact.md`: + +``` ++++ +name = "compact" +summary = "Compact a .qual file" +sees_also = ["review"] +since = "0.5.0" ++++ + +``` + +- [ ] **Step 7: Verify `build.rs` produces the registry** + +Run: `cargo build 2>&1 | head -40` +Expected: clean build. No more panics. (If a panic happens, the error names the offending file; fix the frontmatter and re-run.) + +You can sanity-check the generated file: + +```bash +find target -name agents_pages.rs -path '*/build/*' | head -1 | xargs head -5 +``` + +Expected: shows `pub const OVERVIEW: &str = "qualifier agents — guide for AI coding agents...` followed by the `PAGES` array. + +- [ ] **Step 8: Update `mod.rs` to consume the generated registry** + +Open `src/cli/commands/agents/mod.rs`. Make these specific changes: + +**Replace the `Page` struct** (currently `struct Page { name, summary, body }`) with the expanded shape: + +```rust +pub struct Page { + pub name: &'static str, + pub summary: &'static str, + pub sees_also: &'static [&'static str], + pub since: Option<&'static str>, + pub body: &'static str, +} +``` + +(Note `pub` on the struct and fields — the generated code references them.) + +**Delete the hand-coded `OVERVIEW` line and the entire `PAGES` const definition** (the const ends at the closing `];`). + +**Insert in their place:** + +```rust +include!(concat!(env!("OUT_DIR"), "/agents_pages.rs")); +``` + +The rest of the file (`Args`, `topic_names()`, `run()`, `render_overview()`, the `#[cfg(test)] mod tests` block) stays unchanged. They consume `OVERVIEW` and `PAGES` exactly as before. + +- [ ] **Step 9: Build and run all tests** + +Run: `cargo build && cargo test --all-features` +Expected: full suite green. The orientation output matches byte-for-byte what it produced before the migration. + +If a test fails, the most likely cause is summary text drift — verify your frontmatter `summary` strings match the originals exactly. + +- [ ] **Step 10: Manual smoke** + +```bash +target/debug/qualifier agents | head -10 +target/debug/qualifier agents record | head -10 +target/debug/qualifier agents bogus 2>&1 ; echo "exit=$?" +``` + +Expected: +- `agents` shows the orientation including the topic list with all 12 summaries (no `+++` leakage anywhere). +- `agents record` shows the record page (no `+++` leakage). +- `agents bogus` shows `no such topic 'bogus'. Available: concepts, workflows, ...` and `exit=2`. + +- [ ] **Step 11: fmt + clippy** + +Run: `cargo fmt && cargo clippy --all-targets --all-features -- -D warnings` +Expected: clean. + +- [ ] **Step 12: Commit** + +```bash +git add Cargo.toml build.rs src/cli/commands/agents tests/cli_integration.rs +git commit -m "$(cat <<'EOF' +refactor(agents): generate page registry from TOML frontmatter + +Replaces the hand-coded &[Page] in src/cli/commands/agents/mod.rs with +a build.rs-generated const derived from frontmatter on each +pages/*.md. Adds toml + serde as build dependencies; emits +$OUT_DIR/agents_pages.rs included by mod.rs. New schema: +name (required), summary (required for topic pages), sees_also +(optional, parsed but not yet rendered), since (optional). User-visible +CLI behavior is unchanged. +EOF +)" +``` + +--- + +## Task 4: Add a contract unit test for non-empty summaries + +**Files:** +- Modify: `src/cli/commands/agents/mod.rs` + +This is a small follow-up that adds belt-and-suspenders coverage on the new build pipeline. Locks in the invariant that every topic page has a non-empty summary. + +- [ ] **Step 1: Append to the existing `#[cfg(test)] mod tests` block in `mod.rs`** + +The block already contains `overview_contains_topics_sentinel`. Add a second test alongside it: + +```rust + #[test] + fn all_pages_have_non_empty_summaries() { + for page in PAGES { + assert!( + !page.summary.is_empty(), + "page '{}' has empty summary", + page.name + ); + } + } +``` + +- [ ] **Step 2: Run the test** + +Run: `cargo test all_pages_have_non_empty_summaries` +Expected: PASS. + +- [ ] **Step 3: Run full suite + clippy** + +Run: `cargo test --all-features && cargo clippy --all-targets --all-features -- -D warnings` +Expected: clean. + +- [ ] **Step 4: Commit** + +```bash +git add src/cli/commands/agents/mod.rs +git commit -m "test(agents): assert every page has a non-empty summary" +``` + +--- + +## Task 5: CHANGELOG entry + +**Files:** +- Modify: `CHANGELOG.md` + +- [ ] **Step 1: Open `CHANGELOG.md` and find the existing 0.5.0 section** + +The file currently has `## [0.5.0] — unreleased` (or, if 0.5.0 has been published, a dated header). Either way, the new entry goes above it. + +- [ ] **Step 2: Add a 0.5.1 section above 0.5.0** + +```markdown +## [0.5.1] — unreleased + +### Added + +- **AGENTS-CLI 0.1 protocol document** (`AGENTS-CLI.md`) — a draft + cross-tool convention for CLI tools that self-describe to AI coding + agents. qualifier is named as the reference implementation. The + protocol defines five MUST rules (`agents` subcommand, bare + orientation, topic dispatch, exit-2 unknown-topic, `--help` + discoverability) and four SHOULD recommendations. + +### Changed + +- **Internal:** the `qualifier agents` page registry is now generated + at build time from TOML frontmatter on each `pages/*.md` file + instead of being a hand-coded `&[Page]` array in `mod.rs`. New + per-page fields: `name`, `summary`, `sees_also`, `since`. + User-visible CLI behavior is unchanged. +``` + +- [ ] **Step 3: Commit** + +```bash +git add CHANGELOG.md +git commit -m "docs: changelog for 0.5.1 (AGENTS-CLI + frontmatter)" +``` + +--- + +## Task 6: Final verification + +**Files:** none new + +- [ ] **Step 1: fmt** + +Run: `cargo fmt` +Expected: no diff. + +- [ ] **Step 2: clippy** + +Run: `cargo clippy --all-targets --all-features -- -D warnings` +Expected: clean. + +- [ ] **Step 3: full test suite** + +Run: `cargo test --all-features` +Expected: all tests pass — including the existing 6 agents tests and the 2 new ones (`test_agents_orientation_summaries_match_pages` integration, `all_pages_have_non_empty_summaries` unit). + +- [ ] **Step 4: end-to-end manual smoke** + +```bash +target/debug/qualifier --help +target/debug/qualifier agents +target/debug/qualifier agents concepts +target/debug/qualifier agents record +target/debug/qualifier agents bogus 2>&1 ; echo "exit=$?" +``` + +Expected: +- `--help` shows the "For AI agents:" group at the top. +- `agents` shows the orientation page with the topic list rendered from frontmatter summaries, ending with the new "## Protocol" footer linking AGENTS-CLI.md. +- `agents concepts` and `agents record` show their pages, no `+++` leakage. +- `agents bogus` exits 2 with the available list. + +- [ ] **Step 5: confirm `Cargo.toml` version is 0.5.1** + +```bash +grep '^version' Cargo.toml +``` + +Expected: `version = "0.5.1"`. + +- [ ] **Step 6: confirm AGENTS-CLI link from README is live** + +```bash +grep -n "AGENTS-CLI" README.md +``` + +Expected: at least one match referencing `AGENTS-CLI.md`. + +- [ ] **Step 7: if anything failed, fix and re-run from Step 1** + +- [ ] **Step 8: optional final fmt/clippy commit** + +```bash +git status +# If only fmt/clippy fixups remain: +git add -u +git commit -m "chore: final fmt/clippy fixups for 0.5.1" +``` + +If the working tree is clean, skip. From fc5b0beae526cede4ad20f19c8f34c1c9edf5152 Mon Sep 17 00:00:00 2001 From: Alex Kesling Date: Wed, 6 May 2026 12:41:05 -0400 Subject: [PATCH 17/26] docs: link AGENTS-CLI from README --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index b7e6cf0..68ef8ca 100644 --- a/README.md +++ b/README.md @@ -44,6 +44,8 @@ qualifier resolve a1b2 qualifier review src/parser.rs ``` +> **For AI coding agents:** qualifier implements [AGENTS-CLI 0.1](AGENTS-CLI.md). Run `qualifier agents` for a self-contained guide. + ## CLI ### For AI agents From 3a3d0d57c6c4e9985f45227f6b101d1c2a3dc51b Mon Sep 17 00:00:00 2001 From: Alex Kesling Date: Wed, 6 May 2026 12:42:05 -0400 Subject: [PATCH 18/26] docs(agents): declare AGENTS-CLI 0.1 conformance in orientation page --- src/cli/commands/agents/pages/_overview.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/cli/commands/agents/pages/_overview.md b/src/cli/commands/agents/pages/_overview.md index 353d229..c43b5b0 100644 --- a/src/cli/commands/agents/pages/_overview.md +++ b/src/cli/commands/agents/pages/_overview.md @@ -53,3 +53,7 @@ Run `qualifier agents ` for any of: For exact flag tables on any subcommand, run `qualifier --help`. For the JSONL wire format and library API, see `SPEC.md` in this repo (if present) or the published spec. + +## Protocol + +qualifier implements [AGENTS-CLI 0.1](https://github.com/empathic/qualifier/blob/main/AGENTS-CLI.md). The protocol defines the `agents` subcommand contract that this page satisfies. From aac824d3ef8ccba65a28c751247c76589c214075 Mon Sep 17 00:00:00 2001 From: Alex Kesling Date: Wed, 6 May 2026 13:07:44 -0400 Subject: [PATCH 19/26] refactor(agents): generate page registry from TOML frontmatter Replaces the hand-coded &[Page] in src/cli/commands/agents/mod.rs with a build.rs-generated const derived from frontmatter on each pages/*.md. Adds toml + serde as build dependencies; emits $OUT_DIR/agents_pages.rs included by mod.rs. New schema: name (required), summary (required for topic pages), sees_also (optional, parsed but not yet rendered), since (optional). User-visible CLI behavior is unchanged. --- Cargo.lock | 3 +- Cargo.toml | 6 +- build.rs | 131 +++++++++++++++++++++ src/cli/commands/agents/mod.rs | 75 ++---------- src/cli/commands/agents/pages/_overview.md | 4 + src/cli/commands/agents/pages/compact.md | 7 ++ src/cli/commands/agents/pages/concepts.md | 6 + src/cli/commands/agents/pages/emit.md | 7 ++ src/cli/commands/agents/pages/ls.md | 7 ++ src/cli/commands/agents/pages/pitfalls.md | 6 + src/cli/commands/agents/pages/praise.md | 7 ++ src/cli/commands/agents/pages/record.md | 7 ++ src/cli/commands/agents/pages/reply.md | 7 ++ src/cli/commands/agents/pages/resolve.md | 7 ++ src/cli/commands/agents/pages/review.md | 7 ++ src/cli/commands/agents/pages/show.md | 7 ++ src/cli/commands/agents/pages/workflows.md | 6 + tests/cli_integration.rs | 21 ++++ 18 files changed, 251 insertions(+), 70 deletions(-) create mode 100644 build.rs diff --git a/Cargo.lock b/Cargo.lock index bf69309..a6c7822 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -674,7 +674,7 @@ dependencies = [ [[package]] name = "qualifier" -version = "0.4.0" +version = "0.5.1" dependencies = [ "blake3", "chrono", @@ -688,6 +688,7 @@ dependencies = [ "serde_json", "tempfile", "thiserror", + "toml", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index f3e9c58..d337fdc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "qualifier" -version = "0.5.0" +version = "0.5.1" edition = "2024" description = "Deterministic quality annotations for software artifacts" license = "MIT OR Apache-2.0" @@ -9,6 +9,10 @@ readme = "README.md" keywords = ["quality", "annotation", "code-review", "ci"] categories = ["command-line-utilities", "development-tools"] +[build-dependencies] +toml = "0.8" +serde = { version = "1", features = ["derive"] } + [features] default = ["cli"] cli = ["dep:clap", "dep:comfy-table", "dep:figment", "dep:rand"] diff --git a/build.rs b/build.rs new file mode 100644 index 0000000..19733ac --- /dev/null +++ b/build.rs @@ -0,0 +1,131 @@ +//! Build-time generator for the `qualifier agents` page registry. +//! +//! Walks `src/cli/commands/agents/pages/*.md`, parses TOML frontmatter +//! delimited by `+++` lines, and emits `$OUT_DIR/agents_pages.rs` +//! containing: +//! +//! - `pub const OVERVIEW: &str = "...";` +//! - `pub const PAGES: &[Page] = &[...];` +//! +//! `Page` is defined in `src/cli/commands/agents/mod.rs`. The generated +//! file is `include!`d there. + +use std::collections::HashSet; +use std::env; +use std::fs; +use std::path::PathBuf; + +use serde::Deserialize; + +const PAGES_DIR: &str = "src/cli/commands/agents/pages"; + +#[derive(Deserialize)] +struct PageMeta { + name: String, + summary: Option, + #[serde(default)] + sees_also: Vec, + since: Option, +} + +fn main() { + println!("cargo:rerun-if-changed={PAGES_DIR}"); + println!("cargo:rerun-if-changed=build.rs"); + + let mut entries: Vec<_> = fs::read_dir(PAGES_DIR) + .unwrap_or_else(|e| panic!("read {PAGES_DIR}: {e}")) + .filter_map(Result::ok) + .filter(|e| e.path().extension().and_then(|s| s.to_str()) == Some("md")) + .collect(); + entries.sort_by_key(|e| e.path()); + + let mut overview_body: Option = None; + let mut topic_entries: Vec = Vec::new(); + let mut seen_names: HashSet = HashSet::new(); + + for entry in entries { + let path = entry.path(); + let stem = path + .file_stem() + .and_then(|s| s.to_str()) + .unwrap_or_else(|| panic!("non-utf8 filename: {}", path.display())) + .to_string(); + let raw = + fs::read_to_string(&path).unwrap_or_else(|e| panic!("read {}: {e}", path.display())); + + let after_open = raw.strip_prefix("+++\n").unwrap_or_else(|| { + panic!( + "{}: file must begin with '+++' frontmatter delimiter on its first line", + path.display() + ) + }); + let close_offset = after_open.find("\n+++\n").unwrap_or_else(|| { + panic!( + "{}: missing closing '+++' frontmatter delimiter", + path.display() + ) + }); + let frontmatter = &after_open[..close_offset]; + let body = &after_open[close_offset + "\n+++\n".len()..]; + + let meta: PageMeta = toml::from_str(frontmatter) + .unwrap_or_else(|e| panic!("{}: invalid TOML frontmatter: {e}", path.display())); + + if meta.name != stem { + panic!( + "{}: frontmatter name '{}' does not match filename stem '{}'", + path.display(), + meta.name, + stem + ); + } + if !seen_names.insert(meta.name.clone()) { + panic!("duplicate page name '{}'", meta.name); + } + + if stem.starts_with('_') { + if stem == "_overview" { + overview_body = Some(body.to_string()); + } else { + panic!( + "{}: unsupported internal page; only '_overview' is recognized", + path.display() + ); + } + } else { + let summary = meta.summary.unwrap_or_else(|| { + panic!( + "{}: topic page is missing required 'summary' field", + path.display() + ) + }); + let sees_also_lit = meta + .sees_also + .iter() + .map(|s| format!("{s:?}")) + .collect::>() + .join(", "); + let since_lit = match meta.since { + Some(v) => format!("Some({v:?})"), + None => "None".into(), + }; + topic_entries.push(format!( + " Page {{ name: {:?}, summary: {:?}, sees_also: &[{sees_also_lit}], since: {since_lit}, body: {:?} }},", + meta.name, summary, body, + )); + } + } + + let overview = overview_body.unwrap_or_else(|| { + panic!("{PAGES_DIR}/_overview.md is required but not found"); + }); + + let generated = format!( + "pub const OVERVIEW: &str = {overview:?};\n\npub const PAGES: &[Page] = &[\n{}\n];\n", + topic_entries.join("\n"), + ); + + let out_path = PathBuf::from(env::var("OUT_DIR").unwrap()).join("agents_pages.rs"); + fs::write(&out_path, generated) + .unwrap_or_else(|e| panic!("write {}: {e}", out_path.display(),)); +} diff --git a/src/cli/commands/agents/mod.rs b/src/cli/commands/agents/mod.rs index 9d37685..89fa346 100644 --- a/src/cli/commands/agents/mod.rs +++ b/src/cli/commands/agents/mod.rs @@ -6,76 +6,15 @@ pub struct Args { pub topic: Option, } -struct Page { - name: &'static str, - summary: &'static str, - body: &'static str, +pub struct Page { + pub name: &'static str, + pub summary: &'static str, + pub sees_also: &'static [&'static str], + pub since: Option<&'static str>, + pub body: &'static str, } -const OVERVIEW: &str = include_str!("pages/_overview.md"); - -const PAGES: &[Page] = &[ - Page { - name: "concepts", - summary: "Annotation model, kinds, supersession, IDs, .qual layout", - body: include_str!("pages/concepts.md"), - }, - Page { - name: "workflows", - summary: "Worked recipes for common tasks", - body: include_str!("pages/workflows.md"), - }, - Page { - name: "pitfalls", - summary: "Common mistakes agents make with qualifier", - body: include_str!("pages/pitfalls.md"), - }, - Page { - name: "record", - summary: "Record a new annotation", - body: include_str!("pages/record.md"), - }, - Page { - name: "reply", - summary: "Reply to an existing record", - body: include_str!("pages/reply.md"), - }, - Page { - name: "resolve", - summary: "Resolve (close) a record", - body: include_str!("pages/resolve.md"), - }, - Page { - name: "emit", - summary: "Emit a raw record of any type", - body: include_str!("pages/emit.md"), - }, - Page { - name: "show", - summary: "Show annotations for an artifact", - body: include_str!("pages/show.md"), - }, - Page { - name: "ls", - summary: "List artifacts by kind", - body: include_str!("pages/ls.md"), - }, - Page { - name: "praise", - summary: "Show who annotated an artifact and why", - body: include_str!("pages/praise.md"), - }, - Page { - name: "review", - summary: "Check freshness of annotations against current code", - body: include_str!("pages/review.md"), - }, - Page { - name: "compact", - summary: "Compact a .qual file", - body: include_str!("pages/compact.md"), - }, -]; +include!(concat!(env!("OUT_DIR"), "/agents_pages.rs")); fn topic_names() -> String { PAGES.iter().map(|p| p.name).collect::>().join(", ") diff --git a/src/cli/commands/agents/pages/_overview.md b/src/cli/commands/agents/pages/_overview.md index c43b5b0..9bd4fcf 100644 --- a/src/cli/commands/agents/pages/_overview.md +++ b/src/cli/commands/agents/pages/_overview.md @@ -1,3 +1,7 @@ ++++ +name = "_overview" ++++ + # qualifier — guide for AI coding agents You are an AI coding agent in a user's repository. The `qualifier` CLI is diff --git a/src/cli/commands/agents/pages/compact.md b/src/cli/commands/agents/pages/compact.md index 2d2f7a9..ddeda5b 100644 --- a/src/cli/commands/agents/pages/compact.md +++ b/src/cli/commands/agents/pages/compact.md @@ -1,3 +1,10 @@ ++++ +name = "compact" +summary = "Compact a .qual file" +sees_also = ["review"] +since = "0.5.0" ++++ + # qualifier compact ## Purpose diff --git a/src/cli/commands/agents/pages/concepts.md b/src/cli/commands/agents/pages/concepts.md index b5dc6aa..ace43ac 100644 --- a/src/cli/commands/agents/pages/concepts.md +++ b/src/cli/commands/agents/pages/concepts.md @@ -1,3 +1,9 @@ ++++ +name = "concepts" +summary = "Annotation model, kinds, supersession, IDs, .qual layout" +since = "0.5.0" ++++ + # qualifier — key concepts ## The annotation envelope diff --git a/src/cli/commands/agents/pages/emit.md b/src/cli/commands/agents/pages/emit.md index 6798b90..21b22da 100644 --- a/src/cli/commands/agents/pages/emit.md +++ b/src/cli/commands/agents/pages/emit.md @@ -1,3 +1,10 @@ ++++ +name = "emit" +summary = "Emit a raw record of any type" +sees_also = ["record"] +since = "0.5.0" ++++ + # qualifier emit ## Purpose diff --git a/src/cli/commands/agents/pages/ls.md b/src/cli/commands/agents/pages/ls.md index 2f392b8..35c12df 100644 --- a/src/cli/commands/agents/pages/ls.md +++ b/src/cli/commands/agents/pages/ls.md @@ -1,3 +1,10 @@ ++++ +name = "ls" +summary = "List artifacts by kind" +sees_also = ["show"] +since = "0.5.0" ++++ + # qualifier ls ## Purpose diff --git a/src/cli/commands/agents/pages/pitfalls.md b/src/cli/commands/agents/pages/pitfalls.md index bb66951..f5311ea 100644 --- a/src/cli/commands/agents/pages/pitfalls.md +++ b/src/cli/commands/agents/pages/pitfalls.md @@ -1,3 +1,9 @@ ++++ +name = "pitfalls" +summary = "Common mistakes agents make with qualifier" +since = "0.5.0" ++++ + # qualifier — common pitfalls - **Recording without `--span` when the concern is about specific lines.** diff --git a/src/cli/commands/agents/pages/praise.md b/src/cli/commands/agents/pages/praise.md index 0c3f80b..fc156c4 100644 --- a/src/cli/commands/agents/pages/praise.md +++ b/src/cli/commands/agents/pages/praise.md @@ -1,3 +1,10 @@ ++++ +name = "praise" +summary = "Show who annotated an artifact and why" +sees_also = ["show"] +since = "0.5.0" ++++ + # qualifier praise ## Purpose diff --git a/src/cli/commands/agents/pages/record.md b/src/cli/commands/agents/pages/record.md index 431ffc8..08073e0 100644 --- a/src/cli/commands/agents/pages/record.md +++ b/src/cli/commands/agents/pages/record.md @@ -1,3 +1,10 @@ ++++ +name = "record" +summary = "Record a new annotation" +sees_also = ["reply", "resolve", "emit"] +since = "0.5.0" ++++ + # qualifier record ## Purpose diff --git a/src/cli/commands/agents/pages/reply.md b/src/cli/commands/agents/pages/reply.md index ab8fa7e..07d4c65 100644 --- a/src/cli/commands/agents/pages/reply.md +++ b/src/cli/commands/agents/pages/reply.md @@ -1,3 +1,10 @@ ++++ +name = "reply" +summary = "Reply to an existing record" +sees_also = ["resolve", "record"] +since = "0.5.0" ++++ + # qualifier reply ## Purpose diff --git a/src/cli/commands/agents/pages/resolve.md b/src/cli/commands/agents/pages/resolve.md index 919ad0b..4729244 100644 --- a/src/cli/commands/agents/pages/resolve.md +++ b/src/cli/commands/agents/pages/resolve.md @@ -1,3 +1,10 @@ ++++ +name = "resolve" +summary = "Resolve (close) a record" +sees_also = ["reply", "record"] +since = "0.5.0" ++++ + # qualifier resolve ## Purpose diff --git a/src/cli/commands/agents/pages/review.md b/src/cli/commands/agents/pages/review.md index 62deb6d..847e81c 100644 --- a/src/cli/commands/agents/pages/review.md +++ b/src/cli/commands/agents/pages/review.md @@ -1,3 +1,10 @@ ++++ +name = "review" +summary = "Check freshness of annotations against current code" +sees_also = ["show", "compact"] +since = "0.5.0" ++++ + # qualifier review ## Purpose diff --git a/src/cli/commands/agents/pages/show.md b/src/cli/commands/agents/pages/show.md index 98364f8..66889ea 100644 --- a/src/cli/commands/agents/pages/show.md +++ b/src/cli/commands/agents/pages/show.md @@ -1,3 +1,10 @@ ++++ +name = "show" +summary = "Show annotations for an artifact" +sees_also = ["ls", "praise", "review"] +since = "0.5.0" ++++ + # qualifier show ## Purpose diff --git a/src/cli/commands/agents/pages/workflows.md b/src/cli/commands/agents/pages/workflows.md index f9dbd5b..64aec46 100644 --- a/src/cli/commands/agents/pages/workflows.md +++ b/src/cli/commands/agents/pages/workflows.md @@ -1,3 +1,9 @@ ++++ +name = "workflows" +summary = "Worked recipes for common tasks" +since = "0.5.0" ++++ + # qualifier — common workflows ## Record a finding during code review diff --git a/tests/cli_integration.rs b/tests/cli_integration.rs index 6bc78e6..b466af1 100644 --- a/tests/cli_integration.rs +++ b/tests/cli_integration.rs @@ -2398,6 +2398,27 @@ fn test_agents_overview_renders_topics_index() { ); } +#[test] +fn test_agents_orientation_summaries_match_pages() { + // Lock in the contract that the orientation page renders the topic + // index from frontmatter summaries (rather than hard-coded ones in + // mod.rs). Each topic's summary must appear in bare-agents output. + let dir = tempfile::tempdir().unwrap(); + let (stdout, _stderr, code) = run_qualifier(dir.path(), &["agents"]); + assert_eq!(code, 0); + for needle in [ + "Annotation model, kinds, supersession", // concepts + "Worked recipes for common tasks", // workflows + "Common mistakes agents make with qualifier", // pitfalls + "Record a new annotation", // record + ] { + assert!( + stdout.contains(needle), + "orientation should include summary '{needle}': {stdout}" + ); + } +} + #[test] fn test_top_level_help_shows_agents_group() { let dir = tempfile::tempdir().unwrap(); From 0273d463cfd9fbb8e1129d9080b824b69fc974b1 Mon Sep 17 00:00:00 2001 From: Alex Kesling Date: Wed, 6 May 2026 15:34:03 -0400 Subject: [PATCH 20/26] fix(agents): trim leading newline from page bodies; tighten Page visibility Strip the leading '\n' after the closing +++ delimiter so rendered pages no longer start with a blank line. Also narrow Page and its generated constants from pub to private (with #[allow(dead_code)] on the two not-yet-read fields) to avoid the private_interfaces lint under -D warnings. --- build.rs | 4 ++-- src/cli/commands/agents/mod.rs | 18 ++++++++++++------ 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/build.rs b/build.rs index 19733ac..ecbff62 100644 --- a/build.rs +++ b/build.rs @@ -66,7 +66,7 @@ fn main() { ) }); let frontmatter = &after_open[..close_offset]; - let body = &after_open[close_offset + "\n+++\n".len()..]; + let body = after_open[close_offset + "\n+++\n".len()..].trim_start_matches('\n'); let meta: PageMeta = toml::from_str(frontmatter) .unwrap_or_else(|e| panic!("{}: invalid TOML frontmatter: {e}", path.display())); @@ -121,7 +121,7 @@ fn main() { }); let generated = format!( - "pub const OVERVIEW: &str = {overview:?};\n\npub const PAGES: &[Page] = &[\n{}\n];\n", + "const OVERVIEW: &str = {overview:?};\n\nconst PAGES: &[Page] = &[\n{}\n];\n", topic_entries.join("\n"), ); diff --git a/src/cli/commands/agents/mod.rs b/src/cli/commands/agents/mod.rs index 89fa346..44f223a 100644 --- a/src/cli/commands/agents/mod.rs +++ b/src/cli/commands/agents/mod.rs @@ -6,12 +6,18 @@ pub struct Args { pub topic: Option, } -pub struct Page { - pub name: &'static str, - pub summary: &'static str, - pub sees_also: &'static [&'static str], - pub since: Option<&'static str>, - pub body: &'static str, +struct Page { + name: &'static str, + summary: &'static str, + /// Cross-references to related topics. Stored for future rendering + /// (a "See also: ..." footer is planned); not yet read. + #[allow(dead_code)] + sees_also: &'static [&'static str], + /// Version when the topic was first present. Stored for future + /// version-aware navigation; not yet read. + #[allow(dead_code)] + since: Option<&'static str>, + body: &'static str, } include!(concat!(env!("OUT_DIR"), "/agents_pages.rs")); From 9cbbadc7070a500cd24904a0954ebcd8a01ee58e Mon Sep 17 00:00:00 2001 From: Alex Kesling Date: Wed, 6 May 2026 15:34:45 -0400 Subject: [PATCH 21/26] test(agents): assert every page has a non-empty summary --- src/cli/commands/agents/mod.rs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/cli/commands/agents/mod.rs b/src/cli/commands/agents/mod.rs index 44f223a..8fd2c2b 100644 --- a/src/cli/commands/agents/mod.rs +++ b/src/cli/commands/agents/mod.rs @@ -65,4 +65,15 @@ mod tests { // anchor and the rendered overview will no longer list children. assert!(super::OVERVIEW.contains("{{TOPICS}}")); } + + #[test] + fn all_pages_have_non_empty_summaries() { + for page in super::PAGES { + assert!( + !page.summary.is_empty(), + "page '{}' has empty summary", + page.name + ); + } + } } From 6f1385e4c5f7bb2ab8e70a9c765288edff47fba3 Mon Sep 17 00:00:00 2001 From: Alex Kesling Date: Wed, 6 May 2026 15:35:28 -0400 Subject: [PATCH 22/26] docs: changelog for 0.5.1 (AGENTS-CLI + frontmatter) --- CHANGELOG.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 73dca54..a8ce598 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,25 @@ All notable changes to this project are documented here. Format follows adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html) (with the pre-1.0 caveat that any breaking change bumps the minor version). +## [0.5.1] — unreleased + +### Added + +- **AGENTS-CLI 0.1 protocol document** (`AGENTS-CLI.md`) — a draft + cross-tool convention for CLI tools that self-describe to AI coding + agents. qualifier is named as the reference implementation. The + protocol defines five MUST rules (`agents` subcommand, bare + orientation, topic dispatch, exit-2 unknown-topic, `--help` + discoverability) and four SHOULD recommendations. + +### Changed + +- **Internal:** the `qualifier agents` page registry is now generated + at build time from TOML frontmatter on each `pages/*.md` file + instead of being a hand-coded `&[Page]` array in `mod.rs`. New + per-page fields: `name`, `summary`, `sees_also`, `since`. + User-visible CLI behavior is unchanged. + ## [0.5.0] — unreleased ### Added From 7aee1e1e92416e0bd87bdf9e4a406935b8385d24 Mon Sep 17 00:00:00 2001 From: Alex Kesling Date: Wed, 6 May 2026 15:42:37 -0400 Subject: [PATCH 23/26] docs: align build.rs doc comment with non-pub consts; note order change --- CHANGELOG.md | 4 ++++ build.rs | 4 ++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a8ce598..26da0ad 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,10 @@ the pre-1.0 caveat that any breaking change bumps the minor version). instead of being a hand-coded `&[Page]` array in `mod.rs`. New per-page fields: `name`, `summary`, `sees_also`, `since`. User-visible CLI behavior is unchanged. +- **Topic display order:** the topic index in `qualifier agents` and the + available-topics list in unknown-topic errors are now in lexicographic + order (file-sorted), where they were previously hand-ordered. This is + cosmetic; the same set of topics is exposed. ## [0.5.0] — unreleased diff --git a/build.rs b/build.rs index ecbff62..37a4789 100644 --- a/build.rs +++ b/build.rs @@ -4,8 +4,8 @@ //! delimited by `+++` lines, and emits `$OUT_DIR/agents_pages.rs` //! containing: //! -//! - `pub const OVERVIEW: &str = "...";` -//! - `pub const PAGES: &[Page] = &[...];` +//! - `const OVERVIEW: &str = "...";` +//! - `const PAGES: &[Page] = &[...];` //! //! `Page` is defined in `src/cli/commands/agents/mod.rs`. The generated //! file is `include!`d there. From 7e6ce383ca83296a7d4177efe0692ab3865280e5 Mon Sep 17 00:00:00 2001 From: Alex Kesling Date: Wed, 6 May 2026 15:55:31 -0400 Subject: [PATCH 24/26] docs(agents): trim noise-encouraging guidance from orientation and examples Three changes to the agent-facing pages: - _overview.md: drop the bullet recommending agents proactively record praise; drop the ## Protocol footer (AGENTS-CLI conformance is for tool authors, not for the agents reading this page). - pitfalls.md: add an explicit pitfall against volunteering positive-polarity annotations without user direction. - record.md: replace the "Approve an artifact" example (which modeled exactly the proactive-positive pattern) with a suggestion example that preserves the --issuer / --issuer-type ai illustration. --- src/cli/commands/agents/pages/_overview.md | 6 ------ src/cli/commands/agents/pages/pitfalls.md | 10 ++++++++++ src/cli/commands/agents/pages/record.md | 4 ++-- 3 files changed, 12 insertions(+), 8 deletions(-) diff --git a/src/cli/commands/agents/pages/_overview.md b/src/cli/commands/agents/pages/_overview.md index 9bd4fcf..f848b94 100644 --- a/src/cli/commands/agents/pages/_overview.md +++ b/src/cli/commands/agents/pages/_overview.md @@ -20,8 +20,6 @@ attached to specific places in the codebase. - You found a bug, smell, risk, or stylistic concern that survives this edit and is worth surfacing for whoever touches the code next. Record it. -- You want to praise a piece of code so future readers know it was - intentional, not accidental. Record it. - An earlier annotation no longer applies (the code changed, the concern was addressed). Resolve it. @@ -57,7 +55,3 @@ Run `qualifier agents ` for any of: For exact flag tables on any subcommand, run `qualifier --help`. For the JSONL wire format and library API, see `SPEC.md` in this repo (if present) or the published spec. - -## Protocol - -qualifier implements [AGENTS-CLI 0.1](https://github.com/empathic/qualifier/blob/main/AGENTS-CLI.md). The protocol defines the `agents` subcommand contract that this page satisfies. diff --git a/src/cli/commands/agents/pages/pitfalls.md b/src/cli/commands/agents/pages/pitfalls.md index f5311ea..4f228b0 100644 --- a/src/cli/commands/agents/pages/pitfalls.md +++ b/src/cli/commands/agents/pages/pitfalls.md @@ -39,6 +39,16 @@ since = "0.5.0" Prefer `concern` for non-blocking bugs and `blocker` for must-fix issues; reserve custom kinds for genuinely domain-specific signals. +- **Adding positive annotations the user did not ask for.** + An agent volunteering `praise`, `pass`, or other positive-polarity + annotations without explicit user direction creates review noise the + user has to triage. Annotations exist to surface things the user needs + to act on; reflexively recording approval of code you happen to be + reading does the opposite. Record a positive annotation only when the + user explicitly asks for it, or when documenting an intentional + non-obvious design choice that future readers would otherwise mistake + for a bug. + - **Using a non-URI issuer (must contain `:`).** Validation rejects any `issuer` value that does not contain a colon. A bare email address like `agent@example.com` will fail. Wrap it: diff --git a/src/cli/commands/agents/pages/record.md b/src/cli/commands/agents/pages/record.md index 08073e0..e05676c 100644 --- a/src/cli/commands/agents/pages/record.md +++ b/src/cli/commands/agents/pages/record.md @@ -32,8 +32,8 @@ qualifier record concern src/auth.rs:42 "Null check missing" \ --detail "The function returns early but does not reset the session token." \ --suggested-fix "Add session.reset() before the return." -# Approve an artifact, marking yourself as an AI issuer -qualifier record pass src/auth.rs "Auth logic reviewed" \ +# Record a suggestion with explicit AI issuer identity +qualifier record suggestion src/auth.rs "Replace inline regex with a named constant" \ --issuer "mailto:agent@ci.example.com" \ --issuer-type ai From 009e127638b1abe6c15e9ae5480c451952902546 Mon Sep 17 00:00:00 2001 From: Alex Kesling Date: Wed, 6 May 2026 16:01:13 -0400 Subject: [PATCH 25/26] docs(agents): reframe resolve as user-directed; agents surface, do not close Across four pages: - _overview.md "When to use it" drops the resolve bullet; the section now lists only recording-a-finding as an agent-initiated action. - workflows.md drops the "Resolve a finding" recipe and rewrites the drift-triage recipe so the agent surfaces drift to the user instead of auto-resolving DRIFTED/MISSING annotations. - pitfalls.md adds an explicit pitfall against resolving annotations the user has not directed the agent to close. - resolve.md "When to use it" is rewritten to frame resolve as a user-directed action; the --issuer-type ai note is reworded to assume resolve is always done on behalf of the user, never autonomously. --- .../2026-05-06-agents-cli-and-frontmatter.md | 723 ------------- .../2026-05-06-qualifier-agents-command.md | 976 ------------------ ...2026-05-06-frontmatter-migration-design.md | 225 ---- ...6-05-06-qualifier-agents-command-design.md | 202 ---- src/cli/commands/agents/pages/_overview.md | 2 - src/cli/commands/agents/pages/pitfalls.md | 8 + src/cli/commands/agents/pages/resolve.md | 27 +- src/cli/commands/agents/pages/workflows.md | 34 +- 8 files changed, 35 insertions(+), 2162 deletions(-) delete mode 100644 docs/superpowers/plans/2026-05-06-agents-cli-and-frontmatter.md delete mode 100644 docs/superpowers/plans/2026-05-06-qualifier-agents-command.md delete mode 100644 docs/superpowers/specs/2026-05-06-frontmatter-migration-design.md delete mode 100644 docs/superpowers/specs/2026-05-06-qualifier-agents-command-design.md diff --git a/docs/superpowers/plans/2026-05-06-agents-cli-and-frontmatter.md b/docs/superpowers/plans/2026-05-06-agents-cli-and-frontmatter.md deleted file mode 100644 index 530852a..0000000 --- a/docs/superpowers/plans/2026-05-06-agents-cli-and-frontmatter.md +++ /dev/null @@ -1,723 +0,0 @@ -# AGENTS-CLI cross-linking + frontmatter migration plan - -> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. - -**Goal:** Land two related changes for qualifier 0.5.1 — (a) cross-link the new AGENTS-CLI 0.1 protocol from qualifier's README and orientation page, declaring qualifier as the reference implementation; (b) migrate the agents page registry from a hand-coded `&[Page]` const to a build-time-generated registry derived from TOML frontmatter on each `pages/*.md`. - -**Architecture:** Phase A is two surgical doc edits. Phase B introduces a `build.rs` that walks `src/cli/commands/agents/pages/`, parses TOML frontmatter via `toml` + `serde`, and emits `$OUT_DIR/agents_pages.rs` containing `OVERVIEW: &str` and `PAGES: &[Page]`. `mod.rs` `include!()`s the generated file and drops the hand-coded const. - -**Tech Stack:** Rust 2024, clap 4, new build dependencies `toml = "0.8"` and `serde = { version = "1", features = ["derive"] }`. - -**Specs:** -- [AGENTS-CLI.md](../../../AGENTS-CLI.md) -- [docs/superpowers/specs/2026-05-06-frontmatter-migration-design.md](../specs/2026-05-06-frontmatter-migration-design.md) - ---- - -## File Structure - -**Created:** -- `build.rs` (crate root) — walks the pages directory at compile time, parses frontmatter, emits the registry. - -**Modified:** -- `README.md` — link to AGENTS-CLI.md near the top. -- `src/cli/commands/agents/pages/_overview.md` — add minimal frontmatter; add a conformance footer. -- `src/cli/commands/agents/pages/{concepts,workflows,pitfalls,record,reply,resolve,emit,show,ls,praise,review,compact}.md` — add TOML frontmatter (12 files). -- `src/cli/commands/agents/mod.rs` — replace hand-coded `&[Page]` with `include!()` of the generated file; expand `Page` struct with `sees_also` and `since`. -- `Cargo.toml` — add `[build-dependencies]`; bump version to 0.5.1. -- `CHANGELOG.md` — add a 0.5.1 entry. -- `tests/cli_integration.rs` — one new integration test. - -**Boundaries:** `build.rs` is the only code outside the agents module that's affected. The user-visible CLI behavior is unchanged. Existing tests pass without modification. - ---- - -## Task 1: Cross-link AGENTS-CLI from README - -**Files:** -- Modify: `README.md` - -- [ ] **Step 1: Read the current README to find the right insertion point** - -The CLI Commands section starts around line 46. The new link should sit *above* the CLI tables but *below* any existing intro/quickstart, so a reader sees "this tool implements AGENTS-CLI" before they scan the command tables. - -If there's an existing project-level intro paragraph, place the link right after it. Otherwise place it as a new paragraph just above the `## CLI` heading (or whichever heading currently introduces the command tables). - -- [ ] **Step 2: Add the link** - -Insert this paragraph at the chosen location: - -```markdown -> **For AI coding agents:** qualifier implements [AGENTS-CLI 0.1](AGENTS-CLI.md). Run `qualifier agents` for a self-contained guide. -``` - -The blockquote prefix (`> `) is intentional — it sets the tone as a sidebar / call-out rather than blending into the prose. - -- [ ] **Step 3: Verify the link target exists** - -Run: `ls AGENTS-CLI.md` -Expected: file exists at repo root (it does — committed in `a05d6f2`). - -- [ ] **Step 4: Render-check** - -Open `README.md` in any markdown previewer (or just re-read the diff). Confirm the new line reads cleanly in context and the link is a relative link (not `./AGENTS-CLI.md` or absolute). - -- [ ] **Step 5: Commit** - -```bash -git add README.md -git commit -m "docs: link AGENTS-CLI from README" -``` - ---- - -## Task 2: Conformance footer on `_overview.md` - -**Files:** -- Modify: `src/cli/commands/agents/pages/_overview.md` - -- [ ] **Step 1: Read the current `_overview.md`** - -Find the `## Reference` section near the bottom. - -- [ ] **Step 2: Append a conformance line** - -After the existing `## Reference` section content, before any final newline, add: - -```markdown - -## Protocol - -qualifier implements [AGENTS-CLI 0.1](https://github.com/empathic/qualifier/blob/main/AGENTS-CLI.md). The protocol defines the `agents` subcommand contract that this page satisfies. -``` - -The link is absolute (`https://...`) because the orientation page is read by an agent in someone else's repo — relative paths won't resolve. Use the canonical GitHub URL on the main branch. - -- [ ] **Step 3: Sanity-check render** - -Run: `cargo run --bin qualifier -- agents | tail -10` -Expected: the new "## Protocol" section appears at the bottom of the orientation output. - -- [ ] **Step 4: Run all tests to confirm nothing broke** - -Run: `cargo test --all-features` -Expected: all pass. (No tests assert on the absence of "## Protocol", so this should be clean.) - -- [ ] **Step 5: Commit** - -```bash -git add src/cli/commands/agents/pages/_overview.md -git commit -m "docs(agents): declare AGENTS-CLI 0.1 conformance in orientation page" -``` - ---- - -## Task 3: Frontmatter migration — atomic refactor - -This is the meat of the work. It lands as a single commit because the changes are interlocked: adding frontmatter to a page without a corresponding `build.rs` to strip it would break user-visible output, and shipping `build.rs` without frontmatter on the pages would cause it to fail. - -The task is large but each step is bite-sized. Follow the sequence; do not commit between steps. - -**Files:** -- Modify: `Cargo.toml` -- Create: `build.rs` -- Modify: `src/cli/commands/agents/pages/_overview.md` -- Modify: `src/cli/commands/agents/pages/{concepts,workflows,pitfalls,record,reply,resolve,emit,show,ls,praise,review,compact}.md` (12 files) -- Modify: `src/cli/commands/agents/mod.rs` -- Test: `tests/cli_integration.rs` - -- [ ] **Step 1: Add the new contract test (TDD red)** - -Append to `tests/cli_integration.rs`: - -```rust -#[test] -fn test_agents_orientation_summaries_match_pages() { - // Lock in the contract that the orientation page renders the topic - // index from frontmatter summaries (rather than hard-coded ones in - // mod.rs). Each topic's summary must appear in bare-agents output. - let dir = tempfile::tempdir().unwrap(); - let (stdout, _stderr, code) = run_qualifier(dir.path(), &["agents"]); - assert_eq!(code, 0); - for needle in [ - "Annotation model, kinds, supersession", // concepts - "Worked recipes for common tasks", // workflows - "Common mistakes agents make with qualifier", // pitfalls - "Record a new annotation", // record - ] { - assert!( - stdout.contains(needle), - "orientation should include summary '{needle}': {stdout}" - ); - } -} -``` - -This test passes today (the substrings already appear because `render_overview()` substitutes them in). It is a regression guard for the migration: if the build.rs ever fails to wire summaries through, this catches it. - -Run: `cargo test --test cli_integration test_agents_orientation_summaries_match_pages` -Expected: PASS already. (We're locking in the contract before touching the implementation.) - -- [ ] **Step 2: Bump version and add build dependencies in `Cargo.toml`** - -Find the `[package]` block and change: - -```toml -version = "0.5.0" -``` - -to: - -```toml -version = "0.5.1" -``` - -Then, **above** the existing `[features]` block (or wherever fits the file's structure), add a new `[build-dependencies]` section: - -```toml -[build-dependencies] -toml = "0.8" -serde = { version = "1", features = ["derive"] } -``` - -Run: `cargo build` -Expected: succeeds (no `build.rs` exists yet, so deps are unused but resolved). Cargo may print a warning about unused build-dependencies — that's fine; it goes away in Step 3. - -- [ ] **Step 3: Create `build.rs` at the crate root** - -Create `build.rs` with the full content below. This is the complete file — copy verbatim, don't trim. - -```rust -//! Build-time generator for the `qualifier agents` page registry. -//! -//! Walks `src/cli/commands/agents/pages/*.md`, parses TOML frontmatter -//! delimited by `+++` lines, and emits `$OUT_DIR/agents_pages.rs` -//! containing: -//! -//! - `pub const OVERVIEW: &str = "...";` -//! - `pub const PAGES: &[Page] = &[...];` -//! -//! `Page` is defined in `src/cli/commands/agents/mod.rs`. The generated -//! file is `include!`d there. - -use std::collections::HashSet; -use std::env; -use std::fs; -use std::path::PathBuf; - -use serde::Deserialize; - -const PAGES_DIR: &str = "src/cli/commands/agents/pages"; - -#[derive(Deserialize)] -struct PageMeta { - name: String, - summary: Option, - #[serde(default)] - sees_also: Vec, - since: Option, -} - -fn main() { - println!("cargo:rerun-if-changed={PAGES_DIR}"); - println!("cargo:rerun-if-changed=build.rs"); - - let mut entries: Vec<_> = fs::read_dir(PAGES_DIR) - .unwrap_or_else(|e| panic!("read {PAGES_DIR}: {e}")) - .filter_map(Result::ok) - .filter(|e| e.path().extension().and_then(|s| s.to_str()) == Some("md")) - .collect(); - entries.sort_by_key(|e| e.path()); - - let mut overview_body: Option = None; - let mut topic_entries: Vec = Vec::new(); - let mut seen_names: HashSet = HashSet::new(); - - for entry in entries { - let path = entry.path(); - let stem = path - .file_stem() - .and_then(|s| s.to_str()) - .unwrap_or_else(|| panic!("non-utf8 filename: {}", path.display())) - .to_string(); - let raw = fs::read_to_string(&path) - .unwrap_or_else(|e| panic!("read {}: {e}", path.display())); - - let after_open = raw.strip_prefix("+++\n").unwrap_or_else(|| { - panic!( - "{}: file must begin with '+++' frontmatter delimiter on its first line", - path.display() - ) - }); - let close_offset = after_open.find("\n+++\n").unwrap_or_else(|| { - panic!( - "{}: missing closing '+++' frontmatter delimiter", - path.display() - ) - }); - let frontmatter = &after_open[..close_offset]; - let body = &after_open[close_offset + "\n+++\n".len()..]; - - let meta: PageMeta = toml::from_str(frontmatter) - .unwrap_or_else(|e| panic!("{}: invalid TOML frontmatter: {e}", path.display())); - - if meta.name != stem { - panic!( - "{}: frontmatter name '{}' does not match filename stem '{}'", - path.display(), - meta.name, - stem - ); - } - if !seen_names.insert(meta.name.clone()) { - panic!("duplicate page name '{}'", meta.name); - } - - if stem.starts_with('_') { - if stem == "_overview" { - overview_body = Some(body.to_string()); - } else { - panic!( - "{}: unsupported internal page; only '_overview' is recognized", - path.display() - ); - } - } else { - let summary = meta.summary.unwrap_or_else(|| { - panic!( - "{}: topic page is missing required 'summary' field", - path.display() - ) - }); - let sees_also_lit = meta - .sees_also - .iter() - .map(|s| format!("{s:?}")) - .collect::>() - .join(", "); - let since_lit = match meta.since { - Some(v) => format!("Some({v:?})"), - None => "None".into(), - }; - topic_entries.push(format!( - " Page {{ name: {:?}, summary: {:?}, sees_also: &[{sees_also_lit}], since: {since_lit}, body: {:?} }},", - meta.name, summary, body, - )); - } - } - - let overview = overview_body.unwrap_or_else(|| { - panic!("{PAGES_DIR}/_overview.md is required but not found"); - }); - - let generated = format!( - "pub const OVERVIEW: &str = {overview:?};\n\npub const PAGES: &[Page] = &[\n{}\n];\n", - topic_entries.join("\n"), - ); - - let out_path = PathBuf::from(env::var("OUT_DIR").unwrap()).join("agents_pages.rs"); - fs::write(&out_path, generated) - .unwrap_or_else(|e| panic!("write {}: {e}", out_path.display(), )); -} -``` - -Run: `cargo build` -Expected: FAILS — every page lacks frontmatter, and `build.rs` will panic on the first file that doesn't start with `+++`. The error message will name the file. This is expected; we're about to fix it. - -- [ ] **Step 4: Add frontmatter to `_overview.md`** - -Open `src/cli/commands/agents/pages/_overview.md`. Prepend, as the very first lines of the file: - -``` -+++ -name = "_overview" -+++ - -``` - -(The trailing blank line is part of the prepend so the existing body is offset cleanly.) - -The frontmatter for `_overview.md` is intentionally minimal: it has no `summary` because it isn't in the topic registry, and no `since` because it's the orientation page that always exists. - -- [ ] **Step 5: Add frontmatter to topical pages** - -For each of `concepts.md`, `workflows.md`, `pitfalls.md`, prepend frontmatter using the existing summary strings from the current `mod.rs`. **The summary text MUST match `mod.rs::PAGES` exactly** so that orientation output is byte-identical after the migration. - -`concepts.md`: - -``` -+++ -name = "concepts" -summary = "Annotation model, kinds, supersession, IDs, .qual layout" -since = "0.5.0" -+++ - -``` - -`workflows.md`: - -``` -+++ -name = "workflows" -summary = "Worked recipes for common tasks" -since = "0.5.0" -+++ - -``` - -`pitfalls.md`: - -``` -+++ -name = "pitfalls" -summary = "Common mistakes agents make with qualifier" -since = "0.5.0" -+++ - -``` - -- [ ] **Step 6: Add frontmatter to per-subcommand pages** - -For each of the 9 per-subcommand pages, prepend frontmatter with the matching summary. The exact summaries to use come from the current hand-coded `PAGES` array in `src/cli/commands/agents/mod.rs`. Read the file once and copy each summary verbatim. - -`record.md`: - -``` -+++ -name = "record" -summary = "Record a new annotation" -sees_also = ["reply", "resolve", "emit"] -since = "0.5.0" -+++ - -``` - -`reply.md`: - -``` -+++ -name = "reply" -summary = "Reply to an existing record" -sees_also = ["resolve", "record"] -since = "0.5.0" -+++ - -``` - -`resolve.md`: - -``` -+++ -name = "resolve" -summary = "Resolve (close) a record" -sees_also = ["reply", "record"] -since = "0.5.0" -+++ - -``` - -`emit.md`: - -``` -+++ -name = "emit" -summary = "Emit a raw record of any type" -sees_also = ["record"] -since = "0.5.0" -+++ - -``` - -`show.md`: - -``` -+++ -name = "show" -summary = "Show annotations for an artifact" -sees_also = ["ls", "praise", "review"] -since = "0.5.0" -+++ - -``` - -`ls.md`: - -``` -+++ -name = "ls" -summary = "List artifacts by kind" -sees_also = ["show"] -since = "0.5.0" -+++ - -``` - -`praise.md`: - -``` -+++ -name = "praise" -summary = "Show who annotated an artifact and why" -sees_also = ["show"] -since = "0.5.0" -+++ - -``` - -`review.md`: - -``` -+++ -name = "review" -summary = "Check freshness of annotations against current code" -sees_also = ["show", "compact"] -since = "0.5.0" -+++ - -``` - -`compact.md`: - -``` -+++ -name = "compact" -summary = "Compact a .qual file" -sees_also = ["review"] -since = "0.5.0" -+++ - -``` - -- [ ] **Step 7: Verify `build.rs` produces the registry** - -Run: `cargo build 2>&1 | head -40` -Expected: clean build. No more panics. (If a panic happens, the error names the offending file; fix the frontmatter and re-run.) - -You can sanity-check the generated file: - -```bash -find target -name agents_pages.rs -path '*/build/*' | head -1 | xargs head -5 -``` - -Expected: shows `pub const OVERVIEW: &str = "qualifier agents — guide for AI coding agents...` followed by the `PAGES` array. - -- [ ] **Step 8: Update `mod.rs` to consume the generated registry** - -Open `src/cli/commands/agents/mod.rs`. Make these specific changes: - -**Replace the `Page` struct** (currently `struct Page { name, summary, body }`) with the expanded shape: - -```rust -pub struct Page { - pub name: &'static str, - pub summary: &'static str, - pub sees_also: &'static [&'static str], - pub since: Option<&'static str>, - pub body: &'static str, -} -``` - -(Note `pub` on the struct and fields — the generated code references them.) - -**Delete the hand-coded `OVERVIEW` line and the entire `PAGES` const definition** (the const ends at the closing `];`). - -**Insert in their place:** - -```rust -include!(concat!(env!("OUT_DIR"), "/agents_pages.rs")); -``` - -The rest of the file (`Args`, `topic_names()`, `run()`, `render_overview()`, the `#[cfg(test)] mod tests` block) stays unchanged. They consume `OVERVIEW` and `PAGES` exactly as before. - -- [ ] **Step 9: Build and run all tests** - -Run: `cargo build && cargo test --all-features` -Expected: full suite green. The orientation output matches byte-for-byte what it produced before the migration. - -If a test fails, the most likely cause is summary text drift — verify your frontmatter `summary` strings match the originals exactly. - -- [ ] **Step 10: Manual smoke** - -```bash -target/debug/qualifier agents | head -10 -target/debug/qualifier agents record | head -10 -target/debug/qualifier agents bogus 2>&1 ; echo "exit=$?" -``` - -Expected: -- `agents` shows the orientation including the topic list with all 12 summaries (no `+++` leakage anywhere). -- `agents record` shows the record page (no `+++` leakage). -- `agents bogus` shows `no such topic 'bogus'. Available: concepts, workflows, ...` and `exit=2`. - -- [ ] **Step 11: fmt + clippy** - -Run: `cargo fmt && cargo clippy --all-targets --all-features -- -D warnings` -Expected: clean. - -- [ ] **Step 12: Commit** - -```bash -git add Cargo.toml build.rs src/cli/commands/agents tests/cli_integration.rs -git commit -m "$(cat <<'EOF' -refactor(agents): generate page registry from TOML frontmatter - -Replaces the hand-coded &[Page] in src/cli/commands/agents/mod.rs with -a build.rs-generated const derived from frontmatter on each -pages/*.md. Adds toml + serde as build dependencies; emits -$OUT_DIR/agents_pages.rs included by mod.rs. New schema: -name (required), summary (required for topic pages), sees_also -(optional, parsed but not yet rendered), since (optional). User-visible -CLI behavior is unchanged. -EOF -)" -``` - ---- - -## Task 4: Add a contract unit test for non-empty summaries - -**Files:** -- Modify: `src/cli/commands/agents/mod.rs` - -This is a small follow-up that adds belt-and-suspenders coverage on the new build pipeline. Locks in the invariant that every topic page has a non-empty summary. - -- [ ] **Step 1: Append to the existing `#[cfg(test)] mod tests` block in `mod.rs`** - -The block already contains `overview_contains_topics_sentinel`. Add a second test alongside it: - -```rust - #[test] - fn all_pages_have_non_empty_summaries() { - for page in PAGES { - assert!( - !page.summary.is_empty(), - "page '{}' has empty summary", - page.name - ); - } - } -``` - -- [ ] **Step 2: Run the test** - -Run: `cargo test all_pages_have_non_empty_summaries` -Expected: PASS. - -- [ ] **Step 3: Run full suite + clippy** - -Run: `cargo test --all-features && cargo clippy --all-targets --all-features -- -D warnings` -Expected: clean. - -- [ ] **Step 4: Commit** - -```bash -git add src/cli/commands/agents/mod.rs -git commit -m "test(agents): assert every page has a non-empty summary" -``` - ---- - -## Task 5: CHANGELOG entry - -**Files:** -- Modify: `CHANGELOG.md` - -- [ ] **Step 1: Open `CHANGELOG.md` and find the existing 0.5.0 section** - -The file currently has `## [0.5.0] — unreleased` (or, if 0.5.0 has been published, a dated header). Either way, the new entry goes above it. - -- [ ] **Step 2: Add a 0.5.1 section above 0.5.0** - -```markdown -## [0.5.1] — unreleased - -### Added - -- **AGENTS-CLI 0.1 protocol document** (`AGENTS-CLI.md`) — a draft - cross-tool convention for CLI tools that self-describe to AI coding - agents. qualifier is named as the reference implementation. The - protocol defines five MUST rules (`agents` subcommand, bare - orientation, topic dispatch, exit-2 unknown-topic, `--help` - discoverability) and four SHOULD recommendations. - -### Changed - -- **Internal:** the `qualifier agents` page registry is now generated - at build time from TOML frontmatter on each `pages/*.md` file - instead of being a hand-coded `&[Page]` array in `mod.rs`. New - per-page fields: `name`, `summary`, `sees_also`, `since`. - User-visible CLI behavior is unchanged. -``` - -- [ ] **Step 3: Commit** - -```bash -git add CHANGELOG.md -git commit -m "docs: changelog for 0.5.1 (AGENTS-CLI + frontmatter)" -``` - ---- - -## Task 6: Final verification - -**Files:** none new - -- [ ] **Step 1: fmt** - -Run: `cargo fmt` -Expected: no diff. - -- [ ] **Step 2: clippy** - -Run: `cargo clippy --all-targets --all-features -- -D warnings` -Expected: clean. - -- [ ] **Step 3: full test suite** - -Run: `cargo test --all-features` -Expected: all tests pass — including the existing 6 agents tests and the 2 new ones (`test_agents_orientation_summaries_match_pages` integration, `all_pages_have_non_empty_summaries` unit). - -- [ ] **Step 4: end-to-end manual smoke** - -```bash -target/debug/qualifier --help -target/debug/qualifier agents -target/debug/qualifier agents concepts -target/debug/qualifier agents record -target/debug/qualifier agents bogus 2>&1 ; echo "exit=$?" -``` - -Expected: -- `--help` shows the "For AI agents:" group at the top. -- `agents` shows the orientation page with the topic list rendered from frontmatter summaries, ending with the new "## Protocol" footer linking AGENTS-CLI.md. -- `agents concepts` and `agents record` show their pages, no `+++` leakage. -- `agents bogus` exits 2 with the available list. - -- [ ] **Step 5: confirm `Cargo.toml` version is 0.5.1** - -```bash -grep '^version' Cargo.toml -``` - -Expected: `version = "0.5.1"`. - -- [ ] **Step 6: confirm AGENTS-CLI link from README is live** - -```bash -grep -n "AGENTS-CLI" README.md -``` - -Expected: at least one match referencing `AGENTS-CLI.md`. - -- [ ] **Step 7: if anything failed, fix and re-run from Step 1** - -- [ ] **Step 8: optional final fmt/clippy commit** - -```bash -git status -# If only fmt/clippy fixups remain: -git add -u -git commit -m "chore: final fmt/clippy fixups for 0.5.1" -``` - -If the working tree is clean, skip. diff --git a/docs/superpowers/plans/2026-05-06-qualifier-agents-command.md b/docs/superpowers/plans/2026-05-06-qualifier-agents-command.md deleted file mode 100644 index 049daf3..0000000 --- a/docs/superpowers/plans/2026-05-06-qualifier-agents-command.md +++ /dev/null @@ -1,976 +0,0 @@ -# `qualifier agents` Implementation Plan - -> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. - -**Goal:** Add a `qualifier agents` subcommand that prints a self-contained guide so an AI coding agent can bootstrap into productive use of qualifier without a separately-installed skill. - -**Architecture:** New `src/cli/commands/agents/` module with a registry of pages (each loaded via `include_str!()` from `pages/*.md`). Single positional arg: bare → orientation page with `{{TOPICS}}` index substitution; named → that page's body verbatim; unknown → exit 2. New "For AI agents:" group at the top of the existing `HELP_TEMPLATE`. - -**Tech Stack:** Rust 2024, clap 4, existing CLI integration test harness (`tests/cli_integration.rs`) using `std::process::Command`. - -**Spec:** [docs/superpowers/specs/2026-05-06-qualifier-agents-command-design.md](../specs/2026-05-06-qualifier-agents-command-design.md) - ---- - -## File Structure - -**Created:** -- `src/cli/commands/agents/mod.rs` — clap `Args`, `run()`, registry, dispatch, overview rendering -- `src/cli/commands/agents/pages/_overview.md` — orientation (printed by bare `qualifier agents`) -- `src/cli/commands/agents/pages/concepts.md` — annotation model, kinds, supersession, IDs, layout -- `src/cli/commands/agents/pages/workflows.md` — five worked recipes -- `src/cli/commands/agents/pages/pitfalls.md` — common mistakes (stub-friendly) -- `src/cli/commands/agents/pages/{record,reply,resolve,emit,show,ls,praise,review,compact}.md` — per-subcommand pages (9 files) - -**Modified:** -- `src/cli/commands/mod.rs` — add `pub mod agents;` -- `src/cli/mod.rs` — add `Agents` variant, dispatch arm, update `HELP_TEMPLATE` -- `tests/cli_integration.rs` — new tests (see Task 9) -- `Cargo.toml` — bump version (e.g., 0.4.0 → 0.5.0; pick whichever next-minor matches crates.io state) -- `CHANGELOG.md` — note new subcommand under Added -- `README.md` — add `agents` to the CLI Commands tables under a new "For AI agents" sub-table - -**Boundaries:** the agents module is self-contained. Its only outward dependency is being wired into the `Commands` enum and `HELP_TEMPLATE` in `src/cli/mod.rs`. It does not touch `annotation.rs`, `qual_file.rs`, or any other domain code. - ---- - -## Task 1: Scaffold the agents module - -**Files:** -- Create: `src/cli/commands/agents/mod.rs` -- Create: `src/cli/commands/agents/pages/_overview.md` (placeholder) -- Modify: `src/cli/commands/mod.rs` -- Modify: `src/cli/mod.rs` -- Test: `tests/cli_integration.rs` - -- [ ] **Step 1: Write the failing test** - -Append to `tests/cli_integration.rs`: - -```rust -// --- qualifier agents --- - -#[test] -fn test_agents_bare_invocation_succeeds() { - let dir = tempfile::tempdir().unwrap(); - let (stdout, stderr, code) = run_qualifier(dir.path(), &["agents"]); - assert_eq!(code, 0, "agents should succeed: stderr={stderr}"); - assert!(!stdout.is_empty(), "agents should print something"); -} -``` - -- [ ] **Step 2: Run test to verify it fails** - -Run: `cargo test --test cli_integration test_agents_bare_invocation_succeeds` -Expected: FAIL with clap error like "unrecognized subcommand 'agents'". - -- [ ] **Step 3: Create the placeholder overview page** - -Create `src/cli/commands/agents/pages/_overview.md` with one line so `include_str!` succeeds: - -```markdown -qualifier agents — orientation page (placeholder) -``` - -- [ ] **Step 4: Create the agents module** - -Create `src/cli/commands/agents/mod.rs`: - -```rust -use clap::Parser; - -#[derive(Parser, Debug)] -pub struct Args { - /// Topic name (e.g. `record`, `concepts`, `workflows`). Omit to print the orientation page. - pub topic: Option, -} - -const OVERVIEW: &str = include_str!("pages/_overview.md"); - -pub fn run(args: Args) -> crate::Result<()> { - match args.topic.as_deref() { - None => { - print!("{OVERVIEW}"); - Ok(()) - } - Some(name) => { - eprintln!("qualifier agents: no such topic '{name}'. Available: (none yet)"); - std::process::exit(2); - } - } -} -``` - -- [ ] **Step 5: Register the module** - -Modify `src/cli/commands/mod.rs`. Add the line in alphabetical order: - -```rust -pub mod agents; -pub mod compact; -pub mod emit; -// ... existing entries -``` - -- [ ] **Step 6: Wire the subcommand into `Commands`** - -Modify `src/cli/mod.rs`. Add the variant inside `enum Commands`: - -```rust - /// Self-contained guide for AI coding agents (start here) - Agents(commands::agents::Args), -``` - -Place it as the first variant (above `Record`) so it sits logically with the new "For AI agents" group. - -Then add the dispatch arm inside the `match cli.command` block in `pub fn run()`: - -```rust - Commands::Agents(args) => commands::agents::run(args), -``` - -- [ ] **Step 7: Run the test to verify it passes** - -Run: `cargo test --test cli_integration test_agents_bare_invocation_succeeds` -Expected: PASS. - -- [ ] **Step 8: Run the full test suite to ensure nothing broke** - -Run: `cargo test --all-features` -Expected: all tests pass. - -- [ ] **Step 9: Commit** - -```bash -git add src/cli/commands/agents src/cli/commands/mod.rs src/cli/mod.rs tests/cli_integration.rs -git commit -m "feat(agents): scaffold qualifier agents subcommand" -``` - ---- - -## Task 2: Add the unknown-topic error path - -**Files:** -- Test: `tests/cli_integration.rs` - -- [ ] **Step 1: Write the failing test** - -Append to `tests/cli_integration.rs`: - -```rust -#[test] -fn test_agents_unknown_topic_exits_2() { - let dir = tempfile::tempdir().unwrap(); - let (_stdout, stderr, code) = run_qualifier(dir.path(), &["agents", "bogus-topic"]); - assert_eq!(code, 2, "unknown topic should exit 2: stderr={stderr}"); - assert!( - stderr.contains("no such topic"), - "stderr should explain: {stderr}" - ); - assert!( - stderr.contains("bogus-topic"), - "stderr should name the bad topic: {stderr}" - ); -} -``` - -- [ ] **Step 2: Run test to verify it passes already** - -Run: `cargo test --test cli_integration test_agents_unknown_topic_exits_2` -Expected: PASS (Task 1's `run()` already prints the error and exits 2). - -If it fails, re-check the `eprintln!` wording in `agents/mod.rs` — it must contain the literal string `no such topic`. - -- [ ] **Step 3: Commit** - -```bash -git add tests/cli_integration.rs -git commit -m "test(agents): cover unknown-topic exit path" -``` - ---- - -## Task 3: Introduce the page registry and per-topic dispatch - -**Files:** -- Modify: `src/cli/commands/agents/mod.rs` -- Create: `src/cli/commands/agents/pages/concepts.md` (placeholder) -- Create: `src/cli/commands/agents/pages/workflows.md` (placeholder) -- Create: `src/cli/commands/agents/pages/pitfalls.md` (placeholder) -- Create: `src/cli/commands/agents/pages/{record,reply,resolve,emit,show,ls,praise,review,compact}.md` (placeholders, 9 files) -- Test: `tests/cli_integration.rs` - -- [ ] **Step 1: Write the failing tests** - -Append to `tests/cli_integration.rs`: - -```rust -#[test] -fn test_agents_concepts_topic_prints_body() { - let dir = tempfile::tempdir().unwrap(); - let (stdout, stderr, code) = run_qualifier(dir.path(), &["agents", "concepts"]); - assert_eq!(code, 0, "agents concepts should succeed: stderr={stderr}"); - assert!(!stdout.is_empty(), "should print body"); -} - -#[test] -fn test_agents_all_registered_topics_render() { - // Each topic in the registry must produce non-empty stdout with exit 0. - // If you add a topic, add it here too. - let topics = [ - "concepts", "workflows", "pitfalls", - "record", "reply", "resolve", "emit", - "show", "ls", "praise", "review", "compact", - ]; - let dir = tempfile::tempdir().unwrap(); - for topic in topics { - let (stdout, stderr, code) = run_qualifier(dir.path(), &["agents", topic]); - assert_eq!(code, 0, "agents {topic} should succeed: stderr={stderr}"); - assert!(!stdout.is_empty(), "agents {topic} should print body"); - } -} -``` - -- [ ] **Step 2: Run tests to verify they fail** - -Run: `cargo test --test cli_integration test_agents_concepts_topic_prints_body test_agents_all_registered_topics_render` -Expected: FAIL — both topics are unknown. - -- [ ] **Step 3: Create placeholder markdown files** - -Create each file with a single placeholder line so `include_str!` resolves and the smoke test passes. You can use the same one-liner for all of them: - -```markdown -qualifier agents — page (placeholder; replace in Task 7/8) -``` - -Replace `` with the topic name. Files to create: - -- `src/cli/commands/agents/pages/concepts.md` -- `src/cli/commands/agents/pages/workflows.md` -- `src/cli/commands/agents/pages/pitfalls.md` -- `src/cli/commands/agents/pages/record.md` -- `src/cli/commands/agents/pages/reply.md` -- `src/cli/commands/agents/pages/resolve.md` -- `src/cli/commands/agents/pages/emit.md` -- `src/cli/commands/agents/pages/show.md` -- `src/cli/commands/agents/pages/ls.md` -- `src/cli/commands/agents/pages/praise.md` -- `src/cli/commands/agents/pages/review.md` -- `src/cli/commands/agents/pages/compact.md` - -- [ ] **Step 4: Replace `agents/mod.rs` with the registry-backed implementation** - -Replace the contents of `src/cli/commands/agents/mod.rs` with: - -```rust -use clap::Parser; - -#[derive(Parser, Debug)] -pub struct Args { - /// Topic name (e.g. `record`, `concepts`, `workflows`). Omit to print the orientation page. - pub topic: Option, -} - -struct Page { - name: &'static str, - summary: &'static str, - body: &'static str, -} - -const OVERVIEW: &str = include_str!("pages/_overview.md"); - -const PAGES: &[Page] = &[ - Page { - name: "concepts", - summary: "Annotation model, kinds, supersession, IDs, .qual layout", - body: include_str!("pages/concepts.md"), - }, - Page { - name: "workflows", - summary: "Worked recipes for common tasks", - body: include_str!("pages/workflows.md"), - }, - Page { - name: "pitfalls", - summary: "Common mistakes agents make with qualifier", - body: include_str!("pages/pitfalls.md"), - }, - Page { - name: "record", - summary: "Record a new annotation", - body: include_str!("pages/record.md"), - }, - Page { - name: "reply", - summary: "Reply to an existing record", - body: include_str!("pages/reply.md"), - }, - Page { - name: "resolve", - summary: "Resolve (close) a record", - body: include_str!("pages/resolve.md"), - }, - Page { - name: "emit", - summary: "Emit a raw record of any type", - body: include_str!("pages/emit.md"), - }, - Page { - name: "show", - summary: "Show annotations for an artifact", - body: include_str!("pages/show.md"), - }, - Page { - name: "ls", - summary: "List artifacts by kind", - body: include_str!("pages/ls.md"), - }, - Page { - name: "praise", - summary: "Show who annotated an artifact and why", - body: include_str!("pages/praise.md"), - }, - Page { - name: "review", - summary: "Check freshness of annotations against current code", - body: include_str!("pages/review.md"), - }, - Page { - name: "compact", - summary: "Compact a .qual file", - body: include_str!("pages/compact.md"), - }, -]; - -fn topic_names() -> String { - PAGES - .iter() - .map(|p| p.name) - .collect::>() - .join(", ") -} - -pub fn run(args: Args) -> crate::Result<()> { - match args.topic.as_deref() { - None => { - print!("{}", render_overview()); - Ok(()) - } - Some(name) => { - if let Some(page) = PAGES.iter().find(|p| p.name == name) { - print!("{}", page.body); - Ok(()) - } else { - eprintln!( - "qualifier agents: no such topic '{name}'. Available: {}", - topic_names() - ); - std::process::exit(2); - } - } - } -} - -fn render_overview() -> String { - // Replace the {{TOPICS}} sentinel with a `name — summary` listing. - let topics_block: String = PAGES - .iter() - .map(|p| format!("- `{}` — {}", p.name, p.summary)) - .collect::>() - .join("\n"); - OVERVIEW.replace("{{TOPICS}}", &topics_block) -} -``` - -- [ ] **Step 5: Run the per-topic tests to verify they pass** - -Run: `cargo test --test cli_integration test_agents_concepts_topic_prints_body test_agents_all_registered_topics_render` -Expected: PASS. - -- [ ] **Step 6: Re-run all earlier agents tests** - -Run: `cargo test --test cli_integration test_agents` -Expected: all four `test_agents_*` tests pass. - -- [ ] **Step 7: Commit** - -```bash -git add src/cli/commands/agents tests/cli_integration.rs -git commit -m "feat(agents): registry and per-topic dispatch with placeholder pages" -``` - ---- - -## Task 4: Implement `{{TOPICS}}` substitution in the overview - -**Files:** -- Modify: `src/cli/commands/agents/pages/_overview.md` -- Test: `tests/cli_integration.rs` - -- [ ] **Step 1: Write the failing tests** - -Append to `tests/cli_integration.rs`: - -```rust -#[test] -fn test_agents_overview_renders_topics_index() { - let dir = tempfile::tempdir().unwrap(); - let (stdout, _stderr, code) = run_qualifier(dir.path(), &["agents"]); - assert_eq!(code, 0); - // Every registered topic name should appear in the rendered overview. - for topic in [ - "concepts", "workflows", "pitfalls", - "record", "reply", "resolve", "emit", - "show", "ls", "praise", "review", "compact", - ] { - assert!( - stdout.contains(topic), - "overview should mention topic '{topic}': {stdout}" - ); - } - // The literal sentinel must not leak through. - assert!( - !stdout.contains("{{TOPICS}}"), - "sentinel should be substituted: {stdout}" - ); -} -``` - -Also add a unit test inside `src/cli/commands/agents/mod.rs` (append to the file): - -```rust -#[cfg(test)] -mod tests { - #[test] - fn overview_contains_topics_sentinel() { - // If this fails, the orientation page lost the {{TOPICS}} substitution - // anchor and the rendered overview will no longer list children. - assert!(super::OVERVIEW.contains("{{TOPICS}}")); - } -} -``` - -- [ ] **Step 2: Run the integration test to verify it fails** - -Run: `cargo test --test cli_integration test_agents_overview_renders_topics_index` -Expected: FAIL — the placeholder `_overview.md` from Task 1 doesn't contain topic names or `{{TOPICS}}`. - -- [ ] **Step 3: Replace `_overview.md` with a real (still concise) orientation that uses the sentinel** - -Replace contents of `src/cli/commands/agents/pages/_overview.md` with the following. This is the launch version; deeper content is filled in during Task 7. - -```markdown -# qualifier — guide for AI coding agents - -You are an AI coding agent in a user's repository. The `qualifier` CLI is -installed. This page tells you what qualifier is, when to use it, and how to -get more detail on any specific feature. - -## What it is - -Qualifier records *annotations* — structured, content-addressed notes about -software artifacts (files, directories, line ranges). Annotations live in -`.qual` files alongside the code, are intended to be committed to version -control, and form a durable, reviewable record of quality observations -attached to specific places in the codebase. - -## When to use it - -- You found a bug, smell, risk, or stylistic concern that survives this - edit and is worth surfacing for whoever touches the code next. Record it. -- You want to praise a piece of code so future readers know it was - intentional, not accidental. Record it. -- An earlier annotation no longer applies (the code changed, the concern - was addressed). Resolve it. - -## When NOT to use it - -- One-off scratch debugging notes. Use scratch space. -- Information that belongs in the commit message (what *this* change does - and why). Use git. -- Project-wide policy or architecture decisions. Those belong in - `docs/` or an ADR, not as an annotation on a file. - -## Quickstart - -```bash -# Record a concern about a function in src/foo.rs -qualifier record concern src/foo.rs --message "tight coupling to the cache" - -# See what's annotated on a file -qualifier show src/foo.rs - -# After fixing it, resolve the annotation by id-prefix or location -qualifier resolve src/foo.rs --message "refactored to inject the cache" -``` - -## Available topics - -Run `qualifier agents ` for any of: - -{{TOPICS}} - -## Reference - -For exact flag tables on any subcommand, run `qualifier --help`. -For the JSONL wire format and library API, see `SPEC.md` in this repo (if -present) or the published spec. -``` - -- [ ] **Step 4: Run the failing tests to verify they now pass** - -Run: `cargo test --test cli_integration test_agents_overview_renders_topics_index` -Expected: PASS. - -- [ ] **Step 5: Run the unit test** - -Run: `cargo test --lib agents::tests::overview_contains_topics_sentinel` -Expected: PASS. - -If `cargo test --lib` doesn't find the test, the agents module may not be exposed under the library crate (it's CLI-only). In that case, the unit test will be discovered when running `cargo test --all-features` or `cargo test --bin qualifier`. Either is fine; the test just needs to run somewhere. - -- [ ] **Step 6: Run the whole suite** - -Run: `cargo test --all-features` -Expected: all tests pass. - -- [ ] **Step 7: Commit** - -```bash -git add src/cli/commands/agents tests/cli_integration.rs -git commit -m "feat(agents): render overview with topic index" -``` - ---- - -## Task 5: Add the "For AI agents" group to `--help` - -**Files:** -- Modify: `src/cli/mod.rs` -- Test: `tests/cli_integration.rs` - -- [ ] **Step 1: Write the failing test** - -Append to `tests/cli_integration.rs`: - -```rust -#[test] -fn test_top_level_help_shows_agents_group() { - let dir = tempfile::tempdir().unwrap(); - let (stdout, _stderr, code) = run_qualifier(dir.path(), &["--help"]); - assert_eq!(code, 0); - assert!( - stdout.contains("For AI agents:"), - "help should show the agents group header: {stdout}" - ); - // The agents row should appear under that header, with the "start here" nudge. - assert!( - stdout.contains("agents") && stdout.contains("start here"), - "help should mention the agents subcommand: {stdout}" - ); -} -``` - -- [ ] **Step 2: Run the test to verify it fails** - -Run: `cargo test --test cli_integration test_top_level_help_shows_agents_group` -Expected: FAIL — `HELP_TEMPLATE` doesn't yet include the new group. - -- [ ] **Step 3: Update `HELP_TEMPLATE` in `src/cli/mod.rs`** - -Locate the `const HELP_TEMPLATE: &str = "..."` block. Insert a new group **above** "Record observations:" so the agents row is the first thing under the usage line: - -```rust -const HELP_TEMPLATE: &str = "\ -{about-with-newline} -{usage-heading} {usage} - -For AI agents: - agents Self-contained guide for AI coding agents (start here) - -Record observations: - record Record an annotation: `qualifier record [message]` - reply Reply to an existing record (id-prefix or location) - resolve Resolve (close) an existing record (id-prefix or location) - emit Emit a raw record of any type - -Inspect annotations: - show Show annotations for an artifact - ls List artifacts by kind - praise Show who annotated an artifact and why (alias: blame) - review Check freshness of annotations against current code - -Maintain: - compact Compact a .qual file - -Other: - haiku Print a random qualifier haiku - help Print this message or the help of the given subcommand(s) - -Run `qualifier --help` for command-specific options. - -Options: -{options} -"; -``` - -The only change is inserting the four lines `For AI agents:` … `agents Self-contained guide for AI coding agents (start here)` plus the blank line that follows. Existing groups are untouched. - -- [ ] **Step 4: Run the test to verify it passes** - -Run: `cargo test --test cli_integration test_top_level_help_shows_agents_group` -Expected: PASS. - -- [ ] **Step 5: Run all tests** - -Run: `cargo test --all-features` -Expected: all tests pass. - -- [ ] **Step 6: Commit** - -```bash -git add src/cli/mod.rs tests/cli_integration.rs -git commit -m "feat(agents): surface agents group at top of --help" -``` - ---- - -## Task 6: Format and lint check (mid-implementation gate) - -**Files:** none new - -This is a checkpoint before writing prose. The infrastructure is in place; lock it in by ensuring the toolchain is happy. - -- [ ] **Step 1: Format** - -Run: `cargo fmt` -Expected: no output (or whitespace-only diffs). - -- [ ] **Step 2: Lint** - -Run: `cargo clippy --all-targets --all-features -- -D warnings` -Expected: clean. - -- [ ] **Step 3: If clippy or fmt produced changes, commit them** - -```bash -git add -u -git commit -m "style: cargo fmt and clippy fixes for agents module" -``` - -If both produced no changes, skip the commit. - ---- - -## Task 7: Write content for the topical pages - -This task fills in the four topical pages with real prose. Each step is "write one file." Follow the spec's per-page templates exactly. - -The spec's templates are reproduced inline below so you don't need to switch documents. - -**Files:** -- Modify: `src/cli/commands/agents/pages/concepts.md` -- Modify: `src/cli/commands/agents/pages/workflows.md` -- Modify: `src/cli/commands/agents/pages/pitfalls.md` - -(`_overview.md` was finalized in Task 4; no edit needed here.) - -- [ ] **Step 1: Write `concepts.md` (~150 lines)** - -Replace the placeholder. Cover, in this order, with section headings: - -1. **The annotation envelope** — explain the metabox envelope (fields in fixed order: `metabox`, `type`, `subject`, `issuer`, `issuer_type`, `created_at`, `id`, `body`). Note: `metabox` is always `"1"`. Keep terse — agents need to recognize the shape, not memorize it. -2. **Kinds** — list the kinds (`note`, `concern`, `bug`, `praise`, plus any others present in `src/annotation.rs`'s `Kind` enum) and give one-sentence guidance per kind on when to choose it. -3. **Supersession** — explain that an annotation can supersede an earlier one (by id), chains must be acyclic, and cross-subject supersession is rejected. -4. **`.qual` file layout and discovery** — directory-level vs file-level `.qual` files, that discovery walks up looking for VCS markers, and that `.qualignore` and `.gitignore` filter what's considered. -5. **Issuer URIs and `issuer_type`** — issuer must be a URI (contain `:`); `mailto:user@example.com` is the typical agent-recordable form; `issuer_type` is one of `human`, `ai`, `tool`, `unknown`. -6. **Content addressing (BLAKE3)** — IDs are computed from a canonical form of the body; changing canonical fields changes the ID. Implication: don't hand-edit `.qual` files; use `record`/`emit`. - -Keep each section to ~20 lines. Use code blocks for example JSON when it clarifies the shape. Source of truth for any specific behavior is `SPEC.md` and `src/annotation.rs`. - -- [ ] **Step 2: Write `workflows.md` (~150 lines)** - -Replace the placeholder. Five recipes, each a 1–2 sentence motivation followed by a concrete shell example: - -1. **Record a finding during code review.** Motivation: agent reviewing a diff notices a concern. Example: `qualifier record concern src/payments/charge.rs --message "..." --span 42-58`. -2. **Reply to an existing observation with new info.** Motivation: another agent (or you, later) has more context to add to an existing annotation. Example: `qualifier reply --message "..."`. -3. **Resolve a finding once it's addressed.** Motivation: the concern is fixed. Example: `qualifier resolve --message "fixed in commit X"`. -4. **Triage stale annotations after a refactor.** Motivation: span-bound annotations may no longer point at the right code. Example: `qualifier review` and how to interpret the output. -5. **Compact a noisy `.qual` file.** Motivation: many superseded entries; collapse to a clean snapshot. Example: `qualifier compact `. - -For each recipe: 1 paragraph + 1 code block. ~25 lines per recipe. - -- [ ] **Step 3: Write `pitfalls.md` (~60 lines)** - -Replace the placeholder. Bulleted list of common agent mistakes, each with a one-sentence "why this is wrong" and a "do this instead": - -- Recording without `--span` when the concern is about a few lines, not the whole file. -- Conflating `reply` (add info) with `resolve` (close). -- Recording an annotation when a commit message would do. -- Editing `.qual` files by hand instead of using `record`/`emit`. -- Choosing `bug` when `concern` fits — kinds matter for downstream filtering. -- Using a non-URI issuer (must contain `:`). - -End with: `` - -- [ ] **Step 4: Sanity-check all three files render** - -Run: - -```bash -cargo run --bin qualifier -- agents concepts | head -5 -cargo run --bin qualifier -- agents workflows | head -5 -cargo run --bin qualifier -- agents pitfalls | head -5 -``` - -Expected: each prints meaningful first lines (the section headings you wrote), not the placeholder. - -- [ ] **Step 5: Run all tests** - -Run: `cargo test --all-features` -Expected: all tests pass (the parameterized topic test still passes; existing tests unaffected). - -- [ ] **Step 6: Commit** - -```bash -git add src/cli/commands/agents/pages -git commit -m "docs(agents): write topical pages — concepts, workflows, pitfalls" -``` - ---- - -## Task 8: Write content for the per-subcommand pages - -Each per-subcommand page follows the same template. The template is reproduced inline so you don't need to switch documents. - -**Per-page template (same for all 9 files):** - -```markdown -# qualifier - -## Purpose - - - -## When to use it - -<2–3 sentences distinguishing this command from adjacent ones — e.g., -record vs reply vs resolve, or show vs praise vs ls.> - -## Common invocations - -\`\`\`bash -# Realistic example 1 -qualifier ... - -# Realistic example 2 -qualifier ... -\`\`\` - -## Flags worth knowing - - --help`.> - -## Gotchas - -- -- -``` - -**Length target:** 50–100 lines per file. If you're under 50 you've likely under-explained "When to use it"; if over 100 you've duplicated `--help`. - -**Files:** -- Modify: `src/cli/commands/agents/pages/record.md` -- Modify: `src/cli/commands/agents/pages/reply.md` -- Modify: `src/cli/commands/agents/pages/resolve.md` -- Modify: `src/cli/commands/agents/pages/emit.md` -- Modify: `src/cli/commands/agents/pages/show.md` -- Modify: `src/cli/commands/agents/pages/ls.md` -- Modify: `src/cli/commands/agents/pages/praise.md` -- Modify: `src/cli/commands/agents/pages/review.md` -- Modify: `src/cli/commands/agents/pages/compact.md` - -Source of truth for behavior of each command: read `src/cli/commands/.rs` and run `qualifier --help`. Do not invent flags. - -- [ ] **Step 1: Write `record.md`** - -Cover, beyond the template: that this is the unified annotation-write verb (replaced per-kind verbs), `--span`, `--stdin` for batch, kind argument, location argument. - -- [ ] **Step 2: Write `reply.md`** - -Cover: target can be id-prefix or location; reply *adds* to a thread (does not close it). Differentiate from `resolve`. - -- [ ] **Step 3: Write `resolve.md`** - -Cover: target can be id-prefix or location; this *closes* a record. Differentiate from `reply`. - -- [ ] **Step 4: Write `emit.md`** - -Cover: this is a low-level passthrough for any record type — agents should generally use `record`/`reply`/`resolve` instead, but `emit` exists for emitting raw `epoch` or `dependency` records or anything the higher-level commands don't cover. Mention `--body ''`. - -- [ ] **Step 5: Write `show.md`** - -Cover: shows annotations for an artifact (threaded). Mention `--format json` for programmatic consumption. - -- [ ] **Step 6: Write `ls.md`** - -Cover: list artifacts by kind, optional `--kind` filter. - -- [ ] **Step 7: Write `praise.md`** - -Cover: shows who annotated an artifact and why; alias is `blame` (with a hint that we prefer `praise`). Mention `--vcs` if it surfaces VCS info. - -- [ ] **Step 8: Write `review.md`** - -Cover: checks freshness of span-bound annotations against current code; an agent should run this after editing files that have annotations on them. - -- [ ] **Step 9: Write `compact.md`** - -Cover: prunes superseded records and/or snapshots to an epoch record. Mention this is maintenance, not something to run constantly. - -- [ ] **Step 10: Sanity-check a couple of pages render with real content** - -Run: - -```bash -cargo run --bin qualifier -- agents record | head -10 -cargo run --bin qualifier -- agents emit | head -10 -``` - -Expected: each prints the new prose, not the placeholder. - -- [ ] **Step 11: Run all tests** - -Run: `cargo test --all-features` -Expected: all tests pass. - -- [ ] **Step 12: Format and lint** - -Run: `cargo fmt && cargo clippy --all-targets --all-features -- -D warnings` -Expected: clean. - -- [ ] **Step 13: Commit** - -```bash -git add src/cli/commands/agents/pages -git commit -m "docs(agents): write per-subcommand pages" -``` - ---- - -## Task 9: Update CHANGELOG, README, and bump version - -**Files:** -- Modify: `Cargo.toml` -- Modify: `CHANGELOG.md` -- Modify: `README.md` - -- [ ] **Step 1: Bump the crate version** - -Edit `Cargo.toml`. Choose the next minor (e.g., `0.4.0` → `0.5.0`); confirm against published versions on crates.io if unsure. The change is purely additive (no breaking changes), so a minor bump is correct. - -```toml -version = "0.5.0" -``` - -- [ ] **Step 2: Add a CHANGELOG entry** - -In `CHANGELOG.md`, add a new section above the `[0.4.0]` section, or under `Added` of an existing unreleased section if one exists: - -```markdown -## [0.5.0] — unreleased - -### Added - -- **`qualifier agents`** — self-contained guide for AI coding agents. - Bare `qualifier agents` prints an orientation page with an index of - topics; `qualifier agents ` (e.g. `concepts`, `workflows`, - `record`) drills into per-topic detail. The agent group also appears - at the top of `qualifier --help` so models reading the help text - discover the entry point on their own. -``` - -If a `[0.5.0] — unreleased` section already exists, add only the bullet under its `### Added` block. - -- [ ] **Step 3: Update README CLI tables** - -In `README.md`, find the existing "Record observations / Inspect / Maintain" CLI command tables (around line 51 onward). Add a new table **above** them: - -```markdown -### For AI agents - -| Command | Description | -|---------|-------------| -| `qualifier agents [topic]` | Self-contained guide for AI coding agents (start here) | -``` - -Keep the existing tables unchanged. - -- [ ] **Step 4: Run all tests** - -Run: `cargo test --all-features` -Expected: all tests pass. - -- [ ] **Step 5: Commit** - -```bash -git add Cargo.toml CHANGELOG.md README.md -git commit -m "chore: bump to 0.5.0 and document agents subcommand" -``` - -(Adjust the version in the commit message if you chose a different number.) - ---- - -## Task 10: Final verification - -**Files:** none new - -- [ ] **Step 1: Format** - -Run: `cargo fmt` -Expected: no diff. - -- [ ] **Step 2: Lint** - -Run: `cargo clippy --all-targets --all-features -- -D warnings` -Expected: clean. - -- [ ] **Step 3: Full test suite** - -Run: `cargo test --all-features` -Expected: all tests pass. - -- [ ] **Step 4: Manual smoke test** - -Run each in turn: - -```bash -cargo run --bin qualifier -- --help -cargo run --bin qualifier -- agents -cargo run --bin qualifier -- agents concepts -cargo run --bin qualifier -- agents record -cargo run --bin qualifier -- agents bogus 2>&1 ; echo "exit=$?" -``` - -Expected: -- `--help` shows "For AI agents:" group at the top with the `agents` row. -- `agents` prints the orientation page with a topic list (no literal `{{TOPICS}}`). -- `agents concepts` and `agents record` print real prose. -- `agents bogus` prints `qualifier agents: no such topic 'bogus'. Available: ...` to stderr and exits with `exit=2`. - -- [ ] **Step 5: If anything failed, fix and re-run from Step 1** - -- [ ] **Step 6: Final commit if there are leftover style fixups** - -```bash -git status -# If anything is staged that wasn't covered by an earlier task's commit: -git add -u -git commit -m "chore: final fmt/clippy fixups for agents subcommand" -``` - -If the working tree is clean, skip this step. diff --git a/docs/superpowers/specs/2026-05-06-frontmatter-migration-design.md b/docs/superpowers/specs/2026-05-06-frontmatter-migration-design.md deleted file mode 100644 index f91a686..0000000 --- a/docs/superpowers/specs/2026-05-06-frontmatter-migration-design.md +++ /dev/null @@ -1,225 +0,0 @@ -# Frontmatter migration for `qualifier agents` pages - -## Goal - -Replace the hand-coded `&[Page]` registry in `src/cli/commands/agents/mod.rs` with a build-time-generated registry derived from TOML frontmatter on each `pages/*.md` file. Adding, renaming, or describing a topic becomes a single-file edit; the Rust source tree no longer carries a duplicated topic list to keep in sync. - -## Non-goals - -- Changing the runtime CLI surface. The user-visible behavior of `qualifier agents`, `qualifier agents `, and `qualifier agents ` is unchanged. -- Rendering `sees_also` cross-links in page output. The field is parsed and stored on `Page` but not yet rendered. A future PR can wire up a "See also:" footer. -- Standardizing this storage format across tools. AGENTS-CLI 0.1 explicitly leaves source-tree storage to each tool's choice; this is qualifier's choice. -- Runtime parsing. Pages are still read at compile time via `include_str!`-equivalent mechanics — never from the filesystem at run time. - -## Frontmatter schema - -Every `pages/*.md` file begins with a TOML frontmatter block delimited by `+++` lines: - -``` -+++ -name = "record" -summary = "Record a new annotation" -sees_also = ["reply", "resolve"] -since = "0.5.0" -+++ - -# qualifier record - -(page body...) -``` - -**Fields:** - -| Field | Required | Type | Purpose | -|-------------|----------|----------------|--------------------------------------------------------------------------| -| `name` | yes | string | Topic key. Must match the filename stem (or be `_overview` for overview).| -| `summary` | yes | string | One-line description used in the topic index. | -| `sees_also` | no | array | Names of related topics. Stored on `Page`; rendering deferred. | -| `since` | no | string | Version the topic was first present (e.g., `"0.5.0"`). | - -The first `+++` line MUST be the very first line of the file. The frontmatter ends at the next line that is exactly `+++`. The body is everything after that, leading whitespace preserved (the body normally starts with a blank line before the `#` heading). - -## Filename convention - -- Filenames matching `[a-z][a-z0-9_-]*\.md` are topics. Their `name` frontmatter field MUST equal the filename stem. -- Filenames beginning with `_` are *internal* pages, excluded from the topic registry. The build emits them as named consts. Currently only `_overview.md` exists; the convention generalizes to e.g. `_about.md` or `_compat.md` later. -- The build fails if it encounters a filename that doesn't match either convention. - -## `Page` struct (new shape) - -In `src/cli/commands/agents/mod.rs`: - -```rust -pub struct Page { - pub name: &'static str, - pub summary: &'static str, - pub sees_also: &'static [&'static str], - pub since: Option<&'static str>, - pub body: &'static str, -} -``` - -The build emits `PAGES: &[Page]` and individual consts for underscore-prefixed pages. - -## `build.rs` algorithm - -A new `build.rs` at the crate root performs, at compile time: - -1. Read `cargo:rerun-if-changed=src/cli/commands/agents/pages` so edits trigger a rebuild. -2. Walk `src/cli/commands/agents/pages/*.md` (sorted lexicographically for deterministic output). -3. For each file: - 1. Read full contents as UTF-8. - 2. Confirm the first line is exactly `+++`. Locate the next `+++`-only line. - 3. Parse the lines between as TOML using the `toml` crate, deserialized via `serde::Deserialize` into a `PageMeta` struct. - 4. Capture the body (everything after the closing `+++` line). - 5. Validate: - - `name` is non-empty. - - For non-underscore filenames: `name == filename_stem`. - - For `_overview.md`: `name == "_overview"`. - - `summary` is non-empty (for non-underscore files; the overview SHOULD have a summary too but enforcement is on topic pages). -4. Detect duplicates: no two `Page`s may share the same `name`. -5. Emit `$OUT_DIR/agents_pages.rs` containing: - - `pub const OVERVIEW: &str = "";` - - `pub const PAGES: &[Page] = &[...];` populated from non-underscore files in lexicographic order. - -Build errors include the offending file path and a clear reason. Examples: - -- `src/cli/commands/agents/pages/record.md: missing required field 'summary' in frontmatter` -- `src/cli/commands/agents/pages/record.md: name 'reply' does not match filename stem 'record'` -- `src/cli/commands/agents/pages/typo.md: no '+++' frontmatter delimiter on first line` -- `pages/record.md and pages/aliased.md: duplicate name 'record'` - -## `mod.rs` changes - -The current code: - -```rust -const OVERVIEW: &str = include_str!("pages/_overview.md"); - -const PAGES: &[Page] = &[ - Page { name: "concepts", summary: "...", body: include_str!("pages/concepts.md") }, - // ... 11 more entries -]; -``` - -becomes: - -```rust -include!(concat!(env!("OUT_DIR"), "/agents_pages.rs")); -``` - -The hand-coded summaries are removed. The `Page` struct definition stays in `mod.rs` (or moves to a tiny `page.rs` sibling for clarity; either is fine). - -`render_overview()`, `topic_names()`, and `run()` keep their current shape. They consume the generated `OVERVIEW` and `PAGES` consts identically. - -## Page-file edits - -Each existing page gets a frontmatter prepend. Concrete edits: - -- `_overview.md` — add `name = "_overview"`. No other frontmatter required. -- `concepts.md`, `workflows.md`, `pitfalls.md` — add `name`, `summary`, `since = "0.5.0"`. -- `record.md`, `reply.md`, `resolve.md`, `emit.md`, `show.md`, `ls.md`, `praise.md`, `review.md`, `compact.md` — add `name`, `summary`, `since = "0.5.0"`. Optionally `sees_also` for the obviously-related pairs (`record` ↔ `reply`/`resolve`; `reply` ↔ `resolve`; etc.) — see "Suggested `sees_also` map" below. - -The summaries used in frontmatter MUST match the strings currently hand-coded in `mod.rs::PAGES` to preserve the exact `qualifier agents` orientation output. - -## Suggested `sees_also` map - -Not enforced; included so the implementer doesn't have to invent it. Skip any pairing that doesn't feel natural. - -- `record` → `["reply", "resolve", "emit"]` -- `reply` → `["resolve", "record"]` -- `resolve` → `["reply", "record"]` -- `emit` → `["record"]` -- `show` → `["ls", "praise", "review"]` -- `ls` → `["show"]` -- `praise` → `["show"]` -- `review` → `["show", "compact"]` -- `compact` → `["review"]` -- `concepts`, `workflows`, `pitfalls` → omit (`sees_also = []` or absent) - -## Dependencies added - -In `Cargo.toml`: - -```toml -[build-dependencies] -toml = "0.8" -serde = { version = "1", features = ["derive"] } -``` - -These do not affect the runtime binary — they are only used by `build.rs`. - -## Testing - -Existing tests pass without changes — the user-visible CLI behavior is identical. - -Add to `tests/cli_integration.rs`: - -```rust -#[test] -fn test_agents_orientation_summaries_match_pages() { - // Lock in the contract that the orientation page renders the topic - // index from frontmatter summaries. We assert each topic's summary - // appears in the bare-agents output. - let dir = tempfile::tempdir().unwrap(); - let (stdout, _stderr, code) = run_qualifier(dir.path(), &["agents"]); - assert_eq!(code, 0); - for needle in [ - "Annotation model, kinds, supersession", // concepts summary - "Worked recipes for common tasks", // workflows summary - "Common mistakes agents make with qualifier",// pitfalls summary - "Record a new annotation", // record summary - ] { - assert!( - stdout.contains(needle), - "orientation should include summary '{needle}': {stdout}" - ); - } -} -``` - -Add a unit test in `agents/mod.rs`: - -```rust -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn all_pages_have_non_empty_summaries() { - for page in PAGES { - assert!( - !page.summary.is_empty(), - "page '{}' has empty summary", - page.name - ); - } - } - - #[test] - fn overview_contains_topics_sentinel() { - // Existing test, preserved. - assert!(super::OVERVIEW.contains("{{TOPICS}}")); - } -} -``` - -## File touch list - -- New: `build.rs` at crate root (~80 lines) -- New: dependency lines in `Cargo.toml` `[build-dependencies]` -- Modified: `src/cli/commands/agents/mod.rs` — drop hand-coded `&[Page]`, add `include!`, expand `Page` struct -- Modified: `src/cli/commands/agents/pages/_overview.md` — add minimal frontmatter -- Modified: `src/cli/commands/agents/pages/{concepts,workflows,pitfalls,record,reply,resolve,emit,show,ls,praise,review,compact}.md` — add frontmatter (12 files) -- Modified: `tests/cli_integration.rs` — add the orientation/summary test -- Modified: `Cargo.toml` — bump version to 0.5.1 (patch — no user-visible behavior change) -- Modified: `CHANGELOG.md` — note the build-time refactor under an internal/changed section -- Untouched: README.md (no user-visible change) -- Untouched: AGENTS-CLI.md (this migration is qualifier-internal; the protocol is silent on storage) - -## Out of scope / future - -- Rendering `sees_also` as a "See also: …" footer on each page. -- A `--probe` flag for AGENTS-CLI discovery (covered separately if/when AGENTS-CLI 0.2 lands). -- A JSON output mode (deferred per AGENTS-CLI 0.1 out-of-scope list). -- Auto-checking that `since` versions are monotone or match `Cargo.toml`. diff --git a/docs/superpowers/specs/2026-05-06-qualifier-agents-command-design.md b/docs/superpowers/specs/2026-05-06-qualifier-agents-command-design.md deleted file mode 100644 index bf47ead..0000000 --- a/docs/superpowers/specs/2026-05-06-qualifier-agents-command-design.md +++ /dev/null @@ -1,202 +0,0 @@ -# `qualifier agents` — agent-bootstrap subcommand - -## Goal - -Let an AI coding agent working in a user's repo bootstrap into productive use of `qualifier` without the user installing a separate skill, prompt, or doc bundle. The agent reads `qualifier --help`, sees a clearly agent-targeted entry, and runs `qualifier agents` to get a self-contained guide. The guide covers concepts, workflows, and a per-subcommand reference, all bundled with the binary. - -## Non-goals - -- Replacing or competing with `--help`. Clap-generated help stays for humans. -- Auto-generating content from clap definitions. Pages are hand-written prose, including syntax sections. -- Multi-format rendering. Output is plain UTF-8 markdown source — no ANSI, no pager, no HTML. -- Online updates. The guide ships frozen with the installed binary version. An agent gets exactly the doc that matches the tool it can call. - -## Naming - -Subcommand is `agents` (plural). Rationale: matches the established `AGENTS.md` convention that coding agents already pattern-match on, and reads as "instructions for agents" the way `man` reads as "manual." Help description carries the imperative meaning: `Self-contained guide for AI coding agents (start here)`. - -The existing `AGENTS.md` at the repo root is contributor-facing (instructions for an agent helping develop qualifier). The new `agents` subcommand is consumer-facing (instructions for an agent using qualifier as a tool). The two coexist; the spec does not modify `AGENTS.md`. - -## Module layout - -``` -src/cli/commands/agents/ - mod.rs // clap Args, run(), dispatch, registry - pages/ - _overview.md // printed by bare `qualifier agents` - concepts.md - workflows.md - pitfalls.md - record.md - reply.md - resolve.md - emit.md - show.md - ls.md - praise.md - review.md - compact.md -``` - -`haiku` is intentionally omitted — it's a flavor command with no agent-relevant usage guidance. The page set covers exactly the subcommands an agent should learn. - -`pages/` holds pure markdown content. `mod.rs` owns dispatch and the registry. The leading underscore on `_overview.md` keeps the filename out of the topic namespace and signals "not a topic — it's the index." - -## Registry - -```rust -struct Page { - name: &'static str, - summary: &'static str, - body: &'static str, -} - -const PAGES: &[Page] = &[ - Page { name: "concepts", summary: "Annotation model, kinds, supersession, IDs, .qual layout", - body: include_str!("pages/concepts.md") }, - Page { name: "workflows", summary: "Worked recipes for common tasks", - body: include_str!("pages/workflows.md") }, - Page { name: "pitfalls", summary: "Common mistakes agents make with qualifier", - body: include_str!("pages/pitfalls.md") }, - Page { name: "record", summary: "Record a new annotation", - body: include_str!("pages/record.md") }, - // … one entry per remaining subcommand -]; -``` - -The registry is the only place that needs editing when a topic is added or removed. The overview's `{{TOPICS}}` substitution renders `name — summary` for each entry. - -## CLI surface - -```rust -#[derive(clap::Parser)] -pub struct Args { - /// Topic name (e.g. `record`, `concepts`, `workflows`). Omit to print the orientation page. - topic: Option, -} - -pub fn run(args: Args) -> crate::Result<()>; -``` - -Behavior: - -| Invocation | Behavior | -|--------------------------------|-------------------------------------------------------------------------------------------| -| `qualifier agents` | Print rendered overview (body of `_overview.md` with `{{TOPICS}}` expanded) | -| `qualifier agents ` | Print that page's `body` verbatim to stdout | -| `qualifier agents ` | `eprintln!("qualifier agents: no such topic ''. Available: ...")` and exit code 2 | -| `qualifier agents --help` | Standard clap help (one positional arg, no flags). Untouched. | - -Why a single positional rather than nested clap subcommands: every page prints text and accepts no flags. A clap subcommand tree would add boilerplate without earning a meaningful per-topic `--help` (the page *is* the help). - -Output is stdout only; no ANSI, no pager. An agent consuming the output wants raw text. - -The error path uses exit code 2 to match clap's convention for usage errors. Since `cli::run` exits 1 on any returned `Err`, `agents::run` handles its own usage error: print the message to stderr and call `std::process::exit(2)` directly rather than returning an `Err`. - -## Help template integration - -`HELP_TEMPLATE` in `src/cli/mod.rs` gets a new group above the existing ones: - -``` -For AI agents: - agents Self-contained guide for AI coding agents (start here) - -Record observations: - ... -``` - -The group header `For AI agents:` is the discoverability signal — a model scanning grouped help output recognizes this entry as targeted at it. Description ("start here") is the imperative nudge. `agents` does not appear in any other group. - -The `Commands` enum gains: - -```rust -/// Self-contained guide for AI coding agents (start here) -Agents(commands::agents::Args), -``` - -dispatched in the `run` match like every other subcommand. - -The existing comment above `HELP_TEMPLATE` already warns to keep template and enum in sync — no new instruction needed. - -## Page content templates - -Actual prose is written during implementation; the spec defines structure and sizing. - -### `_overview.md` (~80–120 lines) - -1. Two-sentence framing: what qualifier is, what shape of artifact it produces. -2. "When to use this tool" — three bullets. -3. "When NOT to use it" — e.g., one-off scratch debugging, info that belongs in a commit message. -4. Quickstart: one minimal end-to-end example (record → show → resolve). -5. `{{TOPICS}}` index — auto-rendered from registry. -6. Pointer: "For more on any topic, run `qualifier agents `." - -### `concepts.md` (~150 lines) - -- Annotation envelope (metabox, type, subject, issuer, body) and why each matters to an agent. -- Kinds (`note`, `concern`, `bug`, `praise`, etc.) and how to choose. -- Supersession: mechanics, why chains must be acyclic, cross-subject rejection. -- `.qual` file layout: directory-level vs file-level, discovery rules, `.qualignore`. -- Issuer URIs and `issuer_type`. -- BLAKE3 content addressing — implication that changing canonical body changes the ID. - -### `workflows.md` (~150 lines) - -Five recipes, each a short narrative plus commands: - -1. Record a finding during code review. -2. Reply to an existing observation with new info. -3. Resolve a finding once it's addressed. -4. Triage stale annotations after a refactor (`review`). -5. Compact a noisy `.qual` file. - -### `pitfalls.md` (~60 lines, grows over time) - -- Forgetting `--span` when scope is narrower than the file. -- Conflating `reply` (add info) with `resolve` (close). -- Recording an annotation when a commit message would do. -- Editing `.qual` files by hand instead of using `record`/`emit`. -- Section labelled "Add new pitfalls here as we observe them." - -### Per-subcommand pages (~50–100 lines each) - -Each follows the same template: - -1. One-sentence purpose. -2. When to use it (vs adjacent commands). -3. Common invocation forms with realistic examples. -4. Flags worth knowing about, in prose (not a flag table — agents can run `qualifier --help` for the table). -5. Gotchas specific to this command. - -## Testing - -CLI integration tests live in `tests/cli_integration.rs`. Add cases: - -1. `qualifier agents` exits 0 and prints non-empty output containing the rendered topic index (assert that each registry entry's name and summary appear). -2. `qualifier agents concepts` exits 0 and prints non-empty output (smoke check). -3. `qualifier agents ` exits 0 in a parameterized loop, asserting non-empty output. Catches missing `include_str!` files and registry mismatches. -4. `qualifier agents bogus-topic` exits non-zero and stderr contains `no such topic`. -5. `qualifier --help` output contains the exact line `For AI agents:` and the `agents` row, ensuring discoverability stays wired. - -A small unit test in `agents/mod.rs` verifies that `_overview.md` contains the literal `{{TOPICS}}` sentinel — guards against accidentally removing it. - -No new test fixtures or helpers are needed. The pages are static; coverage is mostly "they render and are non-empty." - -## Out of scope / future - -- A `qualifier agents pitfalls` page that grows from observed agent failures over time. Stub at launch. -- Republishing pages to the docs site verbatim. The .md files are website-friendly; if/when desired this is a docs-site change, not a qualifier change. -- Search or fuzzy match for topic names. If `agents fop` should suggest `agents foo`, defer that until we have a topic count where guessing helps. -- Auto-derived per-subcommand syntax sections from clap. Pages stay hand-written; the small drift cost is accepted. -- Localization. English only. - -## File touch list - -- New: `src/cli/commands/agents/mod.rs` -- New: `src/cli/commands/agents/pages/*.md` (13 files: `_overview` + 3 topical + 9 per-subcommand) -- Modify: `src/cli/commands.rs` or `src/cli/commands/mod.rs` — add `pub mod agents;` -- Modify: `src/cli/mod.rs` — add `Agents` variant, add dispatch arm, update `HELP_TEMPLATE` -- Modify: `tests/cli_integration.rs` — add the test cases above -- Modify: `Cargo.toml` — bump version (per AGENTS.md "Keeping Things in Sync") -- Modify: `CHANGELOG.md` — note the new subcommand -- Modify: `README.md` — add `agents` row to the CLI Commands table if one exists diff --git a/src/cli/commands/agents/pages/_overview.md b/src/cli/commands/agents/pages/_overview.md index f848b94..136ea3f 100644 --- a/src/cli/commands/agents/pages/_overview.md +++ b/src/cli/commands/agents/pages/_overview.md @@ -20,8 +20,6 @@ attached to specific places in the codebase. - You found a bug, smell, risk, or stylistic concern that survives this edit and is worth surfacing for whoever touches the code next. Record it. -- An earlier annotation no longer applies (the code changed, the concern - was addressed). Resolve it. ## When NOT to use it diff --git a/src/cli/commands/agents/pages/pitfalls.md b/src/cli/commands/agents/pages/pitfalls.md index 4f228b0..e9294f0 100644 --- a/src/cli/commands/agents/pages/pitfalls.md +++ b/src/cli/commands/agents/pages/pitfalls.md @@ -39,6 +39,14 @@ since = "0.5.0" Prefer `concern` for non-blocking bugs and `blocker` for must-fix issues; reserve custom kinds for genuinely domain-specific signals. +- **Resolving annotations the user has not directed you to close.** + `qualifier resolve` *closes* a record — the original concern is hidden + from `qualifier show`, `qualifier ls`, and other active views. An agent + resolving an annotation it does not fully understand silently buries a + concern the user may still want to act on. Resolution is a user + decision: surface the annotation (e.g., with `qualifier show`) and let + the user decide whether it is addressed. + - **Adding positive annotations the user did not ask for.** An agent volunteering `praise`, `pass`, or other positive-polarity annotations without explicit user direction creates review noise the diff --git a/src/cli/commands/agents/pages/resolve.md b/src/cli/commands/agents/pages/resolve.md index 4729244..2192533 100644 --- a/src/cli/commands/agents/pages/resolve.md +++ b/src/cli/commands/agents/pages/resolve.md @@ -14,13 +14,21 @@ supersedes it. ## When to use it -Use `resolve` after you have fixed the issue described in an annotation and -want it removed from active annotation lists. Under the hood, `resolve` writes -a new annotation with `kind: resolve` and `body.supersedes` pointing at the -target — there is no deletion; the full history is preserved in the `.qual` -file. This is the right command when an issue is actually done. If you just -want to add context, a comment, or acknowledge that work remains, use `reply` -instead; `reply` does not close anything. +`resolve` is a user-directed action. As an agent, do not invoke `resolve` +unless the user has explicitly told you to close a specific annotation. +Closing a record hides it from `qualifier show`, `qualifier ls`, and other +active views; if you close a concern you do not fully understand, you +silently bury something the user may still want to act on. When you +encounter an annotation that *might* be addressable, surface it to the +user (e.g., quote it, or print `qualifier show ` for them) and +let them decide. + +When the user does direct you to resolve, this is what the command does: +it writes a new annotation with `kind: resolve` and `body.supersedes` +pointing at the target. There is no deletion; the full history is +preserved in the `.qual` file. If you instead want to *add context* to a +record (a comment, a status update, an acknowledgment that work remains), +use `reply` — `reply` does not close anything. ## Common invocations @@ -54,8 +62,9 @@ useful context — the message ends up in the annotation history visible to the resolution record. This is useful when you want reviewers to be able to jump to the exact commit that addressed the issue. -**`--issuer-type ai`** should be set whenever an agent is the one closing a -record, so the resolution is attributable to a machine rather than a human. +**`--issuer-type ai`** should be set whenever you do close a record on +behalf of the user, so the resolution is attributable to a machine rather +than a human and the user can review what their agent closed. ## Gotchas diff --git a/src/cli/commands/agents/pages/workflows.md b/src/cli/commands/agents/pages/workflows.md index 64aec46..a61a4aa 100644 --- a/src/cli/commands/agents/pages/workflows.md +++ b/src/cli/commands/agents/pages/workflows.md @@ -53,31 +53,15 @@ The id-prefix form matches by prefix. The location form resolves to the most-rec If multiple records share the newest timestamp, the CLI exits non-zero with a disambiguation list showing id-prefix, kind, line, and summary. -## Resolve a finding once it's addressed - -When the code has been fixed, close the annotation so it no longer appears -in active views. `qualifier resolve` writes a `resolve`-kind record that -supersedes the target; the original concern is removed from `qualifier show` -output but the full history remains in VCS. - -```bash -qualifier resolve a1b2 "Fixed in commit abc1234 — expiry check now unconditional" \ - --issuer "mailto:review-agent@example.com" \ - --issuer-type ai -``` - -The message is optional; it defaults to `"Resolved"`. Prefer a meaningful -message so the tombstone is useful to future readers. - -After resolving, confirm with `qualifier show src/auth.rs` — the original -concern should no longer appear (unless you pass `--all`). - -## Triage stale annotations after a refactor +## Surface drifted annotations after a refactor Span-addressed annotations include a `content_hash` (BLAKE3 of the spanned lines at write time). After a refactor, those hashes may no longer match the current file content. `qualifier review` checks this and reports each -annotation's freshness status. +annotation's freshness status. As an agent, your job here is to surface +the results to the user. Closing or rewriting a drifted annotation +requires understanding the original concern; the user is better +positioned to make that call. ```bash # Check all annotations in the project @@ -93,10 +77,10 @@ qualifier review --format json Possible statuses: - `FRESH` — the spanned lines are unchanged. No action needed. -- `DRIFTED` — the lines changed. Review whether the annotation still applies; - resolve it if addressed, or record a new annotation at the updated span. -- `MISSING` — the file or span no longer exists. The annotation is likely stale; - resolve it. +- `DRIFTED` — the lines changed. Surface the annotation to the user and + let them decide whether it still applies and what to do about it. +- `MISSING` — the file or span no longer exists. Surface this to the + user; do not assume the annotation is stale. Annotations without a span or without a `content_hash` are not checked. Whole-file annotations and older records written without `--span` fall into From de6cd659e4efdd283df6086c20308aa6418fce5f Mon Sep 17 00:00:00 2001 From: Alex Kesling Date: Wed, 6 May 2026 16:41:32 -0400 Subject: [PATCH 26/26] docs: reflow AGENTS-CLI.md to 80-col hard wrap --- AGENTS-CLI.md | 120 +++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 94 insertions(+), 26 deletions(-) diff --git a/AGENTS-CLI.md b/AGENTS-CLI.md index b313e00..8809604 100644 --- a/AGENTS-CLI.md +++ b/AGENTS-CLI.md @@ -2,79 +2,147 @@ A convention for command-line tools that self-describe to AI coding agents. -> **Status:** Draft. Breaking changes are allowed until 1.0. Feedback and adoption reports welcome via issues on any conforming tool's repository. +> **Status:** Draft. Breaking changes are allowed until 1.0. Feedback and +> adoption reports welcome via issues on any conforming tool's repository. ## Audience -CLI tool authors who want their tool to be productively usable by an AI coding agent on first contact, with no separately-installed prompt bundle, MCP server, or external skill. +CLI tool authors who want their tool to be productively usable by an AI +coding agent on first contact, with no separately-installed prompt +bundle, MCP server, or external skill. ## Motivation -A coding agent dropped into an unfamiliar shell typically does one of two things when it encounters a CLI: relies on prior training-data knowledge of the tool, or runs ` --help` and pattern-matches. Neither path teaches the agent the *judgment* it needs — when to use the tool, when not to, what the common workflows look like, what mistakes to avoid. That judgment is what separates an agent that uses your tool well from one that strings invocations together by syntactic mimicry. +A coding agent dropped into an unfamiliar shell typically does one of two +things when it encounters a CLI: relies on prior training-data knowledge +of the tool, or runs ` --help` and pattern-matches. Neither path +teaches the agent the *judgment* it needs — when to use the tool, when +not to, what the common workflows look like, what mistakes to avoid. +That judgment is what separates an agent that uses your tool well from +one that strings invocations together by syntactic mimicry. -AGENTS-CLI defines the smallest surface that lets a tool ship its own onboarding for AI agents, version-locked to the binary, discovered through the existing `--help` channel that agents already inspect. +AGENTS-CLI defines the smallest surface that lets a tool ship its own +onboarding for AI agents, version-locked to the binary, discovered +through the existing `--help` channel that agents already inspect. ## Conformance -This document uses the requirement levels from RFC 2119: **MUST**, **MUST NOT**, **SHOULD**, **SHOULD NOT**, **MAY**. +This document uses the requirement levels from RFC 2119: **MUST**, +**MUST NOT**, **SHOULD**, **SHOULD NOT**, **MAY**. ### Required (MUST) A conforming tool **MUST** satisfy all of the following. -1. **Subcommand exists.** The tool exposes a subcommand named `agents`. Examples: `qualifier agents`, `gh agents`, `cargo agents`. +1. **Subcommand exists.** The tool exposes a subcommand named `agents`. + Examples: `qualifier agents`, `gh agents`, `cargo agents`. -2. **Bare invocation prints an orientation page.** Running ` agents` with no further arguments prints a self-contained orientation document to standard output and exits with status 0. The orientation document MUST be sufficient on its own to teach an agent (a) what the tool is, (b) when to use it, (c) when not to use it, and (d) how to drill into per-topic detail. +2. **Bare invocation prints an orientation page.** Running ` + agents` with no further arguments prints a self-contained orientation + document to standard output and exits with status 0. The orientation + document MUST be sufficient on its own to teach an agent (a) what the + tool is, (b) when to use it, (c) when not to use it, and (d) how to + drill into per-topic detail. -3. **Topic invocation prints the named page.** Running ` agents ` prints the page identified by `` to standard output and exits with status 0. The orientation page MUST list the available topics. +3. **Topic invocation prints the named page.** Running ` agents + ` prints the page identified by `` to standard output + and exits with status 0. The orientation page MUST list the available + topics. -4. **Unknown topic produces a structured error.** Running ` agents ` MUST exit with status 2, and MUST write a message to standard error of the form: +4. **Unknown topic produces a structured error.** Running ` agents + ` MUST exit with status 2, and MUST write a message to + standard error of the form: ``` agents: no such topic ''. Available: , , ... ``` - The available list MUST contain every topic name that would succeed under rule 3. + The available list MUST contain every topic name that would succeed + under rule 3. -5. **`--help` discoverability.** The output of ` --help` MUST surface the `agents` subcommand in a way that signals to a coding agent that it is the agent-targeted entry point. The conformance test is informal but operational: *an LLM scanning the help output reliably identifies `agents` as the entry point intended for AI agents.* In practice this means a labeled section header ("For AI agents:"), a banner line, or an equivalent affordance — not just an unmarked entry buried in a generic command list. +5. **`--help` discoverability.** The output of ` --help` MUST + surface the `agents` subcommand in a way that signals to a coding + agent that it is the agent-targeted entry point. The conformance test + is informal but operational: *an LLM scanning the help output + reliably identifies `agents` as the entry point intended for AI + agents.* In practice this means a labeled section header ("For AI + agents:"), a banner line, or an equivalent affordance — not just an + unmarked entry buried in a generic command list. ### Recommended (SHOULD) -A conforming tool **SHOULD** satisfy the following. These are common-sense quality bars; departing from them is allowed if there's a clear reason. +A conforming tool **SHOULD** satisfy the following. These are +common-sense quality bars; departing from them is allowed if there's a +clear reason. -1. **Output is markdown.** Pages SHOULD be UTF-8 markdown text. Each page SHOULD begin with a single `# ` heading naming the page (e.g., `# record`). +1. **Output is markdown.** Pages SHOULD be UTF-8 markdown text. Each + page SHOULD begin with a single `# ` heading naming the page (e.g., + `# record`). -2. **Page set covers concepts, workflows, pitfalls, and per-subcommand detail.** At minimum, the topic set SHOULD include: +2. **Page set covers concepts, workflows, pitfalls, and per-subcommand + detail.** At minimum, the topic set SHOULD include: - - A concept primer (often `concepts`) — the tool's data model, key invariants, and any vocabulary an agent must understand to use the tool well. - - Common workflows (often `workflows`) — three to five worked recipes covering the most common tasks. - - Common pitfalls (often `pitfalls`) — mistakes agents make and how to avoid them. + - A concept primer (often `concepts`) — the tool's data model, key + invariants, and any vocabulary an agent must understand to use the + tool well. + - Common workflows (often `workflows`) — three to five worked recipes + covering the most common tasks. + - Common pitfalls (often `pitfalls`) — mistakes agents make and how + to avoid them. - One page per non-trivial subcommand, named after the subcommand. -3. **Pages are hand-written.** Pages SHOULD be authored with judgment, not auto-generated from `--help` output. Agents already have access to `--help` for syntax; AGENTS-CLI exists to convey the *when* and *why* that flag tables cannot. +3. **Pages are hand-written.** Pages SHOULD be authored with judgment, + not auto-generated from `--help` output. Agents already have access + to `--help` for syntax; AGENTS-CLI exists to convey the *when* and + *why* that flag tables cannot. -4. **Pages are version-locked to the binary.** Pages SHOULD ship inside the binary or alongside it such that the guidance an agent receives matches the tool it can actually invoke. Network fetches at runtime are discouraged. +4. **Pages are version-locked to the binary.** Pages SHOULD ship inside + the binary or alongside it such that the guidance an agent receives + matches the tool it can actually invoke. Network fetches at runtime + are discouraged. ### Out of scope for 0.1 -The following are deferred to later versions of this protocol or to companion specifications. Tools MAY implement them, but doing so is not part of 0.1 conformance: +The following are deferred to later versions of this protocol or to +companion specifications. Tools MAY implement them, but doing so is not +part of 0.1 conformance: -- A discovery handshake for sweeping `$PATH` (e.g., ` agents --probe`). +- A discovery handshake for sweeping `$PATH` (e.g., ` agents + --probe`). - Structured (JSON) output for programmatic consumption. - Internationalization or per-locale page sets. -- A standard format for storing pages in the source tree (each tool chooses). +- A standard format for storing pages in the source tree (each tool + chooses). - A registry of conforming tools. ## Reference implementation -**qualifier** (0.5.0 and later) is the reference implementation: . +**qualifier** (0.5.0 and later) is the reference implementation: +. -`qualifier agents`, `qualifier agents concepts`, `qualifier agents `, and `qualifier agents ` exercise every MUST in this document. The "For AI agents:" group at the top of `qualifier --help` is the discoverability mechanism for rule 5. Pages live at `src/cli/commands/agents/pages/*.md`, embedded into the binary at compile time. +`qualifier agents`, `qualifier agents concepts`, `qualifier agents +`, and `qualifier agents ` exercise every MUST in +this document. The "For AI agents:" group at the top of `qualifier +--help` is the discoverability mechanism for rule 5. Pages live at +`src/cli/commands/agents/pages/*.md`, embedded into the binary at +compile time. ## Versioning -AGENTS-CLI uses semantic versioning. The current version is 0.1. While the major version is 0, breaking changes are allowed between minor versions; downstream tools should expect to update conformance as the protocol stabilizes. Tools MAY indicate which version they conform to in any way they choose at 0.1 (a footer in the orientation page is the conventional choice). A standardized declaration mechanism is deferred to 1.0. +AGENTS-CLI uses semantic versioning. The current version is 0.1. While +the major version is 0, breaking changes are allowed between minor +versions; downstream tools should expect to update conformance as the +protocol stabilizes. Tools MAY indicate which version they conform to +in any way they choose at 0.1 (a footer in the orientation page is the +conventional choice). A standardized declaration mechanism is deferred +to 1.0. ## Acknowledgements -The protocol distills practice from man pages (orientation + per-topic drill-down), shell help conventions (machine-discoverable invocation surface), and the more recent `AGENTS.md` repo-level convention (the audience signal). It exists because no single one of those mechanisms gives a coding agent everything it needs on first contact, and because the friction of bolting on a separate skill bundle for every CLI is incompatible with how agents actually arrive at a codebase. +The protocol distills practice from man pages (orientation + per-topic +drill-down), shell help conventions (machine-discoverable invocation +surface), and the more recent `AGENTS.md` repo-level convention (the +audience signal). It exists because no single one of those mechanisms +gives a coding agent everything it needs on first contact, and because +the friction of bolting on a separate skill bundle for every CLI is +incompatible with how agents actually arrive at a codebase.