Skip to content

feat(init): --adopt flow detects + mirrors existing gates (closes #97)#101

Merged
liam-ai-reality merged 5 commits into
mainfrom
feat/init-adopt-97
May 8, 2026
Merged

feat(init): --adopt flow detects + mirrors existing gates (closes #97)#101
liam-ai-reality merged 5 commits into
mainfrom
feat/init-adopt-97

Conversation

@liam-ai-reality
Copy link
Copy Markdown
Contributor

Summary

Acceptance criteria coverage

All 9 AC items from #97 covered with integration tests in klasp/tests/init_adopt.rs:

  • --mode inspect prints plan, writes nothing
  • --mode mirror writes klasp.toml without modifying user config
  • mirror never touches .git/hooks, .husky/, lefthook.yml, .pre-commit-config.yaml
  • --mode chain rejected with explanatory message (exit 2)
  • pre-commit detector emits type = "pre_commit"
  • Husky detector picks pkg-manager-aware lint-staged command via lockfile sniff
  • Lefthook detector emits per-run: shell checks
  • plain .git/hooks never overwritten by default
  • klasp doctor surfaces missing pre-commit on PATH

Architecture

  • klasp/src/adopt/plan.rs — shared AdoptionPlan / DetectedGate API
  • klasp/src/adopt/detect_*.rs — one detector per gate type, all returning io::Result<Vec<DetectedGate>>
  • klasp/src/adopt/render.rs — plan formatter (matches issue's example output)
  • klasp/src/adopt/writer.rs — atomic klasp.toml generator with round-trip parse validation
  • klasp/src/adopt/mode.rs — chain-mode rejection message
  • docs/adopt.md — user-facing doc

Test plan

  • cargo build --workspace clean (4 dead-code warnings only — ChainSupport::AutoSafe and GateDetector trait reserved for chain-mode future work)
  • cargo test --workspace: 647 passed, 6 ignored, 0 failed
  • 10 integration tests in init_adopt.rs cover all ACs
  • ~70 unit tests across detectors, render, writer, mode

🤖 Generated with Claude Code

liam-ai-reality and others added 3 commits May 8, 2026 09:05
Implements #97. `klasp init --adopt --mode <mode>` detects
existing quality gates (pre-commit framework, Husky, Lefthook, plain
.git/hooks, lint-staged) and either prints a plan (inspect), writes a
mirroring klasp.toml (mirror), or rejects with a clear "not yet
supported" message (chain).

- Adds non-destructive detectors for each supported gate. Hook configs
  are NEVER modified by mirror mode (per AC #3).
- Generated klasp.toml is round-trip validated via ConfigV1::parse
  before write.
- klasp doctor now flags `pre-commit` PATH gaps for adopted checks.
- 10 integration tests + ~70 unit tests cover all 9 acceptance criteria.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Fix duplicate `.pre-commit-config.yaml` filename in detector list (was
listed twice instead of `.yaml`/`.yml`). Tighten generic "verify
everything is healthy" → "verify each adopted binary is on PATH" so the
final step's purpose is concrete.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- drop dead code (ChainSupport::AutoSafe, GateDetector trait, DetectedGate.confidence)
- tighten types (TriggerKind, HookStage enums; timeout_secs becomes u64)
- extract fs_util::atomic_write_text shared between init + writer
- dedup detector helpers (first_existing_file, hook_to_trigger, package_json_has_lint_staged)
- replace TOCTOU is_file()-then-read with NotFound-matched read
- drop narrating comment + hardcoded --mode mirror in writer header

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@liam-ai-reality
Copy link
Copy Markdown
Contributor Author

Code review

Summary

PR #101 adds klasp init --adopt --mode <inspect|mirror|chain> (closes #97). 12 new files under klasp/src/adopt/ (Plan API + 5 detectors + render + writer + mode + fs_util), 10 integration tests in klasp/tests/init_adopt.rs, doctor extended for pre-commit PATH coverage, docs/adopt.md user doc.

After the simplify pass (refactor(adopt): apply simplify pass) the diff is tight: typed TriggerKind / HookStage enums, no dead Confidence field, atomic_write_text shared via crate::fs_util, TOCTOU-free reads. Workspace builds clean (1 dead-code warning on planned GateType::Tooling variant). 442 tests pass.

Reviewed against /Users/liammccarthy/Projects/CLAUDE.md. 4 reviewers (2× CLAUDE.md compliance + 2× bugs/security) + 7 validation passes. 5 raised findings rejected as INVALID (theoretical / dead-code / repo-wide convention / tested-and-deliberate).

Critical issues

# File Issue
None. Round-trip parse in writer.rs:39-47 gates filesystem writes. No path-traversal vector (every repo_root.join(...) uses a hardcoded literal). No shell-injection elevation (writer mirrors what already runs via sh -c; an attacker editing .husky/pre-commit already has commit-time code execution).

Suggestions

# File Issue Suggested fix
1 klasp/src/adopt/detect_husky.rs first_substantive_command returns only the FIRST substantive line of a Husky hook. Realistic hooks like pnpm lint\npnpm test get silently mirrored as just pnpm lint — no warning emitted. Drifts from the PR's "agents see the same checks your humans hit" goal. Either (a) emit one ProposedCheck per substantive line, or (b) when >1 substantive line is present, emit a warning ("husky hook contains N commands; only first mirrored — review manually"). Add a multi-command fixture to the integration tests.
2 klasp/src/adopt/detect_plain_hooks.rs MANAGED_MARKERS includes bare substrings "husky" and "lefthook". False-positive: a plain user hook with a comment like # we used to use husky defers and produces no finding. False-attribution: a hook invoking /opt/husky-tool/bin/run gets wrongly skipped. Tighten to husky.sh (Husky's actual generated source line) and require lefthook run / line-leading anchoring. Keep .husky/ (already specific) and # Generated by pre-commit (already specific).

What looks good

  • Typed Plan API after simplify pass — TriggerKind/HookStage enums, no stringly-typed slots in the public surface.
  • Atomic write helper extracted to klasp/src/fs_util.rs and shared between init scaffold and adopt writer (no copy-paste).
  • Round-trip ConfigV1::parse validation in writer.rs:42-47 BEFORE filesystem write — generated TOML is provably parseable or the operation aborts with a clean InvalidData error.
  • Mirror mode tested to be non-destructive: mirror_pre_commit_writes_klasp_toml and mirror_plain_git_hook_does_NOT_overwrite_hook both byte-compare hook configs pre/post-run.
  • --force semantics tested both ways (mirror_existing_klasp_toml_without_force_errors + _with_force_overwrites).
  • Chain mode rejection tested with explicit exit-code-2 assertion (chain_mode_rejects_with_explanatory_message); 2 is consistent with klasp's existing convention (klasp/src/cmd/gate.rs:48 uses 2 for Outcome::Block), not a clap usage-error collision.
  • doctor.rs surfaces missing pre-commit binary on PATH for adopted checks (AC W5 release workflow: reintroduce x86_64-apple-darwin (macos-13) for tag-push artefact builds #9).

Verdict

🟡 OK to merge with follow-ups filed. Both suggestions are correctness gaps in detector edge cases, not blockers — mirror mode runs ALONGSIDE the human gate, so silent under-mirroring degrades to "klasp covers a subset of human gate checks" rather than producing wrong output. File two follow-up issues for the husky multi-line and managed-marker tightening; merge this PR; address in a follow-up that can carry tests for the realistic-input cases neither detector currently exercises.

liam-ai-reality and others added 2 commits May 8, 2026 09:51
- detect_husky: emit one ProposedCheck per substantive line; multi-line
  hook bodies no longer silently drop trailing commands. Add multi-command
  tests in unit + integration suites.
- detect_plain_hooks: tighten MANAGED_MARKERS from bare "husky"/"lefthook"
  substrings to "husky.sh" and "lefthook run" (the actual generator-line
  patterns Husky/Lefthook write). Adds tests for user-comment false-
  positive + husky-named-tool false-attribution cases.

Addresses code-review suggestions in PR #101 comment 4404947676.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
CI clippy + fmt failed on the prior fix commit. Apply `cargo fmt --all`
across the adopt module + test fixtures, and tag the planned `GateType::
Tooling` variant with `#[allow(dead_code)]` (reserved for future stack-
detection work; no detector emits it in v1).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@liam-ai-reality
Copy link
Copy Markdown
Contributor Author

liam-ai-reality commented May 8, 2026

Review remediation

Both code-review suggestions addressed inline. No follow-up issues filed — fixed in this PR.

# Suggestion Resolution Commit
1 detect_husky.rs::first_substantive_command silently dropped multi-line hook bodies Renamed to substantive_commands returning Vec<String>; emits one ProposedCheck per substantive line (each gets per-command name detection: lint-staged / test / lint / fallback). Multi-command unit + integration tests added. 026c2c4
2 detect_plain_hooks.rs::MANAGED_MARKERS substrings "husky" / "lefthook" too loose Tightened to "husky.sh" (Husky's actual generated source line) + "lefthook run" (Lefthook's actual generator command). Existing .husky/ and # Generated by pre-commit retained. Added regression tests for user-comment false-positive and husky-named-tool false-attribution cases. 026c2c4

CI fmt + clippy enforcement that the local cargo build did not catch was patched in 10c66f1 (cargo fmt --all across adopt module + #[allow(dead_code)] on the planned GateType::Tooling variant).

Status
Critical findings deferred none
Suggestions deferred none
Tests after remediation 451 passed, 1 ignored, 0 failed
Clippy / fmt clean

@liam-ai-reality liam-ai-reality merged commit 5bccad8 into main May 8, 2026
7 checks passed
@liam-ai-reality liam-ai-reality deleted the feat/init-adopt-97 branch May 8, 2026 08:55
liam-ai-reality added a commit that referenced this pull request May 8, 2026
- README.md: replace 3-command quickstart with `klasp setup`; add v0.4.0
  callout near top; mark #97 + #103 as shipped in feature table; link
  docs/setup.md from quickstart and Documentation sections; keep
  3-command flow as "scriptable / CI" sub-section.
- docs/recipes.md: add `klasp setup` as canonical first-run section
  above per-tool recipes; reference docs/setup.md; update version header
  to include v0.4.
- docs/roadmap.md: update status blurb to 2026-05-08; add v0.4.0 section
  with shipped deliverables for #97 (PR #101) and #103 (PR #104).
- docs/adopt.md: add Agent narrowing section reflecting that [gate].agents
  now defaults to detected agents (not 3-agent default); update example
  session Next: block accordingly; link to docs/setup.md.
- Cargo.toml + npm/pypi manifests: bump all versions to 0.4.0.

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant