agentleFS
etinpresetinpres/assemble/SKILL.md

assemble

Skill2 starsChanged 6 months agoMITView on GitHub

Tool concierge — scan installed Claude Code skills/plugins/agents, recommend a per-stage workflow, and let the user pick which tool to use at each stage. Use when the user types `/assemble <task description>` or `/assemble resume` or `/assemble list`.

3 other projects use a version of this.

Adopted by

Most recently taken up by

  • Other versions3 projects use a version of this.

    Word for word · 0

    None found.

    Edited · 0

    None found.

    Wrote their own version · 3

    1. mrdainami/omni-video-director★ 16same name
    2. lucasacoutinho/dds★ 2same name
    3. Mharbulous/probanda-dev★ 1same name

    How matches are found. Word for word: the exact same text. Edited: shares passages with this file (“99% same” means 99% of its five-word phrases also appear here; 10% or more counts). Their own version: similar meaning, or, weakest, the same name. We can’t tell which project had it first. Projects are counted among the 200 most-starred candidates, and mentions are links to the repository we found, up to 50 per site, so both counts are a floor.

The file

We split this skill into the rules it gives an agent

11 rules. 0 are common, 4 are distinctive, and 7 are very specific to this project.

  • 0

    Common

    Most of the 3 edited versions we read keep these.

  • 4

    Distinctive

    This author's own choices. Worth a look.

  • 7

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Distinctive:

    When invoked, follow this 6-phase loop.

    line 8
  2. Very specific:

    All Python snippets must use from server import … — never reference submodules (server.progress, server.inventory, etc.) directly.

    line 17
  3. Distinctive:

    After §1 Boot extracts the task token, additionally check for sub-command keywords.

    line 39
  4. Very specific:

    If no sub-command keyword matches, proceed to §1 Boot's existing resume/list/task-string routing.

    line 49
  5. Very specific:

    Current approach: every skill self-describes via its own SKILL.md frontmatter (description + first 500 chars of body).

    line 74
  6. Distinctive:

    Everything else is left alone: If a tie or zero-score set means nothing qualified, tell the user once:

    line 109
  7. Very specific:

    Use the localized version via t("notices.unclassified_hint", n=<n>) (the bash snippet below prints it for you).

    line 113
  8. Distinctive:

    Never bundle 3+ stages. Selection quality beats perceived speed.

    line 156
  9. Very specific:

    When the tool finishes: mark_stage('<rid>','<stage>','done',tool_used='<name>')

    line 178
  10. Very specific:

    If <run_id> given → use it directly.

    line 195
  11. Very specific:

    After the last stage hits done (or the user picks done):

    line 213

“Common” is measured: kept by at least 6 in 10 of the 3 edited versions of this file we could read. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

/assemble — Tool Concierge

When invoked, follow this 6-phase loop. Every step is mandatory unless explicitly marked optional.

0. Prerequisites

  • ~/.claude/skills/assemble/server/{state_store,inventory,classify,sequence,menu,progress,i18n}.py must exist
  • ~/.claude/skills/assemble/server/__init__.py exposes the full facade — always import from server, never from submodules
  • Python 3 (stdlib only)

Facade convention (important)

All Python snippets must use from server import … — never reference submodules (server.progress, server.inventory, etc.) directly. This removes guesswork about where each function lives and keeps the next LLM from tripping over ImportError when a module gets refactored.

Exported symbols:

scan, apply_classification,
unclassified_names, unclassified_entries,
parse_skill_frontmatter, load_stages, load_stage_roles,
create_run, load_progress, mark_stage, list_runs, find_resumable,
build_stage_options, tools_for_stage, contextual_helpers,
build_classify_prompt, parse_classify_response,
build_sequence_prompt, parse_sequence_response,

unclassified_entries() returns a flat list of unclassified items — each entry is {'kind': 'skill'|'agent', 'name': ..., 'path': ...}. It is not bucketed by kind; the kind field lives on each row. unclassified_names() is kept for back-compat as skills-only (returns a list of names).

Language

User-facing text is picked via ASSEMBLE_LOCALE (default en; ko ships in-tree). When you address the user directly, match their conversation language. The menu labels and stage descriptions rendered by build_stage_options / load_stages already respect the locale — don't translate those manually.

Sub-commands

After §1 Boot extracts the task token, additionally check for sub-command keywords. If matched, route to the corresponding flow doc and STOP §2~§7 default concierge flow:

Keyword (first arg token)Flow docSpike
eject~/.claude/skills/assemble/docs/eject-flow.mdXII
(future) roles(deferred)—
(future) import(deferred)V5

Behavior: if first token after /assemble is a sub-command keyword, read the flow doc and follow it line-by-line. Default concierge flow (§2 inventory refresh through §7 list) is bypassed.

If no sub-command keyword matches, proceed to §1 Boot's existing resume/list/task-string routing.

1. Boot

Strip the trailing assemble/leading /assemble token to extract the task.

Routing:

  • /assemble resume [<run_id>] → §5 Resume
  • /assemble list → §7 List
  • otherwise → §2

2. Inventory refresh

cd ~/.claude/skills/assemble && python3 -c "
from server import scan, unclassified_names
inv = scan()
print(f'INV_OK skills={len(inv[\"skills\"])} agents={len(inv[\"agents\"])} unclassified={len(unclassified_names())}')
"

Classification policy (pre_mapping retired, 2026-04-21 rework):

Earlier builds shipped config/pre_mapping.json — a name→stage table. That approach had a fundamental flaw: you can't pre-map tools you don't know are installed. A fresh user's Mac dumped most skills into unclassified because the table only covered the original author's toolchain.

Current approach: every skill self-describes via its own SKILL.md frontmatter (description + first 500 chars of body). scan() reads that and classifies.

  1. Heuristic (first pass) — _classify_heuristic in inventory.py requires ≥2 stage-keyword hits in the description to mark a skill heuristic-classified. Safety/meta skills also get a role hint so contextual_helpers picks them up.
  2. Heuristic miss → unclassified — the skill isn't visible in any stage menu yet.
  3. Task-relevance inline (LLM, second pass) — score unclassified entries against the /assemble <task> string by token overlap on name/description. For the top 2 only, build the classify prompt, reply with JSON yourself (you are the LLM — no external call), then validate and apply.

    Canonical invocation (works for both skills and agents — no kind argument; the facade keys on name):

    from pathlib import Path
    from server import (
        parse_skill_frontmatter,
        build_classify_prompt,
        parse_classify_response,
        apply_classification,
    )
    
    fm = parse_skill_frontmatter(Path(entry['path']))  # entry from unclassified_entries()
    prompt = build_classify_prompt(
        name=fm['name'],
        description=fm.get('description', ''),
        body_excerpt=fm.get('body_excerpt', ''),
    )
    # Reply to `prompt` yourself with the JSON format it specifies.
    parsed = parse_classify_response(your_json_reply)
    apply_classification(
        name=fm['name'],
        mappings=parsed['mappings'],
        confidence=parsed['confidence'],
        reasoning=parsed['reasoning'],
    )
    • confidence == high → apply directly as shown above
    • confidence in {medium, low} → one AskUserQuestion to confirm before applying
  4. Everything else is left alone. If a tie or zero-score set means nothing qualified, tell the user once:

    "{n} skill(s) are unclassified and won't appear in this run's menus. To bulk-classify, run:\n ~/.claude/skills/assemble/bin/classify-inventory"

    Use the localized version via t("notices.unclassified_hint", n=<n>) (the bash snippet below prints it for you).

  5. User override wins — anything stored as llm-classified via apply_classification() is preserved across scans, and heuristic cannot overwrite it.

Outcome: classification is driven entirely by what's installed in front of you. No global table to maintain.

3. Sequence recommendation

cd ~/.claude/skills/assemble && python3 -c "
from server import build_sequence_prompt
import sys
print(build_sequence_prompt(task=sys.argv[1]))
" "<task>"

Reply with a JSON sequence yourself. Validate with parse_sequence_response. Then ask via AskUserQuestion:

question: "Does this sequence look right? <stage1> → <stage2> → ..."
options:  ["approve", "edit", "cancel"]
  • approve → continue
  • edit → follow-up AskUserQuestion to add/remove stages (multi-select, options = 8 sequential stages)
  • cancel → STOP

Then create the run:

python3 -c "
from server import create_run
print(create_run(task='<task>', sequence=<approved sequence>))
"

4. Stage loop

Bundling policy (revised after 2026-04-21 review). An earlier version batched up to 4 stages into a single AskUserQuestion. Problem: stages aren't really independent (plan shapes design; execute failure drives debug), so pre-selected later-stage picks got stale. Current rules are conservative:

4a. When to bundle

  • Default: surface exactly one stage at a time. User answers → tool runs → mark done → advance → open next menu.
  • Preload exception: bundle at most 2 stages into one AskUserQuestion (2 questions) iff those stages are clearly independent. Independence check:
    • Stage A's outcome doesn't affect Stage B's tool choice (e.g. verify + ship is often independent; plan + design is not)
    • Both stages' tool candidates are already heuristic-classified or llm-classified (no unclassified mixed in)
  • Never bundle 3+ stages. Selection quality beats perceived speed.

4b. Build the menu

python3 -c "
import json
from server import build_stage_options
for s in ['<s1>','<s2>','<s3>']:
    print(s, json.dumps(build_stage_options(s), ensure_ascii=False))
"

Each stage becomes one AskUserQuestion question:

  • header: stage name (lowercase, ≤12 chars)
  • options: up to 3 tools + whichever of skip / manual / done fits the context. If there are more than 4, paginate: show top-3 recommended + one "more options" entry that reveals the rest on the next turn.

4c. Handle each response

As soon as a stage answer arrives:

  1. Invoke the picked tool (Skill/Agent, or invoke the helper directly)
  2. mark_stage('<rid>','<stage>','in_progress',tool_used='<name>')
  3. When the tool finishes: mark_stage('<rid>','<stage>','done',tool_used='<name>')

Note: done alone auto-backfills started_at if missing (2026-04-21 guard). Still, calling in_progress before done is the canonical pattern.

4d. Menu option semantics

  • tool → invoke the skill/agent; done when the tool returns.
  • helper → a meta/safety assist. Invoke it, mark only the helper, then re-open the same stage menu.
  • ask (물어보기 in ko) → reply with a paragraph recommending one of the listed tools given the task context, then re-render the menu.
  • skip → status='skipped', advance.
  • manual (직접 in ko) → status='manual', advance.
  • back → mark_stage(rid, cur_stage, status='back'). This moves the cursor only; it does not overwrite the stage's recorded status (pending/in_progress/done stay intact). Earlier versions persisted status='back', which broke wrap-up counts and find_resumable.
  • done → break out of the stage loop.

5. Resume

When /assemble resume:

  • If <run_id> given → use it directly.
  • Else: find_resumable(). Empty → say so and STOP. 1 → use it. Many → AskUserQuestion to pick.
  • Load progress, then announce using progress.resume_announce (see below for the localized template). Example output (en): "Resuming: <task>. Current stage: <s> (<idx+1>/<total>)".
  • Re-enter §4 from current_stage_index.
python3 -c "
from server import load_progress
from server.i18n import t
p = load_progress('<rid>')
idx = p['current_stage_index']
print(t('progress.resume_announce', task=p['task'],
        stage=p['stages'][idx]['stage'], idx=idx+1, total=len(p['stages'])))
"

6. Wrap-up

After the last stage hits done (or the user picks done):

python3 -c "
from server import load_progress
from server.i18n import t
from datetime import datetime
p = load_progress('<rid>')
created = datetime.fromisoformat(p['created_at'])
updated = datetime.fromisoformat(p['updated_at'])
duration = int((updated - created).total_seconds())
done    = sum(1 for s in p['stages'] if s['status'] == 'done')
skipped = sum(1 for s in p['stages'] if s['status'] == 'skipped')
manual  = sum(1 for s in p['stages'] if s['status'] == 'manual')
tools = sorted({s['tool_used'] for s in p['stages'] if s['tool_used']})
m, s = divmod(duration, 60)
tools_out = ', '.join(tools) or t('progress.no_tools')
print(t('progress.wrap_up', rid=p['run_id'], m=m, s=s,
        done=done, skipped=skipped, manual=manual, tools=tools_out))
print(t('progress.progress_path',
        path=f'~/.claude/channels/assemble/runs/{p[\"run_id\"]}/progress.json'))
"

(Optional) If the learn skill is installed, offer a retro.

7. List (past runs)

When /assemble list:

python3 -c "
from server import list_runs, load_progress
for rid in list_runs()[-10:]:
    p = load_progress(rid) or {}
    done = sum(1 for s in p.get('stages',[]) if s.get('status')=='done')
    total = len(p.get('stages',[]))
    status = 'done' if done == total else f'{done}/{total}'
    print(f'{rid}  [{status}]  {p.get(\"task\",\"\")[:60]}')
"

Show the output verbatim, then mention they can resume any line with /assemble resume <run_id>.

8. Troubleshooting

  • ImportError: cannot import 'X' from 'server.Y' — don't reach into submodules. Use from server import X. See §0.
  • started_at: NULL — pre-2026-04-21 build. Current mark_stage guards against this.
  • Classifications keep disappearing — the scan merge preserves llm-classified entries; heuristic-classified recomputes on every scan when the description changes (by design).
  • Lots of unclassified on a fresh install — expected after pre_mapping retirement. The heuristic catches descriptions with enough stage keywords; the rest land in unclassified until the user runs bin/classify-inventory --apply or the inline LLM pass covers them.
  • I've seen a pre-mapped source — legacy label. Current sources are heuristic-classified, llm-classified, unclassified.
  • inventory.json.bad-<ts> appeared — cache was corrupt, scan auto-quarantined + rebuilt. llm-classified entries survive unless the cache was destroyed mid-write; check with bin/classify-inventory --status.
  • User skill was overwritten by a plugin skill — pre-2026-04-21 precedence bug. Current enumerate_skill_paths returns user → plugin, so first-wins dedupe always keeps user.
  • Concurrent classify-inventory + /assemble lost a classification — pre-2026-04-21 race. Current apply_classification and scan share update_json_locked.
  • illegal transition: stage 'X' already 'done' — trying to drive done back into in_progress. Use back to move the cursor; don't call mark_stage again.

9. Internals: lazy-load policy

  • Each option returned by build_stage_options() is {label, kind, description, tool_path}. Tool-kind options additionally carry bundled: bool (V4 Phase A — True for entries under ~/.claude/skills/assemble/bundled/; bundled labels are prefixed with ★ and sorted before user/plugin tools). Description is capped at 80 chars, except bundled-only fallback options append a localized hint that may extend slightly past that cap. The skill body is never loaded until the user picks and the appropriate invocation path runs (see next bullet for slash-skill vs bundled distinction).
  • ★-prefixed bundled options are NOT slash-skill registry entries. When the user picks one, load it by Read on <tool_path>/SKILL.md and follow the body in-place. Do NOT call Skill(<name>) — that resolves slash-skills only and will return Unknown skill: <name> for bundled bundles. The tool_path field on each option is the absolute directory path to the bundle's SKILL.md (verified by enumerate_skill_paths). Example: Skill tool is for /loop//assemble/etc.; Read is for ★ plan-pack.
  • The cache at ~/.claude/channels/assemble/inventory.json stores a 500-char body_excerpt per skill, used for classification only — it never reaches the user menu.
  • Audit: confirmed 2026-04-21. Skill bodies don't leak into context.

10. Coexisting with other plugins

Other installed plugins (Vercel, gstack, context7, etc.) register their own SessionStart and file-pattern hooks. These hooks fire independently of /assemble — they're part of Claude Code's runtime, not something this skill triggers or controls.

Policy:

  • Ignore the hook output. Session-start preambles and file-pattern notices from other plugins aren't errors and don't affect the stage loop. Continue with §1–§7 as if they weren't there.
  • Those plugins' skills are still first-class tool candidates. scan() inventories every SKILL.md on disk regardless of which plugin owns it. After classification, they show up under the relevant stages (e.g. vercel-cli under ship, vercel-agent under review/debug, vercel-storage under execute). That's a feature — /assemble is a concierge over whatever the user has installed.
  • Don't suggest disabling other plugins as a workaround. The user installed them for reasons unrelated to /assemble.

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.