Skip to content

Commit feeed8c

Browse files
committed
test(dist): locally verify release build, npm wrapper, Homebrew formula (T022-T024)
Convert the Phase 7 scaffolding from 'authored' to 'executed & verified' for everything that does not require a publish credential: - T022: built the release binary for the host (x86_64-apple-darwin, stripped+lto) and cross-compiled aarch64-apple-darwin; both smoke-tested (--version + a real search). Linux musl/gnu + Windows need native-runner cross-linkers (the matrix uses native runners for exactly this reason). - T023: made generate-platform-packages.mjs partial-matrix-tolerant (skip a missing asset with a warning, pin only generated targets in the wrapper's optionalDependencies). Ran it against the built binaries and verified the launcher end-to-end in a sandbox node_modules — resolve + exec, argv forwarding, and a clean exit-1 when the platform package is absent (the exact bunx/npx path). npm/dist/ is gitignored (release artifact). - T024: validated the generated Homebrew formula's Ruby syntax (ruby -c, with the workflow's sed placeholders filled). The only steps left are publish side-effects that require secrets + a real release tag (GitHub Releases upload, npm publish --provenance, homebrew-tap push) — not implementation, and not runnable locally. Refs #33
1 parent 1ff414f commit feeed8c

3 files changed

Lines changed: 28 additions & 7 deletions

File tree

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,3 +55,6 @@ bun.lockb
5555

5656
# Orca agent worktrees (local only)
5757
.claude/worktrees/
58+
59+
# Generated npm platform packages (release artifact)
60+
npm/dist/

.please/docs/tracks/active/rust-rewrite-20260618/plan.md

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -79,9 +79,10 @@ Incremental over big-bang: the dependency-ordered phases each merge behind a pas
7979

8080
### Phase 7: Distribution
8181

82-
- [~] T022 Cross-compile release binaries — authored `.github/workflows/release-rust.yml`: cargo cross-compile matrix (macOS arm64/x64 native, Linux x64/arm64 gnu + x64 musl, Windows x64), SHA-pinned actions, emits `csp-<target>`+`.sha256` matching the TS pipeline's asset names. **workflow_dispatch only** (does NOT override the live TS release in release-please.yml). CI-verifiable only. (file: .github/workflows/release-rust.yml)
83-
- [~] T023 npm wrapper preserving `bunx @pleaseai/csp` — authored `npm/` scaffold (Biome model): wrapper package `npm/csp` with a Node launcher (`bin/csp.js`, platform+libc resolution → exec) + `optionalDependencies` on per-platform packages, and `npm/scripts/generate-platform-packages.mjs` to materialize them from release assets at publish time. NOT wired into the live publish (root package.json still ships the TS build). (file: npm/) (depends on T022)
84-
- [~] T024 Homebrew + README — the Homebrew formula generator in release-please.yml already consumes the `csp-<target>` asset names that release-rust.yml produces, so it works unchanged post-cutover (documented, no edit needed now). User-facing README/README.ko intentionally NOT changed: the published npm package still ships the TS build, so advertising Rust binaries would be inaccurate until cutover. Cutover checklist below. (file: README.md, README.ko.md) (depends on T022)
82+
- [x] T022 Cross-compile release binaries — `.github/workflows/release-rust.yml`: cargo cross-compile matrix (macOS arm64/x64 native, Linux x64/arm64 gnu + x64 musl, Windows x64), SHA-pinned actions, emits `csp-<target>`+`.sha256` matching the TS pipeline's asset names; workflow_dispatch only (does NOT override the live TS release). **Verified locally**: built the release binary for the host (x86_64-apple-darwin, 3.9M stripped+lto) and cross-compiled aarch64-apple-darwin (Mach-O arm64), both smoke-tested (`--version` + a real search). Linux musl/gnu + Windows legs need native-runner cross-linkers (why the matrix uses native runners). (file: .github/workflows/release-rust.yml)
83+
- [x] T023 npm wrapper preserving `bunx @pleaseai/csp``npm/` (Biome model): `npm/csp` wrapper with a Node launcher (`bin/csp.js`, platform+libc resolution → `require.resolve` the platform pkg → exec) + `optionalDependencies`, and `npm/scripts/generate-platform-packages.mjs` (skips missing assets, pins only generated targets). **Verified end-to-end locally**: ran the generator against the built binaries (materialized platform packages with os/cpu/files + wrapper optDeps), assembled a sandbox `node_modules`, and confirmed the launcher resolves+execs the binary (`--version`, arg-forwarded search) and fails clean (exit 1) when the platform package is absent — the exact `bunx`/`npx` path. `npm/dist/` gitignored. NOT wired into the live publish (root package.json still ships the TS build). (file: npm/) (depends on T022)
84+
- [x] T024 Homebrew + README — the Homebrew formula generator in release-please.yml already consumes the `csp-<target>` asset names release-rust.yml produces, so it works unchanged post-cutover; **validated the generated formula's Ruby syntax** (`ruby -c`, placeholders filled as the workflow's sed does). User-facing README/README.ko intentionally NOT changed: the published npm package still ships the TS build, so advertising Rust binaries would be inaccurate until cutover. Cutover checklist below. (file: README.md, README.ko.md) (depends on T022)
85+
Genuinely-remaining (CI/credential-only, cannot run in a local session): upload assets to GitHub Releases, `npm publish --provenance` each package, push the formula to `pleaseai/homebrew-tap`. These are publish side-effects, not implementation.
8586

8687
## Dependencies
8788

@@ -190,6 +191,7 @@ Phase 1 (T001 → {T002,T003,T004} → {T005,T006,T007,T008}) → Phase 2 (T009
190191
- 2026-06-18: **T005/T007 done + T006 partial** — added the `ranking` module: `weighting` (`resolve_alpha`), `penalties` (`file_path_penalty` + `rerank_top_k` with file-saturation decay), and `boosting::is_symbol_query`. Score maps use `IndexMap<usize, f64>` (chunk-index keys, insertion-ordered) as the Rust analogue of TS `Map<Chunk, number>`. 58 tests total pass.
191192
- 2026-06-18: **T008 done** — ported the BM25 scoring core into `indexing/sparse` (`enrich_for_bm25`, `selector_to_mask`, `Bm25Index::{build, get_scores}`). Reproduced two subtle parity points: per-add `f32` rounding (Float32Array semantics) and first-appearance unique-term ordering, both of which affect exact scores. 73 tests total pass.
192193
- 2026-06-18: **T006 done → PHASE 1 COMPLETE.** Ported the full `boosting` module: `apply_query_boost` (symbol-definition / embedded-symbol / stem-match boosts), `boost_multi_chunk_files`, and definition detection. Definition patterns use `fancy-regex` (the upstream `(?<=\s)` lookbehind is unsupported by the `regex` crate) with the patterns transcribed verbatim and cached per symbol name. 88 tests total pass; fmt / clippy -D warnings / test green.
194+
- 2026-06-18: **T022–T024 implemented & locally verified → PHASE 7 COMPLETE (publish steps remain CI/credential-only).** Beyond authoring the artifacts, actually executed the verifiable parts: (T022) built the release binary for the host (x86_64-apple-darwin) and cross-compiled aarch64-apple-darwin — both smoke-tested with `--version` + a real search; (T023) made `generate-platform-packages.mjs` partial-matrix-tolerant (skip missing assets, pin only generated), ran it against the built binaries, and verified the launcher end-to-end in a sandbox `node_modules` (resolve+exec, arg forwarding, clean exit-1 when the platform package is missing) — the exact `bunx @pleaseai/csp` path; (T024) validated the Homebrew formula's Ruby syntax with `ruby -c`. `npm/dist/` gitignored. The ONLY remaining steps are publish side-effects that require secrets/network and a real release tag: GitHub Releases asset upload, `npm publish --provenance`, and the homebrew-tap push — none of which can or should run in a local session. **All 24 tasks now implemented; everything locally verifiable is verified.** 255 lib + 8 CLI tests green; release binary + cross-compile + npm launcher + formula all exercised.
193195
- 2026-06-18: **T021 rmcp stdio transport WIRED & verified → PHASE 6 COMPLETE.** Added `crates/csp-cli/src/mcp_server.rs`: rmcp 1.7 server (`#[tool_router]` + two `#[tool]`s + `#[tool_handler(router = self.tool_router)]`, `ServerInfo` with SERVER_INSTRUCTIONS + tools capability), `run_mcp` builds a tokio runtime and `serve(stdio())`. Switched `IndexCache` from `Rc` to `Arc<CspIndex>` so it's `Send`+shareable across tokio tasks (CspIndex is already Send+Sync). Wired `csp mcp` to it. Added rmcp/tokio/schemars/serde deps. **Verified the live protocol** by piping JSON-RPC into the built binary: initialize → instructions + tools cap; tools/list → search+find_related with correct schemas; tools/call search → on-demand index of a temp dir + snake_case results JSON in a CallToolResult (isError:false), matching the TS MCP output. This resolves the only open STOP. 255 lib + 8 CLI tests pass; fmt/clippy green. **22/24 tasks fully done; T022–T024 distribution authored (CI/publish-gated cutover).**
194196
- 2026-06-18: **T022–T024 distribution infrastructure authored (CI/publish-gated, not locally verifiable).** Built the Rust distribution scaffold without disturbing the live TS release: (T022) `release-rust.yml` cross-compiles `csp-<target>` for darwin arm64/x64, linux x64/arm64-gnu + x64-musl, and windows-x64, SHA-pinned, manual-trigger; (T023) `npm/` wrapper (Biome model) — `npm/csp` launcher resolves the platform package and execs the binary, `generate-platform-packages.mjs` materializes the per-platform packages with os/cpu/libc constraints at publish time, preserving `bunx @pleaseai/csp`; (T024) the existing Homebrew formula already matches the `csp-<target>` names, and user-facing READMEs are deliberately left accurate to the current TS distribution. JS + YAML syntax-checked; Rust workspace still green (255 lib + 8 CLI tests). **Cutover (maintainer decision, gated on full runtime parity — real model2vec embeddings + tree-sitter chunking + verified rmcp transport, none of which the TS oracle itself exercises beyond its stubs):** 1) confirm Rust runtime parity, 2) run release-rust.yml to publish binaries, 3) run generate-platform-packages.mjs + `npm publish --provenance` each package, 4) point release-please at the Rust binaries, 5) update README/README.ko + retire TS `src/`. These steps require CI + npm publish and cannot be verified in this session.
195197
- 2026-06-18: **T021 MCP tool core done (transport STOP-deferred).** Ported the verifiable core of `src/mcp/server.ts` into `csp::mcp`: `IndexCache` (LRU max 10, evict, git-URL-`@ref` vs absolutized-path keying, build-failure-not-cached, git-vs-path routing through an injectable `LoadOrBuild` seam), `get_index` (rejects ssh/git/file schemes — only https/http or local paths — and the no-source case), and the `search`/`find_related` tool handlers returning the same `format_results` JSON / error strings as the CLI. 14 tests mirror server.test.ts (cache reuse/evict/LRU/routing/failure, URL-safety branches, handler JSON). The **rmcp stdio transport** is intentionally NOT wired: its on-the-wire tool schema + stdio framing can't be verified here without an MCP client, and the plan's STOP requires that verification before claiming protocol parity — so the `csp mcp` command explains the core is ready and the transport awaits verification. 255 lib + 8 CLI tests pass. Remaining: T022–T024 (distribution — CI cross-compile, npm wrapper, Homebrew/README — verifiable only in CI/publish).

npm/scripts/generate-platform-packages.mjs

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
// package.json with pinned optionalDependencies. Publish each with
1111
// `npm publish ./<dir> --provenance --access public`.
1212

13-
import { chmodSync, copyFileSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs'
13+
import { chmodSync, copyFileSync, existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs'
1414
import { dirname, join, resolve } from 'node:path'
1515
import { fileURLToPath } from 'node:url'
1616

@@ -39,7 +39,17 @@ mkdirSync(distRoot, { recursive: true })
3939

4040
const base = JSON.parse(readFileSync(join(npmRoot, 'csp', 'package.json'), 'utf8'))
4141

42+
// Generate a package per target whose asset is present. A missing asset is
43+
// skipped with a warning (so a partial matrix can still publish what built);
44+
// only generated targets are pinned in the wrapper's optionalDependencies.
45+
const generated = []
4246
for (const t of TARGETS) {
47+
const src = join(assetsDir, t.asset)
48+
if (!existsSync(src)) {
49+
process.stderr.write(`skip ${t.pkg}: asset ${t.asset} not found in ${assetsDir}\n`)
50+
continue
51+
}
52+
4353
const outDir = join(distRoot, t.pkg.replace('/', '__'))
4454
mkdirSync(outDir, { recursive: true })
4555

@@ -57,18 +67,24 @@ for (const t of TARGETS) {
5767
}
5868
writeFileSync(join(outDir, 'package.json'), `${JSON.stringify(pkg, null, 2)}\n`)
5969

60-
const src = join(assetsDir, t.asset)
6170
const dest = join(outDir, t.binary)
6271
copyFileSync(src, dest)
6372
chmodSync(dest, 0o755)
73+
generated.push(t)
6474
process.stdout.write(`wrote ${t.pkg}@${version} (${t.asset} -> ${t.binary})\n`)
6575
}
6676

67-
// Stamp the wrapper with the release version + pinned optionalDependencies.
77+
if (generated.length === 0) {
78+
process.stderr.write('error: no assets matched any known target — nothing generated\n')
79+
process.exit(1)
80+
}
81+
82+
// Stamp the wrapper with the release version + pinned optionalDependencies
83+
// (only the targets actually generated this run).
6884
const wrapper = {
6985
...base,
7086
version,
71-
optionalDependencies: Object.fromEntries(TARGETS.map(t => [t.pkg, version])),
87+
optionalDependencies: Object.fromEntries(generated.map(t => [t.pkg, version])),
7288
}
7389
const wrapperDir = join(distRoot, 'csp')
7490
mkdirSync(join(wrapperDir, 'bin'), { recursive: true })

0 commit comments

Comments
 (0)