@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,240 @@
1
+ /**
2
+ * Devran AI Kit — Developer Identity System
3
+ *
4
+ * Local identity management for multi-developer scenarios.
5
+ * Auto-detects from git config with manual registration fallback.
6
+ *
7
+ * @module lib/identity
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
+ const crypto = require('crypto');
17
+ const { execSync } = require('child_process');
18
+
19
+ const { AGENT_DIR, ENGINE_DIR } = require('./constants');
20
+ const { writeJsonAtomic } = require('./io');
21
+ const IDENTITY_FILE = 'identity.json';
22
+
23
+ /** @type {readonly string[]} */
24
+ const VALID_ROLES = ['owner', 'contributor', 'reviewer'];
25
+
26
+ /**
27
+ * @typedef {object} Identity
28
+ * @property {string} id - Deterministic developer ID (SHA-256 truncated)
29
+ * @property {string} name - Developer name
30
+ * @property {string} email - Developer email
31
+ * @property {string} role - Developer role
32
+ * @property {string} registeredAt - ISO timestamp
33
+ * @property {string} lastActiveAt - ISO timestamp
34
+ */
35
+
36
+ /**
37
+ * Resolves the identity file path.
38
+ *
39
+ * @param {string} projectRoot - Root directory of the project
40
+ * @returns {string} Absolute path to identity.json
41
+ */
42
+ function resolveIdentityPath(projectRoot) {
43
+ return path.join(projectRoot, AGENT_DIR, ENGINE_DIR, IDENTITY_FILE);
44
+ }
45
+
46
+ /**
47
+ * Generates a deterministic developer ID from email.
48
+ *
49
+ * @param {string} email - Developer email
50
+ * @returns {string} 12-char hex ID
51
+ */
52
+ function generateDeveloperId(email) {
53
+ return crypto
54
+ .createHash('sha256')
55
+ .update(email.toLowerCase().trim())
56
+ .digest('hex')
57
+ .slice(0, 12);
58
+ }
59
+
60
+ /**
61
+ * Loads the identity registry from disk.
62
+ *
63
+ * @param {string} projectRoot - Root directory of the project
64
+ * @returns {{ developers: Identity[], activeId: string | null }}
65
+ */
66
+ function loadIdentityRegistry(projectRoot) {
67
+ const identityPath = resolveIdentityPath(projectRoot);
68
+
69
+ if (!fs.existsSync(identityPath)) {
70
+ return { developers: [], activeId: null };
71
+ }
72
+
73
+ try {
74
+ return JSON.parse(fs.readFileSync(identityPath, 'utf-8'));
75
+ } catch {
76
+ return { developers: [], activeId: null };
77
+ }
78
+ }
79
+
80
+ /**
81
+ * Writes the identity registry to disk atomically.
82
+ *
83
+ * @param {string} projectRoot - Root directory of the project
84
+ * @param {object} registry - Registry data
85
+ * @returns {void}
86
+ */
87
+ function writeIdentityRegistry(projectRoot, registry) {
88
+ const identityPath = resolveIdentityPath(projectRoot);
89
+ writeJsonAtomic(identityPath, registry);
90
+ }
91
+
92
+ /**
93
+ * Auto-detects developer identity from git config.
94
+ *
95
+ * @returns {{ name: string, email: string } | null}
96
+ */
97
+ function detectFromGit() {
98
+ try {
99
+ const name = execSync('git config user.name', { encoding: 'utf-8' }).trim();
100
+ const email = execSync('git config user.email', { encoding: 'utf-8' }).trim();
101
+
102
+ if (name && email) {
103
+ return { name, email };
104
+ }
105
+ return null;
106
+ } catch {
107
+ return null;
108
+ }
109
+ }
110
+
111
+ /**
112
+ * Registers a new developer identity.
113
+ *
114
+ * @param {string} projectRoot - Root directory of the project
115
+ * @param {object} params - Identity parameters
116
+ * @param {string} params.name - Developer name
117
+ * @param {string} params.email - Developer email
118
+ * @param {string} [params.role] - Developer role (default: 'contributor')
119
+ * @returns {{ success: boolean, identity: Identity, isNew: boolean }}
120
+ */
121
+ function registerIdentity(projectRoot, { name, email, role }) {
122
+ const registry = loadIdentityRegistry(projectRoot);
123
+ const developerId = generateDeveloperId(email);
124
+ const now = new Date().toISOString();
125
+ const identityRole = role && VALID_ROLES.includes(role) ? role : 'contributor';
126
+
127
+ // Check if already registered
128
+ const existingIndex = registry.developers.findIndex((d) => d.id === developerId);
129
+
130
+ if (existingIndex !== -1) {
131
+ // Update last active (immutable)
132
+ const updatedDev = { ...registry.developers[existingIndex], lastActiveAt: now, name };
133
+ const updatedRegistry = {
134
+ ...registry,
135
+ developers: registry.developers.map((d, i) => (i === existingIndex ? updatedDev : d)),
136
+ activeId: developerId,
137
+ };
138
+ writeIdentityRegistry(projectRoot, updatedRegistry);
139
+ return { success: true, identity: updatedDev, isNew: false };
140
+ }
141
+
142
+ // First developer becomes owner automatically
143
+ const assignedRole = registry.developers.length === 0 ? 'owner' : identityRole;
144
+
145
+ /** @type {Identity} */
146
+ const identity = {
147
+ id: developerId,
148
+ name,
149
+ email: email.toLowerCase().trim(),
150
+ role: assignedRole,
151
+ registeredAt: now,
152
+ lastActiveAt: now,
153
+ };
154
+
155
+ const updatedRegistry = {
156
+ ...registry,
157
+ developers: [...registry.developers, identity],
158
+ activeId: developerId,
159
+ };
160
+ writeIdentityRegistry(projectRoot, updatedRegistry);
161
+
162
+ return { success: true, identity, isNew: true };
163
+ }
164
+
165
+ /**
166
+ * Gets the current active developer identity.
167
+ * Auto-detects and registers from git if no identity exists.
168
+ *
169
+ * @param {string} projectRoot - Root directory of the project
170
+ * @returns {Identity | null}
171
+ */
172
+ function getCurrentIdentity(projectRoot) {
173
+ const registry = loadIdentityRegistry(projectRoot);
174
+
175
+ if (registry.activeId) {
176
+ const identity = registry.developers.find((d) => d.id === registry.activeId);
177
+ if (identity) {
178
+ return identity;
179
+ }
180
+ }
181
+
182
+ // Auto-detect from git
183
+ const gitInfo = detectFromGit();
184
+ if (gitInfo) {
185
+ const result = registerIdentity(projectRoot, { name: gitInfo.name, email: gitInfo.email });
186
+ return result.identity;
187
+ }
188
+
189
+ return null;
190
+ }
191
+
192
+ /**
193
+ * Validates an identity exists and is properly formed.
194
+ *
195
+ * @param {string} developerId - Developer ID to validate
196
+ * @param {string} projectRoot - Root directory of the project
197
+ * @returns {{ valid: boolean, errors: string[] }}
198
+ */
199
+ function validateIdentity(developerId, projectRoot) {
200
+ const registry = loadIdentityRegistry(projectRoot);
201
+ /** @type {string[]} */
202
+ const errors = [];
203
+
204
+ const identity = registry.developers.find((d) => d.id === developerId);
205
+
206
+ if (!identity) {
207
+ return { valid: false, errors: [`Identity not found: ${developerId}`] };
208
+ }
209
+
210
+ if (!identity.name || identity.name.trim().length === 0) {
211
+ errors.push('Identity missing name');
212
+ }
213
+ if (!identity.email || !identity.email.includes('@')) {
214
+ errors.push('Identity missing valid email');
215
+ }
216
+ if (!VALID_ROLES.includes(identity.role)) {
217
+ errors.push(`Invalid role: ${identity.role}`);
218
+ }
219
+
220
+ return { valid: errors.length === 0, errors };
221
+ }
222
+
223
+ /**
224
+ * Lists all registered developer identities.
225
+ *
226
+ * @param {string} projectRoot - Root directory of the project
227
+ * @returns {{ developers: Identity[], activeId: string | null }}
228
+ */
229
+ function listIdentities(projectRoot) {
230
+ return loadIdentityRegistry(projectRoot);
231
+ }
232
+
233
+ module.exports = {
234
+ registerIdentity,
235
+ getCurrentIdentity,
236
+ validateIdentity,
237
+ listIdentities,
238
+ generateDeveloperId,
239
+ detectFromGit,
240
+ };
package/lib/io.js ADDED
@@ -0,0 +1,146 @@
1
+ /**
2
+ * Devran AI Kit — Shared I/O Utilities
3
+ *
4
+ * Provides atomic file write operations and safe JSON parsing
5
+ * used across all runtime modules. Single point for error
6
+ * handling around filesystem operations.
7
+ *
8
+ * @module lib/io
9
+ * @author Emre Dursun
10
+ * @since v3.2.0
11
+ */
12
+
13
+ 'use strict';
14
+
15
+ const fs = require('fs');
16
+ const path = require('path');
17
+ const { createLogger } = require('./logger');
18
+ const log = createLogger('io');
19
+
20
+ /**
21
+ * Writes JSON data to a file atomically (temp file + rename).
22
+ * Creates parent directories if they don't exist.
23
+ *
24
+ * @param {string} filePath - Target file path
25
+ * @param {object} data - Data to serialize as JSON
26
+ * @returns {void}
27
+ */
28
+ function writeJsonAtomic(filePath, data) {
29
+ const dir = path.dirname(filePath);
30
+ if (!fs.existsSync(dir)) {
31
+ fs.mkdirSync(dir, { recursive: true });
32
+ }
33
+
34
+ const content = JSON.stringify(data, null, 2) + '\n';
35
+ const tempPath = `${filePath}.tmp`;
36
+
37
+ try {
38
+ fs.writeFileSync(tempPath, content, 'utf-8');
39
+ } catch (writeErr) {
40
+ throw writeErr;
41
+ }
42
+
43
+ // Rename temp → target. On Windows, EPERM/EACCES can occur when
44
+ // another handle briefly holds the target file (antivirus, prior
45
+ // read, etc.). Retry up to 3 times with a small delay before
46
+ // falling back to a direct overwrite.
47
+ let renamed = false;
48
+ for (let attempt = 0; attempt < 3; attempt++) {
49
+ try {
50
+ fs.renameSync(tempPath, filePath);
51
+ renamed = true;
52
+ break;
53
+ } catch (renameErr) {
54
+ const isTransient = renameErr.code === 'EPERM' || renameErr.code === 'EACCES';
55
+ if (!isTransient || attempt === 2) {
56
+ // Final attempt failed — fall back to direct write
57
+ try {
58
+ fs.writeFileSync(filePath, content, 'utf-8');
59
+ renamed = true;
60
+ } catch (fallbackErr) {
61
+ // Clean up temp file before throwing
62
+ try { fs.unlinkSync(tempPath); } catch { /* non-critical */ }
63
+ throw fallbackErr;
64
+ }
65
+ break;
66
+ }
67
+ // Brief pause before retry (1ms, 5ms)
68
+ const delay = attempt === 0 ? 1 : 5;
69
+ const start = Date.now();
70
+ while (Date.now() - start < delay) { /* spin wait */ }
71
+ }
72
+ }
73
+
74
+ // Clean up temp file if direct-write fallback was used
75
+ if (renamed) {
76
+ try {
77
+ if (fs.existsSync(tempPath)) {
78
+ fs.unlinkSync(tempPath);
79
+ }
80
+ } catch {
81
+ // Cleanup failure is non-critical
82
+ }
83
+ }
84
+ }
85
+
86
+ /**
87
+ * Safely parses a JSON file, returning a default value on failure.
88
+ *
89
+ * @param {string} filePath - Path to JSON file
90
+ * @param {*} defaultValue - Value to return if file doesn't exist or is invalid
91
+ * @returns {*} Parsed JSON or default value
92
+ */
93
+ function readJsonSafe(filePath, defaultValue = null) {
94
+ if (!fs.existsSync(filePath)) {
95
+ return defaultValue;
96
+ }
97
+
98
+ try {
99
+ return JSON.parse(fs.readFileSync(filePath, 'utf-8'));
100
+ } catch (error) {
101
+ log.debug('Failed to parse JSON file, returning default', { filePath, error: error.message });
102
+ return defaultValue;
103
+ }
104
+ }
105
+
106
+ /**
107
+ * Recursively copies a directory, skipping symbolic links for security.
108
+ *
109
+ * Symlinks are skipped because they could point outside the intended
110
+ * scope (e.g., outside .agent/), enabling path traversal attacks.
111
+ *
112
+ * @param {string} src - Source directory path
113
+ * @param {string} dest - Destination directory path
114
+ * @returns {void}
115
+ */
116
+ function safeCopyDirSync(src, dest) {
117
+ if (!fs.existsSync(dest)) {
118
+ fs.mkdirSync(dest, { recursive: true });
119
+ }
120
+
121
+ const entries = fs.readdirSync(src, { withFileTypes: true });
122
+
123
+ for (const entry of entries) {
124
+ const srcPath = path.join(src, entry.name);
125
+ const destPath = path.join(dest, entry.name);
126
+
127
+ // Security: skip symlinks to prevent path traversal
128
+ const stat = fs.lstatSync(srcPath);
129
+ if (stat.isSymbolicLink()) {
130
+ log.debug('Skipping symlink for security', { path: srcPath });
131
+ continue;
132
+ }
133
+
134
+ if (entry.isDirectory()) {
135
+ safeCopyDirSync(srcPath, destPath);
136
+ } else {
137
+ fs.copyFileSync(srcPath, destPath);
138
+ }
139
+ }
140
+ }
141
+
142
+ module.exports = {
143
+ writeJsonAtomic,
144
+ readJsonSafe,
145
+ safeCopyDirSync,
146
+ };
@@ -0,0 +1,163 @@
1
+ /**
2
+ * Devran AI Kit — Learning Engine
3
+ *
4
+ * Confidence scoring, pattern clustering, and decay model
5
+ * for the continuous learning system. Inspired by Netflix's
6
+ * chaos engineering feedback loop: observe, score, cluster, promote, decay.
7
+ *
8
+ * All functions are pure — no I/O, no side effects.
9
+ *
10
+ * @module lib/learning-engine
11
+ * @since v4.1.0
12
+ */
13
+
14
+ 'use strict';
15
+
16
+ /** Confidence tier boundaries */
17
+ const TIERS = Object.freeze([
18
+ { min: 10, score: 5 },
19
+ { min: 6, score: 4 },
20
+ { min: 4, score: 3 },
21
+ { min: 2, score: 2 },
22
+ { min: 1, score: 1 },
23
+ ]);
24
+
25
+ /** Sessions without reinforcement before 1 point of decay */
26
+ const DECAY_INTERVAL = 10;
27
+
28
+ /** Minimum high-confidence patterns for skill promotion */
29
+ const SKILL_PROMOTION_THRESHOLD = 3;
30
+
31
+ /** Minimum confidence score for promotion eligibility */
32
+ const PROMOTION_CONFIDENCE = 4;
33
+
34
+ /**
35
+ * Score confidence for a pattern based on reinforcement count.
36
+ * Scale: 0 (never seen) to 5 (battle-tested, 10+ reinforcements).
37
+ *
38
+ * @param {number} reinforcementCount - Times pattern was observed
39
+ * @returns {number} Confidence score 0-5
40
+ */
41
+ function scoreConfidence(reinforcementCount) {
42
+ if (!Number.isFinite(reinforcementCount) || reinforcementCount <= 0) {
43
+ return 0;
44
+ }
45
+ for (const tier of TIERS) {
46
+ if (reinforcementCount >= tier.min) {
47
+ return tier.score;
48
+ }
49
+ }
50
+ return 0;
51
+ }
52
+
53
+ /**
54
+ * Cluster patterns by domain using loading-rules keyword overlap.
55
+ *
56
+ * @param {Array<{id: string, keywords: string[], reinforcements: number}>} patterns
57
+ * @param {Array<{domain: string, keywords: string[]}>} domainRules
58
+ * @returns {Map<string, Array>} Patterns grouped by domain
59
+ */
60
+ function clusterPatterns(patterns, domainRules) {
61
+ if (!Array.isArray(patterns)) return new Map();
62
+ if (!Array.isArray(domainRules)) return new Map();
63
+
64
+ const clusters = new Map();
65
+
66
+ for (const rule of domainRules) {
67
+ clusters.set(rule.domain, []);
68
+ }
69
+ clusters.set('uncategorized', []);
70
+
71
+ for (const pattern of patterns) {
72
+ let matched = false;
73
+ for (const rule of domainRules) {
74
+ const hasOverlap = pattern.keywords.some(pk =>
75
+ rule.keywords.some(rk =>
76
+ rk.toLowerCase() === pk.toLowerCase()
77
+ )
78
+ );
79
+ if (hasOverlap) {
80
+ clusters.get(rule.domain).push(pattern);
81
+ matched = true;
82
+ break;
83
+ }
84
+ }
85
+ if (!matched) {
86
+ clusters.get('uncategorized').push(pattern);
87
+ }
88
+ }
89
+
90
+ return clusters;
91
+ }
92
+
93
+ /**
94
+ * Apply decay to patterns based on sessions since last reinforcement.
95
+ * Loses 1 confidence point per DECAY_INTERVAL unreinforced sessions.
96
+ * Score 0 marks the pattern as archived.
97
+ *
98
+ * @param {Array<{id: string, confidence: number, sessionsSinceReinforcement: number}>} patterns
99
+ * @returns {Array<{id: string, confidence: number, archived: boolean}>}
100
+ */
101
+ function decayPatterns(patterns) {
102
+ if (!Array.isArray(patterns)) return [];
103
+
104
+ return patterns.map(p => {
105
+ const sessions = Number.isFinite(p.sessionsSinceReinforcement) ? p.sessionsSinceReinforcement : 0;
106
+ const confidence = Number.isFinite(p.confidence) ? p.confidence : 0;
107
+ const decayAmount = Math.floor(sessions / DECAY_INTERVAL);
108
+ const newConfidence = Math.max(0, confidence - decayAmount);
109
+ return {
110
+ ...p,
111
+ confidence: newConfidence,
112
+ archived: newConfidence === 0,
113
+ };
114
+ });
115
+ }
116
+
117
+ /**
118
+ * Generate recommendations for pattern promotion.
119
+ * Clusters with 3+ high-confidence patterns suggest skill creation.
120
+ * Clusters with 1-2 high-confidence patterns suggest rule creation.
121
+ *
122
+ * @param {Map<string, Array<{reinforcements: number}>>} clusters
123
+ * @returns {Array<{domain: string, action: string, reason: string, patterns: Array}>}
124
+ */
125
+ function getRecommendations(clusters) {
126
+ const recommendations = [];
127
+
128
+ for (const [domain, patterns] of clusters) {
129
+ if (domain === 'uncategorized') continue;
130
+
131
+ const highConfidence = patterns.filter(p =>
132
+ scoreConfidence(p.reinforcements) >= PROMOTION_CONFIDENCE
133
+ );
134
+
135
+ if (highConfidence.length >= SKILL_PROMOTION_THRESHOLD) {
136
+ recommendations.push({
137
+ domain,
138
+ action: 'promote-to-skill',
139
+ reason: `${highConfidence.length} battle-tested patterns in ${domain}`,
140
+ patterns: highConfidence,
141
+ });
142
+ } else if (highConfidence.length >= 1) {
143
+ recommendations.push({
144
+ domain,
145
+ action: 'promote-to-rule',
146
+ reason: `${highConfidence.length} high-confidence pattern(s) in ${domain}`,
147
+ patterns: highConfidence,
148
+ });
149
+ }
150
+ }
151
+
152
+ return recommendations;
153
+ }
154
+
155
+ module.exports = Object.freeze({
156
+ scoreConfidence,
157
+ clusterPatterns,
158
+ decayPatterns,
159
+ getRecommendations,
160
+ DECAY_INTERVAL,
161
+ SKILL_PROMOTION_THRESHOLD,
162
+ PROMOTION_CONFIDENCE,
163
+ });