javi-forge 1.6.0 → 1.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (322) hide show
  1. package/README.md +45 -1
  2. package/ci-local/README.md +56 -11
  3. package/ci-local/ci-local.ps1 +445 -0
  4. package/ci-local/ci-local.sh +52 -9
  5. package/ci-local/docker/python.Dockerfile +7 -0
  6. package/ci-local/hooks/commit-msg +120 -44
  7. package/ci-local/hooks/commit-msg.test.sh +286 -0
  8. package/ci-local/hooks/pre-commit +9 -1
  9. package/ci-local/hooks/pre-push +13 -2
  10. package/ci-local/install.ps1 +367 -0
  11. package/ci-local/install.sh +151 -5
  12. package/ci-local/uninstall.ps1 +85 -0
  13. package/ci-local/uninstall.sh +95 -0
  14. package/dist/cli/dispatch/ci.d.ts +12 -0
  15. package/dist/cli/dispatch/ci.js +38 -0
  16. package/dist/cli/dispatch/security.d.ts +11 -0
  17. package/dist/cli/dispatch/security.js +68 -0
  18. package/dist/cli/dispatch/simple-renderers.d.ts +16 -0
  19. package/dist/cli/dispatch/simple-renderers.js +65 -0
  20. package/dist/cli/dispatch/skill-publish.d.ts +15 -0
  21. package/dist/cli/dispatch/skill-publish.js +44 -0
  22. package/dist/cli/dispatch/skills-cmd.d.ts +13 -0
  23. package/dist/cli/dispatch/skills-cmd.js +93 -0
  24. package/dist/cli/dispatch/tdd.d.ts +11 -0
  25. package/dist/cli/dispatch/tdd.js +45 -0
  26. package/dist/cli/dispatch/types.d.ts +29 -0
  27. package/dist/cli/dispatch/types.js +2 -0
  28. package/dist/cli/dispatch/workflow.d.ts +11 -0
  29. package/dist/cli/dispatch/workflow.js +67 -0
  30. package/dist/cli/help.d.ts +127 -0
  31. package/dist/cli/help.js +138 -0
  32. package/dist/cli/runtime.d.ts +16 -0
  33. package/dist/cli/runtime.js +38 -0
  34. package/dist/cli/validators.d.ts +17 -0
  35. package/dist/cli/validators.js +35 -0
  36. package/dist/commands/analyze.d.ts +1 -1
  37. package/dist/commands/analyze.js +14 -16
  38. package/dist/commands/ci.d.ts +50 -3
  39. package/dist/commands/ci.js +537 -182
  40. package/dist/commands/doctor.d.ts +2 -2
  41. package/dist/commands/doctor.js +112 -62
  42. package/dist/commands/init/report.d.ts +13 -0
  43. package/dist/commands/init/report.js +13 -0
  44. package/dist/commands/init/steps/agent-skills.d.ts +13 -0
  45. package/dist/commands/init/steps/agent-skills.js +45 -0
  46. package/dist/commands/init/steps/ai-sync.d.ts +15 -0
  47. package/dist/commands/init/steps/ai-sync.js +62 -0
  48. package/dist/commands/init/steps/ci.d.ts +24 -0
  49. package/dist/commands/init/steps/ci.js +74 -0
  50. package/dist/commands/init/steps/claude-md.d.ts +14 -0
  51. package/dist/commands/init/steps/claude-md.js +54 -0
  52. package/dist/commands/init/steps/code-graph.d.ts +16 -0
  53. package/dist/commands/init/steps/code-graph.js +68 -0
  54. package/dist/commands/init/steps/context-dir.d.ts +13 -0
  55. package/dist/commands/init/steps/context-dir.js +47 -0
  56. package/dist/commands/init/steps/docker-deploy.d.ts +16 -0
  57. package/dist/commands/init/steps/docker-deploy.js +60 -0
  58. package/dist/commands/init/steps/ghagga.d.ts +15 -0
  59. package/dist/commands/init/steps/ghagga.js +58 -0
  60. package/dist/commands/init/steps/git.d.ts +25 -0
  61. package/dist/commands/init/steps/git.js +81 -0
  62. package/dist/commands/init/steps/gitignore.d.ts +13 -0
  63. package/dist/commands/init/steps/gitignore.js +39 -0
  64. package/dist/commands/init/steps/local-ai.d.ts +14 -0
  65. package/dist/commands/init/steps/local-ai.js +53 -0
  66. package/dist/commands/init/steps/manifest.d.ts +13 -0
  67. package/dist/commands/init/steps/manifest.js +58 -0
  68. package/dist/commands/init/steps/memory.d.ts +15 -0
  69. package/dist/commands/init/steps/memory.js +60 -0
  70. package/dist/commands/init/steps/mock.d.ts +13 -0
  71. package/dist/commands/init/steps/mock.js +64 -0
  72. package/dist/commands/init/steps/sdd.d.ts +12 -0
  73. package/dist/commands/init/steps/sdd.js +40 -0
  74. package/dist/commands/init/steps/security.d.ts +28 -0
  75. package/dist/commands/init/steps/security.js +103 -0
  76. package/dist/commands/init/types.d.ts +30 -0
  77. package/dist/commands/init/types.js +2 -0
  78. package/dist/commands/init.d.ts +5 -4
  79. package/dist/commands/init.js +42 -387
  80. package/dist/commands/llmstxt.d.ts +1 -1
  81. package/dist/commands/llmstxt.js +36 -34
  82. package/dist/commands/plugin.d.ts +10 -1
  83. package/dist/commands/plugin.js +92 -47
  84. package/dist/commands/security.d.ts +11 -5
  85. package/dist/commands/security.js +236 -77
  86. package/dist/commands/skills/analysis.d.ts +19 -0
  87. package/dist/commands/skills/analysis.js +211 -0
  88. package/dist/commands/skills/benchmark.d.ts +6 -0
  89. package/dist/commands/skills/benchmark.js +82 -0
  90. package/dist/commands/skills/constants.d.ts +10 -0
  91. package/dist/commands/skills/constants.js +27 -0
  92. package/dist/commands/skills/directives.d.ts +21 -0
  93. package/dist/commands/skills/directives.js +101 -0
  94. package/dist/commands/skills/parsing.d.ts +16 -0
  95. package/dist/commands/skills/parsing.js +108 -0
  96. package/dist/commands/skills/rules.d.ts +5 -0
  97. package/dist/commands/skills/rules.js +32 -0
  98. package/dist/commands/skills/scoring.d.ts +47 -0
  99. package/dist/commands/skills/scoring.js +306 -0
  100. package/dist/commands/skills.d.ts +8 -69
  101. package/dist/commands/skills.js +9 -490
  102. package/dist/commands/tdd-pipeline.d.ts +17 -0
  103. package/dist/commands/tdd-pipeline.js +144 -0
  104. package/dist/commands/tdd.d.ts +1 -1
  105. package/dist/commands/tdd.js +21 -18
  106. package/dist/commands/workflow.d.ts +23 -0
  107. package/dist/commands/workflow.js +116 -0
  108. package/dist/constants.d.ts +15 -1
  109. package/dist/constants.js +161 -122
  110. package/dist/index.js +43 -280
  111. package/dist/lib/agent-skills.d.ts +36 -1
  112. package/dist/lib/agent-skills.js +168 -19
  113. package/dist/lib/atlassian-mcp.d.ts +42 -0
  114. package/dist/lib/atlassian-mcp.js +98 -0
  115. package/dist/lib/auto-skill-install.d.ts +37 -0
  116. package/dist/lib/auto-skill-install.js +91 -0
  117. package/dist/lib/auto-wire.d.ts +20 -0
  118. package/dist/lib/auto-wire.js +240 -0
  119. package/dist/lib/ci-config.d.ts +65 -0
  120. package/dist/lib/ci-config.js +297 -0
  121. package/dist/lib/claudemd.d.ts +13 -1
  122. package/dist/lib/claudemd.js +174 -24
  123. package/dist/lib/codex-export.d.ts +1 -1
  124. package/dist/lib/codex-export.js +26 -32
  125. package/dist/lib/common.d.ts +1 -1
  126. package/dist/lib/common.js +52 -44
  127. package/dist/lib/context.d.ts +17 -2
  128. package/dist/lib/context.js +142 -13
  129. package/dist/lib/crash-recovery.d.ts +34 -0
  130. package/dist/lib/crash-recovery.js +123 -0
  131. package/dist/lib/docker.d.ts +23 -2
  132. package/dist/lib/docker.js +168 -131
  133. package/dist/lib/exec.d.ts +10 -0
  134. package/dist/lib/exec.js +11 -0
  135. package/dist/lib/frontmatter.d.ts +1 -1
  136. package/dist/lib/frontmatter.js +29 -15
  137. package/dist/lib/harness-audit.d.ts +35 -0
  138. package/dist/lib/harness-audit.js +277 -0
  139. package/dist/lib/parallel-batch.d.ts +42 -0
  140. package/dist/lib/parallel-batch.js +88 -0
  141. package/dist/lib/plugin.d.ts +9 -3
  142. package/dist/lib/plugin.js +127 -70
  143. package/dist/lib/secret-scanner.d.ts +30 -0
  144. package/dist/lib/secret-scanner.js +274 -0
  145. package/dist/lib/security-analysis.d.ts +74 -0
  146. package/dist/lib/security-analysis.js +489 -0
  147. package/dist/lib/skill-publish.d.ts +40 -0
  148. package/dist/lib/skill-publish.js +164 -0
  149. package/dist/lib/skill-scanner.d.ts +63 -0
  150. package/dist/lib/skill-scanner.js +383 -0
  151. package/dist/lib/stack-detector.d.ts +38 -0
  152. package/dist/lib/stack-detector.js +207 -0
  153. package/dist/lib/supply-chain.d.ts +23 -0
  154. package/dist/lib/supply-chain.js +126 -0
  155. package/dist/lib/team-presets.d.ts +53 -0
  156. package/dist/lib/team-presets.js +201 -0
  157. package/dist/lib/template.d.ts +16 -1
  158. package/dist/lib/template.js +46 -17
  159. package/dist/lib/workflow/discovery.d.ts +27 -0
  160. package/dist/lib/workflow/discovery.js +85 -0
  161. package/dist/lib/workflow/index.d.ts +5 -0
  162. package/dist/lib/workflow/index.js +5 -0
  163. package/dist/lib/workflow/parser.d.ts +16 -0
  164. package/dist/lib/workflow/parser.js +198 -0
  165. package/dist/lib/workflow/renderer.d.ts +9 -0
  166. package/dist/lib/workflow/renderer.js +152 -0
  167. package/dist/lib/workflow/validator.d.ts +10 -0
  168. package/dist/lib/workflow/validator.js +189 -0
  169. package/dist/tasks/index.d.ts +4 -0
  170. package/dist/tasks/index.js +4 -0
  171. package/dist/tasks/scaffold-tasks.d.ts +3 -0
  172. package/dist/tasks/scaffold-tasks.js +14 -0
  173. package/dist/tasks/task-id.d.ts +30 -0
  174. package/dist/tasks/task-id.js +59 -0
  175. package/dist/tasks/task-tracker.d.ts +15 -0
  176. package/dist/tasks/task-tracker.js +81 -0
  177. package/dist/types/index.d.ts +134 -6
  178. package/dist/types/index.js +11 -1
  179. package/dist/ui/AnalyzeUI.d.ts +1 -1
  180. package/dist/ui/AnalyzeUI.js +38 -39
  181. package/dist/ui/App.d.ts +5 -3
  182. package/dist/ui/App.js +86 -46
  183. package/dist/ui/AutoSkills.d.ts +9 -0
  184. package/dist/ui/AutoSkills.js +124 -0
  185. package/dist/ui/CI.d.ts +2 -2
  186. package/dist/ui/CI.js +24 -26
  187. package/dist/ui/CIContext.d.ts +1 -1
  188. package/dist/ui/CIContext.js +2 -2
  189. package/dist/ui/CISelector.d.ts +2 -2
  190. package/dist/ui/CISelector.js +23 -15
  191. package/dist/ui/Doctor.d.ts +1 -1
  192. package/dist/ui/Doctor.js +35 -29
  193. package/dist/ui/Header.d.ts +1 -1
  194. package/dist/ui/Header.js +14 -14
  195. package/dist/ui/HookProfileSelector.d.ts +9 -0
  196. package/dist/ui/HookProfileSelector.js +54 -0
  197. package/dist/ui/LlmsTxt.d.ts +1 -1
  198. package/dist/ui/LlmsTxt.js +31 -22
  199. package/dist/ui/MemorySelector.d.ts +2 -2
  200. package/dist/ui/MemorySelector.js +28 -16
  201. package/dist/ui/NameInput.d.ts +1 -1
  202. package/dist/ui/NameInput.js +21 -21
  203. package/dist/ui/OptionSelector.d.ts +6 -2
  204. package/dist/ui/OptionSelector.js +83 -32
  205. package/dist/ui/Plugin.d.ts +4 -3
  206. package/dist/ui/Plugin.js +78 -35
  207. package/dist/ui/Progress.d.ts +3 -3
  208. package/dist/ui/Progress.js +23 -22
  209. package/dist/ui/Skills.d.ts +2 -2
  210. package/dist/ui/Skills.js +61 -32
  211. package/dist/ui/StackSelector.d.ts +2 -2
  212. package/dist/ui/StackSelector.js +27 -17
  213. package/dist/ui/Summary.d.ts +3 -3
  214. package/dist/ui/Summary.js +60 -50
  215. package/dist/ui/Welcome.d.ts +1 -1
  216. package/dist/ui/Welcome.js +15 -16
  217. package/dist/ui/theme.d.ts +1 -1
  218. package/dist/ui/theme.js +6 -6
  219. package/lib/common.psm1 +167 -0
  220. package/lib/common.sh +30 -0
  221. package/package.json +46 -24
  222. package/templates/common/atlassian/mcp-atlassian-snippet.json +16 -0
  223. package/templates/common/repoforge/mcp-repoforge-snippet.json +11 -0
  224. package/templates/common/repoforge/repoforge.yaml +34 -0
  225. package/templates/github/deploy-docker-zero-downtime.yml +140 -0
  226. package/templates/github/repoforge-graph.yml +45 -0
  227. package/templates/gitlab/deploy-docker-zero-downtime.yml +57 -0
  228. package/templates/local-ai/.env.example +17 -0
  229. package/templates/local-ai/docker-compose.yml +95 -0
  230. package/templates/security-hooks/claude-settings-security.json +30 -0
  231. package/templates/security-hooks/commit-msg-signing +29 -0
  232. package/templates/security-hooks/pre-commit-permissions +74 -0
  233. package/templates/security-hooks/pre-commit-secrets +74 -0
  234. package/templates/security-hooks/pre-push-branch-protection +62 -0
  235. package/templates/security-hooks/pre-push-deps +83 -0
  236. package/templates/security-hooks/pre-push-signing +67 -0
  237. package/templates/woodpecker/deploy-docker-zero-downtime.yml +50 -0
  238. package/templates/workflows/ci-pipeline.dot +15 -0
  239. package/templates/workflows/feature-flow.dot +21 -0
  240. package/templates/workflows/release.dot +16 -0
  241. package/dist/__integration__/helpers.d.ts +0 -20
  242. package/dist/__integration__/helpers.d.ts.map +0 -1
  243. package/dist/__integration__/helpers.js +0 -31
  244. package/dist/__integration__/helpers.js.map +0 -1
  245. package/dist/commands/analyze.d.ts.map +0 -1
  246. package/dist/commands/analyze.js.map +0 -1
  247. package/dist/commands/ci.d.ts.map +0 -1
  248. package/dist/commands/ci.js.map +0 -1
  249. package/dist/commands/doctor.d.ts.map +0 -1
  250. package/dist/commands/doctor.js.map +0 -1
  251. package/dist/commands/init.d.ts.map +0 -1
  252. package/dist/commands/init.js.map +0 -1
  253. package/dist/commands/llmstxt.d.ts.map +0 -1
  254. package/dist/commands/llmstxt.js.map +0 -1
  255. package/dist/commands/plugin.d.ts.map +0 -1
  256. package/dist/commands/plugin.js.map +0 -1
  257. package/dist/commands/security.d.ts.map +0 -1
  258. package/dist/commands/security.js.map +0 -1
  259. package/dist/commands/skills.d.ts.map +0 -1
  260. package/dist/commands/skills.js.map +0 -1
  261. package/dist/commands/tdd.d.ts.map +0 -1
  262. package/dist/commands/tdd.js.map +0 -1
  263. package/dist/constants.d.ts.map +0 -1
  264. package/dist/constants.js.map +0 -1
  265. package/dist/index.d.ts.map +0 -1
  266. package/dist/index.js.map +0 -1
  267. package/dist/lib/agent-skills.d.ts.map +0 -1
  268. package/dist/lib/agent-skills.js.map +0 -1
  269. package/dist/lib/claudemd.d.ts.map +0 -1
  270. package/dist/lib/claudemd.js.map +0 -1
  271. package/dist/lib/codex-export.d.ts.map +0 -1
  272. package/dist/lib/codex-export.js.map +0 -1
  273. package/dist/lib/common.d.ts.map +0 -1
  274. package/dist/lib/common.js.map +0 -1
  275. package/dist/lib/context.d.ts.map +0 -1
  276. package/dist/lib/context.js.map +0 -1
  277. package/dist/lib/docker.d.ts.map +0 -1
  278. package/dist/lib/docker.js.map +0 -1
  279. package/dist/lib/frontmatter.d.ts.map +0 -1
  280. package/dist/lib/frontmatter.js.map +0 -1
  281. package/dist/lib/plugin.d.ts.map +0 -1
  282. package/dist/lib/plugin.js.map +0 -1
  283. package/dist/lib/template.d.ts.map +0 -1
  284. package/dist/lib/template.js.map +0 -1
  285. package/dist/types/index.d.ts.map +0 -1
  286. package/dist/types/index.js.map +0 -1
  287. package/dist/ui/AnalyzeUI.d.ts.map +0 -1
  288. package/dist/ui/AnalyzeUI.js.map +0 -1
  289. package/dist/ui/App.d.ts.map +0 -1
  290. package/dist/ui/App.js.map +0 -1
  291. package/dist/ui/CI.d.ts.map +0 -1
  292. package/dist/ui/CI.js.map +0 -1
  293. package/dist/ui/CIContext.d.ts.map +0 -1
  294. package/dist/ui/CIContext.js.map +0 -1
  295. package/dist/ui/CISelector.d.ts.map +0 -1
  296. package/dist/ui/CISelector.js.map +0 -1
  297. package/dist/ui/Doctor.d.ts.map +0 -1
  298. package/dist/ui/Doctor.js.map +0 -1
  299. package/dist/ui/Header.d.ts.map +0 -1
  300. package/dist/ui/Header.js.map +0 -1
  301. package/dist/ui/LlmsTxt.d.ts.map +0 -1
  302. package/dist/ui/LlmsTxt.js.map +0 -1
  303. package/dist/ui/MemorySelector.d.ts.map +0 -1
  304. package/dist/ui/MemorySelector.js.map +0 -1
  305. package/dist/ui/NameInput.d.ts.map +0 -1
  306. package/dist/ui/NameInput.js.map +0 -1
  307. package/dist/ui/OptionSelector.d.ts.map +0 -1
  308. package/dist/ui/OptionSelector.js.map +0 -1
  309. package/dist/ui/Plugin.d.ts.map +0 -1
  310. package/dist/ui/Plugin.js.map +0 -1
  311. package/dist/ui/Progress.d.ts.map +0 -1
  312. package/dist/ui/Progress.js.map +0 -1
  313. package/dist/ui/Skills.d.ts.map +0 -1
  314. package/dist/ui/Skills.js.map +0 -1
  315. package/dist/ui/StackSelector.d.ts.map +0 -1
  316. package/dist/ui/StackSelector.js.map +0 -1
  317. package/dist/ui/Summary.d.ts.map +0 -1
  318. package/dist/ui/Summary.js.map +0 -1
  319. package/dist/ui/Welcome.d.ts.map +0 -1
  320. package/dist/ui/Welcome.js.map +0 -1
  321. package/dist/ui/theme.d.ts.map +0 -1
  322. package/dist/ui/theme.js.map +0 -1
@@ -0,0 +1,164 @@
1
+ import path from "node:path";
2
+ import fs from "fs-extra";
3
+ import { PLUGIN_MANIFEST_FILE } from "../constants.js";
4
+ import { parseFrontmatter } from "./frontmatter.js";
5
+ // ── Core ───────────────────────────────────────────────────────────────────
6
+ /**
7
+ * Package a skill directory for marketplace distribution.
8
+ *
9
+ * Reads the SKILL.md, extracts metadata from frontmatter, and generates
10
+ * a plugin.json compatible with both `javi-forge plugin install` and
11
+ * `claude plugin install` (Anthropic's plugin format).
12
+ *
13
+ * Expected input structure:
14
+ * skill-name/
15
+ * SKILL.md
16
+ * (optional other files)
17
+ *
18
+ * Output:
19
+ * skill-name/
20
+ * SKILL.md
21
+ * plugin.json ← generated
22
+ */
23
+ export async function publishSkill(options) {
24
+ const { skillDir, author, repository, tags = [], dryRun = false } = options;
25
+ // Validate skill directory
26
+ const skillMdPath = path.join(skillDir, "SKILL.md");
27
+ if (!(await fs.pathExists(skillMdPath))) {
28
+ return { success: false, error: `SKILL.md not found in ${skillDir}` };
29
+ }
30
+ // Refuse to follow a symlink. If SKILL.md is a symlink to a file outside
31
+ // the skill directory (e.g., /etc/passwd), readFile + the later fs.copy
32
+ // would expose the target's contents in the published manifest bundle.
33
+ // Mirrors the symlink-reject pattern in security.ts writeBaseline /
34
+ // ci.ts installCIHooks.
35
+ try {
36
+ const stat = await fs.lstat(skillMdPath);
37
+ if (stat.isSymbolicLink()) {
38
+ return {
39
+ success: false,
40
+ error: `SKILL.md is a symlink; refusing to publish. Use a regular file.`,
41
+ };
42
+ }
43
+ }
44
+ catch {
45
+ // pathExists already returned true above; lstat failures here are
46
+ // unexpected. Fall through to readFile which will surface the error.
47
+ }
48
+ // Read and parse SKILL.md
49
+ let raw;
50
+ try {
51
+ raw = await fs.readFile(skillMdPath, "utf-8");
52
+ }
53
+ catch {
54
+ return { success: false, error: "Failed to read SKILL.md" };
55
+ }
56
+ const parsed = parseFrontmatter(raw);
57
+ const frontmatter = parsed?.data ?? {};
58
+ // Extract metadata
59
+ const name = frontmatter.name ?? path.basename(skillDir);
60
+ const version = frontmatter.version ?? "1.0.0";
61
+ const description = frontmatter.description ?? `${name} AI skill`;
62
+ // Validate name format (kebab-case)
63
+ if (!/^[a-z0-9]+(-[a-z0-9]+)*$/.test(name)) {
64
+ return {
65
+ success: false,
66
+ error: `Skill name "${name}" must be kebab-case (e.g., "react-19", "tailwind-4")`,
67
+ };
68
+ }
69
+ // Validate description length
70
+ if (description.length < 10) {
71
+ return {
72
+ success: false,
73
+ error: "Description must be at least 10 characters",
74
+ };
75
+ }
76
+ // Build plugin.json manifest
77
+ const manifest = {
78
+ name,
79
+ version,
80
+ description: description.length > 200
81
+ ? `${description.slice(0, 197)}...`
82
+ : description,
83
+ skills: [name],
84
+ tags: tags.length > 0 ? tags : extractTagsFromDescription(description),
85
+ ...(author ? { author } : {}),
86
+ ...(repository ? { repository } : {}),
87
+ };
88
+ if (!dryRun) {
89
+ const pluginJsonPath = path.join(skillDir, PLUGIN_MANIFEST_FILE);
90
+ await fs.writeJson(pluginJsonPath, manifest, { spaces: 2 });
91
+ // Also create a skills/ subdirectory structure for plugin compatibility
92
+ const skillsSubdir = path.join(skillDir, "skills", name);
93
+ if (!(await fs.pathExists(skillsSubdir))) {
94
+ await fs.ensureDir(skillsSubdir);
95
+ // Symlink or copy SKILL.md into skills/name/
96
+ const targetSkillMd = path.join(skillsSubdir, "SKILL.md");
97
+ if (!(await fs.pathExists(targetSkillMd))) {
98
+ await fs.copy(skillMdPath, targetSkillMd);
99
+ }
100
+ }
101
+ return { success: true, pluginJsonPath, manifest };
102
+ }
103
+ return {
104
+ success: true,
105
+ pluginJsonPath: path.join(skillDir, PLUGIN_MANIFEST_FILE),
106
+ manifest,
107
+ };
108
+ }
109
+ // ── Helpers ────────────────────────────────────────────────────────────────
110
+ /**
111
+ * Extract reasonable tags from a skill description.
112
+ * Looks for common tech keywords.
113
+ */
114
+ function extractTagsFromDescription(description) {
115
+ const keywords = [
116
+ "react",
117
+ "next",
118
+ "nextjs",
119
+ "angular",
120
+ "vue",
121
+ "svelte",
122
+ "typescript",
123
+ "javascript",
124
+ "python",
125
+ "go",
126
+ "rust",
127
+ "java",
128
+ "elixir",
129
+ "tailwind",
130
+ "css",
131
+ "styling",
132
+ "testing",
133
+ "test",
134
+ "e2e",
135
+ "unit",
136
+ "api",
137
+ "rest",
138
+ "graphql",
139
+ "ai",
140
+ "llm",
141
+ "ml",
142
+ "agent",
143
+ "security",
144
+ "auth",
145
+ "database",
146
+ "sql",
147
+ "nosql",
148
+ "docker",
149
+ "kubernetes",
150
+ "devops",
151
+ "ci",
152
+ "cd",
153
+ "state",
154
+ "management",
155
+ "store",
156
+ "validation",
157
+ "schema",
158
+ "zod",
159
+ ];
160
+ const lower = description.toLowerCase();
161
+ const found = keywords.filter((kw) => lower.includes(kw));
162
+ return found.slice(0, 10); // Max 10 tags per plugin spec
163
+ }
164
+ //# sourceMappingURL=skill-publish.js.map
@@ -0,0 +1,63 @@
1
+ /**
2
+ * CI-level skill security scanner — pre-install analysis of SKILL.md files.
3
+ *
4
+ * Detects credential theft, code injection, data exfiltration, and scope escape
5
+ * patterns before skills are installed. Inspired by the SkillGuard skill
6
+ * (javi-ai) but implemented as a programmatic module with structured output.
7
+ */
8
+ import type { SecuritySeverity } from "../types/index.js";
9
+ export type SkillThreatCategory = "credential-theft" | "code-injection" | "data-exfiltration" | "scope-escape" | "privilege-escalation" | "destructive-command" | "self-modification" | "hook-tampering" | "obfuscation" | "missing-provenance" | "excessive-permissions" | "file-traversal";
10
+ export interface SkillThreat {
11
+ category: SkillThreatCategory;
12
+ severity: SecuritySeverity;
13
+ pattern: string;
14
+ line: number;
15
+ context: string;
16
+ message: string;
17
+ }
18
+ export type SkillScanVerdict = "pass" | "warn" | "block";
19
+ export interface SkillScanResult {
20
+ skillPath: string;
21
+ skillName: string;
22
+ verdict: SkillScanVerdict;
23
+ threats: SkillThreat[];
24
+ summary: SkillScanSummary;
25
+ }
26
+ export interface SkillScanSummary {
27
+ total: number;
28
+ critical: number;
29
+ high: number;
30
+ moderate: number;
31
+ low: number;
32
+ }
33
+ interface ThreatPattern {
34
+ category: SkillThreatCategory;
35
+ severity: SecuritySeverity;
36
+ pattern: RegExp;
37
+ message: string;
38
+ }
39
+ /**
40
+ * Ordered by severity (critical first). Each pattern is tested against
41
+ * every non-comment line in the skill file.
42
+ */
43
+ export declare const THREAT_PATTERNS: ThreatPattern[];
44
+ interface ProvenanceInfo {
45
+ hasAuthor: boolean;
46
+ hasVersion: boolean;
47
+ hasDescription: boolean;
48
+ }
49
+ export declare function checkProvenance(content: string): ProvenanceInfo;
50
+ export declare function scanSkillContent(content: string, _filePath: string): SkillThreat[];
51
+ export declare function computeVerdict(threats: SkillThreat[]): SkillScanVerdict;
52
+ export declare function computeScanSummary(threats: SkillThreat[]): SkillScanSummary;
53
+ export declare function extractSkillName(content: string, filePath: string): string;
54
+ export declare function scanSkillFile(filePath: string): Promise<SkillScanResult>;
55
+ /**
56
+ * Scan all SKILL.md files in a directory (recursive).
57
+ * Useful for scanning a plugin's skills directory before installation.
58
+ */
59
+ export declare function scanSkillsDirectory(dir: string): Promise<SkillScanResult[]>;
60
+ export declare function formatScanReport(result: SkillScanResult): string;
61
+ export declare function formatBatchReport(results: SkillScanResult[]): string;
62
+ export {};
63
+ //# sourceMappingURL=skill-scanner.d.ts.map
@@ -0,0 +1,383 @@
1
+ /**
2
+ * CI-level skill security scanner — pre-install analysis of SKILL.md files.
3
+ *
4
+ * Detects credential theft, code injection, data exfiltration, and scope escape
5
+ * patterns before skills are installed. Inspired by the SkillGuard skill
6
+ * (javi-ai) but implemented as a programmatic module with structured output.
7
+ */
8
+ import path from "node:path";
9
+ import fs from "fs-extra";
10
+ /**
11
+ * Ordered by severity (critical first). Each pattern is tested against
12
+ * every non-comment line in the skill file.
13
+ */
14
+ export const THREAT_PATTERNS = [
15
+ // ── Critical: Credential Theft ──
16
+ {
17
+ category: "credential-theft",
18
+ severity: "critical",
19
+ pattern: /(?:~\/\.ssh|~\/\.aws|~\/\.config\/gh|~\/\.gnupg|~\/\.netrc|~\/\.npmrc|\/etc\/shadow|\/etc\/passwd|id_rsa|id_ed25519)/i,
20
+ message: "References sensitive credential paths — potential data theft",
21
+ },
22
+ {
23
+ category: "credential-theft",
24
+ severity: "critical",
25
+ pattern: /(?:AWS_SECRET_ACCESS_KEY|GITHUB_TOKEN|NPM_TOKEN|PRIVATE_KEY|API_SECRET|DATABASE_URL|MONGO_URI|REDIS_URL)\s*[=:]/i,
26
+ message: "References environment variable containing secrets — potential exfiltration",
27
+ },
28
+ {
29
+ category: "credential-theft",
30
+ severity: "critical",
31
+ pattern: /(?:read|cat|type|get-content|less|more|head|tail)\s+.*(?:\.env|credentials|secrets?\.(json|yaml|yml|toml))/i,
32
+ message: "Reads secret/credential files directly — potential credential theft",
33
+ },
34
+ // ── Critical: Code Injection ──
35
+ {
36
+ category: "code-injection",
37
+ severity: "critical",
38
+ pattern: /\beval\s*\(\s*(?:user|input|req|params|args|data|body)/i,
39
+ message: "eval() with user-controlled input — enables arbitrary code execution",
40
+ },
41
+ {
42
+ category: "code-injection",
43
+ severity: "critical",
44
+ pattern: /\b(?:exec|execSync|spawn|spawnSync)\s*\(\s*(?:user|input|req|params|args|data)/i,
45
+ message: "Process execution with user input — enables command injection",
46
+ },
47
+ {
48
+ category: "code-injection",
49
+ severity: "critical",
50
+ pattern: /\b(?:subprocess\.(?:call|run|Popen)|os\.system|os\.popen)\s*\(\s*(?:f['"]|user|input|req)/i,
51
+ message: "Python subprocess with user input — enables command injection",
52
+ },
53
+ // ── Critical: Data Exfiltration ──
54
+ {
55
+ category: "data-exfiltration",
56
+ severity: "critical",
57
+ pattern: /(?:curl|wget|fetch|axios|got|request)\s+.*(?:--data|--upload|-d\s|-F\s|\.post\(|\.put\()\s*.*(?:\/etc\/|~\/\.|\.env|secret|credential|token|key)/i,
58
+ message: "Sending sensitive data to external endpoint — data exfiltration",
59
+ },
60
+ {
61
+ category: "data-exfiltration",
62
+ severity: "high",
63
+ pattern: /(?:curl|wget)\s+(?:-[sSfLkO]*\s+)*(?:https?:\/\/)?(?!localhost|127\.0\.0\.1|0\.0\.0\.0|::1)[\w.-]+\.\w{2,}/i,
64
+ message: "Outbound HTTP request to external URL — verify the destination is trusted",
65
+ },
66
+ {
67
+ category: "data-exfiltration",
68
+ severity: "high",
69
+ pattern: /fetch\s*\(\s*['"`]https?:\/\/(?!localhost|127\.0\.0\.1)/i,
70
+ message: "fetch() to external URL — verify the destination is trusted",
71
+ },
72
+ // ── Critical: Scope Escape ──
73
+ {
74
+ category: "scope-escape",
75
+ severity: "critical",
76
+ pattern: /(?:ignore\s+(?:all\s+)?previous|disregard\s+(?:all\s+)?(?:prior|above)|override\s+(?:safety|security|rules)|bypass\s+(?:safety|security|restrictions))/i,
77
+ message: "Prompt injection attempt — tries to override safety instructions",
78
+ },
79
+ {
80
+ category: "scope-escape",
81
+ severity: "critical",
82
+ pattern: /(?:you\s+are\s+now|from\s+now\s+on|new\s+instructions?:?\s+)/i,
83
+ message: "Attempts to redefine agent identity — prompt injection risk",
84
+ },
85
+ // ── Critical: Self-Modification ──
86
+ {
87
+ category: "self-modification",
88
+ severity: "critical",
89
+ pattern: /(?:write|append|modify|edit|overwrite|patch)\s+.*(?:CLAUDE\.md|AGENTS\.md|settings\.json|\.claude\/)/i,
90
+ message: "Attempts to modify agent config files — persistence/privilege escalation",
91
+ },
92
+ {
93
+ category: "hook-tampering",
94
+ severity: "critical",
95
+ pattern: /(?:rm|remove|delete|disable)\s+.*(?:pre-commit|pre-push|commit-msg|\.git\/hooks)/i,
96
+ message: "Attempts to disable or remove git hooks — bypasses safety guardrails",
97
+ },
98
+ // ── High: Privilege Escalation ──
99
+ {
100
+ category: "privilege-escalation",
101
+ severity: "high",
102
+ pattern: /\bsudo\s+/i,
103
+ message: "Uses sudo — may escalate to root privileges",
104
+ },
105
+ {
106
+ category: "privilege-escalation",
107
+ severity: "high",
108
+ pattern: /chmod\s+(?:777|666|a\+[rwx])/i,
109
+ message: "Sets overly permissive file permissions — security risk",
110
+ },
111
+ {
112
+ category: "privilege-escalation",
113
+ severity: "high",
114
+ pattern: /chown\s+root/i,
115
+ message: "Changes file ownership to root — privilege escalation",
116
+ },
117
+ // ── High: Destructive Commands ──
118
+ {
119
+ category: "destructive-command",
120
+ severity: "high",
121
+ pattern: /\brm\s+-rf?\s+(?:\/|~|\$HOME|\.\.)/i,
122
+ message: "Destructive file deletion targeting root, home, or parent directories",
123
+ },
124
+ {
125
+ category: "destructive-command",
126
+ severity: "high",
127
+ pattern: /git\s+push\s+--force\b/i,
128
+ message: "Force push can destroy remote history",
129
+ },
130
+ {
131
+ category: "destructive-command",
132
+ severity: "high",
133
+ pattern: /DROP\s+(?:TABLE|DATABASE|INDEX)/i,
134
+ message: "SQL DROP statement — potential data loss",
135
+ },
136
+ // ── High: File Traversal ──
137
+ {
138
+ category: "file-traversal",
139
+ severity: "high",
140
+ pattern: /(?:\.\.\/){2,}/,
141
+ message: "Multiple path traversal sequences — may access files outside project",
142
+ },
143
+ {
144
+ category: "file-traversal",
145
+ severity: "high",
146
+ pattern: /(?:readFile|writeFile|open|fs\.)\s*\(\s*['"`]\/(?:etc|usr|var|tmp|root|home)\//i,
147
+ message: "Absolute path to system directory — scope escape risk",
148
+ },
149
+ // ── Moderate: Obfuscation ──
150
+ {
151
+ category: "obfuscation",
152
+ severity: "moderate",
153
+ pattern: /(?:atob|btoa|Buffer\.from)\s*\(\s*['"`][A-Za-z0-9+/]{40,}/,
154
+ message: "Base64 encoded content — may hide malicious payloads",
155
+ },
156
+ {
157
+ category: "obfuscation",
158
+ severity: "moderate",
159
+ pattern: /\\x[0-9a-fA-F]{2}(?:\\x[0-9a-fA-F]{2}){4,}/,
160
+ message: "Hex-encoded string sequence — may hide malicious payloads",
161
+ },
162
+ // ── Moderate: Excessive Permissions ──
163
+ {
164
+ category: "excessive-permissions",
165
+ severity: "moderate",
166
+ pattern: /allowed-tools:\s*.*(?:Bash|Edit|Write|Read|Glob|Grep|WebFetch|WebSearch).*(?:Bash|Edit|Write|Read|Glob|Grep|WebFetch|WebSearch).*(?:Bash|Edit|Write|Read|Glob|Grep|WebFetch|WebSearch).*(?:Bash|Edit|Write|Read|Glob|Grep|WebFetch|WebSearch)/i,
167
+ message: "Requests many tools — verify skill actually needs all of them",
168
+ },
169
+ ];
170
+ export function checkProvenance(content) {
171
+ // Check YAML frontmatter
172
+ const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---/);
173
+ if (!frontmatterMatch) {
174
+ return { hasAuthor: false, hasVersion: false, hasDescription: false };
175
+ }
176
+ const fm = frontmatterMatch[1];
177
+ return {
178
+ hasAuthor: /\bauthor\s*:/i.test(fm),
179
+ hasVersion: /\bversion\s*:/i.test(fm),
180
+ hasDescription: /\bdescription\s*:/i.test(fm),
181
+ };
182
+ }
183
+ // =============================================================================
184
+ // Core scanner
185
+ // =============================================================================
186
+ export function scanSkillContent(content, _filePath) {
187
+ const threats = [];
188
+ const lines = content.split("\n");
189
+ for (let i = 0; i < lines.length; i++) {
190
+ const line = lines[i];
191
+ const trimmed = line.trim();
192
+ // Skip empty lines
193
+ if (!trimmed)
194
+ continue;
195
+ for (const tp of THREAT_PATTERNS) {
196
+ if (tp.pattern.test(trimmed)) {
197
+ threats.push({
198
+ category: tp.category,
199
+ severity: tp.severity,
200
+ pattern: tp.pattern.source.slice(0, 80),
201
+ line: i + 1,
202
+ context: trimmed.slice(0, 120),
203
+ message: tp.message,
204
+ });
205
+ }
206
+ }
207
+ }
208
+ // Check provenance
209
+ const prov = checkProvenance(content);
210
+ if (!prov.hasAuthor) {
211
+ threats.push({
212
+ category: "missing-provenance",
213
+ severity: "moderate",
214
+ pattern: "missing author",
215
+ line: 1,
216
+ context: "YAML frontmatter",
217
+ message: "No author metadata — skill origin is unknown",
218
+ });
219
+ }
220
+ if (!prov.hasVersion) {
221
+ threats.push({
222
+ category: "missing-provenance",
223
+ severity: "moderate",
224
+ pattern: "missing version",
225
+ line: 1,
226
+ context: "YAML frontmatter",
227
+ message: "No version metadata — cannot track updates",
228
+ });
229
+ }
230
+ return threats;
231
+ }
232
+ // =============================================================================
233
+ // Verdict computation
234
+ // =============================================================================
235
+ export function computeVerdict(threats) {
236
+ if (threats.some((t) => t.severity === "critical"))
237
+ return "block";
238
+ if (threats.some((t) => t.severity === "high"))
239
+ return "warn";
240
+ return "pass";
241
+ }
242
+ export function computeScanSummary(threats) {
243
+ const summary = {
244
+ total: threats.length,
245
+ critical: 0,
246
+ high: 0,
247
+ moderate: 0,
248
+ low: 0,
249
+ };
250
+ for (const t of threats) {
251
+ switch (t.severity) {
252
+ case "critical":
253
+ summary.critical++;
254
+ break;
255
+ case "high":
256
+ summary.high++;
257
+ break;
258
+ case "moderate":
259
+ summary.moderate++;
260
+ break;
261
+ case "low":
262
+ summary.low++;
263
+ break;
264
+ }
265
+ }
266
+ return summary;
267
+ }
268
+ // =============================================================================
269
+ // Skill name extraction
270
+ // =============================================================================
271
+ export function extractSkillName(content, filePath) {
272
+ // Try frontmatter name
273
+ const fmMatch = content.match(/^---\n[\s\S]*?\bname:\s*(.+)/m);
274
+ if (fmMatch?.[1])
275
+ return fmMatch[1].trim();
276
+ // Fall back to directory name
277
+ const dirName = path.basename(path.dirname(filePath));
278
+ if (dirName && dirName !== ".")
279
+ return dirName;
280
+ return path.basename(filePath, path.extname(filePath));
281
+ }
282
+ // =============================================================================
283
+ // Main scan function
284
+ // =============================================================================
285
+ export async function scanSkillFile(filePath) {
286
+ const content = await fs.readFile(filePath, "utf-8");
287
+ const skillName = extractSkillName(content, filePath);
288
+ const threats = scanSkillContent(content, filePath);
289
+ const verdict = computeVerdict(threats);
290
+ const summary = computeScanSummary(threats);
291
+ return {
292
+ skillPath: filePath,
293
+ skillName,
294
+ verdict,
295
+ threats,
296
+ summary,
297
+ };
298
+ }
299
+ /**
300
+ * Scan all SKILL.md files in a directory (recursive).
301
+ * Useful for scanning a plugin's skills directory before installation.
302
+ */
303
+ export async function scanSkillsDirectory(dir) {
304
+ const results = [];
305
+ async function walk(currentDir) {
306
+ let entries;
307
+ try {
308
+ entries = await fs.readdir(currentDir);
309
+ }
310
+ catch {
311
+ return;
312
+ }
313
+ for (const entry of entries) {
314
+ if (entry === "node_modules" || entry === ".git")
315
+ continue;
316
+ const fullPath = path.join(currentDir, entry);
317
+ let stat;
318
+ try {
319
+ stat = await fs.stat(fullPath);
320
+ }
321
+ catch {
322
+ continue;
323
+ }
324
+ if (stat.isDirectory()) {
325
+ await walk(fullPath);
326
+ }
327
+ else if (entry === "SKILL.md" ||
328
+ entry === "PLUGIN.md" ||
329
+ entry.toLowerCase() === "skill.md") {
330
+ const result = await scanSkillFile(fullPath);
331
+ results.push(result);
332
+ }
333
+ }
334
+ }
335
+ await walk(dir);
336
+ return results;
337
+ }
338
+ // =============================================================================
339
+ // Report formatting
340
+ // =============================================================================
341
+ export function formatScanReport(result) {
342
+ const lines = [];
343
+ const { summary, threats, verdict } = result;
344
+ lines.push(`=== SkillGuard Scan: ${result.skillName} ===`);
345
+ lines.push(`Path: ${result.skillPath}`);
346
+ lines.push(`Verdict: ${verdict.toUpperCase()}`);
347
+ lines.push(`Findings: ${summary.total} (${summary.critical} critical, ${summary.high} high, ${summary.moderate} moderate, ${summary.low} low)`);
348
+ lines.push("");
349
+ if (threats.length > 0) {
350
+ lines.push("--- Threats ---");
351
+ for (const t of threats) {
352
+ lines.push(`[${t.severity.toUpperCase()}] ${t.category} (line ${t.line})`);
353
+ lines.push(` ${t.message}`);
354
+ lines.push(` Context: ${t.context}`);
355
+ }
356
+ }
357
+ if (verdict === "block") {
358
+ lines.push("");
359
+ lines.push("BLOCKED: Critical threats detected. Review and remove before installing.");
360
+ }
361
+ else if (verdict === "warn") {
362
+ lines.push("");
363
+ lines.push("WARNING: High-severity threats detected. Confirm you trust this skill.");
364
+ }
365
+ return lines.join("\n");
366
+ }
367
+ export function formatBatchReport(results) {
368
+ const lines = [];
369
+ const blocked = results.filter((r) => r.verdict === "block");
370
+ const warned = results.filter((r) => r.verdict === "warn");
371
+ const passed = results.filter((r) => r.verdict === "pass");
372
+ lines.push(`=== SkillGuard Batch Scan ===`);
373
+ lines.push(`Scanned: ${results.length} skill(s)`);
374
+ lines.push(`Blocked: ${blocked.length}`);
375
+ lines.push(`Warned: ${warned.length}`);
376
+ lines.push(`Passed: ${passed.length}`);
377
+ lines.push("");
378
+ for (const result of results) {
379
+ lines.push(`[${result.verdict.toUpperCase()}] ${result.skillName} (${result.summary.total} finding(s))`);
380
+ }
381
+ return lines.join("\n");
382
+ }
383
+ //# sourceMappingURL=skill-scanner.js.map
@@ -0,0 +1,38 @@
1
+ import type { Stack } from "../types/index.js";
2
+ /**
3
+ * Maps detected project signals to recommended AI skills.
4
+ * Each key is a detection signal (file/dependency/pattern),
5
+ * and the value is the list of skills it implies.
6
+ */
7
+ export declare const SIGNAL_SKILL_MAP: Record<string, string[]>;
8
+ /**
9
+ * Docker-related files that indicate a containerized project.
10
+ * Used by the init command to suggest Docker zero-downtime deploy scaffolding.
11
+ */
12
+ export declare const DOCKER_FILES: string[];
13
+ /**
14
+ * Detect whether the project uses Docker (Dockerfile or compose file present).
15
+ */
16
+ export declare function detectDockerPresence(projectDir: string): Promise<boolean>;
17
+ export interface StackDetectionResult {
18
+ /** Primary stack type (node, python, etc.) */
19
+ stack: Stack | null;
20
+ /** All detected signals with their source */
21
+ signals: DetectedSignal[];
22
+ /** De-duplicated list of recommended skill names */
23
+ recommendedSkills: string[];
24
+ }
25
+ export interface DetectedSignal {
26
+ /** What was detected (e.g., "react", "tailwindcss", "tsconfig.json") */
27
+ signal: string;
28
+ /** Where it was found (e.g., "package.json dependencies", "file exists") */
29
+ source: string;
30
+ /** Skills this signal maps to */
31
+ skills: string[];
32
+ }
33
+ /**
34
+ * Scan a project directory and detect its tech stack + recommended skills.
35
+ * Reads package.json deps, pyproject.toml, and well-known config files.
36
+ */
37
+ export declare function detectProjectStack(projectDir: string): Promise<StackDetectionResult>;
38
+ //# sourceMappingURL=stack-detector.d.ts.map