Skip to content

feat(import): add single-skill import from configured agent sources - #6

Merged
eezzekl merged 4 commits into
mainfrom
feat/import_skills
Oct 2, 2026
Merged

eezzekl merged 4 commits into
mainfrom
feat/import_skills

Conversation

@eezzekl

@eezzekl eezzekl commented Oct 2, 2026 •

Copy link
Copy Markdown
Owner

Summary

  • Adds skill-sync import <skill-name>: a selective, single-skill import from the globally configured agent sources into the project-local ./.agents/skills/<skillID>/. This is distinct from mesh sync — it pulls one skill into the current project instead of reconciling every target.
  • Introduces internal/importer, a reusable primitive (FindSkill, FindAllSkills, Resolve, CopySkill, DirHash) that delegates winner selection to the existing mesh resolver.
  • Wires the flow into the TUI with two new views and two new states, including a [CONFLICT] marker for candidates the resolver cannot settle.

Design notes

Directory-level hashing. DirHash hashes a sorted manifest of every regular file in the skill directory (relative path + contents, filepath.ToSlash normalized for cross-platform determinism), not just SKILL.md. A skill whose sibling files changed is correctly seen as different.

Transactional copy. CopySkill writes everything into a .tmp staging directory first. On failure it removes .tmp and leaves the destination completely untouched; only on full success does it rotate the existing directory to .bak and rename .tmp into place. This avoids the window where a naive rename-then-write leaves a half-written destination.

Conflict handling. When two sources report the same version and the same mtime but different hashes, the import fails with ErrConflict and exit code 1 in both CLI and TUI. Interactive tie-breaking is deliberately out of scope for v1.

Changes

File Change
internal/importer/manifest.go DirHash — SHA-256 over the sorted file manifest; skips symlinks
internal/importer/importer.go FindSkill, FindAllSkills (grouped by skill ID + resolved), Resolve, CopySkill, readMetadata
internal/importer/errors.go ErrSkillNotFound, ErrConflict sentinels
internal/cli/import.go NewImportCmd + runImport; uses the getWd package var for test injection
internal/cli/root.go Registers the command and wires the FindSkillsForImport / RunImport callbacks
internal/models/skill.go SkillMetadata gains Description for the TUI listing
internal/tui/import_source_view/ Agent source filter, built on shared_toggle, all sources checked by default
internal/tui/import_skill_view/ Single-select skill picker, [CONFLICT] prefix, description truncated at 60 runes
internal/tui/program.go stateImportSource and stateImportSkill, plus the importSources field so BackMsg can recreate the source view
internal/tui/menu/model.go "Import" item between Verify and Config
openspec/ SDD scaffolding and the full skill-import change record (proposal, spec, design, tasks)
.gitignore Ignores local agent tool directories and test backup residue

Test plan

All run on feat/import_skills at c2cba25, and confirmed green in CI on ubuntu-latest and windows-latest:

  • go build ./... — passes
  • go vet ./... — passes
  • gofmt -s -l . — clean (the first CI run caught unformatted files; fixed in c2cba25)
  • go test ./... — passes, every package green

New coverage, all table-driven per project convention:

  • internal/importer/manifest_test.go — determinism, order independence, content change, file addition, symlink skipping (Unix only, t.Skip on Windows)
  • internal/importer/importer_test.go — find/resolve/copy paths including the transactional failure case where the destination must stay untouched
  • internal/cli/import_test.go — 8 integration tests against real temp dirs: happy path, missing config, skill not found, wrong argument count (x2), conflict, idempotent re-import, and .bak backup creation
  • internal/models/skill_test.go — description YAML round-trip
  • TUI view tests for both new views, plus menu and program.go state transitions

Not applicable: shellcheck (no shell scripts touched).

Review size

~2,100 added lines, of which ~1,200 are tests — above the 400-line advisory budget in openspec/config.yaml, which declares chained_pr_strategy: single-pr-default. Suggested reading order: internal/importer/ first (the primitive and its invariants), then internal/cli/import.go, then the TUI wiring.

Known follow-ups

  • A test in internal/tui writes skill-sync.yaml.bak into the package directory instead of a temp dir. This PR only gitignores the residue; the test should be fixed to use t.TempDir().
  • sdd-archive for the skill-import change (merge delta specs, move the change folder to archived) is not included here.

Adds the skill-import change: a new internal/importer package that locates a
skill across globally configured agent sources, resolves the winner with the
mesh rules, and copies it into ./.agents/skills/<skillID>/ transactionally.

- importer.DirHash hashes the whole skill directory manifest instead of only
  SKILL.md, so conflict resolution sees sibling file changes.
- importer.CopySkill stages into .tmp and only rotates .bak and renames on
  full success, leaving the destination untouched on failure.
- CLI: skill-sync import <skill-name>.
- TUI: import_source_view and import_skill_view wired into program.go, with
  a [CONFLICT] marker for unresolvable candidates.
- models.SkillMetadata gains Description for the TUI listing.
@eezzekl eezzekl added the enhancement New feature or request label Oct 2, 2026
@eezzekl
eezzekl merged commit 449284d into main Oct 2, 2026
3 checks passed
@eezzekl
eezzekl deleted the feat/import_skills branch October 2, 2026 22:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant