javi-forge 1.6.0 → 1.6.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 (231) hide show
  1. package/dist/commands/analyze.d.ts +1 -1
  2. package/dist/commands/analyze.js +15 -15
  3. package/dist/commands/atlassian-mcp.d.ts +42 -0
  4. package/dist/commands/atlassian-mcp.js +98 -0
  5. package/dist/commands/ci.d.ts +3 -3
  6. package/dist/commands/ci.js +185 -147
  7. package/dist/commands/crash-recovery.d.ts +34 -0
  8. package/dist/commands/crash-recovery.js +123 -0
  9. package/dist/commands/doctor.d.ts +2 -2
  10. package/dist/commands/doctor.js +113 -61
  11. package/dist/commands/harness-audit.d.ts +35 -0
  12. package/dist/commands/harness-audit.js +277 -0
  13. package/dist/commands/init.d.ts +1 -1
  14. package/dist/commands/init.js +384 -141
  15. package/dist/commands/llmstxt.d.ts +1 -1
  16. package/dist/commands/llmstxt.js +36 -34
  17. package/dist/commands/parallel-batch.d.ts +42 -0
  18. package/dist/commands/parallel-batch.js +90 -0
  19. package/dist/commands/plugin.d.ts +10 -1
  20. package/dist/commands/plugin.js +92 -47
  21. package/dist/commands/secret-scanner.d.ts +30 -0
  22. package/dist/commands/secret-scanner.js +272 -0
  23. package/dist/commands/security-analysis.d.ts +74 -0
  24. package/dist/commands/security-analysis.js +487 -0
  25. package/dist/commands/security.d.ts +11 -5
  26. package/dist/commands/security.js +216 -76
  27. package/dist/commands/skill-scanner.d.ts +63 -0
  28. package/dist/commands/skill-scanner.js +383 -0
  29. package/dist/commands/skills.d.ts +62 -5
  30. package/dist/commands/skills.js +439 -54
  31. package/dist/commands/supply-chain.d.ts +23 -0
  32. package/dist/commands/supply-chain.js +126 -0
  33. package/dist/commands/tdd-pipeline.d.ts +17 -0
  34. package/dist/commands/tdd-pipeline.js +144 -0
  35. package/dist/commands/tdd.d.ts +1 -1
  36. package/dist/commands/tdd.js +21 -18
  37. package/dist/commands/team-presets.d.ts +53 -0
  38. package/dist/commands/team-presets.js +201 -0
  39. package/dist/commands/workflow.d.ts +23 -0
  40. package/dist/commands/workflow.js +114 -0
  41. package/dist/constants.d.ts +15 -1
  42. package/dist/constants.js +161 -122
  43. package/dist/index.js +308 -98
  44. package/dist/lib/agent-skills.d.ts +36 -1
  45. package/dist/lib/agent-skills.js +168 -19
  46. package/dist/lib/auto-skill-install.d.ts +37 -0
  47. package/dist/lib/auto-skill-install.js +92 -0
  48. package/dist/lib/auto-wire.d.ts +20 -0
  49. package/dist/lib/auto-wire.js +240 -0
  50. package/dist/lib/claudemd.d.ts +13 -1
  51. package/dist/lib/claudemd.js +174 -24
  52. package/dist/lib/codex-export.d.ts +1 -1
  53. package/dist/lib/codex-export.js +29 -31
  54. package/dist/lib/common.d.ts +1 -1
  55. package/dist/lib/common.js +52 -44
  56. package/dist/lib/context.d.ts +17 -2
  57. package/dist/lib/context.js +142 -13
  58. package/dist/lib/docker.d.ts +1 -1
  59. package/dist/lib/docker.js +141 -112
  60. package/dist/lib/frontmatter.d.ts +1 -1
  61. package/dist/lib/frontmatter.js +29 -15
  62. package/dist/lib/plugin.d.ts +9 -3
  63. package/dist/lib/plugin.js +128 -69
  64. package/dist/lib/skill-publish.d.ts +40 -0
  65. package/dist/lib/skill-publish.js +146 -0
  66. package/dist/lib/stack-detector.d.ts +38 -0
  67. package/dist/lib/stack-detector.js +207 -0
  68. package/dist/lib/template.d.ts +16 -1
  69. package/dist/lib/template.js +46 -17
  70. package/dist/lib/workflow/discovery.d.ts +19 -0
  71. package/dist/lib/workflow/discovery.js +68 -0
  72. package/dist/lib/workflow/index.d.ts +5 -0
  73. package/dist/lib/workflow/index.js +5 -0
  74. package/dist/lib/workflow/parser.d.ts +16 -0
  75. package/dist/lib/workflow/parser.js +198 -0
  76. package/dist/lib/workflow/renderer.d.ts +9 -0
  77. package/dist/lib/workflow/renderer.js +152 -0
  78. package/dist/lib/workflow/validator.d.ts +10 -0
  79. package/dist/lib/workflow/validator.js +189 -0
  80. package/dist/tasks/index.d.ts +4 -0
  81. package/dist/tasks/index.js +4 -0
  82. package/dist/tasks/scaffold-tasks.d.ts +3 -0
  83. package/dist/tasks/scaffold-tasks.js +14 -0
  84. package/dist/tasks/task-id.d.ts +30 -0
  85. package/dist/tasks/task-id.js +55 -0
  86. package/dist/tasks/task-tracker.d.ts +15 -0
  87. package/dist/tasks/task-tracker.js +81 -0
  88. package/dist/types/index.d.ts +134 -6
  89. package/dist/types/index.js +11 -1
  90. package/dist/ui/AnalyzeUI.d.ts +1 -1
  91. package/dist/ui/AnalyzeUI.js +38 -39
  92. package/dist/ui/App.d.ts +5 -3
  93. package/dist/ui/App.js +86 -46
  94. package/dist/ui/AutoSkills.d.ts +9 -0
  95. package/dist/ui/AutoSkills.js +124 -0
  96. package/dist/ui/CI.d.ts +2 -2
  97. package/dist/ui/CI.js +24 -26
  98. package/dist/ui/CIContext.d.ts +1 -1
  99. package/dist/ui/CIContext.js +3 -2
  100. package/dist/ui/CISelector.d.ts +2 -2
  101. package/dist/ui/CISelector.js +23 -15
  102. package/dist/ui/Doctor.d.ts +1 -1
  103. package/dist/ui/Doctor.js +35 -29
  104. package/dist/ui/Header.d.ts +1 -1
  105. package/dist/ui/Header.js +14 -14
  106. package/dist/ui/HookProfileSelector.d.ts +9 -0
  107. package/dist/ui/HookProfileSelector.js +54 -0
  108. package/dist/ui/LlmsTxt.d.ts +1 -1
  109. package/dist/ui/LlmsTxt.js +31 -22
  110. package/dist/ui/MemorySelector.d.ts +2 -2
  111. package/dist/ui/MemorySelector.js +28 -16
  112. package/dist/ui/NameInput.d.ts +1 -1
  113. package/dist/ui/NameInput.js +21 -21
  114. package/dist/ui/OptionSelector.d.ts +6 -2
  115. package/dist/ui/OptionSelector.js +83 -32
  116. package/dist/ui/Plugin.d.ts +4 -3
  117. package/dist/ui/Plugin.js +78 -35
  118. package/dist/ui/Progress.d.ts +3 -3
  119. package/dist/ui/Progress.js +23 -22
  120. package/dist/ui/Skills.d.ts +2 -2
  121. package/dist/ui/Skills.js +61 -32
  122. package/dist/ui/StackSelector.d.ts +2 -2
  123. package/dist/ui/StackSelector.js +26 -16
  124. package/dist/ui/Summary.d.ts +3 -3
  125. package/dist/ui/Summary.js +60 -50
  126. package/dist/ui/Welcome.d.ts +1 -1
  127. package/dist/ui/Welcome.js +15 -16
  128. package/dist/ui/theme.d.ts +1 -1
  129. package/dist/ui/theme.js +6 -6
  130. package/package.json +9 -6
  131. package/templates/common/atlassian/mcp-atlassian-snippet.json +16 -0
  132. package/templates/common/repoforge/mcp-repoforge-snippet.json +11 -0
  133. package/templates/common/repoforge/repoforge.yaml +34 -0
  134. package/templates/github/deploy-docker-zero-downtime.yml +140 -0
  135. package/templates/github/repoforge-graph.yml +45 -0
  136. package/templates/gitlab/deploy-docker-zero-downtime.yml +57 -0
  137. package/templates/local-ai/.env.example +17 -0
  138. package/templates/local-ai/docker-compose.yml +95 -0
  139. package/templates/security-hooks/claude-settings-security.json +30 -0
  140. package/templates/security-hooks/commit-msg-signing +29 -0
  141. package/templates/security-hooks/pre-commit-permissions +74 -0
  142. package/templates/security-hooks/pre-commit-secrets +74 -0
  143. package/templates/security-hooks/pre-push-branch-protection +62 -0
  144. package/templates/security-hooks/pre-push-deps +83 -0
  145. package/templates/security-hooks/pre-push-signing +67 -0
  146. package/templates/woodpecker/deploy-docker-zero-downtime.yml +50 -0
  147. package/templates/workflows/ci-pipeline.dot +15 -0
  148. package/templates/workflows/feature-flow.dot +21 -0
  149. package/templates/workflows/release.dot +16 -0
  150. package/dist/__integration__/helpers.d.ts +0 -20
  151. package/dist/__integration__/helpers.d.ts.map +0 -1
  152. package/dist/__integration__/helpers.js +0 -31
  153. package/dist/__integration__/helpers.js.map +0 -1
  154. package/dist/commands/analyze.d.ts.map +0 -1
  155. package/dist/commands/analyze.js.map +0 -1
  156. package/dist/commands/ci.d.ts.map +0 -1
  157. package/dist/commands/ci.js.map +0 -1
  158. package/dist/commands/doctor.d.ts.map +0 -1
  159. package/dist/commands/doctor.js.map +0 -1
  160. package/dist/commands/init.d.ts.map +0 -1
  161. package/dist/commands/init.js.map +0 -1
  162. package/dist/commands/llmstxt.d.ts.map +0 -1
  163. package/dist/commands/llmstxt.js.map +0 -1
  164. package/dist/commands/plugin.d.ts.map +0 -1
  165. package/dist/commands/plugin.js.map +0 -1
  166. package/dist/commands/security.d.ts.map +0 -1
  167. package/dist/commands/security.js.map +0 -1
  168. package/dist/commands/skills.d.ts.map +0 -1
  169. package/dist/commands/skills.js.map +0 -1
  170. package/dist/commands/tdd.d.ts.map +0 -1
  171. package/dist/commands/tdd.js.map +0 -1
  172. package/dist/constants.d.ts.map +0 -1
  173. package/dist/constants.js.map +0 -1
  174. package/dist/index.d.ts.map +0 -1
  175. package/dist/index.js.map +0 -1
  176. package/dist/lib/agent-skills.d.ts.map +0 -1
  177. package/dist/lib/agent-skills.js.map +0 -1
  178. package/dist/lib/claudemd.d.ts.map +0 -1
  179. package/dist/lib/claudemd.js.map +0 -1
  180. package/dist/lib/codex-export.d.ts.map +0 -1
  181. package/dist/lib/codex-export.js.map +0 -1
  182. package/dist/lib/common.d.ts.map +0 -1
  183. package/dist/lib/common.js.map +0 -1
  184. package/dist/lib/context.d.ts.map +0 -1
  185. package/dist/lib/context.js.map +0 -1
  186. package/dist/lib/docker.d.ts.map +0 -1
  187. package/dist/lib/docker.js.map +0 -1
  188. package/dist/lib/frontmatter.d.ts.map +0 -1
  189. package/dist/lib/frontmatter.js.map +0 -1
  190. package/dist/lib/plugin.d.ts.map +0 -1
  191. package/dist/lib/plugin.js.map +0 -1
  192. package/dist/lib/template.d.ts.map +0 -1
  193. package/dist/lib/template.js.map +0 -1
  194. package/dist/types/index.d.ts.map +0 -1
  195. package/dist/types/index.js.map +0 -1
  196. package/dist/ui/AnalyzeUI.d.ts.map +0 -1
  197. package/dist/ui/AnalyzeUI.js.map +0 -1
  198. package/dist/ui/App.d.ts.map +0 -1
  199. package/dist/ui/App.js.map +0 -1
  200. package/dist/ui/CI.d.ts.map +0 -1
  201. package/dist/ui/CI.js.map +0 -1
  202. package/dist/ui/CIContext.d.ts.map +0 -1
  203. package/dist/ui/CIContext.js.map +0 -1
  204. package/dist/ui/CISelector.d.ts.map +0 -1
  205. package/dist/ui/CISelector.js.map +0 -1
  206. package/dist/ui/Doctor.d.ts.map +0 -1
  207. package/dist/ui/Doctor.js.map +0 -1
  208. package/dist/ui/Header.d.ts.map +0 -1
  209. package/dist/ui/Header.js.map +0 -1
  210. package/dist/ui/LlmsTxt.d.ts.map +0 -1
  211. package/dist/ui/LlmsTxt.js.map +0 -1
  212. package/dist/ui/MemorySelector.d.ts.map +0 -1
  213. package/dist/ui/MemorySelector.js.map +0 -1
  214. package/dist/ui/NameInput.d.ts.map +0 -1
  215. package/dist/ui/NameInput.js.map +0 -1
  216. package/dist/ui/OptionSelector.d.ts.map +0 -1
  217. package/dist/ui/OptionSelector.js.map +0 -1
  218. package/dist/ui/Plugin.d.ts.map +0 -1
  219. package/dist/ui/Plugin.js.map +0 -1
  220. package/dist/ui/Progress.d.ts.map +0 -1
  221. package/dist/ui/Progress.js.map +0 -1
  222. package/dist/ui/Skills.d.ts.map +0 -1
  223. package/dist/ui/Skills.js.map +0 -1
  224. package/dist/ui/StackSelector.d.ts.map +0 -1
  225. package/dist/ui/StackSelector.js.map +0 -1
  226. package/dist/ui/Summary.d.ts.map +0 -1
  227. package/dist/ui/Summary.js.map +0 -1
  228. package/dist/ui/Welcome.d.ts.map +0 -1
  229. package/dist/ui/Welcome.js.map +0 -1
  230. package/dist/ui/theme.d.ts.map +0 -1
  231. package/dist/ui/theme.js.map +0 -1
@@ -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 fs from "fs-extra";
9
+ import path from "path";
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
@@ -1,4 +1,30 @@
1
- import type { SkillConflict, SkillBudgetResult, SkillDuplicate, SkillDoctorResult, SkillScore, SkillBenchmarkResult } from '../types/index.js';
1
+ import type { ConflictKind, SkillBenchmarkResult, SkillBudgetEntry, SkillBudgetResult, SkillBudgetSuggestion, SkillConflict, SkillDoctorResult, SkillDuplicate, SkillGrade, SkillRegistryGateResult, SkillScore } from "../types/index.js";
2
+ /** A directive is a sentiment + subject extracted from a rule */
3
+ export interface RuleDirective {
4
+ sentiment: "positive" | "negative";
5
+ subject: string;
6
+ }
7
+ /**
8
+ * Extract a directive (sentiment + subject) from a rule string.
9
+ * Returns null if the rule has no clear directive.
10
+ */
11
+ export declare function extractDirective(rule: string): RuleDirective | null;
12
+ /**
13
+ * Check if two subjects are similar enough to be "about the same thing".
14
+ * Uses simple word-overlap (Jaccard-like) — no external NLP needed.
15
+ */
16
+ export declare function subjectsSimilar(a: string, b: string, threshold?: number): boolean;
17
+ /**
18
+ * Detect a directive clash between two rules:
19
+ * opposite sentiments about the same subject.
20
+ */
21
+ export declare function detectDirectiveClash(ruleA: string, ruleB: string): string | null;
22
+ /**
23
+ * Generate minimal disable sets to bring total tokens under budget.
24
+ * Uses a greedy approach: disable largest skills first until under budget.
25
+ * Returns up to 3 alternative optimization suggestions.
26
+ */
27
+ export declare function generateBudgetOptimizations(entries: SkillBudgetEntry[], totalTokens: number, budget: number): SkillBudgetSuggestion[];
2
28
  /** Estimate token count from a string */
3
29
  export declare function estimateTokens(text: string): number;
4
30
  /** Read a SKILL.md and extract its name + critical rules section */
@@ -14,15 +40,18 @@ export declare function extractCriticalRules(content: string): string[];
14
40
  export declare function extractTriggers(description: string): string[];
15
41
  /** Discover all SKILL.md files in a skills directory */
16
42
  export declare function discoverSkills(skillsDir: string): Promise<string[]>;
17
- /** Check if two rules contradict each other */
18
- export declare function detectRuleConflict(ruleA: string, ruleB: string): string | null;
43
+ /** Check if two rules contradict each other (regex pairs + directive clash) */
44
+ export declare function detectRuleConflict(ruleA: string, ruleB: string): {
45
+ reason: string;
46
+ kind: ConflictKind;
47
+ } | null;
19
48
  /** Scan all skills for conflicting critical rules */
20
49
  export declare function findConflicts(skillsDir: string): Promise<SkillConflict[]>;
21
50
  /** Calculate token budget for all installed skills */
22
51
  export declare function calculateBudget(skillsDir: string, budget?: number): Promise<SkillBudgetResult>;
23
52
  /** Find skills that overlap in scope/triggers */
24
53
  export declare function findDuplicates(skillsDir: string): Promise<SkillDuplicate[]>;
25
- export type SkillsDoctorMode = 'doctor' | 'budget';
54
+ export type SkillsDoctorMode = "doctor" | "budget";
26
55
  export interface SkillsDoctorOptions {
27
56
  mode: SkillsDoctorMode;
28
57
  skillsDir?: string;
@@ -72,9 +101,37 @@ export declare function scoreTokenEfficiency(parsed: {
72
101
  triggers: string[];
73
102
  }): number;
74
103
  /**
75
- * Score a skill on all 4 dimensions and compute overall.
104
+ * Score safety (0-100): absence of dangerous patterns, injection risks, credential leaks.
105
+ * Starts at 100 and deducts for each dangerous pattern found.
106
+ */
107
+ export declare function scoreSafety(parsed: {
108
+ name: string;
109
+ rules: string[];
110
+ rawContent: string;
111
+ triggers: string[];
112
+ }): number;
113
+ /**
114
+ * Score agent readiness (0-100): how well-prepared a skill is for AI agent consumption.
115
+ * Checks for triggers, tool restrictions, examples, structured output, and error handling.
116
+ */
117
+ export declare function scoreAgentReadiness(parsed: {
118
+ name: string;
119
+ rules: string[];
120
+ rawContent: string;
121
+ triggers: string[];
122
+ }): number;
123
+ /**
124
+ * Convert numeric score to letter grade.
125
+ */
126
+ export declare function computeGrade(overall: number): SkillGrade;
127
+ /**
128
+ * Score a skill on all 6 dimensions and compute overall with letter grade.
76
129
  */
77
130
  export declare function scoreSkill(skillPath: string, threshold?: number): Promise<SkillScore | null>;
131
+ /**
132
+ * Gate check for registry inclusion. Rejects skills below the configured threshold.
133
+ */
134
+ export declare function registryGate(skillPath: string, threshold?: number): Promise<SkillRegistryGateResult | null>;
78
135
  /**
79
136
  * Run structural quality benchmark checks against a skill.
80
137
  */