@kolisachint/hoocode-agent 0.4.108 → 0.4.109
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +2 -0
- package/dist/core/agent-session-compaction.d.ts +79 -0
- package/dist/core/agent-session-compaction.d.ts.map +1 -0
- package/dist/core/agent-session-compaction.js +346 -0
- package/dist/core/agent-session-compaction.js.map +1 -0
- package/dist/core/agent-session-retry.d.ts +76 -0
- package/dist/core/agent-session-retry.d.ts.map +1 -0
- package/dist/core/agent-session-retry.js +192 -0
- package/dist/core/agent-session-retry.js.map +1 -0
- package/dist/core/agent-session-skills.d.ts +34 -0
- package/dist/core/agent-session-skills.d.ts.map +1 -0
- package/dist/core/agent-session-skills.js +52 -0
- package/dist/core/agent-session-skills.js.map +1 -0
- package/dist/core/agent-session-stats.d.ts +74 -0
- package/dist/core/agent-session-stats.d.ts.map +1 -0
- package/dist/core/agent-session-stats.js +187 -0
- package/dist/core/agent-session-stats.js.map +1 -0
- package/dist/core/agent-session-tree-navigation.d.ts +69 -0
- package/dist/core/agent-session-tree-navigation.d.ts.map +1 -0
- package/dist/core/agent-session-tree-navigation.js +198 -0
- package/dist/core/agent-session-tree-navigation.js.map +1 -0
- package/dist/core/agent-session.d.ts +10 -66
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +96 -806
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/context-files.d.ts +25 -0
- package/dist/core/context-files.d.ts.map +1 -0
- package/dist/core/context-files.js +97 -0
- package/dist/core/context-files.js.map +1 -0
- package/dist/core/package-manager.d.ts.map +1 -1
- package/dist/core/package-manager.js +3 -519
- package/dist/core/package-manager.js.map +1 -1
- package/dist/core/package-resource-discovery.d.ts +62 -0
- package/dist/core/package-resource-discovery.d.ts.map +1 -0
- package/dist/core/package-resource-discovery.js +530 -0
- package/dist/core/package-resource-discovery.js.map +1 -0
- package/dist/core/resource-loader.d.ts +1 -10
- package/dist/core/resource-loader.d.ts.map +1 -1
- package/dist/core/resource-loader.js +4 -83
- package/dist/core/resource-loader.js.map +1 -1
- package/dist/core/settings-manager.d.ts +5 -140
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js +4 -81
- package/dist/core/settings-manager.js.map +1 -1
- package/dist/core/settings-storage.d.ts +29 -0
- package/dist/core/settings-storage.d.ts.map +1 -0
- package/dist/core/settings-storage.js +90 -0
- package/dist/core/settings-storage.js.map +1 -0
- package/dist/core/settings-types.d.ts +128 -0
- package/dist/core/settings-types.d.ts.map +1 -0
- package/dist/core/settings-types.js +9 -0
- package/dist/core/settings-types.js.map +1 -0
- package/examples/extensions/custom-provider-anthropic/package.json +1 -1
- package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
- package/examples/extensions/sandbox/package.json +1 -1
- package/examples/extensions/with-deps/package.json +1 -1
- package/package.json +4 -4
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resource discovery and pattern matching for the package manager.
|
|
3
|
+
*
|
|
4
|
+
* Pure filesystem/string helpers extracted from package-manager.ts: recursive
|
|
5
|
+
* file collection honoring nested ignore files, skill/prompt/theme/extension
|
|
6
|
+
* auto-discovery, hoocode package-manifest reading, and the include/exclude
|
|
7
|
+
* pattern engine (plain globs plus `!`/`+`/`-` overrides). None of these touch
|
|
8
|
+
* the DefaultPackageManager instance; it imports and composes them.
|
|
9
|
+
*/
|
|
10
|
+
import ignore from "ignore";
|
|
11
|
+
export interface HooCodeManifest {
|
|
12
|
+
extensions?: string[];
|
|
13
|
+
skills?: string[];
|
|
14
|
+
prompts?: string[];
|
|
15
|
+
themes?: string[];
|
|
16
|
+
agents?: string[];
|
|
17
|
+
}
|
|
18
|
+
export type ResourceType = "extensions" | "skills" | "prompts" | "themes" | "agents";
|
|
19
|
+
export declare const RESOURCE_TYPES: ResourceType[];
|
|
20
|
+
/** Resource types that are configurable via user/project settings. Excludes "agents" because
|
|
21
|
+
* agent discovery is handled by AgentRegistry from conventional directories; only package
|
|
22
|
+
* manifests supply agents through the package manager. */
|
|
23
|
+
export type SettingsResourceType = Exclude<ResourceType, "agents">;
|
|
24
|
+
export declare const SETTINGS_RESOURCE_TYPES: SettingsResourceType[];
|
|
25
|
+
export declare const FILE_PATTERNS: Record<ResourceType, RegExp>;
|
|
26
|
+
type IgnoreMatcher = ReturnType<typeof ignore>;
|
|
27
|
+
export declare function getHomeDir(): string;
|
|
28
|
+
export declare function isPattern(s: string): boolean;
|
|
29
|
+
export declare function isOverridePattern(s: string): boolean;
|
|
30
|
+
export declare function hasGlobPattern(s: string): boolean;
|
|
31
|
+
export declare function splitPatterns(entries: string[]): {
|
|
32
|
+
plain: string[];
|
|
33
|
+
patterns: string[];
|
|
34
|
+
};
|
|
35
|
+
export declare function collectFiles(dir: string, filePattern: RegExp, skipNodeModules?: boolean, ignoreMatcher?: IgnoreMatcher, rootDir?: string): string[];
|
|
36
|
+
export type SkillDiscoveryMode = "pi" | "agents";
|
|
37
|
+
export declare function collectSkillEntries(dir: string, mode: SkillDiscoveryMode, ignoreMatcher?: IgnoreMatcher, rootDir?: string): string[];
|
|
38
|
+
export declare function collectAutoSkillEntries(dir: string, mode: SkillDiscoveryMode): string[];
|
|
39
|
+
export declare function collectAncestorAgentsSkillDirs(startDir: string): string[];
|
|
40
|
+
export declare function collectAutoPromptEntries(dir: string): string[];
|
|
41
|
+
export declare function collectAutoThemeEntries(dir: string): string[];
|
|
42
|
+
export declare function readHooCodeManifestFile(packageJsonPath: string): HooCodeManifest | null;
|
|
43
|
+
export declare function resolveExtensionEntries(dir: string): string[] | null;
|
|
44
|
+
export declare function collectAutoExtensionEntries(dir: string): string[];
|
|
45
|
+
/**
|
|
46
|
+
* Collect resource files from a directory based on resource type.
|
|
47
|
+
* Extensions use smart discovery (index.ts in subdirs), others use recursive collection.
|
|
48
|
+
*/
|
|
49
|
+
export declare function collectResourceFiles(dir: string, resourceType: ResourceType): string[];
|
|
50
|
+
export declare function getOverridePatterns(entries: string[]): string[];
|
|
51
|
+
export declare function isEnabledByOverrides(filePath: string, patterns: string[], baseDir: string): boolean;
|
|
52
|
+
/**
|
|
53
|
+
* Apply patterns to paths and return a Set of enabled paths.
|
|
54
|
+
* Pattern types:
|
|
55
|
+
* - Plain patterns: include matching paths
|
|
56
|
+
* - `!pattern`: exclude matching paths
|
|
57
|
+
* - `+path`: force-include exact path (overrides exclusions)
|
|
58
|
+
* - `-path`: force-exclude exact path (overrides force-includes)
|
|
59
|
+
*/
|
|
60
|
+
export declare function applyPatterns(allPaths: string[], patterns: string[], baseDir: string): Set<string>;
|
|
61
|
+
export {};
|
|
62
|
+
//# sourceMappingURL=package-resource-discovery.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"package-resource-discovery.d.ts","sourceRoot":"","sources":["../../src/core/package-resource-discovery.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Resource discovery and pattern matching for the package manager.\n *\n * Pure filesystem/string helpers extracted from package-manager.ts: recursive\n * file collection honoring nested ignore files, skill/prompt/theme/extension\n * auto-discovery, hoocode package-manifest reading, and the include/exclude\n * pattern engine (plain globs plus `!`/`+`/`-` overrides). None of these touch\n * the DefaultPackageManager instance; it imports and composes them.\n */\n\nimport { existsSync, readdirSync, readFileSync, statSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { basename, dirname, join, relative, resolve, sep } from \"node:path\";\nimport ignore from \"ignore\";\nimport { minimatch } from \"minimatch\";\n\nexport interface HooCodeManifest {\n\textensions?: string[];\n\tskills?: string[];\n\tprompts?: string[];\n\tthemes?: string[];\n\tagents?: string[];\n}\n\nexport type ResourceType = \"extensions\" | \"skills\" | \"prompts\" | \"themes\" | \"agents\";\n\nexport const RESOURCE_TYPES: ResourceType[] = [\"extensions\", \"skills\", \"prompts\", \"themes\", \"agents\"];\n\n/** Resource types that are configurable via user/project settings. Excludes \"agents\" because\n * agent discovery is handled by AgentRegistry from conventional directories; only package\n * manifests supply agents through the package manager. */\nexport type SettingsResourceType = Exclude<ResourceType, \"agents\">;\nexport const SETTINGS_RESOURCE_TYPES: SettingsResourceType[] = [\"extensions\", \"skills\", \"prompts\", \"themes\"];\n\nexport const FILE_PATTERNS: Record<ResourceType, RegExp> = {\n\textensions: /\\.(ts|js)$/,\n\tskills: /\\.md$/,\n\tprompts: /\\.md$/,\n\tthemes: /\\.json$/,\n\tagents: /\\.md$/,\n};\n\nconst IGNORE_FILE_NAMES = [\".gitignore\", \".ignore\", \".fdignore\"];\n\ntype IgnoreMatcher = ReturnType<typeof ignore>;\n\nfunction toPosixPath(p: string): string {\n\treturn p.split(sep).join(\"/\");\n}\n\nexport function getHomeDir(): string {\n\treturn process.env.HOME || homedir();\n}\n\nfunction prefixIgnorePattern(line: string, prefix: string): string | null {\n\tconst trimmed = line.trim();\n\tif (!trimmed) return null;\n\tif (trimmed.startsWith(\"#\") && !trimmed.startsWith(\"\\\\#\")) return null;\n\n\tlet pattern = line;\n\tlet negated = false;\n\n\tif (pattern.startsWith(\"!\")) {\n\t\tnegated = true;\n\t\tpattern = pattern.slice(1);\n\t} else if (pattern.startsWith(\"\\\\!\")) {\n\t\tpattern = pattern.slice(1);\n\t}\n\n\tif (pattern.startsWith(\"/\")) {\n\t\tpattern = pattern.slice(1);\n\t}\n\n\tconst prefixed = prefix ? `${prefix}${pattern}` : pattern;\n\treturn negated ? `!${prefixed}` : prefixed;\n}\n\nfunction addIgnoreRules(ig: IgnoreMatcher, dir: string, rootDir: string): void {\n\tconst relativeDir = relative(rootDir, dir);\n\tconst prefix = relativeDir ? `${toPosixPath(relativeDir)}/` : \"\";\n\n\tfor (const filename of IGNORE_FILE_NAMES) {\n\t\tconst ignorePath = join(dir, filename);\n\t\tif (!existsSync(ignorePath)) continue;\n\t\ttry {\n\t\t\tconst content = readFileSync(ignorePath, \"utf-8\");\n\t\t\tconst patterns = content\n\t\t\t\t.split(/\\r?\\n/)\n\t\t\t\t.map((line) => prefixIgnorePattern(line, prefix))\n\t\t\t\t.filter((line): line is string => Boolean(line));\n\t\t\tif (patterns.length > 0) {\n\t\t\t\tig.add(patterns);\n\t\t\t}\n\t\t} catch {}\n\t}\n}\n\nexport function isPattern(s: string): boolean {\n\treturn s.startsWith(\"!\") || s.startsWith(\"+\") || s.startsWith(\"-\") || s.includes(\"*\") || s.includes(\"?\");\n}\n\nexport function isOverridePattern(s: string): boolean {\n\treturn s.startsWith(\"!\") || s.startsWith(\"+\") || s.startsWith(\"-\");\n}\n\nexport function hasGlobPattern(s: string): boolean {\n\treturn s.includes(\"*\") || s.includes(\"?\");\n}\n\nexport function splitPatterns(entries: string[]): { plain: string[]; patterns: string[] } {\n\tconst plain: string[] = [];\n\tconst patterns: string[] = [];\n\tfor (const entry of entries) {\n\t\tif (isPattern(entry)) {\n\t\t\tpatterns.push(entry);\n\t\t} else {\n\t\t\tplain.push(entry);\n\t\t}\n\t}\n\treturn { plain, patterns };\n}\n\nexport function collectFiles(\n\tdir: string,\n\tfilePattern: RegExp,\n\tskipNodeModules = true,\n\tignoreMatcher?: IgnoreMatcher,\n\trootDir?: string,\n): string[] {\n\tconst files: string[] = [];\n\tif (!existsSync(dir)) return files;\n\n\tconst root = rootDir ?? dir;\n\tconst ig = ignoreMatcher ?? ignore();\n\taddIgnoreRules(ig, dir, root);\n\n\ttry {\n\t\tconst entries = readdirSync(dir, { withFileTypes: true });\n\t\tfor (const entry of entries) {\n\t\t\tif (entry.name.startsWith(\".\")) continue;\n\t\t\tif (skipNodeModules && entry.name === \"node_modules\") continue;\n\n\t\t\tconst fullPath = join(dir, entry.name);\n\t\t\tlet isDir = entry.isDirectory();\n\t\t\tlet isFile = entry.isFile();\n\n\t\t\tif (entry.isSymbolicLink()) {\n\t\t\t\ttry {\n\t\t\t\t\tconst stats = statSync(fullPath);\n\t\t\t\t\tisDir = stats.isDirectory();\n\t\t\t\t\tisFile = stats.isFile();\n\t\t\t\t} catch {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst relPath = toPosixPath(relative(root, fullPath));\n\t\t\tconst ignorePath = isDir ? `${relPath}/` : relPath;\n\t\t\tif (ig.ignores(ignorePath)) continue;\n\n\t\t\tif (isDir) {\n\t\t\t\tfiles.push(...collectFiles(fullPath, filePattern, skipNodeModules, ig, root));\n\t\t\t} else if (isFile && filePattern.test(entry.name)) {\n\t\t\t\tfiles.push(fullPath);\n\t\t\t}\n\t\t}\n\t} catch {\n\t\t// Ignore errors\n\t}\n\n\treturn files;\n}\n\nexport type SkillDiscoveryMode = \"pi\" | \"agents\";\n\nexport function collectSkillEntries(\n\tdir: string,\n\tmode: SkillDiscoveryMode,\n\tignoreMatcher?: IgnoreMatcher,\n\trootDir?: string,\n): string[] {\n\tconst entries: string[] = [];\n\tif (!existsSync(dir)) return entries;\n\n\tconst root = rootDir ?? dir;\n\tconst ig = ignoreMatcher ?? ignore();\n\taddIgnoreRules(ig, dir, root);\n\n\ttry {\n\t\tconst dirEntries = readdirSync(dir, { withFileTypes: true });\n\n\t\tfor (const entry of dirEntries) {\n\t\t\tif (entry.name !== \"SKILL.md\") {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tconst fullPath = join(dir, entry.name);\n\t\t\tlet isFile = entry.isFile();\n\t\t\tif (entry.isSymbolicLink()) {\n\t\t\t\ttry {\n\t\t\t\t\tisFile = statSync(fullPath).isFile();\n\t\t\t\t} catch {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst relPath = toPosixPath(relative(root, fullPath));\n\t\t\tif (isFile && !ig.ignores(relPath)) {\n\t\t\t\tentries.push(fullPath);\n\t\t\t\treturn entries;\n\t\t\t}\n\t\t}\n\n\t\tfor (const entry of dirEntries) {\n\t\t\tif (entry.name.startsWith(\".\")) continue;\n\t\t\tif (entry.name === \"node_modules\") continue;\n\n\t\t\tconst fullPath = join(dir, entry.name);\n\t\t\tlet isDir = entry.isDirectory();\n\t\t\tlet isFile = entry.isFile();\n\n\t\t\tif (entry.isSymbolicLink()) {\n\t\t\t\ttry {\n\t\t\t\t\tconst stats = statSync(fullPath);\n\t\t\t\t\tisDir = stats.isDirectory();\n\t\t\t\t\tisFile = stats.isFile();\n\t\t\t\t} catch {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst relPath = toPosixPath(relative(root, fullPath));\n\t\t\tif (mode === \"pi\" && dir === root && isFile && entry.name.endsWith(\".md\") && !ig.ignores(relPath)) {\n\t\t\t\tentries.push(fullPath);\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif (!isDir) continue;\n\t\t\tif (ig.ignores(`${relPath}/`)) continue;\n\n\t\t\tentries.push(...collectSkillEntries(fullPath, mode, ig, root));\n\t\t}\n\t} catch {\n\t\t// Ignore errors\n\t}\n\n\treturn entries;\n}\n\nexport function collectAutoSkillEntries(dir: string, mode: SkillDiscoveryMode): string[] {\n\treturn collectSkillEntries(dir, mode);\n}\n\nfunction findGitRepoRoot(startDir: string): string | null {\n\tlet dir = resolve(startDir);\n\twhile (true) {\n\t\tif (existsSync(join(dir, \".git\"))) {\n\t\t\treturn dir;\n\t\t}\n\t\tconst parent = dirname(dir);\n\t\tif (parent === dir) {\n\t\t\treturn null;\n\t\t}\n\t\tdir = parent;\n\t}\n}\n\nexport function collectAncestorAgentsSkillDirs(startDir: string): string[] {\n\tconst skillDirs: string[] = [];\n\tconst resolvedStartDir = resolve(startDir);\n\tconst gitRepoRoot = findGitRepoRoot(resolvedStartDir);\n\n\tlet dir = resolvedStartDir;\n\twhile (true) {\n\t\tskillDirs.push(join(dir, \".agents\", \"skills\"));\n\t\tif (gitRepoRoot && dir === gitRepoRoot) {\n\t\t\tbreak;\n\t\t}\n\t\tconst parent = dirname(dir);\n\t\tif (parent === dir) {\n\t\t\tbreak;\n\t\t}\n\t\tdir = parent;\n\t}\n\n\treturn skillDirs;\n}\n\nexport function collectAutoPromptEntries(dir: string): string[] {\n\tconst entries: string[] = [];\n\tif (!existsSync(dir)) return entries;\n\n\tconst ig = ignore();\n\taddIgnoreRules(ig, dir, dir);\n\n\ttry {\n\t\tconst dirEntries = readdirSync(dir, { withFileTypes: true });\n\t\tfor (const entry of dirEntries) {\n\t\t\tif (entry.name.startsWith(\".\")) continue;\n\t\t\tif (entry.name === \"node_modules\") continue;\n\n\t\t\tconst fullPath = join(dir, entry.name);\n\t\t\tlet isFile = entry.isFile();\n\t\t\tif (entry.isSymbolicLink()) {\n\t\t\t\ttry {\n\t\t\t\t\tisFile = statSync(fullPath).isFile();\n\t\t\t\t} catch {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst relPath = toPosixPath(relative(dir, fullPath));\n\t\t\tif (ig.ignores(relPath)) continue;\n\n\t\t\tif (isFile && entry.name.endsWith(\".md\")) {\n\t\t\t\tentries.push(fullPath);\n\t\t\t}\n\t\t}\n\t} catch {\n\t\t// Ignore errors\n\t}\n\n\treturn entries;\n}\n\nexport function collectAutoThemeEntries(dir: string): string[] {\n\tconst entries: string[] = [];\n\tif (!existsSync(dir)) return entries;\n\n\tconst ig = ignore();\n\taddIgnoreRules(ig, dir, dir);\n\n\ttry {\n\t\tconst dirEntries = readdirSync(dir, { withFileTypes: true });\n\t\tfor (const entry of dirEntries) {\n\t\t\tif (entry.name.startsWith(\".\")) continue;\n\t\t\tif (entry.name === \"node_modules\") continue;\n\n\t\t\tconst fullPath = join(dir, entry.name);\n\t\t\tlet isFile = entry.isFile();\n\t\t\tif (entry.isSymbolicLink()) {\n\t\t\t\ttry {\n\t\t\t\t\tisFile = statSync(fullPath).isFile();\n\t\t\t\t} catch {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst relPath = toPosixPath(relative(dir, fullPath));\n\t\t\tif (ig.ignores(relPath)) continue;\n\n\t\t\tif (isFile && entry.name.endsWith(\".json\")) {\n\t\t\t\tentries.push(fullPath);\n\t\t\t}\n\t\t}\n\t} catch {\n\t\t// Ignore errors\n\t}\n\n\treturn entries;\n}\n\nexport function readHooCodeManifestFile(packageJsonPath: string): HooCodeManifest | null {\n\ttry {\n\t\tconst content = readFileSync(packageJsonPath, \"utf-8\");\n\t\tconst pkg = JSON.parse(content) as { hoocode?: HooCodeManifest; pi?: HooCodeManifest };\n\t\treturn pkg.hoocode ?? pkg.pi ?? null;\n\t} catch {\n\t\treturn null;\n\t}\n}\n\nexport function resolveExtensionEntries(dir: string): string[] | null {\n\tconst packageJsonPath = join(dir, \"package.json\");\n\tif (existsSync(packageJsonPath)) {\n\t\tconst manifest = readHooCodeManifestFile(packageJsonPath);\n\t\tif (manifest?.extensions?.length) {\n\t\t\tconst entries: string[] = [];\n\t\t\tfor (const extPath of manifest.extensions) {\n\t\t\t\tconst resolvedExtPath = resolve(dir, extPath);\n\t\t\t\tif (existsSync(resolvedExtPath)) {\n\t\t\t\t\tentries.push(resolvedExtPath);\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (entries.length > 0) {\n\t\t\t\treturn entries;\n\t\t\t}\n\t\t}\n\t}\n\n\tconst indexTs = join(dir, \"index.ts\");\n\tconst indexJs = join(dir, \"index.js\");\n\tif (existsSync(indexTs)) {\n\t\treturn [indexTs];\n\t}\n\tif (existsSync(indexJs)) {\n\t\treturn [indexJs];\n\t}\n\n\treturn null;\n}\n\nexport function collectAutoExtensionEntries(dir: string): string[] {\n\tconst entries: string[] = [];\n\tif (!existsSync(dir)) return entries;\n\n\t// First check if this directory itself has explicit extension entries (package.json or index)\n\tconst rootEntries = resolveExtensionEntries(dir);\n\tif (rootEntries) {\n\t\treturn rootEntries;\n\t}\n\n\t// Otherwise, discover extensions from directory contents\n\tconst ig = ignore();\n\taddIgnoreRules(ig, dir, dir);\n\n\ttry {\n\t\tconst dirEntries = readdirSync(dir, { withFileTypes: true });\n\t\tfor (const entry of dirEntries) {\n\t\t\tif (entry.name.startsWith(\".\")) continue;\n\t\t\tif (entry.name === \"node_modules\") continue;\n\n\t\t\tconst fullPath = join(dir, entry.name);\n\t\t\tlet isDir = entry.isDirectory();\n\t\t\tlet isFile = entry.isFile();\n\n\t\t\tif (entry.isSymbolicLink()) {\n\t\t\t\ttry {\n\t\t\t\t\tconst stats = statSync(fullPath);\n\t\t\t\t\tisDir = stats.isDirectory();\n\t\t\t\t\tisFile = stats.isFile();\n\t\t\t\t} catch {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst relPath = toPosixPath(relative(dir, fullPath));\n\t\t\tconst ignorePath = isDir ? `${relPath}/` : relPath;\n\t\t\tif (ig.ignores(ignorePath)) continue;\n\n\t\t\tif (isFile && (entry.name.endsWith(\".ts\") || entry.name.endsWith(\".js\"))) {\n\t\t\t\tentries.push(fullPath);\n\t\t\t} else if (isDir) {\n\t\t\t\tconst resolvedEntries = resolveExtensionEntries(fullPath);\n\t\t\t\tif (resolvedEntries) {\n\t\t\t\t\tentries.push(...resolvedEntries);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t} catch {\n\t\t// Ignore errors\n\t}\n\n\treturn entries;\n}\n\n/**\n * Collect resource files from a directory based on resource type.\n * Extensions use smart discovery (index.ts in subdirs), others use recursive collection.\n */\nexport function collectResourceFiles(dir: string, resourceType: ResourceType): string[] {\n\tif (resourceType === \"skills\") {\n\t\treturn collectSkillEntries(dir, \"pi\");\n\t}\n\tif (resourceType === \"extensions\") {\n\t\treturn collectAutoExtensionEntries(dir);\n\t}\n\treturn collectFiles(dir, FILE_PATTERNS[resourceType]);\n}\n\nfunction matchesAnyPattern(filePath: string, patterns: string[], baseDir: string): boolean {\n\tconst rel = toPosixPath(relative(baseDir, filePath));\n\tconst name = basename(filePath);\n\tconst filePathPosix = toPosixPath(filePath);\n\tconst isSkillFile = name === \"SKILL.md\";\n\tconst parentDir = isSkillFile ? dirname(filePath) : undefined;\n\tconst parentRel = isSkillFile ? toPosixPath(relative(baseDir, parentDir!)) : undefined;\n\tconst parentName = isSkillFile ? basename(parentDir!) : undefined;\n\tconst parentDirPosix = isSkillFile ? toPosixPath(parentDir!) : undefined;\n\n\treturn patterns.some((pattern) => {\n\t\tconst normalizedPattern = toPosixPath(pattern);\n\t\tif (\n\t\t\tminimatch(rel, normalizedPattern) ||\n\t\t\tminimatch(name, normalizedPattern) ||\n\t\t\tminimatch(filePathPosix, normalizedPattern)\n\t\t) {\n\t\t\treturn true;\n\t\t}\n\t\tif (!isSkillFile) return false;\n\t\treturn (\n\t\t\tminimatch(parentRel!, normalizedPattern) ||\n\t\t\tminimatch(parentName!, normalizedPattern) ||\n\t\t\tminimatch(parentDirPosix!, normalizedPattern)\n\t\t);\n\t});\n}\n\nfunction normalizeExactPattern(pattern: string): string {\n\tconst normalized = pattern.startsWith(\"./\") || pattern.startsWith(\".\\\\\") ? pattern.slice(2) : pattern;\n\treturn toPosixPath(normalized);\n}\n\nfunction matchesAnyExactPattern(filePath: string, patterns: string[], baseDir: string): boolean {\n\tif (patterns.length === 0) return false;\n\tconst rel = toPosixPath(relative(baseDir, filePath));\n\tconst name = basename(filePath);\n\tconst filePathPosix = toPosixPath(filePath);\n\tconst isSkillFile = name === \"SKILL.md\";\n\tconst parentDir = isSkillFile ? dirname(filePath) : undefined;\n\tconst parentRel = isSkillFile ? toPosixPath(relative(baseDir, parentDir!)) : undefined;\n\tconst parentDirPosix = isSkillFile ? toPosixPath(parentDir!) : undefined;\n\n\treturn patterns.some((pattern) => {\n\t\tconst normalized = normalizeExactPattern(pattern);\n\t\tif (normalized === rel || normalized === filePathPosix) {\n\t\t\treturn true;\n\t\t}\n\t\tif (!isSkillFile) return false;\n\t\treturn normalized === parentRel || normalized === parentDirPosix;\n\t});\n}\n\nexport function getOverridePatterns(entries: string[]): string[] {\n\treturn entries.filter((pattern) => pattern.startsWith(\"!\") || pattern.startsWith(\"+\") || pattern.startsWith(\"-\"));\n}\n\nexport function isEnabledByOverrides(filePath: string, patterns: string[], baseDir: string): boolean {\n\tconst overrides = getOverridePatterns(patterns);\n\tconst excludes = overrides.filter((pattern) => pattern.startsWith(\"!\")).map((pattern) => pattern.slice(1));\n\tconst forceIncludes = overrides.filter((pattern) => pattern.startsWith(\"+\")).map((pattern) => pattern.slice(1));\n\tconst forceExcludes = overrides.filter((pattern) => pattern.startsWith(\"-\")).map((pattern) => pattern.slice(1));\n\n\tlet enabled = true;\n\tif (excludes.length > 0 && matchesAnyPattern(filePath, excludes, baseDir)) {\n\t\tenabled = false;\n\t}\n\tif (forceIncludes.length > 0 && matchesAnyExactPattern(filePath, forceIncludes, baseDir)) {\n\t\tenabled = true;\n\t}\n\tif (forceExcludes.length > 0 && matchesAnyExactPattern(filePath, forceExcludes, baseDir)) {\n\t\tenabled = false;\n\t}\n\treturn enabled;\n}\n\n/**\n * Apply patterns to paths and return a Set of enabled paths.\n * Pattern types:\n * - Plain patterns: include matching paths\n * - `!pattern`: exclude matching paths\n * - `+path`: force-include exact path (overrides exclusions)\n * - `-path`: force-exclude exact path (overrides force-includes)\n */\nexport function applyPatterns(allPaths: string[], patterns: string[], baseDir: string): Set<string> {\n\tconst includes: string[] = [];\n\tconst excludes: string[] = [];\n\tconst forceIncludes: string[] = [];\n\tconst forceExcludes: string[] = [];\n\n\tfor (const p of patterns) {\n\t\tif (p.startsWith(\"+\")) {\n\t\t\tforceIncludes.push(p.slice(1));\n\t\t} else if (p.startsWith(\"-\")) {\n\t\t\tforceExcludes.push(p.slice(1));\n\t\t} else if (p.startsWith(\"!\")) {\n\t\t\texcludes.push(p.slice(1));\n\t\t} else {\n\t\t\tincludes.push(p);\n\t\t}\n\t}\n\n\t// Step 1: Apply includes (or all if no includes)\n\tlet result: string[];\n\tif (includes.length === 0) {\n\t\tresult = [...allPaths];\n\t} else {\n\t\tresult = allPaths.filter((filePath) => matchesAnyPattern(filePath, includes, baseDir));\n\t}\n\n\t// Step 2: Apply excludes\n\tif (excludes.length > 0) {\n\t\tresult = result.filter((filePath) => !matchesAnyPattern(filePath, excludes, baseDir));\n\t}\n\n\t// Step 3: Force-include (add back from allPaths, overriding exclusions)\n\tif (forceIncludes.length > 0) {\n\t\tfor (const filePath of allPaths) {\n\t\t\tif (!result.includes(filePath) && matchesAnyExactPattern(filePath, forceIncludes, baseDir)) {\n\t\t\t\tresult.push(filePath);\n\t\t\t}\n\t\t}\n\t}\n\n\t// Step 4: Force-exclude (remove even if included or force-included)\n\tif (forceExcludes.length > 0) {\n\t\tresult = result.filter((filePath) => !matchesAnyExactPattern(filePath, forceExcludes, baseDir));\n\t}\n\n\treturn new Set(result);\n}\n"]}
|
|
@@ -0,0 +1,530 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resource discovery and pattern matching for the package manager.
|
|
3
|
+
*
|
|
4
|
+
* Pure filesystem/string helpers extracted from package-manager.ts: recursive
|
|
5
|
+
* file collection honoring nested ignore files, skill/prompt/theme/extension
|
|
6
|
+
* auto-discovery, hoocode package-manifest reading, and the include/exclude
|
|
7
|
+
* pattern engine (plain globs plus `!`/`+`/`-` overrides). None of these touch
|
|
8
|
+
* the DefaultPackageManager instance; it imports and composes them.
|
|
9
|
+
*/
|
|
10
|
+
import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
|
|
11
|
+
import { homedir } from "node:os";
|
|
12
|
+
import { basename, dirname, join, relative, resolve, sep } from "node:path";
|
|
13
|
+
import ignore from "ignore";
|
|
14
|
+
import { minimatch } from "minimatch";
|
|
15
|
+
export const RESOURCE_TYPES = ["extensions", "skills", "prompts", "themes", "agents"];
|
|
16
|
+
export const SETTINGS_RESOURCE_TYPES = ["extensions", "skills", "prompts", "themes"];
|
|
17
|
+
export const FILE_PATTERNS = {
|
|
18
|
+
extensions: /\.(ts|js)$/,
|
|
19
|
+
skills: /\.md$/,
|
|
20
|
+
prompts: /\.md$/,
|
|
21
|
+
themes: /\.json$/,
|
|
22
|
+
agents: /\.md$/,
|
|
23
|
+
};
|
|
24
|
+
const IGNORE_FILE_NAMES = [".gitignore", ".ignore", ".fdignore"];
|
|
25
|
+
function toPosixPath(p) {
|
|
26
|
+
return p.split(sep).join("/");
|
|
27
|
+
}
|
|
28
|
+
export function getHomeDir() {
|
|
29
|
+
return process.env.HOME || homedir();
|
|
30
|
+
}
|
|
31
|
+
function prefixIgnorePattern(line, prefix) {
|
|
32
|
+
const trimmed = line.trim();
|
|
33
|
+
if (!trimmed)
|
|
34
|
+
return null;
|
|
35
|
+
if (trimmed.startsWith("#") && !trimmed.startsWith("\\#"))
|
|
36
|
+
return null;
|
|
37
|
+
let pattern = line;
|
|
38
|
+
let negated = false;
|
|
39
|
+
if (pattern.startsWith("!")) {
|
|
40
|
+
negated = true;
|
|
41
|
+
pattern = pattern.slice(1);
|
|
42
|
+
}
|
|
43
|
+
else if (pattern.startsWith("\\!")) {
|
|
44
|
+
pattern = pattern.slice(1);
|
|
45
|
+
}
|
|
46
|
+
if (pattern.startsWith("/")) {
|
|
47
|
+
pattern = pattern.slice(1);
|
|
48
|
+
}
|
|
49
|
+
const prefixed = prefix ? `${prefix}${pattern}` : pattern;
|
|
50
|
+
return negated ? `!${prefixed}` : prefixed;
|
|
51
|
+
}
|
|
52
|
+
function addIgnoreRules(ig, dir, rootDir) {
|
|
53
|
+
const relativeDir = relative(rootDir, dir);
|
|
54
|
+
const prefix = relativeDir ? `${toPosixPath(relativeDir)}/` : "";
|
|
55
|
+
for (const filename of IGNORE_FILE_NAMES) {
|
|
56
|
+
const ignorePath = join(dir, filename);
|
|
57
|
+
if (!existsSync(ignorePath))
|
|
58
|
+
continue;
|
|
59
|
+
try {
|
|
60
|
+
const content = readFileSync(ignorePath, "utf-8");
|
|
61
|
+
const patterns = content
|
|
62
|
+
.split(/\r?\n/)
|
|
63
|
+
.map((line) => prefixIgnorePattern(line, prefix))
|
|
64
|
+
.filter((line) => Boolean(line));
|
|
65
|
+
if (patterns.length > 0) {
|
|
66
|
+
ig.add(patterns);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
catch { }
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
export function isPattern(s) {
|
|
73
|
+
return s.startsWith("!") || s.startsWith("+") || s.startsWith("-") || s.includes("*") || s.includes("?");
|
|
74
|
+
}
|
|
75
|
+
export function isOverridePattern(s) {
|
|
76
|
+
return s.startsWith("!") || s.startsWith("+") || s.startsWith("-");
|
|
77
|
+
}
|
|
78
|
+
export function hasGlobPattern(s) {
|
|
79
|
+
return s.includes("*") || s.includes("?");
|
|
80
|
+
}
|
|
81
|
+
export function splitPatterns(entries) {
|
|
82
|
+
const plain = [];
|
|
83
|
+
const patterns = [];
|
|
84
|
+
for (const entry of entries) {
|
|
85
|
+
if (isPattern(entry)) {
|
|
86
|
+
patterns.push(entry);
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
plain.push(entry);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return { plain, patterns };
|
|
93
|
+
}
|
|
94
|
+
export function collectFiles(dir, filePattern, skipNodeModules = true, ignoreMatcher, rootDir) {
|
|
95
|
+
const files = [];
|
|
96
|
+
if (!existsSync(dir))
|
|
97
|
+
return files;
|
|
98
|
+
const root = rootDir ?? dir;
|
|
99
|
+
const ig = ignoreMatcher ?? ignore();
|
|
100
|
+
addIgnoreRules(ig, dir, root);
|
|
101
|
+
try {
|
|
102
|
+
const entries = readdirSync(dir, { withFileTypes: true });
|
|
103
|
+
for (const entry of entries) {
|
|
104
|
+
if (entry.name.startsWith("."))
|
|
105
|
+
continue;
|
|
106
|
+
if (skipNodeModules && entry.name === "node_modules")
|
|
107
|
+
continue;
|
|
108
|
+
const fullPath = join(dir, entry.name);
|
|
109
|
+
let isDir = entry.isDirectory();
|
|
110
|
+
let isFile = entry.isFile();
|
|
111
|
+
if (entry.isSymbolicLink()) {
|
|
112
|
+
try {
|
|
113
|
+
const stats = statSync(fullPath);
|
|
114
|
+
isDir = stats.isDirectory();
|
|
115
|
+
isFile = stats.isFile();
|
|
116
|
+
}
|
|
117
|
+
catch {
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
const relPath = toPosixPath(relative(root, fullPath));
|
|
122
|
+
const ignorePath = isDir ? `${relPath}/` : relPath;
|
|
123
|
+
if (ig.ignores(ignorePath))
|
|
124
|
+
continue;
|
|
125
|
+
if (isDir) {
|
|
126
|
+
files.push(...collectFiles(fullPath, filePattern, skipNodeModules, ig, root));
|
|
127
|
+
}
|
|
128
|
+
else if (isFile && filePattern.test(entry.name)) {
|
|
129
|
+
files.push(fullPath);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
catch {
|
|
134
|
+
// Ignore errors
|
|
135
|
+
}
|
|
136
|
+
return files;
|
|
137
|
+
}
|
|
138
|
+
export function collectSkillEntries(dir, mode, ignoreMatcher, rootDir) {
|
|
139
|
+
const entries = [];
|
|
140
|
+
if (!existsSync(dir))
|
|
141
|
+
return entries;
|
|
142
|
+
const root = rootDir ?? dir;
|
|
143
|
+
const ig = ignoreMatcher ?? ignore();
|
|
144
|
+
addIgnoreRules(ig, dir, root);
|
|
145
|
+
try {
|
|
146
|
+
const dirEntries = readdirSync(dir, { withFileTypes: true });
|
|
147
|
+
for (const entry of dirEntries) {
|
|
148
|
+
if (entry.name !== "SKILL.md") {
|
|
149
|
+
continue;
|
|
150
|
+
}
|
|
151
|
+
const fullPath = join(dir, entry.name);
|
|
152
|
+
let isFile = entry.isFile();
|
|
153
|
+
if (entry.isSymbolicLink()) {
|
|
154
|
+
try {
|
|
155
|
+
isFile = statSync(fullPath).isFile();
|
|
156
|
+
}
|
|
157
|
+
catch {
|
|
158
|
+
continue;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
const relPath = toPosixPath(relative(root, fullPath));
|
|
162
|
+
if (isFile && !ig.ignores(relPath)) {
|
|
163
|
+
entries.push(fullPath);
|
|
164
|
+
return entries;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
for (const entry of dirEntries) {
|
|
168
|
+
if (entry.name.startsWith("."))
|
|
169
|
+
continue;
|
|
170
|
+
if (entry.name === "node_modules")
|
|
171
|
+
continue;
|
|
172
|
+
const fullPath = join(dir, entry.name);
|
|
173
|
+
let isDir = entry.isDirectory();
|
|
174
|
+
let isFile = entry.isFile();
|
|
175
|
+
if (entry.isSymbolicLink()) {
|
|
176
|
+
try {
|
|
177
|
+
const stats = statSync(fullPath);
|
|
178
|
+
isDir = stats.isDirectory();
|
|
179
|
+
isFile = stats.isFile();
|
|
180
|
+
}
|
|
181
|
+
catch {
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
const relPath = toPosixPath(relative(root, fullPath));
|
|
186
|
+
if (mode === "pi" && dir === root && isFile && entry.name.endsWith(".md") && !ig.ignores(relPath)) {
|
|
187
|
+
entries.push(fullPath);
|
|
188
|
+
continue;
|
|
189
|
+
}
|
|
190
|
+
if (!isDir)
|
|
191
|
+
continue;
|
|
192
|
+
if (ig.ignores(`${relPath}/`))
|
|
193
|
+
continue;
|
|
194
|
+
entries.push(...collectSkillEntries(fullPath, mode, ig, root));
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
catch {
|
|
198
|
+
// Ignore errors
|
|
199
|
+
}
|
|
200
|
+
return entries;
|
|
201
|
+
}
|
|
202
|
+
export function collectAutoSkillEntries(dir, mode) {
|
|
203
|
+
return collectSkillEntries(dir, mode);
|
|
204
|
+
}
|
|
205
|
+
function findGitRepoRoot(startDir) {
|
|
206
|
+
let dir = resolve(startDir);
|
|
207
|
+
while (true) {
|
|
208
|
+
if (existsSync(join(dir, ".git"))) {
|
|
209
|
+
return dir;
|
|
210
|
+
}
|
|
211
|
+
const parent = dirname(dir);
|
|
212
|
+
if (parent === dir) {
|
|
213
|
+
return null;
|
|
214
|
+
}
|
|
215
|
+
dir = parent;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
export function collectAncestorAgentsSkillDirs(startDir) {
|
|
219
|
+
const skillDirs = [];
|
|
220
|
+
const resolvedStartDir = resolve(startDir);
|
|
221
|
+
const gitRepoRoot = findGitRepoRoot(resolvedStartDir);
|
|
222
|
+
let dir = resolvedStartDir;
|
|
223
|
+
while (true) {
|
|
224
|
+
skillDirs.push(join(dir, ".agents", "skills"));
|
|
225
|
+
if (gitRepoRoot && dir === gitRepoRoot) {
|
|
226
|
+
break;
|
|
227
|
+
}
|
|
228
|
+
const parent = dirname(dir);
|
|
229
|
+
if (parent === dir) {
|
|
230
|
+
break;
|
|
231
|
+
}
|
|
232
|
+
dir = parent;
|
|
233
|
+
}
|
|
234
|
+
return skillDirs;
|
|
235
|
+
}
|
|
236
|
+
export function collectAutoPromptEntries(dir) {
|
|
237
|
+
const entries = [];
|
|
238
|
+
if (!existsSync(dir))
|
|
239
|
+
return entries;
|
|
240
|
+
const ig = ignore();
|
|
241
|
+
addIgnoreRules(ig, dir, dir);
|
|
242
|
+
try {
|
|
243
|
+
const dirEntries = readdirSync(dir, { withFileTypes: true });
|
|
244
|
+
for (const entry of dirEntries) {
|
|
245
|
+
if (entry.name.startsWith("."))
|
|
246
|
+
continue;
|
|
247
|
+
if (entry.name === "node_modules")
|
|
248
|
+
continue;
|
|
249
|
+
const fullPath = join(dir, entry.name);
|
|
250
|
+
let isFile = entry.isFile();
|
|
251
|
+
if (entry.isSymbolicLink()) {
|
|
252
|
+
try {
|
|
253
|
+
isFile = statSync(fullPath).isFile();
|
|
254
|
+
}
|
|
255
|
+
catch {
|
|
256
|
+
continue;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
const relPath = toPosixPath(relative(dir, fullPath));
|
|
260
|
+
if (ig.ignores(relPath))
|
|
261
|
+
continue;
|
|
262
|
+
if (isFile && entry.name.endsWith(".md")) {
|
|
263
|
+
entries.push(fullPath);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
catch {
|
|
268
|
+
// Ignore errors
|
|
269
|
+
}
|
|
270
|
+
return entries;
|
|
271
|
+
}
|
|
272
|
+
export function collectAutoThemeEntries(dir) {
|
|
273
|
+
const entries = [];
|
|
274
|
+
if (!existsSync(dir))
|
|
275
|
+
return entries;
|
|
276
|
+
const ig = ignore();
|
|
277
|
+
addIgnoreRules(ig, dir, dir);
|
|
278
|
+
try {
|
|
279
|
+
const dirEntries = readdirSync(dir, { withFileTypes: true });
|
|
280
|
+
for (const entry of dirEntries) {
|
|
281
|
+
if (entry.name.startsWith("."))
|
|
282
|
+
continue;
|
|
283
|
+
if (entry.name === "node_modules")
|
|
284
|
+
continue;
|
|
285
|
+
const fullPath = join(dir, entry.name);
|
|
286
|
+
let isFile = entry.isFile();
|
|
287
|
+
if (entry.isSymbolicLink()) {
|
|
288
|
+
try {
|
|
289
|
+
isFile = statSync(fullPath).isFile();
|
|
290
|
+
}
|
|
291
|
+
catch {
|
|
292
|
+
continue;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
const relPath = toPosixPath(relative(dir, fullPath));
|
|
296
|
+
if (ig.ignores(relPath))
|
|
297
|
+
continue;
|
|
298
|
+
if (isFile && entry.name.endsWith(".json")) {
|
|
299
|
+
entries.push(fullPath);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
catch {
|
|
304
|
+
// Ignore errors
|
|
305
|
+
}
|
|
306
|
+
return entries;
|
|
307
|
+
}
|
|
308
|
+
export function readHooCodeManifestFile(packageJsonPath) {
|
|
309
|
+
try {
|
|
310
|
+
const content = readFileSync(packageJsonPath, "utf-8");
|
|
311
|
+
const pkg = JSON.parse(content);
|
|
312
|
+
return pkg.hoocode ?? pkg.pi ?? null;
|
|
313
|
+
}
|
|
314
|
+
catch {
|
|
315
|
+
return null;
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
export function resolveExtensionEntries(dir) {
|
|
319
|
+
const packageJsonPath = join(dir, "package.json");
|
|
320
|
+
if (existsSync(packageJsonPath)) {
|
|
321
|
+
const manifest = readHooCodeManifestFile(packageJsonPath);
|
|
322
|
+
if (manifest?.extensions?.length) {
|
|
323
|
+
const entries = [];
|
|
324
|
+
for (const extPath of manifest.extensions) {
|
|
325
|
+
const resolvedExtPath = resolve(dir, extPath);
|
|
326
|
+
if (existsSync(resolvedExtPath)) {
|
|
327
|
+
entries.push(resolvedExtPath);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
if (entries.length > 0) {
|
|
331
|
+
return entries;
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
const indexTs = join(dir, "index.ts");
|
|
336
|
+
const indexJs = join(dir, "index.js");
|
|
337
|
+
if (existsSync(indexTs)) {
|
|
338
|
+
return [indexTs];
|
|
339
|
+
}
|
|
340
|
+
if (existsSync(indexJs)) {
|
|
341
|
+
return [indexJs];
|
|
342
|
+
}
|
|
343
|
+
return null;
|
|
344
|
+
}
|
|
345
|
+
export function collectAutoExtensionEntries(dir) {
|
|
346
|
+
const entries = [];
|
|
347
|
+
if (!existsSync(dir))
|
|
348
|
+
return entries;
|
|
349
|
+
// First check if this directory itself has explicit extension entries (package.json or index)
|
|
350
|
+
const rootEntries = resolveExtensionEntries(dir);
|
|
351
|
+
if (rootEntries) {
|
|
352
|
+
return rootEntries;
|
|
353
|
+
}
|
|
354
|
+
// Otherwise, discover extensions from directory contents
|
|
355
|
+
const ig = ignore();
|
|
356
|
+
addIgnoreRules(ig, dir, dir);
|
|
357
|
+
try {
|
|
358
|
+
const dirEntries = readdirSync(dir, { withFileTypes: true });
|
|
359
|
+
for (const entry of dirEntries) {
|
|
360
|
+
if (entry.name.startsWith("."))
|
|
361
|
+
continue;
|
|
362
|
+
if (entry.name === "node_modules")
|
|
363
|
+
continue;
|
|
364
|
+
const fullPath = join(dir, entry.name);
|
|
365
|
+
let isDir = entry.isDirectory();
|
|
366
|
+
let isFile = entry.isFile();
|
|
367
|
+
if (entry.isSymbolicLink()) {
|
|
368
|
+
try {
|
|
369
|
+
const stats = statSync(fullPath);
|
|
370
|
+
isDir = stats.isDirectory();
|
|
371
|
+
isFile = stats.isFile();
|
|
372
|
+
}
|
|
373
|
+
catch {
|
|
374
|
+
continue;
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
const relPath = toPosixPath(relative(dir, fullPath));
|
|
378
|
+
const ignorePath = isDir ? `${relPath}/` : relPath;
|
|
379
|
+
if (ig.ignores(ignorePath))
|
|
380
|
+
continue;
|
|
381
|
+
if (isFile && (entry.name.endsWith(".ts") || entry.name.endsWith(".js"))) {
|
|
382
|
+
entries.push(fullPath);
|
|
383
|
+
}
|
|
384
|
+
else if (isDir) {
|
|
385
|
+
const resolvedEntries = resolveExtensionEntries(fullPath);
|
|
386
|
+
if (resolvedEntries) {
|
|
387
|
+
entries.push(...resolvedEntries);
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
catch {
|
|
393
|
+
// Ignore errors
|
|
394
|
+
}
|
|
395
|
+
return entries;
|
|
396
|
+
}
|
|
397
|
+
/**
|
|
398
|
+
* Collect resource files from a directory based on resource type.
|
|
399
|
+
* Extensions use smart discovery (index.ts in subdirs), others use recursive collection.
|
|
400
|
+
*/
|
|
401
|
+
export function collectResourceFiles(dir, resourceType) {
|
|
402
|
+
if (resourceType === "skills") {
|
|
403
|
+
return collectSkillEntries(dir, "pi");
|
|
404
|
+
}
|
|
405
|
+
if (resourceType === "extensions") {
|
|
406
|
+
return collectAutoExtensionEntries(dir);
|
|
407
|
+
}
|
|
408
|
+
return collectFiles(dir, FILE_PATTERNS[resourceType]);
|
|
409
|
+
}
|
|
410
|
+
function matchesAnyPattern(filePath, patterns, baseDir) {
|
|
411
|
+
const rel = toPosixPath(relative(baseDir, filePath));
|
|
412
|
+
const name = basename(filePath);
|
|
413
|
+
const filePathPosix = toPosixPath(filePath);
|
|
414
|
+
const isSkillFile = name === "SKILL.md";
|
|
415
|
+
const parentDir = isSkillFile ? dirname(filePath) : undefined;
|
|
416
|
+
const parentRel = isSkillFile ? toPosixPath(relative(baseDir, parentDir)) : undefined;
|
|
417
|
+
const parentName = isSkillFile ? basename(parentDir) : undefined;
|
|
418
|
+
const parentDirPosix = isSkillFile ? toPosixPath(parentDir) : undefined;
|
|
419
|
+
return patterns.some((pattern) => {
|
|
420
|
+
const normalizedPattern = toPosixPath(pattern);
|
|
421
|
+
if (minimatch(rel, normalizedPattern) ||
|
|
422
|
+
minimatch(name, normalizedPattern) ||
|
|
423
|
+
minimatch(filePathPosix, normalizedPattern)) {
|
|
424
|
+
return true;
|
|
425
|
+
}
|
|
426
|
+
if (!isSkillFile)
|
|
427
|
+
return false;
|
|
428
|
+
return (minimatch(parentRel, normalizedPattern) ||
|
|
429
|
+
minimatch(parentName, normalizedPattern) ||
|
|
430
|
+
minimatch(parentDirPosix, normalizedPattern));
|
|
431
|
+
});
|
|
432
|
+
}
|
|
433
|
+
function normalizeExactPattern(pattern) {
|
|
434
|
+
const normalized = pattern.startsWith("./") || pattern.startsWith(".\\") ? pattern.slice(2) : pattern;
|
|
435
|
+
return toPosixPath(normalized);
|
|
436
|
+
}
|
|
437
|
+
function matchesAnyExactPattern(filePath, patterns, baseDir) {
|
|
438
|
+
if (patterns.length === 0)
|
|
439
|
+
return false;
|
|
440
|
+
const rel = toPosixPath(relative(baseDir, filePath));
|
|
441
|
+
const name = basename(filePath);
|
|
442
|
+
const filePathPosix = toPosixPath(filePath);
|
|
443
|
+
const isSkillFile = name === "SKILL.md";
|
|
444
|
+
const parentDir = isSkillFile ? dirname(filePath) : undefined;
|
|
445
|
+
const parentRel = isSkillFile ? toPosixPath(relative(baseDir, parentDir)) : undefined;
|
|
446
|
+
const parentDirPosix = isSkillFile ? toPosixPath(parentDir) : undefined;
|
|
447
|
+
return patterns.some((pattern) => {
|
|
448
|
+
const normalized = normalizeExactPattern(pattern);
|
|
449
|
+
if (normalized === rel || normalized === filePathPosix) {
|
|
450
|
+
return true;
|
|
451
|
+
}
|
|
452
|
+
if (!isSkillFile)
|
|
453
|
+
return false;
|
|
454
|
+
return normalized === parentRel || normalized === parentDirPosix;
|
|
455
|
+
});
|
|
456
|
+
}
|
|
457
|
+
export function getOverridePatterns(entries) {
|
|
458
|
+
return entries.filter((pattern) => pattern.startsWith("!") || pattern.startsWith("+") || pattern.startsWith("-"));
|
|
459
|
+
}
|
|
460
|
+
export function isEnabledByOverrides(filePath, patterns, baseDir) {
|
|
461
|
+
const overrides = getOverridePatterns(patterns);
|
|
462
|
+
const excludes = overrides.filter((pattern) => pattern.startsWith("!")).map((pattern) => pattern.slice(1));
|
|
463
|
+
const forceIncludes = overrides.filter((pattern) => pattern.startsWith("+")).map((pattern) => pattern.slice(1));
|
|
464
|
+
const forceExcludes = overrides.filter((pattern) => pattern.startsWith("-")).map((pattern) => pattern.slice(1));
|
|
465
|
+
let enabled = true;
|
|
466
|
+
if (excludes.length > 0 && matchesAnyPattern(filePath, excludes, baseDir)) {
|
|
467
|
+
enabled = false;
|
|
468
|
+
}
|
|
469
|
+
if (forceIncludes.length > 0 && matchesAnyExactPattern(filePath, forceIncludes, baseDir)) {
|
|
470
|
+
enabled = true;
|
|
471
|
+
}
|
|
472
|
+
if (forceExcludes.length > 0 && matchesAnyExactPattern(filePath, forceExcludes, baseDir)) {
|
|
473
|
+
enabled = false;
|
|
474
|
+
}
|
|
475
|
+
return enabled;
|
|
476
|
+
}
|
|
477
|
+
/**
|
|
478
|
+
* Apply patterns to paths and return a Set of enabled paths.
|
|
479
|
+
* Pattern types:
|
|
480
|
+
* - Plain patterns: include matching paths
|
|
481
|
+
* - `!pattern`: exclude matching paths
|
|
482
|
+
* - `+path`: force-include exact path (overrides exclusions)
|
|
483
|
+
* - `-path`: force-exclude exact path (overrides force-includes)
|
|
484
|
+
*/
|
|
485
|
+
export function applyPatterns(allPaths, patterns, baseDir) {
|
|
486
|
+
const includes = [];
|
|
487
|
+
const excludes = [];
|
|
488
|
+
const forceIncludes = [];
|
|
489
|
+
const forceExcludes = [];
|
|
490
|
+
for (const p of patterns) {
|
|
491
|
+
if (p.startsWith("+")) {
|
|
492
|
+
forceIncludes.push(p.slice(1));
|
|
493
|
+
}
|
|
494
|
+
else if (p.startsWith("-")) {
|
|
495
|
+
forceExcludes.push(p.slice(1));
|
|
496
|
+
}
|
|
497
|
+
else if (p.startsWith("!")) {
|
|
498
|
+
excludes.push(p.slice(1));
|
|
499
|
+
}
|
|
500
|
+
else {
|
|
501
|
+
includes.push(p);
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
// Step 1: Apply includes (or all if no includes)
|
|
505
|
+
let result;
|
|
506
|
+
if (includes.length === 0) {
|
|
507
|
+
result = [...allPaths];
|
|
508
|
+
}
|
|
509
|
+
else {
|
|
510
|
+
result = allPaths.filter((filePath) => matchesAnyPattern(filePath, includes, baseDir));
|
|
511
|
+
}
|
|
512
|
+
// Step 2: Apply excludes
|
|
513
|
+
if (excludes.length > 0) {
|
|
514
|
+
result = result.filter((filePath) => !matchesAnyPattern(filePath, excludes, baseDir));
|
|
515
|
+
}
|
|
516
|
+
// Step 3: Force-include (add back from allPaths, overriding exclusions)
|
|
517
|
+
if (forceIncludes.length > 0) {
|
|
518
|
+
for (const filePath of allPaths) {
|
|
519
|
+
if (!result.includes(filePath) && matchesAnyExactPattern(filePath, forceIncludes, baseDir)) {
|
|
520
|
+
result.push(filePath);
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
// Step 4: Force-exclude (remove even if included or force-included)
|
|
525
|
+
if (forceExcludes.length > 0) {
|
|
526
|
+
result = result.filter((filePath) => !matchesAnyExactPattern(filePath, forceExcludes, baseDir));
|
|
527
|
+
}
|
|
528
|
+
return new Set(result);
|
|
529
|
+
}
|
|
530
|
+
//# sourceMappingURL=package-resource-discovery.js.map
|