Summary
Replace the current two-layer symlink dance (.claude/skills/<name> → .agents/skills/<name> → skills/<name>) with a declarative host registry that lists every supported agent host and the directory layout each one expects. first-tree tree init should then generate the correct files (real directory or symlink, whichever fits the host) in one pass. first-tree skill doctor becomes a pure read-only check instead of a repair tool.
Motivation
Today the repo maintains:
skills/<name>/ — the authoritative payload.
.agents/skills/<name>/ — tracked symlink to ../../skills/<name>.
.claude/skills/<name>/ — tracked symlink to ../../.agents/skills/<name>.
Four skills × two alias layers = eight tracked symlinks just for Claude Code. Every new host (Codex already, Cursor likely, others possible) either needs its own alias layer added to the install flow, scripts/check-skill-sync.sh, and skill doctor, or silently fails to resolve the skills.
src/meta/skill-tools/ exists largely to repair this layout when it drifts (skill doctor diagnoses, skill link fixes). The fact we have a repair tool is a sign the layout is fragile.
Proposed design
1. Host registry — single source of truth
Add src/shared/host-registry.ts:
export interface HostLayout {
/** Stable identifier used in config / docs. */
readonly id: "claude-code" | "codex" | "cursor" | ...;
/** Human-readable name shown in `skill list` output. */
readonly displayName: string;
/** Where this host looks for skills, relative to repo root. */
readonly skillRoot: string; // e.g. ".claude/skills"
/** Materialization strategy. */
readonly strategy: "symlink-to-agents" | "real-dir" | "symlink-to-source";
}
export const HOSTS: readonly HostLayout[] = [
{ id: "claude-code", ..., skillRoot: ".claude/skills", strategy: "symlink-to-agents" },
{ id: "codex", ..., skillRoot: ".codex/skills", strategy: "symlink-to-source" },
// …
];
2. Installer consumes the registry
first-tree tree init / first-tree tree upgrade iterates HOSTS × ALL_SKILL_NAMES and materializes each (host, skill) pair once, using the strategy declared in the registry. No more hand-written alias lists.
3. skill doctor becomes read-only
Rewrite src/meta/skill-tools/engine/commands/doctor.ts to:
- Iterate the registry.
- Report mismatches as diagnostics only.
- Exit non-zero but never mutate the filesystem.
4. skill link becomes a thin re-run of installer
Rewrite src/meta/skill-tools/engine/commands/link.ts to delegate to the installer's materialize step. The command is preserved for convenience ("re-apply the layout") but no longer carries bespoke repair logic.
5. scripts/check-skill-sync.sh reads the registry
Right now the script hard-codes every expected alias in require_symlink_target calls. After this change it should be driven by the same registry — or be replaced entirely by a first-tree skill doctor --strict invocation.
Out of scope
- Adding new host integrations in this issue. The goal is to make adding hosts declarative; actual support for Cursor / other hosts follows in dedicated issues that just add registry entries.
- Changing how
skills/<name>/ themselves are authored.
Deliverables
src/shared/host-registry.ts with HOSTS + HostLayout type.
- Installer rewritten to iterate the registry (in
src/products/tree/engine/runtime/installer.ts).
doctor rewritten as read-only; link rewritten to delegate to installer.
scripts/check-skill-sync.sh simplified (ideally deleted in favor of first-tree skill doctor --strict in CI).
- Docs update:
docs/architecture/overview.md gains a "Host integration" section; src/meta/skill-tools/README.md points at the registry.
- Tests in
tests/meta/ covering: registry-driven install, doctor's read-only behavior, adding a fake host in a test-only registry override.
Acceptance criteria
- Adding a new agent host requires exactly one code change: adding an entry to
HOSTS. Tests, installer, doctor, and CI script pick it up automatically.
first-tree skill doctor never writes to the filesystem; running it on a clean checkout is a no-op.
first-tree skill link can fully rebuild the host-side layout from the registry alone (deleting .claude/skills then running link restores it).
- All existing tests pass without modification; new tests cover the registry path.
- The four Claude Code alias symlinks currently tracked in git remain functionally equivalent — users and CI see no breakage.
Priority
P2 — Medium. Not urgent (today's system works) but the sooner this lands, the easier every future host integration becomes. Block on this before adding support for any third host (Cursor, etc.) — adding one more hand-maintained alias layer would cement the debt.
Notes
- This deletes a surprising amount of code. The current repair flow (
skill doctor / skill link) is ~150 lines across src/meta/skill-tools/engine/commands/ that becomes ~40 once the registry exists.
- The
symlink-to-source strategy in the sketch is speculative — check what Codex / Cursor actually support before committing to it.
- Coordinate with the
first-tree tree upgrade path: users upgrading from a pre-registry install need their layout re-materialized once, idempotently.
References
- Current installer:
src/products/tree/engine/runtime/installer.ts
- Current doctor/link:
src/meta/skill-tools/engine/commands/{doctor,link}.ts
- Alias assertions:
scripts/check-skill-sync.sh (search for require_symlink_target)
- README architecture block:
README.md — "Package And Command" section
Summary
Replace the current two-layer symlink dance (
.claude/skills/<name>→.agents/skills/<name>→skills/<name>) with a declarative host registry that lists every supported agent host and the directory layout each one expects.first-tree tree initshould then generate the correct files (real directory or symlink, whichever fits the host) in one pass.first-tree skill doctorbecomes a pure read-only check instead of a repair tool.Motivation
Today the repo maintains:
skills/<name>/— the authoritative payload..agents/skills/<name>/— tracked symlink to../../skills/<name>..claude/skills/<name>/— tracked symlink to../../.agents/skills/<name>.Four skills × two alias layers = eight tracked symlinks just for Claude Code. Every new host (Codex already, Cursor likely, others possible) either needs its own alias layer added to the install flow,
scripts/check-skill-sync.sh, andskill doctor, or silently fails to resolve the skills.src/meta/skill-tools/exists largely to repair this layout when it drifts (skill doctordiagnoses,skill linkfixes). The fact we have a repair tool is a sign the layout is fragile.Proposed design
1. Host registry — single source of truth
Add
src/shared/host-registry.ts:2. Installer consumes the registry
first-tree tree init/first-tree tree upgradeiteratesHOSTS × ALL_SKILL_NAMESand materializes each(host, skill)pair once, using the strategy declared in the registry. No more hand-written alias lists.3.
skill doctorbecomes read-onlyRewrite
src/meta/skill-tools/engine/commands/doctor.tsto:4.
skill linkbecomes a thin re-run of installerRewrite
src/meta/skill-tools/engine/commands/link.tsto delegate to the installer's materialize step. The command is preserved for convenience ("re-apply the layout") but no longer carries bespoke repair logic.5.
scripts/check-skill-sync.shreads the registryRight now the script hard-codes every expected alias in
require_symlink_targetcalls. After this change it should be driven by the same registry — or be replaced entirely by afirst-tree skill doctor --strictinvocation.Out of scope
skills/<name>/themselves are authored.Deliverables
src/shared/host-registry.tswithHOSTS+HostLayouttype.src/products/tree/engine/runtime/installer.ts).doctorrewritten as read-only;linkrewritten to delegate to installer.scripts/check-skill-sync.shsimplified (ideally deleted in favor offirst-tree skill doctor --strictin CI).docs/architecture/overview.mdgains a "Host integration" section;src/meta/skill-tools/README.mdpoints at the registry.tests/meta/covering: registry-driven install, doctor's read-only behavior, adding a fake host in a test-only registry override.Acceptance criteria
HOSTS. Tests, installer, doctor, and CI script pick it up automatically.first-tree skill doctornever writes to the filesystem; running it on a clean checkout is a no-op.first-tree skill linkcan fully rebuild the host-side layout from the registry alone (deleting.claude/skillsthen runninglinkrestores it).Priority
P2 — Medium. Not urgent (today's system works) but the sooner this lands, the easier every future host integration becomes. Block on this before adding support for any third host (Cursor, etc.) — adding one more hand-maintained alias layer would cement the debt.
Notes
skill doctor/skill link) is ~150 lines acrosssrc/meta/skill-tools/engine/commands/that becomes ~40 once the registry exists.symlink-to-sourcestrategy in the sketch is speculative — check what Codex / Cursor actually support before committing to it.first-tree tree upgradepath: users upgrading from a pre-registry install need their layout re-materialized once, idempotently.References
src/products/tree/engine/runtime/installer.tssrc/meta/skill-tools/engine/commands/{doctor,link}.tsscripts/check-skill-sync.sh(search forrequire_symlink_target)README.md— "Package And Command" section