sigma-skills 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.
@@ -0,0 +1,4 @@
1
+ export { SkillScanner } from './scanner.js';
2
+ export { SkillLoader } from './loader.js';
3
+ export * from './types.js';
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAC5C,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC1C,cAAc,YAAY,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,23 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ exports.SkillLoader = exports.SkillScanner = void 0;
18
+ var scanner_js_1 = require("./scanner.js");
19
+ Object.defineProperty(exports, "SkillScanner", { enumerable: true, get: function () { return scanner_js_1.SkillScanner; } });
20
+ var loader_js_1 = require("./loader.js");
21
+ Object.defineProperty(exports, "SkillLoader", { enumerable: true, get: function () { return loader_js_1.SkillLoader; } });
22
+ __exportStar(require("./types.js"), exports);
23
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;AAAA,2CAA4C;AAAnC,0GAAA,YAAY,OAAA;AACrB,yCAA0C;AAAjC,wGAAA,WAAW,OAAA;AACpB,6CAA2B"}
@@ -0,0 +1,31 @@
1
+ import { Skill, SkillMatch } from './types.js';
2
+ import { SkillScanner } from './scanner.js';
3
+ export declare class SkillLoader {
4
+ private scanner;
5
+ private skills;
6
+ private lastScanTime;
7
+ private readonly SCAN_CACHE_MS;
8
+ constructor(scanner: SkillScanner);
9
+ /**
10
+ * Cherche les skills pertinents pour un prompt donné
11
+ */
12
+ findRelevantSkills(prompt: string): SkillMatch[];
13
+ /**
14
+ * Retourne le contenu d'un skill par nom
15
+ */
16
+ getSkillContext(skillName: string): string | null;
17
+ /**
18
+ * Liste tous les skills installés
19
+ */
20
+ listSkills(): Skill[];
21
+ /**
22
+ * Copie un skill d'un chemin source vers le répertoire skills
23
+ */
24
+ installSkill(source: string, targetDir?: string): boolean;
25
+ private refreshSkillsIfNeeded;
26
+ private refreshSkills;
27
+ private forceRefresh;
28
+ private extractWords;
29
+ private copyDirectory;
30
+ }
31
+ //# sourceMappingURL=loader.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"loader.d.ts","sourceRoot":"","sources":["../src/loader.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,EAAE,UAAU,EAAgB,MAAM,YAAY,CAAC;AAC7D,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAE5C,qBAAa,WAAW;IACtB,OAAO,CAAC,OAAO,CAAe;IAC9B,OAAO,CAAC,MAAM,CAAe;IAC7B,OAAO,CAAC,YAAY,CAAa;IACjC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAS;gBAE3B,OAAO,EAAE,YAAY;IAKjC;;OAEG;IACH,kBAAkB,CAAC,MAAM,EAAE,MAAM,GAAG,UAAU,EAAE;IA6ChD;;OAEG;IACH,eAAe,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI;IAOjD;;OAEG;IACH,UAAU,IAAI,KAAK,EAAE;IAKrB;;OAEG;IACH,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,OAAO;IAqCzD,OAAO,CAAC,qBAAqB;IAO7B,OAAO,CAAC,aAAa;IAKrB,OAAO,CAAC,YAAY;IAKpB,OAAO,CAAC,YAAY;IAQpB,OAAO,CAAC,aAAa;CAkBtB"}
package/dist/loader.js ADDED
@@ -0,0 +1,176 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.SkillLoader = void 0;
37
+ const fs = __importStar(require("fs"));
38
+ const path = __importStar(require("path"));
39
+ class SkillLoader {
40
+ scanner;
41
+ skills = [];
42
+ lastScanTime = 0;
43
+ SCAN_CACHE_MS = 60000; // Cache les scans pendant 1 minute
44
+ constructor(scanner) {
45
+ this.scanner = scanner;
46
+ this.refreshSkills();
47
+ }
48
+ /**
49
+ * Cherche les skills pertinents pour un prompt donné
50
+ */
51
+ findRelevantSkills(prompt) {
52
+ this.refreshSkillsIfNeeded();
53
+ const promptWords = this.extractWords(prompt.toLowerCase());
54
+ const matches = [];
55
+ for (const skill of this.skills) {
56
+ const matchedKeywords = [];
57
+ let score = 0;
58
+ // Vérifier les correspondances avec les keywords du skill
59
+ for (const keyword of skill.keywords) {
60
+ if (promptWords.includes(keyword)) {
61
+ matchedKeywords.push(keyword);
62
+ score += 1;
63
+ }
64
+ }
65
+ // Bonus pour les correspondances dans le nom ou la description
66
+ const skillName = skill.name.toLowerCase();
67
+ const skillDesc = skill.description.toLowerCase();
68
+ for (const word of promptWords) {
69
+ if (skillName.includes(word)) {
70
+ score += 2; // Bonus pour le nom
71
+ }
72
+ if (skillDesc.includes(word)) {
73
+ score += 1; // Bonus pour la description
74
+ }
75
+ }
76
+ // Ajouter si pertinent
77
+ if (score > 0) {
78
+ matches.push({
79
+ skill,
80
+ matchedKeywords,
81
+ score
82
+ });
83
+ }
84
+ }
85
+ // Trier par score décroissant
86
+ return matches.sort((a, b) => b.score - a.score);
87
+ }
88
+ /**
89
+ * Retourne le contenu d'un skill par nom
90
+ */
91
+ getSkillContext(skillName) {
92
+ this.refreshSkillsIfNeeded();
93
+ const skill = this.skills.find(s => s.name === skillName);
94
+ return skill ? skill.content : null;
95
+ }
96
+ /**
97
+ * Liste tous les skills installés
98
+ */
99
+ listSkills() {
100
+ this.refreshSkillsIfNeeded();
101
+ return [...this.skills]; // Retourner une copie
102
+ }
103
+ /**
104
+ * Copie un skill d'un chemin source vers le répertoire skills
105
+ */
106
+ installSkill(source, targetDir) {
107
+ try {
108
+ // Déterminer le répertoire cible (par défaut: globalDir)
109
+ const targetBase = targetDir || this.scanner['config'].globalDir;
110
+ const skillName = path.basename(source);
111
+ const targetPath = path.join(targetBase, skillName);
112
+ // Vérifier que le source existe
113
+ if (!fs.existsSync(source)) {
114
+ console.error(`Source skill directory not found: ${source}`);
115
+ return false;
116
+ }
117
+ // Vérifier qu'il y a un SKILL.md dans le source
118
+ const sourceSkillMd = path.join(source, 'SKILL.md');
119
+ if (!fs.existsSync(sourceSkillMd)) {
120
+ console.error(`No SKILL.md found in source: ${source}`);
121
+ return false;
122
+ }
123
+ // Créer le répertoire cible si nécessaire
124
+ fs.mkdirSync(targetBase, { recursive: true });
125
+ // Copier récursivement
126
+ this.copyDirectory(source, targetPath);
127
+ // Rafraîchir la liste des skills
128
+ this.forceRefresh();
129
+ console.log(`Skill '${skillName}' installed successfully to ${targetPath}`);
130
+ return true;
131
+ }
132
+ catch (error) {
133
+ console.error(`Failed to install skill from ${source}:`, error);
134
+ return false;
135
+ }
136
+ }
137
+ refreshSkillsIfNeeded() {
138
+ const now = Date.now();
139
+ if (now - this.lastScanTime > this.SCAN_CACHE_MS) {
140
+ this.refreshSkills();
141
+ }
142
+ }
143
+ refreshSkills() {
144
+ this.skills = this.scanner.scan();
145
+ this.lastScanTime = Date.now();
146
+ }
147
+ forceRefresh() {
148
+ this.lastScanTime = 0;
149
+ this.refreshSkills();
150
+ }
151
+ extractWords(text) {
152
+ return text
153
+ .replace(/[^\w\s]/g, ' ')
154
+ .split(/\s+/)
155
+ .filter(word => word.length > 2)
156
+ .map(word => word.toLowerCase());
157
+ }
158
+ copyDirectory(source, target) {
159
+ if (!fs.existsSync(target)) {
160
+ fs.mkdirSync(target, { recursive: true });
161
+ }
162
+ const entries = fs.readdirSync(source, { withFileTypes: true });
163
+ for (const entry of entries) {
164
+ const sourcePath = path.join(source, entry.name);
165
+ const targetPath = path.join(target, entry.name);
166
+ if (entry.isDirectory()) {
167
+ this.copyDirectory(sourcePath, targetPath);
168
+ }
169
+ else {
170
+ fs.copyFileSync(sourcePath, targetPath);
171
+ }
172
+ }
173
+ }
174
+ }
175
+ exports.SkillLoader = SkillLoader;
176
+ //# sourceMappingURL=loader.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"loader.js","sourceRoot":"","sources":["../src/loader.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,21 @@
1
+ import { Skill, SkillsConfig } from './types.js';
2
+ export declare class SkillScanner {
3
+ private config;
4
+ constructor(config: SkillsConfig);
5
+ /**
6
+ * Scanne tous les répertoires et charge les skills
7
+ */
8
+ scan(): Skill[];
9
+ /**
10
+ * Charge un skill depuis un dossier (lit SKILL.md, extrait keywords des headers/bullet points)
11
+ */
12
+ loadSkill(dir: string): Skill | null;
13
+ /**
14
+ * Extrait les mots-clés d'un SKILL.md (headers h1/h2, mots après "When to use", termes techniques)
15
+ */
16
+ extractKeywords(content: string): string[];
17
+ private addWordsToKeywords;
18
+ private isStopWord;
19
+ private getSkillFiles;
20
+ }
21
+ //# sourceMappingURL=scanner.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scanner.d.ts","sourceRoot":"","sources":["../src/scanner.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAEjD,qBAAa,YAAY;IACvB,OAAO,CAAC,MAAM,CAAe;gBAEjB,MAAM,EAAE,YAAY;IAIhC;;OAEG;IACH,IAAI,IAAI,KAAK,EAAE;IA6Bf;;OAEG;IACH,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,KAAK,GAAG,IAAI;IA+CpC;;OAEG;IACH,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,EAAE;IA4B1C,OAAO,CAAC,kBAAkB;IAa1B,OAAO,CAAC,UAAU;IAOlB,OAAO,CAAC,aAAa;CAStB"}
@@ -0,0 +1,169 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.SkillScanner = void 0;
37
+ const fs = __importStar(require("fs"));
38
+ const path = __importStar(require("path"));
39
+ class SkillScanner {
40
+ config;
41
+ constructor(config) {
42
+ this.config = config;
43
+ }
44
+ /**
45
+ * Scanne tous les répertoires et charge les skills
46
+ */
47
+ scan() {
48
+ const skills = [];
49
+ // Scanner les trois répertoires possibles
50
+ const dirs = [
51
+ this.config.globalDir,
52
+ this.config.projectDir,
53
+ this.config.bundledDir
54
+ ];
55
+ for (const dir of dirs) {
56
+ if (!fs.existsSync(dir))
57
+ continue;
58
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
59
+ for (const entry of entries) {
60
+ if (entry.isDirectory()) {
61
+ const skillPath = path.join(dir, entry.name);
62
+ const skill = this.loadSkill(skillPath);
63
+ if (skill) {
64
+ skills.push(skill);
65
+ }
66
+ }
67
+ }
68
+ }
69
+ return skills;
70
+ }
71
+ /**
72
+ * Charge un skill depuis un dossier (lit SKILL.md, extrait keywords des headers/bullet points)
73
+ */
74
+ loadSkill(dir) {
75
+ const skillMdPath = path.join(dir, 'SKILL.md');
76
+ if (!fs.existsSync(skillMdPath)) {
77
+ return null;
78
+ }
79
+ try {
80
+ const content = fs.readFileSync(skillMdPath, 'utf-8');
81
+ const name = path.basename(dir);
82
+ // Extraire la description du premier paragraphe après le titre
83
+ const lines = content.split('\n');
84
+ let description = '';
85
+ let foundHeader = false;
86
+ for (const line of lines) {
87
+ if (line.startsWith('#')) {
88
+ foundHeader = true;
89
+ continue;
90
+ }
91
+ if (foundHeader && line.trim()) {
92
+ description = line.trim();
93
+ break;
94
+ }
95
+ }
96
+ // Extraire les mots-clés
97
+ const keywords = this.extractKeywords(content);
98
+ // Lister les fichiers du dossier
99
+ const files = this.getSkillFiles(dir);
100
+ return {
101
+ name,
102
+ description: description || `Skill: ${name}`,
103
+ content,
104
+ path: dir,
105
+ keywords,
106
+ files
107
+ };
108
+ }
109
+ catch (error) {
110
+ console.warn(`Failed to load skill from ${dir}:`, error);
111
+ return null;
112
+ }
113
+ }
114
+ /**
115
+ * Extrait les mots-clés d'un SKILL.md (headers h1/h2, mots après "When to use", termes techniques)
116
+ */
117
+ extractKeywords(content) {
118
+ const keywords = new Set();
119
+ const lines = content.split('\n');
120
+ for (const line of lines) {
121
+ // Headers H1/H2
122
+ if (line.startsWith('#')) {
123
+ const headerText = line.replace(/^#+\s*/, '').toLowerCase();
124
+ this.addWordsToKeywords(headerText, keywords);
125
+ }
126
+ // Lignes contenant "when to use", "use when", etc.
127
+ if (line.toLowerCase().includes('when to use') ||
128
+ line.toLowerCase().includes('use when') ||
129
+ line.toLowerCase().includes('trigger on')) {
130
+ this.addWordsToKeywords(line, keywords);
131
+ }
132
+ // Listes à puces (bullet points)
133
+ if (line.trim().startsWith('-') || line.trim().startsWith('*')) {
134
+ const bulletText = line.replace(/^\s*[-*]\s*/, '');
135
+ this.addWordsToKeywords(bulletText, keywords);
136
+ }
137
+ }
138
+ return Array.from(keywords).filter(k => k.length > 2); // Filtrer les mots trop courts
139
+ }
140
+ addWordsToKeywords(text, keywords) {
141
+ // Nettoyer et extraire les mots
142
+ const words = text
143
+ .toLowerCase()
144
+ .replace(/[^\w\s]/g, ' ')
145
+ .split(/\s+/)
146
+ .filter(word => word.length > 2 && !this.isStopWord(word));
147
+ for (const word of words) {
148
+ keywords.add(word);
149
+ }
150
+ }
151
+ isStopWord(word) {
152
+ const stopWords = new Set([
153
+ 'the', 'and', 'for', 'are', 'but', 'not', 'you', 'all', 'can', 'had', 'her', 'was', 'one', 'our', 'out', 'day', 'get', 'has', 'him', 'his', 'how', 'its', 'may', 'new', 'now', 'old', 'see', 'two', 'way', 'who', 'boy', 'did', 'she', 'use', 'her', 'how', 'man', 'new', 'now', 'old', 'see', 'two', 'way', 'who', 'oil', 'sit', 'set'
154
+ ]);
155
+ return stopWords.has(word);
156
+ }
157
+ getSkillFiles(dir) {
158
+ try {
159
+ return fs.readdirSync(dir, { withFileTypes: true })
160
+ .map(entry => entry.name)
161
+ .filter(name => !name.startsWith('.'));
162
+ }
163
+ catch {
164
+ return [];
165
+ }
166
+ }
167
+ }
168
+ exports.SkillScanner = SkillScanner;
169
+ //# sourceMappingURL=scanner.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scanner.js","sourceRoot":"","sources":["../src/scanner.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,20 @@
1
+ export interface Skill {
2
+ name: string;
3
+ description: string;
4
+ content: string;
5
+ path: string;
6
+ keywords: string[];
7
+ files: string[];
8
+ }
9
+ export interface SkillMatch {
10
+ skill: Skill;
11
+ matchedKeywords: string[];
12
+ score: number;
13
+ }
14
+ export interface SkillsConfig {
15
+ globalDir: string;
16
+ projectDir: string;
17
+ bundledDir: string;
18
+ autoInject: boolean;
19
+ }
20
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,KAAK;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,KAAK,EAAE,MAAM,EAAE,CAAC;CACjB;AAED,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,KAAK,CAAC;IACb,eAAe,EAAE,MAAM,EAAE,CAAC;IAC1B,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,YAAY;IAC3B,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,OAAO,CAAC;CACrB"}
package/dist/types.js ADDED
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
package/package.json ADDED
@@ -0,0 +1,23 @@
1
+ {
2
+ "name": "sigma-skills",
3
+ "version": "0.1.0",
4
+ "description": "Dynamic skill loading system for Phi Code",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "scripts": {
8
+ "build": "tsc",
9
+ "clean": "rm -rf dist"
10
+ },
11
+ "dependencies": {},
12
+ "devDependencies": {
13
+ "typescript": "^5.4.0"
14
+ },
15
+ "repository": {
16
+ "type": "git",
17
+ "url": "https://github.com/uglyswap/phi-code.git"
18
+ },
19
+ "homepage": "https://github.com/uglyswap/phi-code",
20
+ "keywords": ["skills", "dynamic-loading", "typescript", "phi-code"],
21
+ "license": "MIT",
22
+ "files": ["dist", "src", "README.md"]
23
+ }
package/src/index.ts ADDED
@@ -0,0 +1,3 @@
1
+ export { SkillScanner } from './scanner.js';
2
+ export { SkillLoader } from './loader.js';
3
+ export * from './types.js';
package/src/loader.ts ADDED
@@ -0,0 +1,166 @@
1
+ import * as fs from 'fs';
2
+ import * as path from 'path';
3
+ import { Skill, SkillMatch, SkillsConfig } from './types.js';
4
+ import { SkillScanner } from './scanner.js';
5
+
6
+ export class SkillLoader {
7
+ private scanner: SkillScanner;
8
+ private skills: Skill[] = [];
9
+ private lastScanTime: number = 0;
10
+ private readonly SCAN_CACHE_MS = 60000; // Cache les scans pendant 1 minute
11
+
12
+ constructor(scanner: SkillScanner) {
13
+ this.scanner = scanner;
14
+ this.refreshSkills();
15
+ }
16
+
17
+ /**
18
+ * Cherche les skills pertinents pour un prompt donné
19
+ */
20
+ findRelevantSkills(prompt: string): SkillMatch[] {
21
+ this.refreshSkillsIfNeeded();
22
+
23
+ const promptWords = this.extractWords(prompt.toLowerCase());
24
+ const matches: SkillMatch[] = [];
25
+
26
+ for (const skill of this.skills) {
27
+ const matchedKeywords: string[] = [];
28
+ let score = 0;
29
+
30
+ // Vérifier les correspondances avec les keywords du skill
31
+ for (const keyword of skill.keywords) {
32
+ if (promptWords.includes(keyword)) {
33
+ matchedKeywords.push(keyword);
34
+ score += 1;
35
+ }
36
+ }
37
+
38
+ // Bonus pour les correspondances dans le nom ou la description
39
+ const skillName = skill.name.toLowerCase();
40
+ const skillDesc = skill.description.toLowerCase();
41
+
42
+ for (const word of promptWords) {
43
+ if (skillName.includes(word)) {
44
+ score += 2; // Bonus pour le nom
45
+ }
46
+ if (skillDesc.includes(word)) {
47
+ score += 1; // Bonus pour la description
48
+ }
49
+ }
50
+
51
+ // Ajouter si pertinent
52
+ if (score > 0) {
53
+ matches.push({
54
+ skill,
55
+ matchedKeywords,
56
+ score
57
+ });
58
+ }
59
+ }
60
+
61
+ // Trier par score décroissant
62
+ return matches.sort((a, b) => b.score - a.score);
63
+ }
64
+
65
+ /**
66
+ * Retourne le contenu d'un skill par nom
67
+ */
68
+ getSkillContext(skillName: string): string | null {
69
+ this.refreshSkillsIfNeeded();
70
+
71
+ const skill = this.skills.find(s => s.name === skillName);
72
+ return skill ? skill.content : null;
73
+ }
74
+
75
+ /**
76
+ * Liste tous les skills installés
77
+ */
78
+ listSkills(): Skill[] {
79
+ this.refreshSkillsIfNeeded();
80
+ return [...this.skills]; // Retourner une copie
81
+ }
82
+
83
+ /**
84
+ * Copie un skill d'un chemin source vers le répertoire skills
85
+ */
86
+ installSkill(source: string, targetDir?: string): boolean {
87
+ try {
88
+ // Déterminer le répertoire cible (par défaut: globalDir)
89
+ const targetBase = targetDir || this.scanner['config'].globalDir;
90
+ const skillName = path.basename(source);
91
+ const targetPath = path.join(targetBase, skillName);
92
+
93
+ // Vérifier que le source existe
94
+ if (!fs.existsSync(source)) {
95
+ console.error(`Source skill directory not found: ${source}`);
96
+ return false;
97
+ }
98
+
99
+ // Vérifier qu'il y a un SKILL.md dans le source
100
+ const sourceSkillMd = path.join(source, 'SKILL.md');
101
+ if (!fs.existsSync(sourceSkillMd)) {
102
+ console.error(`No SKILL.md found in source: ${source}`);
103
+ return false;
104
+ }
105
+
106
+ // Créer le répertoire cible si nécessaire
107
+ fs.mkdirSync(targetBase, { recursive: true });
108
+
109
+ // Copier récursivement
110
+ this.copyDirectory(source, targetPath);
111
+
112
+ // Rafraîchir la liste des skills
113
+ this.forceRefresh();
114
+
115
+ console.log(`Skill '${skillName}' installed successfully to ${targetPath}`);
116
+ return true;
117
+ } catch (error) {
118
+ console.error(`Failed to install skill from ${source}:`, error);
119
+ return false;
120
+ }
121
+ }
122
+
123
+ private refreshSkillsIfNeeded() {
124
+ const now = Date.now();
125
+ if (now - this.lastScanTime > this.SCAN_CACHE_MS) {
126
+ this.refreshSkills();
127
+ }
128
+ }
129
+
130
+ private refreshSkills() {
131
+ this.skills = this.scanner.scan();
132
+ this.lastScanTime = Date.now();
133
+ }
134
+
135
+ private forceRefresh() {
136
+ this.lastScanTime = 0;
137
+ this.refreshSkills();
138
+ }
139
+
140
+ private extractWords(text: string): string[] {
141
+ return text
142
+ .replace(/[^\w\s]/g, ' ')
143
+ .split(/\s+/)
144
+ .filter(word => word.length > 2)
145
+ .map(word => word.toLowerCase());
146
+ }
147
+
148
+ private copyDirectory(source: string, target: string) {
149
+ if (!fs.existsSync(target)) {
150
+ fs.mkdirSync(target, { recursive: true });
151
+ }
152
+
153
+ const entries = fs.readdirSync(source, { withFileTypes: true });
154
+
155
+ for (const entry of entries) {
156
+ const sourcePath = path.join(source, entry.name);
157
+ const targetPath = path.join(target, entry.name);
158
+
159
+ if (entry.isDirectory()) {
160
+ this.copyDirectory(sourcePath, targetPath);
161
+ } else {
162
+ fs.copyFileSync(sourcePath, targetPath);
163
+ }
164
+ }
165
+ }
166
+ }
package/src/scanner.ts ADDED
@@ -0,0 +1,154 @@
1
+ import * as fs from 'fs';
2
+ import * as path from 'path';
3
+ import { Skill, SkillsConfig } from './types.js';
4
+
5
+ export class SkillScanner {
6
+ private config: SkillsConfig;
7
+
8
+ constructor(config: SkillsConfig) {
9
+ this.config = config;
10
+ }
11
+
12
+ /**
13
+ * Scanne tous les répertoires et charge les skills
14
+ */
15
+ scan(): Skill[] {
16
+ const skills: Skill[] = [];
17
+
18
+ // Scanner les trois répertoires possibles
19
+ const dirs = [
20
+ this.config.globalDir,
21
+ this.config.projectDir,
22
+ this.config.bundledDir
23
+ ];
24
+
25
+ for (const dir of dirs) {
26
+ if (!fs.existsSync(dir)) continue;
27
+
28
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
29
+
30
+ for (const entry of entries) {
31
+ if (entry.isDirectory()) {
32
+ const skillPath = path.join(dir, entry.name);
33
+ const skill = this.loadSkill(skillPath);
34
+ if (skill) {
35
+ skills.push(skill);
36
+ }
37
+ }
38
+ }
39
+ }
40
+
41
+ return skills;
42
+ }
43
+
44
+ /**
45
+ * Charge un skill depuis un dossier (lit SKILL.md, extrait keywords des headers/bullet points)
46
+ */
47
+ loadSkill(dir: string): Skill | null {
48
+ const skillMdPath = path.join(dir, 'SKILL.md');
49
+
50
+ if (!fs.existsSync(skillMdPath)) {
51
+ return null;
52
+ }
53
+
54
+ try {
55
+ const content = fs.readFileSync(skillMdPath, 'utf-8');
56
+ const name = path.basename(dir);
57
+
58
+ // Extraire la description du premier paragraphe après le titre
59
+ const lines = content.split('\n');
60
+ let description = '';
61
+ let foundHeader = false;
62
+
63
+ for (const line of lines) {
64
+ if (line.startsWith('#')) {
65
+ foundHeader = true;
66
+ continue;
67
+ }
68
+ if (foundHeader && line.trim()) {
69
+ description = line.trim();
70
+ break;
71
+ }
72
+ }
73
+
74
+ // Extraire les mots-clés
75
+ const keywords = this.extractKeywords(content);
76
+
77
+ // Lister les fichiers du dossier
78
+ const files = this.getSkillFiles(dir);
79
+
80
+ return {
81
+ name,
82
+ description: description || `Skill: ${name}`,
83
+ content,
84
+ path: dir,
85
+ keywords,
86
+ files
87
+ };
88
+ } catch (error) {
89
+ console.warn(`Failed to load skill from ${dir}:`, error);
90
+ return null;
91
+ }
92
+ }
93
+
94
+ /**
95
+ * Extrait les mots-clés d'un SKILL.md (headers h1/h2, mots après "When to use", termes techniques)
96
+ */
97
+ extractKeywords(content: string): string[] {
98
+ const keywords = new Set<string>();
99
+ const lines = content.split('\n');
100
+
101
+ for (const line of lines) {
102
+ // Headers H1/H2
103
+ if (line.startsWith('#')) {
104
+ const headerText = line.replace(/^#+\s*/, '').toLowerCase();
105
+ this.addWordsToKeywords(headerText, keywords);
106
+ }
107
+
108
+ // Lignes contenant "when to use", "use when", etc.
109
+ if (line.toLowerCase().includes('when to use') ||
110
+ line.toLowerCase().includes('use when') ||
111
+ line.toLowerCase().includes('trigger on')) {
112
+ this.addWordsToKeywords(line, keywords);
113
+ }
114
+
115
+ // Listes à puces (bullet points)
116
+ if (line.trim().startsWith('-') || line.trim().startsWith('*')) {
117
+ const bulletText = line.replace(/^\s*[-*]\s*/, '');
118
+ this.addWordsToKeywords(bulletText, keywords);
119
+ }
120
+ }
121
+
122
+ return Array.from(keywords).filter(k => k.length > 2); // Filtrer les mots trop courts
123
+ }
124
+
125
+ private addWordsToKeywords(text: string, keywords: Set<string>) {
126
+ // Nettoyer et extraire les mots
127
+ const words = text
128
+ .toLowerCase()
129
+ .replace(/[^\w\s]/g, ' ')
130
+ .split(/\s+/)
131
+ .filter(word => word.length > 2 && !this.isStopWord(word));
132
+
133
+ for (const word of words) {
134
+ keywords.add(word);
135
+ }
136
+ }
137
+
138
+ private isStopWord(word: string): boolean {
139
+ const stopWords = new Set([
140
+ 'the', 'and', 'for', 'are', 'but', 'not', 'you', 'all', 'can', 'had', 'her', 'was', 'one', 'our', 'out', 'day', 'get', 'has', 'him', 'his', 'how', 'its', 'may', 'new', 'now', 'old', 'see', 'two', 'way', 'who', 'boy', 'did', 'she', 'use', 'her', 'how', 'man', 'new', 'now', 'old', 'see', 'two', 'way', 'who', 'oil', 'sit', 'set'
141
+ ]);
142
+ return stopWords.has(word);
143
+ }
144
+
145
+ private getSkillFiles(dir: string): string[] {
146
+ try {
147
+ return fs.readdirSync(dir, { withFileTypes: true })
148
+ .map(entry => entry.name)
149
+ .filter(name => !name.startsWith('.'));
150
+ } catch {
151
+ return [];
152
+ }
153
+ }
154
+ }
package/src/types.ts ADDED
@@ -0,0 +1,21 @@
1
+ export interface Skill {
2
+ name: string;
3
+ description: string;
4
+ content: string; // Contenu du SKILL.md
5
+ path: string; // Chemin absolu
6
+ keywords: string[]; // Mots-clés extraits du SKILL.md
7
+ files: string[]; // Fichiers dans le dossier du skill
8
+ }
9
+
10
+ export interface SkillMatch {
11
+ skill: Skill;
12
+ matchedKeywords: string[];
13
+ score: number;
14
+ }
15
+
16
+ export interface SkillsConfig {
17
+ globalDir: string; // ~/.phi/agent/skills/
18
+ projectDir: string; // .phi/skills/
19
+ bundledDir: string; // Chemin vers skills/ dans le repo
20
+ autoInject: boolean; // Injecter auto le contexte des skills matchés
21
+ }