You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
* test: fix flaky edit both-stdin race (BrokenPipe on early reject)
quire edit - --content - rejects the both-stdin-args combination before it
reads stdin, so the test's write_all raced the child's exit and panicked with
BrokenPipe when the child won (intermittent CI failure on main). Tolerate the
expected BrokenPipe; the real assertions (exit 1 + stderr message) are unchanged.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* feat(update): add install-source-aware self-update subcommand
quire update upgrades the installed binary to the latest published version.
Because quire ships via two channels (npm prebuilt-binary wrapper and
cargo install --git), it detects the install source from the running
executable path (node_modules -> npm, .cargo -> cargo, else unknown) and
drives the matching package manager; an unknown source prints manual
instructions and exits 0 without touching the network.
The logic lives in a package-agnostic self_update engine (config-struct
driven, no quire coupling) so it can later extract into a shared Rust CLI
kit; commands/update.rs is the thin quire wrapper. No cross-scheme version
diff (npm wrapper and Cargo versions are decoupled) -- idempotency is
delegated to npm/cargo. A --registry override is applied as the
--@agent-ix:registry= scope form (a plain --registry is ignored for a
scoped package when npmrc pins a scope registry).
Adds FR-016 + tests.md traceability. Deliberate exception to the quire-rs
thin boundary (StR-004): binary lifecycle, not artifact behavior.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: Agent IX <agent@agent-ix.dev>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
The CLI SHALL expose an `update` subcommand that upgrades the installed `quire`
15
+
binary to the latest published version. Because the single binary
16
+
([StR-001](../stakeholder/StR-001-static-binary-hot-path.md)) is distributed
17
+
through more than one channel — the npm prebuilt-binary wrapper
18
+
(`@agent-ix/quire-cli`) and `cargo install --git` — `update` SHALL first detect
19
+
**how the running binary was installed** and then drive the matching package
20
+
manager.
21
+
22
+
`update` is the one subcommand exempt from the quire-rs thin-boundary
23
+
([StR-004](../stakeholder/StR-004-thin-boundary-over-quire-rs.md)): it manages
24
+
binary lifecycle, not artifact behavior, so it carries no parser/renderer/
25
+
validator logic and wraps no `quire-rs` API. Its logic lives in a deliberately
26
+
**package-agnostic**`self_update` engine (config-struct driven, no quire
27
+
coupling), so the engine can later move verbatim into a shared Rust CLI kit; the
28
+
`update` command itself is a thin wrapper that supplies quire's distribution
29
+
coordinates.
30
+
31
+
The npm wrapper version and the Cargo crate version are independently numbered,
32
+
so `update` SHALL NOT compare the running binary's version against a registry —
33
+
a cross-scheme diff is meaningless. Idempotency is delegated to npm/cargo, which
34
+
already no-op (or rebuild) when current.
35
+
36
+
## Behavior
37
+
38
+
```
39
+
quire update [--check] [--registry <URL>]
40
+
```
41
+
42
+
1. Resolve the running executable path (`current_exe`) and classify the install
43
+
source: a path under a `node_modules` tree ⇒ **npm**; a path under `.cargo`
44
+
⇒ **cargo**; anything else ⇒ **unknown**.
45
+
2.**npm channel**:
46
+
-`--check`: run `npm view @agent-ix/quire-cli version` and report the latest
47
+
published version; install nothing.
48
+
- otherwise: run `npm install -g @agent-ix/quire-cli@latest` with inherited
49
+
stdio.
50
+
- When `--registry <URL>` is given, the override is applied as the
51
+
**scope-specific**`--@agent-ix:registry=<URL>` form, because a plain
52
+
`--registry` is silently ignored for a scoped package when the user's npmrc
53
+
pins a `@scope:registry`. With no `--registry`, the ambient npm config
54
+
resolves the package (mirroring how it was installed).
55
+
3.**cargo channel**:
56
+
-`--check`: report that cargo installs track the git default branch (no
57
+
single published version to compare); install nothing.
58
+
- otherwise: run `cargo install --git https://github.com/agent-ix/quire-cli
59
+
--force` with inherited stdio.
60
+
4.**unknown source**: never guess and clobber a binary the tool did not place.
61
+
Print manual upgrade instructions (the npm and cargo recipes plus the
62
+
releases URL) and exit **0** — performing no install, touching no network.
63
+
5. The summary lines describing the detected source and action are the
64
+
subcommand's primary output on **stdout**; any npm/cargo progress is the
65
+
child process's own inherited output.
66
+
6. Exit codes: **0** on success (including `--check` and the unknown-source
67
+
manual path); **1** when an invoked `npm`/`cargo` command fails or the
68
+
registry cannot be reached.
69
+
70
+
## Acceptance Criteria
71
+
72
+
| ID | Criteria | Verification |
73
+
|----|----------|--------------|
74
+
| FR-016-AC-1 | A binary path under `node_modules/...` classifies as the npm channel; a path under `.cargo/...` classifies as the cargo channel; any other path classifies as unknown | Test |
75
+
| FR-016-AC-2 | On an unknown source, `quire update` (with or without `--check`) prints manual upgrade instructions including the npm recipe, the cargo recipe, and the releases URL, exits **0**, and performs no install | Test |
76
+
| 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 |
77
+
| 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 |
78
+
| 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 |
79
+
| 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 |
80
+
| FR-016-AC-7 | A failing `npm`/`cargo` invocation, or an unreachable registry, exits **1**| Test |
81
+
82
+
## Dependencies
83
+
84
+
-**Upstream**: none in `quire-rs` (binary-lifecycle feature, not an engine
85
+
behavior). The shared `self_update` engine is in-crate today and is the
86
+
intended extraction unit for a future Rust CLI kit.
87
+
-**Downstream**: agent/CI setup docs that pin a single binary version
Copy file name to clipboardExpand all lines: spec/log.md
+1Lines changed: 1 addition & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -10,4 +10,5 @@ description: "Chronological log of structural changes to this bundle."
10
10
***2026-06-15** — Adopted OKF-compatible bundle structure with directory indexes.
11
11
***2026-06-16** — Added [FR-014](./functional/FR-014-validate-okf-bundle.md) (`quire validate --okf` permissive OKF bundle posture: `type` required, unknown-type/broken-link/index-incompleteness warn). Added [FR-003-AC-5](./functional/FR-003-extract-subcommand.md) (extract emits shared `[frontmatter]` untyped-document diagnostic). Backsynced the `artifact_type` → `type` discriminator rename across [FR-003](./functional/FR-003-extract-subcommand.md)/004/007/013 and spec.md via CR notes. Mapped IT-069..072 (`tests/cli_okf.rs`) + IT-026 reuse in tests.md.
12
12
***2026-06-17** — Added [FR-015](./functional/FR-015-fix-subcommand.md) (`quire fix` subcommand, ADR 0007): surfaces quire-rs unlinked-reference suggestions (FR-039) and, with `--write`, applies the auto-fixable ones via byte-exact writeback. Dry-run lists `would-fix`/`warning` and exits 1 when auto-fixes remain (CI gate); `--write` is idempotent; warn-only (unresolved/ambiguous) tokens are never written. Mapped IT-076..080 + AUDIT-002 (thin boundary) in tests.md.
13
+
***2026-06-20** — Added [FR-016](./functional/FR-016-update-subcommand.md) (`quire update` subcommand): install-source-aware self-update that detects the install channel from `current_exe` (`node_modules` ⇒ npm, `.cargo` ⇒ cargo, else unknown→manual instructions) and drives the matching package manager. No cross-scheme version diff (npm-wrapper and Cargo versions are decoupled); idempotency delegated to npm/cargo. A `--registry` override is applied as the scope-specific `--@agent-ix:registry=` form. Logic lives in a package-agnostic `self_update` engine (kit-extraction unit); the command is a thin wrapper. Deliberate exception to the quire-rs thin boundary ([StR-004](./stakeholder/StR-004-thin-boundary-over-quire-rs.md)) since it manages binary lifecycle, not artifact behavior.
13
14
***2026-06-19** — [FR-004](./functional/FR-004-validate-subcommand.md) scoped discovery now also searches the default install root `~/.ix/filament/modules` and `IX_FILAMENT_MODULES_PATH` (preferred over the legacy `IX_SCHEMA_PATH`); on zero discovered modules it lazy-installs the default set via `quoin plugin ensure-defaults` and reloads once (FR-004-AC-13/AC-14). Added [ADR-0001](./assets/adr/0001-validate-lazy-init-module-bootstrap.md) and amended [NFR-004](./non-functional/NFR-004-no-network.md) via CR note: the no-network guarantee is scoped to quire's own process; the lazy-init's `quoin` child is the sole documented network exception. Mapped IT-081 (scoped discovery network-free) + IT-082 (quoin-absent actionable error) in tests.md.
| 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), UT-SU-1 (`detect_source` npm/cargo/unknown classification — `self_update::tests`), UT-SU-2 (`registry_args` scope-form for scoped pkg / plain for unscoped / empty when no override), UT-SU-3 (`cargo``--check` reports branch-tracking, `latest: None`), AUDIT-002 (thin boundary — `update` carries no parser/validator), AUDIT-005 (`self_update` engine imports nothing from quire's `io`/command ctx — package-agnostic) ⚠️ npm-channel `--check`/install (AC-3), registry-unreachable/`npm`-fail exit 1 (AC-7), and cargo install (part of AC-1 dispatch) have **no automated trace** (network + global-install side effects) | ⚠️ |
73
74
74
75
## Non-Functional Requirement Coverage
75
76
@@ -170,11 +171,17 @@ The CLI is a thin process boundary over `quire-rs`; the upstream engine is indep
170
171
| IT-078 | A warn-only (unresolved/ambiguous) token is surfaced as `warning: … (<reason>)`, never written even under `--write`, and does not alone cause a nonzero exit | Integration | P0 | FR-015-AC-3 |
171
172
| IT-079 | A clean bundle (no auto-fix findings) exits 0 with empty stdout in both dry-run and `--write`| Integration | P1 | FR-015-AC-4 |
172
173
| IT-080 |`quire fix --scope <DIR> --module $M` with no positional uses `--scope` as root; a `..`/symlink-escape on root or `--module` is rejected by path-safety before any load | Integration | P0 | FR-015-AC-5, FR-005 |
174
+
| 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 |
175
+
| 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 |
| UT-SU-2 |`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 |
178
+
| UT-SU-3 |`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 |
| AUDIT-004 |`scripts/check_unsafe_comments.sh` zero unsafe in src/ + tests/ | Static | P0 | NFR-003-AC-1 |
184
+
| AUDIT-005 |`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 |
0 commit comments