@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,256 @@
1
+ /**
2
+ * Devran AI Kit — Hook Trigger System
3
+ *
4
+ * Event-driven lifecycle hook execution based on hooks.json.
5
+ * Evaluates hook actions and reports results with severity awareness.
6
+ *
7
+ * @module lib/hook-system
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, ENGINE_DIR, HOOKS_DIR } = require('./constants');
18
+
19
+ const HOOKS_FILE = 'hooks.json';
20
+
21
+ /**
22
+ * @typedef {object} ActionResult
23
+ * @property {string} action - Action description
24
+ * @property {'critical' | 'high' | 'medium' | 'low'} severity - Action severity
25
+ * @property {'block' | 'warn' | 'log'} onFailure - Failure behavior
26
+ * @property {'pass' | 'fail' | 'skip'} status - Evaluation result
27
+ * @property {string} reason - Reason for status
28
+ */
29
+
30
+ /**
31
+ * @typedef {object} HookEvaluation
32
+ * @property {string} event - Hook event name
33
+ * @property {boolean} blocked - Whether any blocking action failed
34
+ * @property {number} passed - Count of passed actions
35
+ * @property {number} failed - Count of failed actions
36
+ * @property {number} skipped - Count of skipped actions
37
+ * @property {ActionResult[]} results - Individual action results
38
+ */
39
+
40
+ /**
41
+ * Loads hooks definition from hooks.json.
42
+ *
43
+ * @param {string} projectRoot - Root directory of the project
44
+ * @returns {object} Parsed hooks config
45
+ */
46
+ function loadHooks(projectRoot) {
47
+ const hooksPath = path.join(projectRoot, AGENT_DIR, HOOKS_DIR, HOOKS_FILE);
48
+
49
+ if (!fs.existsSync(hooksPath)) {
50
+ return { hooks: [] };
51
+ }
52
+
53
+ try {
54
+ return JSON.parse(fs.readFileSync(hooksPath, 'utf-8'));
55
+ } catch {
56
+ return { hooks: [] };
57
+ }
58
+ }
59
+
60
+ /**
61
+ * Gets the action definitions for a specific event.
62
+ *
63
+ * @param {string} eventName - Hook event name (e.g., 'session-start')
64
+ * @param {string} projectRoot - Root directory of the project
65
+ * @returns {object[]} Action definitions for this event
66
+ */
67
+ function getHookActions(eventName, projectRoot) {
68
+ const config = loadHooks(projectRoot);
69
+ const hook = (config.hooks || []).find((h) => h.event === eventName);
70
+
71
+ if (!hook) {
72
+ return [];
73
+ }
74
+
75
+ return hook.actions || [];
76
+ }
77
+
78
+ /**
79
+ * Evaluates a hook event against the current project state.
80
+ *
81
+ * This performs a "check" pass — it determines which actions would
82
+ * pass or fail without actually executing them. Actions are evaluated
83
+ * based on the existence of required files and configurations.
84
+ *
85
+ * @param {string} eventName - Hook event name
86
+ * @param {object} context - Evaluation context
87
+ * @param {string} context.projectRoot - Root directory of the project
88
+ * @param {boolean} [context.gitClean] - Whether git status is clean
89
+ * @param {boolean} [context.testsPass] - Whether tests pass
90
+ * @param {boolean} [context.buildPass] - Whether build passes
91
+ * @param {boolean} [context.lintPass] - Whether lint passes
92
+ * @returns {HookEvaluation}
93
+ */
94
+ function evaluateHook(eventName, context) {
95
+ const projectRoot = context.projectRoot;
96
+ const actions = getHookActions(eventName, projectRoot);
97
+
98
+ if (actions.length === 0) {
99
+ return { event: eventName, blocked: false, passed: 0, failed: 0, skipped: 0, results: [] };
100
+ }
101
+
102
+ /** @type {ActionResult[]} */
103
+ const results = [];
104
+ let blocked = false;
105
+
106
+ for (const action of actions) {
107
+ const result = evaluateAction(action, context);
108
+ results.push(result);
109
+
110
+ if (result.status === 'fail' && action.onFailure === 'block') {
111
+ blocked = true;
112
+ }
113
+ }
114
+
115
+ return {
116
+ event: eventName,
117
+ blocked,
118
+ passed: results.filter((r) => r.status === 'pass').length,
119
+ failed: results.filter((r) => r.status === 'fail').length,
120
+ skipped: results.filter((r) => r.status === 'skip').length,
121
+ results,
122
+ };
123
+ }
124
+
125
+ /**
126
+ * Evaluates a single action based on context.
127
+ *
128
+ * @param {object} action - Action definition from hooks.json
129
+ * @param {object} context - Evaluation context
130
+ * @returns {ActionResult}
131
+ */
132
+ function evaluateAction(action, context) {
133
+ const actionLower = action.action.toLowerCase();
134
+ const projectRoot = context.projectRoot;
135
+
136
+ // File existence checks
137
+ if (actionLower.includes('session-context.md') || actionLower.includes('session-state.json')) {
138
+ const targetFile = actionLower.includes('session-context.md') ? 'session-context.md' : 'session-state.json';
139
+ const filePath = path.join(projectRoot, AGENT_DIR, targetFile);
140
+ const exists = fs.existsSync(filePath);
141
+
142
+ return {
143
+ action: action.action,
144
+ severity: action.severity,
145
+ onFailure: action.onFailure,
146
+ status: exists ? 'pass' : 'fail',
147
+ reason: exists ? `${targetFile} exists` : `${targetFile} not found`,
148
+ };
149
+ }
150
+
151
+ if (actionLower.includes('loading-rules.json') || actionLower.includes('workflow-state.json')) {
152
+ const targetFile = actionLower.includes('loading-rules.json') ? 'loading-rules.json' : 'workflow-state.json';
153
+ const filePath = path.join(projectRoot, AGENT_DIR, ENGINE_DIR, targetFile);
154
+ const exists = fs.existsSync(filePath);
155
+
156
+ return {
157
+ action: action.action,
158
+ severity: action.severity,
159
+ onFailure: action.onFailure,
160
+ status: exists ? 'pass' : 'fail',
161
+ reason: exists ? `${targetFile} exists and readable` : `${targetFile} not found`,
162
+ };
163
+ }
164
+
165
+ // Git status check
166
+ if (actionLower.includes('git status')) {
167
+ const gitClean = context.gitClean !== undefined ? context.gitClean : true;
168
+ return {
169
+ action: action.action,
170
+ severity: action.severity,
171
+ onFailure: action.onFailure,
172
+ status: gitClean ? 'pass' : 'fail',
173
+ reason: gitClean ? 'Git status clean' : 'Git has uncommitted changes',
174
+ };
175
+ }
176
+
177
+ // Build/test/lint checks
178
+ if (actionLower.includes('build passes') || actionLower.includes('npm run build')) {
179
+ return {
180
+ action: action.action,
181
+ severity: action.severity,
182
+ onFailure: action.onFailure,
183
+ status: context.buildPass ? 'pass' : context.buildPass === false ? 'fail' : 'skip',
184
+ reason: context.buildPass ? 'Build passes' : context.buildPass === false ? 'Build failed' : 'Build not checked',
185
+ };
186
+ }
187
+
188
+ if (actionLower.includes('tests pass') || actionLower.includes('npm test')) {
189
+ return {
190
+ action: action.action,
191
+ severity: action.severity,
192
+ onFailure: action.onFailure,
193
+ status: context.testsPass ? 'pass' : context.testsPass === false ? 'fail' : 'skip',
194
+ reason: context.testsPass ? 'Tests pass' : context.testsPass === false ? 'Tests failed' : 'Tests not checked',
195
+ };
196
+ }
197
+
198
+ if (actionLower.includes('lint passes') || actionLower.includes('npm run lint')) {
199
+ return {
200
+ action: action.action,
201
+ severity: action.severity,
202
+ onFailure: action.onFailure,
203
+ status: context.lintPass ? 'pass' : context.lintPass === false ? 'fail' : 'skip',
204
+ reason: context.lintPass ? 'Lint passes' : context.lintPass === false ? 'Lint failed' : 'Lint not checked',
205
+ };
206
+ }
207
+
208
+ // Default: skip actions we can't evaluate programmatically
209
+ return {
210
+ action: action.action,
211
+ severity: action.severity,
212
+ onFailure: action.onFailure,
213
+ status: 'skip',
214
+ reason: 'Cannot evaluate programmatically — requires agent execution',
215
+ };
216
+ }
217
+
218
+ /**
219
+ * Lists all available hook events.
220
+ *
221
+ * @param {string} projectRoot - Root directory of the project
222
+ * @returns {Array<{ event: string, description: string, actionCount: number }>}
223
+ */
224
+ function listEvents(projectRoot) {
225
+ const config = loadHooks(projectRoot);
226
+
227
+ return (config.hooks || []).map((hook) => ({
228
+ event: hook.event,
229
+ description: hook.description,
230
+ actionCount: (hook.actions || []).length,
231
+ }));
232
+ }
233
+
234
+ /**
235
+ * Generates a full hook readiness report for all events.
236
+ *
237
+ * @param {string} projectRoot - Root directory of the project
238
+ * @returns {{ events: HookEvaluation[], totalActions: number, readyCount: number }}
239
+ */
240
+ function getHookReport(projectRoot) {
241
+ const events = listEvents(projectRoot);
242
+ const context = { projectRoot };
243
+
244
+ const evaluations = events.map((e) => evaluateHook(e.event, context));
245
+ const totalActions = evaluations.reduce((sum, e) => sum + e.passed + e.failed + e.skipped, 0);
246
+ const readyCount = evaluations.filter((e) => !e.blocked).length;
247
+
248
+ return { events: evaluations, totalActions, readyCount };
249
+ }
250
+
251
+ module.exports = {
252
+ getHookActions,
253
+ evaluateHook,
254
+ listEvents,
255
+ getHookReport,
256
+ };
@@ -0,0 +1,434 @@
1
+ /**
2
+ * Devran AI Kit — IDE Configuration Generator
3
+ *
4
+ * Generates IDE-specific configuration files from manifest.json
5
+ * for Cursor, OpenCode, and Codex. Single source of truth ensures
6
+ * all IDE configs stay in sync with the agent manifest.
7
+ *
8
+ * @module lib/ide-generator
9
+ * @since v4.1.0
10
+ */
11
+
12
+ 'use strict';
13
+
14
+ const fs = require('fs');
15
+ const path = require('path');
16
+ const { CURSOR_DIR, OPENCODE_DIR, CODEX_DIR } = require('./constants');
17
+
18
+ /**
19
+ * @typedef {object} IdeFile
20
+ * @property {string} path - Relative path from project root
21
+ * @property {string} content - File content to write
22
+ */
23
+
24
+ /**
25
+ * @typedef {object} IdeConfig
26
+ * @property {IdeFile[]} files - Array of files to generate
27
+ */
28
+
29
+ /**
30
+ * @typedef {object} WriteResult
31
+ * @property {string[]} written - Files that were written
32
+ * @property {string[]} skipped - Files that were skipped (already exist)
33
+ */
34
+
35
+ /**
36
+ * Condenses rules.md content to fit under 4K characters.
37
+ * Extracts operating constraints, coding style, and security requirements.
38
+ *
39
+ * @param {string} rulesContent - Full rules.md content
40
+ * @returns {string} Condensed rules content
41
+ */
42
+ function condenseRules(rulesContent) {
43
+ const sections = [];
44
+
45
+ sections.push('# Devran AI Kit Governance\n');
46
+
47
+ // Extract operating constraints table
48
+ const constraintsMatch = rulesContent.match(
49
+ /### Operating Constraints[\s\S]*?\n\n/
50
+ );
51
+ if (constraintsMatch) {
52
+ sections.push(constraintsMatch[0].trim());
53
+ }
54
+
55
+ // Extract key protocols by matching the heading format generically
56
+ const protocolRegex = /### [A-Z]\. [\s\S]*?(?=###|$)/g;
57
+ let protocolMatch;
58
+ while ((protocolMatch = protocolRegex.exec(rulesContent)) !== null) {
59
+ const trimmed = protocolMatch[0].trim().split('\n').slice(0, 5).join('\n');
60
+ sections.push(trimmed);
61
+ }
62
+
63
+ // Extract technical standards
64
+ const techMatch = rulesContent.match(
65
+ /## .* TECHNICAL STANDARDS[\s\S]*?(?=## |$)/
66
+ );
67
+ if (techMatch) {
68
+ sections.push(techMatch[0].trim().split('\n').slice(0, 12).join('\n'));
69
+ }
70
+
71
+ const result = sections.join('\n\n');
72
+ // Enforce 4K limit
73
+ return result.length > 4000 ? result.slice(0, 3997) + '...' : result;
74
+ }
75
+
76
+ /**
77
+ * Generates Cursor IDE configuration with governance rules.
78
+ *
79
+ * @param {object} _manifest - Parsed manifest.json
80
+ * @param {string} rulesContent - Raw rules.md content
81
+ * @returns {IdeConfig}
82
+ */
83
+ function generateCursorConfig(_manifest, rulesContent) {
84
+ const condensed = condenseRules(rulesContent);
85
+ const mdcContent = [
86
+ '---',
87
+ 'description: "Devran AI Kit - Trust-Grade AI governance and coding standards"',
88
+ 'alwaysApply: true',
89
+ '---',
90
+ '',
91
+ condensed,
92
+ '',
93
+ ].join('\n');
94
+
95
+ return Object.freeze({
96
+ files: Object.freeze([
97
+ Object.freeze({
98
+ path: `${CURSOR_DIR}/rules/kit-governance.mdc`,
99
+ content: mdcContent,
100
+ }),
101
+ ]),
102
+ });
103
+ }
104
+
105
+ /**
106
+ * Extracts agent descriptions from manifest for IDE config.
107
+ *
108
+ * @param {object} manifest - Parsed manifest.json
109
+ * @returns {{ planner: string, reviewer: string }}
110
+ */
111
+ function extractAgentDescriptions(manifest) {
112
+ const agents = manifest?.capabilities?.agents?.items || [];
113
+ const plannerAgent = agents.find((a) => a.name === 'planner');
114
+ const reviewerAgent = agents.find((a) => a.name === 'code-reviewer');
115
+
116
+ return {
117
+ planner: plannerAgent?.domain || 'Implementation planning specialist',
118
+ reviewer: reviewerAgent?.domain || 'Code review and security analysis',
119
+ };
120
+ }
121
+
122
+ /**
123
+ * Generates OpenCode IDE configuration.
124
+ *
125
+ * @param {object} manifest - Parsed manifest.json
126
+ * @returns {IdeConfig}
127
+ */
128
+ function generateOpenCodeConfig(manifest) {
129
+ const descriptions = extractAgentDescriptions(manifest);
130
+
131
+ const config = {
132
+ $schema: 'https://opencode.ai/config.json',
133
+ model: 'anthropic/claude-sonnet-4-5',
134
+ small_model: 'anthropic/claude-haiku-4-5',
135
+ instructions: ['.agent/rules.md'],
136
+ agent: {
137
+ build: {
138
+ description: 'Primary development agent with Devran AI Kit governance',
139
+ model: 'anthropic/claude-sonnet-4-5',
140
+ },
141
+ planner: {
142
+ description: descriptions.planner,
143
+ model: 'anthropic/claude-sonnet-4-5',
144
+ },
145
+ reviewer: {
146
+ description: descriptions.reviewer,
147
+ model: 'anthropic/claude-sonnet-4-5',
148
+ },
149
+ },
150
+ };
151
+
152
+ return Object.freeze({
153
+ files: Object.freeze([
154
+ Object.freeze({
155
+ path: `${OPENCODE_DIR}/opencode.json`,
156
+ content: JSON.stringify(config, null, 2) + '\n',
157
+ }),
158
+ ]),
159
+ });
160
+ }
161
+
162
+ /**
163
+ * Serializes a value to TOML format.
164
+ *
165
+ * @param {string|number|boolean|string[]} value - Value to serialize
166
+ * @returns {string} TOML-formatted value
167
+ */
168
+ function serializeTomlValue(value) {
169
+ if (typeof value === 'string') {
170
+ return `"${value.replace(/\\/g, '\\\\').replace(/"/g, '\\"')}"`;
171
+ }
172
+ if (typeof value === 'boolean' || typeof value === 'number') {
173
+ return String(value);
174
+ }
175
+ if (Array.isArray(value)) {
176
+ const items = value.map(v => serializeTomlValue(v));
177
+ return `[${items.join(', ')}]`;
178
+ }
179
+ return `"${String(value)}"`;
180
+ }
181
+
182
+ /**
183
+ * Minimal TOML serializer for IDE config generation.
184
+ *
185
+ * Intentionally limited to the subset needed by Codex config:
186
+ * strings, numbers, booleans, arrays, and single-level nested objects.
187
+ * Full TOML spec (inline tables, arrays of tables, multi-line strings)
188
+ * is not supported. This is a zero-dependency design decision — we avoid
189
+ * adding @iarna/toml to maintain the zero-dependency guarantee.
190
+ *
191
+ * @param {object} obj - Object to serialize
192
+ * @returns {string} TOML-formatted string
193
+ */
194
+ function serializeToml(obj) {
195
+ const lines = [];
196
+ const sections = [];
197
+
198
+ // First pass: top-level scalars
199
+ for (const [key, value] of Object.entries(obj)) {
200
+ if (value !== null && typeof value === 'object' && !Array.isArray(value)) {
201
+ sections.push([key, value]);
202
+ } else {
203
+ lines.push(`${key} = ${serializeTomlValue(value)}`);
204
+ }
205
+ }
206
+
207
+ // Second pass: nested sections
208
+ for (const [sectionKey, sectionValue] of sections) {
209
+ lines.push('');
210
+
211
+ // Check if section contains further nested objects
212
+ const hasNestedObjects = Object.values(sectionValue).some(
213
+ (v) => v !== null && typeof v === 'object' && !Array.isArray(v)
214
+ );
215
+
216
+ if (hasNestedObjects) {
217
+ // Emit section header once for top-level scalars
218
+ const scalars = Object.entries(sectionValue).filter(
219
+ ([, v]) => v === null || typeof v !== 'object' || Array.isArray(v)
220
+ );
221
+ if (scalars.length > 0) {
222
+ lines.push(`[${sectionKey}]`);
223
+ for (const [subKey, subValue] of scalars) {
224
+ lines.push(`${subKey} = ${serializeTomlValue(subValue)}`);
225
+ }
226
+ lines.push('');
227
+ }
228
+ // Then nested subsections
229
+ for (const [subKey, subValue] of Object.entries(sectionValue)) {
230
+ if (subValue !== null && typeof subValue === 'object' && !Array.isArray(subValue)) {
231
+ lines.push(`[${sectionKey}.${subKey}]`);
232
+ for (const [innerKey, innerValue] of Object.entries(subValue)) {
233
+ lines.push(`${innerKey} = ${serializeTomlValue(innerValue)}`);
234
+ }
235
+ lines.push('');
236
+ }
237
+ }
238
+ } else {
239
+ lines.push(`[${sectionKey}]`);
240
+ for (const [innerKey, innerValue] of Object.entries(sectionValue)) {
241
+ lines.push(`${innerKey} = ${serializeTomlValue(innerValue)}`);
242
+ }
243
+ }
244
+ }
245
+
246
+ return lines.join('\n') + '\n';
247
+ }
248
+
249
+ /**
250
+ * Generates Codex IDE configuration.
251
+ *
252
+ * @param {object} manifest - Parsed manifest.json
253
+ * @returns {IdeConfig}
254
+ */
255
+ function generateCodexConfig(manifest) {
256
+ const descriptions = extractAgentDescriptions(manifest);
257
+
258
+ const config = {
259
+ approval_policy: 'suggest',
260
+ sandbox_mode: 'workspace-write',
261
+ model: {
262
+ default: 'anthropic/claude-sonnet-4-5',
263
+ },
264
+ agents: {
265
+ planner: {
266
+ description: descriptions.planner,
267
+ model: 'anthropic/claude-sonnet-4-5',
268
+ },
269
+ reviewer: {
270
+ description: descriptions.reviewer,
271
+ model: 'anthropic/claude-sonnet-4-5',
272
+ },
273
+ },
274
+ };
275
+
276
+ const tomlHeader = [
277
+ '# Devran AI Kit — Codex Configuration',
278
+ '# Generated by kit init — do not edit manually',
279
+ '',
280
+ '',
281
+ ].join('\n');
282
+
283
+ return Object.freeze({
284
+ files: Object.freeze([
285
+ Object.freeze({
286
+ path: `${CODEX_DIR}/config.toml`,
287
+ content: tomlHeader + serializeToml(config),
288
+ }),
289
+ ]),
290
+ });
291
+ }
292
+
293
+ /**
294
+ * Validates that a file path resolves under the project root.
295
+ * Prevents path traversal attacks.
296
+ *
297
+ * @param {string} projectRoot - Absolute path to project root
298
+ * @param {string} filePath - Relative file path to validate
299
+ * @returns {string} Resolved absolute path
300
+ * @throws {Error} If path resolves outside project root
301
+ */
302
+ function validatePath(projectRoot, filePath) {
303
+ if (typeof filePath !== 'string' || filePath.includes('\0')) {
304
+ throw new Error(`Invalid file path: ${filePath}`);
305
+ }
306
+ const resolved = path.resolve(projectRoot, filePath);
307
+ const normalizedRoot = path.resolve(projectRoot) + path.sep;
308
+
309
+ if (!resolved.startsWith(normalizedRoot)) {
310
+ throw new Error(`Path traversal detected: ${filePath}`);
311
+ }
312
+
313
+ return resolved;
314
+ }
315
+
316
+ /**
317
+ * Writes a file atomically using temp-file-then-rename pattern.
318
+ * Falls back to direct write on Windows EPERM/EACCES errors.
319
+ *
320
+ * @param {string} absolutePath - Absolute destination path
321
+ * @param {string} content - File content
322
+ */
323
+ function atomicWriteFile(absolutePath, content) {
324
+ const dir = path.dirname(absolutePath);
325
+ const tempPath = `${absolutePath}.tmp`;
326
+
327
+ if (!fs.existsSync(dir)) {
328
+ fs.mkdirSync(dir, { recursive: true });
329
+ }
330
+
331
+ try {
332
+ fs.writeFileSync(tempPath, content, 'utf-8');
333
+ fs.renameSync(tempPath, absolutePath);
334
+ } catch (renameErr) {
335
+ const isTransient = renameErr.code === 'EPERM' || renameErr.code === 'EACCES';
336
+ if (isTransient) {
337
+ fs.writeFileSync(absolutePath, content, 'utf-8');
338
+ } else {
339
+ throw renameErr;
340
+ }
341
+ } finally {
342
+ // Ensure temp file is always cleaned up
343
+ try {
344
+ if (fs.existsSync(tempPath)) fs.unlinkSync(tempPath);
345
+ } catch { /* non-critical */ }
346
+ }
347
+ }
348
+
349
+ /**
350
+ * Rolls back previously written files during transactional failure.
351
+ *
352
+ * @param {string} projectRoot - Absolute project root
353
+ * @param {string[]} writtenPaths - Relative paths of written files
354
+ */
355
+ function rollbackWrittenFiles(projectRoot, writtenPaths) {
356
+ for (const writtenPath of writtenPaths) {
357
+ try {
358
+ const abs = path.resolve(projectRoot, writtenPath);
359
+ if (fs.existsSync(abs)) fs.unlinkSync(abs);
360
+ } catch { /* rollback failure is non-critical */ }
361
+ }
362
+ }
363
+
364
+ /**
365
+ * Writes IDE configuration files to the project directory.
366
+ * Uses atomic write pattern (write to .tmp, then rename).
367
+ * Transactional: rolls back all written files if any write fails.
368
+ *
369
+ * @param {string} projectRoot - Absolute path to project root
370
+ * @param {IdeConfig[]} configs - Array of IDE configurations
371
+ * @param {object} [options={}] - Write options
372
+ * @param {boolean} [options.force=false] - Overwrite existing files
373
+ * @param {boolean} [options.skipExisting=false] - Silently skip existing
374
+ * @returns {WriteResult}
375
+ */
376
+ function writeIdeConfigs(projectRoot, configs, options = {}) {
377
+ if (typeof projectRoot !== 'string' || !path.isAbsolute(projectRoot)) {
378
+ throw new TypeError('projectRoot must be an absolute path string');
379
+ }
380
+ if (!Array.isArray(configs)) {
381
+ throw new TypeError('configs must be an array');
382
+ }
383
+
384
+ const { force = false, skipExisting = false } = options;
385
+ const written = [];
386
+ const skipped = [];
387
+
388
+ // Collect all files from all configs
389
+ const allFiles = configs.flatMap((config) => config.files || []);
390
+
391
+ for (const file of allFiles) {
392
+ const absolutePath = validatePath(projectRoot, file.path);
393
+
394
+ if (fs.existsSync(absolutePath) && !force) {
395
+ skipped.push(file.path);
396
+ continue;
397
+ }
398
+
399
+ try {
400
+ atomicWriteFile(absolutePath, file.content);
401
+ written.push(file.path);
402
+ } catch (err) {
403
+ rollbackWrittenFiles(projectRoot, written);
404
+ throw new Error(`Failed to write ${file.path}: ${err.message}`);
405
+ }
406
+ }
407
+
408
+ return Object.freeze({ written: Object.freeze([...written]), skipped: Object.freeze([...skipped]) });
409
+ }
410
+
411
+ /**
412
+ * Generates all IDE configurations from manifest and rules.
413
+ *
414
+ * @param {object} manifest - Parsed manifest.json
415
+ * @param {string} rulesContent - Raw rules.md content
416
+ * @returns {IdeConfig[]}
417
+ */
418
+ function generateAllIdeConfigs(manifest, rulesContent) {
419
+ return [
420
+ generateCursorConfig(manifest, rulesContent),
421
+ generateOpenCodeConfig(manifest),
422
+ generateCodexConfig(manifest),
423
+ ];
424
+ }
425
+
426
+ module.exports = Object.freeze({
427
+ generateCursorConfig,
428
+ generateOpenCodeConfig,
429
+ generateCodexConfig,
430
+ generateAllIdeConfigs,
431
+ writeIdeConfigs,
432
+ serializeToml,
433
+ condenseRules,
434
+ });