@devran-ai/kit 4.1.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 (231) hide show
  1. package/.agent/CheatSheet.md +350 -0
  2. package/.agent/README.md +76 -0
  3. package/.agent/agents/README.md +155 -0
  4. package/.agent/agents/architect.md +185 -0
  5. package/.agent/agents/backend-specialist.md +276 -0
  6. package/.agent/agents/build-error-resolver.md +207 -0
  7. package/.agent/agents/code-reviewer.md +162 -0
  8. package/.agent/agents/database-architect.md +138 -0
  9. package/.agent/agents/devops-engineer.md +144 -0
  10. package/.agent/agents/doc-updater.md +229 -0
  11. package/.agent/agents/e2e-runner.md +145 -0
  12. package/.agent/agents/explorer-agent.md +143 -0
  13. package/.agent/agents/frontend-specialist.md +144 -0
  14. package/.agent/agents/go-reviewer.md +128 -0
  15. package/.agent/agents/knowledge-agent.md +197 -0
  16. package/.agent/agents/mobile-developer.md +150 -0
  17. package/.agent/agents/performance-optimizer.md +175 -0
  18. package/.agent/agents/planner.md +133 -0
  19. package/.agent/agents/pr-reviewer.md +148 -0
  20. package/.agent/agents/python-reviewer.md +123 -0
  21. package/.agent/agents/refactor-cleaner.md +201 -0
  22. package/.agent/agents/reliability-engineer.md +156 -0
  23. package/.agent/agents/security-reviewer.md +141 -0
  24. package/.agent/agents/sprint-orchestrator.md +124 -0
  25. package/.agent/agents/tdd-guide.md +179 -0
  26. package/.agent/agents/typescript-reviewer.md +110 -0
  27. package/.agent/checklists/README.md +102 -0
  28. package/.agent/checklists/pre-commit.md +93 -0
  29. package/.agent/checklists/session-end.md +99 -0
  30. package/.agent/checklists/session-start.md +102 -0
  31. package/.agent/checklists/task-complete.md +81 -0
  32. package/.agent/commands/README.md +130 -0
  33. package/.agent/commands/adr.md +29 -0
  34. package/.agent/commands/ask.md +28 -0
  35. package/.agent/commands/build.md +30 -0
  36. package/.agent/commands/changelog.md +40 -0
  37. package/.agent/commands/checkpoint.md +28 -0
  38. package/.agent/commands/code-review.md +65 -0
  39. package/.agent/commands/compact.md +28 -0
  40. package/.agent/commands/cook.md +30 -0
  41. package/.agent/commands/db.md +30 -0
  42. package/.agent/commands/debug.md +31 -0
  43. package/.agent/commands/deploy.md +37 -0
  44. package/.agent/commands/design.md +29 -0
  45. package/.agent/commands/doc.md +30 -0
  46. package/.agent/commands/eval.md +30 -0
  47. package/.agent/commands/fix.md +32 -0
  48. package/.agent/commands/git.md +32 -0
  49. package/.agent/commands/help.md +273 -0
  50. package/.agent/commands/implement.md +30 -0
  51. package/.agent/commands/integrate.md +32 -0
  52. package/.agent/commands/learn.md +29 -0
  53. package/.agent/commands/perf.md +31 -0
  54. package/.agent/commands/plan.md +56 -0
  55. package/.agent/commands/pr-describe.md +65 -0
  56. package/.agent/commands/pr-fix.md +45 -0
  57. package/.agent/commands/pr-merge.md +45 -0
  58. package/.agent/commands/pr-review.md +50 -0
  59. package/.agent/commands/pr-split.md +54 -0
  60. package/.agent/commands/pr-status.md +56 -0
  61. package/.agent/commands/pr.md +58 -0
  62. package/.agent/commands/refactor.md +32 -0
  63. package/.agent/commands/research.md +28 -0
  64. package/.agent/commands/scout.md +30 -0
  65. package/.agent/commands/security-scan.md +33 -0
  66. package/.agent/commands/setup.md +31 -0
  67. package/.agent/commands/status.md +59 -0
  68. package/.agent/commands/tdd.md +73 -0
  69. package/.agent/commands/verify.md +58 -0
  70. package/.agent/contexts/brainstorm.md +26 -0
  71. package/.agent/contexts/debug.md +28 -0
  72. package/.agent/contexts/implement.md +29 -0
  73. package/.agent/contexts/plan-quality-log.md +30 -0
  74. package/.agent/contexts/review.md +27 -0
  75. package/.agent/contexts/ship.md +28 -0
  76. package/.agent/decisions/001-trust-grade-governance.md +46 -0
  77. package/.agent/decisions/002-cross-ide-generation.md +15 -0
  78. package/.agent/engine/identity.json +4 -0
  79. package/.agent/engine/loading-rules.json +193 -0
  80. package/.agent/engine/marketplace-index.json +29 -0
  81. package/.agent/engine/mcp-servers/filesystem.json +9 -0
  82. package/.agent/engine/mcp-servers/github.json +11 -0
  83. package/.agent/engine/mcp-servers/postgres.json +11 -0
  84. package/.agent/engine/mcp-servers/supabase.json +11 -0
  85. package/.agent/engine/mcp-servers/vercel.json +11 -0
  86. package/.agent/engine/reliability-config.json +14 -0
  87. package/.agent/engine/sdlc-map.json +50 -0
  88. package/.agent/engine/workflow-state.json +167 -0
  89. package/.agent/hooks/README.md +101 -0
  90. package/.agent/hooks/hooks.json +104 -0
  91. package/.agent/hooks/templates/session-end.md +110 -0
  92. package/.agent/hooks/templates/session-start.md +95 -0
  93. package/.agent/manifest.json +466 -0
  94. package/.agent/rules/agent-upgrade-policy.md +56 -0
  95. package/.agent/rules/architecture.md +111 -0
  96. package/.agent/rules/coding-style.md +75 -0
  97. package/.agent/rules/documentation.md +74 -0
  98. package/.agent/rules/git-workflow.md +140 -0
  99. package/.agent/rules/quality-gate.md +117 -0
  100. package/.agent/rules/security.md +67 -0
  101. package/.agent/rules/sprint-tracking.md +103 -0
  102. package/.agent/rules/testing.md +80 -0
  103. package/.agent/rules/workflow-standards.md +30 -0
  104. package/.agent/rules.md +293 -0
  105. package/.agent/session-context.md +69 -0
  106. package/.agent/session-state.json +27 -0
  107. package/.agent/skills/README.md +135 -0
  108. package/.agent/skills/api-patterns/SKILL.md +117 -0
  109. package/.agent/skills/app-builder/SKILL.md +202 -0
  110. package/.agent/skills/architecture/SKILL.md +101 -0
  111. package/.agent/skills/behavioral-modes/SKILL.md +295 -0
  112. package/.agent/skills/brainstorming/SKILL.md +156 -0
  113. package/.agent/skills/clean-code/SKILL.md +142 -0
  114. package/.agent/skills/context-budget/SKILL.md +78 -0
  115. package/.agent/skills/continuous-learning/SKILL.md +145 -0
  116. package/.agent/skills/database-design/SKILL.md +303 -0
  117. package/.agent/skills/debugging-strategies/SKILL.md +158 -0
  118. package/.agent/skills/deployment-procedures/SKILL.md +191 -0
  119. package/.agent/skills/docker-patterns/SKILL.md +161 -0
  120. package/.agent/skills/eval-harness/SKILL.md +89 -0
  121. package/.agent/skills/frontend-patterns/SKILL.md +141 -0
  122. package/.agent/skills/git-workflow/SKILL.md +159 -0
  123. package/.agent/skills/i18n-localization/SKILL.md +191 -0
  124. package/.agent/skills/intelligent-routing/SKILL.md +180 -0
  125. package/.agent/skills/mcp-integration/SKILL.md +240 -0
  126. package/.agent/skills/mobile-design/SKILL.md +191 -0
  127. package/.agent/skills/nodejs-patterns/SKILL.md +164 -0
  128. package/.agent/skills/parallel-agents/SKILL.md +200 -0
  129. package/.agent/skills/performance-profiling/SKILL.md +134 -0
  130. package/.agent/skills/plan-validation/SKILL.md +192 -0
  131. package/.agent/skills/plan-writing/SKILL.md +183 -0
  132. package/.agent/skills/plan-writing/domain-enhancers.md +184 -0
  133. package/.agent/skills/plan-writing/plan-retrospective.md +116 -0
  134. package/.agent/skills/plan-writing/plan-schema.md +119 -0
  135. package/.agent/skills/pr-toolkit/SKILL.md +174 -0
  136. package/.agent/skills/production-readiness/SKILL.md +126 -0
  137. package/.agent/skills/security-practices/SKILL.md +109 -0
  138. package/.agent/skills/shell-conventions/SKILL.md +92 -0
  139. package/.agent/skills/strategic-compact/SKILL.md +62 -0
  140. package/.agent/skills/testing-patterns/SKILL.md +141 -0
  141. package/.agent/skills/typescript-expert/SKILL.md +160 -0
  142. package/.agent/skills/ui-ux-pro-max/SKILL.md +137 -0
  143. package/.agent/skills/ui-ux-pro-max/data/charts.csv +26 -0
  144. package/.agent/skills/ui-ux-pro-max/data/colors.csv +97 -0
  145. package/.agent/skills/ui-ux-pro-max/data/icons.csv +101 -0
  146. package/.agent/skills/ui-ux-pro-max/data/landing.csv +31 -0
  147. package/.agent/skills/ui-ux-pro-max/data/products.csv +97 -0
  148. package/.agent/skills/ui-ux-pro-max/data/react-performance.csv +45 -0
  149. package/.agent/skills/ui-ux-pro-max/data/stacks/astro.csv +54 -0
  150. package/.agent/skills/ui-ux-pro-max/data/stacks/flutter.csv +53 -0
  151. package/.agent/skills/ui-ux-pro-max/data/stacks/html-tailwind.csv +56 -0
  152. package/.agent/skills/ui-ux-pro-max/data/stacks/jetpack-compose.csv +53 -0
  153. package/.agent/skills/ui-ux-pro-max/data/stacks/nextjs.csv +53 -0
  154. package/.agent/skills/ui-ux-pro-max/data/stacks/nuxt-ui.csv +51 -0
  155. package/.agent/skills/ui-ux-pro-max/data/stacks/nuxtjs.csv +59 -0
  156. package/.agent/skills/ui-ux-pro-max/data/stacks/react-native.csv +52 -0
  157. package/.agent/skills/ui-ux-pro-max/data/stacks/react.csv +54 -0
  158. package/.agent/skills/ui-ux-pro-max/data/stacks/shadcn.csv +61 -0
  159. package/.agent/skills/ui-ux-pro-max/data/stacks/svelte.csv +54 -0
  160. package/.agent/skills/ui-ux-pro-max/data/stacks/swiftui.csv +51 -0
  161. package/.agent/skills/ui-ux-pro-max/data/stacks/vue.csv +50 -0
  162. package/.agent/skills/ui-ux-pro-max/data/styles.csv +68 -0
  163. package/.agent/skills/ui-ux-pro-max/data/typography.csv +58 -0
  164. package/.agent/skills/ui-ux-pro-max/data/ui-reasoning.csv +101 -0
  165. package/.agent/skills/ui-ux-pro-max/data/ux-guidelines.csv +100 -0
  166. package/.agent/skills/ui-ux-pro-max/data/web-interface.csv +31 -0
  167. package/.agent/skills/ui-ux-pro-max/scripts/core.py +253 -0
  168. package/.agent/skills/ui-ux-pro-max/scripts/design_system.py +1067 -0
  169. package/.agent/skills/ui-ux-pro-max/scripts/search.py +114 -0
  170. package/.agent/skills/verification-loop/SKILL.md +89 -0
  171. package/.agent/skills/webapp-testing/SKILL.md +175 -0
  172. package/.agent/templates/adr-template.md +32 -0
  173. package/.agent/templates/bug-report.md +37 -0
  174. package/.agent/templates/feature-request.md +32 -0
  175. package/.agent/workflows/README.md +101 -0
  176. package/.agent/workflows/brainstorm.md +86 -0
  177. package/.agent/workflows/create.md +85 -0
  178. package/.agent/workflows/debug.md +83 -0
  179. package/.agent/workflows/deploy.md +114 -0
  180. package/.agent/workflows/enhance.md +85 -0
  181. package/.agent/workflows/orchestrate.md +106 -0
  182. package/.agent/workflows/plan.md +105 -0
  183. package/.agent/workflows/pr-fix.md +163 -0
  184. package/.agent/workflows/pr-merge.md +117 -0
  185. package/.agent/workflows/pr-review.md +178 -0
  186. package/.agent/workflows/pr-split.md +118 -0
  187. package/.agent/workflows/pr.md +184 -0
  188. package/.agent/workflows/preflight.md +107 -0
  189. package/.agent/workflows/preview.md +95 -0
  190. package/.agent/workflows/quality-gate.md +103 -0
  191. package/.agent/workflows/retrospective.md +100 -0
  192. package/.agent/workflows/review.md +104 -0
  193. package/.agent/workflows/status.md +89 -0
  194. package/.agent/workflows/test.md +98 -0
  195. package/.agent/workflows/ui-ux-pro-max.md +93 -0
  196. package/.agent/workflows/upgrade.md +97 -0
  197. package/LICENSE +21 -0
  198. package/README.md +218 -0
  199. package/bin/kit.js +773 -0
  200. package/lib/agent-registry.js +228 -0
  201. package/lib/agent-reputation.js +343 -0
  202. package/lib/circuit-breaker.js +195 -0
  203. package/lib/cli-commands.js +322 -0
  204. package/lib/config-validator.js +274 -0
  205. package/lib/conflict-detector.js +252 -0
  206. package/lib/constants.js +47 -0
  207. package/lib/engineering-manager.js +336 -0
  208. package/lib/error-budget.js +370 -0
  209. package/lib/hook-system.js +256 -0
  210. package/lib/ide-generator.js +434 -0
  211. package/lib/identity.js +240 -0
  212. package/lib/io.js +146 -0
  213. package/lib/learning-engine.js +163 -0
  214. package/lib/loading-engine.js +421 -0
  215. package/lib/logger.js +118 -0
  216. package/lib/marketplace.js +321 -0
  217. package/lib/plugin-system.js +604 -0
  218. package/lib/plugin-verifier.js +197 -0
  219. package/lib/rate-limiter.js +113 -0
  220. package/lib/security-scanner.js +312 -0
  221. package/lib/self-healing.js +468 -0
  222. package/lib/session-manager.js +264 -0
  223. package/lib/skill-sandbox.js +244 -0
  224. package/lib/task-governance.js +522 -0
  225. package/lib/task-model.js +332 -0
  226. package/lib/updater.js +240 -0
  227. package/lib/verify.js +279 -0
  228. package/lib/workflow-engine.js +373 -0
  229. package/lib/workflow-events.js +166 -0
  230. package/lib/workflow-persistence.js +160 -0
  231. package/package.json +57 -0
@@ -0,0 +1,264 @@
1
+ /**
2
+ * Devran AI Kit — Session Manager
3
+ *
4
+ * Automates session-state.json updates so it is no longer
5
+ * a blank template. Tracks active sessions, tasks, and
6
+ * repository state.
7
+ *
8
+ * @module lib/session-manager
9
+ * @author Emre Dursun
10
+ * @since v3.0.0
11
+ */
12
+
13
+ 'use strict';
14
+
15
+ const fs = require('fs');
16
+ const path = require('path');
17
+ const crypto = require('crypto');
18
+ const { execSync } = require('child_process');
19
+
20
+ const { AGENT_DIR } = require('./constants');
21
+ const { writeJsonAtomic } = require('./io');
22
+ const STATE_FILENAME = 'session-state.json';
23
+
24
+ /**
25
+ * Resolves the absolute path to session-state.json.
26
+ *
27
+ * @param {string} projectRoot - Root directory of the project
28
+ * @returns {string} Absolute path to session-state.json
29
+ */
30
+ function resolveStatePath(projectRoot) {
31
+ return path.join(projectRoot, AGENT_DIR, STATE_FILENAME);
32
+ }
33
+
34
+ /**
35
+ * Loads session state from disk.
36
+ *
37
+ * @param {string} projectRoot - Root directory of the project
38
+ * @returns {object} Parsed session state
39
+ */
40
+ function loadSessionState(projectRoot) {
41
+ const filePath = resolveStatePath(projectRoot);
42
+
43
+ if (!fs.existsSync(filePath)) {
44
+ throw new Error(`Session state file not found: ${filePath}`);
45
+ }
46
+
47
+ const raw = fs.readFileSync(filePath, 'utf-8');
48
+ return JSON.parse(raw);
49
+ }
50
+
51
+ /**
52
+ * Writes session state to disk atomically.
53
+ *
54
+ * @param {string} projectRoot - Root directory of the project
55
+ * @param {object} state - Session state object to write
56
+ * @returns {void}
57
+ */
58
+ function writeSessionState(projectRoot, state) {
59
+ const filePath = resolveStatePath(projectRoot);
60
+ const updatedState = { ...state, lastUpdated: new Date().toISOString() };
61
+ writeJsonAtomic(filePath, updatedState);
62
+ }
63
+
64
+ /**
65
+ * Retrieves current Git branch name safely.
66
+ *
67
+ * @param {string} projectRoot - Root directory of the project
68
+ * @returns {string | null} Branch name or null if not a git repo
69
+ */
70
+ function getGitBranch(projectRoot) {
71
+ try {
72
+ const result = execSync('git rev-parse --abbrev-ref HEAD', {
73
+ cwd: projectRoot,
74
+ encoding: 'utf-8',
75
+ timeout: 5000,
76
+ stdio: ['pipe', 'pipe', 'pipe'],
77
+ });
78
+ return result.trim() || null;
79
+ } catch {
80
+ return null;
81
+ }
82
+ }
83
+
84
+ /**
85
+ * Retrieves the last Git commit SHA safely.
86
+ *
87
+ * @param {string} projectRoot - Root directory of the project
88
+ * @returns {string | null} Commit SHA or null
89
+ */
90
+ function getGitLastCommit(projectRoot) {
91
+ try {
92
+ const result = execSync('git log -1 --format=%H', {
93
+ cwd: projectRoot,
94
+ encoding: 'utf-8',
95
+ timeout: 5000,
96
+ stdio: ['pipe', 'pipe', 'pipe'],
97
+ });
98
+ return result.trim() || null;
99
+ } catch {
100
+ return null;
101
+ }
102
+ }
103
+
104
+ /**
105
+ * Starts a new session. Generates a session ID, populates
106
+ * date, Git info, and sets status to "active".
107
+ *
108
+ * @param {string} projectRoot - Root directory of the project
109
+ * @param {string} [focus] - Optional session focus description
110
+ * @returns {{ sessionId: string, state: object }}
111
+ */
112
+ function startSession(projectRoot, focus) {
113
+ const loadedState = loadSessionState(projectRoot);
114
+ const sessionId = crypto.randomUUID();
115
+
116
+ const state = {
117
+ ...loadedState,
118
+ session: {
119
+ id: sessionId,
120
+ date: new Date().toISOString(),
121
+ focus: focus || null,
122
+ status: 'active',
123
+ },
124
+ repository: {
125
+ currentBranch: getGitBranch(projectRoot),
126
+ lastCommit: getGitLastCommit(projectRoot),
127
+ remoteSynced: false,
128
+ },
129
+ };
130
+
131
+ writeSessionState(projectRoot, state);
132
+
133
+ return { sessionId, state };
134
+ }
135
+
136
+ /**
137
+ * Ends the current session. Sets status to "completed".
138
+ *
139
+ * @param {string} projectRoot - Root directory of the project
140
+ * @returns {{ success: boolean, sessionId: string | null }}
141
+ */
142
+ function endSession(projectRoot) {
143
+ const loadedState = loadSessionState(projectRoot);
144
+
145
+ if (!loadedState.session || !loadedState.session.id) {
146
+ return { success: false, sessionId: null };
147
+ }
148
+
149
+ const sessionId = loadedState.session.id;
150
+ const state = {
151
+ ...loadedState,
152
+ session: { ...loadedState.session, status: 'completed' },
153
+ notes: `Session ${sessionId} completed at ${new Date().toISOString()}`,
154
+ };
155
+
156
+ writeSessionState(projectRoot, state);
157
+
158
+ return { success: true, sessionId };
159
+ }
160
+
161
+ /**
162
+ * Adds a task to the open tasks list.
163
+ *
164
+ * @param {string} projectRoot - Root directory of the project
165
+ * @param {string} title - Task title
166
+ * @param {string} [description] - Optional description
167
+ * @returns {{ taskId: string }}
168
+ */
169
+ function addTask(projectRoot, title, description) {
170
+ const loadedState = loadSessionState(projectRoot);
171
+ const taskId = `TASK-${Date.now().toString(36).toUpperCase()}`;
172
+
173
+ const task = {
174
+ id: taskId,
175
+ title,
176
+ description: description || null,
177
+ createdAt: new Date().toISOString(),
178
+ status: 'open',
179
+ };
180
+
181
+ const existingTasks = Array.isArray(loadedState.openTasks) ? loadedState.openTasks : [];
182
+ const state = {
183
+ ...loadedState,
184
+ openTasks: [...existingTasks, task],
185
+ currentTask: taskId,
186
+ };
187
+
188
+ writeSessionState(projectRoot, state);
189
+
190
+ return { taskId };
191
+ }
192
+
193
+ /**
194
+ * Marks a task as completed, moving it from openTasks to completedTasks.
195
+ *
196
+ * @param {string} projectRoot - Root directory of the project
197
+ * @param {string} taskId - ID of the task to complete
198
+ * @returns {{ success: boolean }}
199
+ */
200
+ function completeTask(projectRoot, taskId) {
201
+ const loadedState = loadSessionState(projectRoot);
202
+
203
+ if (!Array.isArray(loadedState.openTasks)) {
204
+ return { success: false };
205
+ }
206
+
207
+ const taskIndex = loadedState.openTasks.findIndex((task) => task.id === taskId);
208
+
209
+ if (taskIndex === -1) {
210
+ return { success: false };
211
+ }
212
+
213
+ const completedTask = {
214
+ ...loadedState.openTasks[taskIndex],
215
+ status: 'completed',
216
+ completedAt: new Date().toISOString(),
217
+ };
218
+
219
+ const remainingTasks = loadedState.openTasks.filter((_, i) => i !== taskIndex);
220
+ const existingCompleted = Array.isArray(loadedState.completedTasks) ? loadedState.completedTasks : [];
221
+
222
+ const state = {
223
+ ...loadedState,
224
+ openTasks: remainingTasks,
225
+ completedTasks: [...existingCompleted, completedTask],
226
+ currentTask: loadedState.currentTask === taskId
227
+ ? (remainingTasks.length > 0 ? remainingTasks[0].id : null)
228
+ : loadedState.currentTask,
229
+ };
230
+
231
+ writeSessionState(projectRoot, state);
232
+
233
+ return { success: true };
234
+ }
235
+
236
+ /**
237
+ * Returns a summary of the current session state.
238
+ *
239
+ * @param {string} projectRoot - Root directory of the project
240
+ * @returns {object} Session summary
241
+ */
242
+ function getSessionSummary(projectRoot) {
243
+ const state = loadSessionState(projectRoot);
244
+
245
+ return {
246
+ sessionId: state.session?.id || null,
247
+ status: state.session?.status || 'new',
248
+ focus: state.session?.focus || null,
249
+ branch: state.repository?.currentBranch || null,
250
+ openTaskCount: Array.isArray(state.openTasks) ? state.openTasks.length : 0,
251
+ completedTaskCount: Array.isArray(state.completedTasks) ? state.completedTasks.length : 0,
252
+ currentTask: state.currentTask || null,
253
+ lastUpdated: state.lastUpdated || null,
254
+ };
255
+ }
256
+
257
+ module.exports = {
258
+ loadSessionState,
259
+ startSession,
260
+ endSession,
261
+ addTask,
262
+ completeTask,
263
+ getSessionSummary,
264
+ };
@@ -0,0 +1,244 @@
1
+ /**
2
+ * Devran AI Kit — Skill Sandboxing
3
+ *
4
+ * Enforces allowed-tools declarations from skill SKILL.md frontmatter.
5
+ * Validates that skills only reference tools within their permission set.
6
+ *
7
+ * @module lib/skill-sandbox
8
+ * @author Emre Dursun
9
+ * @since v3.0.0
10
+ */
11
+
12
+ 'use strict';
13
+
14
+ const fs = require('fs');
15
+ const path = require('path');
16
+
17
+ const { AGENT_DIR } = require('./constants');
18
+ const SKILLS_DIR = 'skills';
19
+
20
+ /** Valid permission levels (ordered from least to most privileged) */
21
+ const PERMISSION_LEVELS = ['read-only', 'read-write', 'execute', 'network'];
22
+
23
+ /** Tool patterns that indicate permission level requirements */
24
+ const TOOL_PERMISSION_MAP = {
25
+ 'network': [
26
+ /\bfetch\b/i, /\bhttp\b/i, /\bapi\b/i, /\bcurl\b/i, /\brequest\b/i,
27
+ /\bwebhook\b/i, /\bsocket\b/i, /\bdownload\b/i, /\bupload\b/i,
28
+ ],
29
+ 'execute': [
30
+ /\bexec\b/i, /\bspawn\b/i, /\bchild_process\b/i, /\bshell\b/i,
31
+ /\brun_command\b/i, /\bterminal\b/i, /\bscript\b/i,
32
+ ],
33
+ 'read-write': [
34
+ /\bwrite\b/i, /\bcreate\b/i, /\bdelete\b/i, /\bmodify\b/i,
35
+ /\bedit\b/i, /\bsave\b/i, /\bremove\b/i, /\bupdate\b/i,
36
+ ],
37
+ };
38
+
39
+ /**
40
+ * @typedef {object} SkillPermissions
41
+ * @property {string} skillName - Name of the skill
42
+ * @property {string[]} allowedTools - Declared allowed tools
43
+ * @property {string} permissionLevel - Highest permission level
44
+ * @property {boolean} hasFrontmatter - Whether frontmatter was found
45
+ */
46
+
47
+ /**
48
+ * @typedef {object} SandboxViolation
49
+ * @property {string} skillName - Skill that violated
50
+ * @property {string} violation - Description of violation
51
+ * @property {'critical' | 'high' | 'medium' | 'low'} severity - Violation severity
52
+ * @property {string} file - File where violation was found
53
+ * @property {number} [line] - Line number (if applicable)
54
+ */
55
+
56
+ /**
57
+ * Extracts permissions from a SKILL.md frontmatter.
58
+ *
59
+ * @param {string} content - Raw SKILL.md content
60
+ * @returns {{ allowedTools: string[], permissionLevel: string }}
61
+ */
62
+ function extractPermissionsFromFrontmatter(content) {
63
+ const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---/);
64
+
65
+ if (!frontmatterMatch) {
66
+ return { allowedTools: [], permissionLevel: 'read-only' };
67
+ }
68
+
69
+ const frontmatter = frontmatterMatch[1];
70
+ /** @type {string[]} */
71
+ const allowedTools = [];
72
+ let permissionLevel = 'read-only';
73
+
74
+ // Parse allowed-tools from frontmatter
75
+ const toolsMatch = frontmatter.match(/allowed-tools:\s*\[(.*?)\]/);
76
+ if (toolsMatch) {
77
+ const toolsList = toolsMatch[1].split(',').map((t) => t.trim().replace(/['"]/g, '')).filter(Boolean);
78
+ allowedTools.push(...toolsList);
79
+ }
80
+
81
+ // Parse permission-level from frontmatter
82
+ const permMatch = frontmatter.match(/permission-level:\s*(\S+)/);
83
+ if (permMatch && PERMISSION_LEVELS.includes(permMatch[1])) {
84
+ permissionLevel = permMatch[1];
85
+ }
86
+
87
+ return { allowedTools, permissionLevel };
88
+ }
89
+
90
+ /**
91
+ * Gets the permissions declared by a specific skill.
92
+ *
93
+ * @param {string} skillName - Name of the skill
94
+ * @param {string} projectRoot - Root directory of the project
95
+ * @returns {SkillPermissions}
96
+ */
97
+ function getSkillPermissions(skillName, projectRoot) {
98
+ const skillPath = path.join(projectRoot, AGENT_DIR, SKILLS_DIR, skillName, 'SKILL.md');
99
+
100
+ if (!fs.existsSync(skillPath)) {
101
+ return {
102
+ skillName,
103
+ allowedTools: [],
104
+ permissionLevel: 'read-only',
105
+ hasFrontmatter: false,
106
+ };
107
+ }
108
+
109
+ const content = fs.readFileSync(skillPath, 'utf-8');
110
+ const { allowedTools, permissionLevel } = extractPermissionsFromFrontmatter(content);
111
+ const hasFrontmatter = content.startsWith('---');
112
+
113
+ return { skillName, allowedTools, permissionLevel, hasFrontmatter };
114
+ }
115
+
116
+ /**
117
+ * Scans a skill's content for tool usage that exceeds its declared permissions.
118
+ *
119
+ * @param {string} skillName - Name of the skill
120
+ * @param {string} projectRoot - Root directory of the project
121
+ * @returns {SandboxViolation[]}
122
+ */
123
+ function validateSkillPermissions(skillName, projectRoot) {
124
+ const permissions = getSkillPermissions(skillName, projectRoot);
125
+ const skillDir = path.join(projectRoot, AGENT_DIR, SKILLS_DIR, skillName);
126
+ /** @type {SandboxViolation[]} */
127
+ const violations = [];
128
+
129
+ if (!fs.existsSync(skillDir)) {
130
+ violations.push({
131
+ skillName,
132
+ violation: `Skill directory not found: ${skillName}`,
133
+ severity: 'critical',
134
+ file: skillDir,
135
+ });
136
+ return violations;
137
+ }
138
+
139
+ // Get the permission level index for comparison
140
+ const declaredLevelIndex = PERMISSION_LEVELS.indexOf(permissions.permissionLevel);
141
+
142
+ // Scan all files in the skill directory
143
+ const files = scanSkillFiles(skillDir);
144
+
145
+ for (const file of files) {
146
+ const content = fs.readFileSync(file, 'utf-8');
147
+ const lines = content.split('\n');
148
+
149
+ for (let lineIndex = 0; lineIndex < lines.length; lineIndex++) {
150
+ const line = lines[lineIndex];
151
+
152
+ // Check each permission level higher than declared
153
+ for (const [level, patterns] of Object.entries(TOOL_PERMISSION_MAP)) {
154
+ const levelIndex = PERMISSION_LEVELS.indexOf(level);
155
+
156
+ if (levelIndex > declaredLevelIndex) {
157
+ for (const pattern of patterns) {
158
+ if (pattern.test(line)) {
159
+ violations.push({
160
+ skillName,
161
+ violation: `Uses ${level}-level tool pattern "${pattern.source}" but declared permission is "${permissions.permissionLevel}"`,
162
+ severity: levelIndex - declaredLevelIndex >= 2 ? 'critical' : 'high',
163
+ file: path.relative(projectRoot, file),
164
+ line: lineIndex + 1,
165
+ });
166
+ }
167
+ }
168
+ }
169
+ }
170
+ }
171
+ }
172
+
173
+ return violations;
174
+ }
175
+
176
+ /**
177
+ * Recursively scans a skill directory for readable files.
178
+ *
179
+ * @param {string} dirPath - Directory to scan
180
+ * @returns {string[]} Array of file paths
181
+ */
182
+ function scanSkillFiles(dirPath) {
183
+ /** @type {string[]} */
184
+ const files = [];
185
+
186
+ if (!fs.existsSync(dirPath)) {
187
+ return files;
188
+ }
189
+
190
+ const entries = fs.readdirSync(dirPath, { withFileTypes: true });
191
+
192
+ for (const entry of entries) {
193
+ const fullPath = path.join(dirPath, entry.name);
194
+
195
+ if (entry.isDirectory()) {
196
+ files.push(...scanSkillFiles(fullPath));
197
+ } else if (entry.isFile() && (entry.name.endsWith('.md') || entry.name.endsWith('.json') || entry.name.endsWith('.js'))) {
198
+ files.push(fullPath);
199
+ }
200
+ }
201
+
202
+ return files;
203
+ }
204
+
205
+ /**
206
+ * Enforces allowed-tools compliance across all skills.
207
+ *
208
+ * @param {string} projectRoot - Root directory of the project
209
+ * @returns {{ total: number, compliant: number, violations: SandboxViolation[] }}
210
+ */
211
+ function enforceAllowedTools(projectRoot) {
212
+ const skillsDir = path.join(projectRoot, AGENT_DIR, SKILLS_DIR);
213
+
214
+ if (!fs.existsSync(skillsDir)) {
215
+ return { total: 0, compliant: 0, violations: [] };
216
+ }
217
+
218
+ const skillDirs = fs.readdirSync(skillsDir, { withFileTypes: true })
219
+ .filter((d) => d.isDirectory())
220
+ .map((d) => d.name);
221
+
222
+ /** @type {SandboxViolation[]} */
223
+ const allViolations = [];
224
+
225
+ for (const skillName of skillDirs) {
226
+ const violations = validateSkillPermissions(skillName, projectRoot);
227
+ allViolations.push(...violations);
228
+ }
229
+
230
+ const violatedSkills = new Set(allViolations.map((v) => v.skillName));
231
+
232
+ return {
233
+ total: skillDirs.length,
234
+ compliant: skillDirs.length - violatedSkills.size,
235
+ violations: allViolations,
236
+ };
237
+ }
238
+
239
+ module.exports = {
240
+ getSkillPermissions,
241
+ validateSkillPermissions,
242
+ enforceAllowedTools,
243
+ extractPermissionsFromFrontmatter,
244
+ };