Skip to content

[tech-debt] Replace .agents/.claude skill-alias symlinks with a host registry #185

@bingran-you

Description

@bingran-you

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

  1. src/shared/host-registry.ts with HOSTS + HostLayout type.
  2. Installer rewritten to iterate the registry (in src/products/tree/engine/runtime/installer.ts).
  3. doctor rewritten as read-only; link rewritten to delegate to installer.
  4. scripts/check-skill-sync.sh simplified (ideally deleted in favor of first-tree skill doctor --strict in CI).
  5. Docs update: docs/architecture/overview.md gains a "Host integration" section; src/meta/skill-tools/README.md points at the registry.
  6. 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

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions