#2544

perf(install): restore each repository once in install

Openhowmanysmallwants to merge intofrom

skills experimental_install cloned and discovered the whole source once per skill. A lock with many skills from the same repository paid for the same clone over and over. Restoring 81 skills into an empty .agents/skills/ took 95 seconds. With this change, it takes 10 seconds.

What changes

  • Lock entries now group by repository, ref, and source type. Each group clones and discovers its source once, then installs every locked skill it contains.
  • When every entry in a group records a skillPath, the installer reads only those SKILL.md files. It no longer walks the whole repository. If a path no longer exists, it falls back to a full walk and finds the moved skill by name.
  • Up to 4 repositories restore at a time (SKILLS_INSTALL_CONCURRENCY overrides this).
  • If a skill has disappeared upstream, the restore reports it as missing and continues with the rest. The command still exits with code 1.
  • well-known entries no longer go through runAdd. A new restoreWellKnownSkills restores them from the published index without prompting or exiting.
  • At the end, the installer writes skills-lock.json once. It skips the write entirely when nothing changed.

What changed in the installer:

  • installToTargets writes each skill's canonical copy once per batch instead of once per agent (reuseCanonicalCopy). This also applies to skills add with more than one agent. The result on disk is the same. It skips the repeated delete-and-copy.
  • The "no symlink needed" check now compares the agent folder with the canonical folder. It no longer checks isGlobal && isUniversalAgent. Universal agents already point to the canonical folder, so this covers the same cases and project scope now.
BeforeAfter
81 skills, empty .agents/skills/95.4 seconds10.0 seconds

Tests

  • tests/install.test.ts: every skill from one repository restores through one source install, and well-known sources restore without exiting on a missing skill.
  • tests/install-from-lock.test.ts: a skill deleted upstream does not stop the others. Broken skills elsewhere in the repository do not cause warnings. A moved skill reinstalls with its locked path updated.
  • tests/install-wellknown.test.ts: well-known restore reports a missing skill and an unreachable index without exiting.
  • tests/local-lock.test.ts: addSkillsToLocalLock writes once and leaves an up-to-date lock untouched.

The full suite passes (989). TypeScript and Prettier pass.