@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,421 @@
1
+ /**
2
+ * Devran AI Kit — Loading Rules Engine
3
+ *
4
+ * Runtime implementation of loading-rules.json keyword matching
5
+ * and context budget enforcement. Includes enhanced planning
6
+ * resolution with mandatory cross-cutting concerns, implicit
7
+ * domain triggers, and protected budget items.
8
+ *
9
+ * @module lib/loading-engine
10
+ * @author Emre Dursun
11
+ * @since v3.0.0
12
+ */
13
+
14
+ 'use strict';
15
+
16
+ const fs = require('fs');
17
+ const path = require('path');
18
+
19
+ const { AGENT_DIR, ENGINE_DIR } = require('./constants');
20
+ const LOADING_RULES_FILE = 'loading-rules.json';
21
+
22
+ /** Default maximum agents per session */
23
+ const DEFAULT_MAX_AGENTS = 4;
24
+ /** Default maximum skills per session */
25
+ const DEFAULT_MAX_SKILLS = 8;
26
+ /** Default warning threshold percentage */
27
+ const DEFAULT_WARNING_THRESHOLD_PERCENT = 80;
28
+
29
+ /**
30
+ * @typedef {object} ProtectedItems
31
+ * @property {string[]} [agents] - Agents exempt from budget trimming
32
+ * @property {string[]} [skills] - Skills exempt from budget trimming
33
+ */
34
+
35
+ /**
36
+ * @typedef {object} LoadPlan
37
+ * @property {string[]} agents - Agents to load
38
+ * @property {string[]} skills - Skills to load
39
+ * @property {string[]} warnings - Budget or resolution warnings
40
+ * @property {object} budgetUsage - Context budget usage stats
41
+ * @property {number} budgetUsage.agentsUsed - Number of agents selected
42
+ * @property {number} budgetUsage.agentsMax - Maximum allowed
43
+ * @property {number} budgetUsage.skillsUsed - Number of skills selected
44
+ * @property {number} budgetUsage.skillsMax - Maximum allowed
45
+ * @property {string[]} matchedDomains - Which domains matched
46
+ * @property {string[]} [mandatoryRules] - Rule files that must be consulted (planning only)
47
+ */
48
+
49
+ /**
50
+ * Loads and parses loading-rules.json.
51
+ *
52
+ * @param {string} projectRoot - Root directory of the project
53
+ * @returns {object} Parsed loading rules
54
+ */
55
+ function loadRules(projectRoot) {
56
+ const rulesPath = path.join(projectRoot, AGENT_DIR, ENGINE_DIR, LOADING_RULES_FILE);
57
+
58
+ if (!fs.existsSync(rulesPath)) {
59
+ throw new Error(`Loading rules not found: ${rulesPath}`);
60
+ }
61
+
62
+ return JSON.parse(fs.readFileSync(rulesPath, 'utf-8'));
63
+ }
64
+
65
+ /**
66
+ * Trims an array of items respecting a protected set.
67
+ * Protected items survive trimming; non-protected items are dropped from end.
68
+ *
69
+ * @param {string[]} items - Items to potentially trim
70
+ * @param {Set<string>} protectedSet - Items exempt from trimming
71
+ * @param {number} max - Maximum allowed count
72
+ * @returns {{ result: string[], trimmed: boolean, warnings: string[] }}
73
+ */
74
+ function trimWithProtection(items, protectedSet, max, label) {
75
+ if (items.length <= max) {
76
+ return { result: items, trimmed: false, warnings: [] };
77
+ }
78
+
79
+ const protectedArr = items.filter((i) => protectedSet.has(i));
80
+ const nonProtected = items.filter((i) => !protectedSet.has(i));
81
+ const slotsForNonProtected = Math.max(0, max - protectedArr.length);
82
+ const result = [...protectedArr, ...nonProtected.slice(0, slotsForNonProtected)];
83
+
84
+ const warnings = [
85
+ `${label} budget exceeded: ${items.length}/${max} — trimmed to ${result.length}`,
86
+ ];
87
+
88
+ if (protectedArr.length > max) {
89
+ warnings.push(`Protected ${label.toLowerCase()} (${protectedArr.length}) exceed budget (${max}) — budget override in effect`);
90
+ }
91
+
92
+ return { result, trimmed: true, warnings };
93
+ }
94
+
95
+ /**
96
+ * Builds a pre-compiled word-boundary regex from an array of trigger strings.
97
+ * Combines all triggers into a single alternation pattern for efficient matching.
98
+ *
99
+ * @param {string[]} triggers - Trigger strings to compile
100
+ * @returns {RegExp | null} Compiled regex, or null if no triggers
101
+ */
102
+ function buildImplicitTriggerRegex(triggers) {
103
+ if (!triggers || triggers.length === 0) {
104
+ return null;
105
+ }
106
+
107
+ const pattern = triggers
108
+ .map((t) => t.toLowerCase().replace(/[.*+?^${}()|[\]\\]/g, '\\$&'))
109
+ .join('|');
110
+
111
+ return new RegExp(`\\b(?:${pattern})\\b`);
112
+ }
113
+
114
+ /**
115
+ * Matches a task description against domain keywords.
116
+ *
117
+ * @param {string} taskDescription - Human-readable task text
118
+ * @param {string} projectRoot - Root directory of the project
119
+ * @returns {{ matchedDomains: string[], agents: string[], skills: string[] }}
120
+ */
121
+ function resolveForTask(taskDescription, projectRoot) {
122
+ if (typeof taskDescription !== 'string') {
123
+ return { matchedDomains: [], agents: [], skills: [] };
124
+ }
125
+
126
+ const rules = loadRules(projectRoot);
127
+ return resolveForTaskWithRules(taskDescription, rules);
128
+ }
129
+
130
+ /**
131
+ * Internal: Matches a task description against domain keywords using pre-loaded rules.
132
+ *
133
+ * @param {string} taskDescription - Human-readable task text
134
+ * @param {object} rules - Pre-loaded rules object
135
+ * @returns {{ matchedDomains: string[], agents: string[], skills: string[] }}
136
+ */
137
+ function resolveForTaskWithRules(taskDescription, rules) {
138
+ const domainRules = rules.domainRules || [];
139
+ const lowerTask = taskDescription.toLowerCase();
140
+
141
+ /** @type {Set<string>} */
142
+ const agents = new Set();
143
+ /** @type {Set<string>} */
144
+ const skills = new Set();
145
+ /** @type {string[]} */
146
+ const matchedDomains = [];
147
+
148
+ for (const rule of domainRules) {
149
+ const hasMatch = (rule.keywords || []).some((keyword) => lowerTask.includes(keyword.toLowerCase()));
150
+
151
+ if (hasMatch) {
152
+ matchedDomains.push(rule.domain);
153
+
154
+ for (const agent of (rule.loadAgents || [])) {
155
+ agents.add(agent);
156
+ }
157
+ for (const skill of (rule.loadSkills || [])) {
158
+ skills.add(skill);
159
+ }
160
+ }
161
+ }
162
+
163
+ return {
164
+ matchedDomains,
165
+ agents: [...agents],
166
+ skills: [...skills],
167
+ };
168
+ }
169
+
170
+ /**
171
+ * Enhanced task resolution for planning workflows.
172
+ * Extends standard keyword matching with implicit trigger detection
173
+ * and mandatory planning resources from planningMandates config.
174
+ *
175
+ * @param {string} taskDescription - Human-readable task text
176
+ * @param {string} projectRoot - Root directory of the project
177
+ * @returns {{ matchedDomains: string[], agents: string[], skills: string[], mandatoryRules: string[] }}
178
+ */
179
+ function resolveForPlanning(taskDescription, projectRoot) {
180
+ if (typeof taskDescription !== 'string') {
181
+ return { matchedDomains: [], agents: [], skills: [], mandatoryRules: [] };
182
+ }
183
+
184
+ const rules = loadRules(projectRoot);
185
+ return resolveForPlanningWithRules(taskDescription, rules);
186
+ }
187
+
188
+ /**
189
+ * Internal: Enhanced planning resolution using pre-loaded rules.
190
+ *
191
+ * @param {string} taskDescription - Human-readable task text
192
+ * @param {object} rules - Pre-loaded rules object
193
+ * @returns {{ matchedDomains: string[], agents: string[], skills: string[], mandatoryRules: string[] }}
194
+ */
195
+ function resolveForPlanningWithRules(taskDescription, rules) {
196
+ const domainRules = rules.domainRules || [];
197
+ const planningMandates = rules.planningMandates || {};
198
+ const lowerTask = taskDescription.toLowerCase();
199
+
200
+ /** @type {Set<string>} */
201
+ const agents = new Set();
202
+ /** @type {Set<string>} */
203
+ const skills = new Set();
204
+ /** @type {string[]} */
205
+ const matchedDomains = [];
206
+
207
+ // Step 1: Standard keyword matching + implicit trigger detection
208
+ for (const rule of domainRules) {
209
+ const hasKeywordMatch = (rule.keywords || []).some(
210
+ (keyword) => lowerTask.includes(keyword.toLowerCase())
211
+ );
212
+
213
+ // Pre-compile a single regex for all implicit triggers per domain rule
214
+ const triggerRegex = buildImplicitTriggerRegex(rule.implicitTriggers);
215
+ const hasImplicitMatch = triggerRegex !== null && triggerRegex.test(lowerTask);
216
+
217
+ if (hasKeywordMatch || hasImplicitMatch) {
218
+ matchedDomains.push(rule.domain);
219
+
220
+ for (const agent of (rule.loadAgents || [])) {
221
+ agents.add(agent);
222
+ }
223
+ for (const skill of (rule.loadSkills || [])) {
224
+ skills.add(skill);
225
+ }
226
+ }
227
+ }
228
+
229
+ // Step 2: Merge mandatory planning skills
230
+ for (const skill of (planningMandates.alwaysLoadSkills || [])) {
231
+ skills.add(skill);
232
+ }
233
+
234
+ // Step 3: Build mandatory rules list (file paths for planner to reference)
235
+ const mandatoryRules = (planningMandates.alwaysLoadRules || []).map(
236
+ (ruleName) => path.join(AGENT_DIR, 'rules', `${ruleName}.md`)
237
+ );
238
+
239
+ return {
240
+ matchedDomains,
241
+ agents: [...agents],
242
+ skills: [...skills],
243
+ mandatoryRules,
244
+ };
245
+ }
246
+
247
+ /**
248
+ * Resolves agents and skills for a named workflow using workflow bindings.
249
+ *
250
+ * @param {string} workflowName - Name of the workflow
251
+ * @param {string} projectRoot - Root directory of the project
252
+ * @returns {{ agents: string[], skills: string[], bindingType: string }}
253
+ */
254
+ function resolveForWorkflow(workflowName, projectRoot) {
255
+ const rules = loadRules(projectRoot);
256
+ return resolveForWorkflowWithRules(workflowName, rules);
257
+ }
258
+
259
+ /**
260
+ * Internal: Resolves workflow bindings using pre-loaded rules.
261
+ *
262
+ * @param {string} workflowName - Name of the workflow
263
+ * @param {object} rules - Pre-loaded rules object
264
+ * @returns {{ agents: string[], skills: string[], bindingType: string }}
265
+ */
266
+ function resolveForWorkflowWithRules(workflowName, rules) {
267
+ const bindings = rules.workflowBindings || [];
268
+ const match = bindings.find((b) => b.workflow === workflowName);
269
+
270
+ if (!match) {
271
+ return { agents: [], skills: [], bindingType: 'none' };
272
+ }
273
+
274
+ return {
275
+ agents: match.loadAgents || [],
276
+ skills: match.loadSkills || [],
277
+ bindingType: match.bindingType || 'inferred',
278
+ };
279
+ }
280
+
281
+ /**
282
+ * Enforces context budget limits by trimming agents and skills.
283
+ * Protected items (from planning mandates) are exempt from trimming.
284
+ *
285
+ * @param {string[]} agents - Candidate agents
286
+ * @param {string[]} skills - Candidate skills
287
+ * @param {string} projectRoot - Root directory of the project
288
+ * @param {ProtectedItems} [protectedItems] - Items exempt from budget trimming
289
+ * @returns {{ agents: string[], skills: string[], trimmed: boolean, warnings: string[] }}
290
+ */
291
+ function enforceContextBudget(agents, skills, projectRoot, protectedItems) {
292
+ const rules = loadRules(projectRoot);
293
+ return enforceContextBudgetWithRules(agents, skills, rules, protectedItems);
294
+ }
295
+
296
+ /**
297
+ * Internal: Enforces context budget using pre-loaded rules.
298
+ *
299
+ * @param {string[]} agents - Candidate agents
300
+ * @param {string[]} skills - Candidate skills
301
+ * @param {object} rules - Pre-loaded rules object
302
+ * @param {ProtectedItems} [protectedItems] - Items exempt from budget trimming
303
+ * @returns {{ agents: string[], skills: string[], trimmed: boolean, warnings: string[] }}
304
+ */
305
+ function enforceContextBudgetWithRules(agents, skills, rules, protectedItems) {
306
+ const budget = rules.contextBudget || {};
307
+ const maxAgents = budget.maxAgentsPerSession || DEFAULT_MAX_AGENTS;
308
+ const maxSkills = budget.maxSkillsPerSession || DEFAULT_MAX_SKILLS;
309
+ const warningThreshold = (budget.warningThresholdPercent || DEFAULT_WARNING_THRESHOLD_PERCENT) / 100;
310
+
311
+ const protectedAgentSet = new Set((protectedItems && protectedItems.agents) || []);
312
+ const protectedSkillSet = new Set((protectedItems && protectedItems.skills) || []);
313
+
314
+ const agentTrim = trimWithProtection([...agents], protectedAgentSet, maxAgents, 'Agent');
315
+ const skillTrim = trimWithProtection([...skills], protectedSkillSet, maxSkills, 'Skill');
316
+
317
+ const warnings = [...agentTrim.warnings, ...skillTrim.warnings];
318
+
319
+ // Add near-limit warnings for non-trimmed cases
320
+ if (!agentTrim.trimmed && agentTrim.result.length >= maxAgents * warningThreshold) {
321
+ warnings.push(`Agent budget near limit: ${agentTrim.result.length}/${maxAgents} (${Math.round(agentTrim.result.length / maxAgents * 100)}%)`);
322
+ }
323
+ if (!skillTrim.trimmed && skillTrim.result.length >= maxSkills * warningThreshold) {
324
+ warnings.push(`Skill budget near limit: ${skillTrim.result.length}/${maxSkills} (${Math.round(skillTrim.result.length / maxSkills * 100)}%)`);
325
+ }
326
+
327
+ return {
328
+ agents: agentTrim.result,
329
+ skills: skillTrim.result,
330
+ trimmed: agentTrim.trimmed || skillTrim.trimmed,
331
+ warnings,
332
+ };
333
+ }
334
+
335
+ /**
336
+ * Full resolution: combines domain matching, workflow binding, and budget enforcement.
337
+ * Uses enhanced planning resolution when workflow is 'plan'.
338
+ * Loads rules once and passes through to all internal functions.
339
+ *
340
+ * @param {string} taskDescription - Task text for domain matching
341
+ * @param {string} [workflowName] - Optional workflow name for binding resolution
342
+ * @param {string} projectRoot - Root directory of the project
343
+ * @returns {LoadPlan}
344
+ */
345
+ function getLoadPlan(taskDescription, workflowName, projectRoot) {
346
+ if (typeof taskDescription !== 'string') {
347
+ return {
348
+ agents: [],
349
+ skills: [],
350
+ warnings: ['Invalid task description'],
351
+ budgetUsage: { agentsUsed: 0, agentsMax: DEFAULT_MAX_AGENTS, skillsUsed: 0, skillsMax: DEFAULT_MAX_SKILLS },
352
+ matchedDomains: [],
353
+ };
354
+ }
355
+
356
+ // Load rules ONCE and pass through to all helpers (H-1 fix)
357
+ const rules = loadRules(projectRoot);
358
+ const budget = rules.contextBudget || {};
359
+ const planningMandates = rules.planningMandates || {};
360
+ const isPlanWorkflow = workflowName === 'plan';
361
+ const maxAgents = budget.maxAgentsPerSession || DEFAULT_MAX_AGENTS;
362
+ const maxSkills = budget.maxSkillsPerSession || DEFAULT_MAX_SKILLS;
363
+
364
+ // Step 1: Domain matching (enhanced for planning workflows)
365
+ const taskResolution = isPlanWorkflow
366
+ ? resolveForPlanningWithRules(taskDescription, rules)
367
+ : resolveForTaskWithRules(taskDescription, rules);
368
+
369
+ // Step 2: Workflow bindings (if workflow specified)
370
+ /** @type {Set<string>} */
371
+ const allAgents = new Set(taskResolution.agents);
372
+ /** @type {Set<string>} */
373
+ const allSkills = new Set(taskResolution.skills);
374
+
375
+ if (workflowName) {
376
+ const wfResolution = resolveForWorkflowWithRules(workflowName, rules);
377
+ for (const agent of wfResolution.agents) {
378
+ allAgents.add(agent);
379
+ }
380
+ for (const skill of wfResolution.skills) {
381
+ allSkills.add(skill);
382
+ }
383
+ }
384
+
385
+ // Step 3: Budget enforcement (with protected items for planning)
386
+ const protectedItems = isPlanWorkflow
387
+ ? { agents: [], skills: planningMandates.alwaysLoadSkills || [] }
388
+ : undefined;
389
+
390
+ const budgetResult = enforceContextBudgetWithRules(
391
+ [...allAgents],
392
+ [...allSkills],
393
+ rules,
394
+ protectedItems
395
+ );
396
+
397
+ // Construct complete object in one expression (H-3 immutability fix)
398
+ return {
399
+ agents: budgetResult.agents,
400
+ skills: budgetResult.skills,
401
+ warnings: budgetResult.warnings,
402
+ budgetUsage: {
403
+ agentsUsed: budgetResult.agents.length,
404
+ agentsMax: maxAgents,
405
+ skillsUsed: budgetResult.skills.length,
406
+ skillsMax: maxSkills,
407
+ },
408
+ matchedDomains: taskResolution.matchedDomains,
409
+ ...(isPlanWorkflow && taskResolution.mandatoryRules
410
+ ? { mandatoryRules: taskResolution.mandatoryRules }
411
+ : {}),
412
+ };
413
+ }
414
+
415
+ module.exports = {
416
+ resolveForTask,
417
+ resolveForPlanning,
418
+ resolveForWorkflow,
419
+ enforceContextBudget,
420
+ getLoadPlan,
421
+ };
package/lib/logger.js ADDED
@@ -0,0 +1,118 @@
1
+ /**
2
+ * Devran AI Kit — Structured Logger
3
+ *
4
+ * Provides structured JSON logging with log levels, correlation IDs,
5
+ * and module context. Replaces raw console.log across all runtime modules.
6
+ *
7
+ * @module lib/logger
8
+ * @author Emre Dursun
9
+ * @since v3.2.0
10
+ */
11
+
12
+ 'use strict';
13
+
14
+ const crypto = require('crypto');
15
+
16
+ /** @typedef {'debug' | 'info' | 'warn' | 'error'} LogLevel */
17
+
18
+ const LOG_LEVELS = { debug: 0, info: 1, warn: 2, error: 3 };
19
+
20
+ /** Default minimum log level */
21
+ let minLevel = LOG_LEVELS[process.env.AG_LOG_LEVEL] ?? LOG_LEVELS.info;
22
+
23
+ /** Output mode: 'json' for structured, 'text' for human-readable */
24
+ let outputMode = process.env.AG_LOG_FORMAT === 'json' ? 'json' : 'text';
25
+
26
+ /**
27
+ * Configures the logger.
28
+ *
29
+ * @param {object} options - Logger configuration
30
+ * @param {LogLevel} [options.level] - Minimum log level
31
+ * @param {'json' | 'text'} [options.format] - Output format
32
+ * @returns {void}
33
+ */
34
+ function configure(options = {}) {
35
+ if (options.level && LOG_LEVELS[options.level] !== undefined) {
36
+ minLevel = LOG_LEVELS[options.level];
37
+ }
38
+ if (options.format === 'json' || options.format === 'text') {
39
+ outputMode = options.format;
40
+ }
41
+ }
42
+
43
+ /**
44
+ * Generates a short correlation ID for request tracing.
45
+ *
46
+ * @returns {string} 8-character hex correlation ID
47
+ */
48
+ function correlationId() {
49
+ return crypto.randomBytes(4).toString('hex');
50
+ }
51
+
52
+ /**
53
+ * Creates a child logger scoped to a specific module.
54
+ *
55
+ * @param {string} moduleName - Module name for context
56
+ * @param {string} [corrId] - Optional correlation ID (auto-generated if omitted)
57
+ * @returns {{ debug: Function, info: Function, warn: Function, error: Function, child: Function }}
58
+ */
59
+ function createLogger(moduleName, corrId) {
60
+ const cid = corrId || correlationId();
61
+
62
+ /**
63
+ * Emits a structured log entry.
64
+ *
65
+ * @param {LogLevel} level - Log level
66
+ * @param {string} message - Log message
67
+ * @param {object} [data] - Additional structured data
68
+ * @returns {void}
69
+ */
70
+ function emit(level, message, data) {
71
+ if (LOG_LEVELS[level] < minLevel) {
72
+ return;
73
+ }
74
+
75
+ const entry = {
76
+ timestamp: new Date().toISOString(),
77
+ level,
78
+ module: moduleName,
79
+ correlationId: cid,
80
+ message,
81
+ ...(data && Object.keys(data).length > 0 ? { data } : {}),
82
+ };
83
+
84
+ if (outputMode === 'json') {
85
+ const stream = level === 'error' ? process.stderr : process.stdout;
86
+ stream.write(JSON.stringify(entry) + '\n');
87
+ } else {
88
+ const prefix = `[${entry.timestamp.slice(11, 19)}] [${level.toUpperCase().padEnd(5)}] [${moduleName}]`;
89
+ const suffix = data && Object.keys(data).length > 0
90
+ ? ` ${JSON.stringify(data)}`
91
+ : '';
92
+ const stream = level === 'error' ? console.error : console.log;
93
+ stream(`${prefix} ${message}${suffix}`);
94
+ }
95
+ }
96
+
97
+ return {
98
+ debug: (message, data) => emit('debug', message, data),
99
+ info: (message, data) => emit('info', message, data),
100
+ warn: (message, data) => emit('warn', message, data),
101
+ error: (message, data) => emit('error', message, data),
102
+
103
+ /**
104
+ * Creates a child logger inheriting the correlation ID.
105
+ *
106
+ * @param {string} childModule - Child module name
107
+ * @returns {ReturnType<typeof createLogger>}
108
+ */
109
+ child: (childModule) => createLogger(childModule, cid),
110
+ };
111
+ }
112
+
113
+ module.exports = {
114
+ createLogger,
115
+ correlationId,
116
+ configure,
117
+ LOG_LEVELS,
118
+ };