@softspark/ai-toolkit 4.17.0 → 4.18.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.
@@ -291,7 +291,7 @@ python3 scripts/validate.py --strict
291
291
  ai-toolkit plugin list # show available packs
292
292
  ai-toolkit plugin install --editor claude <name> # install for Claude Code global target
293
293
  ai-toolkit plugin install --editor codex <name> # install for Codex global target
294
- ai-toolkit plugin install --editor all --all # install all 11 packs for both runtimes
294
+ ai-toolkit plugin install --editor all --all # install all 12 packs for both runtimes
295
295
  ai-toolkit plugin update --editor all --all # re-apply all installed packs after toolkit updates
296
296
  ai-toolkit plugin clean <name> # prune data older than 90 days
297
297
  ai-toolkit plugin clean <name> --days 30 # custom retention
@@ -250,12 +250,17 @@ Add entry at the top of `CHANGELOG.md` (after the header, before previous releas
250
250
 
251
251
  ## Phase 4: Regenerate Artifacts
252
252
 
253
+ Use the npm scripts, not the generators directly:
254
+
253
255
  ```bash
254
- python3 scripts/generate_agents_md.py > AGENTS.md
255
- python3 scripts/generate_llms_txt.py > llms.txt
256
- python3 scripts/generate_llms_txt.py --full > llms-full.txt
256
+ npm run generate:agents # AI_TOOLKIT_NO_CUSTOM_RULES=1 python3 scripts/generate_agents_md.py > AGENTS.md
257
+ npm run generate:llms # llms.txt + llms-full.txt
257
258
  ```
258
259
 
260
+ `generate:agents` sets `AI_TOOLKIT_NO_CUSTOM_RULES=1`. Running
261
+ `generate_agents_md.py` bare picks up whatever is registered in the maintainer's
262
+ own `~/.softspark/ai-toolkit/rules/`, which then ships inside `AGENTS.md`.
263
+
259
264
  Check if anything actually changed:
260
265
 
261
266
  ```bash
@@ -0,0 +1,279 @@
1
+ ---
2
+ title: "SOP: rtk Upstream Sync"
3
+ category: procedures
4
+ service: ai-toolkit
5
+ tags: [sop, rtk, rtk-pack, upstream, cross-build, telemetry, checksum, port-validation, advisory]
6
+ version: "1.0.0"
7
+ created: "2026-07-26"
8
+ last_updated: "2026-07-26"
9
+ description: "Procedure for moving rtk-pack to a newer upstream rtk release: detect the tag, review the files we depend on, re-validate the Python port that every coverage number rests on, rebuild five targets with telemetry undefined, verify silence, publish to our own release namespace, and bump the pack. Written after the v0.43.0 to v0.44.0 bump, which changed every file this SOP names."
10
+ ---
11
+
12
+ # SOP: rtk Upstream Sync
13
+
14
+ Moves `rtk-pack` from one pinned upstream tag to the next.
15
+
16
+ Currently pinned: **v0.44.0**, shipped as
17
+ `softspark-rtk-v0.44.0-1`. The pin lives in
18
+ `app/plugins/rtk-pack/plugin.json` under `upstream.version`.
19
+
20
+ Upstream ships stable tags roughly every two to four weeks behind a long
21
+ release-candidate train (300+ RCs preceded v0.44.0). Do not track RCs.
22
+
23
+ Background and the measured numbers: `kb/history/completed/rtk-pack-integration-20260726.md`.
24
+
25
+ ## Why this SOP is not "just rebuild"
26
+
27
+ The v0.43.0 to v0.44.0 bump was 200 commits and touched **every file listed in
28
+ Phase 2 below**. It also inverted pipeline rewriting: v0.43.0 rewrote the first
29
+ stage of a pipeline, v0.44.0 rewrites the last. A rebuild without the review
30
+ step would have shipped that silently, and the coverage numbers quoted to users
31
+ would have described a version we no longer ship.
32
+
33
+ ## Phase 1: Detect
34
+
35
+ ```bash
36
+ gh api repos/rtk-ai/rtk/releases --paginate \
37
+ --jq '.[] | select(.tag_name | test("^v[0-9]")) | "\(.tag_name)\t\(.published_at)"' | head -5
38
+ ```
39
+
40
+ Compare against `upstream.version` in `app/plugins/rtk-pack/plugin.json`.
41
+
42
+ Cadence: on demand, plus a check folded into release preparation.
43
+
44
+ ## Phase 2: Review before building
45
+
46
+ Fetch the diff for the areas the pack depends on:
47
+
48
+ ```bash
49
+ gh api repos/rtk-ai/rtk/compare/<pinned>...<new> \
50
+ --jq '{ahead: .ahead_by, files: [.files[] | {f: .filename, add: .additions, del: .deletions}]}'
51
+ ```
52
+
53
+ Read the changelog, then diff these specifically:
54
+
55
+ | File | Why it matters |
56
+ |---|---|
57
+ | `src/discover/registry.rs` | rewrite eligibility, pipeline handling, the TOML call sites |
58
+ | `src/discover/rules.rs` | the rule table and `IGNORED_PREFIXES` |
59
+ | `src/discover/lexer.rs` | tokenisation and `contains_unattestable_construct` |
60
+ | `src/core/toml_filter.rs` | the filter DSL, which affects users who write their own filters |
61
+ | `src/hooks/trust.rs` | the trust gate and its paths |
62
+ | `src/hooks/hook_cmd.rs` | the Claude hook contract and permission handling |
63
+ | `src/core/telemetry.rs`, `src/core/telemetry_cmd.rs` | the compile-time endpoint gate |
64
+ | `Cargo.toml`, `Cargo.lock` | native deps, the MSRV, and new advisories |
65
+
66
+ **A change to the DSL, to trust handling, or to the permission flow is a
67
+ stop-and-think, not a rebuild.** In particular:
68
+
69
+ - The pack ships no filters of its own, so a DSL change cannot break us. It can
70
+ still break a **user's** `filters.toml`, which upstream then skips silently
71
+ (`toml_filter.rs:220-221`). Worth a release-note line, not a code change.
72
+ - If `hook_cmd.rs` changes when `permissionDecision` is emitted, the trust
73
+ boundary documented in the pack README changes with it.
74
+ - If `IGNORED_PREFIXES` or the `pipeline_final_safe` rule set moves, every
75
+ coverage number is stale.
76
+
77
+ ## Phase 3: Re-validate the port
78
+
79
+ Every coverage and saving figure the pack quotes comes from `rtk_port.py`, a
80
+ Python model of rtk's rewrite pipeline. **Anything short of full agreement
81
+ invalidates those numbers until the port is fixed.** Tooling lives in
82
+ `~/rtk-measurement-archive/`.
83
+
84
+ ```bash
85
+ git clone --depth 1 --branch <new-tag> https://github.com/rtk-ai/rtk.git /tmp/rtk-new
86
+ cd ~/rtk-measurement-archive
87
+
88
+ # Rewrite assertions from the tag's own test block.
89
+ python3 extract_cases.py /tmp/rtk-new/src/discover/registry.rs cases.json
90
+ python3 validate_port.py cases.json
91
+
92
+ # The entry gate both real hook paths apply before rewrite_command.
93
+ # Its assertions live in lexer.rs; re-extract if that block moved.
94
+ python3 -c "import entry_gate; print('gate import ok')"
95
+ ```
96
+
97
+ Baseline at v0.44.0: **203/203** rewrite assertions, **35/35** gate assertions.
98
+
99
+ If the port diverges, fix the port first, then re-measure:
100
+
101
+ ```bash
102
+ python3 measure_gated.py gated 0:1224
103
+ ```
104
+
105
+ Measure over the whole transcript pool, never the default 134-file window: at
106
+ that size the projection swings 8.8x on an unchanged mechanism.
107
+
108
+ ### 3.1 Re-measure, do not just re-validate
109
+
110
+ Port agreement proves rtk still rewrites the same commands. It says nothing
111
+ about how much each rewrite saves, and that is where the value actually sits.
112
+
113
+ **`rtk grep` carries 4.04 MB of the 5.66 MB measurable saving: 71% of the total
114
+ rests on one family.** If upstream changes that one filter, the headline number
115
+ moves even with the port at full agreement. Measured effectiveness has already
116
+ diverged from expectation in both directions once: `rtk grep` measured 22.3%
117
+ against 9.0% modelled, `rtk rg` 7.0% against 30% modelled.
118
+
119
+ So on every bump, after the port passes, replay against the newly built binary:
120
+
121
+ ```bash
122
+ python3 replay_rtk.py --pool 1300 --rtk <path-to-the-new-binary>
123
+ ```
124
+
125
+ Compare per family against the recorded baseline:
126
+
127
+ | Family | Measured at v0.44.0 | Share of total saving |
128
+ |---|---:|---:|
129
+ | `rtk grep` | 22.3% | 71% |
130
+ | `rtk git` | 33.1% | 21% |
131
+ | `rtk find` | 35.6% | 8% |
132
+
133
+ **Act on the result, do not just record it.** If the total lands below the
134
+ published kill number of **0.05% of input tokens**, the pack has stopped earning
135
+ its supply-chain surface and retiring it is the correct outcome, exactly as the
136
+ in-house filter was retired. The margin at v0.44.0 is 0.0615%, which is 1.23x
137
+ the kill number, so a single-family regression is enough to cross it.
138
+
139
+ ## Phase 4: Rebuild
140
+
141
+ ```bash
142
+ gh workflow run rtk-build.yml --ref main \
143
+ -f upstream_tag=<new-tag> -f build_revision=1 -f rust_version=<pinned> -f publish=false
144
+ ```
145
+
146
+ Five targets: `x86_64-apple-darwin`, `aarch64-apple-darwin`,
147
+ `x86_64-unknown-linux-musl`, `aarch64-unknown-linux-gnu`,
148
+ `x86_64-pc-windows-msvc`. A target that will not build is dropped, not faked.
149
+
150
+ Pin `rust_version` explicitly. Upstream uses unpinned `stable` with
151
+ `warnings = "deny"`, so a new rustc lint can turn the build red with no change
152
+ on either side.
153
+
154
+ **Advisories.** The `audit` job blocks. Re-derive the disposition rather than
155
+ carrying the previous one forward:
156
+
157
+ ```bash
158
+ # Cross-reference the new lockfile against OSV without waiting for CI.
159
+ python3 - <<'PY'
160
+ import json, re, urllib.request, pathlib
161
+ lock = pathlib.Path("/tmp/rtk-new/Cargo.lock").read_text()
162
+ pkgs = [(re.search(r'^name = "([^"]+)"', b, re.M).group(1),
163
+ re.search(r'^version = "([^"]+)"', b, re.M).group(1))
164
+ for b in lock.split("[[package]]")[1:]
165
+ if re.search(r'^name = ', b, re.M) and re.search(r'^version = ', b, re.M)]
166
+ q = [{"package": {"name": n, "ecosystem": "crates.io"}, "version": v} for n, v in pkgs]
167
+ req = urllib.request.Request("https://api.osv.dev/v1/querybatch",
168
+ data=json.dumps({"queries": q}).encode(), headers={"Content-Type": "application/json"})
169
+ res = json.load(urllib.request.urlopen(req, timeout=60))
170
+ for (n, v), r in zip(pkgs, res["results"]):
171
+ if r.get("vulns"):
172
+ print(n, v, [x["id"] for x in r["vulns"]])
173
+ PY
174
+ ```
175
+
176
+ For each advisory decide, and record the reason in the workflow:
177
+
178
+ - **In-range fix** (`cargo update -p <crate>` works): add the crate to
179
+ `RTK_CARGO_UPDATES` in `.github/workflows/rtk-build.yml`. Both the audit job
180
+ and every build job apply it, so we audit what we ship.
181
+ - **Needs a `Cargo.toml` change**: that is a source modification and breaks the
182
+ NOTICE claim. Ignore with a written reason, or escalate.
183
+ - **Carried-forward ignores**: re-check every `--ignore` still applies. The
184
+ quick-xml pair exists only because upstream pins `"0.37"`; **delete both the
185
+ moment upstream moves to 0.41 or later** rather than carrying them.
186
+
187
+ ## Phase 5: Verify
188
+
189
+ CI asserts this per target and the run fails on any `fail` verdict:
190
+
191
+ 1. `RTK_TELEMETRY_URL` and `RTK_TELEMETRY_TOKEN` unset at build time
192
+ 2. the artifact starts and reports the expected version
193
+ 3. no telemetry state written into a sandboxed home
194
+ 4. on Linux, identical behaviour with no network route
195
+ 5. every archive holds exactly one flat entry
196
+
197
+ Then check by hand:
198
+
199
+ ```bash
200
+ gh run download <run-id> --dir /tmp/rtk-verify
201
+ cd /tmp/rtk-verify && shasum -a 256 -c checksums.txt
202
+ ```
203
+
204
+ Three things CI cannot tell you:
205
+
206
+ - **`strings` markers.** Compare `tls_markers_present` per target against the
207
+ previous build. Their absence is an LLVM outcome under LTO, not a guarantee,
208
+ so a sudden appearance means the telemetry stack survived and is worth
209
+ understanding before shipping.
210
+ - **Reproducibility.** Four of five targets are bit-reproducible; compare
211
+ extracted binaries, never the tarballs, because gzip records a timestamp.
212
+ `x86_64-pc-windows-msvc` differs by 24 bytes per link (MSVC timestamp plus a
213
+ CodeView GUID), so a changed Windows digest proves nothing on its own.
214
+ - **Upstream has no test asserting network silence.** That property can regress
215
+ on any bump without turning their CI red. We own it.
216
+
217
+ ## Phase 6: Publish
218
+
219
+ ```bash
220
+ gh workflow run rtk-build.yml --ref main \
221
+ -f upstream_tag=<new-tag> -f build_revision=1 -f rust_version=<pinned> -f publish=true
222
+ ```
223
+
224
+ Creates `softspark-rtk-<upstream>-<revision>`. Bump the revision, not the
225
+ upstream part, when rebuilding the same upstream tag.
226
+
227
+ ## Phase 7: Bump the pack
228
+
229
+ In `app/plugins/rtk-pack/plugin.json`:
230
+
231
+ - `upstream.version` to the new tag
232
+ - `binary.release_tag` to the new release
233
+ - every `assets.*.sha256` from the published `checksums.txt`
234
+ - `version` (the pack's own) — bump it whether or not the upstream tag moved,
235
+ because `plugin update` skips a pack whose recorded version still matches, so
236
+ an unbumped pack never reaches installed users. The two fields are separate so
237
+ a pack-only fix does not pretend to be an upstream bump
238
+
239
+ Then:
240
+
241
+ ```bash
242
+ npm test # tests/test_rtk_pack.bats asserts digest shape and layout
243
+ python3 scripts/validate.py --strict
244
+ python3 scripts/audit_skills.py --ci
245
+ shellcheck --severity=warning app/hooks/*.sh app/plugins/*/hooks/*.sh
246
+ ```
247
+
248
+ Verify a real install end to end, against the published release rather than a
249
+ mirror:
250
+
251
+ ```bash
252
+ H=$(mktemp -d)
253
+ AI_TOOLKIT_DATA_DIR="$H" python3 app/plugins/rtk-pack/scripts/init.py
254
+ AI_TOOLKIT_DATA_DIR="$H" python3 app/plugins/rtk-pack/scripts/status.py
255
+ ```
256
+
257
+ ## Phase 8: Ship
258
+
259
+ The pack version bump reaches installed users through `ai-toolkit update`,
260
+ which runs `plugin update --editor all --all`. A pack whose recorded version
261
+ matches its manifest is skipped silently, so the bump in Phase 7 is what makes
262
+ the update fire at all. Forgetting it means nobody gets the new binary.
263
+
264
+ ## Phase 9: Record the licence position
265
+
266
+ If upstream relicenses, adds a `NOTICE`, or changes `DISCLAIMER.md`, update the
267
+ NOTICE generated in `.github/workflows/rtk-build.yml`. It currently records two
268
+ build-time differences: the undefined telemetry endpoint, and in-range lockfile
269
+ security updates. If Phase 4 changes that set, the NOTICE text changes with it.
270
+
271
+ `DISCLAIMER.md` is deliberately not redistributed because it states telemetry is
272
+ collected by default, which our builds contradict. Re-check that this is still
273
+ the reason before changing the decision.
274
+
275
+ ## Related
276
+
277
+ - `kb/history/completed/rtk-pack-integration-20260726.md` — decisions, measurements, pre-mortem
278
+ - `kb/history/completed/output-filter-retirement-20260726.md` — why premise validation comes first
279
+ - `app/plugins/rtk-pack/README.md` — the user-facing trust boundary
@@ -58,7 +58,7 @@ Usage: ai-toolkit <command> [options]
58
58
  |---------|-------------|
59
59
  | `plugin list` | Show available plugin packs with install status |
60
60
  | `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) |
61
- | `plugin install --all [--editor claude\|codex\|all]` | Install all 11 plugin packs |
61
+ | `plugin install --all [--editor claude\|codex\|all]` | Install all 12 plugin packs |
62
62
  | `plugin update <name> [--editor claude\|codex\|all]` | Update a plugin pack (remove + reinstall, preserves data) |
63
63
  | `plugin update --all [--editor claude\|codex\|all]` | Update all installed plugin packs |
64
64
  | `plugin clean <name> [--days N]` | Prune old plugin data (default: 90 days) |
@@ -72,10 +72,10 @@ Required keys:
72
72
  ## CLI Management
73
73
 
74
74
  ```bash
75
- ai-toolkit plugin list # show all 11 packs with install status
75
+ ai-toolkit plugin list # show all 12 packs with install status
76
76
  ai-toolkit plugin install --editor claude <name> # Claude Code global target
77
77
  ai-toolkit plugin install --editor codex <name> # Codex global target
78
- ai-toolkit plugin install --editor all --all # install all 11 packs for both runtimes
78
+ ai-toolkit plugin install --editor all --all # install all 12 packs for both runtimes
79
79
  ai-toolkit plugin update --editor all --all # update all installed packs
80
80
  ai-toolkit plugin clean <name> # prune data older than 90 days (default)
81
81
  ai-toolkit plugin clean <name> --days 30 # prune data older than 30 days
@@ -138,6 +138,21 @@ ai-toolkit plugin status --editor all # show installed packs with r
138
138
  | `kotlin-pack` | kotlin | 0 | 1 | 0 | Kotlin patterns |
139
139
  | `swift-pack` | swift | 0 | 1 | 0 | Swift patterns |
140
140
  | `ruby-pack` | ruby | 0 | 1 | 0 | Ruby patterns |
141
+ | `rtk-pack` | token-reduction | 0 | 0 | 1 | Command rewriting via a checksum-pinned rtk binary fetched at install |
142
+
143
+ `rtk-pack` is the first pack to break three assumptions the others share, so it
144
+ is the one to read when extending the contract:
145
+
146
+ - **It fetches from the network at install time.** `scripts/init.py` downloads a
147
+ platform-specific artifact and verifies its SHA-256 against `plugin.json`
148
+ before installing anything. A mismatch aborts and leaves nothing behind.
149
+ - **It declares platform assets and digests in `plugin.json`.** The manifest
150
+ schema tolerates extra keys, so `upstream` and `binary` are additive; nothing
151
+ validates them, which means a malformed block fails at install rather than in
152
+ `validate.py --strict`.
153
+ - **It reports its own health.** `scripts/status.py` is picked up generically by
154
+ `plugin status`, replacing what used to be a hardcoded `if name == "memory-pack"`
155
+ branch. Any pack can now ship one.
141
156
 
142
157
  ## Optional Hook Modules
143
158