Skip to content

feat(fm-spawn): load project .claude/skills for pi crewmates#422

Open
blazingbunny wants to merge 4 commits into
kunchenguid:mainfrom
blazingbunny:fm/spawn-piskill
Open

feat(fm-spawn): load project .claude/skills for pi crewmates#422
blazingbunny wants to merge 4 commits into
kunchenguid:mainfrom
blazingbunny:fm/spawn-piskill

Conversation

@blazingbunny

Copy link
Copy Markdown

Intent

Make firstmate's pi crewmates load their project's own skills at launch by threading --skill /.claude/skills into the pi crewmate/scout launch template in bin/fm-spawn.sh, only when the worktree has a .claude/skills directory, leaving non-pi harnesses, skill-less projects, and pi secondmates unaffected

What Changed

  • bin/fm-spawn.sh now threads a --skill <worktree>/.claude/skills flag into the pi crewmate/scout launch template (via a new __PISKILL__ token), but only when the harness is pi, the task is not a secondmate, and the worktree actually has a .claude/skills directory — so pi crewmates load their project's own skills at launch since pi does not auto-discover them. Non-pi harnesses, skill-less worktrees, and pi secondmates get nothing.
  • Documented the behavior in docs/configuration.md and the harness-adapters skill, noting pi ignores .claude/skills/.pi/skills at the project root but accepts --skill <dir>.
  • Added tests/fm-spawn-piskill.test.sh (4 behavior tests) plus shared helpers in tests/lib.sh, and hardened existing tool-detection tests (bootstrap, session-start, backlog-handoff, secondmate-lifecycle) against host environment leakage.

Risk Assessment

✅ Low: The change is minimal, tightly guarded (harness/kind/dir checks), leaves all other harnesses and secondmates byte-identical, uses the same safe shell_quote + pattern-substitution idiom as existing flags, and is covered by four targeted behavior tests.

Testing

The configured full e2e suite passed as baseline; I additionally ran the new pi-skill behavior test plus every other test touched by the change (backlog-handoff, bootstrap, secondmate-lifecycle-e2e, session-start) and all pass. To show the intent working the way a firstmate operator would experience it, I captured the actual launch command pi is handed via a fake-tmux capture harness: a pi crewmate whose worktree has .claude/skills gets pi --skill &#39;&lt;abs&gt;/.claude/skills&#39; -e ..., a pi crewmate without the dir stays byte-identical to the old template, and both a codex crewmate (dir present) and a pi secondmate (dir present in home) receive no --skill flag. Round 1's generic exit-1 failure no longer reproduces and was addressed by the final commit hardening tool-detection tests against host env leakage.

Evidence: Captured pi/codex/secondmate launch commands across four scenarios

=== pi crewmate, worktree HAS .claude/skills === pi --skill '<wt>/.claude/skills' -e '<...>.pi-ext.ts' "$(cat '<brief>')" === pi crewmate, NO .claude/skills === pi -e '<...>.pi-ext.ts' "$(cat '<brief>')" === codex crewmate, HAS .claude/skills (no --skill) === codex --dangerously-bypass-approvals-and-sandbox ... "$(cat '<brief>')" === pi SECONDMATE, home HAS .claude/skills (no --skill) === ... pi -e '<...>fm-primary-turnend-guard.ts' -e '<...>fm-primary-pi-watch.ts' "$(cat '<charter>')"

=== Scenario 1: pi crewmate, worktree HAS .claude/skills ===
pi --skill '/tmp/piskill-demo.zu81nl/s1/wt/.claude/skills' -e '/tmp/piskill-demo.zu81nl/s1/home/state/j1.pi-ext.ts' "$(cat '/tmp/piskill-demo.zu81nl/s1/home/data/j1/brief.md')"

=== Scenario 2: pi crewmate, worktree has NO .claude/skills ===
pi -e '/tmp/piskill-demo.zu81nl/s2/home/state/j2.pi-ext.ts' "$(cat '/tmp/piskill-demo.zu81nl/s2/home/data/j2/brief.md')"

=== Scenario 3: codex crewmate, worktree HAS .claude/skills (must NOT get --skill) ===
codex --dangerously-bypass-approvals-and-sandbox -c "notify=[\"bash\",\"-c\",\"touch '/tmp/piskill-demo.zu81nl/s3/home/state/j3.turn-ended'\"]" "$(cat '/tmp/piskill-demo.zu81nl/s3/home/data/j3/brief.md')"

=== Scenario 4: pi SECONDMATE, home HAS .claude/skills (must NOT get --skill) ===
FM_ROOT_OVERRIDE= FM_STATE_OVERRIDE= FM_DATA_OVERRIDE= FM_PROJECTS_OVERRIDE= FM_CONFIG_OVERRIDE= FM_HOME='/tmp/piskill-demo.zu81nl/s4/sm' pi -e '/tmp/piskill-demo.zu81nl/s4/sm/.pi/extensions/fm-primary-turnend-guard.ts' -e '/tmp/piskill-demo.zu81nl/s4/sm/.pi/extensions/fm-primary-pi-watch.ts' "$(cat '/tmp/piskill-demo.zu81nl/s4/sm/data/charter.md')"
- Outcome: 🔧 1 issue found → auto-fixed ✅ across 2 runs (2h35m16s)

Pipeline

Updates from git push no-mistakes

✅ **intent** - passed

✅ No issues found.

✅ **Rebase** - passed

✅ No issues found.

⚠️ **Review** - 1 info
  • ℹ️ bin/fm-spawn.sh:1028 - In this repo, .claude/skills is a symlink to .agents/skills (firstmate's internal operational skills, all marked metadata.internal=true). Because pwd -P resolves the symlink, a pi crewmate/scout spawned on a firstmate-on-itself worktree will now auto-load firstmate's own internal skills (harness-adapters, secondmate-provisioning, etc.) via --skill. This is within the literal 'load the project's own skills' intent and is benign (skills are only surfaced, not forced), but it's a non-obvious consequence specific to this self-pooled repo. No action needed unless you want firstmate-repo tasks excluded.
🔧 **Test** - 1 issue found → auto-fixed ✅
  • 🚨 tests failed with exit code 1
  • command -v tmux >/dev/null || { echo "tmux is required for e2e tests" >&2; exit 1; }; tmux -V; rc=0; for t in tests/*.test.sh; do echo "== $t =="; bash "$t" || rc=1; done; exit "$rc"

🔧 Fix: test: harden tool-detection tests against host env leakage
✅ Re-checked - no issues remain.

  • command -v tmux >/dev/null || { echo "tmux is required for e2e tests" >&2; exit 1; }; tmux -V; rc=0; for t in tests/*.test.sh; do echo "== $t =="; bash "$t" || rc=1; done; exit "$rc"
  • bash tests/fm-spawn-piskill.test.sh (4 behavior tests, all pass)
  • bash tests/fm-backlog-handoff.test.sh (pass)
  • bash tests/fm-bootstrap.test.sh (pass)
  • bash tests/fm-secondmate-lifecycle-e2e.test.sh (pass)
  • bash tests/fm-session-start.test.sh (pass)
  • Manual launch-command capture harness printing the real pi/codex/secondmate launch strings for all four scenarios
✅ **Document** - passed

✅ No issues found.

✅ **Lint** - passed

✅ No issues found.

✅ **Push** - passed

✅ No issues found.

Pi does not auto-discover project skills (it ignores .claude/skills and
.pi/skills at the project root) but accepts --skill <dir>. Thread
--skill <worktree>/.claude/skills into the pi crewmate/scout launch
template only, when the resolved worktree actually has a .claude/skills
directory. Empty for non-pi harnesses, skill-less projects, and pi
secondmates, so all other launch paths stay byte-identical.

Adds tests/fm-spawn-piskill.test.sh pinning the four cases: pi crewmate
with dir gets --skill, pi crewmate without dir omits it, codex/claude
crewmates never get it, and pi --secondmate never gets it.
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