@omerrgocmen/crewctl 1.3.0 → 1.3.1

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 (92) hide show
  1. package/README.md +193 -193
  2. package/orchestrator/README.md +473 -473
  3. package/orchestrator/config.default.json +90 -79
  4. package/orchestrator/roles/executor.md +57 -57
  5. package/orchestrator/roles/operator.md +77 -77
  6. package/orchestrator/roles/planner.md +58 -58
  7. package/orchestrator/roles/reviewer.md +63 -63
  8. package/orchestrator/skills/acceptance-criteria.md +18 -18
  9. package/orchestrator/skills/accessibility-audit.md +16 -16
  10. package/orchestrator/skills/accessible-forms.md +16 -16
  11. package/orchestrator/skills/api-design.md +27 -27
  12. package/orchestrator/skills/api-documentation.md +16 -16
  13. package/orchestrator/skills/architecture-decision-record.md +18 -18
  14. package/orchestrator/skills/authentication-design.md +16 -16
  15. package/orchestrator/skills/authorization-review.md +16 -16
  16. package/orchestrator/skills/backward-compatibility.md +17 -17
  17. package/orchestrator/skills/changelog-writing.md +16 -16
  18. package/orchestrator/skills/ci-pipeline-design.md +16 -16
  19. package/orchestrator/skills/cli-design.md +16 -16
  20. package/orchestrator/skills/code-review.md +27 -27
  21. package/orchestrator/skills/configuration-management.md +16 -16
  22. package/orchestrator/skills/container-review.md +16 -16
  23. package/orchestrator/skills/contract-testing.md +16 -16
  24. package/orchestrator/skills/dashboard-design.md +16 -16
  25. package/orchestrator/skills/database-migration.md +16 -16
  26. package/orchestrator/skills/database-schema-design.md +16 -16
  27. package/orchestrator/skills/debugging.md +26 -26
  28. package/orchestrator/skills/dependency-review.md +16 -16
  29. package/orchestrator/skills/design-review.md +29 -29
  30. package/orchestrator/skills/design-system.md +16 -16
  31. package/orchestrator/skills/docs-api-reference.md +16 -16
  32. package/orchestrator/skills/docs-troubleshooting.md +16 -16
  33. package/orchestrator/skills/docs-tutorial.md +16 -16
  34. package/orchestrator/skills/docs-writing.md +25 -25
  35. package/orchestrator/skills/empty-error-loading-states.md +16 -16
  36. package/orchestrator/skills/end-to-end-testing.md +16 -16
  37. package/orchestrator/skills/error-handling.md +16 -16
  38. package/orchestrator/skills/frontend-design.md +28 -28
  39. package/orchestrator/skills/git-commit-writing.md +16 -16
  40. package/orchestrator/skills/graphql-design.md +16 -16
  41. package/orchestrator/skills/incident-runbook.md +18 -18
  42. package/orchestrator/skills/input-validation.md +16 -16
  43. package/orchestrator/skills/integration-testing.md +16 -16
  44. package/orchestrator/skills/interaction-design.md +16 -16
  45. package/orchestrator/skills/landing-page-design.md +16 -16
  46. package/orchestrator/skills/observability-design.md +16 -16
  47. package/orchestrator/skills/openapi-contract.md +16 -16
  48. package/orchestrator/skills/performance-profiling.md +16 -16
  49. package/orchestrator/skills/privacy-review.md +16 -16
  50. package/orchestrator/skills/property-based-testing.md +16 -16
  51. package/orchestrator/skills/pull-request-writing.md +16 -16
  52. package/orchestrator/skills/refactoring.md +16 -16
  53. package/orchestrator/skills/release-readiness.md +16 -16
  54. package/orchestrator/skills/responsive-design.md +16 -16
  55. package/orchestrator/skills/secrets-management.md +16 -16
  56. package/orchestrator/skills/secure-file-upload.md +16 -16
  57. package/orchestrator/skills/security-review.md +26 -26
  58. package/orchestrator/skills/semantic-versioning.md +17 -17
  59. package/orchestrator/skills/seo-on-page.md +16 -16
  60. package/orchestrator/skills/seo-structured-data.md +16 -16
  61. package/orchestrator/skills/seo-technical-audit.md +16 -16
  62. package/orchestrator/skills/sql-query-review.md +16 -16
  63. package/orchestrator/skills/supply-chain-security.md +16 -16
  64. package/orchestrator/skills/test-strategy.md +16 -16
  65. package/orchestrator/skills/threat-modeling.md +16 -16
  66. package/orchestrator/skills/unit-testing.md +16 -16
  67. package/orchestrator/skills/write-tests.md +28 -28
  68. package/orchestrator/src/checkpoints.js +0 -4
  69. package/orchestrator/src/cli-registry.js +848 -740
  70. package/orchestrator/src/cli.js +140 -140
  71. package/orchestrator/src/doctor.js +64 -64
  72. package/orchestrator/src/engine.js +1914 -1735
  73. package/orchestrator/src/schedule.js +124 -126
  74. package/orchestrator/src/server.js +817 -818
  75. package/orchestrator/src/skill-registry.js +272 -272
  76. package/orchestrator/src/store.js +478 -470
  77. package/orchestrator/web/OrbitControls.js +1417 -1417
  78. package/orchestrator/web/board.html +146 -146
  79. package/orchestrator/web/code.html +213 -213
  80. package/orchestrator/web/flow.html +741 -741
  81. package/orchestrator/web/index.html +622 -602
  82. package/orchestrator/web/jsm/postprocessing/EffectComposer.js +231 -231
  83. package/orchestrator/web/jsm/postprocessing/MaskPass.js +104 -104
  84. package/orchestrator/web/jsm/postprocessing/Pass.js +95 -95
  85. package/orchestrator/web/jsm/postprocessing/RenderPass.js +99 -99
  86. package/orchestrator/web/jsm/postprocessing/ShaderPass.js +77 -77
  87. package/orchestrator/web/jsm/postprocessing/UnrealBloomPass.js +415 -415
  88. package/orchestrator/web/jsm/shaders/CopyShader.js +45 -45
  89. package/orchestrator/web/jsm/shaders/LuminosityHighPassShader.js +66 -66
  90. package/orchestrator/web/site.webmanifest +12 -12
  91. package/orchestrator/web/three.module.min.js +6 -6
  92. package/package.json +51 -51
@@ -1,272 +1,272 @@
1
- // skill-registry.js — yeniden kullanilabilir "beceri" (skill) dosyalarini yukler, ayristirir
2
- // ve operatore/uzmana verilecek bicime cevirir. Beceriler roles/*.md gibi Markdown'dir; farki
3
- // frontmatter tasimalari ve KULLANICI TARAFINDAN ETKINLESTIRILENLERIN operatore katalog olarak
4
- // sunulmasidir. Sifir bagimlilik: frontmatter'i kendi minimal ayristiricimizla okuruz.
5
- const fs = require("fs");
6
- const path = require("path");
7
- const store = require("./store");
8
-
9
- const SKILLS_DIR = path.join(store.ROOT, "skills");
10
-
11
- function ensureDir() {
12
- try { fs.mkdirSync(SKILLS_DIR, { recursive: true }); } catch {}
13
- }
14
-
15
- function toList(value) {
16
- if (Array.isArray(value)) return value.map(String).map((x) => x.trim()).filter(Boolean);
17
- if (typeof value === "string" && value.trim()) return value.split(",").map((x) => x.trim()).filter(Boolean);
18
- return [];
19
- }
20
-
21
- // `--- ... ---` bloğundan basit `key: value` ciftlerini okur. Deger `[a, b]` veya virgullu
22
- // liste ise diziye cevrilir. YAML'in tamamini desteklemeyiz; skill dosyalari icin bu yeterli.
23
- function parseFrontmatter(text) {
24
- const raw = String(text || "");
25
- const match = raw.match(/^?---[ \t]*\r?\n([\s\S]*?)\r?\n---[ \t]*\r?\n?([\s\S]*)$/);
26
- if (!match) return { meta: {}, body: raw.trim() };
27
- const meta = {};
28
- for (const line of match[1].split(/\r?\n/)) {
29
- const kv = line.match(/^([A-Za-z0-9_-]+)[ \t]*:[ \t]*(.*)$/);
30
- if (!kv) continue;
31
- const key = kv[1].trim();
32
- let value = kv[2].trim();
33
- if (/^\[.*\]$/.test(value)) {
34
- value = value.slice(1, -1).split(",").map((x) => x.trim().replace(/^["']|["']$/g, "")).filter(Boolean);
35
- } else {
36
- value = value.replace(/^["']|["']$/g, "");
37
- }
38
- meta[key] = value;
39
- }
40
- return { meta, body: match[2].trim() };
41
- }
42
-
43
- function fileNameFor(nameOrFile) {
44
- const base = path.basename(String(nameOrFile || ""));
45
- return base.toLowerCase().endsWith(".md") ? base : `${base}.md`;
46
- }
47
-
48
- function slugFor(file) {
49
- return path.basename(String(file)).replace(/\.md$/i, "");
50
- }
51
-
52
- function listSkillFiles() {
53
- ensureDir();
54
- try { return fs.readdirSync(SKILLS_DIR).filter((f) => f.toLowerCase().endsWith(".md")).sort(); }
55
- catch { return []; }
56
- }
57
-
58
- function loadSkill(nameOrFile) {
59
- const file = fileNameFor(nameOrFile);
60
- const p = path.join(SKILLS_DIR, file);
61
- if (!fs.existsSync(p)) {
62
- // Etkin liste dosya adi yerine frontmatter `name` slug'ini tutuyor olabilir.
63
- const byName = listSkillFiles().map((f) => ({ f, s: parseFrontmatter(readRaw(f)) }))
64
- .find(({ f, s }) => String(s.meta.name || slugFor(f)) === slugFor(nameOrFile));
65
- if (!byName) return null;
66
- return buildSkill(byName.f, byName.s);
67
- }
68
- return buildSkill(file, parseFrontmatter(fs.readFileSync(p, "utf8")));
69
- }
70
-
71
- function buildSkill(file, parsed) {
72
- const { meta, body } = parsed;
73
- return {
74
- name: String(meta.name || slugFor(file)),
75
- file,
76
- description: String(meta.description || ""),
77
- category: String(meta.category || "general"),
78
- appliesTo: toList(meta.appliesTo),
79
- match: toList(meta.match),
80
- body,
81
- };
82
- }
83
-
84
- function readRaw(nameOrFile) {
85
- const p = path.join(SKILLS_DIR, fileNameFor(nameOrFile));
86
- return fs.existsSync(p) ? fs.readFileSync(p, "utf8") : "";
87
- }
88
-
89
- function allSkills() {
90
- return listSkillFiles().map((f) => buildSkill(f, parseFrontmatter(readRaw(f))));
91
- }
92
-
93
- function enabledNames(cfg) {
94
- const list = cfg && cfg.skills && Array.isArray(cfg.skills.enabled) ? cfg.skills.enabled.map(String) : [];
95
- return new Set(list);
96
- }
97
-
98
- function enabledSkills(cfg, installed = null) {
99
- const enabled = enabledNames(cfg);
100
- if (!enabled.size) return [];
101
- const wildcard = enabled.has("*");
102
- return (installed || allSkills()).filter((skill) => wildcard || enabled.has(skill.name));
103
- }
104
-
105
- function normalizeText(value) {
106
- return String(value || "").toLocaleLowerCase("tr-TR").normalize("NFD")
107
- .replace(/[\u0300-\u036f]/g, "").replace(/ı/g, "i").replace(/[^a-z0-9]+/g, " ").trim();
108
- }
109
-
110
- function scoreSkill(skill, context, kind = "") {
111
- const haystack = ` ${normalizeText(context)} `;
112
- if (!haystack.trim()) return 0;
113
- const stopwords = new Set(["icin", "ile", "veya", "olan", "olarak", "yap", "yaz", "ekle", "kur", "kullan", "tasarla", "incele", "duzelt", "olustur", "guncelle", "planla", "create", "build", "write", "add", "use", "design", "review", "fix", "update", "implement"]);
114
- const tokens = new Set(haystack.trim().split(/\s+/).filter((token) => token.length > 1 && !stopwords.has(token)));
115
- let lexical = 0;
116
- for (const rawPhrase of skill.match) {
117
- const phrase = normalizeText(rawPhrase);
118
- if (!phrase) continue;
119
- if (!phrase.includes(" ") && stopwords.has(phrase)) continue;
120
- if (haystack.includes(` ${phrase} `)) lexical += 18 + Math.min(8, phrase.split(" ").length * 2);
121
- else if (phrase.split(" ").every((token) => tokens.has(token))) lexical += 10;
122
- }
123
- const nameParts = normalizeText(skill.name).split("-").filter((token) => token.length > 2);
124
- lexical += nameParts.filter((token) => tokens.has(token)).length * 8;
125
- const descriptionTokens = new Set(normalizeText(skill.description).split(" ").filter((token) => token.length > 3));
126
- const descriptionHits = [...tokens].filter((token) => descriptionTokens.has(token));
127
- if (descriptionHits.length > 1) lexical += descriptionHits.length * 2;
128
- if (!lexical) return 0;
129
- const kindFit = !kind || !skill.appliesTo.length || skill.appliesTo.includes(kind) ? 6 : -8;
130
- return Math.max(1, lexical + kindFit);
131
- }
132
-
133
- function ranked(cfg, context = "", kind = "", installed = null) {
134
- return enabledSkills(cfg, installed).map((skill) => ({ skill, score: scoreSkill(skill, context, kind) }))
135
- .filter(({ score }) => !context || score > 0)
136
- .sort((a, b) => b.score - a.score || a.skill.name.localeCompare(b.skill.name, "en"));
137
- }
138
-
139
- function compact(skill) {
140
- const description = skill.description.length > 180 ? `${skill.description.slice(0, 177)}...` : skill.description;
141
- return { name: skill.name, description, appliesTo: skill.appliesTo };
142
- }
143
-
144
- // Operatore tum govdeleri veya tum aciklamalari yuklemek yerine, gorev metnine gore en ilgili
145
- // etkin becerileri verir. Limit ve karakter butcesi katalog buyuse bile prompt maliyetini sabitler.
146
- function catalogFromRanked(cfg, rankedSkills) {
147
- const limit = Math.max(1, Number(cfg?.skills?.catalogLimit) || 12);
148
- const budget = Math.max(200, Number(cfg?.skills?.charBudget) || 2400);
149
- const out = [];
150
- for (const { skill } of rankedSkills) {
151
- if (out.length >= limit) break;
152
- let item = compact(skill);
153
- let candidate = [...out, item];
154
- if (JSON.stringify(candidate).length > budget) {
155
- if (out.length) break;
156
- const withoutDescription = { ...item, description: "" };
157
- const fixedCost = JSON.stringify([withoutDescription]).length;
158
- if (fixedCost > budget) break;
159
- const room = Math.max(0, budget - fixedCost - 3);
160
- item = { ...item, description: room ? `${item.description.slice(0, room)}...` : "" };
161
- candidate = [item];
162
- }
163
- out.push(item);
164
- }
165
- return out;
166
- }
167
-
168
- function discover(cfg, context = "", kind = "") {
169
- const installed = allSkills();
170
- const enabled = enabledSkills(cfg, installed);
171
- const matches = ranked(cfg, context, kind, installed);
172
- return {
173
- catalog: catalogFromRanked(cfg, matches),
174
- stats: { installed: installed.length, enabled: enabled.length, matched: matches.length },
175
- };
176
- }
177
-
178
- function catalog(cfg, context = "", kind = "") {
179
- return discover(cfg, context, kind).catalog;
180
- }
181
-
182
- function catalogStats(cfg, context = "", kind = "") {
183
- return discover(cfg, context, kind).stats;
184
- }
185
-
186
- function suggest(cfg, context, kind = "") {
187
- const limit = Math.max(1, Number(cfg?.skills?.maxSkillsPerAssignment) || 3);
188
- return ranked(cfg, context, kind).slice(0, limit).map(({ skill }) => skill.name);
189
- }
190
-
191
- // Bir delegasyona istenen beceri adlarini cozer; yalnizca hem var olan hem de kullanicinin
192
- // etkinlestirdigi beceriler donuir. Boylece operator, katalog disinda bir beceri iliştiremez.
193
- function resolveForAssignment(names, cfg) {
194
- const enabled = enabledNames(cfg);
195
- const wildcard = enabled.has("*");
196
- const limit = Math.max(1, Number(cfg?.skills?.maxSkillsPerAssignment) || 3);
197
- const out = [];
198
- const seen = new Set();
199
- for (const name of toList(names)) {
200
- if (out.length >= limit) break;
201
- if (seen.has(name) || (!wildcard && !enabled.has(name))) continue;
202
- const skill = loadSkill(name);
203
- if (skill) { out.push(skill); seen.add(name); }
204
- }
205
- return out;
206
- }
207
-
208
- // Progressive disclosure: uzmana becerinin TAM govdesini yigmadan, ad + kisa ozet + rehber dosya
209
- // yolunu verir. Uzman gercekten ihtiyac duyarsa dosyayi kendisi okur. Boylece prompt hafif kalir.
210
- function toPromptRefs(skills, charBudget = 1200) {
211
- if (!Array.isArray(skills) || !skills.length) return "";
212
- const lines = [];
213
- for (const skill of skills) {
214
- const location = path.join(SKILLS_DIR, skill.file);
215
- let line = `- ${skill.name}: ${skill.description || "(aciklama yok)"}\n Rehber: ${location}`;
216
- if (charBudget && [...lines, line].join("\n").length > charBudget) {
217
- if (lines.length) break;
218
- line = `- ${skill.name}\n Rehber: ${location}`;
219
- if (line.length > charBudget) break;
220
- }
221
- lines.push(line);
222
- }
223
- return lines.join("\n");
224
- }
225
-
226
- function validateSkill(nameOrFile, content) {
227
- const { meta, body } = parseFrontmatter(content);
228
- const errors = [];
229
- const name = String(meta.name || "");
230
- const expected = slugFor(fileNameFor(nameOrFile));
231
- if (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(name) || name.length > 64) errors.push("name kucuk harfli kebab-case ve en fazla 64 karakter olmali");
232
- if (name && name !== expected) errors.push("frontmatter name dosya adiyla ayni olmali");
233
- const description = String(meta.description || "").trim();
234
- if (!description || description.length > 1024) errors.push("description 1-1024 karakter olmali");
235
- if (!body.trim()) errors.push("beceri govdesi bos olamaz");
236
- const invalidKinds = toList(meta.appliesTo).filter((kind) => !["implement", "review", "plan", "research"].includes(kind));
237
- if (invalidKinds.length) errors.push(`gecersiz appliesTo: ${invalidKinds.join(", ")}`);
238
- return { ok: !errors.length, errors };
239
- }
240
-
241
- function writeSkill(nameOrFile, content) {
242
- ensureDir();
243
- const file = fileNameFor(nameOrFile);
244
- fs.writeFileSync(path.join(SKILLS_DIR, file), String(content == null ? "" : content));
245
- return file;
246
- }
247
-
248
- function deleteSkill(nameOrFile) {
249
- const p = path.join(SKILLS_DIR, fileNameFor(nameOrFile));
250
- if (fs.existsSync(p)) fs.rmSync(p);
251
- }
252
-
253
- module.exports = {
254
- SKILLS_DIR,
255
- parseFrontmatter,
256
- listSkillFiles,
257
- loadSkill,
258
- allSkills,
259
- enabledNames,
260
- enabledSkills,
261
- scoreSkill,
262
- discover,
263
- catalog,
264
- catalogStats,
265
- suggest,
266
- resolveForAssignment,
267
- toPromptRefs,
268
- validateSkill,
269
- readRaw,
270
- writeSkill,
271
- deleteSkill,
272
- };
1
+ // skill-registry.js — yeniden kullanilabilir "beceri" (skill) dosyalarini yukler, ayristirir
2
+ // ve operatore/uzmana verilecek bicime cevirir. Beceriler roles/*.md gibi Markdown'dir; farki
3
+ // frontmatter tasimalari ve KULLANICI TARAFINDAN ETKINLESTIRILENLERIN operatore katalog olarak
4
+ // sunulmasidir. Sifir bagimlilik: frontmatter'i kendi minimal ayristiricimizla okuruz.
5
+ const fs = require("fs");
6
+ const path = require("path");
7
+ const store = require("./store");
8
+
9
+ const SKILLS_DIR = path.join(store.ROOT, "skills");
10
+
11
+ function ensureDir() {
12
+ try { fs.mkdirSync(SKILLS_DIR, { recursive: true }); } catch {}
13
+ }
14
+
15
+ function toList(value) {
16
+ if (Array.isArray(value)) return value.map(String).map((x) => x.trim()).filter(Boolean);
17
+ if (typeof value === "string" && value.trim()) return value.split(",").map((x) => x.trim()).filter(Boolean);
18
+ return [];
19
+ }
20
+
21
+ // `--- ... ---` bloğundan basit `key: value` ciftlerini okur. Deger `[a, b]` veya virgullu
22
+ // liste ise diziye cevrilir. YAML'in tamamini desteklemeyiz; skill dosyalari icin bu yeterli.
23
+ function parseFrontmatter(text) {
24
+ const raw = String(text || "");
25
+ const match = raw.match(/^?---[ \t]*\r?\n([\s\S]*?)\r?\n---[ \t]*\r?\n?([\s\S]*)$/);
26
+ if (!match) return { meta: {}, body: raw.trim() };
27
+ const meta = {};
28
+ for (const line of match[1].split(/\r?\n/)) {
29
+ const kv = line.match(/^([A-Za-z0-9_-]+)[ \t]*:[ \t]*(.*)$/);
30
+ if (!kv) continue;
31
+ const key = kv[1].trim();
32
+ let value = kv[2].trim();
33
+ if (/^\[.*\]$/.test(value)) {
34
+ value = value.slice(1, -1).split(",").map((x) => x.trim().replace(/^["']|["']$/g, "")).filter(Boolean);
35
+ } else {
36
+ value = value.replace(/^["']|["']$/g, "");
37
+ }
38
+ meta[key] = value;
39
+ }
40
+ return { meta, body: match[2].trim() };
41
+ }
42
+
43
+ function fileNameFor(nameOrFile) {
44
+ const base = path.basename(String(nameOrFile || ""));
45
+ return base.toLowerCase().endsWith(".md") ? base : `${base}.md`;
46
+ }
47
+
48
+ function slugFor(file) {
49
+ return path.basename(String(file)).replace(/\.md$/i, "");
50
+ }
51
+
52
+ function listSkillFiles() {
53
+ ensureDir();
54
+ try { return fs.readdirSync(SKILLS_DIR).filter((f) => f.toLowerCase().endsWith(".md")).sort(); }
55
+ catch { return []; }
56
+ }
57
+
58
+ function loadSkill(nameOrFile) {
59
+ const file = fileNameFor(nameOrFile);
60
+ const p = path.join(SKILLS_DIR, file);
61
+ if (!fs.existsSync(p)) {
62
+ // Etkin liste dosya adi yerine frontmatter `name` slug'ini tutuyor olabilir.
63
+ const byName = listSkillFiles().map((f) => ({ f, s: parseFrontmatter(readRaw(f)) }))
64
+ .find(({ f, s }) => String(s.meta.name || slugFor(f)) === slugFor(nameOrFile));
65
+ if (!byName) return null;
66
+ return buildSkill(byName.f, byName.s);
67
+ }
68
+ return buildSkill(file, parseFrontmatter(fs.readFileSync(p, "utf8")));
69
+ }
70
+
71
+ function buildSkill(file, parsed) {
72
+ const { meta, body } = parsed;
73
+ return {
74
+ name: String(meta.name || slugFor(file)),
75
+ file,
76
+ description: String(meta.description || ""),
77
+ category: String(meta.category || "general"),
78
+ appliesTo: toList(meta.appliesTo),
79
+ match: toList(meta.match),
80
+ body,
81
+ };
82
+ }
83
+
84
+ function readRaw(nameOrFile) {
85
+ const p = path.join(SKILLS_DIR, fileNameFor(nameOrFile));
86
+ return fs.existsSync(p) ? fs.readFileSync(p, "utf8") : "";
87
+ }
88
+
89
+ function allSkills() {
90
+ return listSkillFiles().map((f) => buildSkill(f, parseFrontmatter(readRaw(f))));
91
+ }
92
+
93
+ function enabledNames(cfg) {
94
+ const list = cfg && cfg.skills && Array.isArray(cfg.skills.enabled) ? cfg.skills.enabled.map(String) : [];
95
+ return new Set(list);
96
+ }
97
+
98
+ function enabledSkills(cfg, installed = null) {
99
+ const enabled = enabledNames(cfg);
100
+ if (!enabled.size) return [];
101
+ const wildcard = enabled.has("*");
102
+ return (installed || allSkills()).filter((skill) => wildcard || enabled.has(skill.name));
103
+ }
104
+
105
+ function normalizeText(value) {
106
+ return String(value || "").toLocaleLowerCase("tr-TR").normalize("NFD")
107
+ .replace(/[\u0300-\u036f]/g, "").replace(/ı/g, "i").replace(/[^a-z0-9]+/g, " ").trim();
108
+ }
109
+
110
+ function scoreSkill(skill, context, kind = "") {
111
+ const haystack = ` ${normalizeText(context)} `;
112
+ if (!haystack.trim()) return 0;
113
+ const stopwords = new Set(["icin", "ile", "veya", "olan", "olarak", "yap", "yaz", "ekle", "kur", "kullan", "tasarla", "incele", "duzelt", "olustur", "guncelle", "planla", "create", "build", "write", "add", "use", "design", "review", "fix", "update", "implement"]);
114
+ const tokens = new Set(haystack.trim().split(/\s+/).filter((token) => token.length > 1 && !stopwords.has(token)));
115
+ let lexical = 0;
116
+ for (const rawPhrase of skill.match) {
117
+ const phrase = normalizeText(rawPhrase);
118
+ if (!phrase) continue;
119
+ if (!phrase.includes(" ") && stopwords.has(phrase)) continue;
120
+ if (haystack.includes(` ${phrase} `)) lexical += 18 + Math.min(8, phrase.split(" ").length * 2);
121
+ else if (phrase.split(" ").every((token) => tokens.has(token))) lexical += 10;
122
+ }
123
+ const nameParts = normalizeText(skill.name).split("-").filter((token) => token.length > 2);
124
+ lexical += nameParts.filter((token) => tokens.has(token)).length * 8;
125
+ const descriptionTokens = new Set(normalizeText(skill.description).split(" ").filter((token) => token.length > 3));
126
+ const descriptionHits = [...tokens].filter((token) => descriptionTokens.has(token));
127
+ if (descriptionHits.length > 1) lexical += descriptionHits.length * 2;
128
+ if (!lexical) return 0;
129
+ const kindFit = !kind || !skill.appliesTo.length || skill.appliesTo.includes(kind) ? 6 : -8;
130
+ return Math.max(1, lexical + kindFit);
131
+ }
132
+
133
+ function ranked(cfg, context = "", kind = "", installed = null) {
134
+ return enabledSkills(cfg, installed).map((skill) => ({ skill, score: scoreSkill(skill, context, kind) }))
135
+ .filter(({ score }) => !context || score > 0)
136
+ .sort((a, b) => b.score - a.score || a.skill.name.localeCompare(b.skill.name, "en"));
137
+ }
138
+
139
+ function compact(skill) {
140
+ const description = skill.description.length > 180 ? `${skill.description.slice(0, 177)}...` : skill.description;
141
+ return { name: skill.name, description, appliesTo: skill.appliesTo };
142
+ }
143
+
144
+ // Operatore tum govdeleri veya tum aciklamalari yuklemek yerine, gorev metnine gore en ilgili
145
+ // etkin becerileri verir. Limit ve karakter butcesi katalog buyuse bile prompt maliyetini sabitler.
146
+ function catalogFromRanked(cfg, rankedSkills) {
147
+ const limit = Math.max(1, Number(cfg?.skills?.catalogLimit) || 12);
148
+ const budget = Math.max(200, Number(cfg?.skills?.charBudget) || 2400);
149
+ const out = [];
150
+ for (const { skill } of rankedSkills) {
151
+ if (out.length >= limit) break;
152
+ let item = compact(skill);
153
+ let candidate = [...out, item];
154
+ if (JSON.stringify(candidate).length > budget) {
155
+ if (out.length) break;
156
+ const withoutDescription = { ...item, description: "" };
157
+ const fixedCost = JSON.stringify([withoutDescription]).length;
158
+ if (fixedCost > budget) break;
159
+ const room = Math.max(0, budget - fixedCost - 3);
160
+ item = { ...item, description: room ? `${item.description.slice(0, room)}...` : "" };
161
+ candidate = [item];
162
+ }
163
+ out.push(item);
164
+ }
165
+ return out;
166
+ }
167
+
168
+ function discover(cfg, context = "", kind = "") {
169
+ const installed = allSkills();
170
+ const enabled = enabledSkills(cfg, installed);
171
+ const matches = ranked(cfg, context, kind, installed);
172
+ return {
173
+ catalog: catalogFromRanked(cfg, matches),
174
+ stats: { installed: installed.length, enabled: enabled.length, matched: matches.length },
175
+ };
176
+ }
177
+
178
+ function catalog(cfg, context = "", kind = "") {
179
+ return discover(cfg, context, kind).catalog;
180
+ }
181
+
182
+ function catalogStats(cfg, context = "", kind = "") {
183
+ return discover(cfg, context, kind).stats;
184
+ }
185
+
186
+ function suggest(cfg, context, kind = "") {
187
+ const limit = Math.max(1, Number(cfg?.skills?.maxSkillsPerAssignment) || 3);
188
+ return ranked(cfg, context, kind).slice(0, limit).map(({ skill }) => skill.name);
189
+ }
190
+
191
+ // Bir delegasyona istenen beceri adlarini cozer; yalnizca hem var olan hem de kullanicinin
192
+ // etkinlestirdigi beceriler donuir. Boylece operator, katalog disinda bir beceri iliştiremez.
193
+ function resolveForAssignment(names, cfg) {
194
+ const enabled = enabledNames(cfg);
195
+ const wildcard = enabled.has("*");
196
+ const limit = Math.max(1, Number(cfg?.skills?.maxSkillsPerAssignment) || 3);
197
+ const out = [];
198
+ const seen = new Set();
199
+ for (const name of toList(names)) {
200
+ if (out.length >= limit) break;
201
+ if (seen.has(name) || (!wildcard && !enabled.has(name))) continue;
202
+ const skill = loadSkill(name);
203
+ if (skill) { out.push(skill); seen.add(name); }
204
+ }
205
+ return out;
206
+ }
207
+
208
+ // Progressive disclosure: uzmana becerinin TAM govdesini yigmadan, ad + kisa ozet + rehber dosya
209
+ // yolunu verir. Uzman gercekten ihtiyac duyarsa dosyayi kendisi okur. Boylece prompt hafif kalir.
210
+ function toPromptRefs(skills, charBudget = 1200) {
211
+ if (!Array.isArray(skills) || !skills.length) return "";
212
+ const lines = [];
213
+ for (const skill of skills) {
214
+ const location = path.join(SKILLS_DIR, skill.file);
215
+ let line = `- ${skill.name}: ${skill.description || "(aciklama yok)"}\n Rehber: ${location}`;
216
+ if (charBudget && [...lines, line].join("\n").length > charBudget) {
217
+ if (lines.length) break;
218
+ line = `- ${skill.name}\n Rehber: ${location}`;
219
+ if (line.length > charBudget) break;
220
+ }
221
+ lines.push(line);
222
+ }
223
+ return lines.join("\n");
224
+ }
225
+
226
+ function validateSkill(nameOrFile, content) {
227
+ const { meta, body } = parseFrontmatter(content);
228
+ const errors = [];
229
+ const name = String(meta.name || "");
230
+ const expected = slugFor(fileNameFor(nameOrFile));
231
+ if (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(name) || name.length > 64) errors.push("name kucuk harfli kebab-case ve en fazla 64 karakter olmali");
232
+ if (name && name !== expected) errors.push("frontmatter name dosya adiyla ayni olmali");
233
+ const description = String(meta.description || "").trim();
234
+ if (!description || description.length > 1024) errors.push("description 1-1024 karakter olmali");
235
+ if (!body.trim()) errors.push("beceri govdesi bos olamaz");
236
+ const invalidKinds = toList(meta.appliesTo).filter((kind) => !["implement", "review", "plan", "research"].includes(kind));
237
+ if (invalidKinds.length) errors.push(`gecersiz appliesTo: ${invalidKinds.join(", ")}`);
238
+ return { ok: !errors.length, errors };
239
+ }
240
+
241
+ function writeSkill(nameOrFile, content) {
242
+ ensureDir();
243
+ const file = fileNameFor(nameOrFile);
244
+ fs.writeFileSync(path.join(SKILLS_DIR, file), String(content == null ? "" : content));
245
+ return file;
246
+ }
247
+
248
+ function deleteSkill(nameOrFile) {
249
+ const p = path.join(SKILLS_DIR, fileNameFor(nameOrFile));
250
+ if (fs.existsSync(p)) fs.rmSync(p);
251
+ }
252
+
253
+ module.exports = {
254
+ SKILLS_DIR,
255
+ parseFrontmatter,
256
+ listSkillFiles,
257
+ loadSkill,
258
+ allSkills,
259
+ enabledNames,
260
+ enabledSkills,
261
+ scoreSkill,
262
+ discover,
263
+ catalog,
264
+ catalogStats,
265
+ suggest,
266
+ resolveForAssignment,
267
+ toPromptRefs,
268
+ validateSkill,
269
+ readRaw,
270
+ writeSkill,
271
+ deleteSkill,
272
+ };