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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.lock

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

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ name = "quire"
path = "src/main.rs"

[dependencies]
ix-cli-kit = { git = "https://github.com/agent-ix/ix-cli-kit", branch = "main" }
ix-cli-kit = { git = "https://github.com/agent-ix/ix-cli-kit", branch = "main", features = ["self-update"] }
quire-rs = { version = "=0.50.2", git = "https://github.com/agent-ix/quire-rs", rev = "ba384acadb9d859dc3f46b247d884fad0fdc6db5" }
clap = { version = "4", features = ["derive"] }
serde = { version = "1", features = ["derive"] }
Expand Down
22 changes: 22 additions & 0 deletions reviews/SR-180-cli-kit-cutover-code-review.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
---
id: SR-180
title: "Code review — shared CLI kit cutover"
type: SpecReview
analysis: code-review
scope: "PLAT-1176; Cargo.toml; Cargo.lock; src/; tests/; tools/quire-qualify/; spec/functional/FR-016-update-subcommand.md; spec/tests.md"
review_set: subset
---

## Summary

Reviewed the cutover diff using the repository conventions and the Rust review checklist. The update command now calls ix-cli-kit's self-update engine; diagnostics and lexical JSON formatting also use the kit. The in-repo engine and equivalent helpers are removed. Focused CLI and source-audit tests pass, and the full local gate passes.

## Verdict

**PASS.** No new copy, stub, unsafe surface, panic path, or weakened gate was found in the changed code. Existing output contracts are exercised by the CLI tests.

## Findings

| ID | Severity | Summary | Refs |
| --- | --- | --- | --- |
| FND-001 | low | No findings (placeholder) | Cargo.toml; src/commands/update.rs; src/io.rs; tools/quire-qualify/src/source.rs |
28 changes: 28 additions & 0 deletions reviews/SR-181-cli-kit-cutover-gap-analysis.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
---
id: SR-181
title: "Gap analysis — shared CLI kit cutover"
type: SpecReview
analysis: gap-analysis
scope: "PLAT-1176; FR-016-AC-1..7; diagnostic and assurance output affected by the cutover"
review_set: subset
---

## Summary

Checked the changed requirements, tests, source, and computed coverage report for the shared-kit cutover. Quire's unknown-source behavior, JSON diagnostics, validation fields, lexical JSON output, and source boundary remain covered by local tests. The registry-argument unit test now belongs to ix-cli-kit; quire's TC-086 row is marked in progress rather than claiming a local binder.

## Verdict

**PASS for the changed behavior.** The full coverage report has no status lies after the TC-086 correction. Repository-wide legacy gaps are outside this PR's review set.

## Findings

| ID | Severity | Summary | Refs |
| --- | --- | --- | --- |
| FND-001 | low | No new gaps in the reviewed diff (placeholder) | spec/functional/FR-016-update-subcommand.md; spec/tests.md; tests/cli_update.rs; tests/cli_validate.rs; tests/cli_assurance.rs |

## Coverage

Plan completion: not assessed.

The repository has no applicable installed traceability model for a computed `quire matrix` run; the local `make spec` coverage gate reports no status lies and backs its 20 required assurance targets. The broader coverage report includes historical unmatched tags and unbacked rows. The shared engine's registry-argument test is upstream evidence, recorded as such in TC-086 and FR-016's CR note. Optional semantic review was not selected.
20 changes: 10 additions & 10 deletions spec/functional/FR-016-update-subcommand.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,9 @@ manager.
([StR-004](../stakeholder/StR-004-thin-boundary-over-quire-rs.md)): it manages
binary lifecycle, not artifact behavior, so it carries no parser/renderer/
validator logic and wraps no `quire-rs` API. Its logic lives in a deliberately
**package-agnostic** `self_update` engine (config-struct driven, no quire
coupling), so the engine can later move verbatim into a shared Rust CLI kit; the
`update` command itself is a thin wrapper that supplies quire's distribution
coordinates.
**package-agnostic** `ix-cli-kit::self_update` engine (config-struct driven, no
quire coupling). The `update` command is a thin wrapper that supplies quire's
distribution coordinates.

The npm wrapper version and the Cargo crate version are independently numbered,
so `update` SHALL NOT compare the running binary's version against a registry —
Expand Down Expand Up @@ -76,14 +75,16 @@ quire update [--check] [--registry <URL>]
| FR-016-AC-3 | On the npm channel, `--check` runs `npm view @agent-ix/quire-cli version` and reports the latest version without installing | Test |
| FR-016-AC-4 | A `--registry <URL>` override for the scoped package is passed as `--@agent-ix:registry=<URL>` (scope form), not a bare `--registry`; with no override no registry flag is added | Test |
| FR-016-AC-5 | `update` performs no version comparison against the running binary and shells out to npm/cargo for idempotency (no cross-scheme version diff in `src/`) | Inspection |
| FR-016-AC-6 | The `self_update` engine is package-agnostic: it takes quire's coordinates via a config struct and imports nothing from quire's `io`/command context, so it is extractable into a shared crate; `commands/update.rs` is the only quire-specific glue | Inspection |
| FR-016-AC-6 | `commands/update.rs` uses the shared `ix-cli-kit::self_update` engine, supplies quire's coordinates through `SelfUpdateConfig`, and contains no parser, renderer, or validator logic; quire carries no local copy of the engine | Inspection |
| FR-016-AC-7 | A failing `npm`/`cargo` invocation, or an unreachable registry, exits **4** | Test |

> **CR note (untraced criteria, 2026-08-21, #55):** three paths carry **no
> **CR note (untraced criteria, updated 2026-10-20, #55):** four paths carry **no
> automated trace**, because exercising them needs live network access and
> global-install side effects: the npm-channel `--check`/install of AC-3, the
> registry-unreachable / failing-`npm`/`cargo` exit-4 of AC-7, and the
> `cargo install` dispatch half of AC-1. Their `Verification: Test` states the
> `cargo install` dispatch half of AC-1. The registry-argument construction of
> AC-4 is tested by `ix-cli-kit`, but its upstream tests do not bind quire's
> local trace matrix. Their `Verification: Test` states the
> intended method, not an existing trace. This statement lives here rather
> than in the Test Matrix's Coverage Status column because that column
> declares no `column_patterns` and is therefore unenforced — prose there is
Expand All @@ -94,9 +95,8 @@ quire update [--check] [--registry <URL>]

## Dependencies

- **Upstream**: none in `quire-rs` (binary-lifecycle feature, not an engine
behavior). The shared `self_update` engine is in-crate today and is the
intended extraction unit for a future Rust CLI kit.
- **Upstream**: `ix-cli-kit` supplies the shared `self_update` engine;
`quire-rs` is not involved in this binary-lifecycle feature.
- **Downstream**: agent/CI setup docs that pin a single binary version
([StR-001](../stakeholder/StR-001-static-binary-hot-path.md)) — `update`
keeps that pinned binary current.
10 changes: 5 additions & 5 deletions spec/tests.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ The CLI is a thin process boundary over `quire-rs`; the upstream engine is indep
| FR-013 lint subcommand | AC-1..5 | IT-064 (clean exit 0 silent), IT-065 (warning exit 0 + stderr), IT-066 (error exit 1), IT-067 (--archetype scoping), IT-068 (missing manifest fails fast — also covers the FR-004 CR-note eager-loader behavior for validate/extract/schema) | ✅ |
| FR-014 validate --okf bundle posture (`type` discriminator) | AC-1..9 | IT-069 (untyped → exit 1, `[frontmatter]`), IT-070 (unknown type + broken link → warn, exit 0), IT-071 (index incompleteness → warn, exit 0), IT-072 (defaults to --scope dir), IT-026 (bare `validate` no `--okf` → exit 2, `required_unless_present`), TC-090 (thin boundary) | ✅ |
| FR-015 fix subcommand (unlinked-reference autofix, ADR 0007) | AC-1..6 | IT-076 (dry-run `would-fix` → exit 1, no write), IT-077 (`--write` applies + idempotent re-run exit 0), IT-078 (warn-only never written, no nonzero exit), IT-079 (clean bundle exit 0 empty stdout), IT-080 (`--scope` root + path-safety reject), TC-090 (thin boundary) | ✅ |
| FR-016 update subcommand (install-source-aware self-update) | AC-1..7 | IT-083 (Unknown source: `update --check` prints npm+cargo+releases recipes, exit 0, no install/network), IT-084 (Unknown source: bare `update` also no-install, exit 0), TC-085 (`detect_source` npm/cargo/unknown classification — `self_update::tests`), TC-086 (`registry_args` scope-form for scoped pkg / plain for unscoped / empty when no override), TC-087 (`cargo` `--check` reports branch-tracking, `latest: None`), TC-090 (thin boundary — `update` carries no parser/validator), TC-093 (`self_update` engine imports nothing from quire's `io`/command ctx — package-agnostic) — the untraced criteria are recorded in [FR-016's CR note](functional/FR-016-update-subcommand.md) (#55) | 🚧 |
| FR-016 update subcommand (install-source-aware self-update) | AC-1..7 | IT-083 (Unknown source: `update --check` prints npm+cargo+releases recipes, exit 0, no install/network), IT-084 (Unknown source: bare `update` also no-install, exit 0), TC-085 (`ix-cli-kit::self_update::tests` checks npm/cargo/unknown classification), TC-086 (`ix-cli-kit` checks scoped registry arguments), TC-087 (`ix-cli-kit` checks cargo branch-tracking report), TC-090 (thin boundary — `update` carries no parser/validator), TC-093 (quire consumes the shared engine and has no local copy) — the untraced criteria are recorded in [FR-016's CR note](functional/FR-016-update-subcommand.md) (#55) | 🚧 |
| FR-017 coverage subcommand (declarative rollup over two roots; agent-sized projection — #53; #51 close-out over quire-rs; CR-012 census to stdout) | AC-1..21 | IT-146 (repeatable `--module` declares a closed ordered set — #405), IT-147 (`--help` states the resolution order), IT-117 (CR-012 census on stdout, findings on stderr), IT-107, IT-108 (CR-011 the engine capabilities reach a command line), IT-109 (finding lines lead with `row_id` — #51), IT-110 (`coverage` severity pack — off/error/reject-before-read, incl. typo'd checks — #57), IT-111 (`--format tsv` records on stdout, `line` column populated), TC-812 (TSV escaping guard — #57), TC-813 (#66 census, metric envelopes, diagnostics and suspicions reach the human surface), IT-112 (`--json` honours `--pretty`, compact default), IT-113 (`document:line` locus on finding lines → AC-16), IT-114 (`no_symbol_rows` renders in the census → AC-17), IT-115 (excluded-count census line + extraction diagnostics on stderr → AC-18), IT-116 (`shared_trace_ids` through `--json` → AC-19), IT-089 (human census on stdout — CR-012), TC-740 (JSON payload byte-identical + no-model refusal), TC-810 (document root is `<scope>/spec`), IT-087 (code walk excludes it), TC-811 + IT-086 (missing root named + typed kind), IT-092 (`--strict`), TC-797 (zero matched rows ≠ full coverage), TC-090 (thin boundary) | ✅ Complete |
| FR-018 properties subcommand (per-criterion property classification) | AC-1..9 | IT-093 (census), IT-094 (JSON records + determinism), IT-095 (no criteria → empty, exit 0), IT-096 (non-extractable never fails), IT-097 (path-safety), IT-098 (`exclude:` binds this surface too), IT-103 (envelope conforms to the published schema → AC-8), IT-104 (absent obligation source → `obligation: null`, still conforms → AC-9), TC-090 (thin boundary) | ✅ Complete |
| FR-019 symbols subcommand (the extracted symbol table) | AC-1..6 | IT-130 (human census, stdout clean), IT-131 (JSON record fields + byte-stability), IT-132 (NOT ASKED vs not tagged), IT-133 (both denominators), IT-134 (diagnostics on stderr in JSON mode), IT-135 (a language filter narrows records AND census) | ✅ Complete |
Expand Down Expand Up @@ -251,15 +251,15 @@ The CLI is a thin process boundary over `quire-rs`; the upstream engine is indep
| IT-080 | `quire fix --scope <DIR> --module $M` with no positional uses `<DIR>/spec` as root (a repo-root file is never walked), and a `<DIR>` with no `spec/` is the same named error the other commands raise (`cli_fix::it080_*`); a `..`/symlink-escape on root or `--module` is rejected by path-safety before any load | Integration | P0 | FR-015-AC-5, FR-005 | ✅ |
| IT-083 | `update --check` on an Unknown install source (test binary under `target/`) prints manual instructions (npm recipe + cargo recipe + releases URL), exits 0, performs no install/network (`cli_update::update_check_on_unknown_source_prints_manual_instructions_and_exits_zero`) | Integration | P0 | FR-016-AC-1, FR-016-AC-2 | ✅ |
| IT-084 | bare `update` (no `--check`) on an Unknown source also performs no install and exits 0 (`cli_update::update_without_check_on_unknown_source_is_also_safe`) | Integration | P1 | FR-016-AC-2 | ✅ |
| TC-085 | `detect_source` classifies `node_modules` path → Npm, `.cargo` path → Cargo, bare path → Unknown (`self_update::tests::detect_*`) | Unit | P0 | FR-016-AC-1 | ✅ |
| TC-086 | `registry_args` yields the `--@scope:registry=` form for a scoped package, a plain `--registry <url>` for an unscoped package, and an empty vec when no override is supplied (`self_update::tests::registry_args_*`) | Unit | P0 | FR-016-AC-4 | ✅ |
| TC-087 | `run_for_source(Cargo, --check)` reports git-branch tracking with `latest: None` (no cross-scheme version); `run_for_source(Unknown)` emits manual report without installing (`self_update::tests`) | Unit | P1 | FR-016-AC-1, FR-016-AC-5 | ✅ |
| TC-085 | The shared engine classifies `node_modules` path → Npm, `.cargo` path → Cargo, bare path → Unknown (`ix-cli-kit::self_update::tests::detect_*`) | Unit | P0 | FR-016-AC-1 | ✅ |
| TC-086 | The shared engine emits `--@scope:registry=` for a scoped package, plain `--registry <url>` for an unscoped package, and no override flag when absent (`ix-cli-kit::self_update::tests::registry_args_*`); the test is upstream and has no local trace binder | Unit | P0 | FR-016-AC-4 | 🚧 |
| TC-087 | The shared engine reports git-branch tracking with `latest: None` on cargo `--check` and manual instructions for Unknown source (`ix-cli-kit::self_update::tests`) | Unit | P1 | FR-016-AC-1, FR-016-AC-5 | ✅ |
| TC-088 | ⊘ RETIRED (§2bis) — hyperfine render p95 ≤ 50 ms on FR archetype (NFR-001-AC-1..2 (retired); (retired)) | Benchmark | P0 | StR-002 | ⛔ |
| TC-089 | `ldd` shows only libc + loader (no project .so) | Static | P0 | NFR-002-AC-1 | ✅ |
| TC-090 | `src/` grep finds no markdown parsing, no structural-validation logic, and **no render/template code** (validation delegated to quire-rs `validate_document` / `validate_bundle`; render removed per §2bis) | Static | P1 | StR-004-AC-2, FR-004-AC-9, FR-014-AC-9, FR-015-AC-6, FR-017-AC-9 | ✅ |
| TC-091 | `cargo deny check bans` rejects HTTP client crates; `deny.toml` still bans each one and `Cargo.lock` links none (`audit_static::tc091_*`) | Static | P0 | NFR-004-AC-1, NFR-004-AC-3 | ✅ |
| TC-092 | The native AST-backed gate reports zero undocumented `unsafe` in first-party Rust source and tests, AND refuses a synthetic undocumented block — the gate is proven to catch, not merely to pass (`audit_static::tc092_*`) | Static | P0 | NFR-003-AC-1, NFR-003-AC-2 | ✅ |
| TC-093 | `src/self_update/` imports nothing from `quire`'s `io`/command context (engine is package-agnostic, config-struct driven); `commands/update.rs` is the only quire-specific glue and carries no parser/renderer/validator logic | Static | P1 | FR-016-AC-5, FR-016-AC-6, StR-004-AC-2 | ✅ |
| TC-093 | `commands/update.rs` uses `ix-cli-kit::self_update` and carries no parser/renderer/validator logic; `src/self_update/` is absent | Static | P1 | FR-016-AC-5, FR-016-AC-6, StR-004-AC-2 | ✅ |
| IT-101 | A **relative document path** with `--scope` and no `--module` resolves under the scope and validates against the module that scope carries (exact-module branch — the fixture root holds `manifest.yaml`; discovery is IT-081's): exit 0, empty stdout, empty stderr (`cli_validate::it_101_*`) | Integration | P1 | FR-004-AC-19 | ✅ |
| IT-102 | A **relative glob** with `--scope` expands under the scope; a matching non-conformant document exits 1 with a line-numbered stderr diagnostic naming the offending file, stdout empty (`cli_validate::it_102_*`) | Integration | P0 | FR-004-AC-20 | ✅ |
| IT-121 | `--module` does not move glob resolution off `--scope`: the same document validated with and without it yields identical output (`cli_validate::it_121_*`) | Integration | P0 | FR-004-AC-23, FR-018-AC-5 | ✅ |
Expand Down
8 changes: 4 additions & 4 deletions src/commands/assurance.rs
Original file line number Diff line number Diff line change
Expand Up @@ -118,11 +118,11 @@ pub fn run(ctx: &Ctx, args: Args) -> anyhow::Result<()> {
.unwrap_or(&empty_excludes),
);
for diagnostic in &extraction.diagnostics {
io::emit_diagnostic(
ctx.diagnostics,
ix_cli_kit::streams::Record::error(
"SymbolExtraction",
&format!("{}: {}", diagnostic.path, diagnostic.reason),
);
)
.emit(ctx.diagnostics);
}
let graph = model
.map(|traceability| quire_rs::symbols::trace::bind(&extraction, traceability))
Expand Down Expand Up @@ -155,7 +155,7 @@ pub fn run(ctx: &Ctx, args: Args) -> anyhow::Result<()> {
}

let mut output = if ctx.pretty {
io::pretty_validated_json_bytes(&compact)
ix_cli_kit::json::pretty_validated_json_bytes(&compact)
} else {
compact
};
Expand Down
Loading
Loading