@softspark/ai-toolkit 4.17.0 → 4.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/llms-full.txt CHANGED
@@ -21,12 +21,15 @@
21
21
  - [Plan: Offline-First SLM Profile — Lightweight Mode for Local Models](kb/history/completed/offline-slm-profile-plan-20260411.md)
22
22
  - [Retirement: Native Tool-Output Filter](kb/history/completed/output-filter-retirement-20260726.md)
23
23
  - [Plan: Output & Token Discipline](kb/history/completed/output-token-discipline-plan-20260504.md)
24
+ - [rtk Pack Integration](kb/history/completed/rtk-pack-integration-20260726.md)
25
+ - [Retirement: rtk-pack](kb/history/completed/rtk-pack-retirement-20260727.md)
24
26
  - [How-To Guides](kb/howto/README.md)
25
27
  - [Plan: Cloud Security Pack — Multi-Cloud Audit](kb/planning/cloud-security-pack-plan.md)
26
28
  - [Plan: Drop Cascade hooks after 2026-07-01 sunset](kb/planning/drop-cascade-hooks-after-sunset.md)
27
29
  - [PRD: MCP Context Trim v4.0](kb/planning/mcp-context-trim-v4-prd.md)
28
30
  - [SOP: Ecosystem Sync](kb/procedures/ecosystem-sync-sop.md)
29
31
  - [SOP: AI Toolkit Maintenance](kb/procedures/maintenance-sop.md)
32
+ - [SOP: Post-Release Testing](kb/procedures/post-release-testing-sop.md)
30
33
  - [SOP: Release Preparation](kb/procedures/release-preparation-sop.md)
31
34
  - [SOP: Release Verification](kb/procedures/release-verification-sop.md)
32
35
  - [Agents Catalog](kb/reference/agents-catalog.md)
@@ -3447,6 +3450,894 @@ Updated: `README.md`, `kb/reference/architecture-overview.md`, `kb/reference/ski
3447
3450
 
3448
3451
  ---
3449
3452
 
3453
+ ## kb/history/completed/rtk-pack-integration-20260726.md
3454
+
3455
+ ---
3456
+ title: "Plan: rtk Pack Integration"
3457
+ category: planning
3458
+ service: ai-toolkit
3459
+ tags:
3460
+ - rtk
3461
+ - plugin-pack
3462
+ - token-reduction
3463
+ - vendored-binaries
3464
+ - cross-build
3465
+ - upstream-sync
3466
+ doc_type: postmortem
3467
+ status: completed
3468
+ created: "2026-07-26"
3469
+ last_updated: "2026-07-26"
3470
+ completion: "100% — phases 0, 1, 2, 4, 5 delivered; phase 3 cut on the measurement"
3471
+ shipped_in: "v4.18.0"
3472
+ pinned_upstream: "v0.44.0"
3473
+ description: "Integrate rtk as an opt-in ai-toolkit plugin pack: binaries cross-built from source in our CI with telemetry disabled, hosted on our own GitHub Release, fetched and checksum-verified at pack install, auto-update on ai-toolkit update, and an SOP for tracking upstream releases. Custom filter presets were cut on the measurement (0.008% of input tokens). Phase 0 validated the premise against v0.44.0 on the full transcript corpus before any build work."
3474
+ ---
3475
+
3476
+ # rtk Pack Integration
3477
+
3478
+ **Completed 2026-07-26, shipped in v4.18.0.** Kept whole rather than summarised,
3479
+ because the value here is the measurements and the things that turned out to be
3480
+ false, not the plan structure. Live procedure:
3481
+ `kb/procedures/rtk-upstream-sync-sop.md`.
3482
+
3483
+ Headline: rtk-pack ships opt-in, built from source with telemetry compiled out,
3484
+ and saves a **measured 0.0615% of input tokens** against a kill number of 0.05%
3485
+ published before the measurement. Phase 3 (our own filter presets) was cut at
3486
+ 0.008%. The projection this plan published for itself was about twice too
3487
+ optimistic, and section 10.1 says why.
3488
+
3489
+ ## 1. Decision
3490
+
3491
+ Ship `rtk` to ai-toolkit users as an **opt-in plugin pack**, with the binary
3492
+ supply chain owned end to end by us.
3493
+
3494
+ Five choices, made and locked:
3495
+
3496
+ | Question | Decision |
3497
+ |---|---|
3498
+ | Delivery | Plugin pack (`rtk-pack`), never a native feature |
3499
+ | Binary hosting | Our own GitHub Release, fetched and SHA-256 verified at pack install |
3500
+ | Binary provenance | Cross-built from upstream source in our CI, `RTK_TELEMETRY_URL` unset |
3501
+ | Pinned upstream tag | **v0.44.0** (2026-07-26). v0.43.0 was ruled out on evidence, see section 2 |
3502
+ | Upstream tracking | An SOP that rebuilds, verifies, and re-publishes when rtk ships a release |
3503
+
3504
+ Upstream: https://github.com/rtk-ai/rtk, Apache-2.0, Rust 1.91 plus a C
3505
+ toolchain (rusqlite `bundled` compiles SQLite from source).
3506
+
3507
+ ## 2. Phase 0: premise validated before building anything
3508
+
3509
+ The retired output filter validated its design across five phases and its
3510
+ premise not at all until the fifth. This plan runs premise validation first.
3511
+ Everything below is measured, not estimated.
3512
+
3513
+ ### 2.1 Why v0.43.0 was ruled out
3514
+
3515
+ Two structural facts, read from the source of both tags:
3516
+
3517
+ - **Custom TOML filters are not wired into the rewrite path in v0.43.0.**
3518
+ `src/discover/registry.rs` at v0.43.0 contains zero references to
3519
+ `toml_filter`. In v0.44.0 they appear at `registry.rs:1047,1055,1058`.
3520
+ *This reason no longer applies:* section 7 cut our own presets, so nothing we
3521
+ ship depends on that wiring. It is recorded because it was one of the two
3522
+ arguments made at pin time, and the pin now rests on the second alone.
3523
+ - **`pipeline_final_safe` does not exist in v0.43.0.** It arrives in v0.44.0
3524
+ (`rules.rs:7,115,124`, `registry.rs:605,659,864,1069`). v0.43.0 rewrote the
3525
+ first pipeline stage; v0.44.0 rewrites the last. On this traffic 56.5% of
3526
+ `rtk grep` hits arrive through the pipeline-final path, so they do not exist
3527
+ at all on v0.43.0.
3528
+
3529
+ ### 2.2 The port models v0.44.0, not v0.43.0
3530
+
3531
+ `rtk_port.py` is re-validated by extracting the `assert_eq!` assertions from
3532
+ each tag's `registry.rs` `#[cfg(test)]` block and replaying them:
3533
+
3534
+ | Port checked against | Assertions | Exact-string agreement |
3535
+ |---|---:|---:|
3536
+ | **v0.44.0** | 203 | **203/203** |
3537
+ | v0.43.0 | 160 | 147/160 |
3538
+
3539
+ The 13 v0.43.0 failures are all the same pipeline inversion. The port also
3540
+ passes the 15 assertions v0.44.0 added after the port was written. Pinning
3541
+ v0.44.0 makes the existing measurement correct rather than requiring rework.
3542
+
3543
+ `registry.rs:590-592` returns `Some(unchanged)` for a simple already-`rtk`
3544
+ command; the port's equivalent bail must be mapped to that, not to `None`.
3545
+
3546
+ ### 2.3 The recorded numbers came from an 11% sample
3547
+
3548
+ `measure_rtk.py` sets `N_FILES = 134` and takes the most recently modified
3549
+ transcripts. The available pool is **1224 transcripts**. At n=134 the projected
3550
+ saving swings **8.8x** across windows (0.071% to 0.628%); at n>=408 it converges
3551
+ to within **1.05x**. The spread recorded in earlier revisions of this plan was
3552
+ sampling noise.
3553
+
3554
+ ### 2.4 The port over-counted, and by how much
3555
+
3556
+ Both real entry points call `contains_unattestable_construct` **before**
3557
+ `registry::rewrite_command`: `hook_cmd.rs:149-151` (Defer) and
3558
+ `rewrite_cmd.rs:54-56` (Passthrough). Any command carrying a command
3559
+ substitution, a process substitution, or a redirect with a file target is
3560
+ rejected whole. Upstream's rewrite tests call `rewrite_command` directly, so a
3561
+ port validated only against them passes while still over-counting.
3562
+
3563
+ The gate is ported from `lexer.rs:295-347` and validated **35/35** against
3564
+ upstream's own assertions at `lexer.rs:1186-1276`. Applying it costs 3.1%
3565
+ relative coverage.
3566
+
3567
+ ### 2.5 Corrected numbers
3568
+
3569
+ Full corpus, 1224 transcripts, 552 MB, 106 MB of tool results, 26.6 MB of
3570
+ successful Bash, entry gate applied:
3571
+
3572
+ | Metric | Earlier revision (n=134, ungated) | Phase 0 (n=1224, gated) |
3573
+ |---|---:|---:|
3574
+ | Coverage of successful Bash bytes | 31.5% | **35.17%** |
3575
+ | Coverage of all tool-result bytes | 9.69% | **8.77%** |
3576
+ | Ceiling: tool results as share of input volume | 5.52% | **4.54%** |
3577
+ | Projection at rtk's 60-90% claim | 0.32-0.48% | **0.239-0.359%** |
3578
+ | Projection, mechanism modelled | 0.15-0.21% | **0.117-0.164%** |
3579
+ | Reach of our own TOML presets (the number that cut section 7) | 1.91% of Bash | **1.08% of Bash** |
3580
+ | `Read` share of tool-result bytes | 53.8% | **62.8%** |
3581
+
3582
+ Modelled effectiveness per family against the claim: `rtk grep` 75% claimed and
3583
+ **9.0%** modelled; `rtk read` 60% claimed and **0.0%** modelled; `rtk rg` 75%
3584
+ and 30%; `rtk make` 65% and 16%; `rtk jq` 74%.
3585
+
3586
+ Largest unaddressed buckets, as a share of successful Bash bytes: pipeline not a
3587
+ rewritable final stage **39.78%**, ignored by rtk **10.72%**, entry gate
3588
+ **8.29%**, multiline script **4.71%**.
3589
+
3590
+ ### 2.6 Open question 3 answered: no
3591
+
3592
+ Patching `pipeline_final_safe` for `head` and `tail` unlocks 10.56 MB, 39.96% of
3593
+ Bash bytes. **88.3% of that (9.33 MB) routes to `rtk read`, measured at 0.00%
3594
+ saving** on this traffic because it returns files verbatim at the default
3595
+ `--level none`. The largest reachable gap is reachable and worthless. The
3596
+ argument for maintaining a fork does not survive its own measurement.
3597
+
3598
+ ## 3. Why a pack rather than a native feature
3599
+
3600
+ - **Opt-in is structural, not a policy.** Plugin packs must not be
3601
+ auto-installed [PATH: kb/reference/plugin-pack-conventions.md]. The retired
3602
+ output filter shipped `off` by default and still had to be wired into every
3603
+ install path, which is why v4.17.0 needed a bespoke migration cleanup.
3604
+ - **Removal is already solved.** `plugin remove` strips hook entries from
3605
+ `settings.json`, deletes owned `plugin-<pack>-*` assets, and leaves core and
3606
+ user files alone.
3607
+ - **The core package stays lean.** ai-toolkit is 4.4 MB unpacked; the platform
3608
+ artifacts are ~19.5 MB compressed. They never enter the base package.
3609
+ - **There is a working precedent.** `memory-pack` ships hooks, a shared script
3610
+ directory, an install-time init script, its own data with retention, and a
3611
+ clean uninstall. `rtk-pack` is the same shape with a binary instead of a
3612
+ database.
3613
+
3614
+ ## 4. Architecture
3615
+
3616
+ ```text
3617
+ app/plugins/rtk-pack/
3618
+ ├── plugin.json # pinned rtk version + per-platform SHA-256
3619
+ ├── README.md
3620
+ ├── hooks/
3621
+ │ └── rewrite.sh # PreToolUse; delegates to the fetched binary
3622
+ └── scripts/
3623
+ ├── init.py # platform detect → fetch → verify → install
3624
+ └── status.py # reports binary, digest and hook wiring
3625
+ ```
3626
+
3627
+ Runtime layout on a user machine:
3628
+
3629
+ ```text
3630
+ ~/.softspark/ai-toolkit/
3631
+ ├── hooks/plugin-rtk-pack-rewrite.sh # installed hook
3632
+ └── plugin-scripts/rtk-pack/
3633
+ ├── bin/rtk # verified binary, 0755
3634
+ └── version.json # pinned version + recorded digest
3635
+ ```
3636
+
3637
+ Everything the pack writes lives under `plugin-scripts/rtk-pack/`, which
3638
+ `plugin remove` deletes wholesale (`plugin.py:500-503`). The pack owns no state
3639
+ outside the toolkit data directory: no `filters.toml`, no entry in rtk's trust
3640
+ store, nothing in an OS config directory. That is a consequence of cutting
3641
+ section 7 and it is what keeps removal complete.
3642
+
3643
+ Binary supply chain:
3644
+
3645
+ ```text
3646
+ upstream tag → our CI cross-build (RTK_TELEMETRY_URL unset)
3647
+ → our GitHub Release softspark-rtk-<upstream>-<build>
3648
+ → SHA-256 recorded in plugin.json
3649
+ → plugin install: detect platform, fetch, verify, chmod
3650
+ ```
3651
+
3652
+ ## 5. Phase 1: build pipeline and binary release
3653
+
3654
+ **Outcome:** we can produce, from an upstream tag, a set of binaries that
3655
+ provably make no network calls of their own.
3656
+
3657
+ **Targets: five, not six.** `x86_64-apple-darwin`, `aarch64-apple-darwin`,
3658
+ `x86_64-unknown-linux-musl`, `aarch64-unknown-linux-gnu`,
3659
+ `x86_64-pc-windows-msvc`. Earlier revisions added `x86_64-unknown-linux-gnu` on
3660
+ the reasoning that upstream does not ship it. It is dropped: upstream's own
3661
+ Homebrew formula routes Linux x86_64 to the **musl** tarball
3662
+ (`release.yml:314-316`), a static musl binary runs on glibc, and upstream has
3663
+ never validated a gnu x86_64 artifact, so we would be first to ship one and
3664
+ would own all its breakage plus an uncontrolled glibc floor inherited from the
3665
+ runner image.
3666
+
3667
+ Build constraints, all read from the pinned checkout:
3668
+
3669
+ - **Pin the toolchain.** Upstream uses `dtolnay/rust-toolchain@stable`
3670
+ (`release.yml:65,116`) with `warnings = "deny"` (`Cargo.toml:70-72`) and ships
3671
+ no `rust-toolchain` file. A future rustc lint turns our builds red with no
3672
+ change on either side. We pin an exact version at or above 1.91.
3673
+ - **Windows must build natively.** `build.rs:6-13` emits
3674
+ `cargo:rustc-link-arg=/STACK:8388608` under `#[cfg(windows)]`, which in a
3675
+ build script is a **host** predicate. Cross-building Windows from Linux
3676
+ silently drops the 8 MiB stack reservation that upstream's own comment says is
3677
+ what makes `rtk.exe --version`, `--help`, and hook entry points start
3678
+ reliably. Build on `windows-latest`, or pass the link-arg explicitly.
3679
+ - **Both darwin targets build on `macos-latest` (arm64).** `macos-13` is the
3680
+ last x86_64 macOS image and GitHub is winding it down: on runs 30212577757 and
3681
+ 30214341444 that job sat queued indefinitely while every other target
3682
+ finished. Upstream builds both on arm64 too, but never runs its x86_64
3683
+ artifact. We do: the verifier detects Rosetta 2 and executes it, and reports
3684
+ `inconclusive` rather than `pass` if it cannot.
3685
+ - **Every target needs a target-capable C compiler.** rusqlite `bundled`
3686
+ (`Cargo.toml:26`) is not switchable off; there is no `[features]` table and no
3687
+ `cfg(feature` in `src/`. There is no pure-Rust escape route.
3688
+ - **aarch64-linux is the hard case, but upstream already solves it** with
3689
+ `cross: true` on `ubuntu-latest` (`release.yml:51-54`). Copy that. Upstream
3690
+ sets only the linker env var and lets the `cc` crate guess the C compiler;
3691
+ export `CC_aarch64_unknown_linux_gnu` and `AR_aarch64_unknown_linux_gnu`
3692
+ explicitly so a runner image change surfaces as a clear error.
3693
+ - **Run our own blocking `cargo audit`.** Upstream's is advisory: `ci.yml:88-96`
3694
+ swallows failures into a warning. Rebuilding from a tag inherits that
3695
+ lockfile, and on v0.44.0 it inherits four advisories across three of its 203
3696
+ crates. The audit and the build jobs apply the same remediation, so we audit
3697
+ the dependency set we ship rather than a different one:
3698
+
3699
+ | Crate | Advisory | Disposition |
3700
+ |---|---|---|
3701
+ | `anyhow` 1.0.102 | RUSTSEC-2026-0190 | `cargo update` to 1.0.103, in range |
3702
+ | `crossbeam-epoch` 0.9.18 | RUSTSEC-2026-0204 | `cargo update` to 0.9.20, in range |
3703
+ | `quick-xml` 0.37.5 | RUSTSEC-2026-0194, RUSTSEC-2026-0195 | ignored with reasons |
3704
+
3705
+ The quick-xml fix lands only in 0.41.0 while rtk pins `"0.37"` as a direct
3706
+ dependency (`Cargo.toml:34`), so taking it means editing `Cargo.toml` and the
3707
+ calling code in `src/cmds/dotnet/dotnet_trx.rs`. That would break the promise
3708
+ that the only difference from an upstream build is the undefined telemetry
3709
+ endpoint. The reachable surface is narrow: quick-xml parses .NET TRX test
3710
+ output only, the input is a report produced locally by the user's own test
3711
+ run, and both advisories are availability-only (CVSS `C:N/I:N/A:H`). The SOP
3712
+ re-checks this on every sync and deletes the ignores once upstream moves to
3713
+ quick-xml 0.41 or later.
3714
+ - A target that will not build is dropped, not faked.
3715
+
3716
+ ### 5.1 Proving the binary is silent
3717
+
3718
+ The endpoint is compile-time only: `option_env!("RTK_TELEMETRY_URL")` at
3719
+ `telemetry.rs:16` and `telemetry_cmd.rs:176`, with the token at
3720
+ `telemetry.rs:17` and `telemetry_cmd.rs:189`. Nothing supplies a default:
3721
+ `build.rs` emits no `cargo:rustc-env`, `TelemetryConfig` has no URL field, and
3722
+ no runtime `std::env::var` resolves the endpoint. Upstream injects it only in
3723
+ `release.yml:85-86,124-125,151-152`. Building without it yields a binary whose
3724
+ telemetry destination cannot be re-enabled by any env var or config at runtime.
3725
+
3726
+ **The acceptance criterion in earlier revisions was untestable and is replaced.**
3727
+ "No telemetry symbols in the binary" cannot be checked: the guard is a runtime
3728
+ `if TELEMETRY_URL.is_none()` on a const (`telemetry.rs:23-26`), not a `#[cfg]`,
3729
+ so the code compiles in and is only eliminated by LLVM as an optimisation; and
3730
+ `Cargo.toml:51` sets `strip = true`, which removes the symbols regardless of
3731
+ whether the code is present. A symbol check would pass for the wrong reason.
3732
+
3733
+ What we assert instead:
3734
+
3735
+ 0. **The artifact was actually started.** Every other assertion is worthless on
3736
+ a binary nobody executed, and two of the five targets are not native to their
3737
+ runner. `aarch64-unknown-linux-gnu` runs under `qemu-user` with the cross
3738
+ sysroot passed as `-L`, and `x86_64-apple-darwin` runs under Rosetta 2 on the
3739
+ arm64 runner. A target that cannot be started reports `inconclusive`, never
3740
+ `pass`.
3741
+ 1. **Build gate.** `RTK_TELEMETRY_URL` and `RTK_TELEMETRY_TOKEN` are unset in
3742
+ the build environment, asserted in CI before `cargo build`, with a clean
3743
+ target directory per build.
3744
+ 2. **Offline smoke run.** Each artifact runs its real command surface with no
3745
+ network route available and makes zero outbound connections. This is
3746
+ Linux-only: `unshare -rn` has no unprivileged equivalent on macOS or Windows
3747
+ runners, and Ubuntu 24.04's
3748
+ `kernel.apparmor_restrict_unprivileged_userns=1` means even there it needs
3749
+ `sudo`. On the first run this assertion silently degraded to a skip on every
3750
+ target while the verdict still read `pass`, so the manifest now records which
3751
+ isolator was used and a target that could not be started at all reports
3752
+ `inconclusive` rather than `pass`.
3753
+ 3. **Filesystem assertion.** No telemetry state is created under the resolved
3754
+ data directory.
3755
+ 4. **Drift detection.** Record artifact size and a string-allowlist hash per
3756
+ target and fail the build on unexplained drift, since whether `ureq`,
3757
+ `rustls`, `ring` and `webpki-roots` are actually eliminated is an LLVM
3758
+ outcome under `lto = true`, not a guarantee.
3759
+
3760
+ Three findings that must reach the pack README:
3761
+
3762
+ - **`rtk cc` shells out to `npx --yes ccusage`** when `ccusage` is not on PATH
3763
+ (`ccusage.rs:104-119`). That is a runtime npm fetch and third-party code
3764
+ execution. It is not telemetry and not automatic, but it is outbound network
3765
+ from a binary we tell users makes no network calls.
3766
+ - **`RTK_TELEMETRY_DISABLED` only accepts the exact string `1`**
3767
+ (`telemetry_cmd.rs:31-33`, locked by the test at `:222-229`). `true` and `yes`
3768
+ are silent no-ops. Never write anything else anywhere in the pack.
3769
+ - **`rtk telemetry forget` on an endpoint-free build prints a misleading
3770
+ failure** telling the user to email upstream to complete erasure
3771
+ (`telemetry_cmd.rs:158-169`), when nothing was ever sent.
3772
+
3773
+ Apache-2.0 obligations ship alongside: upstream `LICENSE`, a `NOTICE` file
3774
+ (upstream has none), and a statement that the only build-time change is leaving
3775
+ the telemetry endpoint undefined. **Do not redistribute upstream
3776
+ `DISCLAIMER.md` verbatim**: `DISCLAIMER.md:25` states telemetry is collected by
3777
+ default, which the code contradicts and which a compliance reviewer would read
3778
+ as a reason to block the pack. `docs/TELEMETRY.md:180` calls the mechanism
3779
+ "compile-time gating" where "all telemetry code is dead", which is imprecise for
3780
+ the same reason our own criterion changed.
3781
+
3782
+ **Success criteria:** five artifacts build from the pinned tag; each runs
3783
+ `rtk --version` on its target; the silence assertions pass or say plainly that
3784
+ they did not run; checksums published.
3785
+
3786
+ ### 5.2 Result, run 30214882862
3787
+
3788
+ All five targets build and verify. Every artifact was actually started, none
3789
+ shipped on a skipped assertion.
3790
+
3791
+ | Target | `runs` | `offline` |
3792
+ |---|---|---|
3793
+ | `aarch64-apple-darwin` | native | not applicable |
3794
+ | `x86_64-apple-darwin` | translated via Rosetta 2 | not applicable |
3795
+ | `x86_64-unknown-linux-musl` | native | passed under `sudo -n unshare -rn` |
3796
+ | `aarch64-unknown-linux-gnu` | emulated via `qemu-aarch64-static` | passed under `sudo -n unshare -rn` |
3797
+ | `x86_64-pc-windows-msvc` | native | not applicable |
3798
+
3799
+ No TLS markers in any artifact, so `ureq`, `rustls`, `ring` and `webpki-roots`
3800
+ appear to be eliminated under LTO once the endpoint const is `None`. That is an
3801
+ optimisation outcome rather than a guarantee, which is why the fingerprint is
3802
+ recorded per target and drift fails the build.
3803
+
3804
+ **Four of five targets are bit-reproducible.** Across independent runs with
3805
+ identical inputs, `aarch64-apple-darwin`, `x86_64-apple-darwin`,
3806
+ `aarch64-unknown-linux-gnu` and `x86_64-unknown-linux-musl` produced
3807
+ byte-identical binaries. The concern about unset `trim-paths` embedding registry
3808
+ paths does not materialise on these runners.
3809
+
3810
+ `x86_64-pc-windows-msvc` did not, and the cause was bounded: **24 bytes out of
3811
+ 8,018,432 differed**, in five regions. Four were the same two-byte value at
3812
+ `0x100` and in the debug directory, which is the MSVC link timestamp; the fifth
3813
+ was a 16-byte CodeView GUID. The code was identical; only the link stamp varied.
3814
+
3815
+ `-C link-arg=/Brepro` is now set for the Windows target, which makes the linker
3816
+ derive that timestamp from content rather than the clock. **The shipped
3817
+ `softspark-rtk-v0.44.0-1` artifacts predate that change**, so the Windows digest
3818
+ in `plugin.json` is from a non-deterministic link; the flag takes effect on the
3819
+ next rebuild, which will produce a different Windows digest for the same source.
3820
+
3821
+ This matters for the sync SOP: a changed digest means changed input rather than
3822
+ build noise. That inference holds on four targets today and on all five from the
3823
+ next rebuild onward.
3824
+
3825
+ Note that the **archives** are not reproducible even where the binaries are,
3826
+ because gzip records a timestamp. Compare extracted binaries, never the
3827
+ tarballs.
3828
+
3829
+ Three defects the run surfaced, all now fixed and guarded:
3830
+
3831
+ - The gnu target is dynamically linked, so `qemu-user` needs the cross sysroot.
3832
+ It is passed as `-L` rather than `QEMU_LD_PREFIX`, because the offline check
3833
+ runs under `sudo` and `env_reset` drops the variable.
3834
+ - The offline check compared a plain run against a namespaced one, so `sudo`'s
3835
+ environment handling was confounded with the network variable and a harness
3836
+ failure was reported as a behaviour difference in the binary. Both sides now
3837
+ run in a namespace and differ only in `-n`.
3838
+ - `7z` stored the full relative path, so the Windows zip held
3839
+ `rtk-src/target/<triple>/release/rtk.exe` while every tar.gz held a bare
3840
+ `rtk`. The pack installer extracts all five the same way, so this was a
3841
+ silent install failure on one platform. A `collect` step now asserts every
3842
+ archive holds exactly one flat entry.
3843
+
3844
+ ## 6. Phase 2: the pack
3845
+
3846
+ **Outcome:** `ai-toolkit plugin install rtk-pack` produces a working setup and
3847
+ `plugin remove` leaves nothing behind.
3848
+
3849
+ - `scripts/init.py` detects platform and libc, fetches the matching artifact,
3850
+ verifies its SHA-256 against `plugin.json`, and installs to
3851
+ `plugin-scripts/rtk-pack/bin/rtk`. A mismatch aborts and removes the partial
3852
+ download. No network at runtime, only at install.
3853
+ - Failure to fetch is not an install failure: the pack degrades to inert and
3854
+ says so, matching how the core behaves when `jq` is missing.
3855
+ - The hook is wired at `PreToolUse` through the existing pack hook mechanism, so
3856
+ `plugin remove` strips it via the `_source` marker.
3857
+ - `plugin status` dispatches to the pack's own `scripts/status.py`, which is
3858
+ generic rather than another hardcoded `if name == ...` branch. It reports the
3859
+ binary, the install record, the live version, and hook wiring, and announces a
3860
+ missing binary as inert rather than letting a green install imply it works.
3861
+
3862
+ ### 6.1 No trust step, because the pack ships no filters
3863
+
3864
+ Section 7 is cut, so the pack writes no `filters.toml` and takes no entry in
3865
+ rtk's trust store. That removes the single highest-ranked risk this plan
3866
+ carried: an untrusted or content-changed preset produces **zero output on the
3867
+ command path with no warning, no stderr line, and no non-zero exit**
3868
+ (`toml_filter.rs:220-221`, `:450-458`) — installed, apparently fine, doing
3869
+ nothing, which is exactly the shape of failure that retired the in-house filter.
3870
+
3871
+ The pack instead reports whether it is actually working. `scripts/status.py`
3872
+ distinguishes installed from functioning: a missing binary is announced as
3873
+ `MISSING — the hook is inert and every command runs unchanged` rather than
3874
+ inferred from a green install.
3875
+
3876
+ **If presets are ever revisited, these are the constraints, kept because they
3877
+ were expensive to establish:**
3878
+
3879
+ - Trust is a byte-exact SHA-256 (`trust.rs:142-163`), so every write, upgrade,
3880
+ repair, trailing newline, or git CRLF normalisation invalidates it.
3881
+ - `rtk trust --yes` is indiscriminate: it trusts every gated file that exists,
3882
+ including a `.rtk/filters.toml` an attacker committed into the CWD
3883
+ (`trust.rs:262-305`, project path is CWD-relative at `:207`).
3884
+ - It exits 1 both when there is nothing to trust and when the file is invalid
3885
+ TOML (`trust.rs:307-312`), so `set -e` aborts on the benign case.
3886
+ - There is no `rtk doctor`, and `rtk trust --list` never re-hashes
3887
+ (`trust.rs:242-256`). Verification means reading the store and recomputing.
3888
+ - The global path is not `~/.config/rtk/filters.toml` everywhere: it comes from
3889
+ `dirs::config_dir()` (`trust.rs:206-216`), which on macOS is
3890
+ `~/Library/Application Support`. rtk's own error text hardcodes the Linux path
3891
+ and is wrong there.
3892
+ - One global file, no include mechanism, so injection would have to follow the
3893
+ merge-friendly install model [PATH: kb/reference/merge-friendly-install-model.md]
3894
+ with comment markers rather than owning the file.
3895
+
3896
+ **Success criteria:** install, status, update, remove, and re-install are
3897
+ idempotent; a machine without the pack behaves exactly as today; uninstalling
3898
+ ai-toolkit removes every pack artifact.
3899
+
3900
+ ## 7. Phase 3: cut
3901
+
3902
+ **Cut on the measurement, 2026-07-26.** Custom TOML presets reach **1.08% of
3903
+ Bash bytes**, which is 0.27% of tool-result bytes. At a generous 60% reduction
3904
+ that is **0.008% of input tokens**, roughly one part in twelve thousand.
3905
+
3906
+ Against that: presets would have required a trust step that fails silently, a
3907
+ doctor check that recomputes digests, marker injection into a file the user may
3908
+ also edit, re-trusting after every write, and a per-upstream-release review of a
3909
+ DSL whose `deny_unknown_fields` rejects the entire file on one unknown key. The
3910
+ maintenance surface is large, the payoff is not measurable, and every item on
3911
+ that list is a way for the pack to look installed while doing nothing.
3912
+
3913
+ The pack therefore ships upstream rtk's built-in filters and nothing of our own.
3914
+ The 35.2% coverage quoted in section 2.5 is already what those built-ins
3915
+ deliver; it does not shrink as a result of this cut.
3916
+
3917
+ Two consequences recorded so they are not rediscovered:
3918
+
3919
+ - The pack owns no state outside `~/.softspark/ai-toolkit/plugin-scripts/rtk-pack/`,
3920
+ which is what makes `plugin remove` complete.
3921
+ - One of the two arguments for pinning v0.44.0 was that custom filters are only
3922
+ wired into the rewrite path there. That argument is now moot; the pin rests on
3923
+ `pipeline_final_safe`, which carries 56.5% of `rtk grep` hits on this traffic
3924
+ and does not exist in v0.43.0. See section 2.1.
3925
+
3926
+ Reopening this is a plan change, not a task: it needs a workload where rtk's
3927
+ built-ins measurably under-perform and a preset that measurably closes the gap,
3928
+ demonstrated by replay before any code is written.
3929
+
3930
+ ## 8. Phase 4: auto-update on `ai-toolkit update`
3931
+
3932
+ **Outcome:** a user who installed the pack gets the new binary by running the
3933
+ update they already run.
3934
+
3935
+ Verified gap: `handleUpdate` in `bin/ai-toolkit.js` reads installed modules from
3936
+ `state.json` and does not touch `plugins.json`. Nothing propagates to packs
3937
+ today, so this is new wiring, not a configuration change.
3938
+
3939
+ **Done.** `ai-toolkit update` now calls `plugin update --editor all --all`
3940
+ after the core update. Two things had to change first, both generic rather than
3941
+ rtk-specific:
3942
+
3943
+ - `update_pack` was an unconditional remove-then-reinstall, so wiring it into
3944
+ the core update would have refetched the binary on every run. `plugins.json`
3945
+ now records the pack version installed per editor, and an update whose
3946
+ manifest version matches is a silent no-op. `--force` overrides.
3947
+ - State written before versions were tracked has no `versions` map, so every
3948
+ pack reads as stale exactly once and is updated once. That is the intended
3949
+ migration, not a bug.
3950
+
3951
+ `--local` leaves packs alone: they live in `~/.softspark/ai-toolkit` and are
3952
+ global, while `--local` is project-local config only.
3953
+
3954
+ - After the core update completes, `ai-toolkit update` reads `plugins.json` and
3955
+ runs the equivalent of `plugin update` for every **currently installed** pack.
3956
+ Packs that are not installed stay untouched, which preserves adoption rule 1.
3957
+ - The rtk-pack update path is: compare the manifest's pinned version against the
3958
+ recorded pack version; if they differ, fetch and verify the new binary and
3959
+ re-install the hook.
3960
+ - `--dry-run` reports what would change per pack.
3961
+ - A pack update failure warns and continues; it never fails the core update.
3962
+ - Bats coverage in `tests/test_plugin_update.bats`: stale version replaced;
3963
+ current version untouched and silent; pack absent means no work; `--dry-run`
3964
+ reports without acting; legacy state with no version map updates once; a
3965
+ corrupt `plugins.json` does not crash the run.
3966
+
3967
+ **Success criteria:** the wiring is generic across packs, not rtk-specific;
3968
+ `update` remains idempotent; a failed pack update never leaves a half-installed
3969
+ binary.
3970
+
3971
+ ## 9. Phase 5: upstream sync SOP
3972
+
3973
+ **Outcome:** a written procedure so tracking upstream is routine rather than a
3974
+ research project each time. **Written**, at
3975
+ `kb/procedures/rtk-upstream-sync-sop.md`, modelled on the existing
3976
+ ecosystem-sync SOP.
3977
+
3978
+ The 0.43.0 to 0.44.0 bump is a worked example of why the review step exists: 200
3979
+ commits, and every file the SOP names changed, including a semantic inversion in
3980
+ pipeline rewriting and the arrival of the trust gate.
3981
+
3982
+ Steps the SOP must cover:
3983
+
3984
+ 1. **Detect.** Check the upstream releases feed for a tag newer than the pinned
3985
+ one. Their stable cadence is roughly two to four weeks, behind a long
3986
+ release-candidate train. Cadence: on demand, plus a check folded into release
3987
+ preparation.
3988
+ 2. **Review before building.** Read the changelog and diff `src/discover/rules.rs`,
3989
+ `src/discover/registry.rs`, `src/discover/lexer.rs`, `IGNORED_PREFIXES`, the
3990
+ TOML DSL structs in `src/core/toml_filter.rs`, `src/hooks/trust.rs`, and
3991
+ anything touching telemetry. A change to the DSL or to trust handling is a
3992
+ stop-and-think, not a rebuild.
3993
+ 3. **Re-validate the port.** Re-extract the `rewrite_command` assertions from the
3994
+ new tag's `registry.rs` test block and the gate assertions from `lexer.rs`,
3995
+ and replay both. Anything short of full agreement invalidates every coverage
3996
+ number until the port is fixed.
3997
+ 4. **Rebuild** all five targets from the new tag with telemetry unset.
3998
+ 5. **Verify.** Binary runs on each target; the silence assertions pass; the
3999
+ pack's fixtures still produce the expected decisions.
4000
+ 6. **Publish** a new release in our namespace and record the new digests.
4001
+ 7. **Bump** `rtk-pack` version in `plugin.json` and note the upstream version it
4002
+ tracks. The bump is what makes `plugin update` fire at all, since a pack whose
4003
+ recorded version still matches is skipped silently.
4004
+ 8. **Ship** in the next ai-toolkit release; installed packs pick it up through
4005
+ Phase 4.
4006
+ 9. **Record** the licence position if upstream relicenses or adds a NOTICE.
4007
+
4008
+ There is no upstream test asserting network silence, so that property can
4009
+ regress on any bump without turning their CI red. We own that test and re-run it
4010
+ every time.
4011
+
4012
+ **Success criteria:** a maintainer who has never done it can follow the SOP end
4013
+ to end; the review step names specific files rather than saying "check for
4014
+ breaking changes".
4015
+
4016
+ ## 10. Verification
4017
+
4018
+ The pack must be able to answer "did this help?" with a number.
4019
+
4020
+ **The before-and-after design is replaced.** Phase 0 measured its noise floor:
4021
+ on a 134-transcript window the projected saving varies by 8.8x while the
4022
+ mechanism is unchanged. A before-and-after comparison cannot detect an effect of
4023
+ 0.117% to 0.164% against that. Two changes make the measurement possible:
4024
+
4025
+ - **Measure over the whole transcript pool, not a recency window.** At n>=408
4026
+ the same metric converges to within 1.05x.
4027
+ - **Measure by replay, not by elapsed calendar time.** Run real captured tool
4028
+ output through the built binary and compare byte counts directly. That is the
4029
+ method that produced the honest 0% which retired the in-house filter, and it
4030
+ removes the confound of what work the user happened to do that week.
4031
+
4032
+ The harness is `replay_rtk.py` in the measurement archive, not a script inside
4033
+ the pack. It pairs each successful Bash result with the command that produced
4034
+ it, keeps the ones the validated port says rtk would rewrite, and pipes the
4035
+ captured bytes through the shipped binary via `rtk pipe -f <filter>`.
4036
+
4037
+ Two limits, stated because the number is meaningless without them:
4038
+
4039
+ - `rtk pipe` runs the same filter code as the command path but knows less than
4040
+ it does: no result caps, no exit code, no file set. For search families the
4041
+ replay figure is a **lower bound** on what the command path would save.
4042
+ - Families with no pipe filter (`rtk read`, `rtk ls`, `rtk wc`) cannot be
4043
+ measured this way at all. They are reported as **unmeasurable**, never folded
4044
+ in as zero, because a zero that is really an absence is how the previous
4045
+ effort talked itself into shipping.
4046
+
4047
+ Session-level token accounting, when it is wanted, comes from the session JSONL
4048
+ [PATH: scripts/session_token_stats.py], summing all four usage fields: in a
4049
+ cached session most context tokens land in the cache fields rather than
4050
+ `input_tokens`.
4051
+
4052
+ **Kill number, published before the measurement rather than argued after it:**
4053
+ if replay on the full corpus shows the shipped binary saving less than 0.05% of
4054
+ input tokens, the pack is not worth its maintenance and supply-chain surface,
4055
+ and it is retired the way the output filter was.
4056
+
4057
+ ### 10.1 Result: measured 0.0615%, kill number survived by 23%
4058
+
4059
+ Full pool, 28.56 MB of successful Bash output, replayed through the published
4060
+ `softspark-rtk-v0.44.0-1` binary:
4061
+
4062
+ | | |
4063
+ |---|---:|
4064
+ | Addressed by rtk | 9.99 MB, **35.00%** of Bash bytes |
4065
+ | Measurable through `rtk pipe` | 5.66 MB |
4066
+ | After filtering | 4.22 MB |
4067
+ | **Measured saving** | **1.44 MB = 360,529 tokens** |
4068
+ | As a share of tool-result bytes | 1.35% |
4069
+ | **As a share of input tokens** | **0.0615%** |
4070
+
4071
+ Against a kill number of 0.05%, the pack survives by a factor of 1.23. That is a
4072
+ pass, not a vindication.
4073
+
4074
+ **The projection published in section 2.5 was roughly twice too optimistic.**
4075
+ It said 0.117% to 0.164%; the measurement says 0.0615%. The error has a single
4076
+ identifiable cause: the model credited families it could not simulate with
4077
+ rtk's own claimed 60-90%. Replayed, the families that can be measured deliver
4078
+ **25.5%** in aggregate.
4079
+
4080
+ Per family, measured against modelled and against upstream's claim:
4081
+
4082
+ | Family | Measured | Modelled | Claimed |
4083
+ |---|---:|---:|---:|
4084
+ | `rtk grep` | **22.3%** | 9.0% | 75% |
4085
+ | `rtk git` | **33.1%** | — | 70% |
4086
+ | `rtk find` | **35.6%** | — | 70% |
4087
+ | `rtk rg` | **7.0%** | 30% | 75% |
4088
+ | `rtk ruff` | **0.0%** | — | — |
4089
+
4090
+ The model **understated** grep by 2.5x and the total still came out high, so the
4091
+ two errors are unrelated: coverage modelling was sound, per-family effectiveness
4092
+ was guesswork wherever the source was not read closely.
4093
+
4094
+ **What the replay does not settle.** 43.3% of addressed bytes (4.33 MB) have no
4095
+ `rtk pipe` filter and are unmeasurable, the largest being `rtk read` at 1.55 MB.
4096
+ That family is known from source to return files verbatim at the default
4097
+ `--level none`, so the headroom is smaller than 43.3% suggests. No extrapolation
4098
+ is applied: a zero that is really an absence of measurement is how the previous
4099
+ effort talked itself into shipping. For search families the pipe path also lacks
4100
+ the command path's result caps, making these figures a lower bound.
4101
+
4102
+ **One independent confirmation.** The replay measured coverage at 35.00% of Bash
4103
+ bytes; the gated port measured 35.17% on the same pool by a different method.
4104
+ The port is right about *which* commands rtk touches even where it was wrong
4105
+ about how much each saves.
4106
+
4107
+ Reproduce with `replay_rtk.py --pool 1300 --rtk <binary>` in the measurement
4108
+ archive.
4109
+
4110
+ ## 11. Licence and security obligations
4111
+
4112
+ - **Apache-2.0.** Ship upstream `LICENSE` with the binaries, add a `NOTICE`, and
4113
+ state the build-time change. Never relabel any upstream file MIT. Do not ship
4114
+ `DISCLAIMER.md` verbatim, see section 5.1.
4115
+ - **Telemetry.** Proven absent per build by the four assertions in section 5.1,
4116
+ re-proven on every upstream sync.
4117
+ - **Supply chain.** Digests pinned in `plugin.json`, verified on fetch,
4118
+ re-verified by `doctor`. This mirrors the existing checksum-pin discipline for
4119
+ URL-sourced rules and hooks. Run a blocking `cargo audit` on the pinned
4120
+ lockfile, because upstream's is advisory.
4121
+ - **Trust boundary.** rtk rewrites commands before execution, which the retired
4122
+ in-house contract explicitly forbade for itself. Adopting it is a conscious
4123
+ reversal of that constraint and its own threat surface: what runs is not what
4124
+ the model asked for. The pack's README must say this plainly, and
4125
+ `plugin install` must not be silent about it. The README must also disclose
4126
+ the `rtk cc` npx path.
4127
+
4128
+ The sharp edge is more specific than "the command changes". rtk evaluates the
4129
+ host's permission rules against the **original** command and applies the
4130
+ verdict to the **rewritten** one: `decide_hook_action(cmd, ...)` takes the
4131
+ command the model asked for, and `permissionDecision: allow` is then emitted
4132
+ for the substituted `rtk …` form (`hook_cmd.rs:405-436`). An allowlist entry
4133
+ for `git status` therefore authorises `rtk git status`, which the user never
4134
+ wrote a rule for. Upstream documents the absence case in its own test
4135
+ (`hook_cmd.rs:1272-1283`): with no matching allow rule, no decision is emitted
4136
+ and the normal prompt stands. Both halves belong in the README.
4137
+
4138
+ ## 12. Pre-mortem
4139
+
4140
+ | Rank | Failure mode | Probability | Impact | Mitigation |
4141
+ |---:|---|:---:|:---:|---|
4142
+ | — | ~~Presets install but are untrusted, so they silently do nothing~~ | — | — | **Eliminated**, not mitigated: section 7 is cut, so the pack ships no filters and takes no trust-store entry |
4143
+ | 2 | Cross-building with bundled SQLite is fragile in CI | Medium | Medium | Copy upstream's `cross` setup for aarch64-linux; five targets not six; a target that will not build is dropped, not faked |
4144
+ | 3 | Pre-execution rewriting changes command semantics | Medium | High | Opt-in pack, documented one-flag disable, upstream's own review process, our integration tests on real commands |
4145
+ | 4 | Windows binary cross-built without the 8 MiB stack reservation | Medium | High | Build natively on `windows-latest`, or pass the link-arg explicitly. `build.rs:6-13` |
4146
+ | 5 | Upstream velocity breaks the rewrite logic the coverage numbers model | Medium | Medium | Pinned version, SOP review step naming exact files, port re-validation as a gate, fork option preserved by Apache-2.0 |
4147
+ | 6 | Unpinned toolchain plus `warnings = "deny"` turns builds red with no change | Medium | Low | Pin an exact rustc version; treat bumps as deliberate |
4148
+ | 7 | A fetch failure leaves a half-installed pack | Medium | Medium | Verify-then-install, abort and clean on digest mismatch, `doctor` detects drift |
4149
+ | 8 | Live saving lands near the projection, not the claim | High | Medium | Section 10 measures it by replay either way, against a published kill number |
4150
+ | 9 | The auto-update wiring makes `update` slower or flakier | Low | Medium | Skip when versions match, warn-and-continue on failure, `--dry-run` coverage |
4151
+ | 10 | The pack drifts into being installed by default | Low | High | Adoption rule 1 plus a test asserting `install` never pulls it in |
4152
+
4153
+ ## 13. Open questions
4154
+
4155
+ 1. ~~Which upstream tag do we pin first?~~ **Answered: v0.44.0.** See section 2.1.
4156
+ 2. ~~User scope only, or a project-scoped preset variant too?~~ **Moot:** section 7 is cut, so the pack installs no presets at any scope.
4157
+ 3. ~~Is patching `pipeline_final_safe` for `head`/`tail` in scope later?~~
4158
+ **Answered: no.** See section 2.6.
4159
+ 4. ~~Does Phase 3 survive its own measurement at 0.008% of input tokens?~~
4160
+ **Answered: no, cut 2026-07-26.** See section 7.
4161
+ 5. ~~How does the pack share rtk's single global `filters.toml`?~~ **Moot:** it
4162
+ writes no filters at all. The marker-injection design and the trust
4163
+ constraints that would have applied are kept in section 6.1 in case presets
4164
+ are ever revisited.
4165
+
4166
+ ---
4167
+
4168
+ ## kb/history/completed/rtk-pack-retirement-20260727.md
4169
+
4170
+ ---
4171
+ title: "Retirement: rtk-pack — Broken On Install, Removed One Day After Shipping"
4172
+ category: planning
4173
+ service: ai-toolkit
4174
+ tags:
4175
+ - rtk
4176
+ - plugin-pack
4177
+ - token-reduction
4178
+ - postmortem
4179
+ - measurement
4180
+ - release-process
4181
+ doc_type: postmortem
4182
+ status: completed
4183
+ created: "2026-07-27"
4184
+ last_updated: "2026-07-27"
4185
+ shipped_in: "v4.19.0 (removal)"
4186
+ description: "Why rtk-pack, shipped in v4.18.0, was removed in v4.19.0: the first real install proved every rewritten command failed with exit 127, the wrong architecture was fetched on Apple Silicon, and the pack's own status check reported both as green. Measured value was 0.0615% of input tokens, so neither defect was worth fixing."
4187
+ ---
4188
+
4189
+ # Retirement: rtk-pack
4190
+
4191
+ **Shipped:** v4.18.0 (2026-07-26). **Removed:** v4.19.0 (2026-07-27).
4192
+
4193
+ The pack was installed on a maintainer's machine for the first time one day
4194
+ after release. It did not work, in the strongest sense available: it broke the
4195
+ shell.
4196
+
4197
+ ## Defect 1: every rewritten command failed with exit 127
4198
+
4199
+ rtk emits its rewrite as a bare `rtk git status`. The pack installs its binary
4200
+ at `~/.softspark/ai-toolkit/plugin-scripts/rtk-pack/bin/rtk` and never puts that
4201
+ directory on `PATH` — deliberately, so a checksum-pinned binary cannot shadow
4202
+ anything system-wide. The two decisions are individually defensible and jointly
4203
+ fatal: the shell could not find `rtk`, so every command the hook touched died
4204
+ before running.
4205
+
4206
+ Observed on the first three commands issued after install:
4207
+
4208
+ | Command | Result |
4209
+ |---|---|
4210
+ | `cat ~/.softspark/ai-toolkit/plugins.json` | `command not found: rtk` |
4211
+ | `git --no-pager diff` | `command not found: rtk` |
4212
+ | `find ~/.softspark -path '*rtk*'` | `command not found: rtk` |
4213
+
4214
+ The blast radius is every family in `rtk --help`: `git`, `ls`, `read`, `find`,
4215
+ `grep`, `rg`, `diff`, `docker`, `kubectl`, `npm`, `jest`, `tsc`. On the reference
4216
+ workload that is 35% of Bash bytes, which is the same 35% the pack was built to
4217
+ save. The mechanism that produced the benefit produced the outage.
4218
+
4219
+ ## Defect 2: the Intel build on an Apple Silicon host
4220
+
4221
+ `detect_platform()` trusted `platform.machine()`. The maintainer's `python3` is
4222
+ an Intel Homebrew build at `/usr/local/opt/python@3.14`, so it runs under
4223
+ Rosetta 2, where every architecture API inside the process reports `x86_64` —
4224
+ `platform.machine()`, `os.uname()` and `uname -m` alike. The pack fetched
4225
+ `rtk-x86_64-apple-darwin.tar.gz` onto an `arm64` machine and ran it emulated.
4226
+
4227
+ `sysctl.proc_translated` answers the question that distinguishes the two cases
4228
+ and was not consulted. Note that CI had already met Rosetta on this project:
4229
+ commit `30614ca`, *"build x86_64-darwin on arm64 and verify it under Rosetta"*.
4230
+ The build pipeline knew. The install path did not.
4231
+
4232
+ ## Defect 3: the pack's own health check called both of them green
4233
+
4234
+ `plugin status` reported the binary present, the digest recorded, `runs: rtk
4235
+ 0.44.0`, the hook script present and the hook registered. All true, all useless.
4236
+ The pack's `status.py` was written specifically to *"distinguish installed from
4237
+ working"*, and it checked only the installed half.
4238
+
4239
+ `kb/procedures/post-release-testing-sop.md`, written the day before the release,
4240
+ requires exactly the missing step:
4241
+
4242
+ > Presence is not function. Drive the hook directly.
4243
+
4244
+ The SOP was written and not run. Every defect above would have surfaced in its
4245
+ first five minutes.
4246
+
4247
+ ## The number that made fixing it not worth it
4248
+
4249
+ All three defects were fixed and tested before the removal decision: a `PATH`
4250
+ prefix on the emitted command, `sysctl.proc_translated` in the detector, and a
4251
+ status check that executes what the hook emits and looks for 127. Six tests,
4252
+ all passing, all failing against the previous code. The work was not hard.
4253
+
4254
+ It was measured against this, from `rtk-pack-integration-20260726.md` §10.1:
4255
+
4256
+ | | |
4257
+ |---|---:|
4258
+ | Measured saving | 1.44 MB = 360,529 tokens |
4259
+ | As a share of input tokens | **0.0615%** |
4260
+ | Kill number, published before the measurement | 0.05% |
4261
+ | Margin | ×1.23 |
4262
+
4263
+ The integration plan's own verdict on that margin was *"a pass, not a
4264
+ vindication"*. A pack that survives its kill number by 23%, carries a
4265
+ supply-chain surface, an upstream-sync SOP, a cross-build workflow for five
4266
+ targets, and a hook that rewrites every command before it runs, is not worth
4267
+ three defect classes discovered on first contact. The cost side moved; the
4268
+ benefit side never did.
4269
+
4270
+ ## Why the ceiling was always low, independent of any defect
4271
+
4272
+ `Read` results are 62.8% of tool-result bytes on this traffic, and `rtk read`
4273
+ measures 0.0% — at its default `--level none` it returns files verbatim. Tool
4274
+ results are 4.54% of input token volume, so that is the arithmetic ceiling for
4275
+ any tool-output mechanism here, and rtk addresses 8.8% of it.
4276
+
4277
+ Replayed against upstream's own claims, the families that can be measured
4278
+ deliver 25.5% in aggregate against a claimed 60–90%:
4279
+
4280
+ | Family | Measured | Claimed |
4281
+ |---|---:|---:|
4282
+ | `rtk find` | 35.6% | 70% |
4283
+ | `rtk git` | 33.1% | 70% |
4284
+ | `rtk grep` | 22.3% | 75% |
4285
+ | `rtk rg` | 7.0% | 75% |
4286
+ | `rtk read` | 0.0% | 60% |
4287
+
4288
+ Better engineering does not move any of this. The lever is in the wrong place.
4289
+
4290
+ ## The process lesson
4291
+
4292
+ The previous retirement in this series
4293
+ ([output-filter-retirement-20260726.md](output-filter-retirement-20260726.md))
4294
+ concluded that premise validation must come first, with a kill number published
4295
+ before the measurement. rtk-pack did that, and did it well: Phase 0 ran on 1224
4296
+ transcripts before any build work, the kill number was published in advance, and
4297
+ Phase 3 was cut on a measured 0.008%.
4298
+
4299
+ It then shipped without anyone installing it.
4300
+
4301
+ Measurement discipline and release discipline are different disciplines, and
4302
+ this project now has one clean failure of each. The first shipped a feature that
4303
+ worked and saved nothing. The second shipped a feature that would have saved
4304
+ something and did not work. The next plan of this shape needs both gates, and
4305
+ the second one is the cheap one: install the artifact, run the thing, look at
4306
+ what happens.
4307
+
4308
+ ## What survives
4309
+
4310
+ - **Multi-runtime pack hook wiring.** Packs write user-scope entries to
4311
+ `~/.cursor/hooks.json` and `~/.gemini/settings.json`, tagged per pack, with
4312
+ `plugin remove` taking only its own back out. Generic; no rtk in it.
4313
+ - **`supported_editors` in the manifest.** A pack declares the runtimes it works
4314
+ on instead of installing everywhere and silently doing nothing.
4315
+ - **Generic `plugin status` dispatch.** Any pack can ship `scripts/status.py`.
4316
+ The lesson attached: a status check must prove the working half by exercising
4317
+ it.
4318
+ - **Version-aware `plugin update`.** A pack whose manifest has not moved is a
4319
+ silent no-op.
4320
+ - **`audit_skills.py --ci` and the ShellCheck gate now cover `app/plugins/`.**
4321
+ - **[Post-Release Testing SOP](../../procedures/post-release-testing-sop.md).**
4322
+ Kept, and now carries the note that the one time it existed and was skipped,
4323
+ this happened.
4324
+
4325
+ ## What was removed
4326
+
4327
+ `app/plugins/rtk-pack/`, `.github/workflows/rtk-build.yml`,
4328
+ `scripts/verify_rtk_binary.py`, `tests/test_rtk_pack.bats`,
4329
+ `tests/test_verify_rtk_binary.bats`, `kb/procedures/rtk-upstream-sync-sop.md`,
4330
+ and the GitHub Release `softspark-rtk-v0.44.0-1` holding the five cross-built
4331
+ binaries.
4332
+
4333
+ Anyone who installed the pack under v4.18.0 should run
4334
+ `ai-toolkit plugin remove rtk-pack`. With the release deleted, a fresh
4335
+ `plugin install rtk-pack` on v4.18.0 fails at the fetch and leaves the pack
4336
+ inert rather than half-installed, which is the degraded path the pack was
4337
+ designed for.
4338
+
4339
+ ---
4340
+
3450
4341
  ## kb/howto/README.md
3451
4342
 
3452
4343
  ---
@@ -5453,7 +6344,7 @@ python3 scripts/validate.py --strict
5453
6344
  ai-toolkit plugin list # show available packs
5454
6345
  ai-toolkit plugin install --editor claude <name> # install for Claude Code global target
5455
6346
  ai-toolkit plugin install --editor codex <name> # install for Codex global target
5456
- ai-toolkit plugin install --editor all --all # install all 11 packs for both runtimes
6347
+ ai-toolkit plugin install --editor all --all # install all 12 packs for both runtimes
5457
6348
  ai-toolkit plugin update --editor all --all # re-apply all installed packs after toolkit updates
5458
6349
  ai-toolkit plugin clean <name> # prune data older than 90 days
5459
6350
  ai-toolkit plugin clean <name> --days 30 # custom retention
@@ -5650,6 +6541,228 @@ What `uninstall` does:
5650
6541
 
5651
6542
  ---
5652
6543
 
6544
+ ## kb/procedures/post-release-testing-sop.md
6545
+
6546
+ ---
6547
+ title: "SOP: Post-Release Testing"
6548
+ category: procedures
6549
+ service: ai-toolkit
6550
+ tags: [sop, post-release, smoke-test, npm, sandbox, plugin-pack, provenance, isolation]
6551
+ version: "1.0.0"
6552
+ created: "2026-07-26"
6553
+ last_updated: "2026-07-26"
6554
+ description: "Smoke-test a published @softspark/ai-toolkit release from npm in an isolated HOME and npm prefix, without touching the maintainer's real install. Covers provenance, CLI, doctor, and the full plugin-pack lifecycle including the degraded-install path. Written for v4.18.0 and not run; v4.18.0 shipped a pack that broke every command it touched, and every step here would have caught it."
6555
+ ---
6556
+
6557
+ # SOP: Post-Release Testing
6558
+
6559
+ Runs **after** `publish.yml` succeeds on a tag. Verifies the artifact users will
6560
+ actually install, from npm, rather than the working tree.
6561
+
6562
+ Sibling procedures exist for `jira-mcp` and `legal-pl-pack`; this is the
6563
+ ai-toolkit equivalent. It complements
6564
+ [Release Verification](release-verification-sop.md), which checks the toolkit
6565
+ from the maintainer's own installed copy. The difference that matters: this one
6566
+ never writes to the maintainer's `~/.claude` or `~/.softspark`.
6567
+
6568
+ **Time:** 10 minutes.
6569
+
6570
+ ## Why isolation is the first step, not a detail
6571
+
6572
+ The toolkit installs into `$HOME`. Testing a release against your own HOME
6573
+ means the test either pollutes your working setup or, worse, passes because of
6574
+ state your setup already had. Both make the result meaningless.
6575
+
6576
+ Every command below runs against a throwaway HOME and a throwaway npm prefix.
6577
+ Nothing is global.
6578
+
6579
+ ## Phase 1: Sandbox
6580
+
6581
+ ```bash
6582
+ SB=$(mktemp -d)
6583
+ mkdir -p "$SB/home" "$SB/npm"
6584
+ export HOME="$SB/home"
6585
+ AT="$SB/npm/bin/ai-toolkit"
6586
+ echo "sandbox: $SB"
6587
+ ```
6588
+
6589
+ Record the real state now, so Phase 7 can prove it is unchanged:
6590
+
6591
+ ```bash
6592
+ python3 -c "
6593
+ import json, pathlib
6594
+ p = pathlib.Path('$SB/../real-before.json')
6595
+ import os
6596
+ home = pathlib.Path(os.path.expanduser('~'))
6597
+ " 2>/dev/null
6598
+ # Simpler: note what exists today.
6599
+ cat ~/.softspark/ai-toolkit/plugins.json 2>/dev/null
6600
+ ```
6601
+
6602
+ ## Phase 2: Provenance
6603
+
6604
+ Do this before installing anything: an unsigned publish is a release-blocking
6605
+ regression, and there is no point smoke-testing a build you would have to redo.
6606
+
6607
+ ```bash
6608
+ VERSION="X.Y.Z"
6609
+ npm view "@softspark/ai-toolkit@${VERSION}" --json \
6610
+ | python3 -c "
6611
+ import json, sys
6612
+ d = json.load(sys.stdin); att = d['dist'].get('attestations', {})
6613
+ pt = att.get('provenance', {}).get('predicateType')
6614
+ assert pt == 'https://slsa.dev/provenance/v1', f'NO PROVENANCE: {pt}'
6615
+ print('PROVENANCE OK:', att['url'])
6616
+ "
6617
+ ```
6618
+
6619
+ ## Phase 3: Install from npm
6620
+
6621
+ ```bash
6622
+ npm install -g --prefix "$SB/npm" "@softspark/ai-toolkit@${VERSION}"
6623
+ "$AT" --version # must equal VERSION
6624
+ "$AT" --help >/dev/null && echo "help OK"
6625
+ ```
6626
+
6627
+ ## Phase 4: Core surfaces
6628
+
6629
+ ```bash
6630
+ "$AT" install # full global install into the sandbox HOME
6631
+ "$AT" doctor # must end: Errors: 0 | Warnings: 0
6632
+ "$AT" status
6633
+ "$AT" plugin list # pack count must match app/plugins/
6634
+ ```
6635
+
6636
+ **A doctor run before `install` reports `agents directory missing` and
6637
+ `skills directory missing`.** That is the sandbox being empty, not a defect.
6638
+ Install first, then judge doctor.
6639
+
6640
+ ## Phase 5: Plugin-pack lifecycle
6641
+
6642
+ Run this for any pack the release touched. For a pack that downloads a binary,
6643
+ every step below has caught a real defect at least once.
6644
+
6645
+ ```bash
6646
+ "$AT" plugin install <pack>
6647
+ "$AT" plugin status # does it report itself working?
6648
+ find "$HOME/.softspark/ai-toolkit" -path '*<pack>*' # what actually landed
6649
+ ```
6650
+
6651
+ **Verify:**
6652
+ - [ ] `plugin status` distinguishes *installed* from *working*, not just present
6653
+ - [ ] For a binary pack: the binary runs and reports the pinned upstream version
6654
+ - [ ] The hook is registered in `~/.claude/settings.json` with the pack's `_source`
6655
+
6656
+ **Does it do its job?** Presence is not function. Drive the hook directly:
6657
+
6658
+ ```bash
6659
+ printf '%s' '{"tool_name":"Bash","tool_input":{"command":"git status"}}' \
6660
+ | bash "$HOME/.softspark/ai-toolkit/hooks/plugin-<pack>-<hook>.sh"
6661
+ ```
6662
+
6663
+ **Update path:**
6664
+
6665
+ ```bash
6666
+ "$AT" plugin update --editor claude --all # current version: silent
6667
+ "$AT" plugin update --editor claude --all --dry-run # says "up to date"
6668
+ # force a stale marker, then confirm it updates and re-records:
6669
+ python3 -c "
6670
+ import json, pathlib, os
6671
+ p = pathlib.Path(os.environ['HOME'] + '/.softspark/ai-toolkit/plugins.json')
6672
+ d = json.loads(p.read_text()); d['targets']['claude']['versions']['<pack>'] = '0.0.9'
6673
+ p.write_text(json.dumps(d, indent=2))
6674
+ "
6675
+ "$AT" plugin update --editor claude --all # reports 0.0.9 -> <version>
6676
+ "$AT" update # core update leaves a current pack alone
6677
+ ```
6678
+
6679
+ **Removal must be complete:**
6680
+
6681
+ ```bash
6682
+ "$AT" plugin remove <pack>
6683
+ find "$HOME/.softspark" -path '*<pack>*' | wc -l # must be 0
6684
+ python3 -c "
6685
+ import json, pathlib, os
6686
+ d = json.loads(pathlib.Path(os.environ['HOME'] + '/.claude/settings.json').read_text())
6687
+ print('hook still present:', '<pack>' in json.dumps(d.get('hooks', {})))
6688
+ "
6689
+ "$AT" plugin install <pack> # re-install must work
6690
+ ```
6691
+
6692
+ ## Phase 6: The degraded path
6693
+
6694
+ **This is the step most worth keeping.** A pack that fetches anything can fail
6695
+ to fetch, and the failure mode must be inert rather than broken or silent.
6696
+
6697
+ No pack in the toolkit fetches anything today. Run this phase if one ever does
6698
+ again, pointing its source-override variable at a dead URL:
6699
+
6700
+ ```bash
6701
+ "$AT" plugin remove <pack>
6702
+ <PACK>_RELEASE_BASE_URL="file:///nonexistent" "$AT" plugin install <pack>
6703
+ ```
6704
+
6705
+ **Verify:**
6706
+ - [ ] Install reports the failure in words a user can act on, and does not claim success
6707
+ - [ ] No partial artifact is left behind
6708
+ - [ ] The hook is still wired, and passes commands through untouched
6709
+ - [ ] `plugin status` says the pack is inert and names the fix
6710
+ - [ ] Re-installing without the broken source recovers
6711
+
6712
+ ## Phase 7: Prove the real environment is untouched
6713
+
6714
+ ```bash
6715
+ python3 -c "
6716
+ import json, pathlib
6717
+ d = json.loads(pathlib.Path.home().joinpath('.softspark/ai-toolkit/plugins.json').read_text())
6718
+ print('plugins.json:', d['targets']['claude'])
6719
+ p = pathlib.Path.home() / '.claude/settings.json'
6720
+ print('pack hook leaked into real settings:', '<pack>' in json.dumps(json.loads(p.read_text()).get('hooks', {})) if p.exists() else False)
6721
+ print('pack paths in real ~/.softspark:', len(list(pathlib.Path.home().joinpath('.softspark').rglob('*<pack>*'))))
6722
+ "
6723
+ ```
6724
+
6725
+ All three must show the pre-test state.
6726
+
6727
+ ## Phase 8: Clean up
6728
+
6729
+ `guard-destructive.sh` blocks `rm -rf` on a `PreToolUse` hook, so removal goes
6730
+ through an enumerated delete that reports what it removed:
6731
+
6732
+ ```bash
6733
+ python3 -c "
6734
+ import pathlib, shutil
6735
+ sb = pathlib.Path('$SB')
6736
+ assert sb.is_dir() and str(sb).startswith(('/tmp', '/var/folders')), sb
6737
+ n = sum(1 for _ in sb.rglob('*') if _.is_file())
6738
+ shutil.rmtree(sb)
6739
+ print(f'removed {sb} ({n} files)')
6740
+ "
6741
+ ```
6742
+
6743
+ ## Success criteria
6744
+
6745
+ | Area | Criterion |
6746
+ |---|---|
6747
+ | Supply chain | `predicateType == https://slsa.dev/provenance/v1` |
6748
+ | CLI | `--version` equals the tag, `--help` renders |
6749
+ | Health | `doctor` after `install`: 0 errors, 0 warnings |
6750
+ | Catalog | `plugin list` count matches `app/plugins/` |
6751
+ | Pack install | Binary present, runs, digest verified, hook registered |
6752
+ | Pack function | Driving the hook produces the expected effect, not just exit 0 |
6753
+ | Pack update | Current version silent; stale version updates and re-records |
6754
+ | Pack removal | Zero residue in `~/.softspark` and `settings.json`; re-install works |
6755
+ | Degraded path | Fetch failure is inert, loud in status, and recoverable |
6756
+ | Isolation | Real `~/.claude` and `~/.softspark` byte-identical to pre-test |
6757
+
6758
+ ## Related
6759
+
6760
+ - [Release Preparation](release-preparation-sop.md) — run before tagging
6761
+ - [Release Verification](release-verification-sop.md) — the maintainer-install checks
6762
+ - [rtk-pack Retirement](../history/completed/rtk-pack-retirement-20260727.md) — what happened the one time this SOP was written and not run
6763
+
6764
+ ---
6765
+
5653
6766
  ## kb/procedures/release-preparation-sop.md
5654
6767
 
5655
6768
  ---
@@ -5657,9 +6770,9 @@ title: "SOP: Release Preparation"
5657
6770
  category: procedures
5658
6771
  service: ai-toolkit
5659
6772
  tags: [sop, release, version, publish, changelog, semver, provenance, sarif, ecosystem, shellcheck]
5660
- version: "1.11.1"
6773
+ version: "1.11.2"
5661
6774
  created: "2026-04-10"
5662
- last_updated: "2026-07-24"
6775
+ last_updated: "2026-07-27"
5663
6776
  description: "Step-by-step checklist for preparing a new ai-toolkit release — ecosystem-sync drift check, version sync, changelog, artifact regeneration, validation, and tagging. Run BEFORE every git tag. Includes mandatory Provenance, SARIF, and checksum-pin checks added in v2.8.0, the single-run npm test discipline added in v1.8.0, the ecosystem-sync gate added in v1.9.0, the registry-vs-generators drift gate added in v1.10.0, and the mandatory pre-tag ShellCheck gate added in v1.11.0 (publish.yml does not run ShellCheck, so a hook lint failure can publish while reddening main CI — see the v4.5.1 postmortem in Phase 5)."
5664
6777
  ---
5665
6778
 
@@ -5904,12 +7017,17 @@ Add entry at the top of `CHANGELOG.md` (after the header, before previous releas
5904
7017
 
5905
7018
  ## Phase 4: Regenerate Artifacts
5906
7019
 
7020
+ Use the npm scripts, not the generators directly:
7021
+
5907
7022
  ```bash
5908
- python3 scripts/generate_agents_md.py > AGENTS.md
5909
- python3 scripts/generate_llms_txt.py > llms.txt
5910
- python3 scripts/generate_llms_txt.py --full > llms-full.txt
7023
+ npm run generate:agents # AI_TOOLKIT_NO_CUSTOM_RULES=1 python3 scripts/generate_agents_md.py > AGENTS.md
7024
+ npm run generate:llms # llms.txt + llms-full.txt
5911
7025
  ```
5912
7026
 
7027
+ `generate:agents` sets `AI_TOOLKIT_NO_CUSTOM_RULES=1`. Running
7028
+ `generate_agents_md.py` bare picks up whatever is registered in the maintainer's
7029
+ own `~/.softspark/ai-toolkit/rules/`, which then ships inside `AGENTS.md`.
7030
+
5913
7031
  Check if anything actually changed:
5914
7032
 
5915
7033
  ```bash
@@ -5944,7 +7062,15 @@ diff \
5944
7062
  && echo "OK: registry matches filesystem" \
5945
7063
  || { echo "DRIFT: update supported-tools-registry.md before tagging"; exit 1; }
5946
7064
 
5947
- # Run npm test ONCE, cache output, parse from file. The suite is 900+ bats
7065
+ # Stage first IF this release adds or deletes a kb/ file. The test
7066
+ # "npm package KB files match the tracked release set" compares `git ls-files
7067
+ # kb` (the index) against what `npm pack` sees (the working tree), so an
7068
+ # unstaged addition reads as "extra" and an unstaged deletion as "missing".
7069
+ # Phase 6 stages, and it runs after this one, so the ordering fails the test
7070
+ # for any release that touches the KB. Staging early costs nothing.
7071
+ git status --porcelain kb/ | grep -qE '^(\?\?| D|\?M)' && git add -A kb/
7072
+
7073
+ # Run npm test ONCE, cache output, parse from file. The suite is 1400+ bats
5948
7074
  # cases — rerunning it per check wastes minutes. Do not pipe npm test into
5949
7075
  # tail/grep multiple times in the same session.
5950
7076
  npm test > /tmp/npm-test.log 2>&1
@@ -6101,7 +7227,7 @@ git push origin --delete vX.Y.Z
6101
7227
  | 13 | ShellCheck hooks | `shellcheck --severity=warning app/hooks/*.sh` | Exit 0, no output (mirrors ci.yml; publish.yml does NOT run it) |
6102
7228
  | 14 | Provenance flag check | `grep -- '--provenance' .github/workflows/publish.yml` | Present |
6103
7229
  | 15 | Checksum-pin backfill | `sources.json` entries all have `sha256` | No unpinned URL sources |
6104
- | 16 | Tests | `npm test` | All pass |
7230
+ | 16 | Tests | `git add -A kb/` if the KB changed, then `npm test` | All pass |
6105
7231
  | 17 | Commit | `git commit` | Clean working tree |
6106
7232
  | 18 | Tag | `git tag vX.Y.Z` | Tag exists |
6107
7233
  | 19 | Push | `git push origin main --tags` | CI triggered with `id-token: write` |
@@ -7746,7 +8872,7 @@ Usage: ai-toolkit <command> [options]
7746
8872
  |---------|-------------|
7747
8873
  | `plugin list` | Show available plugin packs with install status |
7748
8874
  | `plugin install <name> [--editor claude\|codex\|all]` | Install a plugin pack for Claude Code and/or Codex (`claude` means Claude Code, not the Claude app) |
7749
- | `plugin install --all [--editor claude\|codex\|all]` | Install all 11 plugin packs |
8875
+ | `plugin install --all [--editor claude\|codex\|all]` | Install all 12 plugin packs |
7750
8876
  | `plugin update <name> [--editor claude\|codex\|all]` | Update a plugin pack (remove + reinstall, preserves data) |
7751
8877
  | `plugin update --all [--editor claude\|codex\|all]` | Update all installed plugin packs |
7752
8878
  | `plugin clean <name> [--days N]` | Prune old plugin data (default: 90 days) |
@@ -12491,10 +13617,10 @@ Required keys:
12491
13617
  ## CLI Management
12492
13618
 
12493
13619
  ```bash
12494
- ai-toolkit plugin list # show all 11 packs with install status
13620
+ ai-toolkit plugin list # show all 12 packs with install status
12495
13621
  ai-toolkit plugin install --editor claude <name> # Claude Code global target
12496
13622
  ai-toolkit plugin install --editor codex <name> # Codex global target
12497
- ai-toolkit plugin install --editor all --all # install all 11 packs for both runtimes
13623
+ ai-toolkit plugin install --editor all --all # install all 12 packs for both runtimes
12498
13624
  ai-toolkit plugin update --editor all --all # update all installed packs
12499
13625
  ai-toolkit plugin clean <name> # prune data older than 90 days (default)
12500
13626
  ai-toolkit plugin clean <name> --days 30 # prune data older than 30 days
@@ -12558,6 +13684,30 @@ ai-toolkit plugin status --editor all # show installed packs with r
12558
13684
  | `swift-pack` | swift | 0 | 1 | 0 | Swift patterns |
12559
13685
  | `ruby-pack` | ruby | 0 | 1 | 0 | Ruby patterns |
12560
13686
 
13687
+ Every pack here is content that ships in this repository. None fetches anything
13688
+ at install time.
13689
+
13690
+ **A pack that downloads a binary has been tried once and retired.** `rtk-pack`
13691
+ (v4.18.0, removed in v4.19.0) fetched a checksum-pinned artifact in
13692
+ `scripts/init.py`, declared platform assets and digests in `plugin.json`, and
13693
+ rewrote commands at `PreToolUse`. Read
13694
+ `kb/history/completed/rtk-pack-retirement-20260727.md` before proposing another
13695
+ pack of that shape; the two defects that killed it were both invisible to
13696
+ `validate.py --strict` and to the pack's own status check.
13697
+
13698
+ What survives from that work and applies to any pack:
13699
+
13700
+ - **A pack reports its own health.** `scripts/status.py` is picked up
13701
+ generically by `plugin status`, replacing what used to be a hardcoded
13702
+ `if name == "memory-pack"` branch. A status check must distinguish *installed*
13703
+ from *working*, and prove the working part by exercising it rather than by
13704
+ checking that files exist.
13705
+ - **A pack may declare `supported_editors`.** Without it a pack installs on
13706
+ every runtime and silently does nothing on the ones it was never built for.
13707
+ - **The manifest schema tolerates extra keys.** Anything additive is unvalidated,
13708
+ so a malformed block fails at install time rather than in `validate.py
13709
+ --strict`. Do not rely on the schema to catch it.
13710
+
12561
13711
  ## Optional Hook Modules
12562
13712
 
12563
13713
  `enterprise-pack` provides two optional hook modules: