diff --git a/11-Features.md b/11-Features.md index 1d9bffe..f88409d 100644 --- a/11-Features.md +++ b/11-Features.md @@ -4529,3 +4529,49 @@ closes the channel it ran on — reusing the publish channel would let one miss publish on that instance. And `FleetMcp.probe` cancels a timed-out probe rather than abandoning it; without that, a hung (not down) broker would orphan one channel per `fleet_list` call until the connection's channel-max ran out, breaking publish by a different route. + +## Bridge skills are seeded into every provisioned worktree + +**What it does.** fleetd copies a directory of skill folders into each worker worktree it creates, +at `/.claude/skills/`. A skill folder whose name the target repo already ships is never +touched — the repo's own copy wins, byte for byte. + +**On.** Point `memberSkills:` at the directory: + +```yaml +memberSkills: /Users/dai.ha/LTMS/claude-bridge/.claude/skills +``` + +Leave it out and nothing is seeded, which is the old behaviour. This is a deferred key — the +`GitWorktrees` that reads it is built once at startup, so a change needs a daemon restart. + +**Why it exists.** Every brief starts with `Load the skill.`, and outside this repo that +line was silently a no-op. A member spawned against any other repo — kb on fleet01, for example — +had no `implementer`, `reviewer` or `hunter` to load, and nothing said so. The skills could not +travel in the plugin either: `ClaudeCodeLauncher` exports `CLAUDE_CONFIG_DIR`, so a member never +reads the operator's plugin store. The worktree is the only channel that reaches a member. +fleetd #362 item 3. + +**One thing to know for maintenance.** `core.excludesFile` is **single-valued**. The seeded paths +are hidden from `git status` by pointing that key at a fleetd-written file, scoped `--worktree` — +and a worktree-scoped value *replaces* the operator's global one rather than adding to it. The +first cut of this feature did exactly that, and the consequence was severe: this repo's own +`.gitignore` does not ignore `target/`, only an operator's global excludesFile does, so every +worker that ran `mvn clean install` made `target/` untracked. `GitWorktrees.hasUncommitted` counts +untracked files on purpose (CB-576), so `SessionManager` would have preserved every worktree that +built, forever, with no error to notice. + +So the file is **composed, not replaced**: whatever `core.excludesFile` resolved to beforehand is +copied in ahead of the seeded patterns, including git's own default (`$XDG_CONFIG_HOME/git/ignore`, +else `$HOME/.config/git/ignore`) when the key was unset. Measured on merge: removing the +composition fails 2 tests, and removing just the default-file fallback fails 1. + +Two smaller things. The composed content is a **snapshot** taken at seed time, so an operator +editing their own excludesFile later does not change an already-seeded worktree. And the exclude +file itself lives under the worktree's private git dir (`git rev-parse --absolute-git-dir`), not in +the working tree, so it cannot be committed and `git worktree remove --force` deletes it along with +everything else. + +`.git/info/exclude` was rejected as the mechanism, and this is worth knowing before someone tries +it again: from a linked worktree it resolves to the **common** git dir, so it would have hidden the +seeded paths in the primary checkout and every sibling worktree too.