@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
@@ -0,0 +1,283 @@
1
+ import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
2
+ import { join, basename } from "node:path";
3
+ import { getInstallDir } from "./fs.js";
4
+ /** Map npm package names to tech tags */
5
+ const PACKAGE_TAG_MAP = {
6
+ next: ["next", "react", "typescript"],
7
+ react: ["react"],
8
+ "react-dom": ["react"],
9
+ vue: ["vue"],
10
+ svelte: ["svelte"],
11
+ angular: ["angular"],
12
+ tailwindcss: ["tailwind"],
13
+ prisma: ["prisma", "database"],
14
+ "@prisma/client": ["prisma", "database"],
15
+ "drizzle-orm": ["drizzle", "database"],
16
+ express: ["express", "node"],
17
+ fastify: ["fastify", "node"],
18
+ hono: ["hono", "node"],
19
+ vitest: ["testing"],
20
+ jest: ["testing"],
21
+ mocha: ["testing"],
22
+ playwright: ["playwright", "testing"],
23
+ cypress: ["cypress", "testing"],
24
+ remotion: ["remotion", "react"],
25
+ three: ["threejs"],
26
+ docker: ["docker"],
27
+ electron: ["electron"],
28
+ "react-native": ["react-native", "react", "mobile"],
29
+ expo: ["expo", "react-native", "mobile"],
30
+ graphql: ["graphql"],
31
+ "@apollo/client": ["graphql", "apollo"],
32
+ trpc: ["trpc"],
33
+ "@trpc/server": ["trpc"],
34
+ mongoose: ["mongodb", "database"],
35
+ pg: ["postgresql", "database"],
36
+ redis: ["redis"],
37
+ ioredis: ["redis"],
38
+ "socket.io": ["websocket"],
39
+ ws: ["websocket"],
40
+ webpack: ["webpack"],
41
+ vite: ["vite"],
42
+ tsup: ["tsup"],
43
+ eslint: ["linting"],
44
+ prettier: ["formatting"],
45
+ storybook: ["storybook"],
46
+ "@storybook/react": ["storybook", "react"],
47
+ };
48
+ /** Map Go module paths to tags */
49
+ const GO_MODULE_TAG_MAP = {
50
+ "github.com/gin-gonic/gin": ["gin", "web"],
51
+ "github.com/gofiber/fiber": ["fiber", "web"],
52
+ "github.com/labstack/echo": ["echo", "web"],
53
+ "github.com/gorilla/mux": ["gorilla", "web"],
54
+ "gorm.io/gorm": ["gorm", "database"],
55
+ "github.com/jmoiron/sqlx": ["sqlx", "database"],
56
+ "github.com/jackc/pgx": ["postgresql", "database"],
57
+ "github.com/go-redis/redis": ["redis"],
58
+ "github.com/rs/zerolog": ["zerolog", "logging"],
59
+ "go.uber.org/zap": ["zap", "logging"],
60
+ "github.com/stretchr/testify": ["testing"],
61
+ "google.golang.org/grpc": ["grpc"],
62
+ "google.golang.org/protobuf": ["protobuf"],
63
+ };
64
+ /** Map Python packages to tags */
65
+ const PYTHON_PACKAGE_TAG_MAP = {
66
+ django: ["django", "web"],
67
+ flask: ["flask", "web"],
68
+ fastapi: ["fastapi", "web"],
69
+ pytorch: ["pytorch", "ml"],
70
+ torch: ["pytorch", "ml"],
71
+ tensorflow: ["tensorflow", "ml"],
72
+ numpy: ["numpy"],
73
+ pandas: ["pandas"],
74
+ sqlalchemy: ["sqlalchemy", "database"],
75
+ pytest: ["testing"],
76
+ celery: ["celery", "async"],
77
+ scrapy: ["scrapy", "scraping"],
78
+ playwright: ["playwright", "testing"],
79
+ requests: ["requests"],
80
+ httpx: ["httpx"],
81
+ };
82
+ function readJsonSafe(filePath) {
83
+ try {
84
+ return JSON.parse(readFileSync(filePath, "utf-8"));
85
+ }
86
+ catch {
87
+ return null;
88
+ }
89
+ }
90
+ function readTextSafe(filePath) {
91
+ try {
92
+ return readFileSync(filePath, "utf-8");
93
+ }
94
+ catch {
95
+ return null;
96
+ }
97
+ }
98
+ function detectTypeAndLang(cwd) {
99
+ const name = basename(cwd);
100
+ if (existsSync(join(cwd, "go.mod")))
101
+ return { name, type: "Go", lang: "go" };
102
+ if (existsSync(join(cwd, "Cargo.toml")))
103
+ return { name, type: "Rust", lang: "rust" };
104
+ if (existsSync(join(cwd, "requirements.txt")) || existsSync(join(cwd, "pyproject.toml")))
105
+ return { name, type: "Python", lang: "python" };
106
+ if (existsSync(join(cwd, "package.json"))) {
107
+ const pkg = readJsonSafe(join(cwd, "package.json"));
108
+ if (pkg?.dependencies?.next || pkg?.devDependencies?.next)
109
+ return { name, type: "Next.js", lang: "typescript" };
110
+ if (pkg?.dependencies?.react || pkg?.devDependencies?.react)
111
+ return { name, type: "React", lang: "typescript" };
112
+ return { name, type: "Node.js", lang: "typescript" };
113
+ }
114
+ return { name, type: "Unknown", lang: "general" };
115
+ }
116
+ function extractNpmTags(cwd) {
117
+ const pkgPath = join(cwd, "package.json");
118
+ const pkg = readJsonSafe(pkgPath);
119
+ if (!pkg)
120
+ return [];
121
+ const tags = new Set();
122
+ const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
123
+ for (const dep of Object.keys(allDeps)) {
124
+ const mapped = PACKAGE_TAG_MAP[dep];
125
+ if (mapped)
126
+ mapped.forEach((t) => tags.add(t));
127
+ }
128
+ // Check for TypeScript
129
+ if (existsSync(join(cwd, "tsconfig.json")) || allDeps.typescript) {
130
+ tags.add("typescript");
131
+ }
132
+ return [...tags];
133
+ }
134
+ function extractGoTags(cwd) {
135
+ const goModPath = join(cwd, "go.mod");
136
+ const content = readTextSafe(goModPath);
137
+ if (!content)
138
+ return ["go"];
139
+ const tags = new Set(["go"]);
140
+ for (const [modulePath, moduleTags] of Object.entries(GO_MODULE_TAG_MAP)) {
141
+ if (content.includes(modulePath)) {
142
+ moduleTags.forEach((t) => tags.add(t));
143
+ }
144
+ }
145
+ return [...tags];
146
+ }
147
+ function extractPythonTags(cwd) {
148
+ const tags = new Set(["python"]);
149
+ // Read requirements.txt
150
+ const reqContent = readTextSafe(join(cwd, "requirements.txt"));
151
+ if (reqContent) {
152
+ for (const line of reqContent.split("\n")) {
153
+ const pkg = line
154
+ .trim()
155
+ .split(/[=<>!~[]/)[0]
156
+ ?.toLowerCase();
157
+ if (pkg) {
158
+ const mapped = PYTHON_PACKAGE_TAG_MAP[pkg];
159
+ if (mapped)
160
+ mapped.forEach((t) => tags.add(t));
161
+ }
162
+ }
163
+ }
164
+ // Read pyproject.toml (simple scan, not full TOML parse)
165
+ const pyprojectContent = readTextSafe(join(cwd, "pyproject.toml"));
166
+ if (pyprojectContent) {
167
+ for (const [pkgName, pkgTags] of Object.entries(PYTHON_PACKAGE_TAG_MAP)) {
168
+ if (pyprojectContent.includes(`"${pkgName}"`) || pyprojectContent.includes(`'${pkgName}'`)) {
169
+ pkgTags.forEach((t) => tags.add(t));
170
+ }
171
+ }
172
+ }
173
+ return [...tags];
174
+ }
175
+ function extractInfraTags(cwd) {
176
+ const tags = [];
177
+ if (existsSync(join(cwd, "Dockerfile")) ||
178
+ existsSync(join(cwd, "docker-compose.yml")) ||
179
+ existsSync(join(cwd, "docker-compose.yaml"))) {
180
+ tags.push("docker");
181
+ }
182
+ if (existsSync(join(cwd, ".github", "workflows"))) {
183
+ tags.push("ci-cd", "github-actions");
184
+ }
185
+ if (existsSync(join(cwd, ".gitlab-ci.yml"))) {
186
+ tags.push("ci-cd", "gitlab-ci");
187
+ }
188
+ if (existsSync(join(cwd, "k8s")) || existsSync(join(cwd, "kubernetes"))) {
189
+ tags.push("kubernetes");
190
+ }
191
+ if (existsSync(join(cwd, "terraform")) || existsSync(join(cwd, "main.tf"))) {
192
+ tags.push("terraform");
193
+ }
194
+ return tags;
195
+ }
196
+ function extractPreferences(claudeMdContent) {
197
+ const prefs = [];
198
+ const lines = claudeMdContent.split("\n");
199
+ let inPrefsSection = false;
200
+ for (const line of lines) {
201
+ const lower = line.toLowerCase();
202
+ if (lower.includes("## coding") || lower.includes("## preferences") || lower.includes("## style")) {
203
+ inPrefsSection = true;
204
+ continue;
205
+ }
206
+ if (inPrefsSection && line.startsWith("## ")) {
207
+ inPrefsSection = false;
208
+ continue;
209
+ }
210
+ if (inPrefsSection && line.trim().startsWith("-")) {
211
+ prefs.push(line.trim().replace(/^-\s*/, ""));
212
+ }
213
+ }
214
+ return prefs;
215
+ }
216
+ function readRuleFiles(cwd) {
217
+ const rulesDir = join(cwd, ".claude", "rules");
218
+ if (!existsSync(rulesDir))
219
+ return [];
220
+ try {
221
+ return readdirSync(rulesDir)
222
+ .filter((f) => f.endsWith(".md"))
223
+ .sort();
224
+ }
225
+ catch {
226
+ return [];
227
+ }
228
+ }
229
+ function getInstalledSkillNames() {
230
+ const dir = getInstallDir();
231
+ if (!existsSync(dir))
232
+ return [];
233
+ try {
234
+ return readdirSync(dir)
235
+ .filter((d) => {
236
+ try {
237
+ return statSync(join(dir, d)).isDirectory();
238
+ }
239
+ catch {
240
+ return false;
241
+ }
242
+ })
243
+ .sort();
244
+ }
245
+ catch {
246
+ return [];
247
+ }
248
+ }
249
+ export function detectProjectContext(cwd) {
250
+ const { name, type, lang } = detectTypeAndLang(cwd);
251
+ // Collect tags based on language
252
+ const tagSet = new Set();
253
+ if (lang !== "general" && lang !== "unknown")
254
+ tagSet.add(lang);
255
+ if (lang === "typescript" || lang === "javascript") {
256
+ extractNpmTags(cwd).forEach((t) => tagSet.add(t));
257
+ }
258
+ if (lang === "go" || type === "Go") {
259
+ extractGoTags(cwd).forEach((t) => tagSet.add(t));
260
+ }
261
+ if (lang === "python" || type === "Python") {
262
+ extractPythonTags(cwd).forEach((t) => tagSet.add(t));
263
+ }
264
+ extractInfraTags(cwd).forEach((t) => tagSet.add(t));
265
+ // Read CLAUDE.md
266
+ const claudeMdPath = join(cwd, "CLAUDE.md");
267
+ const claudeMdContent = readTextSafe(claudeMdPath);
268
+ const preferences = claudeMdContent ? extractPreferences(claudeMdContent) : [];
269
+ // Read rule files
270
+ const ruleFiles = readRuleFiles(cwd);
271
+ // Get installed skills
272
+ const installedSkills = getInstalledSkillNames();
273
+ return {
274
+ name,
275
+ type,
276
+ lang,
277
+ tags: [...tagSet],
278
+ preferences,
279
+ ruleFiles,
280
+ claudeMdContent,
281
+ installedSkills,
282
+ };
283
+ }
@@ -0,0 +1,27 @@
1
+ import type { MarketplacePlugin } from "../types.js";
2
+ export interface CrossValidationIssue {
3
+ level: "error" | "warning";
4
+ category: "marketplace-drift" | "orphan" | "companion" | "duplicate-desc";
5
+ skill: string;
6
+ detail: string;
7
+ }
8
+ /**
9
+ * Jaccard word-level similarity between two strings.
10
+ * Returns 0.0 (completely different) to 1.0 (identical).
11
+ */
12
+ export declare function jaccardSimilarity(a: string, b: string): number;
13
+ /**
14
+ * Validate companion references in marketplace plugins.
15
+ * Every companion must reference an existing plugin name.
16
+ */
17
+ export declare function validateCompanions(plugins: MarketplacePlugin[]): CrossValidationIssue[];
18
+ /**
19
+ * Check description sync between SKILL.md frontmatter and marketplace.json.
20
+ * Returns an issue if similarity is below 0.5.
21
+ */
22
+ export declare function validateDescriptionSync(skillName: string, frontmatterDesc: string, marketplaceDesc: string): CrossValidationIssue | null;
23
+ /**
24
+ * Cross-validate skill directories against marketplace.json.
25
+ * Checks: orphans, companions, description drift, near-duplicates.
26
+ */
27
+ export declare function crossValidate(skillsDir: string, marketplacePath: string): CrossValidationIssue[];
@@ -0,0 +1,174 @@
1
+ import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { extractFrontmatter, parseFrontmatter } from "./frontmatter.js";
4
+ /**
5
+ * Jaccard word-level similarity between two strings.
6
+ * Returns 0.0 (completely different) to 1.0 (identical).
7
+ */
8
+ export function jaccardSimilarity(a, b) {
9
+ const wordsA = new Set(a.toLowerCase().split(/\s+/).filter(Boolean));
10
+ const wordsB = new Set(b.toLowerCase().split(/\s+/).filter(Boolean));
11
+ if (wordsA.size === 0 && wordsB.size === 0)
12
+ return 1.0;
13
+ if (wordsA.size === 0 || wordsB.size === 0)
14
+ return 0.0;
15
+ let intersection = 0;
16
+ for (const w of wordsA) {
17
+ if (wordsB.has(w))
18
+ intersection++;
19
+ }
20
+ const union = wordsA.size + wordsB.size - intersection;
21
+ return union === 0 ? 0 : intersection / union;
22
+ }
23
+ /**
24
+ * Validate companion references in marketplace plugins.
25
+ * Every companion must reference an existing plugin name.
26
+ */
27
+ export function validateCompanions(plugins) {
28
+ const issues = [];
29
+ const names = new Set(plugins.map((p) => p.name));
30
+ for (const plugin of plugins) {
31
+ if (!plugin.companions)
32
+ continue;
33
+ for (const companion of plugin.companions) {
34
+ if (!names.has(companion)) {
35
+ issues.push({
36
+ level: "error",
37
+ category: "companion",
38
+ skill: plugin.name,
39
+ detail: `Companion "${companion}" does not exist in marketplace`,
40
+ });
41
+ }
42
+ }
43
+ }
44
+ return issues;
45
+ }
46
+ /**
47
+ * Check description sync between SKILL.md frontmatter and marketplace.json.
48
+ * Returns an issue if similarity is below 0.5.
49
+ */
50
+ export function validateDescriptionSync(skillName, frontmatterDesc, marketplaceDesc) {
51
+ if (!frontmatterDesc || !marketplaceDesc)
52
+ return null;
53
+ const similarity = jaccardSimilarity(frontmatterDesc, marketplaceDesc);
54
+ if (similarity < 0.5) {
55
+ return {
56
+ level: "warning",
57
+ category: "marketplace-drift",
58
+ skill: skillName,
59
+ detail: `Description drift (${Math.round(similarity * 100)}% similarity) between SKILL.md and marketplace.json`,
60
+ };
61
+ }
62
+ return null;
63
+ }
64
+ /**
65
+ * Cross-validate skill directories against marketplace.json.
66
+ * Checks: orphans, companions, description drift, near-duplicates.
67
+ */
68
+ export function crossValidate(skillsDir, marketplacePath) {
69
+ const issues = [];
70
+ // Load marketplace
71
+ let marketplace;
72
+ try {
73
+ marketplace = JSON.parse(readFileSync(marketplacePath, "utf-8"));
74
+ }
75
+ catch {
76
+ issues.push({
77
+ level: "error",
78
+ category: "orphan",
79
+ skill: "marketplace.json",
80
+ detail: "Cannot read or parse marketplace.json",
81
+ });
82
+ return issues;
83
+ }
84
+ const pluginNames = new Set(marketplace.plugins.map((p) => p.name));
85
+ const pluginMap = new Map(marketplace.plugins.map((p) => [p.name, p]));
86
+ // Get skill directories
87
+ let skillDirs;
88
+ try {
89
+ skillDirs = readdirSync(skillsDir).filter((d) => {
90
+ try {
91
+ return statSync(join(skillsDir, d)).isDirectory();
92
+ }
93
+ catch {
94
+ return false;
95
+ }
96
+ });
97
+ }
98
+ catch {
99
+ issues.push({
100
+ level: "error",
101
+ category: "orphan",
102
+ skill: "skills/",
103
+ detail: "Cannot read skills directory",
104
+ });
105
+ return issues;
106
+ }
107
+ const dirNames = new Set(skillDirs);
108
+ // 1. Orphan directories (dir exists, no marketplace entry)
109
+ for (const dir of skillDirs) {
110
+ if (!pluginNames.has(dir)) {
111
+ issues.push({
112
+ level: "error",
113
+ category: "orphan",
114
+ skill: dir,
115
+ detail: "Skill directory exists but no marketplace.json entry",
116
+ });
117
+ }
118
+ }
119
+ // 2. Orphan entries (marketplace entry, no dir)
120
+ for (const name of pluginNames) {
121
+ if (!dirNames.has(name)) {
122
+ issues.push({
123
+ level: "error",
124
+ category: "orphan",
125
+ skill: name,
126
+ detail: "Marketplace entry exists but no skill directory",
127
+ });
128
+ }
129
+ }
130
+ // 3. Companion validation
131
+ issues.push(...validateCompanions(marketplace.plugins));
132
+ // 4. Description drift (SKILL.md frontmatter vs marketplace.json)
133
+ for (const dir of skillDirs) {
134
+ const plugin = pluginMap.get(dir);
135
+ if (!plugin)
136
+ continue;
137
+ const skillMd = join(skillsDir, dir, "SKILL.md");
138
+ if (!existsSync(skillMd))
139
+ continue;
140
+ try {
141
+ const content = readFileSync(skillMd, "utf-8");
142
+ const extracted = extractFrontmatter(content);
143
+ if (!extracted)
144
+ continue;
145
+ const parsed = parseFrontmatter(extracted.raw);
146
+ if (!parsed?.description)
147
+ continue;
148
+ const driftIssue = validateDescriptionSync(dir, parsed.description, plugin.description);
149
+ if (driftIssue)
150
+ issues.push(driftIssue);
151
+ }
152
+ catch {
153
+ /* skip unreadable */
154
+ }
155
+ }
156
+ // 5. Near-duplicate descriptions across skills
157
+ const descriptions = marketplace.plugins.map((p) => ({ name: p.name, desc: p.description }));
158
+ for (let i = 0; i < descriptions.length; i++) {
159
+ for (let j = i + 1; j < descriptions.length; j++) {
160
+ const a = descriptions[i];
161
+ const b = descriptions[j];
162
+ const sim = jaccardSimilarity(a.desc, b.desc);
163
+ if (sim > 0.85) {
164
+ issues.push({
165
+ level: "warning",
166
+ category: "duplicate-desc",
167
+ skill: a.name,
168
+ detail: `Near-duplicate description with ${b.name} (${Math.round(sim * 100)}% similarity)`,
169
+ });
170
+ }
171
+ }
172
+ }
173
+ return issues;
174
+ }
@@ -24,4 +24,3 @@ export declare function hasCriticalIssues(content: string): boolean;
24
24
  * Format scan results for display.
25
25
  */
26
26
  export declare function formatScanResults(skillName: string, issues: ScanIssue[]): string;
27
- //# sourceMappingURL=scanner.d.ts.map
@@ -89,7 +89,7 @@ const PATTERNS = [
89
89
  level: "high",
90
90
  category: "Prompt injection",
91
91
  detail: "System message impersonation",
92
- regex: /\[system\]|\<system\>|SYSTEM:\s+/,
92
+ regex: /\[system\]|<system>|SYSTEM:\s+/,
93
93
  },
94
94
  // HIGH: Hardcoded secrets
95
95
  {
@@ -136,6 +136,138 @@ const PATTERNS = [
136
136
  detail: "Dynamic remote instruction loading",
137
137
  regex: /(?:curl|wget|fetch)\s+[^\n]*(?:instructions|config|setup)\.(?:md|txt|sh|yaml)/i,
138
138
  },
139
+ // =========================================================================
140
+ // v2.4.2 Extended patterns (Snyk ToxicSkills deep coverage)
141
+ // =========================================================================
142
+ // CRITICAL: Additional malicious code patterns
143
+ {
144
+ level: "critical",
145
+ category: "Malicious code",
146
+ detail: "Curl/wget piped to source",
147
+ regex: /(?:curl|wget)\s+[^\n|]*\|\s*source\b/i,
148
+ },
149
+ {
150
+ level: "critical",
151
+ category: "Malicious code",
152
+ detail: "Nested base64 decoding (obfuscation chain)",
153
+ regex: /base64\s+-d[^\n]*\|\s*base64\s+-d/i,
154
+ },
155
+ {
156
+ level: "critical",
157
+ category: "Malicious code",
158
+ detail: "Password-protected 7z or GPG-encrypted archive",
159
+ regex: /(?:7z\s+x\s+-p\S+|gpg\s+--decrypt\s|gpg\s+-d\s)/i,
160
+ },
161
+ {
162
+ level: "critical",
163
+ category: "Suspicious download",
164
+ detail: "GitHub release download from unverified source",
165
+ regex: /github\.com\/[^\s/]+\/[^\s/]+\/releases\/download\//i,
166
+ },
167
+ {
168
+ level: "critical",
169
+ category: "Malicious code",
170
+ detail: "PowerShell encoded command execution",
171
+ regex: /powershell[^\n]*-[Ee](?:nc|ncodedCommand)\s/i,
172
+ },
173
+ // HIGH: Extended credential and prompt injection patterns
174
+ {
175
+ level: "high",
176
+ category: "Credential theft",
177
+ detail: "Instructing to print/echo API keys or secrets",
178
+ regex: /(?:echo|print|cat|display|output|show)\s+.*(?:api[_-]?key|token|secret|password|credential)/i,
179
+ },
180
+ {
181
+ level: "high",
182
+ category: "Secret detected",
183
+ detail: "AWS access key ID pattern",
184
+ regex: /AKIA[0-9A-Z]{16}/,
185
+ },
186
+ {
187
+ level: "high",
188
+ category: "Secret detected",
189
+ detail: "Anthropic or OpenAI project key pattern",
190
+ regex: /(?:sk-ant-[a-zA-Z0-9-]{20,}|sk-proj-[a-zA-Z0-9-]{20,})/,
191
+ },
192
+ {
193
+ level: "high",
194
+ category: "Credential theft",
195
+ detail: "Authorization header in curl/wget command",
196
+ regex: /(?:curl|wget)\s+[^\n]*-H\s+["']?(?:Authorization|Bearer|Token)\b/i,
197
+ },
198
+ {
199
+ level: "high",
200
+ category: "Memory poisoning",
201
+ detail: "Writing to agent config files (SOUL.md, MEMORY.md, CLAUDE.md)",
202
+ regex: /(?:>>?|write|append|modify|edit|update)\s+[^\n]*(?:SOUL\.md|MEMORY\.md|CLAUDE\.md|\.cursorrules|\.windsurfrules)/i,
203
+ },
204
+ {
205
+ level: "high",
206
+ category: "Prompt injection",
207
+ detail: "Invisible Unicode smuggling (zero-width characters)",
208
+ regex: /\u200B|\u200C|\u200D|\uFEFF|\u2060/,
209
+ },
210
+ {
211
+ level: "high",
212
+ category: "Prompt injection",
213
+ detail: "Global behavior override pattern",
214
+ regex: /always\s+(?:respond|reply|output|return|answer)\s+.*(?:json|xml|yaml|markdown|plain)/i,
215
+ },
216
+ {
217
+ level: "high",
218
+ category: "Prompt injection",
219
+ detail: "Agent autonomy escalation (suppressing confirmations)",
220
+ regex: /(?:never|don't|do\s+not|disable)\s+(?:ask|confirm|check|verify|refuse|warn|prompt)/i,
221
+ },
222
+ {
223
+ level: "high",
224
+ category: "Data exfiltration",
225
+ detail: "Instructing agent to include sensitive data in output",
226
+ regex: /include\s+.*(?:contents?|data|credentials?|keys?|tokens?|secrets?)\s+.*(?:response|output|message|reply)/i,
227
+ },
228
+ // MEDIUM: Extended system and dependency patterns
229
+ {
230
+ level: "medium",
231
+ category: "Unverifiable dependency",
232
+ detail: "Global package installation from unknown source",
233
+ regex: /(?:npm\s+install\s+-g|pip\s+install|go\s+install)\s+\S+/i,
234
+ },
235
+ {
236
+ level: "medium",
237
+ category: "Suspicious activity",
238
+ detail: "Cryptocurrency or financial API access",
239
+ regex: /(?:binance|coinbase|metamask|etherscan|stripe|paypal)\.\w+/i,
240
+ },
241
+ {
242
+ level: "medium",
243
+ category: "Destructive command",
244
+ detail: "Recursive force delete on root or home directory",
245
+ regex: /rm\s+-rf\s+(?:\/\s|\/\*|~\/|~\s|\$HOME)/,
246
+ },
247
+ {
248
+ level: "medium",
249
+ category: "Privilege escalation",
250
+ detail: "Sudo usage in skill instructions",
251
+ regex: /\bsudo\s+\w/,
252
+ },
253
+ {
254
+ level: "medium",
255
+ category: "System modification",
256
+ detail: "Writing to system directories",
257
+ regex: />\s*\/(?:etc|usr|var|opt)\//,
258
+ },
259
+ {
260
+ level: "medium",
261
+ category: "Privilege escalation",
262
+ detail: "Docker privileged mode or capability addition",
263
+ regex: /docker\s+run\s+[^\n]*(?:--privileged|--cap-add)/i,
264
+ },
265
+ {
266
+ level: "medium",
267
+ category: "Suspicious activity",
268
+ detail: "Third-party HTTP request in instructions",
269
+ regex: /(?:fetch\(|axios\.|requests\.get|http\.get|urllib)/,
270
+ },
139
271
  ];
140
272
  // ---------------------------------------------------------------------------
141
273
  // Scanner
@@ -168,7 +300,7 @@ export function scanSkillContent(content) {
168
300
  const joined = line.slice(0, -1) + " " + (lines[i + 1] ?? "").trim();
169
301
  for (const pattern of PATTERNS) {
170
302
  if (pattern.regex.test(joined)) {
171
- const alreadyFound = issues.some(iss => iss.line === i + 1 && iss.category === pattern.category);
303
+ const alreadyFound = issues.some((iss) => iss.line === i + 1 && iss.category === pattern.category);
172
304
  if (!alreadyFound) {
173
305
  issues.push({
174
306
  level: pattern.level,
@@ -191,7 +323,7 @@ export function scanSkillContent(content) {
191
323
  * Quick check: does this content have any critical issues?
192
324
  */
193
325
  export function hasCriticalIssues(content) {
194
- return scanSkillContent(content).some(i => i.level === "critical");
326
+ return scanSkillContent(content).some((i) => i.level === "critical");
195
327
  }
196
328
  /**
197
329
  * Format scan results for display.
@@ -200,17 +332,13 @@ export function formatScanResults(skillName, issues) {
200
332
  if (issues.length === 0)
201
333
  return ` [OK] ${skillName}`;
202
334
  const lines = [];
203
- const critical = issues.filter(i => i.level === "critical").length;
204
- const high = issues.filter(i => i.level === "high").length;
205
- const medium = issues.filter(i => i.level === "medium").length;
335
+ const critical = issues.filter((i) => i.level === "critical").length;
336
+ const high = issues.filter((i) => i.level === "high").length;
206
337
  const tag = critical > 0 ? "[!!]" : high > 0 ? "[!!]" : "[i]";
207
338
  lines.push(` ${tag} ${skillName} (${issues.length} issue${issues.length !== 1 ? "s" : ""})`);
208
339
  for (const issue of issues) {
209
- const icon = issue.level === "critical" ? "CRIT"
210
- : issue.level === "high" ? "HIGH"
211
- : "MED";
340
+ const icon = issue.level === "critical" ? "CRIT" : issue.level === "high" ? "HIGH" : "MED";
212
341
  lines.push(` [${icon}] ${issue.category}: ${issue.detail} (line ${issue.line})`);
213
342
  }
214
343
  return lines.join("\n");
215
344
  }
216
- //# sourceMappingURL=scanner.js.map
@@ -0,0 +1,10 @@
1
+ import type { SkillInfo } from "../types.js";
2
+ import type { ProjectContext } from "./project-context.js";
3
+ export interface RecommendVerdict {
4
+ skill: string;
5
+ verdict: "recommended" | "optional" | "skip" | "conflict";
6
+ score: number;
7
+ reasons: string[];
8
+ }
9
+ export declare function scoreSkill(skill: SkillInfo, context: ProjectContext, installedSkills: string[], allSkills: SkillInfo[]): RecommendVerdict;
10
+ export declare function rankSkills(skills: SkillInfo[], context: ProjectContext): RecommendVerdict[];