diff --git a/cli/common/clicore/skillscan_aliases.go b/cli/common/clicore/skillscan_aliases.go deleted file mode 100644 index f0eae17df1..0000000000 --- a/cli/common/clicore/skillscan_aliases.go +++ /dev/null @@ -1,36 +0,0 @@ -package clicore - -import "github.com/hatayama/unity-cli-loop/common/skillscan" - -const ( - SkillFileName = skillscan.SkillFileName - SkillSearchMaxDepth = skillscan.SkillSearchMaxDepth -) - -var ExcludedSkillSearchDirs = skillscan.ExcludedSkillSearchDirs - -type SkillSourceRoot = skillscan.SkillSourceRoot - -func collectInternalSkillToolNames(projectRoot string) map[string]bool { - return skillscan.CollectInternalSkillToolNames(projectRoot) -} - -func FindEditorFolders(basePath string, maxDepth int) []string { - return skillscan.FindEditorFolders(basePath, maxDepth) -} - -func ResolvePackageRoot(projectRoot string) string { - return skillscan.ResolvePackageRoot(projectRoot) -} - -func EnumerateSkillSourceRoots(projectRoot string) []SkillSourceRoot { - return skillscan.EnumerateSkillSourceRoots(projectRoot) -} - -func FallbackSkillName(skillDirectory string) string { - return skillscan.FallbackSkillName(skillDirectory) -} - -func ParseSkillFrontmatter(content string) map[string]string { - return skillscan.ParseSkillFrontmatter(content) -} diff --git a/cli/common/clicore/tool_catalog.go b/cli/common/clicore/tool_catalog.go index df5e619073..669fcad274 100644 --- a/cli/common/clicore/tool_catalog.go +++ b/cli/common/clicore/tool_catalog.go @@ -1,6 +1,7 @@ package clicore import ( + "github.com/hatayama/unity-cli-loop/common/skillscan" "github.com/hatayama/unity-cli-loop/common/tools" ) @@ -17,11 +18,11 @@ type ( ) func LoadTools(projectRoot string) (ToolsCache, error) { - return tools.Load(projectRoot, collectInternalSkillToolNames(projectRoot)) + return tools.Load(projectRoot, skillscan.CollectInternalSkillToolNames(projectRoot)) } func LoadProjectToolCache(projectRoot string) (ToolsCache, bool) { - return tools.LoadProjectCache(projectRoot, collectInternalSkillToolNames(projectRoot)) + return tools.LoadProjectCache(projectRoot, skillscan.CollectInternalSkillToolNames(projectRoot)) } func LoadDefaultTools() ToolsCache { @@ -37,7 +38,7 @@ func FindDefaultTool(name string) (ToolDefinition, bool) { } func FindToolForCommand(projectRoot string, command string) (ToolDefinition, ToolsCache, bool, error) { - return findToolForCommandWithInternalToolNames(projectRoot, command, collectInternalSkillToolNames) + return findToolForCommandWithInternalToolNames(projectRoot, command, skillscan.CollectInternalSkillToolNames) } func findToolForCommandWithInternalToolNames( diff --git a/cli/common/clicore/tool_catalog_test.go b/cli/common/clicore/tool_catalog_test.go index 9e97cb14e8..f7b66c6311 100644 --- a/cli/common/clicore/tool_catalog_test.go +++ b/cli/common/clicore/tool_catalog_test.go @@ -7,6 +7,7 @@ import ( "testing" "github.com/hatayama/unity-cli-loop/common/clitest" + "github.com/hatayama/unity-cli-loop/common/skillscan" ) // Tests that run-tests-specific help text does not leak into unrelated tool schemas. @@ -340,5 +341,5 @@ func writeToolCache(t *testing.T, projectRoot string, content string) { // shared clitest.WriteSkillFile helper, which owns the CRLF normalization. func writeTestSkill(t *testing.T, projectRoot string, relativeDir string, content string) { t.Helper() - clitest.WriteSkillFile(t, projectRoot, relativeDir, SkillFileName, content) + clitest.WriteSkillFile(t, projectRoot, relativeDir, skillscan.SkillFileName, content) } diff --git a/cli/common/skillscan/packages.go b/cli/common/skillscan/packages.go index 9d8db6670e..0f3fa85d89 100644 --- a/cli/common/skillscan/packages.go +++ b/cli/common/skillscan/packages.go @@ -10,6 +10,7 @@ import ( const ( manifestFileName = "manifest.json" + packageFileName = "package.json" packageName = "io.github.hatayama.uloopmcp" packageNameAlias = "io.github.hatayama.uLoopMCP" ) @@ -18,6 +19,21 @@ type manifestData struct { Dependencies map[string]string `json:"dependencies"` } +type packageData struct { + Name string `json:"name"` +} + +// PackageIdentity identifies a Unity package independently from where it was found. +type PackageIdentity struct { + Name string +} + +// PackageSearchResult is a package root candidate discovered from project, manifest, or cache state. +type PackageSearchResult struct { + Identity PackageIdentity + Root string +} + func FindEditorFolders(basePath string, maxDepth int) []string { editorFolders := []string{} var scan func(string, int) @@ -46,55 +62,108 @@ func FindEditorFolders(basePath string, maxDepth int) []string { return editorFolders } -func enumerateDirectProjectPackageRoots(projectRoot string) []string { +func EnumeratePackageSearchResults(projectRoot string) []PackageSearchResult { + results := []PackageSearchResult{} + seen := map[string]bool{} + addResult := func(result PackageSearchResult) { + if result.Root == "" || result.Identity.Name == "" { + return + } + absoluteRoot, err := filepath.Abs(result.Root) + if err != nil { + return + } + key := strings.ToLower(result.Identity.Name) + "\n" + absoluteRoot + if seen[key] { + return + } + seen[key] = true + result.Root = absoluteRoot + results = append(results, result) + } + + for _, result := range enumerateDirectProjectPackageResults(projectRoot) { + addResult(result) + } + for _, result := range enumerateManifestLocalPackageResults(projectRoot) { + addResult(result) + } + for _, result := range enumeratePackageCacheResults(projectRoot) { + addResult(result) + } + for _, result := range enumerateUnityCliLoopPackageCacheFallbackResults(projectRoot) { + addResult(result) + } + return results +} + +func FindUnityCliLoopPackage(projectRoot string) (PackageSearchResult, bool) { + bestResult := PackageSearchResult{} + bestPriority := 0 + found := false + for _, result := range EnumeratePackageSearchResults(projectRoot) { + if !isTargetPackageIdentity(result.Identity) { + continue + } + resolvedRoot := resolvePackageRootCandidate(result.Root) + if resolvedRoot == "" { + continue + } + result.Root = resolvedRoot + priority := unityCliLoopPackagePriority(projectRoot, resolvedRoot) + if !found || priority < bestPriority || priority == bestPriority && result.Root < bestResult.Root { + bestResult = result + bestPriority = priority + found = true + } + } + return bestResult, found +} + +func enumerateDirectProjectPackageResults(projectRoot string) []PackageSearchResult { packagesRoot := filepath.Join(projectRoot, "Packages") entries, err := os.ReadDir(packagesRoot) if err != nil { - return []string{} + return []PackageSearchResult{} } - packageRoots := []string{} + results := []PackageSearchResult{} for _, entry := range entries { if !entry.IsDir() { continue } - packageRoots = append(packageRoots, resolveSkillSearchRootCandidate(filepath.Join(packagesRoot, entry.Name()))) + root := resolveSkillSearchRootCandidate(filepath.Join(packagesRoot, entry.Name())) + identity := resolvePackageIdentity(root, entry.Name()) + results = append(results, PackageSearchResult{Identity: identity, Root: root}) } - sort.Strings(packageRoots) - return packageRoots -} - -func resolveManifestLocalPackageRoots(projectRoot string) []string { - return resolveManifestLocalPackageRootsMatching(projectRoot, nil) -} - -func resolveTargetManifestLocalPackageRoots(projectRoot string) []string { - return resolveManifestLocalPackageRootsMatching(projectRoot, isTargetManifestPackageName) + sortPackageSearchResults(results) + return results } -func resolveManifestLocalPackageRootsMatching(projectRoot string, includeDependency func(string) bool) []string { +func enumerateManifestLocalPackageResults(projectRoot string) []PackageSearchResult { dependencies := readManifestDependencies(projectRoot) if len(dependencies) == 0 { - return []string{} + return []PackageSearchResult{} } - packageRoots := []string{} + results := []PackageSearchResult{} for dependencyName, dependencyValue := range dependencies { - if includeDependency != nil && !includeDependency(dependencyName) { - continue - } localPath := resolveLocalDependencyPath(dependencyValue, projectRoot) if localPath == "" { continue } - packageRoots = append(packageRoots, resolveSkillSearchRootCandidate(localPath)) + root := resolveSkillSearchRootCandidate(localPath) + results = append(results, PackageSearchResult{ + Identity: PackageIdentity{Name: dependencyName}, + Root: root, + }) } - sort.Strings(packageRoots) - return packageRoots + sortPackageSearchResults(results) + return results } -func resolveDependencyPackageCacheRoots(projectRoot string) []string { +func enumeratePackageCacheResults(projectRoot string) []PackageSearchResult { dependencies := readManifestDependencies(projectRoot) if len(dependencies) == 0 { - return []string{} + return []PackageSearchResult{} } dependencyNames := map[string]bool{} for dependencyName := range dependencies { @@ -103,9 +172,9 @@ func resolveDependencyPackageCacheRoots(projectRoot string) []string { packageCacheDir := filepath.Join(projectRoot, "Library", "PackageCache") entries, err := os.ReadDir(packageCacheDir) if err != nil { - return []string{} + return []PackageSearchResult{} } - packageRoots := []string{} + results := []PackageSearchResult{} for _, entry := range entries { if !entry.IsDir() { continue @@ -117,47 +186,43 @@ func resolveDependencyPackageCacheRoots(projectRoot string) []string { if !dependencyNames[strings.ToLower(dependencyName)] { continue } - packageRoots = append(packageRoots, resolveSkillSearchRootCandidate(filepath.Join(packageCacheDir, entry.Name()))) + root := resolveSkillSearchRootCandidate(filepath.Join(packageCacheDir, entry.Name())) + results = append(results, PackageSearchResult{ + Identity: PackageIdentity{Name: dependencyName}, + Root: root, + }) } - sort.Strings(packageRoots) - return packageRoots + sortPackageSearchResults(results) + return results } -func ResolvePackageRoot(projectRoot string) string { - candidates := []string{ - filepath.Join(projectRoot, "Packages", "src"), - filepath.Join(projectRoot, "Packages", packageName), - filepath.Join(projectRoot, "Packages", packageNameAlias), - } - for _, candidate := range candidates { - if resolvedRoot := resolvePackageRootCandidate(candidate); resolvedRoot != "" { - return resolvedRoot - } - } - for _, candidate := range resolveTargetManifestLocalPackageRoots(projectRoot) { - if resolvedRoot := resolvePackageRootCandidate(candidate); resolvedRoot != "" { - return resolvedRoot - } - } - - return resolvePackageCacheRoot(projectRoot) -} - -func resolvePackageCacheRoot(projectRoot string) string { +func enumerateUnityCliLoopPackageCacheFallbackResults(projectRoot string) []PackageSearchResult { packageCacheDir := filepath.Join(projectRoot, "Library", "PackageCache") entries, err := os.ReadDir(packageCacheDir) if err != nil { - return "" + return []PackageSearchResult{} } + results := []PackageSearchResult{} for _, entry := range entries { if !entry.IsDir() || !isTargetPackageCacheDir(entry.Name()) { continue } - if resolvedRoot := resolvePackageRootCandidate(filepath.Join(packageCacheDir, entry.Name())); resolvedRoot != "" { - return resolvedRoot - } + root := resolveSkillSearchRootCandidate(filepath.Join(packageCacheDir, entry.Name())) + results = append(results, PackageSearchResult{ + Identity: PackageIdentity{Name: packageIdentityNameFromCacheDir(entry.Name())}, + Root: root, + }) } - return "" + sortPackageSearchResults(results) + return results +} + +func ResolvePackageRoot(projectRoot string) string { + result, ok := FindUnityCliLoopPackage(projectRoot) + if !ok { + return "" + } + return result.Root } func resolvePackageRootCandidate(candidate string) string { @@ -184,6 +249,20 @@ func resolveSkillSearchRootCandidate(candidate string) string { return candidate } +func resolvePackageIdentity(packageRoot string, fallbackName string) PackageIdentity { + content, err := os.ReadFile(filepath.Join(packageRoot, packageFileName)) + if err == nil { + packageManifest := packageData{} + if json.Unmarshal(content, &packageManifest) == nil && packageManifest.Name != "" { + return PackageIdentity(packageManifest) + } + } + if resolvePackageRootCandidate(packageRoot) != "" && strings.EqualFold(fallbackName, "src") { + return PackageIdentity{Name: packageName} + } + return PackageIdentity{Name: fallbackName} +} + func readManifestDependencies(projectRoot string) map[string]string { manifestPath := filepath.Join(projectRoot, "Packages", manifestFileName) content, err := os.ReadFile(manifestPath) @@ -227,8 +306,50 @@ func isTargetPackageCacheDir(dirName string) bool { strings.HasPrefix(normalizedName, strings.ToLower(packageNameAlias)+"@") } -func isTargetManifestPackageName(dependencyName string) bool { - normalizedName := strings.ToLower(dependencyName) +func isTargetPackageIdentity(identity PackageIdentity) bool { + normalizedName := strings.ToLower(identity.Name) return normalizedName == strings.ToLower(packageName) || normalizedName == strings.ToLower(packageNameAlias) } + +func packageIdentityNameFromCacheDir(dirName string) string { + if separatorIndex := strings.Index(dirName, "@"); separatorIndex >= 0 { + return dirName[:separatorIndex] + } + return dirName +} + +func sortPackageSearchResults(results []PackageSearchResult) { + sort.Slice(results, func(left int, right int) bool { + if results[left].Root == results[right].Root { + return results[left].Identity.Name < results[right].Identity.Name + } + return results[left].Root < results[right].Root + }) +} + +func unityCliLoopPackagePriority(projectRoot string, packageRoot string) int { + priorityCandidates := []string{ + filepath.Join(projectRoot, "Packages", "src"), + filepath.Join(projectRoot, "Packages", packageName), + filepath.Join(projectRoot, "Packages", packageNameAlias), + } + normalizedRoot := normalizedAbsolutePath(packageRoot) + for index, candidate := range priorityCandidates { + if normalizedRoot == normalizedAbsolutePath(candidate) { + return index + } + } + if strings.Contains(normalizedRoot, string(filepath.Separator)+"Library"+string(filepath.Separator)+"PackageCache"+string(filepath.Separator)) { + return 20 + } + return 10 +} + +func normalizedAbsolutePath(path string) string { + absolutePath, err := filepath.Abs(path) + if err != nil { + return filepath.Clean(path) + } + return filepath.Clean(absolutePath) +} diff --git a/cli/common/skillscan/packages_test.go b/cli/common/skillscan/packages_test.go index 64d287f8a0..29b4cf2129 100644 --- a/cli/common/skillscan/packages_test.go +++ b/cli/common/skillscan/packages_test.go @@ -3,9 +3,80 @@ package skillscan import ( "os" "path/filepath" + "reflect" "testing" ) +// Tests that source roots are enumerated through package identity search results in precedence order. +func TestEnumerateSourceRootsUsesPackageSearchResults(t *testing.T) { + projectRoot := t.TempDir() + writeTestPackageJSON(t, filepath.Join(projectRoot, "Packages", "src"), packageName) + if err := os.MkdirAll(filepath.Join(projectRoot, "Packages", "src", "Editor", "FirstPartyTools"), 0o755); err != nil { + t.Fatalf("failed to create package marker: %v", err) + } + projectPackageRoot := filepath.Join(projectRoot, "Packages", "local-package") + if err := os.MkdirAll(projectPackageRoot, 0o755); err != nil { + t.Fatalf("failed to create project package: %v", err) + } + manifestPackageRoot := filepath.Join(t.TempDir(), "manifest-local-package") + if err := os.MkdirAll(manifestPackageRoot, 0o755); err != nil { + t.Fatalf("failed to create manifest package: %v", err) + } + writeManifest( + t, + projectRoot, + `{"dependencies":{"com.example.manifest-local":"file:`+filepath.ToSlash(manifestPackageRoot)+`","com.example.cached":"1.0.0"}}`) + cachedPackageRoot := filepath.Join(projectRoot, "Library", "PackageCache", "com.example.cached@1.0.0") + if err := os.MkdirAll(cachedPackageRoot, 0o755); err != nil { + t.Fatalf("failed to create cached package: %v", err) + } + + sourceRoots := EnumerateSourceRoots(projectRoot) + + actualPaths := sourceRootPaths(sourceRoots) + expectedPaths := []string{ + filepath.Join(projectRoot, "Packages", "src", "Editor", "CliOnlyTools~"), + filepath.Join(projectRoot, "Assets"), + projectPackageRoot, + filepath.Join(projectRoot, "Packages", "src"), + manifestPackageRoot, + cachedPackageRoot, + } + if !reflect.DeepEqual(actualPaths, cleanPaths(expectedPaths)) { + t.Fatalf("source roots mismatch:\nactual: %#v\nexpected: %#v", actualPaths, cleanPaths(expectedPaths)) + } +} + +// Tests that package search results expose stable package identities for direct, manifest, and cached roots. +func TestEnumeratePackageSearchResultsCapturesPackageIdentities(t *testing.T) { + projectRoot := t.TempDir() + writeTestPackageJSON(t, filepath.Join(projectRoot, "Packages", "src"), packageName) + manifestPackageRoot := filepath.Join(t.TempDir(), "manifest-local-package") + if err := os.MkdirAll(manifestPackageRoot, 0o755); err != nil { + t.Fatalf("failed to create manifest package: %v", err) + } + writeManifest( + t, + projectRoot, + `{"dependencies":{"com.example.manifest-local":"file:`+filepath.ToSlash(manifestPackageRoot)+`","com.example.cached":"1.0.0"}}`) + cachedPackageRoot := filepath.Join(projectRoot, "Library", "PackageCache", "com.example.cached@1.0.0") + if err := os.MkdirAll(cachedPackageRoot, 0o755); err != nil { + t.Fatalf("failed to create cached package: %v", err) + } + + results := EnumeratePackageSearchResults(projectRoot) + + actual := packageResultSummaries(results) + expected := []string{ + packageName + "|" + filepath.Join(projectRoot, "Packages", "src"), + "com.example.manifest-local|" + filepath.Clean(manifestPackageRoot), + "com.example.cached|" + filepath.Clean(cachedPackageRoot), + } + if !reflect.DeepEqual(actual, cleanSummaries(expected)) { + t.Fatalf("package results mismatch:\nactual: %#v\nexpected: %#v", actual, cleanSummaries(expected)) + } +} + // Tests that local manifest packages from other dependencies cannot replace the Unity CLI Loop package root. func TestResolvePackageRootIgnoresOtherManifestFilePackages(t *testing.T) { projectRoot := t.TempDir() @@ -29,6 +100,40 @@ func TestResolvePackageRootIgnoresOtherManifestFilePackages(t *testing.T) { } } +// Tests that marker-only project packages are not treated as the Unity CLI Loop package unless they are Packages/src. +func TestResolvePackageRootIgnoresMarkerOnlyUnrelatedProjectPackage(t *testing.T) { + projectRoot := t.TempDir() + unrelatedPackageRoot := filepath.Join(projectRoot, "Packages", "unrelated-package") + markerPath := filepath.Join(unrelatedPackageRoot, "Editor", "FirstPartyTools") + if err := os.MkdirAll(markerPath, 0o755); err != nil { + t.Fatalf("failed to create marker path: %v", err) + } + + actualRoot := ResolvePackageRoot(projectRoot) + if actualRoot != "" { + t.Fatalf("unrelated marker-only package should not resolve as package root: %s", actualRoot) + } +} + +// Tests that the historical Packages/src package root takes precedence over named package folders. +func TestResolvePackageRootPrefersPackagesSrc(t *testing.T) { + projectRoot := t.TempDir() + srcRoot := filepath.Join(projectRoot, "Packages", "src") + namedRoot := filepath.Join(projectRoot, "Packages", packageName) + for _, candidateRoot := range []string{srcRoot, namedRoot} { + markerPath := filepath.Join(candidateRoot, "Editor", "FirstPartyTools") + if err := os.MkdirAll(markerPath, 0o755); err != nil { + t.Fatalf("failed to create marker path: %v", err) + } + } + + actualRoot := filepath.Clean(ResolvePackageRoot(projectRoot)) + expectedRoot := filepath.Clean(srcRoot) + if actualRoot != expectedRoot { + t.Fatalf("package root mismatch: actual=%s expected=%s", actualRoot, expectedRoot) + } +} + // Tests that package root probing uses the current first-party tool marker. func TestResolvePackageRootCandidateUsesFirstPartyToolsMarker(t *testing.T) { projectRoot := t.TempDir() @@ -44,6 +149,49 @@ func TestResolvePackageRootCandidateUsesFirstPartyToolsMarker(t *testing.T) { } } +func sourceRootPaths(sourceRoots []SkillSourceRoot) []string { + paths := []string{} + for _, sourceRoot := range sourceRoots { + paths = append(paths, filepath.Clean(sourceRoot.Path)) + } + return paths +} + +func packageResultSummaries(results []PackageSearchResult) []string { + summaries := []string{} + for _, result := range results { + summaries = append(summaries, result.Identity.Name+"|"+filepath.Clean(result.Root)) + } + return summaries +} + +func cleanPaths(paths []string) []string { + cleaned := []string{} + for _, path := range paths { + cleaned = append(cleaned, filepath.Clean(path)) + } + return cleaned +} + +func cleanSummaries(summaries []string) []string { + cleaned := []string{} + for _, summary := range summaries { + cleaned = append(cleaned, filepath.Clean(summary)) + } + return cleaned +} + +func writeTestPackageJSON(t *testing.T, packageRoot string, name string) { + t.Helper() + if err := os.MkdirAll(packageRoot, 0o755); err != nil { + t.Fatalf("failed to create package root: %v", err) + } + content := `{"name":"` + name + `"}` + if err := os.WriteFile(filepath.Join(packageRoot, "package.json"), []byte(content), 0o644); err != nil { + t.Fatalf("failed to write package.json: %v", err) + } +} + // writeManifest is duplicated from internal/cli's test helper of the same // name: test helpers cannot be shared across packages, and both packages // exercise package-root resolution from a project's manifest.json. diff --git a/cli/common/skillscan/sources.go b/cli/common/skillscan/sources.go index ae98fc42e6..96845e5b99 100644 --- a/cli/common/skillscan/sources.go +++ b/cli/common/skillscan/sources.go @@ -31,7 +31,7 @@ type SkillSourceRoot struct { func CollectInternalSkillToolNames(projectRoot string) map[string]bool { toolNames := map[string]bool{} - for _, sourceRoot := range EnumerateSkillSourceRoots(projectRoot) { + for _, sourceRoot := range EnumerateSourceRoots(projectRoot) { for _, toolName := range scanInternalSkillToolNames(sourceRoot) { toolNames[toolName] = true } @@ -39,7 +39,7 @@ func CollectInternalSkillToolNames(projectRoot string) map[string]bool { return toolNames } -func EnumerateSkillSourceRoots(projectRoot string) []SkillSourceRoot { +func EnumerateSourceRoots(projectRoot string) []SkillSourceRoot { sourceRoots := []SkillSourceRoot{} seen := map[string]bool{} addSourceRoot := func(path string, cliOnly bool) { @@ -56,16 +56,9 @@ func EnumerateSkillSourceRoots(projectRoot string) []SkillSourceRoot { addSourceRoot(CliOnlySourceRoot(projectRoot), true) addSourceRoot(filepath.Join(projectRoot, "Assets"), false) - for _, packageRoot := range enumerateDirectProjectPackageRoots(projectRoot) { - addSourceRoot(packageRoot, false) + for _, packageResult := range EnumeratePackageSearchResults(projectRoot) { + addSourceRoot(packageResult.Root, false) } - for _, packageRoot := range resolveManifestLocalPackageRoots(projectRoot) { - addSourceRoot(packageRoot, false) - } - for _, packageRoot := range resolveDependencyPackageCacheRoots(projectRoot) { - addSourceRoot(packageRoot, false) - } - addSourceRoot(ResolvePackageRoot(projectRoot), false) return sourceRoots } diff --git a/cli/dispatcher/internal/dispatcher/skills.go b/cli/dispatcher/internal/dispatcher/skills.go index 4ddd02be9a..580688d974 100644 --- a/cli/dispatcher/internal/dispatcher/skills.go +++ b/cli/dispatcher/internal/dispatcher/skills.go @@ -8,6 +8,7 @@ import ( "github.com/hatayama/unity-cli-loop/common/clicore" "github.com/hatayama/unity-cli-loop/common/project" + "github.com/hatayama/unity-cli-loop/common/skillscan" ) const ( @@ -355,7 +356,7 @@ func detectInstalledSkillTargets(projectRoot string, skills []skillDefinition, o func hasAnyInstalledSkill(baseDir string, skills []skillDefinition) (bool, error) { for _, skill := range skills { for _, grouped := range []bool{false, true} { - skillFile := filepath.Join(getPreferredSkillDir(baseDir, skill.name, grouped), clicore.SkillFileName) + skillFile := filepath.Join(getPreferredSkillDir(baseDir, skill.name, grouped), skillscan.SkillFileName) if _, err := os.Stat(skillFile); err == nil { return true, nil } else if !os.IsNotExist(err) { diff --git a/cli/dispatcher/internal/dispatcher/skills_discovery.go b/cli/dispatcher/internal/dispatcher/skills_discovery.go index e11269c025..e796d44f05 100644 --- a/cli/dispatcher/internal/dispatcher/skills_discovery.go +++ b/cli/dispatcher/internal/dispatcher/skills_discovery.go @@ -7,13 +7,13 @@ import ( "sort" "strings" - "github.com/hatayama/unity-cli-loop/common/clicore" + "github.com/hatayama/unity-cli-loop/common/skillscan" ) func collectSkillDefinitions(projectRoot string) ([]skillDefinition, error) { skills := []skillDefinition{} seen := map[string]bool{} - for _, sourceRoot := range clicore.EnumerateSkillSourceRoots(projectRoot) { + for _, sourceRoot := range skillscan.EnumerateSourceRoots(projectRoot) { discovered, err := scanSkillSourceRoot(sourceRoot) if err != nil { return nil, err @@ -33,7 +33,7 @@ func collectSkillDefinitions(projectRoot string) ([]skillDefinition, error) { } func collectV3MigrationSkillDefinition(projectRoot string) ([]skillDefinition, error) { - packageRoot := clicore.ResolvePackageRoot(projectRoot) + packageRoot := skillscan.ResolvePackageRoot(projectRoot) if packageRoot == "" { return nil, errors.New("the Unity CLI Loop package root was not found") } @@ -56,14 +56,14 @@ func collectV3MigrationSkillDefinition(projectRoot string) ([]skillDefinition, e return []skillDefinition{skill}, nil } -func scanSkillSourceRoot(sourceRoot clicore.SkillSourceRoot) ([]skillDefinition, error) { +func scanSkillSourceRoot(sourceRoot skillscan.SkillSourceRoot) ([]skillDefinition, error) { if _, err := os.Stat(sourceRoot.Path); err != nil { return []skillDefinition{}, nil } scanRoots := []string{sourceRoot.Path} if !sourceRoot.CLIOnly { - scanRoots = clicore.FindEditorFolders(sourceRoot.Path, clicore.SkillSearchMaxDepth) + scanRoots = skillscan.FindEditorFolders(sourceRoot.Path, skillscan.SkillSearchMaxDepth) } skills := []skillDefinition{} @@ -84,7 +84,7 @@ func scanSkillDirectories(searchRoot string) ([]skillDefinition, error) { return walkErr } if !entry.IsDir() { - if entry.Name() != clicore.SkillFileName { + if entry.Name() != skillscan.SkillFileName { return nil } skill, ok, err := readSkillDefinition(filepath.Dir(path)) @@ -96,7 +96,7 @@ func scanSkillDirectories(searchRoot string) ([]skillDefinition, error) { } return nil } - if clicore.ExcludedSkillSearchDirs[entry.Name()] && entry.Name() != "Skill" { + if skillscan.ExcludedSkillSearchDirs[entry.Name()] && entry.Name() != "Skill" { return filepath.SkipDir } if entry.Name() != "Skill" { @@ -120,7 +120,7 @@ func scanSkillDirectories(searchRoot string) ([]skillDefinition, error) { } func readSkillDefinition(skillDirectory string) (skillDefinition, bool, error) { - skillPath := filepath.Join(skillDirectory, clicore.SkillFileName) + skillPath := filepath.Join(skillDirectory, skillscan.SkillFileName) content, err := os.ReadFile(skillPath) if err != nil { if os.IsNotExist(err) { @@ -128,14 +128,14 @@ func readSkillDefinition(skillDirectory string) (skillDefinition, bool, error) { } return skillDefinition{}, false, err } - content = normalizeSkillFileContent(clicore.SkillFileName, content) - frontmatter := clicore.ParseSkillFrontmatter(string(content)) + content = normalizeSkillFileContent(skillscan.SkillFileName, content) + frontmatter := skillscan.ParseSkillFrontmatter(string(content)) if strings.EqualFold(frontmatter["internal"], "true") { return skillDefinition{}, false, nil } name := frontmatter["name"] if name == "" { - name = clicore.FallbackSkillName(skillDirectory) + name = skillscan.FallbackSkillName(skillDirectory) } if !isSafeSkillName(name) { return skillDefinition{}, false, nil diff --git a/cli/dispatcher/internal/dispatcher/skills_sync.go b/cli/dispatcher/internal/dispatcher/skills_sync.go index 7a2e8ae4e8..b5d35a9b07 100644 --- a/cli/dispatcher/internal/dispatcher/skills_sync.go +++ b/cli/dispatcher/internal/dispatcher/skills_sync.go @@ -6,7 +6,7 @@ import ( "os" "path/filepath" - "github.com/hatayama/unity-cli-loop/common/clicore" + "github.com/hatayama/unity-cli-loop/common/skillscan" ) func syncSkillDirectory(sourceDir string, destinationDir string) error { @@ -107,7 +107,7 @@ func getSkillStatusWithStat( stat func(string) (os.FileInfo, error), ) (string, error) { skillDir := getPreferredSkillDir(baseDir, skill.name, grouped) - if _, err := stat(filepath.Join(skillDir, clicore.SkillFileName)); err != nil { + if _, err := stat(filepath.Join(skillDir, skillscan.SkillFileName)); err != nil { if os.IsNotExist(err) { return "not_installed", nil } @@ -120,12 +120,12 @@ func getSkillStatusWithStat( } func isInstalledSkillOutdated(installedDir string, skill skillDefinition) bool { - installedContent, err := os.ReadFile(filepath.Join(installedDir, clicore.SkillFileName)) + installedContent, err := os.ReadFile(filepath.Join(installedDir, skillscan.SkillFileName)) if err != nil { return true } - installedContent = normalizeSkillFileContent(clicore.SkillFileName, installedContent) - expectedContent := normalizeSkillFileContent(clicore.SkillFileName, skill.content) + installedContent = normalizeSkillFileContent(skillscan.SkillFileName, installedContent) + expectedContent := normalizeSkillFileContent(skillscan.SkillFileName, skill.content) if !bytes.Equal(installedContent, expectedContent) { return true } @@ -151,7 +151,7 @@ func collectComparableSkillFiles(root string) map[string][]byte { return nil } relativePath, err := filepath.Rel(root, path) - if err != nil || relativePath == clicore.SkillFileName { + if err != nil || relativePath == skillscan.SkillFileName { return nil } content, err := os.ReadFile(path) diff --git a/cli/dispatcher/shared-inputs-stamp.json b/cli/dispatcher/shared-inputs-stamp.json index bc67fecf31..8ce2066d24 100644 --- a/cli/dispatcher/shared-inputs-stamp.json +++ b/cli/dispatcher/shared-inputs-stamp.json @@ -1,4 +1,4 @@ { "schemaVersion": 1, - "sharedInputsHash": "d5b7782a709e8025d43352b1746d8fb84fcf261c" + "sharedInputsHash": "55edb6d89876ab6462b5ca689085f4d6f43f8166" } diff --git a/cli/project-runner/shared-inputs-stamp.json b/cli/project-runner/shared-inputs-stamp.json index 537c70a0c0..f12c0cdefc 100644 --- a/cli/project-runner/shared-inputs-stamp.json +++ b/cli/project-runner/shared-inputs-stamp.json @@ -1,4 +1,4 @@ { "schemaVersion": 1, - "sharedInputsHash": "9d71859ae23baf04af3d6dc4241dae5ca798d7ad" + "sharedInputsHash": "c0a61ce5c6fb1066a532cb0195fbb99da66220ae" }