Skip to content

feat: vendoring — freeze LCD into a project, update it safely (0.20.0) - #20

Merged
stid merged 7 commits into
mainfrom
feat/vendor
Oct 24, 2026
Merged

stid merged 7 commits into
mainfrom
feat/vendor

Conversation

@stid

@stid stid commented Oct 24, 2026 •

Copy link
Copy Markdown
Owner

Why

On a contributor-open project, contributors who never installed LCD don't get its triage, hooks or AC tests. This freezes LCD into the project's own .claude/, so cloning the repo is enough.

What

  • bin/lcd-vendor.sh install|update|check [--dry-run] [--force] <project>, run from a plugin checkout.
    • Skills, commands and agents are written as lcd-<name>. Project skills can't use the lcd: namespace, : is reserved in agent names, and bare doctor/resume would shadow built-ins.
    • The engine goes under .claude/lcd/{bin,rules,templates}.
    • Hooks ("$CLAUDE_PROJECT_DIR"/.claude/lcd/bin/…) and path-based permissions are merged into .claude/settings.json, keeping every other key.
    • The CLAUDE.md/AGENTS.md pointer tokens are renamed.
    • .claude/rules/ac-convention.md is installed only if the project has none.
  • Update safety.
    • .claude/lcd/MANIFEST records the sha256 of every managed file. An update that would overwrite a locally edited or deleted file refuses and names it; --force accepts.
    • --dry-run prints from → to, added/changed/removed files and the CHANGELOG sections in between.
    • check exits 0 only when the copy is current and nothing drifted.
  • /lcd-update [vX.Y.Z | path] (vendored copies only) fetches the target tag or uses a local clone, previews, asks, applies with the target's own script, runs doctor, and offers to refresh the CI-gate script copies.
  • Path safety. MANIFEST entries must stay under .claude/ with no .., and no managed path component may be a symlink. A committed MANIFEST is contributor-controlled.

Verification

  • tests/test-vendor.sh covers AC-1..AC-6 (CLI), including traversal and symlink refusal. Suite 14/14; claude plugin validate passes.
  • Headless smoke with lcd@lcd disabled:
    • SessionStart context is rewritten.
    • 15 lcd-* skills/commands and 4 agents are listed.
    • The boundary hook denies an out-of-boundary write and allows an in-boundary one.
  • Doctor on a vendored copy of a real onboarded repo: 0 FAIL / 0 WARN.

Notes

  • Security review finding acknowledged, not changed: the permission rules are relative (Bash(.claude/lcd/bin/…:*)). settings.json is committed and shared, so absolute paths would break for everyone else. The trust level is the same as the plugin's name-based rules.
  • Name collision: the lcd:recon skill and the lcd:lcd-recon agent both become lcd-recon. Skills and agents are separate namespaces, so this should be harmless, but it's untested.
  • Cosmetic leftover: a few "(on PATH via the plugin's bin/)" notes remain next to commands that are already rewritten to .claude/lcd/bin/….

Follow-ups (not in this PR)

  • The boundary hook reads only tool_input.file_path, so NotebookEdit (notebook_path) is allowed unchecked. .. segments aren't normalised before matching.
  • rules/lcd.md and the ac-convention.md header say the rule auto-loads from the plugin. Plugins have no rules/ component, so it doesn't. Doctor's non-default-root check rests on the same claim.

🤖 Generated with Claude Code

https://claude.ai/code/session_01VCp4cMxLbQXy83ZZv9RDXp

stid and others added 5 commits October 24, 2026 11:31
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VCp4cMxLbQXy83ZZv9RDXp
…) + frozen /lcd-update

Vendors skills, commands and agents under lcd- names, the engine under .claude/lcd/,
hooks and path-based permissions merged into .claude/settings.json. A sha256 MANIFEST
guards updates: locally edited or deleted managed files block the update unless --force.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VCp4cMxLbQXy83ZZv9RDXp
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VCp4cMxLbQXy83ZZv9RDXp
…links

A committed MANIFEST is contributor-controlled: entries must match .claude/<path>
with no '..', and no path component (settings.json, .claude/skills, …) may be a
symlink, so update/install can't delete or write outside the project.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VCp4cMxLbQXy83ZZv9RDXp
…ate script copies

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VCp4cMxLbQXy83ZZv9RDXp
stid and others added 2 commits October 24, 2026 11:54
…c-convention seed, lcd:* and docs/

- install crashed under macOS /bin/bash 3.2 (unguarded empty array under set -u);
  the test now runs the script with /bin/bash
- settings merge removes LCD hook entries, not whole groups: a user hook sharing a
  group with an LCD hook survives install and update
- .claude/rules/ac-convention.md is a seed written only when absent and never in the
  MANIFEST, so onboard's non-default-root rewrite no longer blocks every update
- lcd:* rewritten too; docs/ vendored so plugin-root doc links resolve; a test checks
  every vendored .claude/... reference exists
- --force test asserts restore + overwrite; AGENTS.md, refused-install snapshot and
  removed-skill-dir cleanup now asserted

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VCp4cMxLbQXy83ZZv9RDXp
@stid
stid merged commit 9559bdd into main Oct 24, 2026
3 checks passed
@stid
stid deleted the feat/vendor branch October 24, 2026 19:05
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