@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.
- package/LICENSE +21 -0
- package/README.md +296 -0
- package/bin/cli.js +157 -0
- package/package.json +41 -0
- package/skills/api/agents/backend-specialist.md +69 -0
- package/skills/api/agents/database-optimizer.md +176 -0
- package/skills/api/manifest.yaml +20 -0
- package/skills/api/rules/auth-security.md +45 -0
- package/skills/api/skills/api-patterns/SKILL.md +81 -0
- package/skills/api/skills/api-patterns/api-style.md +42 -0
- package/skills/api/skills/api-patterns/auth.md +24 -0
- package/skills/api/skills/api-patterns/documentation.md +26 -0
- package/skills/api/skills/api-patterns/graphql.md +41 -0
- package/skills/api/skills/api-patterns/rate-limiting.md +31 -0
- package/skills/api/skills/api-patterns/response.md +37 -0
- package/skills/api/skills/api-patterns/rest.md +40 -0
- package/skills/api/skills/api-patterns/scripts/api_validator.py +211 -0
- package/skills/api/skills/api-patterns/security-testing.md +122 -0
- package/skills/api/skills/api-patterns/trpc.md +41 -0
- package/skills/api/skills/api-patterns/versioning.md +22 -0
- package/skills/api/skills/database-patterns.md +126 -0
- package/skills/api/skills/deployment-patterns.md +105 -0
- package/skills/api/skills/docker-patterns.md +135 -0
- package/skills/common/agents/code-reviewer.md +78 -0
- package/skills/common/agents/planner.md +80 -0
- package/skills/common/agents/security-reviewer.md +82 -0
- package/skills/common/agents/software-architect.md +81 -0
- package/skills/common/manifest.yaml +25 -0
- package/skills/common/rules/coding-style.md +39 -0
- package/skills/common/rules/git-workflow.md +33 -0
- package/skills/common/rules/security.md +25 -0
- package/skills/common/skills/architecture/SKILL.md +55 -0
- package/skills/common/skills/architecture/context-discovery.md +43 -0
- package/skills/common/skills/architecture/examples.md +94 -0
- package/skills/common/skills/architecture/pattern-selection.md +68 -0
- package/skills/common/skills/architecture/patterns-reference.md +50 -0
- package/skills/common/skills/architecture/trade-off-analysis.md +77 -0
- package/skills/common/skills/brainstorming/SKILL.md +163 -0
- package/skills/common/skills/brainstorming/dynamic-questioning.md +350 -0
- package/skills/common/skills/clean-code.md +99 -0
- package/skills/common/skills/code-review-checklist.md +86 -0
- package/skills/common/skills/plan-writing/SKILL.md +152 -0
- package/skills/common/skills/skill-feedback.md +94 -0
- package/skills/common/skills/tdd-workflow.md +130 -0
- package/skills/common/skills/verification-loop.md +112 -0
- package/skills/cpp/agents/cpp-build-resolver.md +90 -0
- package/skills/cpp/agents/cpp-reviewer.md +72 -0
- package/skills/cpp/manifest.yaml +15 -0
- package/skills/cpp/skills/cpp-coding-standards.md +722 -0
- package/skills/cpp/skills/cpp-testing.md +323 -0
- package/skills/devops/agents/devops-automator.md +376 -0
- package/skills/devops/agents/sre.md +90 -0
- package/skills/devops/manifest.yaml +20 -0
- package/skills/devops/skills/deployment-patterns.md +427 -0
- package/skills/devops/skills/deployment-procedures/SKILL.md +241 -0
- package/skills/devops/skills/docker-patterns.md +364 -0
- package/skills/devops/skills/e2e-testing.md +326 -0
- package/skills/devops/skills/github-ops.md +144 -0
- package/skills/django/manifest.yaml +16 -0
- package/skills/django/skills/django-patterns.md +734 -0
- package/skills/django/skills/django-security.md +593 -0
- package/skills/django/skills/django-tdd.md +729 -0
- package/skills/django/skills/django-verification.md +469 -0
- package/skills/dotnet/agents/csharp-reviewer.md +101 -0
- package/skills/dotnet/manifest.yaml +14 -0
- package/skills/dotnet/skills/csharp-testing.md +321 -0
- package/skills/dotnet/skills/dotnet-patterns.md +321 -0
- package/skills/go/agents/code-reviewer.md +76 -0
- package/skills/go/agents/go-build-resolver.md +94 -0
- package/skills/go/agents/go-reviewer.md +76 -0
- package/skills/go/manifest.yaml +17 -0
- package/skills/go/rules/go-style.md +55 -0
- package/skills/go/skills/golang-patterns.md +674 -0
- package/skills/go/skills/golang-testing.md +720 -0
- package/skills/java/agents/java-build-resolver.md +153 -0
- package/skills/java/agents/java-reviewer.md +92 -0
- package/skills/java/manifest.yaml +18 -0
- package/skills/java/skills/java-coding-standards.md +147 -0
- package/skills/java/skills/jpa-patterns.md +151 -0
- package/skills/java/skills/springboot-patterns.md +314 -0
- package/skills/java/skills/springboot-security.md +272 -0
- package/skills/kotlin/agents/kotlin-build-resolver.md +118 -0
- package/skills/kotlin/agents/kotlin-reviewer.md +159 -0
- package/skills/kotlin/manifest.yaml +17 -0
- package/skills/kotlin/skills/kotlin-coroutines-flows.md +284 -0
- package/skills/kotlin/skills/kotlin-patterns.md +711 -0
- package/skills/kotlin/skills/kotlin-testing.md +824 -0
- package/skills/laravel/manifest.yaml +15 -0
- package/skills/laravel/skills/laravel-patterns.md +409 -0
- package/skills/laravel/skills/laravel-security.md +279 -0
- package/skills/laravel/skills/laravel-tdd.md +277 -0
- package/skills/laravel/skills/laravel-verification.md +173 -0
- package/skills/mobile/agents/dart-build-resolver.md +201 -0
- package/skills/mobile/agents/flutter-reviewer.md +243 -0
- package/skills/mobile/manifest.yaml +19 -0
- package/skills/mobile/skills/android-clean-architecture.md +339 -0
- package/skills/mobile/skills/dart-flutter-patterns.md +563 -0
- package/skills/mobile/skills/swiftui-patterns.md +259 -0
- package/skills/nestjs/manifest.yaml +13 -0
- package/skills/nestjs/skills/nestjs-patterns.md +230 -0
- package/skills/perl/manifest.yaml +13 -0
- package/skills/perl/skills/perl-patterns.md +504 -0
- package/skills/perl/skills/perl-security.md +503 -0
- package/skills/perl/skills/perl-testing.md +475 -0
- package/skills/python/agents/python-reviewer.md +98 -0
- package/skills/python/manifest.yaml +18 -0
- package/skills/python/rules/python-style.md +69 -0
- package/skills/python/skills/python-patterns/SKILL.md +441 -0
- package/skills/python/skills/python-patterns.md +90 -0
- package/skills/python/skills/python-testing.md +81 -0
- package/skills/rust/agents/rust-build-resolver.md +148 -0
- package/skills/rust/agents/rust-reviewer.md +94 -0
- package/skills/rust/manifest.yaml +16 -0
- package/skills/rust/rules/rust-style.md +107 -0
- package/skills/rust/skills/rust-patterns.md +499 -0
- package/skills/rust/skills/rust-testing.md +500 -0
- package/skills/security/agents/accessibility-auditor.md +316 -0
- package/skills/security/agents/security-reviewer.md +108 -0
- package/skills/security/manifest.yaml +19 -0
- package/skills/security/skills/red-team-tactics/SKILL.md +199 -0
- package/skills/security/skills/security-bounty-hunter.md +99 -0
- package/skills/security/skills/security-review.md +495 -0
- package/skills/security/skills/security-scan.md +165 -0
- package/skills/security/skills/vulnerability-scanner/SKILL.md +276 -0
- package/skills/security/skills/vulnerability-scanner/checklists.md +121 -0
- package/skills/security/skills/vulnerability-scanner/scripts/security_scan.py +458 -0
- package/skills/swift/manifest.yaml +16 -0
- package/skills/swift/skills/swift-actor-persistence.md +142 -0
- package/skills/swift/skills/swift-concurrency.md +216 -0
- package/skills/swift/skills/swift-protocol-di-testing.md +190 -0
- package/skills/swift/skills/swiftui-patterns.md +259 -0
- package/skills/unity/agents/game-designer.md +167 -0
- package/skills/unity/agents/unity-architect.md +52 -0
- package/skills/unity/agents/unity-editor-tool-developer.md +310 -0
- package/skills/unity/agents/unity-multiplayer-engineer.md +321 -0
- package/skills/unity/agents/unity-shader-graph-artist.md +269 -0
- package/skills/unity/manifest.yaml +21 -0
- package/skills/unity/rules/csharp-patterns.md +48 -0
- package/skills/unity/rules/unity-specific.md +53 -0
- package/skills/unity/skills/systematic-debugging.md +92 -0
- package/skills/unity/skills/unity-architecture.md +173 -0
- package/skills/unreal/agents/level-designer.md +208 -0
- package/skills/unreal/agents/technical-artist.md +229 -0
- package/skills/unreal/agents/unreal-multiplayer-architect.md +313 -0
- package/skills/unreal/agents/unreal-systems-engineer.md +310 -0
- package/skills/unreal/agents/unreal-technical-artist.md +256 -0
- package/skills/unreal/agents/unreal-world-builder.md +273 -0
- package/skills/unreal/manifest.yaml +21 -0
- package/skills/unreal/skills/unreal-patterns.md +183 -0
- package/skills/web/agents/frontend-specialist.md +71 -0
- package/skills/web/agents/ui-designer.md +383 -0
- package/skills/web/agents/ux-architect.md +469 -0
- package/skills/web/manifest.yaml +22 -0
- package/skills/web/rules/accessibility.md +54 -0
- package/skills/web/rules/css-performance.md +52 -0
- package/skills/web/skills/e2e-testing.md +132 -0
- package/skills/web/skills/frontend-design/SKILL.md +452 -0
- package/skills/web/skills/frontend-design/animation-guide.md +331 -0
- package/skills/web/skills/frontend-design/color-system.md +311 -0
- package/skills/web/skills/frontend-design/decision-trees.md +418 -0
- package/skills/web/skills/frontend-design/motion-graphics.md +306 -0
- package/skills/web/skills/frontend-design/scripts/accessibility_checker.py +183 -0
- package/skills/web/skills/frontend-design/scripts/ux_audit.py +722 -0
- package/skills/web/skills/frontend-design/typography-system.md +345 -0
- package/skills/web/skills/frontend-design/ux-psychology.md +1116 -0
- package/skills/web/skills/frontend-design/visual-effects.md +383 -0
- package/skills/web/skills/react-nextjs.md +135 -0
- package/skills/web/skills/tailwind-patterns/SKILL.md +269 -0
- package/src/adapters/antigravity.js +164 -0
- package/src/adapters/claude.js +188 -0
- package/src/adapters/cursor.js +161 -0
- package/src/adapters/index.js +67 -0
- package/src/adapters/windsurf.js +158 -0
- package/src/commands/add.js +266 -0
- package/src/commands/create.js +127 -0
- package/src/commands/diff.js +78 -0
- package/src/commands/info.js +88 -0
- package/src/commands/init.js +224 -0
- package/src/commands/install.js +90 -0
- package/src/commands/list.js +54 -0
- package/src/commands/remove.js +101 -0
- package/src/commands/targets.js +32 -0
- package/src/commands/update.js +57 -0
- package/src/core/manifest.js +57 -0
- package/src/core/plugins.js +86 -0
- package/src/core/resolver.js +84 -0
- package/src/core/tracker.js +49 -0
- package/src/utils/fs.js +80 -0
- 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
|
+
}
|
package/src/utils/fs.js
ADDED
|
@@ -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
|
+
}
|
package/src/utils/git.js
ADDED
|
@@ -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
|
+
}
|