Ship-blocker found by Docker battle-test: install.sh --profile=full
only built 6/25 Rust binaries on fresh ubuntu:24.04 because
kei-artifact has sibling schemas/*.json files that src/schemas.rs
include_str!s at compile time. Prior copy_rust_primitive only copied
Cargo.toml + src/ + tests/ — schemas dir missing → 5 compile errors.
Fix: whitelist 5 common sibling data directories (schemas, assets,
templates, fixtures, migrations) — copy each if present. Keeps
target/, .git/, and other build artifacts out.
Battle-test (pre-fix):
full profile: 6/25 Rust binaries built (kei-artifact + cascade fails)
install exit 0 (soft-fail design), silent partial-install
Expected post-fix:
full profile: 25/25 binaries (pending re-verification on Docker)
Affects: any future crate that uses include_str!('../<dir>/*') on
a sibling data folder. 5-item whitelist covers the patterns we know;
extend as needed.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Three layers of defense against the dtolnay-SHA-class bug reaching main
(today's incident: agent SHA-pinned dtolnay/rust-toolchain with a pin
that was real but semantically wrong — lost 'install current stable'
meaning, locked to rust 1.94.1 branch tip, broke CI).
Layer 1 — actionlint static lint
scripts/install-actionlint.sh (65 LOC) — installs rhysd/actionlint
v1.7.12 [VERIFIED] to ~/.local/bin or suggests brew install.
scripts/lint-workflows.sh (40 LOC) — runs actionlint on
.github/workflows/*.yml, exit 0 on clean, advisory when binary
missing.
Layer 2 — SHA existence check (today's bug class)
scripts/validate-workflow-shas.sh (98 LOC) — extracts every
'uses: <repo>@<40-hex>' from workflow files + dependabot.yml,
checks each via GitHub REST commits API (exit 200/404/422).
Supports 'validate-workflow-shas: skip=<reason>' trailing
comment for intentional exceptions. Falls back to anonymous
API (60/hr quota) if GITHUB_TOKEN probe fails.
DESIGN PIVOT from spec: spec said 'git ls-remote <repo> <sha>'
but that only resolves REFS (branch/tag tips), not arbitrary
commit SHAs — would have given false-positive 100% MISSING
report. Switched to REST API /commits/{sha} for unambiguous
200/404/422.
Layer 3 — CI gate
.github/workflows/ci.yml — new 'workflow-lint' job after
shell-lint. Installs actionlint + runs both scripts on every
push to main and PR. Blocks CI on any fabricated SHA.
Layer 4 — optional pre-commit hook
scripts/pre-commit-workflow-lint.sh (54 LOC) — detects staged
.github/workflows/*.{yml,yaml} + .github/dependabot.yml
changes, runs layers 1+2, blocks commit on failure.
Install via: ln -sf ../../scripts/pre-commit-workflow-lint.sh
.git/hooks/pre-commit
REAL EXECUTION VERIFIED (not claim-only):
- actionlint ran: zero findings on current workflows
- validate-workflow-shas.sh ran: 21 SHA pins checked, 21 OK,
0 MISSING (confirms all current v0.19.1+ pins resolve)
- bash -n on every new script: clean
- bash-3.2 parser bug workaround: case-in-subshell → grep -E
RULE 0.2 exception #6 (shell is external convention for git hooks
+ GH Actions runs — Rust rewrite would add zero value).
RULE 0.13 respected — no git invocations except read-only API calls.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Same fix as ci.yml in f833a36 applied to release.yml — two more
occurrences of the SHA-pinned toolchain that locks to rust 1.94.1
branch tip.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
TWO CI failures on v0.19.1 SHA-pin commit cb45a27 traced to:
1. dtolnay/rust-toolchain SHA pin accidentally locked to rust 1.94.1
branch tip, not the stable-latest behaviour.
Validator V-2026-04-22 confirmed the pinned SHA (3c5f7ea) points at
the branch tip that added 1.94.1 patch support — functionally
equivalent to pinning a specific Rust version, not 'install stable'.
Runner image may have had newer / incompatible stable installed
system-wide; mixing caused cargo test failures.
Revert to @stable tag. Documented as explicit exception to RULE H5
(SHA-pin everything) in the line comment — dtolnay is a trusted
maintainer (serde/anyhow/cxx author), @stable is the canonical
semantic pointer for this action.
2. shell-lint job exit 1 despite continue-on-error: true on the
shellcheck step. The flag doesn't always suppress the step-level
exit code in GH Actions annotation stream when the step is the
LAST meaningful step. Add explicit '|| echo warnings' suffix to
guarantee the step exits 0 even on shellcheck findings.
Expected outcome: 3 Rust jobs + shell-lint green on next push.
ts-packages already green (they use actions/setup-node@<sha> which
resolves cleanly to v4.4.0).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Closes 2 architect audit P3 findings. MVP on the USB-droppable brain
vision — one brain directory now serves every platform.
Schema v2 — per-platform mcp_server dispatch:
[paths.mcp_server]
darwin-arm64 = 'bin/kei-mcp-server-darwin-arm64'
darwin-x64 = 'bin/kei-mcp-server-darwin-x64'
linux-x64 = 'bin/kei-mcp-server-linux-x64'
linux-arm64 = 'bin/kei-mcp-server-linux-arm64'
windows-x64 = 'bin/kei-mcp-server-windows-x64.exe'
Schema v1 (single string) still accepted — v0.19 brains load unchanged.
Implementation:
brain.rs — new McpServerPath enum (Single / PerPlatform BTreeMap<String, String>)
with #[serde(untagged)]. Brain::current_platform_key() maps std::env::consts
(macos→darwin, x86_64→x64, aarch64→arm64) to canonical key format.
mcp_server_path() now returns Result — looks up current platform,
returns Error::NoPlatformBinary { os, arch, available } if missing.
Pre-canonicalized cache field removed so partial v2 brains load for
status (just fail at actual resolve).
brain_validate.rs — validate_schema accepts MIN..=MAX range (1 or 2);
check_all_paths iterates v2 map entries for confinement check.
ClientAdapter::post_attach_hint() — default method + 4 overrides:
claude_code: 'run /help in Claude Code to verify the MCP server is reachable'
cursor: 'reload Cursor window (Cmd+Shift+P → Reload Window) to pick up the MCP server'
continue_adapter: 'reload the Continue extension in VS Code (or restart) to pick up the MCP server'
zed: 'run Zed :reload command to pick up the MCP server config'
attach.rs prints adapter.post_attach_hint() instead of the hardcoded
Claude-Code-specific string. No more client leak in orchestrator.
Error::NoPlatformBinary { os, arch, available } with thiserror Display.
Tests: 16 existing + 4 new = 20/20 pass.
- schema_v2_current_platform_resolves
- schema_v2_missing_current_platform_errors (macOS-gated)
- schema_v1_still_readable_with_v2_code
- post_attach_hint_is_adapter_specific
Constructor Pattern: all files <200 LOC (continue_adapter.rs 197 LOC
max). All fns <30 LOC (current_platform_key + check_all_paths 19 LOC max).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
bun is a monorepo tool — lockfile lives at workspace root
(_ts_packages/bun.lock), not per-subpackage. Placeholder at
_ts_packages/packages/mcp-server/bun.lock was the wrong path.
Changes:
- Generated real _ts_packages/bun.lock (626 lines) via 'bun install'
(bun 1.3.13, auto-migrated from package-lock.json)
- .github/workflows/release.yml working-directory:
_ts_packages/packages/mcp-server → _ts_packages (workspace root)
- BUILD.md Lockfile section rewritten to document workspace-root
location + coexistence with package-lock.json (L2 audit finding
partially resolved — full consolidation deferred to v0.20)
release.yml build-mcp-binary job now has real lockfile to consume —
H4 'tag build fails on missing lockfile' gate still active but now
there's something actually committed to satisfy it.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Prior README went from personal preface ('From the author') directly
into technical inventory ('What it is'). Reader gap: no value
proposition, no capability summary, no exobrain workflow docs.
Added:
- 'At a glance' — 6-bullet capability summary (agents, hooks,
skills, Rust primitives, exobrain, sleep-sync) written so a
stranger arriving from search gets the model in 30 seconds
- 'What it solves' — 7-row table mapping vanilla-Claude-Code pain
points → specific kit component that addresses each
- 'The keisei CLI' — new subsection documenting attach/mount/
detach/list-adapters/status subcommands with brain-directory
layout, 4 use cases (USB travel / team personas / cloud brain /
experimental isolation), security hardening summary, deferred
v0.19.1 items
Counts markers unchanged — no regen needed.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
4 adapter conflicts resolved by taking fix-wave version — preserves
security hardening (path confinement, name validation + collision
refuse, paths::resolve_home SSoT, fsx::write_atomic_json via
NamedTempFile). jsonmcp.rs from v0.19 refactor commit fa253d0
became dead code post-merge; deleted + unregistered from adapters/mod.rs.
If future iteration wants jsonmcp-style shared merge, it should
layer on top of the security-hardened adapter inline logic, not
replace it.
Tests: 16/16 pass (11 pre-existing + 5 new adversarial).
cargo check -p keisei: clean (4 pre-existing dead-code warnings,
not introduced by this merge).
v0.19 agent's additional factorization that wasn't captured in the
initial branch commit. Extracts shared merge/remove-named helpers
for claude-code/cursor/zed into adapters/jsonmcp.rs (70 LOC). 3
adapters simplify significantly (-65/-68/-102 LOC each).
Also: #[allow(dead_code)] on Error::AdapterFailed (surfaced by
mount/detach orchestration; reserved for library consumers).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Phase 1 of exobrain architecture. Ships TS MCP server as a static
binary so users on machines without Node can run KeiSeiKit (USB /
flashdrive / air-gapped scenarios).
.github/workflows/release.yml (+62 LOC) — new build-mcp-binary job:
- 5-target matrix: darwin arm64/x64, linux arm64/x64, windows x64
- bun build --compile, linux arm64 continue-on-error (ARM runners
less reliable)
- Artifact kei-mcp-server-<os>-<arch>[.exe] + sha256
- release job now needs [build-release, build-mcp-binary]
install/lib-rust.sh (+50 LOC) — have_prebuilt_mcp_server() +
report_mcp_server_binary_status(); KEI_SKIP_MCP_BUILD=1 env
flag skips bun/npm install when a prebuilt binary is present.
File 165 LOC (<200 limit).
_ts_packages/packages/mcp-server/package.json — scripts.build:native
+ 5 per-target aliases (macos-arm, macos-x64, linux-x64,
linux-arm, win-x64) for local dev.
_ts_packages/packages/mcp-server/BUILD.md (NEW, 52 LOC) — local
compile guide per platform + Gatekeeper/code-sign notes +
cites bun docs [VERIFIED: https://bun.sh/docs/bundler/executables].
README.md pre-built-binaries section gains 'MCP server binary'
subsection (download, chmod +x, xattr -d com.apple.quarantine for
macOS, UAC note for Windows).
CHANGELOG.md [Unreleased] bullet added.
Output size: ~90 MB per binary (bundled bun runtime). Acceptable
trade for zero-dep USB distribution.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
README: took A2 markered version over polish bundle's hardcoded counts.
Then ran scripts/regen-counts.sh to sync marker values to current
post-v0.16.1 state — BLOCKS 78 → 79 (polish added _blocks/mode-matrix.md).
regen-counts --check now exits 0: zero drift.
hooks/*.sh: take shim from A1 (gate logic lives in _lib/gate.sh)
install.sh: take dispatcher from v0.16 install-split; ported A1's
_lib-copy logic into install/lib-hooks.sh::install_hooks (since
A1 worktree was based on pre-v0.16-split main and couldn't see
the cube structure).
Gate semantics preserved — tokenized KEI_DISABLED_HOOKS, minimal
profile whitelist. Net: −171 (hooks) +104 (_lib new) +15 (lib-hooks)
= −52 LOC across kit.
Removes 9×20 LOC duplication of KEI_DISABLED_HOOKS gate logic
from each hook into hooks/_lib/gate.sh. Next CVE in gate path
fixes in ONE file, not 9.
hooks/_lib/gate.sh (new, 57 LOC) — POSIX sh library, single
kei_hook_gate() function. Exact-token tokenize on comma OR
space (RED-1 fix preserved). Minimal-profile whitelist baked
in: no-hand-edit-agents, assemble-validate, agent-fork-logger,
session-end-dump. Idempotent re-source guard.
hooks/_lib/test-gate.sh (new, 47 LOC) — 11 test cases covering
empty/comma/space/whitespace/substring-NOT-match/literal 'all'/
minimal-profile included+excluded/minimal+disabled combo.
Per-hook shim (exactly 2 LOC, same in all 9):
_KEI_LIB="$(dirname "$0")/_lib/gate.sh"
if [ -r "$_KEI_LIB" ]; then . "$_KEI_LIB"; kei_hook_gate "<name>" || exit 0; fi
Net LOC delta: −171 (hooks) +104 (lib new) +15 (installer) = −52.
Gate semantics bit-identical to v0.15.1 hotfix on the 6
enumerated behaviors; off/advisory-off profile values dropped
per spec (only 'minimal' recognized, any other = full).
Fail-open on missing lib — if _lib/gate.sh absent (old install
pre-v0.17), hook falls through to normal operation.
install.sh — +15 LOC copies hooks/_lib/*.sh to
$HOOKS_DIR/_lib/, preserving relative path the shim expects.
Note: v0.16 split this file; A1 worktree was based on pre-split
main — merge into current main required resolving conflict so
_lib-copy logic moved to install/lib-hooks.sh.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Makes KeiSeiKit installable both as classic kit AND as an
Anthropic Claude Code plugin.
.claude-plugin/plugin.json — plugin manifest (name, version,
description, author OBJECT per schema, repository, license)
.claude-plugin/marketplace.json — own marketplace declaration
(owner OBJECT per schema, plugins[].source OBJECT)
.claude-plugin/mcp-template.json — template for .mcp.json (actual
.mcp.json write is blocked by hook; user copies template manually)
PLUGIN.md — dual-install docs (plugin vs classic)
hooks/hooks.json — uses ${CLAUDE_PLUGIN_ROOT} (per Anthropic
schema, NOT ${PLUGIN_ROOT}); wraps hooks under top-level
"hooks": {...} key
Schema corrections caught during agent validation:
- marketplace.json owner MUST be object (not string)
- hooks.json requires "hooks": {...} top-level wrapper
- env var is ${CLAUDE_PLUGIN_ROOT} not ${PLUGIN_ROOT}
Companion edits in install-split bundle: install/lib-args.sh
gains an 8-line plugin-first banner in print_help() directing
users toward the plugin install path as recommended default.
Dual-install strategy: users can pick
- `claude plugin marketplace add <url>` then install — latest
and iteration-friendly (this PR enables it)
- classic ./install.sh — legacy kit path, full 37-primitive
control
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>