v0.1 W2: ClaudeCodeSurface install/uninstall + settings.json merge#10
Conversation
Implements W2 of the v0.1 milestone (#2). Per design.md §3.1, §5, §7: - klasp-agents-claude::hook_template — three-line bash shim with the `# klasp:managed` marker and `KLASP_GATE_SCHEMA=N` export, parameterised by schema version so the contract test (W3) can verify the binary and fixture stay in lockstep. - klasp-agents-claude::settings — surgical serde_json-based merge that preserves every sibling key in `.claude/settings.json`, idempotent on exact `command` match. Inverse `unmerge_hook_entry` cleans up empty matchers so install→uninstall round-trips a fresh repo to `{}`. - klasp-agents-claude::surface — `ClaudeCodeSurface` impl of `klasp_core::AgentSurface`. Marker-comment idempotency check, atomic writes via tempfile, chmod 0o755 (Unix). `MarkerConflict` unless `--force` when an existing hook lacks our marker. - klasp/src/registry — `SurfaceRegistry` with `ClaudeCodeSurface` pre-registered. Lives in the binary crate so klasp-core stays free of per-agent dependencies (relevant for the v0.3 plugin model). - klasp/src/cmd/install + uninstall — CLI wiring per design §5: filter surfaces by `--agent` and `detect()`, build `InstallContext`, dispatch. Tests: - 19 unit tests in settings.rs (empty-input, sibling preservation, idempotency, malformed JSON, shape errors, uninstall round-trip) - 3 integration tests against a realistic fallow-shaped fixture (proves fallow's hook entry survives byte-for-byte) - 8 integration tests in klasp/tests/install_claude_code.rs (install, re-install no-op, dry-run, sibling preservation, marker-conflict refusal, --force overwrite, uninstall, uninstall dry-run) - 2 insta snapshot tests of the rendered hook script (v1 + v7 to exercise schema-version interpolation) The hook entry's `command` resolves `${CLAUDE_PROJECT_DIR}` at hook execution time, so the same `.claude/settings.json` works regardless of the cwd Claude is invoked from. Closes #2.
Addresses both findings from the code-review of #10 and the subsequent /simplify sweep, per agentic-flow steps 07/09/10. Review fixes: - atomic_write now preserves the destination's prior Unix mode rather than silently downgrading .claude/settings.json to NamedTempFile's 0o600 default. Fresh files default to 0o644; the hook script continues to land at 0o755. Three new tests in install_claude_code.rs pin the mode contract. - Tighten the doc-comment on unmerge_hook_entry to admit the round-trip caveat: a pre-existing {matcher: "Bash", hooks: []} placeholder is also dropped because we can't recover provenance. The corresponding inline comment now points at the doc. /simplify pass: - atomic_write takes mode: u32 instead of Option<u32>; the None branch was dead. - get_or_insert_object / get_or_insert_array harmonised to (map, key, path) — eliminates the asymmetric "path derived from key" rule. - JSON-schema keys ("hooks", "PreToolUse", "matcher", "Bash", "type", "command") are now module-level constants in settings.rs. Stops a typo from becoming a silent no-op. - Drop a few comments that restated obvious behaviour (HookState variant docs, narrating-the-change comments in tests, POSIX rationale on a single-line newline assertion, task-reference in the fallow-fixture test header). cargo fmt + clippy --workspace -- -D warnings + cargo test --workspace all green: 70 passing, 1 ignored (W1's documented git-dash-c case).
Code reviewRan SummaryImplements W2 of klasp v0.1 per docs/design.md §3.1, §5, §7. CLAUDE.md compliance was checked against the parent Critical issues
Suggestions
What looks good
VerdictApprove with minor notes. Both suggestions are scoped follow-ups, not blockers. Both fixed in 06f448b — see remediation comment below. 🤖 Generated with Claude Code |
Review remediation + simplify passBoth review findings fixed in 06f448b, plus a Review-handoff fixes
|
The original `[#1, W1]` ... `[#5, W5]` brackets were issue numbers, not PR numbers, and GitHub auto-link rules resolve `#N` to PRs first — readers landing on those refs got the build-plan issue rather than the merge commits. Replace with the actual PR numbers, sourced from the merge log: W1 → no PR (direct push to main; reference the SHA `5740eb3` instead) W2 → #10 W3 → #11 W4 → #13 W5 → #15 W6-7 → #17 (this PR) The W3 follow-ups merge `[#14]` was already correct. Co-Authored-By: claude-flow <ruv@ruv.net>
* feat(dogfood): install klasp gate on klasp's own repo Run `klasp init` + `klasp install --agent claude_code` against this worktree and commit the resulting gate files so worktrees and remote agents inherit the install. The klasp.toml is the canonical v0.1 reference example: cargo check + cargo clippy -D warnings on commit+push, cargo test --workspace on push only. .gitignore gains scoped un-ignore rules (`!/.claude/settings.json`, `!/.claude/hooks/klasp-gate.sh`) so only the repo-root .claude/ gate artifacts are tracked; subdirectory .claude/ folders (e.g. agent worktree state) stay ignored. Verified end-to-end: - clean tree: gate exits 0, no stderr - clippy-failing change: gate exits 2, structured findings on stderr - klasp install run twice = no diff (idempotent) - klasp doctor: all checks passed Closes the W6 dogfood deliverable from #6. * docs(recipes): add v0.1 recipes guide Worked klasp.toml [[checks]] blocks for the six tools the v0.1 launch demo and most users will actually run: pre-commit, fallow, pytest, cargo, ESLint/Biome, ruff. Each recipe is paired with two-three sentences on why the chosen flags / triggers fit a klasp gate. The Patterns section up top covers the cross-cutting decisions every config faces — commit vs push triggers, ${KLASP_BASE_REF} usage, monorepo limitations (full discovery is a v0.2.5 deliverable per design.md §14 and roadmap.md §v0.2.5), and fail-open semantics. The "What's next" section points at v0.2's named recipes (`type = "pre_commit"`, `type = "fallow"`, etc.) so users adopting verbose v0.1 shell shapes know the upgrade path. Part of #6. * docs(release): v0.1.0 changelog + flip README placeholder Add CHANGELOG.md with the v0.1.0 entry enumerating every closed v0.1-labelled issue (W1 #1, W2 #2, W3 #3 + follow-ups #14, W4 #4 + follow-up #13, W5 #5 + follow-up #15, and W6-7 #6) under a single release heading. The Out-of-scope section restates v0.2+ deferrals (Codex, named recipes, parallel execution, monorepo discovery) so the launch story is honest about what users get and what they don't. README.md gets three small updates: status flips from "name-reservation placeholder" to "v0.1 ships when v0.1.0 tag is pushed" with a caveat that a `klasp --version` check is the right way to confirm a real install (vs the lingering 0.0.0 placeholder); the docs list grows pointers to recipes.md and CHANGELOG.md; and the repository-layout table drops the *(planned)* qualifiers now that all three crates ship. The user pushes the v0.1.0 tag from main themselves; this commit only prepares the changelog + README so the release is documented when they do. Closes #6. * feat(gate): set KLASP_BASE_REF env var for shell checks Threads a merge-base ref through `RepoState` and exports it as `KLASP_BASE_REF` on every `ShellSource` child. Computes the value via `git merge-base @{upstream} HEAD`, falling back to `origin/main`, `origin/master`, then `HEAD~1` — the canonical "branch divergence point" lookup for diff-aware tools. This matches the contract design.md §3.5 already commits to and the `klasp.toml` / docs/recipes.md examples already reference. Without it, copying the recipes (`pre-commit run --from-ref ${KLASP_BASE_REF}`, `fallow audit --base ${KLASP_BASE_REF}`) silently substitutes empty strings and the diff-aware tools lint the entire tree. Wiring: - klasp-core: `RepoState` gains a `base_ref: String` field. Plugins read it directly; the gate runtime constructs it. - klasp/git.rs: new `compute_base_ref()` helper with three-level fallback chain. Two unit tests against a real `tempdir` git repo cover the no-remote (`HEAD~1`) and clone-with-upstream paths. - klasp/sources/shell.rs: spawn-time `.env("KLASP_BASE_REF", ...)`. New unit test asserts a child running `printf "$KLASP_BASE_REF"` echoes the configured value. - klasp/tests/gate_flow.rs: end-to-end test spawns the binary against a real two-commit git repo, runs a check that writes `$KLASP_BASE_REF` to a sentinel file, asserts the captured value is the expected `HEAD~1` fallback. Touches `klasp/src/sources/shell.rs` (locked file in the W6-7 brief) because the env-var injection is precisely what the gate was designed to hand off to the source — implementing it elsewhere would route around the only `Command::new(...)` call in v0.1. The same file was already touched post-W3 lock by #14 for child-process cleanup. Refs: docs/design.md §3.5, docs/recipes.md §`${KLASP_BASE_REF}`. Co-Authored-By: claude-flow <ruv@ruv.net> * docs(changelog): correct PR references for v0.1.0 entries The original `[#1, W1]` ... `[#5, W5]` brackets were issue numbers, not PR numbers, and GitHub auto-link rules resolve `#N` to PRs first — readers landing on those refs got the build-plan issue rather than the merge commits. Replace with the actual PR numbers, sourced from the merge log: W1 → no PR (direct push to main; reference the SHA `5740eb3` instead) W2 → #10 W3 → #11 W4 → #13 W5 → #15 W6-7 → #17 (this PR) The W3 follow-ups merge `[#14]` was already correct. Co-Authored-By: claude-flow <ruv@ruv.net> * docs(recipes): defer verdict_path mention to v0.2 (not in v0.1 schema) The previous wording claimed "the ConfigV1 schema reserves the `verdict_path` field for this transition; ignore it for now." Reality: `klasp_core::CheckSourceConfig::Shell` has no such field — a user who copy-pasted `verdict_path = "..."` into their klasp.toml hit a serde parse error rather than a "this is reserved, not yet implemented" no-op. Reword to honestly defer JSON-output parsing to v0.2's named recipes (`type = "fallow"`, `type = "pytest"`) and tell the user to fall back on the check tool's exit code in v0.1. Co-Authored-By: claude-flow <ruv@ruv.net> --------- Co-authored-by: claude-flow <ruv@ruv.net>
Summary
klasp-agents-claudenow ships the realClaudeCodeSurfaceimpl per docs/design.md §3.1, §5, §7: hook-script template, surgical settings.json merge, idempotent install/uninstall.klasp installandklasp uninstallare wired through a newSurfaceRegistryin the binary crate.Closes
Closes #2.
What this delivers (issue #2 checklist)
klasp-agents-claudecrate skeleton; depends only onklasp-core(plus serde_json/tempfile/thiserror as documented in design §12)klasp-agents-claude/src/hook_template.rs—# klasp:managedmarker +KLASP_GATE_SCHEMA=Nexport +exec klasp gate "$@"klasp-agents-claude/src/settings.rs— surgical JSON merge preserving every sibling key, idempotent on exactcommandmatchklasp-agents-claude/src/surface.rs—ClaudeCodeSurfaceimpl ofAgentSurfacewithMarkerConflictunless--forceklasp/src/cmd/install.rs+klasp/src/cmd/uninstall.rswired throughSurfaceRegistryinstasnapshot of the rendered hook script (v1 + v7 to exercise schema-version interpolation)Decisions worth flagging for review
command—${CLAUDE_PROJECT_DIR}/.claude/hooks/klasp-gate.sh(envar-resolved). Design didn't pin this; documented inklasp-agents-claude/src/surface.rs.SurfaceRegistrylocation — lives in theklaspbinary crate, not inklasp-core, to keep core free of per-agent crate deps. Design §5'sSurfaceRegistry::default()example is binary-side.Bashmatcher with an emptyhooksarray, we drop the matcher entry too (so install→uninstall round-trips a fresh repo to{}). Untouched non-Bash matchers and Bash matchers that started with sibling hooks are preserved.tempfile::NamedTempFilein the same dir +persist. Avoids torn writes on settings.json (the highest-risk file).chmod 0o755is unix-only; no-op on Windows per docs/design.md §14 3rd bullet (audited at W4).Smoke test (manual)
Test plan
cargo build --workspace(verified locally)cargo fmt --all -- --check(verified locally)cargo clippy --all-targets --workspace -- -D warnings(verified locally)cargo test --workspace— 67 passing (was 35 before this PR), 1 ignored (W1's documented git-dash-c case)cargo check + fmt + clippy,build / macos-14,build / ubuntu-latest,build / windows-latest,npm publish --dry-run,pypi build + twine check/code-review:code-reviewper the agentic-flow plan — settings.json merge is the highest-risk module of v0.1 (design §5 + §14)Out of scope
klasp gateruntime (the hook scriptexecs intoklasp gate, which still prints "not yet implemented" — that's the next issue, [v0.1 W3] klasp gate runtime + trigger + Shell CheckSource #3).serde_json::Valuenormalises key order; the merge tests verify content but not byte-for-byte ordering. File a follow-up if review surfaces real-world drift.chmodaudit (design §14 3rd bullet, scheduled for W4).🤖 Generated with Claude Code