@sporesec/arcana 2.4.0 → 3.0.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 (247) hide show
  1. package/dist/cli.d.ts +0 -1
  2. package/dist/cli.js +124 -9
  3. package/dist/command-registry.d.ts +10 -0
  4. package/dist/command-registry.js +65 -0
  5. package/dist/commands/audit.d.ts +2 -3
  6. package/dist/commands/audit.js +47 -14
  7. package/dist/commands/benchmark.d.ts +4 -0
  8. package/dist/commands/benchmark.js +178 -0
  9. package/dist/commands/clean.d.ts +0 -1
  10. package/dist/commands/clean.js +19 -8
  11. package/dist/commands/compact.d.ts +2 -1
  12. package/dist/commands/compact.js +74 -14
  13. package/dist/commands/completions.d.ts +3 -0
  14. package/dist/commands/completions.js +104 -0
  15. package/dist/commands/config.d.ts +0 -1
  16. package/dist/commands/config.js +15 -6
  17. package/dist/commands/create.d.ts +0 -1
  18. package/dist/commands/create.js +1 -1
  19. package/dist/commands/diff.d.ts +4 -0
  20. package/dist/commands/diff.js +166 -0
  21. package/dist/commands/doctor.d.ts +0 -1
  22. package/dist/commands/doctor.js +64 -23
  23. package/dist/commands/export-cmd.d.ts +4 -0
  24. package/dist/commands/export-cmd.js +66 -0
  25. package/dist/commands/import-cmd.d.ts +4 -0
  26. package/dist/commands/import-cmd.js +131 -0
  27. package/dist/commands/info.d.ts +0 -1
  28. package/dist/commands/info.js +29 -4
  29. package/dist/commands/init.d.ts +0 -1
  30. package/dist/commands/init.js +26 -33
  31. package/dist/commands/install.d.ts +1 -1
  32. package/dist/commands/install.js +118 -205
  33. package/dist/commands/list.d.ts +0 -1
  34. package/dist/commands/list.js +12 -4
  35. package/dist/commands/lock.d.ts +4 -0
  36. package/dist/commands/lock.js +171 -0
  37. package/dist/commands/optimize.d.ts +0 -1
  38. package/dist/commands/optimize.js +111 -20
  39. package/dist/commands/outdated.d.ts +4 -0
  40. package/dist/commands/outdated.js +159 -0
  41. package/dist/commands/profile.d.ts +3 -0
  42. package/dist/commands/profile.js +274 -0
  43. package/dist/commands/providers.d.ts +0 -1
  44. package/dist/commands/providers.js +1 -4
  45. package/dist/commands/recommend.d.ts +5 -0
  46. package/dist/commands/recommend.js +96 -0
  47. package/dist/commands/scan.d.ts +0 -1
  48. package/dist/commands/scan.js +13 -7
  49. package/dist/commands/search.d.ts +2 -1
  50. package/dist/commands/search.js +32 -9
  51. package/dist/commands/stats.d.ts +0 -1
  52. package/dist/commands/stats.js +24 -20
  53. package/dist/commands/team.d.ts +3 -0
  54. package/dist/commands/team.js +291 -0
  55. package/dist/commands/uninstall.d.ts +0 -1
  56. package/dist/commands/uninstall.js +18 -4
  57. package/dist/commands/update.d.ts +0 -1
  58. package/dist/commands/update.js +155 -155
  59. package/dist/commands/validate.d.ts +3 -1
  60. package/dist/commands/validate.js +90 -15
  61. package/dist/commands/verify.d.ts +4 -0
  62. package/dist/commands/verify.js +116 -0
  63. package/dist/constants.d.ts +10 -0
  64. package/dist/constants.js +13 -0
  65. package/dist/index.d.ts +0 -1
  66. package/dist/index.js +0 -1
  67. package/dist/interactive/browse.d.ts +4 -0
  68. package/dist/interactive/browse.js +103 -0
  69. package/dist/interactive/categories.d.ts +4 -0
  70. package/dist/interactive/categories.js +87 -0
  71. package/dist/interactive/health.d.ts +1 -0
  72. package/dist/interactive/health.js +57 -0
  73. package/dist/interactive/helpers.d.ts +11 -0
  74. package/dist/interactive/helpers.js +66 -0
  75. package/dist/interactive/index.d.ts +1 -0
  76. package/dist/interactive/index.js +1 -0
  77. package/dist/interactive/manage.d.ts +2 -0
  78. package/dist/interactive/manage.js +187 -0
  79. package/dist/interactive/menu.d.ts +1 -0
  80. package/dist/interactive/menu.js +107 -0
  81. package/dist/interactive/search.d.ts +2 -0
  82. package/dist/interactive/search.js +66 -0
  83. package/dist/interactive/setup.d.ts +2 -0
  84. package/dist/interactive/setup.js +48 -0
  85. package/dist/interactive/skill-detail.d.ts +5 -0
  86. package/dist/interactive/skill-detail.js +126 -0
  87. package/dist/interactive.d.ts +0 -1
  88. package/dist/interactive.js +89 -66
  89. package/dist/providers/arcana.d.ts +0 -1
  90. package/dist/providers/arcana.js +0 -1
  91. package/dist/providers/base.d.ts +0 -1
  92. package/dist/providers/base.js +0 -1
  93. package/dist/providers/github.d.ts +0 -1
  94. package/dist/providers/github.js +8 -3
  95. package/dist/registry.d.ts +0 -1
  96. package/dist/registry.js +1 -4
  97. package/dist/types.d.ts +10 -1
  98. package/dist/types.js +0 -1
  99. package/dist/utils/atomic.d.ts +0 -1
  100. package/dist/utils/atomic.js +3 -2
  101. package/dist/utils/cache.d.ts +0 -1
  102. package/dist/utils/cache.js +3 -2
  103. package/dist/utils/config.d.ts +2 -1
  104. package/dist/utils/config.js +30 -5
  105. package/dist/utils/conflict-check.d.ts +8 -0
  106. package/dist/utils/conflict-check.js +72 -0
  107. package/dist/utils/errors.d.ts +0 -1
  108. package/dist/utils/errors.js +0 -1
  109. package/dist/utils/frontmatter.d.ts +0 -1
  110. package/dist/utils/frontmatter.js +44 -14
  111. package/dist/utils/fs.d.ts +0 -1
  112. package/dist/utils/fs.js +30 -11
  113. package/dist/utils/help.d.ts +0 -1
  114. package/dist/utils/help.js +15 -28
  115. package/dist/utils/history.d.ts +0 -1
  116. package/dist/utils/history.js +0 -1
  117. package/dist/utils/http.d.ts +0 -1
  118. package/dist/utils/http.js +14 -5
  119. package/dist/utils/install-core.d.ts +48 -0
  120. package/dist/utils/install-core.js +108 -0
  121. package/dist/utils/integrity.d.ts +17 -0
  122. package/dist/utils/integrity.js +84 -0
  123. package/dist/utils/parallel.d.ts +0 -1
  124. package/dist/utils/parallel.js +0 -1
  125. package/dist/utils/project-context.d.ts +19 -0
  126. package/dist/utils/project-context.js +283 -0
  127. package/dist/utils/quality.d.ts +27 -0
  128. package/dist/utils/quality.js +174 -0
  129. package/dist/utils/scanner.d.ts +0 -1
  130. package/dist/utils/scanner.js +138 -10
  131. package/dist/utils/scoring.d.ts +10 -0
  132. package/dist/utils/scoring.js +84 -0
  133. package/dist/utils/ui.d.ts +0 -1
  134. package/dist/utils/ui.js +11 -4
  135. package/dist/utils/validate.d.ts +0 -1
  136. package/dist/utils/validate.js +4 -1
  137. package/package.json +74 -62
  138. package/dist/cli.d.ts.map +0 -1
  139. package/dist/cli.js.map +0 -1
  140. package/dist/commands/audit.d.ts.map +0 -1
  141. package/dist/commands/audit.js.map +0 -1
  142. package/dist/commands/audit.test.d.ts +0 -2
  143. package/dist/commands/audit.test.d.ts.map +0 -1
  144. package/dist/commands/audit.test.js +0 -217
  145. package/dist/commands/audit.test.js.map +0 -1
  146. package/dist/commands/clean.d.ts.map +0 -1
  147. package/dist/commands/clean.js.map +0 -1
  148. package/dist/commands/compact.d.ts.map +0 -1
  149. package/dist/commands/compact.js.map +0 -1
  150. package/dist/commands/config.d.ts.map +0 -1
  151. package/dist/commands/config.js.map +0 -1
  152. package/dist/commands/create.d.ts.map +0 -1
  153. package/dist/commands/create.js.map +0 -1
  154. package/dist/commands/doctor.d.ts.map +0 -1
  155. package/dist/commands/doctor.js.map +0 -1
  156. package/dist/commands/info.d.ts.map +0 -1
  157. package/dist/commands/info.js.map +0 -1
  158. package/dist/commands/init.d.ts.map +0 -1
  159. package/dist/commands/init.js.map +0 -1
  160. package/dist/commands/install.d.ts.map +0 -1
  161. package/dist/commands/install.js.map +0 -1
  162. package/dist/commands/list.d.ts.map +0 -1
  163. package/dist/commands/list.js.map +0 -1
  164. package/dist/commands/optimize.d.ts.map +0 -1
  165. package/dist/commands/optimize.js.map +0 -1
  166. package/dist/commands/providers.d.ts.map +0 -1
  167. package/dist/commands/providers.js.map +0 -1
  168. package/dist/commands/scan.d.ts.map +0 -1
  169. package/dist/commands/scan.js.map +0 -1
  170. package/dist/commands/search.d.ts.map +0 -1
  171. package/dist/commands/search.js.map +0 -1
  172. package/dist/commands/stats.d.ts.map +0 -1
  173. package/dist/commands/stats.js.map +0 -1
  174. package/dist/commands/uninstall.d.ts.map +0 -1
  175. package/dist/commands/uninstall.js.map +0 -1
  176. package/dist/commands/update.d.ts.map +0 -1
  177. package/dist/commands/update.js.map +0 -1
  178. package/dist/commands/validate.d.ts.map +0 -1
  179. package/dist/commands/validate.js.map +0 -1
  180. package/dist/index.d.ts.map +0 -1
  181. package/dist/index.js.map +0 -1
  182. package/dist/interactive.d.ts.map +0 -1
  183. package/dist/interactive.js.map +0 -1
  184. package/dist/providers/arcana.d.ts.map +0 -1
  185. package/dist/providers/arcana.js.map +0 -1
  186. package/dist/providers/base.d.ts.map +0 -1
  187. package/dist/providers/base.js.map +0 -1
  188. package/dist/providers/github.d.ts.map +0 -1
  189. package/dist/providers/github.js.map +0 -1
  190. package/dist/registry.d.ts.map +0 -1
  191. package/dist/registry.js.map +0 -1
  192. package/dist/types.d.ts.map +0 -1
  193. package/dist/types.js.map +0 -1
  194. package/dist/utils/atomic.d.ts.map +0 -1
  195. package/dist/utils/atomic.js.map +0 -1
  196. package/dist/utils/atomic.test.d.ts +0 -2
  197. package/dist/utils/atomic.test.d.ts.map +0 -1
  198. package/dist/utils/atomic.test.js +0 -31
  199. package/dist/utils/atomic.test.js.map +0 -1
  200. package/dist/utils/cache.d.ts.map +0 -1
  201. package/dist/utils/cache.js.map +0 -1
  202. package/dist/utils/config.d.ts.map +0 -1
  203. package/dist/utils/config.js.map +0 -1
  204. package/dist/utils/config.test.d.ts +0 -2
  205. package/dist/utils/config.test.d.ts.map +0 -1
  206. package/dist/utils/config.test.js +0 -38
  207. package/dist/utils/config.test.js.map +0 -1
  208. package/dist/utils/errors.d.ts.map +0 -1
  209. package/dist/utils/errors.js.map +0 -1
  210. package/dist/utils/frontmatter.d.ts.map +0 -1
  211. package/dist/utils/frontmatter.js.map +0 -1
  212. package/dist/utils/frontmatter.test.d.ts +0 -2
  213. package/dist/utils/frontmatter.test.d.ts.map +0 -1
  214. package/dist/utils/frontmatter.test.js +0 -152
  215. package/dist/utils/frontmatter.test.js.map +0 -1
  216. package/dist/utils/fs.d.ts.map +0 -1
  217. package/dist/utils/fs.js.map +0 -1
  218. package/dist/utils/fs.test.d.ts +0 -2
  219. package/dist/utils/fs.test.d.ts.map +0 -1
  220. package/dist/utils/fs.test.js +0 -145
  221. package/dist/utils/fs.test.js.map +0 -1
  222. package/dist/utils/help.d.ts.map +0 -1
  223. package/dist/utils/help.js.map +0 -1
  224. package/dist/utils/help.test.d.ts +0 -2
  225. package/dist/utils/help.test.d.ts.map +0 -1
  226. package/dist/utils/help.test.js +0 -66
  227. package/dist/utils/help.test.js.map +0 -1
  228. package/dist/utils/history.d.ts.map +0 -1
  229. package/dist/utils/history.js.map +0 -1
  230. package/dist/utils/http.d.ts.map +0 -1
  231. package/dist/utils/http.js.map +0 -1
  232. package/dist/utils/http.test.d.ts +0 -2
  233. package/dist/utils/http.test.d.ts.map +0 -1
  234. package/dist/utils/http.test.js +0 -55
  235. package/dist/utils/http.test.js.map +0 -1
  236. package/dist/utils/parallel.d.ts.map +0 -1
  237. package/dist/utils/parallel.js.map +0 -1
  238. package/dist/utils/scanner.d.ts.map +0 -1
  239. package/dist/utils/scanner.js.map +0 -1
  240. package/dist/utils/ui.d.ts.map +0 -1
  241. package/dist/utils/ui.js.map +0 -1
  242. package/dist/utils/ui.test.d.ts +0 -2
  243. package/dist/utils/ui.test.d.ts.map +0 -1
  244. package/dist/utils/ui.test.js +0 -31
  245. package/dist/utils/ui.test.js.map +0 -1
  246. package/dist/utils/validate.d.ts.map +0 -1
  247. package/dist/utils/validate.js.map +0 -1
@@ -18,39 +18,74 @@ const AMBER = chalk.hex("#d4943a");
18
18
  // ---------------------------------------------------------------------------
19
19
  const SKILL_CATEGORIES = {
20
20
  "Code Quality & Review": [
21
- "code-reviewer", "codebase-dissection", "testing-strategy",
22
- "refactoring-patterns", "git-workflow", "pre-production-review",
23
- "frontend-code-review", "dependency-audit", "performance-optimization",
21
+ "code-reviewer",
22
+ "codebase-dissection",
23
+ "testing-strategy",
24
+ "refactoring-patterns",
25
+ "git-workflow",
26
+ "pre-production-review",
27
+ "frontend-code-review",
28
+ "dependency-audit",
29
+ "performance-optimization",
24
30
  ],
25
31
  "Security & Infrastructure": [
26
- "security-review", "local-security", "container-security",
27
- "docker-kubernetes", "ci-cd-pipelines", "ci-cd-automation",
28
- "monitoring-observability", "incident-response",
32
+ "security-review",
33
+ "local-security",
34
+ "container-security",
35
+ "docker-kubernetes",
36
+ "ci-cd-pipelines",
37
+ "ci-cd-automation",
38
+ "monitoring-observability",
39
+ "incident-response",
29
40
  ],
30
41
  "Languages & Frameworks": [
31
- "golang-pro", "go-linter-configuration", "typescript", "typescript-advanced",
32
- "python-best-practices", "rust-best-practices", "frontend-design",
33
- "fullstack-developer", "remotion-best-practices", "npm-package",
42
+ "golang-pro",
43
+ "go-linter-configuration",
44
+ "typescript",
45
+ "typescript-advanced",
46
+ "python-best-practices",
47
+ "rust-best-practices",
48
+ "frontend-design",
49
+ "fullstack-developer",
50
+ "remotion-best-practices",
51
+ "npm-package",
34
52
  ],
35
53
  "API, Data & Docs": [
36
- "api-design", "api-testing", "programming-architecture",
37
- "database-design", "env-config", "cost-optimization",
38
- "docx", "xlsx", "doc-generation", "update-docs",
54
+ "api-design",
55
+ "api-testing",
56
+ "programming-architecture",
57
+ "database-design",
58
+ "env-config",
59
+ "cost-optimization",
60
+ "docx",
61
+ "xlsx",
62
+ "doc-generation",
63
+ "update-docs",
39
64
  ],
40
65
  "Game Design & Production": [
41
- "game-design-theory", "game-engines", "game-programming-languages",
42
- "gameplay-mechanics", "level-design", "game-tools-workflows",
43
- "game-servers", "networking-servers", "synchronization-algorithms",
44
- "monetization-systems", "publishing-platforms", "daw-music",
66
+ "game-design-theory",
67
+ "game-engines",
68
+ "game-programming-languages",
69
+ "gameplay-mechanics",
70
+ "level-design",
71
+ "game-tools-workflows",
72
+ "game-servers",
73
+ "networking-servers",
74
+ "synchronization-algorithms",
75
+ "monetization-systems",
76
+ "publishing-platforms",
77
+ "daw-music",
45
78
  ],
46
79
  "Graphics, Audio & Performance": [
47
- "graphics-rendering", "shader-techniques", "particle-systems",
48
- "audio-systems", "asset-optimization", "optimization-performance",
80
+ "graphics-rendering",
81
+ "shader-techniques",
82
+ "particle-systems",
83
+ "audio-systems",
84
+ "asset-optimization",
85
+ "optimization-performance",
49
86
  "memory-management",
50
87
  ],
51
- "Skill Development": [
52
- "skill-creation-guide", "skill-creator", "find-skills", "project-migration",
53
- ],
88
+ "Skill Development": ["skill-creation-guide", "skill-creator", "find-skills", "project-migration"],
54
89
  };
55
90
  // ---------------------------------------------------------------------------
56
91
  // Helpers
@@ -68,7 +103,7 @@ function countInstalled() {
68
103
  const dir = getInstallDir();
69
104
  if (!existsSync(dir))
70
105
  return 0;
71
- return readdirSync(dir).filter(d => {
106
+ return readdirSync(dir).filter((d) => {
72
107
  try {
73
108
  return statSync(join(dir, d)).isDirectory();
74
109
  }
@@ -91,16 +126,14 @@ function getRelatedSkills(skillName, limit = 3) {
91
126
  const cat = getCategoryFor(skillName);
92
127
  if (!cat)
93
128
  return [];
94
- return (SKILL_CATEGORIES[cat] ?? [])
95
- .filter(s => s !== skillName && !isSkillInstalled(s))
96
- .slice(0, limit);
129
+ return (SKILL_CATEGORIES[cat] ?? []).filter((s) => s !== skillName && !isSkillInstalled(s)).slice(0, limit);
97
130
  }
98
131
  function getInstalledNames() {
99
132
  const dir = getInstallDir();
100
133
  if (!existsSync(dir))
101
134
  return [];
102
135
  return readdirSync(dir)
103
- .filter(d => {
136
+ .filter((d) => {
104
137
  try {
105
138
  return statSync(join(dir, d)).isDirectory();
106
139
  }
@@ -110,7 +143,7 @@ function getInstalledNames() {
110
143
  })
111
144
  .sort();
112
145
  }
113
- function buildMenuOptions(installedCount, availableCount) {
146
+ function buildMenuOptions(installedCount, _availableCount) {
114
147
  const isNew = installedCount === 0;
115
148
  const options = [];
116
149
  if (isNew) {
@@ -211,7 +244,7 @@ function doUninstall(skillName) {
211
244
  // Skill Detail View (central action point)
212
245
  // ---------------------------------------------------------------------------
213
246
  async function skillDetailFlow(skillName, allSkills, providerName) {
214
- const info = allSkills.find(s => s.name === skillName);
247
+ const info = allSkills.find((s) => s.name === skillName);
215
248
  const installed = isSkillInstalled(skillName);
216
249
  const meta = installed ? readSkillMeta(skillName) : null;
217
250
  // Build info block
@@ -277,11 +310,11 @@ async function skillDetailFlow(skillName, allSkills, providerName) {
277
310
  // Browse by Category
278
311
  // ---------------------------------------------------------------------------
279
312
  async function browseByCategory(allSkills, providerName) {
280
- const availableNames = new Set(allSkills.map(s => s.name));
313
+ const availableNames = new Set(allSkills.map((s) => s.name));
281
314
  while (true) {
282
315
  const categoryOptions = Object.entries(SKILL_CATEGORIES).map(([name, skills]) => {
283
- const valid = skills.filter(s => availableNames.has(s));
284
- const installedCount = valid.filter(s => isSkillInstalled(s)).length;
316
+ const valid = skills.filter((s) => availableNames.has(s));
317
+ const installedCount = valid.filter((s) => isSkillInstalled(s)).length;
285
318
  return {
286
319
  value: name,
287
320
  label: name,
@@ -290,10 +323,7 @@ async function browseByCategory(allSkills, providerName) {
290
323
  });
291
324
  const category = await p.select({
292
325
  message: "Browse by category",
293
- options: [
294
- ...categoryOptions,
295
- { value: "__back", label: "Back" },
296
- ],
326
+ options: [...categoryOptions, { value: "__back", label: "Back" }],
297
327
  });
298
328
  handleCancel(category);
299
329
  if (category === "__back")
@@ -302,15 +332,15 @@ async function browseByCategory(allSkills, providerName) {
302
332
  }
303
333
  }
304
334
  async function categorySkillList(categoryName, skillNames, allSkills, providerName) {
305
- const availableNames = new Set(allSkills.map(s => s.name));
306
- const validSkills = skillNames.filter(s => availableNames.has(s));
335
+ const availableNames = new Set(allSkills.map((s) => s.name));
336
+ const validSkills = skillNames.filter((s) => availableNames.has(s));
307
337
  if (validSkills.length === 0) {
308
338
  p.log.warn("No skills found in this category.");
309
339
  return;
310
340
  }
311
341
  while (true) {
312
- const options = validSkills.map(name => {
313
- const info = allSkills.find(s => s.name === name);
342
+ const options = validSkills.map((name) => {
343
+ const info = allSkills.find((s) => s.name === name);
314
344
  const installed = isSkillInstalled(name);
315
345
  return {
316
346
  value: name,
@@ -318,7 +348,7 @@ async function categorySkillList(categoryName, skillNames, allSkills, providerNa
318
348
  hint: truncate(info?.description ?? "", 50),
319
349
  };
320
350
  });
321
- const notInstalled = validSkills.filter(s => !isSkillInstalled(s));
351
+ const notInstalled = validSkills.filter((s) => !isSkillInstalled(s));
322
352
  const extraOptions = [];
323
353
  if (notInstalled.length > 0) {
324
354
  extraOptions.push({
@@ -386,18 +416,14 @@ async function searchFlow(allSkills, providerName) {
386
416
  }
387
417
  async function searchResultsPicker(results, allSkills, providerName) {
388
418
  while (true) {
389
- const options = results.map(skill => ({
419
+ const options = results.map((skill) => ({
390
420
  value: skill.name,
391
421
  label: `${skill.name}${isSkillInstalled(skill.name) ? chalk.green(" \u2713") : ""}`,
392
422
  hint: truncate(skill.description, 50),
393
423
  }));
394
424
  const picked = await p.select({
395
425
  message: "Pick a skill for details",
396
- options: [
397
- ...options,
398
- { value: "__search", label: "Search again" },
399
- { value: "__back", label: "Back" },
400
- ],
426
+ options: [...options, { value: "__search", label: "Search again" }, { value: "__back", label: "Back" }],
401
427
  });
402
428
  handleCancel(picked);
403
429
  if (picked === "__search")
@@ -417,7 +443,7 @@ async function quickSetup(allSkills, providerName) {
417
443
  const proj = detectProject(process.cwd());
418
444
  p.log.step(`Detected: ${chalk.cyan(proj.name)} (${proj.type})`);
419
445
  const suggestions = SKILL_SUGGESTIONS[proj.type] ?? SKILL_SUGGESTIONS_DEFAULT;
420
- const availableNames = new Set(allSkills.map(s => s.name));
446
+ const availableNames = new Set(allSkills.map((s) => s.name));
421
447
  const validSuggestions = suggestions.filter((s) => availableNames.has(s));
422
448
  if (validSuggestions.length === 0) {
423
449
  p.log.info("No specific recommendations for this project type.");
@@ -467,17 +493,17 @@ async function manageInstalled(allSkills, providerName) {
467
493
  const groups = [];
468
494
  const categorized = new Set();
469
495
  for (const [cat, catSkills] of Object.entries(SKILL_CATEGORIES)) {
470
- const installed = catSkills.filter(s => names.includes(s));
496
+ const installed = catSkills.filter((s) => names.includes(s));
471
497
  if (installed.length > 0) {
472
498
  groups.push({ cat, skills: installed });
473
- installed.forEach(s => categorized.add(s));
499
+ installed.forEach((s) => categorized.add(s));
474
500
  }
475
501
  }
476
- const uncategorized = names.filter(s => !categorized.has(s));
502
+ const uncategorized = names.filter((s) => !categorized.has(s));
477
503
  if (uncategorized.length > 0) {
478
504
  groups.push({ cat: "Other", skills: uncategorized });
479
505
  }
480
- const options = groups.map(g => ({
506
+ const options = groups.map((g) => ({
481
507
  value: g.cat,
482
508
  label: g.cat,
483
509
  hint: `${g.skills.length} installed`,
@@ -502,7 +528,7 @@ async function manageInstalled(allSkills, providerName) {
502
528
  await bulkUninstall(names);
503
529
  continue;
504
530
  }
505
- const group = groups.find(g => g.cat === picked);
531
+ const group = groups.find((g) => g.cat === picked);
506
532
  if (group) {
507
533
  await installedCategoryList(group.cat, group.skills, allSkills, providerName);
508
534
  }
@@ -510,12 +536,12 @@ async function manageInstalled(allSkills, providerName) {
510
536
  }
511
537
  async function installedCategoryList(categoryName, installedNames, allSkills, providerName) {
512
538
  while (true) {
513
- const stillInstalled = installedNames.filter(s => isSkillInstalled(s));
539
+ const stillInstalled = installedNames.filter((s) => isSkillInstalled(s));
514
540
  if (stillInstalled.length === 0) {
515
541
  p.log.info("No skills remaining in this category.");
516
542
  return;
517
543
  }
518
- const options = stillInstalled.map(name => {
544
+ const options = stillInstalled.map((name) => {
519
545
  const meta = readSkillMeta(name);
520
546
  const ver = meta ? `v${meta.version}` : "";
521
547
  const date = meta?.installedAt ? new Date(meta.installedAt).toLocaleDateString() : "";
@@ -540,7 +566,7 @@ async function installedCategoryList(categoryName, installedNames, allSkills, pr
540
566
  async function bulkUninstall(installedNames) {
541
567
  const selected = await p.multiselect({
542
568
  message: "Select skills to uninstall",
543
- options: installedNames.map(name => ({ value: name, label: name })),
569
+ options: installedNames.map((name) => ({ value: name, label: name })),
544
570
  required: false,
545
571
  maxItems: 15,
546
572
  });
@@ -583,7 +609,7 @@ async function updateAll(providerName) {
583
609
  p.log.error(ui.dim(err.message));
584
610
  return;
585
611
  }
586
- const remoteMap = new Map(remoteSkills.map(rs => [rs.name, rs]));
612
+ const remoteMap = new Map(remoteSkills.map((rs) => [rs.name, rs]));
587
613
  const updates = [];
588
614
  for (const name of installed) {
589
615
  const remote = remoteMap.get(name);
@@ -642,15 +668,13 @@ async function checkHealth() {
642
668
  const checks = runDoctorChecks();
643
669
  p.log.step(chalk.bold("Environment Health Check"));
644
670
  for (const check of checks) {
645
- const icon = check.status === "pass" ? chalk.green("OK")
646
- : check.status === "warn" ? chalk.yellow("!!")
647
- : chalk.red("XX");
671
+ const icon = check.status === "pass" ? chalk.green("OK") : check.status === "warn" ? chalk.yellow("!!") : chalk.red("XX");
648
672
  p.log.info(`${icon} ${chalk.bold(check.name)}: ${check.message}`);
649
673
  if (check.fix)
650
674
  p.log.info(chalk.dim(` Fix: ${check.fix}`));
651
675
  }
652
- const fails = checks.filter(c => c.status === "fail").length;
653
- const warns = checks.filter(c => c.status === "warn").length;
676
+ const fails = checks.filter((c) => c.status === "fail").length;
677
+ const warns = checks.filter((c) => c.status === "warn").length;
654
678
  if (fails > 0) {
655
679
  p.log.error(`${fails} issue${fails > 1 ? "s" : ""} found`);
656
680
  }
@@ -662,10 +686,10 @@ async function checkHealth() {
662
686
  return;
663
687
  }
664
688
  // Offer fixes once - no loop. User can re-enter health check to verify.
665
- const fixChecks = checks.filter(c => c.fix && c.status !== "pass");
689
+ const fixChecks = checks.filter((c) => c.fix && c.status !== "pass");
666
690
  if (fixChecks.length === 0)
667
691
  return;
668
- const fixOptions = fixChecks.map(c => {
692
+ const fixOptions = fixChecks.map((c) => {
669
693
  const cmd = c.fix.replace(/^Run:\s*/, "");
670
694
  return { value: cmd, label: `Run: ${cmd}`, hint: c.name };
671
695
  });
@@ -677,7 +701,7 @@ async function checkHealth() {
677
701
  if (fixAction !== "__skip") {
678
702
  const cmd = fixAction;
679
703
  const SAFE_PREFIXES = ["arcana ", "git config "];
680
- if (!SAFE_PREFIXES.some(pre => cmd.startsWith(pre))) {
704
+ if (!SAFE_PREFIXES.some((pre) => cmd.startsWith(pre))) {
681
705
  p.log.warn(`Skipped unsafe command: ${cmd}`);
682
706
  }
683
707
  else {
@@ -724,7 +748,7 @@ export async function showInteractiveMenu(version) {
724
748
  const config = loadConfig();
725
749
  const providerName = config.defaultProvider;
726
750
  // Fetch skill list once for the session
727
- let allSkills = [];
751
+ const allSkills = [];
728
752
  let availableCount = 0;
729
753
  try {
730
754
  const providers = getProviders();
@@ -815,4 +839,3 @@ export async function showInteractiveMenu(version) {
815
839
  }
816
840
  }
817
841
  }
818
- //# sourceMappingURL=interactive.js.map
@@ -2,4 +2,3 @@ import { GitHubProvider } from "./github.js";
2
2
  export declare class ArcanaProvider extends GitHubProvider {
3
3
  constructor();
4
4
  }
5
- //# sourceMappingURL=arcana.d.ts.map
@@ -8,4 +8,3 @@ export class ArcanaProvider extends GitHubProvider {
8
8
  });
9
9
  }
10
10
  }
11
- //# sourceMappingURL=arcana.js.map
@@ -8,4 +8,3 @@ export declare abstract class Provider {
8
8
  clearCache(): void;
9
9
  info(skillName: string): Promise<SkillInfo | null>;
10
10
  }
11
- //# sourceMappingURL=base.d.ts.map
@@ -7,4 +7,3 @@ export class Provider {
7
7
  return skills.find((s) => s.name === skillName) ?? null;
8
8
  }
9
9
  }
10
- //# sourceMappingURL=base.js.map
@@ -22,4 +22,3 @@ export declare class GitHubProvider extends Provider {
22
22
  search(query: string): Promise<SkillInfo[]>;
23
23
  clearCache(): void;
24
24
  }
25
- //# sourceMappingURL=github.d.ts.map
@@ -81,6 +81,11 @@ export class GitHubProvider extends Provider {
81
81
  version: p.version,
82
82
  source: this.name,
83
83
  repo: `https://github.com/${this.owner}/${this.repo}`,
84
+ tags: p.tags,
85
+ conflicts: p.conflicts,
86
+ companions: p.companions,
87
+ verified: p.verified,
88
+ author: p.author,
84
89
  }));
85
90
  // Warn about malformed entries
86
91
  const skipped = data.plugins.length - this.cache.length;
@@ -119,7 +124,7 @@ export class GitHubProvider extends Provider {
119
124
  return { path: relativePath, content };
120
125
  }
121
126
  catch (err) {
122
- throw new Error(`Failed to fetch file "${relativePath}" for skill "${skillName}": ${err instanceof Error ? err.message : String(err)}`);
127
+ throw new Error(`Failed to fetch file "${relativePath}" for skill "${skillName}": ${err instanceof Error ? err.message : String(err)}`, { cause: err });
123
128
  }
124
129
  }, 6);
125
130
  return results.filter((r) => r !== null);
@@ -128,7 +133,8 @@ export class GitHubProvider extends Provider {
128
133
  const all = await this.list();
129
134
  const q = query.toLowerCase();
130
135
  const exact = all.filter((s) => s.name.toLowerCase().includes(q) ||
131
- s.description.toLowerCase().includes(q));
136
+ s.description.toLowerCase().includes(q) ||
137
+ s.tags?.some((t) => t.toLowerCase().includes(q)));
132
138
  if (exact.length > 0)
133
139
  return exact;
134
140
  // Fuzzy fallback: match skills where Levenshtein distance to name <= 3
@@ -143,4 +149,3 @@ export class GitHubProvider extends Provider {
143
149
  clearCacheFile(this.cacheKey);
144
150
  }
145
151
  }
146
- //# sourceMappingURL=github.js.map
@@ -6,4 +6,3 @@ export declare function parseProviderSlug(input: string): {
6
6
  };
7
7
  export declare function getProvider(name?: string): Provider;
8
8
  export declare function getProviders(name?: string): Provider[];
9
- //# sourceMappingURL=registry.d.ts.map
package/dist/registry.js CHANGED
@@ -64,8 +64,5 @@ export function getProviders(name) {
64
64
  if (name)
65
65
  return [getProvider(name)];
66
66
  const config = loadConfig();
67
- return config.providers
68
- .filter((p) => p.enabled)
69
- .map((p) => createProvider(p.name, p.type, p.url));
67
+ return config.providers.filter((p) => p.enabled).map((p) => createProvider(p.name, p.type, p.url));
70
68
  }
71
- //# sourceMappingURL=registry.js.map
package/dist/types.d.ts CHANGED
@@ -4,6 +4,11 @@ export interface SkillInfo {
4
4
  version: string;
5
5
  source: string;
6
6
  repo?: string;
7
+ tags?: string[];
8
+ conflicts?: string[];
9
+ companions?: string[];
10
+ verified?: boolean;
11
+ author?: string;
7
12
  }
8
13
  export interface SkillFile {
9
14
  path: string;
@@ -26,6 +31,11 @@ export interface MarketplacePlugin {
26
31
  source: string;
27
32
  description: string;
28
33
  version: string;
34
+ tags?: string[];
35
+ conflicts?: string[];
36
+ companions?: string[];
37
+ verified?: boolean;
38
+ author?: string;
29
39
  }
30
40
  export interface ProviderConfig {
31
41
  name: string;
@@ -64,4 +74,3 @@ export interface DoctorCheck {
64
74
  message: string;
65
75
  fix?: string;
66
76
  }
67
- //# sourceMappingURL=types.d.ts.map
package/dist/types.js CHANGED
@@ -1,2 +1 @@
1
1
  export {};
2
- //# sourceMappingURL=types.js.map
@@ -1,2 +1 @@
1
1
  export declare function atomicWriteSync(filePath: string, content: string, mode?: number): void;
2
- //# sourceMappingURL=atomic.d.ts.map
@@ -13,8 +13,9 @@ export function atomicWriteSync(filePath, content, mode = 0o644) {
13
13
  try {
14
14
  unlinkSync(tmpPath);
15
15
  }
16
- catch { /* cleanup best-effort */ }
16
+ catch {
17
+ /* cleanup best-effort */
18
+ }
17
19
  throw err;
18
20
  }
19
21
  }
20
- //# sourceMappingURL=atomic.js.map
@@ -1,4 +1,3 @@
1
1
  export declare function readCache<T>(key: string, maxAgeMs?: number): T | null;
2
2
  export declare function writeCache<T>(key: string, data: T): void;
3
3
  export declare function clearCacheFile(key: string): void;
4
- //# sourceMappingURL=cache.d.ts.map
@@ -41,7 +41,8 @@ export function clearCacheFile(key) {
41
41
  try {
42
42
  unlinkSync(file);
43
43
  }
44
- catch { /* best-effort */ }
44
+ catch {
45
+ /* best-effort */
46
+ }
45
47
  }
46
48
  }
47
- //# sourceMappingURL=cache.js.map
@@ -1,6 +1,7 @@
1
1
  import type { ArcanaConfig, ProviderConfig } from "../types.js";
2
+ /** Validate config and return warnings for invalid fields. */
3
+ export declare function validateConfig(config: ArcanaConfig): string[];
2
4
  export declare function loadConfig(): ArcanaConfig;
3
5
  export declare function saveConfig(config: ArcanaConfig): void;
4
6
  export declare function addProvider(provider: ProviderConfig): void;
5
7
  export declare function removeProvider(name: string): boolean;
6
- //# sourceMappingURL=config.d.ts.map
@@ -4,6 +4,8 @@ import { homedir } from "node:os";
4
4
  import { ui } from "./ui.js";
5
5
  import { atomicWriteSync } from "./atomic.js";
6
6
  const CONFIG_PATH = join(homedir(), ".arcana", "config.json");
7
+ /** Matches owner/repo slug format (e.g. "medy-gribkov/arcana") */
8
+ const SLUG_RE = /^[a-zA-Z0-9_.-]+\/[a-zA-Z0-9_.-]+$/;
7
9
  const DEFAULT_CONFIG = {
8
10
  defaultProvider: "arcana",
9
11
  installDir: join(homedir(), ".agents", "skills"),
@@ -17,7 +19,27 @@ const DEFAULT_CONFIG = {
17
19
  ],
18
20
  };
19
21
  function cloneConfig(config) {
20
- return { ...config, providers: config.providers.map(p => ({ ...p })) };
22
+ return { ...config, providers: config.providers.map((p) => ({ ...p })) };
23
+ }
24
+ /** Validate config and return warnings for invalid fields. */
25
+ export function validateConfig(config) {
26
+ const warnings = [];
27
+ // Validate providers have valid owner/repo slugs
28
+ for (const p of config.providers) {
29
+ if (p.type === "github" && !SLUG_RE.test(p.url)) {
30
+ warnings.push(`Provider "${p.name}" has invalid URL "${p.url}". Expected owner/repo format.`);
31
+ }
32
+ }
33
+ // Validate installDir is absolute
34
+ if (!isAbsolute(config.installDir)) {
35
+ warnings.push(`installDir "${config.installDir}" is not an absolute path.`);
36
+ }
37
+ // Validate defaultProvider matches a configured provider or valid slug
38
+ const providerNames = config.providers.map((p) => p.name);
39
+ if (!providerNames.includes(config.defaultProvider) && !SLUG_RE.test(config.defaultProvider)) {
40
+ warnings.push(`defaultProvider "${config.defaultProvider}" doesn't match any configured provider and isn't a valid slug.`);
41
+ }
42
+ return warnings;
21
43
  }
22
44
  export function loadConfig() {
23
45
  if (!existsSync(CONFIG_PATH)) {
@@ -29,7 +51,7 @@ export function loadConfig() {
29
51
  const config = {
30
52
  ...DEFAULT_CONFIG,
31
53
  ...loaded,
32
- providers: loaded.providers ?? DEFAULT_CONFIG.providers.map(p => ({ ...p })),
54
+ providers: loaded.providers ?? DEFAULT_CONFIG.providers.map((p) => ({ ...p })),
33
55
  };
34
56
  return applyEnvOverrides(config);
35
57
  }
@@ -51,11 +73,15 @@ function applyEnvOverrides(base) {
51
73
  }
52
74
  const envProvider = process.env.ARCANA_DEFAULT_PROVIDER;
53
75
  if (envProvider) {
54
- if (envProvider.trim().length === 0) {
76
+ const trimmed = envProvider.trim();
77
+ if (trimmed.length === 0) {
55
78
  console.error(ui.warn(" Warning: ARCANA_DEFAULT_PROVIDER is empty. Ignoring."));
56
79
  }
80
+ else if (!SLUG_RE.test(trimmed) && !config.providers.some((p) => p.name === trimmed)) {
81
+ console.error(ui.warn(` Warning: ARCANA_DEFAULT_PROVIDER "${trimmed}" is not a valid slug. Ignoring.`));
82
+ }
57
83
  else {
58
- config.defaultProvider = envProvider.trim();
84
+ config.defaultProvider = trimmed;
59
85
  }
60
86
  }
61
87
  return config;
@@ -87,4 +113,3 @@ export function removeProvider(name) {
87
113
  saveConfig(config);
88
114
  return true;
89
115
  }
90
- //# sourceMappingURL=config.js.map
@@ -0,0 +1,8 @@
1
+ import type { SkillInfo } from "../types.js";
2
+ import type { ProjectContext } from "./project-context.js";
3
+ export interface ConflictWarning {
4
+ type: "rule-overlap" | "skill-conflict" | "preference-clash";
5
+ message: string;
6
+ severity: "warn" | "block";
7
+ }
8
+ export declare function checkConflicts(skillName: string, skillInfo: SkillInfo | null, skillContent: string | null, context: ProjectContext): ConflictWarning[];
@@ -0,0 +1,72 @@
1
+ /** Known opposing preference pairs. If a skill promotes one, and CLAUDE.md has the other, warn. */
2
+ const OPPOSING_PAIRS = [
3
+ ["callbacks", "async/await"],
4
+ ["any", "strict typing"],
5
+ ["abbreviations", "meaningful names"],
6
+ ["classes", "functional"],
7
+ ["oop", "functional programming"],
8
+ ["semicolons", "no semicolons"],
9
+ ["tabs", "spaces"],
10
+ ];
11
+ export function checkConflicts(skillName, skillInfo, skillContent, context) {
12
+ const warnings = [];
13
+ // 1. Explicit skill-level conflicts from marketplace metadata
14
+ if (skillInfo?.conflicts && skillInfo.conflicts.length > 0) {
15
+ const installed = context.installedSkills;
16
+ const conflicting = skillInfo.conflicts.filter((c) => installed.includes(c));
17
+ for (const c of conflicting) {
18
+ warnings.push({
19
+ type: "skill-conflict",
20
+ message: `"${skillName}" conflicts with installed skill "${c}".`,
21
+ severity: "block",
22
+ });
23
+ }
24
+ }
25
+ // 2. Rule overlap: skill name matches existing .claude/rules/*.md filename
26
+ const ruleBasenames = context.ruleFiles.map((f) => f.replace(/\.md$/, "").toLowerCase());
27
+ if (ruleBasenames.includes(skillName.toLowerCase())) {
28
+ warnings.push({
29
+ type: "rule-overlap",
30
+ message: `A rule file "${skillName}.md" already exists in .claude/rules/. This skill may duplicate existing instructions.`,
31
+ severity: "warn",
32
+ });
33
+ }
34
+ // Also check if skill tags overlap heavily with rule file names
35
+ if (skillInfo?.tags) {
36
+ for (const tag of skillInfo.tags) {
37
+ if (ruleBasenames.includes(tag.toLowerCase()) && tag.toLowerCase() !== skillName.toLowerCase()) {
38
+ warnings.push({
39
+ type: "rule-overlap",
40
+ message: `Skill tag "${tag}" matches existing rule "${tag}.md". May overlap.`,
41
+ severity: "warn",
42
+ });
43
+ break; // one warning is enough
44
+ }
45
+ }
46
+ }
47
+ // 3. Preference clash: check skill content against CLAUDE.md preferences
48
+ if (skillContent && context.preferences.length > 0) {
49
+ const contentLower = skillContent.toLowerCase();
50
+ for (const [a, b] of OPPOSING_PAIRS) {
51
+ const skillHasA = contentLower.includes(a);
52
+ const skillHasB = contentLower.includes(b);
53
+ const prefsHaveA = context.preferences.some((p) => p.toLowerCase().includes(a));
54
+ const prefsHaveB = context.preferences.some((p) => p.toLowerCase().includes(b));
55
+ if (skillHasA && prefsHaveB) {
56
+ warnings.push({
57
+ type: "preference-clash",
58
+ message: `Skill mentions "${a}" but your project prefers "${b}".`,
59
+ severity: "warn",
60
+ });
61
+ }
62
+ else if (skillHasB && prefsHaveA) {
63
+ warnings.push({
64
+ type: "preference-clash",
65
+ message: `Skill mentions "${b}" but your project prefers "${a}".`,
66
+ severity: "warn",
67
+ });
68
+ }
69
+ }
70
+ }
71
+ return warnings;
72
+ }
@@ -3,4 +3,3 @@ export declare class CliError extends Error {
3
3
  readonly exitCode: number;
4
4
  constructor(message: string, code?: string, exitCode?: number);
5
5
  }
6
- //# sourceMappingURL=errors.d.ts.map