Skip to content

feat: select project skills for worker launches - #49

Merged
MrGTV-love merged 12 commits into
mainfrom
fm/fm-worker-project-skill-picker
Oct 8, 2026
Merged

MrGTV-love merged 12 commits into
mainfrom
fm/fm-worker-project-skill-picker

Conversation

@MrGTV-love

@MrGTV-love MrGTV-love commented Oct 8, 2026 •

Copy link
Copy Markdown
Owner

Intent

workers should have the skill picker

after workers potentially ignoring DoS and CoC skills, they need it

Calling out the specific skill does not make sense. Firstmate needs to operate with skills. Otherwise, it is a free-for-all as Firstmate is focused on orchestration. The work that is done in the sessions is through skills.

When looking at the timeouts and harness issues, assess individual fixes, but also assess as a system with dependencies. We often fix one thing to break another. I want to fix without breaking. I want to fix without needing to fix again.

Context needed to read the ask (facts checked 2026-10-07):

  • DoS and CoC are the Vernant method skills defining-operational-solutions and chain-of-custody, in Vernant's .agents/skills/ (231 skills) and .claude/skills/ (234 skills).
  • A worker launched into a project worktree already loads that project's skills, but nothing makes sure it uses the right one.
  • Firstmate's skill picker is bin/fm-skill-suggest.sh (Jev, model jev-1.13.0). bin/fm-spawn.sh (around line 3315) runs it only when a brief contains a # Skill selection input section; no worker brief in any Vernant lane home has that section, so it never runs for them.
  • When it does run, its catalog defaults to firstmate's own .agents/skills, not the project's, and it caps the catalog at 128 skills (Vernant has more).
  • Its picks are labelled advisory.

openrouter is a fallback from directly using the typesafe api

is the skill-picker aligned with the example I shared or is it still the makeshift version created before?

don't rebuild it. use your memory which says to use the example and modify only if necessary

that is a lot easier than rebuilding. No slop

Context needed to read those words:

  • "the example I shared" is the TypeSafe skill-suggestion cookbook (https://docs.typesafe.ai/cookbooks/skill_suggestion) and disler's hyper-jev skill with its starter Jev client, vendored unchanged at .agents/skills/hyper-jev (upstream https://github.com/disler/ten-levels-of-jev).
  • "the makeshift version" is bin/fm-skill-suggest.sh with bin/fm-skill-catalog.jq, an invented two-stage design with its own thresholds and a 128-skill cap.
  • "your memory" is the standing rule: install the reference the captain gives as-is, and deviate only where the codebase forces it, naming the reason.

I asked for A already

Context needed to read those words (2026-10-07):

  • "A" is the option where worker skill selection sends the same sanitized, never-send-filtered task text that bin/fm-dispatch-resolve.sh already sends (the brief's task subsections), to TypeSafe direct first and to the OpenRouter fallback, and docs/configuration.md records that authorization without widening any other sending boundary.

What Changed

  • Replace advisory skill suggestions with a cookbook-based picker using the unchanged vendored hyper-jev client, project skill catalogs, large-roster chunking, and highest-fit selection.
  • Run selection for ship/scout launches and relaunches, instruct workers to read and follow the picked skill without overriding mandatory triggers, and record selection and delivery outcomes within a 30-second launch bound.
  • Share sanitized task extraction and never-send checks with dispatch resolution, use TypeSafe directly with OpenRouter fallback, and update configuration docs and picker-dependent test selection.

Risk Assessment

⚠️ Medium: The change affects shared privacy extraction and every supported worker launch/relaunch path, but bounded execution, fail-closed disclosure checks, and explicit delivery outcomes limit source risk, with no new material defect substantiated.

Testing

The targeted picker and key-source suites, focused spawn-selection regression, and live CLI, privacy, catalog, runtime-guard, worker-delivery and failed-relaunch scenarios passed. Initial command limits and driver assertion issues were addressed with a longer picker run, focused runner checks and corrected captures. CLI transcripts, persisted state and rendered terminal evidence were preserved; disposable labs were removed. Authenticated picks and real AI skill adherence remain untested under the previously declined credential blocker.

  • Live validation: ⚠️ inconclusive - 13 of 14 scenarios driven live against the product
Scenario Result Live Evidence
Invoke the advertised picker CLI directly and receive help ✅ pass live live-cli.txt: direct bin/fm-skill-pick.sh --help invocation exited successfully.
Run selection without credentials and receive explicit manual skill-index guidance ✅ pass live live-cli.txt: unavailable result names the missing keys and instructs the worker to check its skill index.
Enumerate a large project catalog while preserving precedence and excluding non-eligible skills ✅ pass live project-roster.json: 261 eligible skills retained, earlier .agents duplicate wins, untracked and linked skills are not judged, and the skill-less plugin link is ignored.
Attempt selection with forbidden task text and a late-roster skill identity ✅ pass live live-cli.txt: both inputs produce dispatch-never-send policy refusals rather than selection or provider fallback.
Extract promoted legacy captain intent and current ship instructions without exposing protected or stale text ✅ pass live live-cli.txt: extraction retains permitted legacy captain words and the current ship spec, excludes stale, fenced and protected text, and leaves the output mode at 600 under umask 022.
Select against an absent catalog and distinguish no skills from unavailable selection ✅ pass live live-cli.txt: actual CLI records status=none for an absent catalog.
Reject invalid credentials at both real providers while retaining fallback provenance ✅ pass live live-cli.txt: real TypeSafe and OpenRouter requests receive HTTP 401; the unavailable record includes TypeSafe direct failure and OpenRouter fallback.
Resolve a nested local home's primary TypeSafe key without copying it ✅ pass live live-cli.txt: a nested home with no own key reaches the picker policy check using its disposable primary-home binding.
Disable actual Node TypeScript support and receive actionable unavailable guidance ✅ pass live live-cli.txt: NODE_OPTIONS=--no-experimental-strip-types causes the real vendored-client import to produce the supported runtime diagnostic.
Supply malformed privacy markers or a non-directory catalog and receive a specific refusal ✅ pass live live-cli.txt: an unclosed protected region, non-regular policy and non-directory catalog each produce a concrete unavailable reason.
Launch ship and scout raw-command workers and deliver picker fallback instructions in isolated worktrees ✅ pass live current-worker-transcripts.html and live-worker-delivery.txt: actual tmux/Treehouse workers consume their generated BRIEF instructions, including unavailable/manual-index guidance; metadata record…
Fail an actual doorbell relaunch and retain undelivered selection metadata without a claimed pick ✅ pass live failed-delivery.txt and failed-delivery-metadata.txt: deliberate inbox publication failure retains skill_selection=undelivered, the publication reason, and no skill_selection_picked field.
Ask changed-test selection to cover a spawn-only change without broadening sibling commands ✅ pass live changed-test-selection.txt: actual runner CLI includes the picker for fm-spawn.sh only, while retaining backend-dispatch and pure-contract coverage for all six exercised inputs.
Launch a real AI worker with an authenticated relevant project-skill pick and observe it follow the skill ⏸️ untested no No usable TypeSafe or OpenRouter credentials were present in the process environment. Disposable credentials reached both real endpoints but received HTTP 401. Isolated fixtures therefore exercised er…
Evidence: Current live picker CLI and boundary transcripts
$ ~/.no-mistakes/worktrees/32d18ed9638d/01M4D34BGQE483Q5806J9GM9AX/bin/fm-skill-pick.sh --brief ~/.no-mistakes/worktrees/32d18ed9638d/01M4D34BGQE483Q5806J9GM9AX/.v/brief.md --catalog ~/.no-mistakes/worktrees/32d18ed9638d/01M4D34BGQE483Q5806J9GM9AX/.v/project/.agents/skills --catalog ~/.no-mistakes/worktrees/32d18ed9638d/01M4D34BGQE483Q5806J9GM9AX/.v/project/.claude/skills --record ~/.no-mistakes/worktrees/32d18ed9638d/01M4D34BGQE483Q5806J9GM9AX/.v/record

# Skill selection

Existing mandatory skill triggers in these instructions and your skill index still apply first and unchanged.
Skill selection was unavailable for this task (withheld by dispatch-never-send policy: brief text matches ~/.no-mistakes/worktrees/32d18ed9638d/01M4D34BGQE483Q5806J9GM9AX/.v/home/config/dispatch-never-send line 1). This does not mean no skill applies: check your skill index for skills that fit this task before starting work.
Not judged, so check them yourself if relevant: linked-skill (not a Git-tracked file in this project), local-only (not a Git-tracked file in this project).
exit=0

$ ~/.no-mistakes/worktrees/32d18ed9638d/01M4D34BGQE483Q5806J9GM9AX/bin/fm-skill-pick.sh --brief ~/.no-mistakes/worktrees/32d18ed9638d/01M4D34BGQE483Q5806J9GM9AX/.v/brief.md --catalog ~/.no-mistakes/worktrees/32d18ed9638d/01M4D34BGQE483Q5806J9GM9AX/.v/project/.agents/skills --catalog ~/.no-mistakes/worktrees/32d18ed9638d/01M4D34BGQE483Q5806J9GM9AX/.v/project/.claude/skills --record ~/.no-mistakes/worktrees/32d18ed9638d/01M4D34BGQE483Q5806J9GM9AX/.v/record

# Skill selection

Existing mandatory skill triggers in these instructions and your skill index still apply first and unchanged.
Skill selection was unavailable for this task (withheld by dispatch-never-send policy: brief text matches ~/.no-mistakes/worktrees/32d18ed9638d/01M4D34BGQE483Q5806J9GM9AX/.v/home/config/dispatch-never-send line 1). This does not mean no skill applies: check your skill index for skills that fit this task before starting work.
Not judged, so check them yourself if relevant: linked-skill (not a Git-tracked file in this project), local-only (not a Git-tracked file in this project).
exit=0

$ ~/.no-mistakes/worktrees/32d18ed9638d/01M4D34BGQE483Q5806J9GM9AX/bin/fm-skill-pick.sh --brief ~/.no-mistakes/worktrees/32d18ed9638d/01M4D34BGQE483Q5806J9GM9AX/.v/brief.md --catalog ~/.no-mistakes/worktrees/32d18ed9638d/01M4D34BGQE483Q5806J9GM9AX/.v/project/.agents/skills --catalog ~/.no-mistakes/worktrees/32d18ed9638d/01M4D34BGQE483Q5806J9GM9AX/.v/project/.claude/skills --record ~/.no-mistakes/worktrees/32d18ed9638d/01M4D34BGQE483Q5806J9GM9AX/.v/record

# Skill selection

Existing mandatory skill triggers in these instructions and your skill index still apply first and unchanged.
Skill selection was unavailable for this task (task text withheld: ~/.no-mistakes/worktrees/32d18ed9638d/01M4D34BGQE483Q5806J9GM9AX/.v/home/config/dispatch-never-send is not a readable regular file). This does not mean no skill applies: check your skill index for skills that fit this task before starting work.
exit=0

$ bash -c . "$1/bin/fm-typesafe-lib.sh"; umask 022; fm_typesafe_brief_task "$2" "$3" "$4" ship; stat -f "%Lp" "$4"; cat "$4" _ ~/.no-mistakes/worktrees/32d18ed9638d/01M4D34BGQE483Q5806J9GM9AX ~/.no-mistakes/worktrees/32d18ed9638d/01M4D34BGQE483Q5806J9GM9AX/.v/brief.md ~/.no-mistakes/worktrees/32d18ed9638d/01M4D34BGQE483Q5806J9GM9AX/.v/home/config/dispatch-never-send ~/.no-mistakes/worktrees/32d18ed9638d/01M4D34BGQE483Q5806J9GM9AX/.v/private-task
600
Investigate the session-floor refusal.
Preserve the launch path.
# Current ship Firstmate spec
Measure the replacement launch.
exit=0

$ ~/.no-mistakes/worktrees/32d18ed9638d/01M4D34BGQE483Q5806J9GM9AX/bin/fm-skill-pick.sh --brief ~/.no-mistakes/worktrees/32d18ed9638d/01M4D34BGQE483Q5806J9GM9AX/.v/brief.md --catalog ~/.no-mistakes/worktrees/32d18ed9638d/01M4D34BGQE483Q5806J9GM9AX/.v/absent --record ~/.no-mistakes/worktrees/32d18ed9638d/01M4D34BGQE483Q5806J9GM9AX/.v/record

# Skill selection

Existing mandatory skill triggers in these instructions and your skill index still apply first and unchanged.
Skill selection found no fit among the judged project skills (this project has no skills to judge); use your skill index as usual.
exit=0

$ ~/.no-mistakes/worktrees/32d18ed9638d/01M4D34BGQE483Q5806J9GM9AX/bin/fm-skill-pick.sh --brief ~/.no-mistakes/worktrees/32d18ed9638d/01M4D34BGQE483Q5806J9GM9AX/.v/brief.md --catalog ~/.no-mistakes/worktrees/32d18ed9638d/01M4D34BGQE483Q5806J9GM9AX/.v/project/.agents/skills --catalog ~/.no-mistakes/worktrees/32d18ed9638d/01M4D34BGQE483Q5806J9GM9AX/.v/project/.claude/skills --record ~/.no-mistakes/worktrees/32d18ed9638d/01M4D34BGQE483Q5806J9GM9AX/.v/record

# Skill selection

Existing mandatory skill triggers in these instructions and your skill index still apply first and unchanged.
Skill selection was unavailable for this task (openrouter HTTP 401. Check the OPENROUTER_API_KEY key.; TypeSafe direct failed (typesafe HTTP 401. Check the TYPESAFE_API_KEY key.; used OpenRouter)). This does not mean no skill applies: check your skill index for skills that fit this task before starting work.
Not judged, so check them yourself if relevant: linked-skill (not a Git-tracked file in this project), local-only (not a Git-tracked file in this project).
exit=0

$ bin/fm-skill-pick.sh --help
Pick the one project skill a worker should load, for its launch instructions.
Usage: fm-skill-pick.sh --brief <filled-brief> --catalog <skill-dir>... [--record <file>] [--kind ship|scout]
bin/fm-skill-pick.mjs runs the TypeSafe skill-suggestion cookbook recipe on
the vendored hyper-jev client; its header owns recipe and roster mechanics.
docs/configuration.md "Worker skill selection" owns provider policy and setup.
This wrapper supplies what the example cannot: the brief text dispatch
resolution may send (fm_typesafe_brief_task), the dispatch-never-send check
over every string a request can carry, and the keys from fm-typesafe-lib.sh,
passed to Node on stdin.
Output: the "# Skill selection" launch-instructions section. --record also
writes status= (picked, none or unavailable), reason= and picked= lines.
Any failure yields status unavailable with its reason; exit 2 is a usage error.

$ bin/fm-skill-pick.sh --brief .v/brief.md --catalog .v/project/.agents/skills --catalog .v/project/.claude/skills --record .v/record (isolated no-key home)

# Skill selection

Existing mandatory skill triggers in these instructions and your skill index still apply first and unchanged.
Skill selection was unavailable for this task (no TypeSafe or OpenRouter key). This does not mean no skill applies: check your skill index for skills that fit this task before starting work.

$ FM_HOME=.v/nested-home bin/fm-skill-pick.sh [same brief/catalog arguments] (primary key inherited; local forbidden skill policy)

# Skill selection

Existing mandatory skill triggers in these instructions and your skill index still apply first and unchanged.
Skill selection was unavailable for this task (withheld by dispatch-never-send policy: brief text matches ~/.no-mistakes/worktrees/32d18ed9638d/01M4D34BGQE483Q5806J9GM9AX/.v/nested-home/config/dispatch-never-send line 1). This does not mean no skill applies: check your skill index for skills that fit this task before starting work.
Not judged, so check them yourself if relevant: linked-skill (not a Git-tracked file in this project), local-only (not a Git-tracked file in this project).

$ NODE_OPTIONS=--no-experimental-strip-types FM_HOME=.v/nested-home bin/fm-skill-pick.sh [same arguments]

# Skill selection

Existing mandatory skill triggers in these instructions and your skill index still apply first and unchanged.
Skill selection was unavailable for this task (unsupported Node runtime: importing the vendored TypeScript client requires Node with TypeScript support (Unknown file extension ".ts" for ~/.no-mistakes/worktrees/32d18ed9638d/01M4D34BGQE483Q5806J9GM9AX/.agents/skills/hyper-jev/templates/starter/src/core/client.ts)). This does not mean no skill applies: check your skill index for skills that fit this task before starting work.

$ bin/fm-skill-pick.sh [same arguments, unclosed never-send region]

# Skill selection

Existing mandatory skill triggers in these instructions and your skill index still apply first and unchanged.
Skill selection was unavailable for this task (task text withheld: invalid never-send markers or unreadable brief). This does not mean no skill applies: check your skill index for skills that fit this task before starting work.

$ bin/fm-skill-pick.sh --brief .v/brief.md --catalog .v/brief.md

# Skill selection

Existing mandatory skill triggers in these instructions and your skill index still apply first and unchanged.
Skill selection was unavailable for this task (could not enumerate skill catalog ~/.no-mistakes/worktrees/32d18ed9638d/01M4D34BGQE483Q5806J9GM9AX/.v/brief.md: ENOTDIR: not a directory, scandir '~/.no-mistakes/worktrees/32d18ed9638d/01M4D34BGQE483Q5806J9GM9AX/.v/brief.md'). This does not mean no skill applies: check your skill index for skills that fit this task before starting work.
Evidence: Actual project roster with 261 eligible skills
{
  "skills": [
    {
      "name": "skill-000",
      "path": "~/.no-mistakes/worktrees/32d18ed9638d/01M4D34BGQE483Q5806J9GM9AX/.v/project/.agents/skills/skill-000/SKILL.md",
      "description": "Documented procedure 0",
      "excerpt": "# Procedure\nFollow the procedure for skill-000.\n"
    },
    {
      "name": "skill-001",
      "path": "~/.no-mistakes/worktrees/32d18ed9638d/01M4D34BGQE483Q5806J9GM9AX/.v/project/.agents/skills/skill-001/SKILL.md",
      "description": "Documented procedure 1",
      "excerpt": "# Procedure\nFollow the procedure for skill-001.\n"
    },
    {
      "name": "skill-002",
      "path": "~/.no-mistakes/worktrees/32d18ed9638d/01M4D34BGQE483Q5806J9GM9AX/.v/project/.agents/skills/skill-002/SKILL.md",
      "description": "Documented procedure 2",
      "excerpt": "# Procedure\nFollow the procedure for skill-002.\n"
    },
    {
      "name": "skill-003",
      "path": "~/.no-mistakes/worktrees/32d18ed9638d/01M4D34BGQE483Q5806J9GM9AX/.v/project/.agents/skills/skill-003/SKILL.md",
      "description": "Documented procedure 3",
      "excerpt": "# Procedure\nFollow the procedure for skill-003.\n"
    },
    {
      "name": "skill-004",
      "path": "~/.no-mistakes/worktrees/32d18ed9638d/01M4D34BGQE483Q5806J9GM9AX/.v/project/.agents/skills/skill-004/SKILL.md",
      "description": "Documented procedure 4",
      "excerpt": "# Procedure\nFollow the procedure for skill-004.\n"
    },
    {
      "name": "skill-005",
      "path": "~/.no-mistakes/worktrees/32d18ed9638d/01M4D34BGQE483Q5806J9GM9AX/.v/project/.agents/skills/skill-005/SKILL.md",
      "description": "Documented procedure 5",
      "excerpt": "# Procedure\nFollow the procedure for skill-005.\n"
    },
    {
      "name": "skill-006",
      "path": "~/.no-mistakes/worktrees/32d18ed9638d/01M4D34BGQE483Q5806J9GM9AX/.v/project/.agents/skills/skill-006/SKILL.md",
      "description": "Documented procedure 6",
      "excerpt": "# Procedure\nFollow the procedure for skill-006.\n"
    },
    {
      "name": "skill-007",
      "path": "~/.no-mistakes/worktrees/32d18ed9638d/01M4D34BGQE483Q5806J9GM9AX/.v/project/.agents/skills/skill-007/SKILL.md",
      "description": "Documented procedure 7",
      "excerpt": "# Procedure\nFollow the procedure for skill-007.\n"
    },
    {
      "name": "skill-008",
      "path": "~/.no-mistakes/worktrees/32d18ed9638d/01M4D34BGQE483Q5806J9GM9AX/.v/project/.agents/skills/skill-008/SKILL.md",
      "description": "Documented procedure 8",
      "excerpt": "# Procedure\nFollow the procedure for skill-008.\n"
    },
    {
      "name": "skill-009",
      "path": "~/.no-mistakes/worktrees/32d18ed9638d/01M4D34BGQE483Q5806J9GM9AX/.v/project/.agents/skills/skill-009/SKILL.md",
      "description": "Documented procedure 9",
      "excerpt": "# Procedure\nFollow the procedure for skill-009.\n"
    },
    {
      "name": "skill-010",
      "path": "~/.no-mistakes/worktrees/32d18ed9638d/01M4D34BGQE483Q5806J9GM9AX/.v/project/.agents/skills/skill-010/SKILL.md",
      "description": "Documented procedure 10",
      "excerpt": "# Procedure\nFollow the procedure for skill-010.\n"
    },
    {
      "name": "skill-011",
      "path": "~/.no-mistakes/worktrees/32d18ed9638d/01M4D34BGQE483Q5806J9GM9AX/.v/project/.agents/skills/skill-011/SKILL.md",
      "description": "Documented procedure 11",
      "excerpt": "# Procedure\nFollow the procedure for skill-011.\n"
    },
    {
      "name": "skill-012",
      "path": "~/.no-mistakes/worktrees/32d18ed9638d/01M4D34BGQE483Q5806J9GM9AX/.v/project/.agents/skills/skill-012/SKILL.md",
      "description": "Documented procedure 12",
      "excerpt": "# Procedure\nFollow the procedure for skill-012.\n"
    },
    {
      "name": "skill-013",
      "path": "~/.no-mistakes/worktrees/32d18ed9638d/01M4D34BGQE483Q5806J9GM9AX/.v/project/.agents/skills/skill-013/SKILL.md",
      "description": "Documented procedure 13",
      "excerpt": "# Procedure\nFollow the procedure for skill-013.\n"
    },
    {
      "name": "skill-014",
      "path": "~/.no-mistakes/worktrees/32d18ed9638d/01M4D34BGQE483Q5806J9GM9AX/.v/project/.agents/skills/skill-014/SKILL.md",
      "description": "Documented procedure 14",
      "excerpt": "# Procedure\nFollow the procedure for skill-014.\n"
    },
    {
      "name": "skill-015",
      "path": "~/.no-mistakes/worktrees/32d18ed9638d/01M4D34BGQE483Q5806J9GM9AX/.v/project/.agents/skills/skill-015/SKILL.md",
      "description": "Documented procedure 15",
      "excerpt": "# Procedure\nFollow the procedure for skill-015.\n"
    },
    {
      "name": "skill-016",
      "path": "~/.no-mistakes/worktrees/32d18ed9638d/01M4D34BGQE483Q5806J9GM9AX/.v/project/.agents/skills/skill-016/SKILL.md",
      "description": "Documented procedure 16",
      "excerpt": "# Procedure\nFollow the procedure for skill-016.\n"
    },
    {
      "name": "skill-017",
      "path": "~/.no-mistakes/worktrees/32d18ed9638d/01M4D34BGQE483Q5806J9GM9AX/.v/project/.agents/skills/skill-017/SKILL.md",
      "description": "Documented procedure 17",
      "excerpt": "# Procedure\nFollow the procedure for skill-017.\n"
    },
    {
      "name": "skill-018",
      "path": "~/.no-mistakes/worktrees/32d18ed9638d/01M4D34BGQE483Q5806J9GM9AX/.v/project/.agents/skills/skill-018/SKILL.md",
      "description": "Documented procedure 18",
      "excerpt": "# Procedure\nFollow the procedure for skill-018.\n"
    },
    {
      "name": "skill-019",
      "path": "~/.no-mistakes/worktrees/32d18ed9638d/01M4D34BGQE483Q5806J9GM9AX/.v/project/.agents/skills/skill-019/SKILL.md",
      "description": "Documented procedure 19",
      "excerpt": "# Procedure\nFollow the procedure for skill-019.\n"
    },
    {
      "name": "skill-020",
      "path": "~/.no-mistakes/worktrees/32d18ed9638d/01M4D34BGQE483Q5806J9GM9AX/.v/project/.agents/skills/skill-020/SKILL.md",
      "description": "Documented procedure 20",
      "excerpt": "# Procedure\nFollow the procedure for skill-020.\n"
    },
    {
      "name": "skill-021",
      "path": "~/.no-mistakes/worktrees/32d18ed9638d/01M4D34BGQE483Q5806J9GM9AX/.v/project/.agents/skills/skill-021/SKILL.md",
      "description": "Documented procedure 21",
      "excerpt": "# Procedure\nFollow the procedure for skill-021.\n"
    },
    {
      "name": "skill-022",
      "path": "~/.no-mistakes/worktrees/32d18ed9638d/01M4D34BGQE483Q5806J9GM9AX/.v/project/.agents/skills/skill-022/SKILL.md",
      "description": "Documented procedure 22",
      "excerpt": "# Procedure\nFollow the procedure for skill-022.\n"
    },
    {
      "name": "skill-023",
      "path": "~/.no-mistakes/worktrees/32d18ed9638d/01M4D34BGQE483Q5806J9GM9AX/.v/project/.agents/skills/skill-023/SKILL.md",
      "description": "Documented procedure 23",
      "excerpt": "# Procedure\nFollow the procedure for skill-023.\n"
    },
    {
      "name": "skill-024",
      "path": "~/.no-mistakes/worktrees/32d18ed9638d/01M4D34BGQE483Q5806J9GM9AX/.v/project/.agents/skills/skill-024/SKILL.md",
      "description": "Documented procedure 24",
      "excerpt": "# Procedure\nFollow the procedure for skill-024.\n"
    },
    {
      "name": "skill-025",
      "path": "~/.no-mistakes/worktrees/32d18ed9638d/01M4D34BGQE483Q5806J9GM9AX/.v/project/.agents/skills/skill-025/SKILL.md",
      "description": "Documented procedure 25",
      "excerpt": "# Procedure\nFollow the procedure for skill-025.\n"
    },
    {
      "name": "skill-026",
      "path": "~/.no-mistakes/worktrees/32d18ed9638d/01M4D34BGQE483Q5806J9GM9AX/.v/project/.agents/skills/skill-026/SKILL.md",
      "description": "Documented procedure 26",
 

... [63887 bytes truncated] ...

35",
      "path": "~/.no-mistakes/worktrees/32d18ed9638d/01M4D34BGQE483Q5806J9GM9AX/.v/project/.agents/skills/skill-235/SKILL.md",
      "description": "Documented procedure 235",
      "excerpt": "# Procedure\nFollow the procedure for skill-235.\n"
    },
    {
      "name": "skill-236",
      "path": "~/.no-mistakes/worktrees/32d18ed9638d/01M4D34BGQE483Q5806J9GM9AX/.v/project/.agents/skills/skill-236/SKILL.md",
      "description": "Documented procedure 236",
      "excerpt": "# Procedure\nFollow the procedure for skill-236.\n"
    },
    {
      "name": "skill-237",
      "path": "~/.no-mistakes/worktrees/32d18ed9638d/01M4D34BGQE483Q5806J9GM9AX/.v/project/.agents/skills/skill-237/SKILL.md",
      "description": "Documented procedure 237",
      "excerpt": "# Procedure\nFollow the procedure for skill-237.\n"
    },
    {
      "name": "skill-238",
      "path": "~/.no-mistakes/worktrees/32d18ed9638d/01M4D34BGQE483Q5806J9GM9AX/.v/project/.agents/skills/skill-238/SKILL.md",
      "description": "Documented procedure 238",
      "excerpt": "# Procedure\nFollow the procedure for skill-238.\n"
    },
    {
      "name": "skill-239",
      "path": "~/.no-mistakes/worktrees/32d18ed9638d/01M4D34BGQE483Q5806J9GM9AX/.v/project/.agents/skills/skill-239/SKILL.md",
      "description": "Documented procedure 239",
      "excerpt": "# Procedure\nFollow the procedure for skill-239.\n"
    },
    {
      "name": "skill-240",
      "path": "~/.no-mistakes/worktrees/32d18ed9638d/01M4D34BGQE483Q5806J9GM9AX/.v/project/.agents/skills/skill-240/SKILL.md",
      "description": "Documented procedure 240",
      "excerpt": "# Procedure\nFollow the procedure for skill-240.\n"
    },
    {
      "name": "skill-241",
      "path": "~/.no-mistakes/worktrees/32d18ed9638d/01M4D34BGQE483Q5806J9GM9AX/.v/project/.agents/skills/skill-241/SKILL.md",
      "description": "Documented procedure 241",
      "excerpt": "# Procedure\nFollow the procedure for skill-241.\n"
    },
    {
      "name": "skill-242",
      "path": "~/.no-mistakes/worktrees/32d18ed9638d/01M4D34BGQE483Q5806J9GM9AX/.v/project/.agents/skills/skill-242/SKILL.md",
      "description": "Documented procedure 242",
      "excerpt": "# Procedure\nFollow the procedure for skill-242.\n"
    },
    {
      "name": "skill-243",
      "path": "~/.no-mistakes/worktrees/32d18ed9638d/01M4D34BGQE483Q5806J9GM9AX/.v/project/.agents/skills/skill-243/SKILL.md",
      "description": "Documented procedure 243",
      "excerpt": "# Procedure\nFollow the procedure for skill-243.\n"
    },
    {
      "name": "skill-244",
      "path": "~/.no-mistakes/worktrees/32d18ed9638d/01M4D34BGQE483Q5806J9GM9AX/.v/project/.agents/skills/skill-244/SKILL.md",
      "description": "Documented procedure 244",
      "excerpt": "# Procedure\nFollow the procedure for skill-244.\n"
    },
    {
      "name": "skill-245",
      "path": "~/.no-mistakes/worktrees/32d18ed9638d/01M4D34BGQE483Q5806J9GM9AX/.v/project/.agents/skills/skill-245/SKILL.md",
      "description": "Documented procedure 245",
      "excerpt": "# Procedure\nFollow the procedure for skill-245.\n"
    },
    {
      "name": "skill-246",
      "path": "~/.no-mistakes/worktrees/32d18ed9638d/01M4D34BGQE483Q5806J9GM9AX/.v/project/.agents/skills/skill-246/SKILL.md",
      "description": "Documented procedure 246",
      "excerpt": "# Procedure\nFollow the procedure for skill-246.\n"
    },
    {
      "name": "skill-247",
      "path": "~/.no-mistakes/worktrees/32d18ed9638d/01M4D34BGQE483Q5806J9GM9AX/.v/project/.agents/skills/skill-247/SKILL.md",
      "description": "Documented procedure 247",
      "excerpt": "# Procedure\nFollow the procedure for skill-247.\n"
    },
    {
      "name": "skill-248",
      "path": "~/.no-mistakes/worktrees/32d18ed9638d/01M4D34BGQE483Q5806J9GM9AX/.v/project/.agents/skills/skill-248/SKILL.md",
      "description": "Documented procedure 248",
      "excerpt": "# Procedure\nFollow the procedure for skill-248.\n"
    },
    {
      "name": "skill-249",
      "path": "~/.no-mistakes/worktrees/32d18ed9638d/01M4D34BGQE483Q5806J9GM9AX/.v/project/.agents/skills/skill-249/SKILL.md",
      "description": "Documented procedure 249",
      "excerpt": "# Procedure\nFollow the procedure for skill-249.\n"
    },
    {
      "name": "skill-250",
      "path": "~/.no-mistakes/worktrees/32d18ed9638d/01M4D34BGQE483Q5806J9GM9AX/.v/project/.agents/skills/skill-250/SKILL.md",
      "description": "Documented procedure 250",
      "excerpt": "# Procedure\nFollow the procedure for skill-250.\n"
    },
    {
      "name": "skill-251",
      "path": "~/.no-mistakes/worktrees/32d18ed9638d/01M4D34BGQE483Q5806J9GM9AX/.v/project/.agents/skills/skill-251/SKILL.md",
      "description": "Documented procedure 251",
      "excerpt": "# Procedure\nFollow the procedure for skill-251.\n"
    },
    {
      "name": "skill-252",
      "path": "~/.no-mistakes/worktrees/32d18ed9638d/01M4D34BGQE483Q5806J9GM9AX/.v/project/.agents/skills/skill-252/SKILL.md",
      "description": "Documented procedure 252",
      "excerpt": "# Procedure\nFollow the procedure for skill-252.\n"
    },
    {
      "name": "skill-253",
      "path": "~/.no-mistakes/worktrees/32d18ed9638d/01M4D34BGQE483Q5806J9GM9AX/.v/project/.agents/skills/skill-253/SKILL.md",
      "description": "Documented procedure 253",
      "excerpt": "# Procedure\nFollow the procedure for skill-253.\n"
    },
    {
      "name": "skill-254",
      "path": "~/.no-mistakes/worktrees/32d18ed9638d/01M4D34BGQE483Q5806J9GM9AX/.v/project/.agents/skills/skill-254/SKILL.md",
      "description": "Documented procedure 254",
      "excerpt": "# Procedure\nFollow the procedure for skill-254.\n"
    },
    {
      "name": "skill-255",
      "path": "~/.no-mistakes/worktrees/32d18ed9638d/01M4D34BGQE483Q5806J9GM9AX/.v/project/.agents/skills/skill-255/SKILL.md",
      "description": "Documented procedure 255",
      "excerpt": "# Procedure\nFollow the procedure for skill-255.\n"
    },
    {
      "name": "skill-256",
      "path": "~/.no-mistakes/worktrees/32d18ed9638d/01M4D34BGQE483Q5806J9GM9AX/.v/project/.agents/skills/skill-256/SKILL.md",
      "description": "Documented procedure 256",
      "excerpt": "# Procedure\nFollow the procedure for skill-256.\n"
    },
    {
      "name": "skill-257",
      "path": "~/.no-mistakes/worktrees/32d18ed9638d/01M4D34BGQE483Q5806J9GM9AX/.v/project/.agents/skills/skill-257/SKILL.md",
      "description": "Documented procedure 257",
      "excerpt": "# Procedure\nFollow the procedure for skill-257.\n"
    },
    {
      "name": "skill-258",
      "path": "~/.no-mistakes/worktrees/32d18ed9638d/01M4D34BGQE483Q5806J9GM9AX/.v/project/.agents/skills/skill-258/SKILL.md",
      "description": "Documented procedure 258",
      "excerpt": "# Procedure\nFollow the procedure for skill-258.\n"
    },
    {
      "name": "skill-259",
      "path": "~/.no-mistakes/worktrees/32d18ed9638d/01M4D34BGQE483Q5806J9GM9AX/.v/project/.agents/skills/skill-259/SKILL.md",
      "description": "Documented procedure 259",
      "excerpt": "# Procedure\nFollow the procedure for skill-259.\n"
    },
    {
      "name": "claude-only",
      "path": "~/.no-mistakes/worktrees/32d18ed9638d/01M4D34BGQE483Q5806J9GM9AX/.v/project/.claude/skills/claude-only/SKILL.md",
      "description": "Claude-only documented procedure",
      "excerpt": "# Procedure\nFollow the procedure for claude-only.\n"
    }
  ],
  "not_judged": [
    {
      "name": "linked-skill",
      "reason": "not a Git-tracked file in this project"
    },
    {
      "name": "local-only",
      "reason": "not a Git-tracked file in this project"
    }
  ]
}
  • Evidence: Current ship and scout terminal transcript export (local file: ~/.no-mistakes/evidence/01M4D34BGQE483Q5806J9GM9AX/current-worker-transcripts.html)
  • Evidence: Actual tmux and Treehouse worker delivery commands (local file: ~/.no-mistakes/evidence/01M4D34BGQE483Q5806J9GM9AX/live-worker-delivery.txt)
Evidence: Actual failed doorbell relaunch transcript
$ ~/.no-mistakes/worktrees/32d18ed9638d/01M4D34BGQE483Q5806J9GM9AX/bin/fm-lab-home.sh create ~/.no-mistakes/worktrees/32d18ed9638d/01M4D34BGQE483Q5806J9GM9AX/.v/failure-lab
~/.no-mistakes/worktrees/32d18ed9638d/01M4D34BGQE483Q5806J9GM9AX/.v/failure-lab
exit=0

$ tmux -S ~/.no-mistakes/worktrees/32d18ed9638d/01M4D34BGQE483Q5806J9GM9AX/.v/f -f /dev/null new-session -d -s labdriver -x 120 -y 40 -c ~/.no-mistakes/worktrees/32d18ed9638d/01M4D34BGQE483Q5806J9GM9AX '/bin/bash --noprofile --norc'
exit=0

$ tmux -S ~/.no-mistakes/worktrees/32d18ed9638d/01M4D34BGQE483Q5806J9GM9AX/.v/f set-option -g default-shell /bin/bash
exit=0

$ git init -q -b main ~/.no-mistakes/worktrees/32d18ed9638d/01M4D34BGQE483Q5806J9GM9AX/.v/failure-project
exit=0

$ git -C ~/.no-mistakes/worktrees/32d18ed9638d/01M4D34BGQE483Q5806J9GM9AX/.v/failure-project add README.md
exit=0

$ git -C ~/.no-mistakes/worktrees/32d18ed9638d/01M4D34BGQE483Q5806J9GM9AX/.v/failure-project -c user.name=Lab -c user.email=lab@example.invalid commit -qm initial
exit=0

$ ~/.no-mistakes/worktrees/32d18ed9638d/01M4D34BGQE483Q5806J9GM9AX/bin/fm-brief.sh failed-delivery failure-project --mode local-only
scaffolded: ~/.no-mistakes/worktrees/32d18ed9638d/01M4D34BGQE483Q5806J9GM9AX/.v/failure-lab/data/failed-delivery/brief.md (ship, mode=local-only; replace {TASK} and {FIRSTMATE_SPEC})
exit=0

$ tmux -S ~/.no-mistakes/worktrees/32d18ed9638d/01M4D34BGQE483Q5806J9GM9AX/.v/f send-keys -t labdriver -l 'env FM_SPAWN_NO_GUARD=1 ~/.no-mistakes/worktrees/32d18ed9638d/01M4D34BGQE483Q5806J9GM9AX/bin/fm-spawn.sh failed-delivery ~/.no-mistakes/worktrees/32d18ed9638d/01M4D34BGQE483Q5806J9GM9AX/.v/failure-project --harness '"'"'cat __BRIEF__'"'"' --mode local-only --yolo off > ~/.no-mistakes/worktrees/32d18ed9638d/01M4D34BGQE483Q5806J9GM9AX/.v/initial.out 2>&1; printf "%s" "$?" > ~/.no-mistakes/worktrees/32d18ed9638d/01M4D34BGQE483Q5806J9GM9AX/.v/initial.exit'
exit=0

$ tmux -S ~/.no-mistakes/worktrees/32d18ed9638d/01M4D34BGQE483Q5806J9GM9AX/.v/f send-keys -t labdriver Enter
exit=0

fm-gate-refuse: gate agent lifecycle permitted only against lab home ~/.no-mistakes/worktrees/32d18ed9638d/01M4D34BGQE483Q5806J9GM9AX/.v/failure-lab
notice: failed-delivery ships mode=local-only while the standing posture for failure-project is no-mistakes - less rigor than the captain's standing posture; proceed only on a current explicit captain instruction or an intake judgment you can state
skill selection for failed-delivery: unavailable - no TypeSafe or OpenRouter key
spawned failed-delivery harness=cat kind=ship mode=local-only yolo=off window=labdriver:fm-failed-delivery worktree=~/.no-mistakes/worktrees/32d18ed9638d/01M4D34BGQE483Q5806J9GM9AX/.v/failure-pool/.treehouse/failure-project-877768/1/failure-project

$ tmux -S ~/.no-mistakes/worktrees/32d18ed9638d/01M4D34BGQE483Q5806J9GM9AX/.v/f send-keys -t labdriver -l 'env FM_SPAWN_NO_GUARD=1 ~/.no-mistakes/worktrees/32d18ed9638d/01M4D34BGQE483Q5806J9GM9AX/bin/fm-spawn.sh failed-delivery --relaunch --harness '"'"'echo __BRIEFDOORBELL__'"'"' > ~/.no-mistakes/worktrees/32d18ed9638d/01M4D34BGQE483Q5806J9GM9AX/.v/relaunch.out 2>&1; printf "%s" "$?" > ~/.no-mistakes/worktrees/32d18ed9638d/01M4D34BGQE483Q5806J9GM9AX/.v/relaunch.exit'
exit=0

$ tmux -S ~/.no-mistakes/worktrees/32d18ed9638d/01M4D34BGQE483Q5806J9GM9AX/.v/f send-keys -t labdriver Enter
exit=0

fm-gate-refuse: gate agent lifecycle permitted only against lab home ~/.no-mistakes/worktrees/32d18ed9638d/01M4D34BGQE483Q5806J9GM9AX/.v/failure-lab
notice: failed-delivery ships mode=local-only while the standing posture for failure-project is no-mistakes - less rigor than the captain's standing posture; proceed only on a current explicit captain instruction or an intake judgment you can state
skill selection for failed-delivery: unavailable - no TypeSafe or OpenRouter key
error: could not publish the launch brief for failed-delivery as an operational-inbox record under ~/.no-mistakes/worktrees/32d18ed9638d/01M4D34BGQE483Q5806J9GM9AX/.v/failure-lab/state; echo strips the typed operational marker, so the worker was not launched

$ tmux -S ~/.no-mistakes/worktrees/32d18ed9638d/01M4D34BGQE483Q5806J9GM9AX/.v/f kill-server
exit=0
Evidence: Retained undelivered selection metadata
window=labdriver:fm-failed-delivery
endpoint_task_id=failed-delivery
worktree=~/.no-mistakes/worktrees/32d18ed9638d/01M4D34BGQE483Q5806J9GM9AX/.v/failure-pool/.treehouse/failure-project-877768/1/failure-project
project=~/.no-mistakes/worktrees/32d18ed9638d/01M4D34BGQE483Q5806J9GM9AX/.v/failure-project
harness=echo
kind=ship
mode=local-only
yolo=off
branch=fm/failed-delivery
tasktmp=/tmp/fm-failed-delivery
model=default
effort=default
spawn_gen=s1791457993.31923.27172
skill_selection=undelivered
skill_selection_reason=could not publish the launch brief as an operational-inbox record
  • Evidence: Actual changed-test selections for spawn and sibling commands (local file: ~/.no-mistakes/evidence/01M4D34BGQE483Q5806J9GM9AX/changed-test-selection.txt)
  • Evidence: Current-run verification and cleanup notes (local file: ~/.no-mistakes/evidence/01M4D34BGQE483Q5806J9GM9AX/current-test-run-notes.json)
  • Outcome: ⚠️ 1 warning across 2 runs (33m52s)

Pipeline

Updates from git push no-mistakes

✅ **intent** - passed

✅ No issues found.

🔧 **Rebase** - 6 issues found → auto-fixed ✅
  • ⚠️ .agents/skills/operational-home-layout/SKILL.md - merge conflict rebasing onto origin/main
  • ⚠️ bin/fm-test-run.sh - merge conflict rebasing onto origin/main
  • ⚠️ bin/fm-typesafe-lib.sh - merge conflict rebasing onto origin/main
  • ⚠️ docs/configuration.md - merge conflict rebasing onto origin/main
  • ⚠️ docs/scripts.md - merge conflict rebasing onto origin/main
  • ⚠️ tests/fm-skill-suggest.test.sh - merge conflict rebasing onto origin/main

🔧 Fix applied.
✅ Re-checked - no issues remain.

🔧 **Review** - 1 issue found → auto-fixed (2) ✅
  • ⚠️ bin/fm-skill-pick.sh:3 - The new CLI wrapper is committed with mode 100644, preventing the direct invocation advertised here and by the error-help command at bin/fm-skill-pick.sh:24. Following bin/fm-skill-pick.sh --help therefore produces a permission error rather than help. Worker launches and the new tests explicitly invoke Bash, so those paths bypass this defect. Commit the wrapper as executable (100755), matching the removed CLI.

🔧 Fix applied.
1 warning still open:

  • ⚠️ bin/fm-test-run.sh:425 - The new picker suite owns worker-launch and failed-relaunch regressions but is classified as standalone. When only bin/fm-spawn.sh changes, --changed selects backend-dispatch and pure-contract-unit at bin/fm-test-run.sh:1641-1644, so tests/fm-skill-pick.test.sh is omitted. This leaves the new picker invocation at bin/fm-spawn.sh:4650 and failed-delivery metadata correction at bin/fm-spawn.sh:1526 without their targeted regression coverage. Related test consumers are tests/fm-skill-pick.test.sh:450 (launch integration) and :929 (failed relaunch delivery); the picker-source mapping at bin/fm-test-run.sh:1527 already selects this suite correctly. Add an explicit fm-skill-pick.test.sh selection for spawn changes while retaining the existing family selections.

🔧 Fix applied.
✅ Re-checked - no issues remain.

✅ No issues found.

⚠️ **Test** - 1 warning
  • ⚠️ The test step could not demonstrate the central intent end-to-end: authenticated selection of a relevant project skill followed by a real worker using it. Neither provider key was available in the process environment. Disposable credentials reached the real TypeSafe and OpenRouter endpoints, but both returned HTTP 401. Mocked regression checks passed, but they cannot establish actual selection quality or worker compliance. Provide usable provider credentials through the disposable lab configuration and rerun these scenarios; operator configuration and credential stores were left untouched.
  • ⚠️ live validation verdict: inconclusive (10 of 11 scenarios were driven live against the product); untested: Obtain an authenticated relevant-skill pick and have a real worker follow it
  • Live validation: ⚠️ inconclusive - 10 of 11 scenarios driven live against the product
Scenario Result Live Evidence
Invoke the picker directly and receive CLI help ✅ pass live CLI boundary evidence: picker-help.txt section.
Discover every project skill while preserving catalog precedence and excluding unsafe entries ✅ pass live Complete project roster and real outbound 301-skill request summary.
Launch ship and scout workers without provider keys and receive actionable skill instructions ✅ pass live Rendered worker transcripts and exact worker lifecycle commands.
Relaunch an agent-free scout and regenerate its skill-selection instructions ✅ pass live Rendered scout-relaunch transcript and worker lifecycle commands.
Launch a raw command without brief transport and avoid claiming a delivered selection ✅ pass live Rendered raw-command metadata and worker lifecycle commands.
Attempt to send forbidden task text or a forbidden skill description ✅ pass live CLI boundary evidence: privacy-task-refusal.txt and privacy-description-refusal.txt sections.
Select from a promoted legacy brief without reviving hidden or superseded instructions ✅ pass live Sanitized promoted outbound requests and CLI boundary evidence: promoted-private-task.txt section.
Distinguish an empty catalog from a catalog containing only excluded skills ✅ pass live CLI boundary evidence: empty-catalog-result.txt and excluded-catalog-result.txt sections.
Recover from direct-provider failure or key absence through the accepted OpenRouter fallback ✅ pass live CLI boundary evidence: real-provider-failure.txt and missing-direct-key-fallback.txt sections.
Run the picker from a child home and inherit the primary TypeSafe key without copying it ✅ pass live CLI boundary evidence: primary-home-inheritance.txt section.
Obtain an authenticated relevant-skill pick and have a real worker follow it ⏸️ untested no Neither provider key was present in the process environment. Disposable homes and synthetic project catalogs were built, and real requests reached both providers, but synthetic credentials returned HT…
  • bash tests/fm-skill-pick.test.sh &amp;&amp; bash tests/fm-typesafe-key-source.test.sh: initially interrupted by the 300-second command deadline during the picker suite; the chained key-source suite did not start.

  • bash tests/fm-skill-pick.test.sh: rerun with a 1200-second ceiling; passed in 507.47 seconds, including failed-delivery metadata and relaunch regressions.

  • bash tests/fm-typesafe-key-source.test.sh: passed independently.

  • test_changed_spawn_selects_picker_without_broadening_siblings: executed the existing focused runner regression; passed.

  • python3 .fm-live-validation.py: drove the executable picker, a 301-skill project roster, privacy refusals, promoted legacy extraction, and real provider-failure fallback. The disposable driver was removed and preserved in the evidence directory.

  • python3 .fm-live-workers.py: drove real ship/scout launches, scout relaunch, and unsupported raw-command delivery in isolated named Herdr labs. Corrected session-wrapper, terminal-wrapping, and relaunch-argument setup issues before obtaining passing results.

  • Additional public-picker invocations exercised empty versus excluded catalogs, sanitized promoted outbound requests, missing-TypeSafe-key OpenRouter fallback, and primary-home key inheritance without a lane .env copy.

  • Captured actual terminal output, generated launch briefs, persisted metadata, and real outbound request bodies without credential values; rendered worker transcripts as HTML.

  • Tore down all disposable Herdr sessions through bin/fm-herdr-lab.sh, with passing default-fleet tripwires, and removed workspace fixtures and throwaway drivers. No source or permanent test changes were made.

  • ⚠️ live validation verdict: inconclusive (13 of 14 scenarios were driven live against the product); untested: Launch a real AI worker with an authenticated relevant project-skill pick and observe it follow the skill

  • Live validation: ⚠️ inconclusive - 13 of 14 scenarios driven live against the product

Scenario Result Live Evidence
Invoke the advertised picker CLI directly and receive help ✅ pass live live-cli.txt: direct bin/fm-skill-pick.sh --help invocation exited successfully.
Run selection without credentials and receive explicit manual skill-index guidance ✅ pass live live-cli.txt: unavailable result names the missing keys and instructs the worker to check its skill index.
Enumerate a large project catalog while preserving precedence and excluding non-eligible skills ✅ pass live project-roster.json: 261 eligible skills retained, earlier .agents duplicate wins, untracked and linked skills are not judged, and the skill-less plugin link is ignored.
Attempt selection with forbidden task text and a late-roster skill identity ✅ pass live live-cli.txt: both inputs produce dispatch-never-send policy refusals rather than selection or provider fallback.
Extract promoted legacy captain intent and current ship instructions without exposing protected or stale text ✅ pass live live-cli.txt: extraction retains permitted legacy captain words and the current ship spec, excludes stale, fenced and protected text, and leaves the output mode at 600 under umask 022.
Select against an absent catalog and distinguish no skills from unavailable selection ✅ pass live live-cli.txt: actual CLI records status=none for an absent catalog.
Reject invalid credentials at both real providers while retaining fallback provenance ✅ pass live live-cli.txt: real TypeSafe and OpenRouter requests receive HTTP 401; the unavailable record includes TypeSafe direct failure and OpenRouter fallback.
Resolve a nested local home's primary TypeSafe key without copying it ✅ pass live live-cli.txt: a nested home with no own key reaches the picker policy check using its disposable primary-home binding.
Disable actual Node TypeScript support and receive actionable unavailable guidance ✅ pass live live-cli.txt: NODE_OPTIONS=--no-experimental-strip-types causes the real vendored-client import to produce the supported runtime diagnostic.
Supply malformed privacy markers or a non-directory catalog and receive a specific refusal ✅ pass live live-cli.txt: an unclosed protected region, non-regular policy and non-directory catalog each produce a concrete unavailable reason.
Launch ship and scout raw-command workers and deliver picker fallback instructions in isolated worktrees ✅ pass live current-worker-transcripts.html and live-worker-delivery.txt: actual tmux/Treehouse workers consume their generated BRIEF instructions, including unavailable/manual-index guidance; metadata record…
Fail an actual doorbell relaunch and retain undelivered selection metadata without a claimed pick ✅ pass live failed-delivery.txt and failed-delivery-metadata.txt: deliberate inbox publication failure retains skill_selection=undelivered, the publication reason, and no skill_selection_picked field.
Ask changed-test selection to cover a spawn-only change without broadening sibling commands ✅ pass live changed-test-selection.txt: actual runner CLI includes the picker for fm-spawn.sh only, while retaining backend-dispatch and pure-contract coverage for all six exercised inputs.
Launch a real AI worker with an authenticated relevant project-skill pick and observe it follow the skill ⏸️ untested no No usable TypeSafe or OpenRouter credentials were present in the process environment. Disposable credentials reached both real endpoints but received HTTP 401. Isolated fixtures therefore exercised er…
  • bash tests/fm-skill-pick.test.sh &amp;&amp; bash tests/fm-typesafe-key-source.test.sh — initial attempt reached the 300-second command limit during the picker suite, before the key-source command ran.
  • bash tests/fm-skill-pick.test.sh — completed successfully with a longer command allowance.
  • bash tests/fm-typesafe-key-source.test.sh — passed.
  • bash tests/fm-test-run.test.sh — passed the spawn-selection regression before reaching the 600-second command limit in later checks.
  • Executed only test_changed_spawn_selects_picker_without_broadening_siblings from the existing runner contract test through a temporary harness — passed; harness removed.
  • Drove bin/fm-test-run.sh --list --changed --base HEAD against six isolated single-file changes, verifying picker selection for spawn only and preservation of existing family selections.
  • Drove the executable bin/fm-skill-pick.sh and node bin/fm-skill-pick.mjs roster against disposable catalogs, homes, privacy policies and actual TypeSafe/OpenRouter endpoints without transport mocks.
  • Executed fm_typesafe_brief_task for a promoted legacy brief under umask 022, checking effective task text and owner-only output permissions.
  • Ran python3 .v/spawn.py against actual private tmux and Treehouse instances; joined terminal captures verified ship and scout brief delivery after correcting a soft-wrapping assertion.
  • Ran python3 .v/failure.py against an actual failed doorbell relaunch, verifying retained undelivered metadata.
  • Exported current terminal transcripts as rendered HTML, stopped private tmux servers, and removed disposable homes, projects, pools and temporary test drivers.
✅ **Document** - passed

✅ No issues found.

✅ No issues found.

✅ **Lint** - passed

✅ No issues found.

✅ No issues found.

✅ **Push** - passed

✅ No issues found.

✅ No issues found.

Every ship and scout launch and relaunch now runs bin/fm-skill-pick.sh once
its task copy exists. It judges the copy's .agents/skills and .claude/skills
(an earlier folder wins a duplicate name) and adds the one picked skill to
the launch instructions as a skill to read in full and follow, after the
existing mandatory triggers. When nothing fits or the picker cannot run, the
launch continues and the instructions and task record say why.

The recipe is the TypeSafe skill-suggestion cookbook (rank the whole roster
with three need gates, then rerank the top three with 700-character excerpts
and one fits noul each; 0.30 and 0.30 thresholds), with the cookbook's
question texts verbatim. It runs on the vendored hyper-jev starter client,
imported unchanged from .agents/skills/hyper-jev/templates/starter/src/core/.
This replaces bin/fm-skill-suggest.sh, bin/fm-skill-catalog.jq and their
test, which ran only when a brief carried a "Skill selection input" section.

The brief text sent is the text dispatch resolution may send:
fm_typesafe_brief_task moves from bin/fm-dispatch-resolve.sh into
bin/fm-typesafe-lib.sh and is shared, and every string either request can
carry passes the dispatch-never-send check first. Keys come from the home
.env through fm_typesafe_key and the new fm_openrouter_key, and reach Node
on stdin.

System assessment

Dependency map:
- bin/fm-spawn.sh -> bin/fm-skill-pick.sh (bounded by fm_run_timed at 30 s,
  process-group kill) -> bin/fm-typesafe-lib.sh (brief text, never-send,
  keys) and bin/fm-skill-pick.mjs -> vendored hyper-jev client -> TypeSafe
  direct, then OpenRouter on a failed direct call.
- bin/fm-dispatch-resolve.sh -> fm_typesafe_brief_task (moved, unchanged
  behavior; tests/fm-dispatch-resolve.test.sh passes).
- The launch section is written before the no-mistakes intent overlay, so it
  never becomes --intent text.
- Runtime needs: node with .ts type stripping (Node 24+, as the vendored
  starter declares) and jq; without either the launch proceeds and records
  the reason.

Blast radius:
- Every ship and scout launch and relaunch gains one picker run and one
  "# Skill selection" section; secondmate charters are not touched.
- No key: the picker stops after the key check (0.05 s), makes no network
  call, and records status unavailable; launch otherwise unchanged.
- Hung or failing provider: each call is bounded at 10 s, the whole pick at
  25 s inside the 30 s spawn bound.
- The task record gains skill_selection=, skill_selection_reason= and
  skill_selection_picked=, owned and preserved across relaunch.

Spawn latency:
- Live, firstmate's 29 skills, synthetic task, TypeSafe direct: 0.90 s,
  1.14 s, 1.50 s for the whole picker (jev-1.13.0, picked afk).
- Offline, Vernant's real catalog: 245 skills in one Choice; request 1 is
  48,689 bytes, request 2 is 3,903 bytes; 1.40 s local wall time at load
  average about 265 on 18 cores.

Deviations from the examples:
- bin/fm-skill-pick.mjs:169: a failed direct TypeSafe call is retried once
  through OpenRouter, which serves the rest of the pick; the starter picks
  one provider by key presence and never switches, and OpenRouter is the
  required fallback for a failed direct call.
- bin/fm-typesafe-lib.sh:34: the OpenRouter key is read from the home .env
  only, never the ambient environment, where another project's key may live.
- bin/fm-skill-pick.mjs:50-51: per-call timeoutMs 10 s and an overall 25 s
  signal, because the starter's 30 s default per call exceeds the spawn bound.
- bin/fm-skill-pick.mjs:184-187: above 255 skills the Choice is split into
  which::N questions in the same request, as the cookbook advises; no skill
  is dropped.
- bin/fm-skill-pick.mjs:72: SKILL.md frontmatter replaces the cookbook's
  prepared roster file; one description serves as description and
  description_full.
- bin/fm-skill-pick.mjs:129: only Git-tracked SKILL.md files are sent, as
  before; other skills are listed by name as not judged.
- bin/fm-skill-pick.sh:53: "read this skill in full and follow it" replaces
  the cookbook's "Ignore this if it does not fit".
- The starter's default jev-latest replaces the cookbook's pinned jev-1.12.
…ck’s memory limit (exit 251, peak RSS 8,388,000 KiB). Removed redundant direct imports of fm-pr-lib.sh and fm-classify-lib.sh; fm-dod-lib.sh still loads both before use, preserving full source-aware analysis and launch behavior. Updated lint ownership documentation without changing CI limits or diagnostic rules. Verification: uncached full spawn lint passed with peak RSS 7,767,568 KiB on Darwin; the complete fm-skill-pick behavioral suite passed when run without concurrent heavy lint; pinned actionlint validated all three workflows. The broader lint regression suite and canonical partition exceeded local command deadlines. Linux address-space enforcement remains unverified locally because macOS cannot enforce that CI bound. Temporary verification files were removed
@MrGTV-love
MrGTV-love force-pushed the fm/fm-worker-project-skill-picker branch from 0ce3d26 to 555f6d4 Compare October 8, 2026 11:36
@MrGTV-love
MrGTV-love merged commit 32fbe8e into main Oct 8, 2026
21 checks passed
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