@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,274 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Devran AI Kit — Configuration Validator
|
|
3
|
+
*
|
|
4
|
+
* Runtime JSON schema validation for engine configuration files.
|
|
5
|
+
* Catches configuration corruption and drift before they cause
|
|
6
|
+
* runtime failures.
|
|
7
|
+
*
|
|
8
|
+
* @module lib/config-validator
|
|
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 { AGENT_DIR, ENGINE_DIR, HOOKS_DIR } = require('./constants');
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* @typedef {object} ValidationResult
|
|
21
|
+
* @property {boolean} valid - Whether the config is valid
|
|
22
|
+
* @property {string[]} errors - Validation error messages
|
|
23
|
+
* @property {string[]} warnings - Non-critical warnings
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Schema definitions for engine configuration files.
|
|
28
|
+
* Each schema defines required fields, their types, and constraints.
|
|
29
|
+
*/
|
|
30
|
+
const SCHEMAS = {
|
|
31
|
+
'manifest.json': {
|
|
32
|
+
required: ['schemaVersion', 'kitVersion', 'capabilities'],
|
|
33
|
+
types: {
|
|
34
|
+
schemaVersion: 'string',
|
|
35
|
+
kitVersion: 'string',
|
|
36
|
+
capabilities: 'object',
|
|
37
|
+
},
|
|
38
|
+
nested: {
|
|
39
|
+
capabilities: {
|
|
40
|
+
required: ['agents', 'commands', 'skills', 'workflows'],
|
|
41
|
+
types: {
|
|
42
|
+
agents: 'object',
|
|
43
|
+
commands: 'object',
|
|
44
|
+
skills: 'object',
|
|
45
|
+
workflows: 'object',
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
|
|
51
|
+
'workflow-state.json': {
|
|
52
|
+
required: ['currentPhase', 'phases', 'transitions'],
|
|
53
|
+
types: {
|
|
54
|
+
currentPhase: 'string',
|
|
55
|
+
phases: 'object',
|
|
56
|
+
transitions: 'array',
|
|
57
|
+
},
|
|
58
|
+
validators: {
|
|
59
|
+
currentPhase: (value) => {
|
|
60
|
+
const validPhases = ['IDLE', 'EXPLORE', 'PLAN', 'IMPLEMENT', 'VERIFY', 'REVIEW', 'DEPLOY', 'MAINTAIN'];
|
|
61
|
+
return validPhases.includes(value) ? null : `Invalid phase: ${value}. Valid: ${validPhases.join(', ')}`;
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
|
|
66
|
+
'loading-rules.json': {
|
|
67
|
+
required: ['domainRules', 'contextBudget', 'planningMandates'],
|
|
68
|
+
types: {
|
|
69
|
+
domainRules: 'array',
|
|
70
|
+
contextBudget: 'object',
|
|
71
|
+
planningMandates: 'object',
|
|
72
|
+
},
|
|
73
|
+
nested: {
|
|
74
|
+
contextBudget: {
|
|
75
|
+
required: ['maxAgentsPerSession', 'maxSkillsPerSession'],
|
|
76
|
+
types: {
|
|
77
|
+
maxAgentsPerSession: 'number',
|
|
78
|
+
maxSkillsPerSession: 'number',
|
|
79
|
+
},
|
|
80
|
+
},
|
|
81
|
+
planningMandates: {
|
|
82
|
+
required: ['alwaysLoadRules', 'alwaysLoadSkills', 'crossCuttingSections'],
|
|
83
|
+
types: {
|
|
84
|
+
alwaysLoadRules: 'array',
|
|
85
|
+
alwaysLoadSkills: 'array',
|
|
86
|
+
crossCuttingSections: 'array',
|
|
87
|
+
specialistContributors: 'object',
|
|
88
|
+
},
|
|
89
|
+
},
|
|
90
|
+
},
|
|
91
|
+
arrayItemSchema: {
|
|
92
|
+
domainRules: {
|
|
93
|
+
required: ['domain', 'keywords'],
|
|
94
|
+
types: {
|
|
95
|
+
domain: 'string',
|
|
96
|
+
keywords: 'array',
|
|
97
|
+
loadAgents: 'array',
|
|
98
|
+
loadSkills: 'array',
|
|
99
|
+
},
|
|
100
|
+
},
|
|
101
|
+
},
|
|
102
|
+
},
|
|
103
|
+
|
|
104
|
+
'reliability-config.json': {
|
|
105
|
+
required: ['errorBudget'],
|
|
106
|
+
types: {
|
|
107
|
+
errorBudget: 'object',
|
|
108
|
+
},
|
|
109
|
+
nested: {
|
|
110
|
+
errorBudget: {
|
|
111
|
+
required: ['thresholds', 'resetCadence'],
|
|
112
|
+
types: {
|
|
113
|
+
thresholds: 'object',
|
|
114
|
+
resetCadence: 'string',
|
|
115
|
+
},
|
|
116
|
+
},
|
|
117
|
+
},
|
|
118
|
+
},
|
|
119
|
+
|
|
120
|
+
'hooks.json': {
|
|
121
|
+
required: ['hooks'],
|
|
122
|
+
types: {
|
|
123
|
+
hooks: 'array',
|
|
124
|
+
},
|
|
125
|
+
arrayItemSchema: {
|
|
126
|
+
hooks: {
|
|
127
|
+
required: ['event'],
|
|
128
|
+
types: {
|
|
129
|
+
event: 'string',
|
|
130
|
+
},
|
|
131
|
+
},
|
|
132
|
+
},
|
|
133
|
+
},
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Validates a value's type.
|
|
138
|
+
*
|
|
139
|
+
* @param {*} value - Value to check
|
|
140
|
+
* @param {string} expectedType - Expected type string
|
|
141
|
+
* @returns {boolean}
|
|
142
|
+
*/
|
|
143
|
+
function checkType(value, expectedType) {
|
|
144
|
+
if (expectedType === 'array') {
|
|
145
|
+
return Array.isArray(value);
|
|
146
|
+
}
|
|
147
|
+
return typeof value === expectedType;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Validates a configuration object against its schema.
|
|
152
|
+
*
|
|
153
|
+
* @param {object} config - Parsed configuration object
|
|
154
|
+
* @param {object} schema - Schema definition
|
|
155
|
+
* @param {string} [prefix=''] - Field path prefix for nested errors
|
|
156
|
+
* @returns {ValidationResult}
|
|
157
|
+
*/
|
|
158
|
+
function validateAgainstSchema(config, schema, prefix = '') {
|
|
159
|
+
const errors = [];
|
|
160
|
+
const warnings = [];
|
|
161
|
+
|
|
162
|
+
// Check required fields
|
|
163
|
+
for (const field of (schema.required || [])) {
|
|
164
|
+
const fieldPath = prefix ? `${prefix}.${field}` : field;
|
|
165
|
+
if (config[field] === undefined || config[field] === null) {
|
|
166
|
+
errors.push(`Missing required field: ${fieldPath}`);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Check types
|
|
171
|
+
for (const [field, expectedType] of Object.entries(schema.types || {})) {
|
|
172
|
+
const fieldPath = prefix ? `${prefix}.${field}` : field;
|
|
173
|
+
if (config[field] !== undefined && config[field] !== null) {
|
|
174
|
+
if (!checkType(config[field], expectedType)) {
|
|
175
|
+
errors.push(`Invalid type for ${fieldPath}: expected ${expectedType}, got ${Array.isArray(config[field]) ? 'array' : typeof config[field]}`);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Run custom validators
|
|
181
|
+
for (const [field, validator] of Object.entries(schema.validators || {})) {
|
|
182
|
+
if (config[field] !== undefined) {
|
|
183
|
+
const error = validator(config[field]);
|
|
184
|
+
if (error) {
|
|
185
|
+
errors.push(error);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Validate nested schemas
|
|
191
|
+
for (const [field, nestedSchema] of Object.entries(schema.nested || {})) {
|
|
192
|
+
if (config[field] && typeof config[field] === 'object' && !Array.isArray(config[field])) {
|
|
193
|
+
const nestedResult = validateAgainstSchema(config[field], nestedSchema, prefix ? `${prefix}.${field}` : field);
|
|
194
|
+
errors.push(...nestedResult.errors);
|
|
195
|
+
warnings.push(...nestedResult.warnings);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// Validate array items
|
|
200
|
+
for (const [field, itemSchema] of Object.entries(schema.arrayItemSchema || {})) {
|
|
201
|
+
if (Array.isArray(config[field])) {
|
|
202
|
+
config[field].forEach((item, index) => {
|
|
203
|
+
if (typeof item === 'object' && item !== null) {
|
|
204
|
+
const itemResult = validateAgainstSchema(item, itemSchema, `${field}[${index}]`);
|
|
205
|
+
errors.push(...itemResult.errors);
|
|
206
|
+
warnings.push(...itemResult.warnings);
|
|
207
|
+
}
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
return { valid: errors.length === 0, errors, warnings };
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Validates a specific engine configuration file.
|
|
217
|
+
*
|
|
218
|
+
* @param {string} projectRoot - Root directory of the project
|
|
219
|
+
* @param {string} configName - Configuration file name (e.g., 'manifest.json')
|
|
220
|
+
* @returns {ValidationResult}
|
|
221
|
+
*/
|
|
222
|
+
function validateConfig(projectRoot, configName) {
|
|
223
|
+
const schema = SCHEMAS[configName];
|
|
224
|
+
if (!schema) {
|
|
225
|
+
return { valid: false, errors: [`No schema defined for: ${configName}`], warnings: [] };
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
const configDir = configName === 'manifest.json'
|
|
229
|
+
? path.join(projectRoot, AGENT_DIR)
|
|
230
|
+
: configName === 'hooks.json'
|
|
231
|
+
? path.join(projectRoot, AGENT_DIR, HOOKS_DIR)
|
|
232
|
+
: path.join(projectRoot, AGENT_DIR, ENGINE_DIR);
|
|
233
|
+
|
|
234
|
+
const configPath = path.join(configDir, configName);
|
|
235
|
+
|
|
236
|
+
if (!fs.existsSync(configPath)) {
|
|
237
|
+
return { valid: false, errors: [`Config file not found: ${configPath}`], warnings: [] };
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
let config;
|
|
241
|
+
try {
|
|
242
|
+
config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
|
|
243
|
+
} catch (parseError) {
|
|
244
|
+
return { valid: false, errors: [`Invalid JSON: ${parseError.message}`], warnings: [] };
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
return validateAgainstSchema(config, schema);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* Validates all known engine configuration files.
|
|
252
|
+
*
|
|
253
|
+
* @param {string} projectRoot - Root directory of the project
|
|
254
|
+
* @returns {{ totalConfigs: number, validConfigs: number, results: Object.<string, ValidationResult> }}
|
|
255
|
+
*/
|
|
256
|
+
function validateAllConfigs(projectRoot) {
|
|
257
|
+
const entries = Object.keys(SCHEMAS).map(
|
|
258
|
+
(configName) => [configName, validateConfig(projectRoot, configName)]
|
|
259
|
+
);
|
|
260
|
+
const results = Object.fromEntries(entries);
|
|
261
|
+
const validCount = entries.filter(([, result]) => result.valid).length;
|
|
262
|
+
|
|
263
|
+
return {
|
|
264
|
+
totalConfigs: Object.keys(SCHEMAS).length,
|
|
265
|
+
validConfigs: validCount,
|
|
266
|
+
results,
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
module.exports = {
|
|
271
|
+
validateConfig,
|
|
272
|
+
validateAllConfigs,
|
|
273
|
+
SCHEMAS,
|
|
274
|
+
};
|
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Devran AI Kit — Agent Conflict Detection
|
|
3
|
+
*
|
|
4
|
+
* Tracks file ownership by agents and detects concurrent modifications.
|
|
5
|
+
* Uses JSON-based locks for cross-platform compatibility.
|
|
6
|
+
*
|
|
7
|
+
* @module lib/conflict-detector
|
|
8
|
+
* @author Emre Dursun
|
|
9
|
+
* @since v3.0.0
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
'use strict';
|
|
13
|
+
|
|
14
|
+
const fs = require('fs');
|
|
15
|
+
const path = require('path');
|
|
16
|
+
|
|
17
|
+
const { AGENT_DIR, ENGINE_DIR } = require('./constants');
|
|
18
|
+
const { writeJsonAtomic } = require('./io');
|
|
19
|
+
const FILE_LOCKS_FILE = 'file-locks.json';
|
|
20
|
+
|
|
21
|
+
/** Default lock TTL in milliseconds (30 minutes) */
|
|
22
|
+
const DEFAULT_LOCK_TTL_MS = 30 * 60 * 1000;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* @typedef {object} FileLock
|
|
26
|
+
* @property {string} filePath - Relative path to the claimed file
|
|
27
|
+
* @property {string} agent - Agent name holding the lock
|
|
28
|
+
* @property {string} claimedAt - ISO timestamp
|
|
29
|
+
* @property {number} ttlMs - Time-to-live in milliseconds
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* @typedef {object} ConflictReport
|
|
34
|
+
* @property {string} filePath - Path with conflict
|
|
35
|
+
* @property {string[]} agents - Agents claiming this file
|
|
36
|
+
* @property {'warning' | 'blocking'} severity - Conflict severity
|
|
37
|
+
*/
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Resolves the file locks path.
|
|
41
|
+
*
|
|
42
|
+
* @param {string} projectRoot - Root directory of the project
|
|
43
|
+
* @returns {string}
|
|
44
|
+
*/
|
|
45
|
+
function resolveLocksPath(projectRoot) {
|
|
46
|
+
return path.join(projectRoot, AGENT_DIR, ENGINE_DIR, FILE_LOCKS_FILE);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Loads current file locks, filtering out stale ones.
|
|
51
|
+
*
|
|
52
|
+
* @param {string} projectRoot - Root directory of the project
|
|
53
|
+
* @returns {FileLock[]}
|
|
54
|
+
*/
|
|
55
|
+
function loadLocks(projectRoot) {
|
|
56
|
+
const locksPath = resolveLocksPath(projectRoot);
|
|
57
|
+
|
|
58
|
+
if (!fs.existsSync(locksPath)) {
|
|
59
|
+
return [];
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
try {
|
|
63
|
+
const data = JSON.parse(fs.readFileSync(locksPath, 'utf-8'));
|
|
64
|
+
const locks = data.locks || [];
|
|
65
|
+
const now = Date.now();
|
|
66
|
+
|
|
67
|
+
// Filter out expired locks
|
|
68
|
+
const activeLocks = locks.filter((lock) => {
|
|
69
|
+
const claimedTime = new Date(lock.claimedAt).getTime();
|
|
70
|
+
return (now - claimedTime) < (lock.ttlMs || DEFAULT_LOCK_TTL_MS);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
// Persist pruned list if stale locks were removed
|
|
74
|
+
if (activeLocks.length < locks.length) {
|
|
75
|
+
writeLocks(projectRoot, activeLocks);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return activeLocks;
|
|
79
|
+
} catch {
|
|
80
|
+
return [];
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Writes locks to disk atomically.
|
|
86
|
+
*
|
|
87
|
+
* @param {string} projectRoot - Root directory of the project
|
|
88
|
+
* @param {FileLock[]} locks - Current locks
|
|
89
|
+
* @returns {void}
|
|
90
|
+
*/
|
|
91
|
+
function writeLocks(projectRoot, locks) {
|
|
92
|
+
const locksPath = resolveLocksPath(projectRoot);
|
|
93
|
+
const data = {
|
|
94
|
+
schemaVersion: '1.0.0',
|
|
95
|
+
lastUpdated: new Date().toISOString(),
|
|
96
|
+
locks,
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
writeJsonAtomic(locksPath, data);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Claims a file for an agent.
|
|
104
|
+
*
|
|
105
|
+
* @param {string} projectRoot - Root directory of the project
|
|
106
|
+
* @param {string} filePath - Relative path to file
|
|
107
|
+
* @param {string} agent - Agent name
|
|
108
|
+
* @param {number} [ttlMs] - Lock TTL in milliseconds
|
|
109
|
+
* @returns {{ success: boolean, conflict?: ConflictReport }}
|
|
110
|
+
*/
|
|
111
|
+
function claimFile(projectRoot, filePath, agent, ttlMs) {
|
|
112
|
+
const locks = loadLocks(projectRoot);
|
|
113
|
+
const normalizedPath = filePath.replace(/\\/g, '/');
|
|
114
|
+
const lockTtl = ttlMs || DEFAULT_LOCK_TTL_MS;
|
|
115
|
+
|
|
116
|
+
// Check for existing claim by a different agent
|
|
117
|
+
const existingLock = locks.find((l) => l.filePath === normalizedPath && l.agent !== agent);
|
|
118
|
+
|
|
119
|
+
if (existingLock) {
|
|
120
|
+
return {
|
|
121
|
+
success: false,
|
|
122
|
+
conflict: {
|
|
123
|
+
filePath: normalizedPath,
|
|
124
|
+
agents: [existingLock.agent, agent],
|
|
125
|
+
severity: 'blocking',
|
|
126
|
+
},
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Update or create lock immutably
|
|
131
|
+
const existingIndex = locks.findIndex((l) => l.filePath === normalizedPath && l.agent === agent);
|
|
132
|
+
const now = new Date().toISOString();
|
|
133
|
+
|
|
134
|
+
let updatedLocks;
|
|
135
|
+
if (existingIndex !== -1) {
|
|
136
|
+
updatedLocks = locks.map((l, i) =>
|
|
137
|
+
i === existingIndex
|
|
138
|
+
? { ...l, claimedAt: now, ttlMs: lockTtl }
|
|
139
|
+
: l
|
|
140
|
+
);
|
|
141
|
+
} else {
|
|
142
|
+
updatedLocks = [
|
|
143
|
+
...locks,
|
|
144
|
+
{
|
|
145
|
+
filePath: normalizedPath,
|
|
146
|
+
agent,
|
|
147
|
+
claimedAt: now,
|
|
148
|
+
ttlMs: lockTtl,
|
|
149
|
+
},
|
|
150
|
+
];
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
writeLocks(projectRoot, updatedLocks);
|
|
154
|
+
return { success: true };
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Releases a file lock held by an agent.
|
|
159
|
+
*
|
|
160
|
+
* @param {string} projectRoot - Root directory of the project
|
|
161
|
+
* @param {string} filePath - Relative path to file
|
|
162
|
+
* @param {string} agent - Agent name
|
|
163
|
+
* @returns {{ success: boolean }}
|
|
164
|
+
*/
|
|
165
|
+
function releaseFile(projectRoot, filePath, agent) {
|
|
166
|
+
const locks = loadLocks(projectRoot);
|
|
167
|
+
const normalizedPath = filePath.replace(/\\/g, '/');
|
|
168
|
+
const filteredLocks = locks.filter((l) => !(l.filePath === normalizedPath && l.agent === agent));
|
|
169
|
+
|
|
170
|
+
if (filteredLocks.length === locks.length) {
|
|
171
|
+
return { success: false };
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
writeLocks(projectRoot, filteredLocks);
|
|
175
|
+
return { success: true };
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Detects all current file conflicts.
|
|
180
|
+
*
|
|
181
|
+
* @param {string} projectRoot - Root directory of the project
|
|
182
|
+
* @returns {ConflictReport[]}
|
|
183
|
+
*/
|
|
184
|
+
function detectConflicts(projectRoot) {
|
|
185
|
+
const locks = loadLocks(projectRoot);
|
|
186
|
+
/** @type {Map<string, string[]>} */
|
|
187
|
+
const fileAgents = new Map();
|
|
188
|
+
|
|
189
|
+
for (const lock of locks) {
|
|
190
|
+
const agents = fileAgents.get(lock.filePath) || [];
|
|
191
|
+
if (!agents.includes(lock.agent)) {
|
|
192
|
+
agents.push(lock.agent);
|
|
193
|
+
}
|
|
194
|
+
fileAgents.set(lock.filePath, agents);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/** @type {ConflictReport[]} */
|
|
198
|
+
const conflicts = [];
|
|
199
|
+
|
|
200
|
+
for (const [filePath, agents] of fileAgents.entries()) {
|
|
201
|
+
if (agents.length > 1) {
|
|
202
|
+
conflicts.push({
|
|
203
|
+
filePath,
|
|
204
|
+
agents,
|
|
205
|
+
severity: agents.length > 2 ? 'blocking' : 'warning',
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
return conflicts;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Gets file ownership information.
|
|
215
|
+
*
|
|
216
|
+
* @param {string} projectRoot - Root directory of the project
|
|
217
|
+
* @returns {Array<{ filePath: string, agent: string, claimedAt: string }>}
|
|
218
|
+
*/
|
|
219
|
+
function getFileOwnership(projectRoot) {
|
|
220
|
+
const locks = loadLocks(projectRoot);
|
|
221
|
+
|
|
222
|
+
return locks.map((lock) => ({
|
|
223
|
+
filePath: lock.filePath,
|
|
224
|
+
agent: lock.agent,
|
|
225
|
+
claimedAt: lock.claimedAt,
|
|
226
|
+
}));
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Generates a full conflict report with metrics.
|
|
231
|
+
*
|
|
232
|
+
* @param {string} projectRoot - Root directory of the project
|
|
233
|
+
* @returns {{ activeLocks: number, conflicts: ConflictReport[], hasBlockingConflict: boolean }}
|
|
234
|
+
*/
|
|
235
|
+
function reportConflicts(projectRoot) {
|
|
236
|
+
const locks = loadLocks(projectRoot);
|
|
237
|
+
const conflicts = detectConflicts(projectRoot);
|
|
238
|
+
|
|
239
|
+
return {
|
|
240
|
+
activeLocks: locks.length,
|
|
241
|
+
conflicts,
|
|
242
|
+
hasBlockingConflict: conflicts.some((c) => c.severity === 'blocking'),
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
module.exports = {
|
|
247
|
+
claimFile,
|
|
248
|
+
releaseFile,
|
|
249
|
+
detectConflicts,
|
|
250
|
+
getFileOwnership,
|
|
251
|
+
reportConflicts,
|
|
252
|
+
};
|
package/lib/constants.js
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Devran AI Kit — Shared Constants
|
|
3
|
+
*
|
|
4
|
+
* Central definition of directory names and paths used across
|
|
5
|
+
* all runtime modules. Prevents drift from duplicated strings.
|
|
6
|
+
* Frozen to prevent accidental mutation at runtime.
|
|
7
|
+
*
|
|
8
|
+
* @module lib/constants
|
|
9
|
+
* @since v3.2.0
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
'use strict';
|
|
13
|
+
|
|
14
|
+
/** Root agent configuration directory name */
|
|
15
|
+
const AGENT_DIR = '.agent';
|
|
16
|
+
|
|
17
|
+
/** Engine subdirectory within .agent */
|
|
18
|
+
const ENGINE_DIR = 'engine';
|
|
19
|
+
|
|
20
|
+
/** Hooks subdirectory within .agent */
|
|
21
|
+
const HOOKS_DIR = 'hooks';
|
|
22
|
+
|
|
23
|
+
/** Skills subdirectory within .agent */
|
|
24
|
+
const SKILLS_DIR = 'skills';
|
|
25
|
+
|
|
26
|
+
/** Plugins subdirectory within .agent */
|
|
27
|
+
const PLUGINS_DIR = 'plugins';
|
|
28
|
+
|
|
29
|
+
/** Cursor IDE configuration directory */
|
|
30
|
+
const CURSOR_DIR = '.cursor';
|
|
31
|
+
|
|
32
|
+
/** OpenCode IDE configuration directory */
|
|
33
|
+
const OPENCODE_DIR = '.opencode';
|
|
34
|
+
|
|
35
|
+
/** Codex IDE configuration directory */
|
|
36
|
+
const CODEX_DIR = '.codex';
|
|
37
|
+
|
|
38
|
+
module.exports = Object.freeze({
|
|
39
|
+
AGENT_DIR,
|
|
40
|
+
ENGINE_DIR,
|
|
41
|
+
HOOKS_DIR,
|
|
42
|
+
SKILLS_DIR,
|
|
43
|
+
PLUGINS_DIR,
|
|
44
|
+
CURSOR_DIR,
|
|
45
|
+
OPENCODE_DIR,
|
|
46
|
+
CODEX_DIR,
|
|
47
|
+
});
|