@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.
- package/.agent/CheatSheet.md +350 -0
- package/.agent/README.md +76 -0
- package/.agent/agents/README.md +155 -0
- package/.agent/agents/architect.md +185 -0
- package/.agent/agents/backend-specialist.md +276 -0
- package/.agent/agents/build-error-resolver.md +207 -0
- package/.agent/agents/code-reviewer.md +162 -0
- package/.agent/agents/database-architect.md +138 -0
- package/.agent/agents/devops-engineer.md +144 -0
- package/.agent/agents/doc-updater.md +229 -0
- package/.agent/agents/e2e-runner.md +145 -0
- package/.agent/agents/explorer-agent.md +143 -0
- package/.agent/agents/frontend-specialist.md +144 -0
- package/.agent/agents/go-reviewer.md +128 -0
- package/.agent/agents/knowledge-agent.md +197 -0
- package/.agent/agents/mobile-developer.md +150 -0
- package/.agent/agents/performance-optimizer.md +175 -0
- package/.agent/agents/planner.md +133 -0
- package/.agent/agents/pr-reviewer.md +148 -0
- package/.agent/agents/python-reviewer.md +123 -0
- package/.agent/agents/refactor-cleaner.md +201 -0
- package/.agent/agents/reliability-engineer.md +156 -0
- package/.agent/agents/security-reviewer.md +141 -0
- package/.agent/agents/sprint-orchestrator.md +124 -0
- package/.agent/agents/tdd-guide.md +179 -0
- package/.agent/agents/typescript-reviewer.md +110 -0
- package/.agent/checklists/README.md +102 -0
- package/.agent/checklists/pre-commit.md +93 -0
- package/.agent/checklists/session-end.md +99 -0
- package/.agent/checklists/session-start.md +102 -0
- package/.agent/checklists/task-complete.md +81 -0
- package/.agent/commands/README.md +130 -0
- package/.agent/commands/adr.md +29 -0
- package/.agent/commands/ask.md +28 -0
- package/.agent/commands/build.md +30 -0
- package/.agent/commands/changelog.md +40 -0
- package/.agent/commands/checkpoint.md +28 -0
- package/.agent/commands/code-review.md +65 -0
- package/.agent/commands/compact.md +28 -0
- package/.agent/commands/cook.md +30 -0
- package/.agent/commands/db.md +30 -0
- package/.agent/commands/debug.md +31 -0
- package/.agent/commands/deploy.md +37 -0
- package/.agent/commands/design.md +29 -0
- package/.agent/commands/doc.md +30 -0
- package/.agent/commands/eval.md +30 -0
- package/.agent/commands/fix.md +32 -0
- package/.agent/commands/git.md +32 -0
- package/.agent/commands/help.md +273 -0
- package/.agent/commands/implement.md +30 -0
- package/.agent/commands/integrate.md +32 -0
- package/.agent/commands/learn.md +29 -0
- package/.agent/commands/perf.md +31 -0
- package/.agent/commands/plan.md +56 -0
- package/.agent/commands/pr-describe.md +65 -0
- package/.agent/commands/pr-fix.md +45 -0
- package/.agent/commands/pr-merge.md +45 -0
- package/.agent/commands/pr-review.md +50 -0
- package/.agent/commands/pr-split.md +54 -0
- package/.agent/commands/pr-status.md +56 -0
- package/.agent/commands/pr.md +58 -0
- package/.agent/commands/refactor.md +32 -0
- package/.agent/commands/research.md +28 -0
- package/.agent/commands/scout.md +30 -0
- package/.agent/commands/security-scan.md +33 -0
- package/.agent/commands/setup.md +31 -0
- package/.agent/commands/status.md +59 -0
- package/.agent/commands/tdd.md +73 -0
- package/.agent/commands/verify.md +58 -0
- package/.agent/contexts/brainstorm.md +26 -0
- package/.agent/contexts/debug.md +28 -0
- package/.agent/contexts/implement.md +29 -0
- package/.agent/contexts/plan-quality-log.md +30 -0
- package/.agent/contexts/review.md +27 -0
- package/.agent/contexts/ship.md +28 -0
- package/.agent/decisions/001-trust-grade-governance.md +46 -0
- package/.agent/decisions/002-cross-ide-generation.md +15 -0
- package/.agent/engine/identity.json +4 -0
- package/.agent/engine/loading-rules.json +193 -0
- package/.agent/engine/marketplace-index.json +29 -0
- package/.agent/engine/mcp-servers/filesystem.json +9 -0
- package/.agent/engine/mcp-servers/github.json +11 -0
- package/.agent/engine/mcp-servers/postgres.json +11 -0
- package/.agent/engine/mcp-servers/supabase.json +11 -0
- package/.agent/engine/mcp-servers/vercel.json +11 -0
- package/.agent/engine/reliability-config.json +14 -0
- package/.agent/engine/sdlc-map.json +50 -0
- package/.agent/engine/workflow-state.json +167 -0
- package/.agent/hooks/README.md +101 -0
- package/.agent/hooks/hooks.json +104 -0
- package/.agent/hooks/templates/session-end.md +110 -0
- package/.agent/hooks/templates/session-start.md +95 -0
- package/.agent/manifest.json +466 -0
- package/.agent/rules/agent-upgrade-policy.md +56 -0
- package/.agent/rules/architecture.md +111 -0
- package/.agent/rules/coding-style.md +75 -0
- package/.agent/rules/documentation.md +74 -0
- package/.agent/rules/git-workflow.md +140 -0
- package/.agent/rules/quality-gate.md +117 -0
- package/.agent/rules/security.md +67 -0
- package/.agent/rules/sprint-tracking.md +103 -0
- package/.agent/rules/testing.md +80 -0
- package/.agent/rules/workflow-standards.md +30 -0
- package/.agent/rules.md +293 -0
- package/.agent/session-context.md +69 -0
- package/.agent/session-state.json +27 -0
- package/.agent/skills/README.md +135 -0
- package/.agent/skills/api-patterns/SKILL.md +117 -0
- package/.agent/skills/app-builder/SKILL.md +202 -0
- package/.agent/skills/architecture/SKILL.md +101 -0
- package/.agent/skills/behavioral-modes/SKILL.md +295 -0
- package/.agent/skills/brainstorming/SKILL.md +156 -0
- package/.agent/skills/clean-code/SKILL.md +142 -0
- package/.agent/skills/context-budget/SKILL.md +78 -0
- package/.agent/skills/continuous-learning/SKILL.md +145 -0
- package/.agent/skills/database-design/SKILL.md +303 -0
- package/.agent/skills/debugging-strategies/SKILL.md +158 -0
- package/.agent/skills/deployment-procedures/SKILL.md +191 -0
- package/.agent/skills/docker-patterns/SKILL.md +161 -0
- package/.agent/skills/eval-harness/SKILL.md +89 -0
- package/.agent/skills/frontend-patterns/SKILL.md +141 -0
- package/.agent/skills/git-workflow/SKILL.md +159 -0
- package/.agent/skills/i18n-localization/SKILL.md +191 -0
- package/.agent/skills/intelligent-routing/SKILL.md +180 -0
- package/.agent/skills/mcp-integration/SKILL.md +240 -0
- package/.agent/skills/mobile-design/SKILL.md +191 -0
- package/.agent/skills/nodejs-patterns/SKILL.md +164 -0
- package/.agent/skills/parallel-agents/SKILL.md +200 -0
- package/.agent/skills/performance-profiling/SKILL.md +134 -0
- package/.agent/skills/plan-validation/SKILL.md +192 -0
- package/.agent/skills/plan-writing/SKILL.md +183 -0
- package/.agent/skills/plan-writing/domain-enhancers.md +184 -0
- package/.agent/skills/plan-writing/plan-retrospective.md +116 -0
- package/.agent/skills/plan-writing/plan-schema.md +119 -0
- package/.agent/skills/pr-toolkit/SKILL.md +174 -0
- package/.agent/skills/production-readiness/SKILL.md +126 -0
- package/.agent/skills/security-practices/SKILL.md +109 -0
- package/.agent/skills/shell-conventions/SKILL.md +92 -0
- package/.agent/skills/strategic-compact/SKILL.md +62 -0
- package/.agent/skills/testing-patterns/SKILL.md +141 -0
- package/.agent/skills/typescript-expert/SKILL.md +160 -0
- package/.agent/skills/ui-ux-pro-max/SKILL.md +137 -0
- package/.agent/skills/ui-ux-pro-max/data/charts.csv +26 -0
- package/.agent/skills/ui-ux-pro-max/data/colors.csv +97 -0
- package/.agent/skills/ui-ux-pro-max/data/icons.csv +101 -0
- package/.agent/skills/ui-ux-pro-max/data/landing.csv +31 -0
- package/.agent/skills/ui-ux-pro-max/data/products.csv +97 -0
- package/.agent/skills/ui-ux-pro-max/data/react-performance.csv +45 -0
- package/.agent/skills/ui-ux-pro-max/data/stacks/astro.csv +54 -0
- package/.agent/skills/ui-ux-pro-max/data/stacks/flutter.csv +53 -0
- package/.agent/skills/ui-ux-pro-max/data/stacks/html-tailwind.csv +56 -0
- package/.agent/skills/ui-ux-pro-max/data/stacks/jetpack-compose.csv +53 -0
- package/.agent/skills/ui-ux-pro-max/data/stacks/nextjs.csv +53 -0
- package/.agent/skills/ui-ux-pro-max/data/stacks/nuxt-ui.csv +51 -0
- package/.agent/skills/ui-ux-pro-max/data/stacks/nuxtjs.csv +59 -0
- package/.agent/skills/ui-ux-pro-max/data/stacks/react-native.csv +52 -0
- package/.agent/skills/ui-ux-pro-max/data/stacks/react.csv +54 -0
- package/.agent/skills/ui-ux-pro-max/data/stacks/shadcn.csv +61 -0
- package/.agent/skills/ui-ux-pro-max/data/stacks/svelte.csv +54 -0
- package/.agent/skills/ui-ux-pro-max/data/stacks/swiftui.csv +51 -0
- package/.agent/skills/ui-ux-pro-max/data/stacks/vue.csv +50 -0
- package/.agent/skills/ui-ux-pro-max/data/styles.csv +68 -0
- package/.agent/skills/ui-ux-pro-max/data/typography.csv +58 -0
- package/.agent/skills/ui-ux-pro-max/data/ui-reasoning.csv +101 -0
- package/.agent/skills/ui-ux-pro-max/data/ux-guidelines.csv +100 -0
- package/.agent/skills/ui-ux-pro-max/data/web-interface.csv +31 -0
- package/.agent/skills/ui-ux-pro-max/scripts/core.py +253 -0
- package/.agent/skills/ui-ux-pro-max/scripts/design_system.py +1067 -0
- package/.agent/skills/ui-ux-pro-max/scripts/search.py +114 -0
- package/.agent/skills/verification-loop/SKILL.md +89 -0
- package/.agent/skills/webapp-testing/SKILL.md +175 -0
- package/.agent/templates/adr-template.md +32 -0
- package/.agent/templates/bug-report.md +37 -0
- package/.agent/templates/feature-request.md +32 -0
- package/.agent/workflows/README.md +101 -0
- package/.agent/workflows/brainstorm.md +86 -0
- package/.agent/workflows/create.md +85 -0
- package/.agent/workflows/debug.md +83 -0
- package/.agent/workflows/deploy.md +114 -0
- package/.agent/workflows/enhance.md +85 -0
- package/.agent/workflows/orchestrate.md +106 -0
- package/.agent/workflows/plan.md +105 -0
- package/.agent/workflows/pr-fix.md +163 -0
- package/.agent/workflows/pr-merge.md +117 -0
- package/.agent/workflows/pr-review.md +178 -0
- package/.agent/workflows/pr-split.md +118 -0
- package/.agent/workflows/pr.md +184 -0
- package/.agent/workflows/preflight.md +107 -0
- package/.agent/workflows/preview.md +95 -0
- package/.agent/workflows/quality-gate.md +103 -0
- package/.agent/workflows/retrospective.md +100 -0
- package/.agent/workflows/review.md +104 -0
- package/.agent/workflows/status.md +89 -0
- package/.agent/workflows/test.md +98 -0
- package/.agent/workflows/ui-ux-pro-max.md +93 -0
- package/.agent/workflows/upgrade.md +97 -0
- package/LICENSE +21 -0
- package/README.md +218 -0
- package/bin/kit.js +773 -0
- package/lib/agent-registry.js +228 -0
- package/lib/agent-reputation.js +343 -0
- package/lib/circuit-breaker.js +195 -0
- package/lib/cli-commands.js +322 -0
- package/lib/config-validator.js +274 -0
- package/lib/conflict-detector.js +252 -0
- package/lib/constants.js +47 -0
- package/lib/engineering-manager.js +336 -0
- package/lib/error-budget.js +370 -0
- package/lib/hook-system.js +256 -0
- package/lib/ide-generator.js +434 -0
- package/lib/identity.js +240 -0
- package/lib/io.js +146 -0
- package/lib/learning-engine.js +163 -0
- package/lib/loading-engine.js +421 -0
- package/lib/logger.js +118 -0
- package/lib/marketplace.js +321 -0
- package/lib/plugin-system.js +604 -0
- package/lib/plugin-verifier.js +197 -0
- package/lib/rate-limiter.js +113 -0
- package/lib/security-scanner.js +312 -0
- package/lib/self-healing.js +468 -0
- package/lib/session-manager.js +264 -0
- package/lib/skill-sandbox.js +244 -0
- package/lib/task-governance.js +522 -0
- package/lib/task-model.js +332 -0
- package/lib/updater.js +240 -0
- package/lib/verify.js +279 -0
- package/lib/workflow-engine.js +373 -0
- package/lib/workflow-events.js +166 -0
- package/lib/workflow-persistence.js +160 -0
- 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
|
+
};
|