Skip to content

[Flat Install Layout] Flat directory naming for global agent skill installs#31

Merged
erdemtuna merged 10 commits intomainfrom
feature/flat-install-layout
Mar 10, 2026
Merged

[Flat Install Layout] Flat directory naming for global agent skill installs#31
erdemtuna merged 10 commits intomainfrom
feature/flat-install-layout

Conversation

@erdemtuna
Copy link
Owner

Summary

Global skill installs now use flat directory names so AI agents (Claude Code, GitHub Copilot) can discover them. The composite key github.com/org/repo/skill becomes github-com--org--repo--skill — a direct child of the agent's skills root.

Project-scoped installs (forge/) are unchanged and continue using nested composite-key paths.

Changes

Core (internal/install/installer.go)

  • FlatKey() — pure function converting composite keys to flat directory names (.-, /--, casing preserved)
  • InstallFlat() — transforms keys via FlatKey() then delegates to Install(), reusing all atomic staging, path traversal validation, and overwrite logic

CLI Wiring (install.go, get.go, update.go)

  • Global installs (craft install -g, craft get, craft update -g) now call InstallFlat() instead of Install()
  • Project installs continue calling Install() — no change to forge/ layout

Remove (remove.go)

  • Global removes use FlatKey() to derive directory names for cleanup
  • Skips cleanEmptyParents for global scope (flat layout has no parents)
  • Project removes unchanged

Display (list.go, tree.go)

  • craft list -g --detailed and craft tree -g now show composite key format (e.g., github.com/org/repo/skill) for readability
  • Project display unchanged

Tests (installer_test.go)

  • 6 new tests: TestFlatKey, TestFlatKeyEdgeCases, TestInstallFlatCreatesStructure, TestInstallFlatMultiPackage, TestInstallFlatSameName, TestInstallFlatOverwrites
  • All existing tests pass unchanged

Documentation

  • README.md — added flat layout format docs with directory structure example
  • E2E_REAL_WORLD_TEST.md — updated global install expected structures

Testing

  • All 6 new tests pass covering: key transformation, edge cases (dots in all segments, mixed casing), flat structure creation, multi-package, same-name collision safety, and atomic overwrite
  • Full suite: go test ./... ✅, go vet ./... ✅, go build ./...

Design Decisions

  • -- separator is collision-safe — GitHub disallows -- in org/repo names
  • InstallFlat() delegates to Install() — no code duplication, all staging/validation logic reused
  • No backward compatibility concern — no existing global installs use nested layout (clean-slate)
  • Pinfile stores original composite keys — removal derives flat paths via FlatKey() without needing reverse transformation

Artifacts

PAW workflow artifacts viewable at a02355e:

  • Spec.md, ImplementationPlan.md, CodeResearch.md, WorkShaping.md

🐾 Generated with PAW

erdemtuna and others added 10 commits March 9, 2026 20:49
Add FlatKey() and InstallFlat() to the install package. FlatKey converts
composite keys (github.com/org/repo/skill) to flat directory names
(github-com--org--repo--skill) by replacing / with -- and . with -.

Wire InstallFlat() for global installs in install, get, and update
commands. Project installs continue using nested layout via Install().

Update remove to use FlatKey() for global cleanup and skip parent
directory cleanup (flat layout has no parents to clean).

Update list --detailed and tree to show composite key format for
globally installed skills.

Add 6 new tests covering FlatKey edge cases, flat install structure,
multi-package, same-name collision safety, and overwrite semantics.

Update README.md and E2E_REAL_WORLD_TEST.md with flat layout
documentation and expected directory structures.

Co-authored-by: Copilot <[email protected]>
- Add collision detection in InstallFlat: returns error when distinct
  composite keys produce the same flat key (MF-2)
- Add TestInstallFlatCollisionDetection for dot-vs-dash collision case
- Improve TestFlatKey to use t.Run subtests for CI diagnostics (C-3)
- Document collision limitation in FlatKey docstring and README (SF-6)

Co-authored-by: Copilot <[email protected]>
SF-1: Make FlatKey injective — only replace slashes with --, preserve
dots as-is. Dots are valid directory characters on all platforms,
eliminating the lossy dot-to-dash collision risk entirely.

MF-1: Add TestRunRemoveGlobal_FlatCleanup integration test that
verifies flat directory cleanup for global remove operations.

SF-2: Extract CompositeKey() helper in install package for consistent
key construction. Remove.go now uses CompositeKey instead of manual
string concatenation, eliminating implicit coupling.

SF-3/SF-4: Extract QualifySkillNames() helper in install package,
used by both list.go and tree.go to eliminate duplicated display
logic. Both commands now use the shared helper instead of inline loops.

SF-5: No migration needed — no prior releases used nested global layout.

TestInstallFlatDistinguishesDotFromDash: New test proving dots and
dashes produce distinct flat keys with injective encoding.

Co-authored-by: Copilot <[email protected]>
C-2: QualifySkillNames skips empty skill names to avoid trailing-slash
display artifacts in list/tree output.

C-4: InstallFlat returns a clear error for empty composite keys instead
of falling through to Install's path traversal check with a misleading
'escapes target directory' message.

Add TestInstallFlatRejectsEmptyKey test.

Co-authored-by: Copilot <[email protected]>
Apply sanitize() to all user-derived strings (alias, URL, skill names,
local skills) before passing to RenderTree. Previously list.go sanitized
its output but tree.go did not, creating an inconsistency.

Co-authored-by: Copilot <[email protected]>
@erdemtuna erdemtuna merged commit 4cf4e9f into main Mar 10, 2026
3 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