Skip to content

Preserved project links during adopt resync#182

Open
TIR44 wants to merge 8 commits into
runkids:adoptfrom
TIR44:fix/adopt-project-resync-root
Open

Preserved project links during adopt resync#182
TIR44 wants to merge 8 commits into
runkids:adoptfrom
TIR44:fix/adopt-project-resync-root

Conversation

@TIR44
Copy link
Copy Markdown
Contributor

@TIR44 TIR44 commented May 31, 2026

Summary

  • Passed the project root through the shared adopt apply path before re-syncing targets.
  • Kept project-mode symlink and merge target re-sync on the existing relative-link behavior instead of falling back to absolute links.
  • Updated the project adopt integration assertion to resolve symlink targets across macOS /private/var canonicalization and to catch absolute project links on non-Windows platforms.

Context

Follow-up to #181. The failing macOS integration job showed TestAdoptProject_MigratesAndResyncs comparing a canonical /private/var/... symlink target against the logical temp path. While checking that, I found the re-sync path was passing an empty project root, so project-mode adopt created absolute links instead of the portable relative links used by normal project sync.

Tests

  • go build -o bin/skillshare ./cmd/skillshare
  • SKILLSHARE_TEST_BINARY=$PWD/bin/skillshare go test -v -race ./tests/integration -run '^TestAdoptProject_MigratesAndResyncs$' -count=1
  • go test ./internal/adopt ./cmd/skillshare -run 'TestApply_ReSyncHonorsCopyMode|TestAdopt' -count=1
  • make test-unit

runkids added 7 commits May 31, 2026 21:15
…ills

External CLI tools (firecrawl/cli, googleworkspace/cli) install skills directly into ~/.agents/skills and track them in their own .skill-lock.json, bypassing skillshare's source-of-truth model: they show up as unmanaged, only reach the agents the installer detected, and get re-clobbered when moved by hand.

adopt detects these, migrates the canonical files into source (reusing the collect/pull machinery), trashes the originals (restorable), prunes the external orphan symlinks, and re-syncs to all targets. The tool's lockfile is read-only to skillshare: adopt warns the user to release the entry from the owning tool rather than editing a file it does not own.

Dual-mode (global + project). Conflicts are skipped without --force; a bare run in a non-interactive shell refuses rather than silently migrating and trashing files.

Refs runkids#135
Expose adopt over REST (GET /api/adopt/preview, POST /api/adopt/apply), reusing the shared internal/adopt.Apply orchestration so the CLI and the server run one implementation of the destructive flow.

Add an Adopt dashboard page: auto-loaded preview, multi-select with conflict/external-link badges, dry-run and force toggles, a result summary, and prominent lockfile warnings. Dual-mode aware (nav visible in both global and project).

Refs runkids#135
Add reference/commands/adopt.md (when-to-use, flow diagram, flags, conflict and non-interactive behavior, JSON output, lockfile read-only note) and register it in the sidebar under Sync Operations.

Refs runkids#135
- lockfile: read .skill-lock.json from the agents dir parent
  (~/.agents/.skill-lock.json), not inside the skills dir, so
  provenance and lingering-entry warnings resolve correctly
- apply: re-sync honors each target's effective mode (copy/symlink/
  merge) instead of forcing merge symlinks, so copy-mode targets get
  real copies after adoption
- pull: dry-run classifies a same-name source skill as skipped (not
  pulled) unless --force, matching real apply
- i18n: add the adopt.* and layout.nav.adopt keys to all 10 locales
  to restore key/placeholder parity
TestRunAdopt_WarnsOnLingeringLockfile still wrote .skill-lock.json
inside the skills dir, but production ReadLock now reads it from the
parent (~/.agents/.skill-lock.json). Use adopt.LockPath so the fixture
matches, restoring the lingering-entry warning assertion.
- Add a visual pipeline (Agents target -> Adopt -> Source) with the same
  hand-drawn wavy connectors and pill stages the sync page uses
- Move the primary action into a centered control card with a status
  summary line (N adoptable / M conflicts), mirroring sync's stat parts
- Replace the dry-run/force checkboxes with a SplitButton: primary
  Adopt, with Preview (dry run) and Force adopt in the dropdown, matching
  sync's action affordance. Conflicting skills are now always selectable
  but default off; plain Adopt skips them, Force adopt overwrites
- Show the all-managed state inline in the control card instead of a
  separate empty card
- Add adopt.pipeline.*, adopt.stat.*, adopt.button.forceAdopt keys across
  all 11 locales
Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces support for passing the ProjectRoot through the adopt process, enabling project-mode sync behavior such as portable relative links for targets under the project root. Feedback on the changes suggests explicitly handling errors from filepath.EvalSymlinks in the integration tests to prevent potential silent test failures where unresolved paths could erroneously match.

Comment thread tests/integration/adopt_test.go Outdated
Comment on lines +311 to +312
got, _ = filepath.EvalSymlinks(got)
want, _ := filepath.EvalSymlinks(filepath.Join(projectSource, "firecrawl"))
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Ignoring the errors returned by filepath.EvalSymlinks can lead to silent test failures or false positives. If both evaluations fail (e.g., if the paths do not exist), they will both return empty strings (""), causing the assertion got != want to evaluate to false and the test to pass.

Instead, explicitly check and handle the errors returned by filepath.EvalSymlinks.

		got, err := filepath.EvalSymlinks(got)
		if err != nil {
			t.Fatalf("failed to resolve got symlink: %v", err)
		}
		want, err := filepath.EvalSymlinks(filepath.Join(projectSource, "firecrawl"))
		if err != nil {
			t.Fatalf("failed to resolve want symlink: %v", err)
		}

@TIR44 TIR44 force-pushed the fix/adopt-project-resync-root branch from f012466 to 0baecf2 Compare May 31, 2026 14:10
@TIR44
Copy link
Copy Markdown
Contributor Author

TIR44 commented May 31, 2026

Addressed the EvalSymlinks error handling review note in 0baecf2. Re-ran the targeted macOS-relevant integration test locally:

SKILLSHARE_TEST_BINARY=$PWD/bin/skillshare go test -v -race ./tests/integration -run '^TestAdoptProject_MigratesAndResyncs$' -count=1

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.

2 participants