@h1dr0n/skill-pool 0.1.0

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.
Files changed (189) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +296 -0
  3. package/bin/cli.js +157 -0
  4. package/package.json +41 -0
  5. package/skills/api/agents/backend-specialist.md +69 -0
  6. package/skills/api/agents/database-optimizer.md +176 -0
  7. package/skills/api/manifest.yaml +20 -0
  8. package/skills/api/rules/auth-security.md +45 -0
  9. package/skills/api/skills/api-patterns/SKILL.md +81 -0
  10. package/skills/api/skills/api-patterns/api-style.md +42 -0
  11. package/skills/api/skills/api-patterns/auth.md +24 -0
  12. package/skills/api/skills/api-patterns/documentation.md +26 -0
  13. package/skills/api/skills/api-patterns/graphql.md +41 -0
  14. package/skills/api/skills/api-patterns/rate-limiting.md +31 -0
  15. package/skills/api/skills/api-patterns/response.md +37 -0
  16. package/skills/api/skills/api-patterns/rest.md +40 -0
  17. package/skills/api/skills/api-patterns/scripts/api_validator.py +211 -0
  18. package/skills/api/skills/api-patterns/security-testing.md +122 -0
  19. package/skills/api/skills/api-patterns/trpc.md +41 -0
  20. package/skills/api/skills/api-patterns/versioning.md +22 -0
  21. package/skills/api/skills/database-patterns.md +126 -0
  22. package/skills/api/skills/deployment-patterns.md +105 -0
  23. package/skills/api/skills/docker-patterns.md +135 -0
  24. package/skills/common/agents/code-reviewer.md +78 -0
  25. package/skills/common/agents/planner.md +80 -0
  26. package/skills/common/agents/security-reviewer.md +82 -0
  27. package/skills/common/agents/software-architect.md +81 -0
  28. package/skills/common/manifest.yaml +25 -0
  29. package/skills/common/rules/coding-style.md +39 -0
  30. package/skills/common/rules/git-workflow.md +33 -0
  31. package/skills/common/rules/security.md +25 -0
  32. package/skills/common/skills/architecture/SKILL.md +55 -0
  33. package/skills/common/skills/architecture/context-discovery.md +43 -0
  34. package/skills/common/skills/architecture/examples.md +94 -0
  35. package/skills/common/skills/architecture/pattern-selection.md +68 -0
  36. package/skills/common/skills/architecture/patterns-reference.md +50 -0
  37. package/skills/common/skills/architecture/trade-off-analysis.md +77 -0
  38. package/skills/common/skills/brainstorming/SKILL.md +163 -0
  39. package/skills/common/skills/brainstorming/dynamic-questioning.md +350 -0
  40. package/skills/common/skills/clean-code.md +99 -0
  41. package/skills/common/skills/code-review-checklist.md +86 -0
  42. package/skills/common/skills/plan-writing/SKILL.md +152 -0
  43. package/skills/common/skills/skill-feedback.md +94 -0
  44. package/skills/common/skills/tdd-workflow.md +130 -0
  45. package/skills/common/skills/verification-loop.md +112 -0
  46. package/skills/cpp/agents/cpp-build-resolver.md +90 -0
  47. package/skills/cpp/agents/cpp-reviewer.md +72 -0
  48. package/skills/cpp/manifest.yaml +15 -0
  49. package/skills/cpp/skills/cpp-coding-standards.md +722 -0
  50. package/skills/cpp/skills/cpp-testing.md +323 -0
  51. package/skills/devops/agents/devops-automator.md +376 -0
  52. package/skills/devops/agents/sre.md +90 -0
  53. package/skills/devops/manifest.yaml +20 -0
  54. package/skills/devops/skills/deployment-patterns.md +427 -0
  55. package/skills/devops/skills/deployment-procedures/SKILL.md +241 -0
  56. package/skills/devops/skills/docker-patterns.md +364 -0
  57. package/skills/devops/skills/e2e-testing.md +326 -0
  58. package/skills/devops/skills/github-ops.md +144 -0
  59. package/skills/django/manifest.yaml +16 -0
  60. package/skills/django/skills/django-patterns.md +734 -0
  61. package/skills/django/skills/django-security.md +593 -0
  62. package/skills/django/skills/django-tdd.md +729 -0
  63. package/skills/django/skills/django-verification.md +469 -0
  64. package/skills/dotnet/agents/csharp-reviewer.md +101 -0
  65. package/skills/dotnet/manifest.yaml +14 -0
  66. package/skills/dotnet/skills/csharp-testing.md +321 -0
  67. package/skills/dotnet/skills/dotnet-patterns.md +321 -0
  68. package/skills/go/agents/code-reviewer.md +76 -0
  69. package/skills/go/agents/go-build-resolver.md +94 -0
  70. package/skills/go/agents/go-reviewer.md +76 -0
  71. package/skills/go/manifest.yaml +17 -0
  72. package/skills/go/rules/go-style.md +55 -0
  73. package/skills/go/skills/golang-patterns.md +674 -0
  74. package/skills/go/skills/golang-testing.md +720 -0
  75. package/skills/java/agents/java-build-resolver.md +153 -0
  76. package/skills/java/agents/java-reviewer.md +92 -0
  77. package/skills/java/manifest.yaml +18 -0
  78. package/skills/java/skills/java-coding-standards.md +147 -0
  79. package/skills/java/skills/jpa-patterns.md +151 -0
  80. package/skills/java/skills/springboot-patterns.md +314 -0
  81. package/skills/java/skills/springboot-security.md +272 -0
  82. package/skills/kotlin/agents/kotlin-build-resolver.md +118 -0
  83. package/skills/kotlin/agents/kotlin-reviewer.md +159 -0
  84. package/skills/kotlin/manifest.yaml +17 -0
  85. package/skills/kotlin/skills/kotlin-coroutines-flows.md +284 -0
  86. package/skills/kotlin/skills/kotlin-patterns.md +711 -0
  87. package/skills/kotlin/skills/kotlin-testing.md +824 -0
  88. package/skills/laravel/manifest.yaml +15 -0
  89. package/skills/laravel/skills/laravel-patterns.md +409 -0
  90. package/skills/laravel/skills/laravel-security.md +279 -0
  91. package/skills/laravel/skills/laravel-tdd.md +277 -0
  92. package/skills/laravel/skills/laravel-verification.md +173 -0
  93. package/skills/mobile/agents/dart-build-resolver.md +201 -0
  94. package/skills/mobile/agents/flutter-reviewer.md +243 -0
  95. package/skills/mobile/manifest.yaml +19 -0
  96. package/skills/mobile/skills/android-clean-architecture.md +339 -0
  97. package/skills/mobile/skills/dart-flutter-patterns.md +563 -0
  98. package/skills/mobile/skills/swiftui-patterns.md +259 -0
  99. package/skills/nestjs/manifest.yaml +13 -0
  100. package/skills/nestjs/skills/nestjs-patterns.md +230 -0
  101. package/skills/perl/manifest.yaml +13 -0
  102. package/skills/perl/skills/perl-patterns.md +504 -0
  103. package/skills/perl/skills/perl-security.md +503 -0
  104. package/skills/perl/skills/perl-testing.md +475 -0
  105. package/skills/python/agents/python-reviewer.md +98 -0
  106. package/skills/python/manifest.yaml +18 -0
  107. package/skills/python/rules/python-style.md +69 -0
  108. package/skills/python/skills/python-patterns/SKILL.md +441 -0
  109. package/skills/python/skills/python-patterns.md +90 -0
  110. package/skills/python/skills/python-testing.md +81 -0
  111. package/skills/rust/agents/rust-build-resolver.md +148 -0
  112. package/skills/rust/agents/rust-reviewer.md +94 -0
  113. package/skills/rust/manifest.yaml +16 -0
  114. package/skills/rust/rules/rust-style.md +107 -0
  115. package/skills/rust/skills/rust-patterns.md +499 -0
  116. package/skills/rust/skills/rust-testing.md +500 -0
  117. package/skills/security/agents/accessibility-auditor.md +316 -0
  118. package/skills/security/agents/security-reviewer.md +108 -0
  119. package/skills/security/manifest.yaml +19 -0
  120. package/skills/security/skills/red-team-tactics/SKILL.md +199 -0
  121. package/skills/security/skills/security-bounty-hunter.md +99 -0
  122. package/skills/security/skills/security-review.md +495 -0
  123. package/skills/security/skills/security-scan.md +165 -0
  124. package/skills/security/skills/vulnerability-scanner/SKILL.md +276 -0
  125. package/skills/security/skills/vulnerability-scanner/checklists.md +121 -0
  126. package/skills/security/skills/vulnerability-scanner/scripts/security_scan.py +458 -0
  127. package/skills/swift/manifest.yaml +16 -0
  128. package/skills/swift/skills/swift-actor-persistence.md +142 -0
  129. package/skills/swift/skills/swift-concurrency.md +216 -0
  130. package/skills/swift/skills/swift-protocol-di-testing.md +190 -0
  131. package/skills/swift/skills/swiftui-patterns.md +259 -0
  132. package/skills/unity/agents/game-designer.md +167 -0
  133. package/skills/unity/agents/unity-architect.md +52 -0
  134. package/skills/unity/agents/unity-editor-tool-developer.md +310 -0
  135. package/skills/unity/agents/unity-multiplayer-engineer.md +321 -0
  136. package/skills/unity/agents/unity-shader-graph-artist.md +269 -0
  137. package/skills/unity/manifest.yaml +21 -0
  138. package/skills/unity/rules/csharp-patterns.md +48 -0
  139. package/skills/unity/rules/unity-specific.md +53 -0
  140. package/skills/unity/skills/systematic-debugging.md +92 -0
  141. package/skills/unity/skills/unity-architecture.md +173 -0
  142. package/skills/unreal/agents/level-designer.md +208 -0
  143. package/skills/unreal/agents/technical-artist.md +229 -0
  144. package/skills/unreal/agents/unreal-multiplayer-architect.md +313 -0
  145. package/skills/unreal/agents/unreal-systems-engineer.md +310 -0
  146. package/skills/unreal/agents/unreal-technical-artist.md +256 -0
  147. package/skills/unreal/agents/unreal-world-builder.md +273 -0
  148. package/skills/unreal/manifest.yaml +21 -0
  149. package/skills/unreal/skills/unreal-patterns.md +183 -0
  150. package/skills/web/agents/frontend-specialist.md +71 -0
  151. package/skills/web/agents/ui-designer.md +383 -0
  152. package/skills/web/agents/ux-architect.md +469 -0
  153. package/skills/web/manifest.yaml +22 -0
  154. package/skills/web/rules/accessibility.md +54 -0
  155. package/skills/web/rules/css-performance.md +52 -0
  156. package/skills/web/skills/e2e-testing.md +132 -0
  157. package/skills/web/skills/frontend-design/SKILL.md +452 -0
  158. package/skills/web/skills/frontend-design/animation-guide.md +331 -0
  159. package/skills/web/skills/frontend-design/color-system.md +311 -0
  160. package/skills/web/skills/frontend-design/decision-trees.md +418 -0
  161. package/skills/web/skills/frontend-design/motion-graphics.md +306 -0
  162. package/skills/web/skills/frontend-design/scripts/accessibility_checker.py +183 -0
  163. package/skills/web/skills/frontend-design/scripts/ux_audit.py +722 -0
  164. package/skills/web/skills/frontend-design/typography-system.md +345 -0
  165. package/skills/web/skills/frontend-design/ux-psychology.md +1116 -0
  166. package/skills/web/skills/frontend-design/visual-effects.md +383 -0
  167. package/skills/web/skills/react-nextjs.md +135 -0
  168. package/skills/web/skills/tailwind-patterns/SKILL.md +269 -0
  169. package/src/adapters/antigravity.js +164 -0
  170. package/src/adapters/claude.js +188 -0
  171. package/src/adapters/cursor.js +161 -0
  172. package/src/adapters/index.js +67 -0
  173. package/src/adapters/windsurf.js +158 -0
  174. package/src/commands/add.js +266 -0
  175. package/src/commands/create.js +127 -0
  176. package/src/commands/diff.js +78 -0
  177. package/src/commands/info.js +88 -0
  178. package/src/commands/init.js +224 -0
  179. package/src/commands/install.js +90 -0
  180. package/src/commands/list.js +54 -0
  181. package/src/commands/remove.js +101 -0
  182. package/src/commands/targets.js +32 -0
  183. package/src/commands/update.js +57 -0
  184. package/src/core/manifest.js +57 -0
  185. package/src/core/plugins.js +86 -0
  186. package/src/core/resolver.js +84 -0
  187. package/src/core/tracker.js +49 -0
  188. package/src/utils/fs.js +80 -0
  189. package/src/utils/git.js +52 -0
@@ -0,0 +1,101 @@
1
+ import chalk from 'chalk';
2
+ import {
3
+ loadTracker,
4
+ saveTracker,
5
+ isInstalled,
6
+ recordRemove,
7
+ } from '../core/tracker.js';
8
+ import { loadManifest } from '../core/manifest.js';
9
+ import { getAdapter, getAllTargetNames } from '../adapters/index.js';
10
+
11
+ async function findDependents(packName, tracker) {
12
+ const dependents = [];
13
+ const installedPacks = Object.keys(tracker.installed);
14
+
15
+ for (const installed of installedPacks) {
16
+ if (installed === packName) continue;
17
+ try {
18
+ const manifest = await loadManifest(installed);
19
+ if (manifest.depends.includes(packName)) {
20
+ dependents.push(installed);
21
+ }
22
+ } catch {
23
+ // skip if manifest can't be loaded
24
+ }
25
+ }
26
+
27
+ return dependents;
28
+ }
29
+
30
+ function getTargetsForPack(tracker, packName) {
31
+ const info = tracker.installed[packName];
32
+ return info?.targets || getAllTargetNames();
33
+ }
34
+
35
+ async function removeFromAllTargets(packName, targets, projectDir) {
36
+ let totalRemoved = 0;
37
+ for (const targetName of targets) {
38
+ try {
39
+ const adapter = getAdapter(targetName);
40
+ const removed = await adapter.remove(packName, projectDir);
41
+ totalRemoved += removed.length;
42
+ } catch {
43
+ // adapter might not have files for this pack
44
+ }
45
+ }
46
+ return totalRemoved;
47
+ }
48
+
49
+ export async function removeCommand(packName, options) {
50
+ const projectDir = process.cwd();
51
+ let tracker = await loadTracker(projectDir);
52
+
53
+ if (options.all) {
54
+ const installedPacks = Object.keys(tracker.installed);
55
+ if (installedPacks.length === 0) {
56
+ console.log(chalk.yellow('No packs installed.'));
57
+ return;
58
+ }
59
+
60
+ for (const name of installedPacks) {
61
+ const targets = getTargetsForPack(tracker, name);
62
+ const count = await removeFromAllTargets(name, targets, projectDir);
63
+ tracker = recordRemove(tracker, name);
64
+ console.log(
65
+ chalk.red(` - ${name}`) + chalk.gray(` (${count} rules removed)`)
66
+ );
67
+ }
68
+
69
+ await saveTracker(projectDir, tracker);
70
+ console.log(chalk.green('\nAll packs removed.'));
71
+ return;
72
+ }
73
+
74
+ if (!packName) {
75
+ console.error(chalk.red('Please specify a pack name or use --all'));
76
+ process.exit(1);
77
+ }
78
+
79
+ if (!isInstalled(tracker, packName)) {
80
+ console.log(chalk.yellow(`Pack "${packName}" is not installed.`));
81
+ return;
82
+ }
83
+
84
+ const dependents = await findDependents(packName, tracker);
85
+ if (dependents.length > 0) {
86
+ console.log(
87
+ chalk.yellow(
88
+ `Warning: pack(s) ${dependents.map((d) => `"${d}"`).join(', ')} depend on "${packName}". Consider removing them first.`
89
+ )
90
+ );
91
+ }
92
+
93
+ const targets = getTargetsForPack(tracker, packName);
94
+ const count = await removeFromAllTargets(packName, targets, projectDir);
95
+ tracker = recordRemove(tracker, packName);
96
+ await saveTracker(projectDir, tracker);
97
+
98
+ console.log(
99
+ chalk.red(` - ${packName}`) + chalk.gray(` (${count} rules removed)`)
100
+ );
101
+ }
@@ -0,0 +1,32 @@
1
+ import chalk from 'chalk';
2
+ import { getAllTargetNames, detectTargets } from '../adapters/index.js';
3
+
4
+ const CONFIG_DIRS = {
5
+ claude: '.claude/',
6
+ cursor: '.cursor/',
7
+ windsurf: '.windsurf/',
8
+ antigravity: '.agent/',
9
+ };
10
+
11
+ export async function targetsCommand() {
12
+ const allTargets = getAllTargetNames();
13
+ const detected = await detectTargets(process.cwd());
14
+ const detectedSet = new Set(detected);
15
+
16
+ const maxNameLen = Math.max(...allTargets.map((name) => name.length));
17
+
18
+ console.log(chalk.bold('\nSupported targets:\n'));
19
+
20
+ for (const name of allTargets) {
21
+ const isDetected = detectedSet.has(name);
22
+ const icon = isDetected ? chalk.green('\u2713') : chalk.gray('-');
23
+ const displayName = isDetected ? chalk.white(name) : chalk.gray(name);
24
+ const configDir = CONFIG_DIRS[name] || 'unknown';
25
+ const displayDir = isDetected ? chalk.white(configDir) : chalk.gray(configDir);
26
+ const padding = ' '.repeat(maxNameLen - name.length);
27
+
28
+ console.log(` ${icon} ${displayName}${padding} ${displayDir}`);
29
+ }
30
+
31
+ console.log('');
32
+ }
@@ -0,0 +1,57 @@
1
+ import chalk from 'chalk';
2
+ import { loadManifest } from '../core/manifest.js';
3
+ import {
4
+ loadTracker,
5
+ saveTracker,
6
+ recordInstall,
7
+ } from '../core/tracker.js';
8
+ import { getAdapter } from '../adapters/index.js';
9
+
10
+ export async function updateCommand(packName) {
11
+ const projectDir = process.cwd();
12
+ let tracker = await loadTracker(projectDir);
13
+ const installedPacks = Object.keys(tracker.installed);
14
+
15
+ if (installedPacks.length === 0) {
16
+ console.log(chalk.yellow('No packs installed.'));
17
+ return;
18
+ }
19
+
20
+ const toUpdate = packName ? [packName] : installedPacks;
21
+ let updatedCount = 0;
22
+
23
+ for (const name of toUpdate) {
24
+ const info = tracker.installed[name];
25
+ if (!info) {
26
+ console.log(chalk.yellow(`Pack "${name}" is not installed.`));
27
+ continue;
28
+ }
29
+
30
+ const manifest = await loadManifest(name);
31
+ const targets = info.targets || ['claude'];
32
+
33
+ // Re-install to pick up content changes
34
+ let totalFiles = 0;
35
+ for (const targetName of targets) {
36
+ const adapter = getAdapter(targetName);
37
+ const files = await adapter.install(manifest, projectDir);
38
+ totalFiles += files.length;
39
+ }
40
+
41
+ tracker = recordInstall(tracker, name, manifest.version, targets);
42
+ updatedCount++;
43
+
44
+ const versionChanged = info.version !== manifest.version;
45
+ const label = versionChanged
46
+ ? `${info.version} -> ${manifest.version}`
47
+ : manifest.version;
48
+
49
+ console.log(
50
+ chalk.green(` ~ ${name}@${label}`) +
51
+ chalk.gray(` (${totalFiles} rules)`)
52
+ );
53
+ }
54
+
55
+ await saveTracker(projectDir, tracker);
56
+ console.log(chalk.green(`\nUpdated ${updatedCount} pack(s).`));
57
+ }
@@ -0,0 +1,57 @@
1
+ import path from 'node:path';
2
+ import { fileURLToPath } from 'node:url';
3
+ import yaml from 'js-yaml';
4
+ import { readFileContent, listDir } from '../utils/fs.js';
5
+
6
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
7
+ const SKILLS_DIR = path.resolve(__dirname, '..', '..', 'skills');
8
+
9
+ export function getSkillsDir() {
10
+ return SKILLS_DIR;
11
+ }
12
+
13
+ export async function loadManifest(packName) {
14
+ const manifestPath = path.join(SKILLS_DIR, packName, 'manifest.yaml');
15
+ const content = await readFileContent(manifestPath);
16
+ const manifest = yaml.load(content);
17
+
18
+ if (!manifest.name) {
19
+ throw new Error(`Manifest for "${packName}" is missing required field: name`);
20
+ }
21
+
22
+ const hasContent =
23
+ (manifest.rules && manifest.rules.length > 0) ||
24
+ (manifest.skills && manifest.skills.length > 0) ||
25
+ (manifest.agents && manifest.agents.length > 0);
26
+
27
+ if (!hasContent) {
28
+ throw new Error(`Manifest for "${packName}" must have at least one of: rules, skills, agents`);
29
+ }
30
+
31
+ return {
32
+ name: manifest.name,
33
+ version: manifest.version || '0.0.0',
34
+ description: manifest.description || '',
35
+ depends: manifest.depends || [],
36
+ tags: manifest.tags || [],
37
+ rules: manifest.rules || [],
38
+ skills: manifest.skills || [],
39
+ agents: manifest.agents || [],
40
+ };
41
+ }
42
+
43
+ export async function listAvailablePacks() {
44
+ const entries = await listDir(SKILLS_DIR);
45
+ const packs = [];
46
+
47
+ for (const entry of entries) {
48
+ try {
49
+ const manifest = await loadManifest(entry);
50
+ packs.push(manifest);
51
+ } catch {
52
+ // skip directories without valid manifests
53
+ }
54
+ }
55
+
56
+ return packs;
57
+ }
@@ -0,0 +1,86 @@
1
+ import path from 'node:path';
2
+ import { readFileContent } from '../utils/fs.js';
3
+
4
+ const CONFIG_FILES = ['.skillpoolrc.json', 'skillpool.config.js'];
5
+
6
+ /**
7
+ * Load plugin configuration from project root.
8
+ * Looks for .skillpoolrc.json or skillpool.config.js.
9
+ *
10
+ * @param {string} projectDir - absolute path to the project root
11
+ * @returns {Promise<{ adapters: Record<string, string> }>}
12
+ */
13
+ async function loadConfig(projectDir) {
14
+ // Try .skillpoolrc.json first
15
+ try {
16
+ const jsonPath = path.join(projectDir, CONFIG_FILES[0]);
17
+ const content = await readFileContent(jsonPath);
18
+ const config = JSON.parse(content);
19
+ return { adapters: config.adapters || {} };
20
+ } catch {
21
+ // not found or invalid, try next
22
+ }
23
+
24
+ // Try skillpool.config.js
25
+ try {
26
+ const jsPath = path.join(projectDir, CONFIG_FILES[1]);
27
+ const fileUrl = 'file:///' + jsPath.replace(/\\/g, '/');
28
+ const mod = await import(fileUrl);
29
+ const config = mod.default || mod;
30
+ return { adapters: config.adapters || {} };
31
+ } catch {
32
+ // not found or invalid
33
+ }
34
+
35
+ return { adapters: {} };
36
+ }
37
+
38
+ const REQUIRED_METHODS = ['detect', 'install', 'remove', 'list'];
39
+
40
+ /**
41
+ * Validate that a custom adapter exports the required interface.
42
+ *
43
+ * @param {object} adapter - the imported adapter module
44
+ * @param {string} name - adapter name for error messages
45
+ */
46
+ function validateAdapter(adapter, name) {
47
+ for (const method of REQUIRED_METHODS) {
48
+ if (typeof adapter[method] !== 'function') {
49
+ throw new Error(
50
+ `Custom adapter "${name}" is missing required method: ${method}`
51
+ );
52
+ }
53
+ }
54
+ }
55
+
56
+ /**
57
+ * Load custom adapters defined in the project config file.
58
+ *
59
+ * @param {string} projectDir - absolute path to the project root
60
+ * @returns {Promise<Record<string, object>>} map of adapter name to adapter module
61
+ */
62
+ export async function loadPlugins(projectDir) {
63
+ const config = await loadConfig(projectDir);
64
+ const customAdapters = {};
65
+
66
+ for (const [name, adapterPath] of Object.entries(config.adapters)) {
67
+ const resolvedPath = path.resolve(projectDir, adapterPath);
68
+ const fileUrl = 'file:///' + resolvedPath.replace(/\\/g, '/');
69
+
70
+ try {
71
+ const mod = await import(fileUrl);
72
+ const adapter = mod.default || mod;
73
+ validateAdapter(adapter, name);
74
+ customAdapters[name] = adapter;
75
+ } catch (err) {
76
+ if (err.message.includes('missing required method')) {
77
+ throw err;
78
+ }
79
+ throw new Error(
80
+ `Failed to load custom adapter "${name}" from ${adapterPath}: ${err.message}`
81
+ );
82
+ }
83
+ }
84
+
85
+ return customAdapters;
86
+ }
@@ -0,0 +1,84 @@
1
+ import { loadManifest } from './manifest.js';
2
+
3
+ export async function resolveDependencies(packName) {
4
+ const resolved = [];
5
+ const visited = new Set();
6
+ const visiting = new Set();
7
+
8
+ async function visit(name) {
9
+ if (resolved.includes(name)) return;
10
+
11
+ if (visiting.has(name)) {
12
+ throw new Error(`Circular dependency detected: ${name}`);
13
+ }
14
+
15
+ visiting.add(name);
16
+ const manifest = await loadManifest(name);
17
+
18
+ for (const dep of manifest.depends) {
19
+ await visit(dep);
20
+ }
21
+
22
+ visiting.delete(name);
23
+ visited.add(name);
24
+ resolved.push(name);
25
+ }
26
+
27
+ await visit(packName);
28
+ return resolved;
29
+ }
30
+
31
+ /**
32
+ * Check for version conflicts across multiple packs' dependency trees.
33
+ *
34
+ * For each requested pack, resolves its full dependency tree and records
35
+ * which version each dependency has. If a dependency appears in multiple
36
+ * trees with different versions, it is reported as a conflict.
37
+ *
38
+ * @param {string[]} packNames - list of top-level pack names to install
39
+ * @returns {Promise<Array<{ dep: string, versions: string[], requestedBy: string[] }>>}
40
+ */
41
+ export async function checkConflicts(packNames) {
42
+ // Map: dep name -> Map<version, Set<requestedBy>>
43
+ const depVersions = new Map();
44
+
45
+ for (const packName of packNames) {
46
+ const deps = await resolveDependencies(packName);
47
+
48
+ for (const depName of deps) {
49
+ const manifest = await loadManifest(depName);
50
+ const version = manifest.version;
51
+
52
+ if (!depVersions.has(depName)) {
53
+ depVersions.set(depName, new Map());
54
+ }
55
+
56
+ const versionMap = depVersions.get(depName);
57
+ if (!versionMap.has(version)) {
58
+ versionMap.set(version, new Set());
59
+ }
60
+ versionMap.get(version).add(packName);
61
+ }
62
+ }
63
+
64
+ const conflicts = [];
65
+
66
+ for (const [dep, versionMap] of depVersions) {
67
+ if (versionMap.size > 1) {
68
+ const versions = [...versionMap.keys()].sort();
69
+ const requestedBy = new Set();
70
+ for (const requesterSet of versionMap.values()) {
71
+ for (const requester of requesterSet) {
72
+ requestedBy.add(requester);
73
+ }
74
+ }
75
+ conflicts.push({
76
+ dep,
77
+ versions,
78
+ requestedBy: [...requestedBy],
79
+ });
80
+ }
81
+ }
82
+
83
+ return conflicts;
84
+ }
@@ -0,0 +1,49 @@
1
+ import path from 'node:path';
2
+ import { readFileContent, writeFileAtomic, fileExists } from '../utils/fs.js';
3
+
4
+ const TRACKER_FILE = '.skillpool.json';
5
+
6
+ function defaultTracker() {
7
+ return { version: '1.0.0', installed: {} };
8
+ }
9
+
10
+ export async function loadTracker(projectDir) {
11
+ const filePath = path.join(projectDir, TRACKER_FILE);
12
+ if (!(await fileExists(filePath))) {
13
+ return defaultTracker();
14
+ }
15
+ const content = await readFileContent(filePath);
16
+ return JSON.parse(content);
17
+ }
18
+
19
+ export async function saveTracker(projectDir, data) {
20
+ const filePath = path.join(projectDir, TRACKER_FILE);
21
+ await writeFileAtomic(filePath, JSON.stringify(data, null, 2) + '\n');
22
+ }
23
+
24
+ export function isInstalled(trackerData, packName) {
25
+ return packName in trackerData.installed;
26
+ }
27
+
28
+ export function getInstalledVersion(trackerData, packName) {
29
+ return trackerData.installed[packName]?.version || null;
30
+ }
31
+
32
+ export function recordInstall(trackerData, packName, version, targets) {
33
+ return {
34
+ ...trackerData,
35
+ installed: {
36
+ ...trackerData.installed,
37
+ [packName]: {
38
+ version,
39
+ installedAt: new Date().toISOString(),
40
+ targets,
41
+ },
42
+ },
43
+ };
44
+ }
45
+
46
+ export function recordRemove(trackerData, packName) {
47
+ const { [packName]: _, ...rest } = trackerData.installed;
48
+ return { ...trackerData, installed: rest };
49
+ }
@@ -0,0 +1,80 @@
1
+ import { mkdir, writeFile, readFile, unlink, stat, readdir, rm, cp } from 'node:fs/promises';
2
+ import path from 'node:path';
3
+
4
+ export async function ensureDir(dirPath) {
5
+ await mkdir(dirPath, { recursive: true });
6
+ }
7
+
8
+ export async function writeFileAtomic(filePath, content) {
9
+ await ensureDir(path.dirname(filePath));
10
+ await writeFile(filePath, content, 'utf-8');
11
+ }
12
+
13
+ export async function readFileContent(filePath) {
14
+ return readFile(filePath, 'utf-8');
15
+ }
16
+
17
+ export async function removeFile(filePath) {
18
+ try {
19
+ await unlink(filePath);
20
+ return true;
21
+ } catch (err) {
22
+ if (err.code === 'ENOENT') return false;
23
+ throw err;
24
+ }
25
+ }
26
+
27
+ export async function fileExists(filePath) {
28
+ try {
29
+ await stat(filePath);
30
+ return true;
31
+ } catch {
32
+ return false;
33
+ }
34
+ }
35
+
36
+ export async function listDir(dirPath) {
37
+ try {
38
+ return await readdir(dirPath);
39
+ } catch (err) {
40
+ if (err.code === 'ENOENT') return [];
41
+ throw err;
42
+ }
43
+ }
44
+
45
+ export async function copyDir(src, dest) {
46
+ await cp(src, dest, { recursive: true });
47
+ }
48
+
49
+ export async function removeDir(dirPath) {
50
+ try {
51
+ await rm(dirPath, { recursive: true, force: true });
52
+ return true;
53
+ } catch {
54
+ return false;
55
+ }
56
+ }
57
+
58
+ export async function isDirectory(filePath) {
59
+ try {
60
+ const s = await stat(filePath);
61
+ return s.isDirectory();
62
+ } catch {
63
+ return false;
64
+ }
65
+ }
66
+
67
+ export async function listDirRecursive(dirPath) {
68
+ const results = [];
69
+ const entries = await listDir(dirPath);
70
+ for (const entry of entries) {
71
+ const fullPath = path.join(dirPath, entry);
72
+ if (await isDirectory(fullPath)) {
73
+ const subEntries = await listDirRecursive(fullPath);
74
+ results.push(...subEntries);
75
+ } else {
76
+ results.push(fullPath);
77
+ }
78
+ }
79
+ return results;
80
+ }
@@ -0,0 +1,52 @@
1
+ import { execFile } from 'node:child_process';
2
+
3
+ /**
4
+ * Run a git command and return stdout.
5
+ * @param {string[]} args - git subcommand and arguments
6
+ * @param {object} [options] - execFile options (cwd, etc.)
7
+ * @returns {Promise<string>}
8
+ */
9
+ function runGit(args, options = {}) {
10
+ return new Promise((resolve, reject) => {
11
+ execFile('git', args, { timeout: 60_000, ...options }, (error, stdout, stderr) => {
12
+ if (error) {
13
+ const message = stderr?.trim() || error.message;
14
+ reject(new Error(`git ${args[0]} failed: ${message}`));
15
+ return;
16
+ }
17
+ resolve(stdout.trim());
18
+ });
19
+ });
20
+ }
21
+
22
+ /**
23
+ * Clone a repository with --depth 1.
24
+ * @param {string} url - Repository URL
25
+ * @param {string} dest - Destination directory
26
+ * @param {string} [branch] - Optional branch or tag to clone
27
+ * @returns {Promise<void>}
28
+ */
29
+ export async function cloneRepo(url, dest, branch) {
30
+ const args = ['clone', '--depth', '1'];
31
+
32
+ if (branch) {
33
+ args.push('--branch', branch);
34
+ }
35
+
36
+ args.push(url, dest);
37
+
38
+ await runGit(args);
39
+ }
40
+
41
+ /**
42
+ * Check whether git is available on the system.
43
+ * @returns {Promise<boolean>}
44
+ */
45
+ export async function isGitAvailable() {
46
+ try {
47
+ await runGit(['--version']);
48
+ return true;
49
+ } catch {
50
+ return false;
51
+ }
52
+ }