@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,228 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Devran AI Kit — Agent Registry
|
|
3
|
+
*
|
|
4
|
+
* Formalizes agent contracts by validating that every agent markdown
|
|
5
|
+
* file exposes required metadata structure.
|
|
6
|
+
*
|
|
7
|
+
* @module lib/agent-registry
|
|
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 } = require('./constants');
|
|
18
|
+
const AGENTS_SUBDIR = 'agents';
|
|
19
|
+
const MANIFEST_FILE = 'manifest.json';
|
|
20
|
+
|
|
21
|
+
/** Minimum expected file size for a valid agent file (bytes) */
|
|
22
|
+
const MIN_AGENT_SIZE = 100;
|
|
23
|
+
/** Maximum expected file size for a valid agent file (bytes) */
|
|
24
|
+
const MAX_AGENT_SIZE = 50000;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* @typedef {object} AgentValidation
|
|
28
|
+
* @property {string} name - Agent name
|
|
29
|
+
* @property {boolean} valid - Overall validity
|
|
30
|
+
* @property {string[]} errors - Validation errors
|
|
31
|
+
* @property {string[]} warnings - Validation warnings
|
|
32
|
+
* @property {object} metadata - Extracted metadata
|
|
33
|
+
*/
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* @typedef {object} RegistryReport
|
|
37
|
+
* @property {number} total - Total agents checked
|
|
38
|
+
* @property {number} valid - Number of valid agents
|
|
39
|
+
* @property {number} invalid - Number of invalid agents
|
|
40
|
+
* @property {AgentValidation[]} agents - Individual agent results
|
|
41
|
+
*/
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Extracts metadata from an agent markdown file by parsing its structure.
|
|
45
|
+
*
|
|
46
|
+
* @param {string} content - Raw markdown content
|
|
47
|
+
* @returns {object} Extracted metadata
|
|
48
|
+
*/
|
|
49
|
+
function extractAgentMetadata(content) {
|
|
50
|
+
const lines = content.split('\n');
|
|
51
|
+
const metadata = {
|
|
52
|
+
hasTitle: false,
|
|
53
|
+
title: '',
|
|
54
|
+
hasRoleDescription: false,
|
|
55
|
+
hasCapabilities: false,
|
|
56
|
+
hasOutputFormat: false,
|
|
57
|
+
headingCount: 0,
|
|
58
|
+
lineCount: lines.length,
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
for (const line of lines) {
|
|
62
|
+
const trimmed = line.trim();
|
|
63
|
+
|
|
64
|
+
// Check for title (# heading)
|
|
65
|
+
if (trimmed.startsWith('# ') && !metadata.hasTitle) {
|
|
66
|
+
metadata.hasTitle = true;
|
|
67
|
+
metadata.title = trimmed.slice(2).trim();
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Count all headings
|
|
71
|
+
if (trimmed.startsWith('#')) {
|
|
72
|
+
metadata.headingCount += 1;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Check for role/responsibility keywords
|
|
76
|
+
const roleLower = trimmed.toLowerCase();
|
|
77
|
+
if (roleLower.includes('role') || roleLower.includes('responsibility') || roleLower.includes('purpose') || roleLower.includes('identity')) {
|
|
78
|
+
metadata.hasRoleDescription = true;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Check for capabilities/skills mentions
|
|
82
|
+
if (roleLower.includes('capabilit') || roleLower.includes('skill') || roleLower.includes('tool') || roleLower.includes('expertise')) {
|
|
83
|
+
metadata.hasCapabilities = true;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Check for output format
|
|
87
|
+
if (roleLower.includes('output') || roleLower.includes('format') || roleLower.includes('deliver') || roleLower.includes('produce')) {
|
|
88
|
+
metadata.hasOutputFormat = true;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return metadata;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Validates a single agent file against contract requirements.
|
|
97
|
+
*
|
|
98
|
+
* @param {string} agentName - Name of the agent
|
|
99
|
+
* @param {string} projectRoot - Root directory of the project
|
|
100
|
+
* @returns {AgentValidation}
|
|
101
|
+
*/
|
|
102
|
+
function validateAgent(agentName, projectRoot) {
|
|
103
|
+
const agentPath = path.join(projectRoot, AGENT_DIR, AGENTS_SUBDIR, `${agentName}.md`);
|
|
104
|
+
/** @type {string[]} */
|
|
105
|
+
const errors = [];
|
|
106
|
+
/** @type {string[]} */
|
|
107
|
+
const warnings = [];
|
|
108
|
+
|
|
109
|
+
if (!fs.existsSync(agentPath)) {
|
|
110
|
+
return { name: agentName, valid: false, errors: [`File not found: ${agentName}.md`], warnings: [], metadata: {} };
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const stats = fs.statSync(agentPath);
|
|
114
|
+
const content = fs.readFileSync(agentPath, 'utf-8');
|
|
115
|
+
const metadata = extractAgentMetadata(content);
|
|
116
|
+
|
|
117
|
+
// Size checks
|
|
118
|
+
if (stats.size < MIN_AGENT_SIZE) {
|
|
119
|
+
errors.push(`File too small (${stats.size} bytes) — likely incomplete`);
|
|
120
|
+
}
|
|
121
|
+
if (stats.size > MAX_AGENT_SIZE) {
|
|
122
|
+
warnings.push(`File very large (${stats.size} bytes) — consider splitting`);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Contract checks
|
|
126
|
+
if (!metadata.hasTitle) {
|
|
127
|
+
errors.push('Missing title header (# AgentName)');
|
|
128
|
+
}
|
|
129
|
+
if (!metadata.hasRoleDescription) {
|
|
130
|
+
warnings.push('No role/responsibility section detected');
|
|
131
|
+
}
|
|
132
|
+
if (!metadata.hasCapabilities) {
|
|
133
|
+
warnings.push('No capabilities/skills section detected');
|
|
134
|
+
}
|
|
135
|
+
if (metadata.headingCount < 2) {
|
|
136
|
+
warnings.push('Very few headings — agent file may lack structure');
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return {
|
|
140
|
+
name: agentName,
|
|
141
|
+
valid: errors.length === 0,
|
|
142
|
+
errors,
|
|
143
|
+
warnings,
|
|
144
|
+
metadata,
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Validates all agents registered in the manifest.
|
|
150
|
+
*
|
|
151
|
+
* @param {string} projectRoot - Root directory of the project
|
|
152
|
+
* @returns {RegistryReport}
|
|
153
|
+
*/
|
|
154
|
+
function validateAllAgents(projectRoot) {
|
|
155
|
+
const manifestPath = path.join(projectRoot, AGENT_DIR, MANIFEST_FILE);
|
|
156
|
+
|
|
157
|
+
let manifest;
|
|
158
|
+
try {
|
|
159
|
+
manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf-8'));
|
|
160
|
+
} catch {
|
|
161
|
+
return { total: 0, valid: 0, invalid: 0, agents: [] };
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const agents = manifest.capabilities?.agents?.items || [];
|
|
165
|
+
|
|
166
|
+
const results = agents.map((agent) => validateAgent(agent.name, projectRoot));
|
|
167
|
+
const validCount = results.filter((r) => r.valid).length;
|
|
168
|
+
|
|
169
|
+
return {
|
|
170
|
+
total: results.length,
|
|
171
|
+
valid: validCount,
|
|
172
|
+
invalid: results.length - validCount,
|
|
173
|
+
agents: results,
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Loads the full agent registry from manifest + filesystem.
|
|
179
|
+
*
|
|
180
|
+
* @param {string} projectRoot - Root directory of the project
|
|
181
|
+
* @returns {{ agents: object[], totalCount: number }}
|
|
182
|
+
*/
|
|
183
|
+
function loadRegistry(projectRoot) {
|
|
184
|
+
const manifestPath = path.join(projectRoot, AGENT_DIR, MANIFEST_FILE);
|
|
185
|
+
|
|
186
|
+
let manifest;
|
|
187
|
+
try {
|
|
188
|
+
manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf-8'));
|
|
189
|
+
} catch {
|
|
190
|
+
return { agents: [], totalCount: 0 };
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const agents = manifest.capabilities?.agents?.items || [];
|
|
194
|
+
|
|
195
|
+
return {
|
|
196
|
+
agents: agents.map((agent) => ({
|
|
197
|
+
name: agent.name,
|
|
198
|
+
file: agent.file,
|
|
199
|
+
domain: agent.domain,
|
|
200
|
+
})),
|
|
201
|
+
totalCount: agents.length,
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Finds agents matching a domain keyword.
|
|
207
|
+
*
|
|
208
|
+
* @param {string} domain - Domain keyword to search for
|
|
209
|
+
* @param {string} projectRoot - Root directory of the project
|
|
210
|
+
* @returns {object[]} Matching agents
|
|
211
|
+
*/
|
|
212
|
+
function getAgentByDomain(domain, projectRoot) {
|
|
213
|
+
const { agents } = loadRegistry(projectRoot);
|
|
214
|
+
const lowerDomain = domain.toLowerCase();
|
|
215
|
+
|
|
216
|
+
return agents.filter((agent) =>
|
|
217
|
+
agent.domain.toLowerCase().includes(lowerDomain) ||
|
|
218
|
+
agent.name.toLowerCase().includes(lowerDomain)
|
|
219
|
+
);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
module.exports = {
|
|
223
|
+
validateAgent,
|
|
224
|
+
validateAllAgents,
|
|
225
|
+
loadRegistry,
|
|
226
|
+
getAgentByDomain,
|
|
227
|
+
extractAgentMetadata,
|
|
228
|
+
};
|
|
@@ -0,0 +1,343 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Devran AI Kit — Agent Reputation Scoring
|
|
3
|
+
*
|
|
4
|
+
* Tracks agent task outcomes and computes reputation scores
|
|
5
|
+
* using a weighted formula with time-decay and cold-start bonus.
|
|
6
|
+
*
|
|
7
|
+
* @module lib/agent-reputation
|
|
8
|
+
* @author Emre Dursun
|
|
9
|
+
* @since v3.0.0
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
'use strict';
|
|
13
|
+
|
|
14
|
+
const fs = require('fs');
|
|
15
|
+
const path = require('path');
|
|
16
|
+
const crypto = require('crypto');
|
|
17
|
+
|
|
18
|
+
const { AGENT_DIR, ENGINE_DIR } = require('./constants');
|
|
19
|
+
const { writeJsonAtomic } = require('./io');
|
|
20
|
+
const REPUTATION_FILE = 'reputation.json';
|
|
21
|
+
|
|
22
|
+
/** Score bounds */
|
|
23
|
+
const MIN_SCORE = 0;
|
|
24
|
+
const MAX_SCORE = 1000;
|
|
25
|
+
|
|
26
|
+
/** Scoring weights */
|
|
27
|
+
const COMPLETION_WEIGHT = 10;
|
|
28
|
+
const FAILURE_WEIGHT = 15;
|
|
29
|
+
|
|
30
|
+
/** Cold-start threshold — bonus for agents with fewer than this many outcomes */
|
|
31
|
+
const COLD_START_THRESHOLD = 3;
|
|
32
|
+
const COLD_START_BONUS = 250;
|
|
33
|
+
|
|
34
|
+
/** Default half-life in days for time decay */
|
|
35
|
+
const DEFAULT_HALF_LIFE_DAYS = 30;
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* @typedef {object} OutcomeRecord
|
|
39
|
+
* @property {string} id - Unique outcome ID
|
|
40
|
+
* @property {string} agent - Agent name
|
|
41
|
+
* @property {'success' | 'failure'} result - Outcome result
|
|
42
|
+
* @property {number} cycleTimeMs - Time to complete in milliseconds
|
|
43
|
+
* @property {string} taskId - Associated task ID
|
|
44
|
+
* @property {string} timestamp - ISO timestamp
|
|
45
|
+
*/
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* @typedef {object} AgentReputation
|
|
49
|
+
* @property {string} agent - Agent name
|
|
50
|
+
* @property {number} score - Clamped reputation score [0, 1000]
|
|
51
|
+
* @property {number} completions - Total successful outcomes
|
|
52
|
+
* @property {number} failures - Total failed outcomes
|
|
53
|
+
* @property {number} avgCycleTimeMs - Average cycle time in milliseconds
|
|
54
|
+
* @property {string | null} lastActive - ISO timestamp of last outcome
|
|
55
|
+
* @property {string} trend - Trend indicator: '↑', '↓', or '→'
|
|
56
|
+
* @property {number} reliability - Reliability percentage (0-100)
|
|
57
|
+
*/
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Resolves the reputation file path.
|
|
61
|
+
*
|
|
62
|
+
* @param {string} projectRoot - Root directory of the project
|
|
63
|
+
* @returns {string}
|
|
64
|
+
*/
|
|
65
|
+
function resolveReputationPath(projectRoot) {
|
|
66
|
+
return path.join(projectRoot, AGENT_DIR, ENGINE_DIR, REPUTATION_FILE);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Loads the reputation data from disk.
|
|
71
|
+
*
|
|
72
|
+
* @param {string} projectRoot - Root directory
|
|
73
|
+
* @returns {{ outcomes: OutcomeRecord[], lastDecayed: string | null }}
|
|
74
|
+
*/
|
|
75
|
+
function loadReputationData(projectRoot) {
|
|
76
|
+
const filePath = resolveReputationPath(projectRoot);
|
|
77
|
+
|
|
78
|
+
if (!fs.existsSync(filePath)) {
|
|
79
|
+
return { outcomes: [], lastDecayed: null };
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
try {
|
|
83
|
+
return JSON.parse(fs.readFileSync(filePath, 'utf-8'));
|
|
84
|
+
} catch {
|
|
85
|
+
return { outcomes: [], lastDecayed: null };
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Writes reputation data to disk atomically.
|
|
91
|
+
*
|
|
92
|
+
* @param {string} projectRoot - Root directory
|
|
93
|
+
* @param {{ outcomes: OutcomeRecord[], lastDecayed: string | null }} data
|
|
94
|
+
* @returns {void}
|
|
95
|
+
*/
|
|
96
|
+
function writeReputationData(projectRoot, data) {
|
|
97
|
+
const filePath = resolveReputationPath(projectRoot);
|
|
98
|
+
writeJsonAtomic(filePath, data);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Clamps a value between min and max.
|
|
103
|
+
*
|
|
104
|
+
* @param {number} value - Value to clamp
|
|
105
|
+
* @param {number} min - Minimum
|
|
106
|
+
* @param {number} max - Maximum
|
|
107
|
+
* @returns {number}
|
|
108
|
+
*/
|
|
109
|
+
function clamp(value, min, max) {
|
|
110
|
+
return Math.max(min, Math.min(max, value));
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Records an agent task outcome.
|
|
115
|
+
*
|
|
116
|
+
* @param {string} projectRoot - Root directory
|
|
117
|
+
* @param {object} params - Outcome parameters
|
|
118
|
+
* @param {string} params.agent - Agent name
|
|
119
|
+
* @param {'success' | 'failure'} params.result - Outcome result
|
|
120
|
+
* @param {number} [params.cycleTimeMs] - Cycle time in ms (default: 0)
|
|
121
|
+
* @param {string} [params.taskId] - Associated task ID
|
|
122
|
+
* @returns {OutcomeRecord}
|
|
123
|
+
*/
|
|
124
|
+
function recordOutcome(projectRoot, { agent, result, cycleTimeMs, taskId }) {
|
|
125
|
+
if (!agent || typeof agent !== 'string') {
|
|
126
|
+
throw new Error('Agent name is required');
|
|
127
|
+
}
|
|
128
|
+
if (!['success', 'failure'].includes(result)) {
|
|
129
|
+
throw new Error(`Invalid result: ${result}. Must be 'success' or 'failure'`);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const data = loadReputationData(projectRoot);
|
|
133
|
+
|
|
134
|
+
/** @type {OutcomeRecord} */
|
|
135
|
+
const record = {
|
|
136
|
+
id: `OUT-${crypto.randomUUID().slice(0, 8).toUpperCase()}`,
|
|
137
|
+
agent,
|
|
138
|
+
result,
|
|
139
|
+
cycleTimeMs: cycleTimeMs || 0,
|
|
140
|
+
taskId: taskId || 'unknown',
|
|
141
|
+
timestamp: new Date().toISOString(),
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
data.outcomes.push(record);
|
|
145
|
+
writeReputationData(projectRoot, data);
|
|
146
|
+
|
|
147
|
+
return record;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Calculates the consistency bonus based on outcome streak.
|
|
152
|
+
* Rewards agents with consecutive successes.
|
|
153
|
+
*
|
|
154
|
+
* @param {OutcomeRecord[]} agentOutcomes - Sorted outcomes for an agent
|
|
155
|
+
* @returns {number} Consistency bonus (0-100)
|
|
156
|
+
*/
|
|
157
|
+
function calculateConsistencyBonus(agentOutcomes) {
|
|
158
|
+
if (agentOutcomes.length === 0) {
|
|
159
|
+
return 0;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Count consecutive successes from most recent.
|
|
163
|
+
// Use original index as a stable tiebreaker when timestamps are identical
|
|
164
|
+
// (prevents non-deterministic sort across JS engines).
|
|
165
|
+
let streak = 0;
|
|
166
|
+
const indexed = agentOutcomes.map((o, i) => ({ ...o, _idx: i }));
|
|
167
|
+
const sorted = indexed.sort((a, b) => {
|
|
168
|
+
const timeDiff = new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime();
|
|
169
|
+
return timeDiff !== 0 ? timeDiff : b._idx - a._idx;
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
for (const outcome of sorted) {
|
|
173
|
+
if (outcome.result === 'success') {
|
|
174
|
+
streak += 1;
|
|
175
|
+
} else {
|
|
176
|
+
break;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Cap bonus at 100 (10 consecutive successes)
|
|
181
|
+
return Math.min(streak * 10, 100);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Computes reputation for a single agent.
|
|
186
|
+
*
|
|
187
|
+
* @param {string} projectRoot - Root directory
|
|
188
|
+
* @param {string} agentName - Agent name
|
|
189
|
+
* @returns {AgentReputation}
|
|
190
|
+
*/
|
|
191
|
+
function getReputation(projectRoot, agentName) {
|
|
192
|
+
const data = loadReputationData(projectRoot);
|
|
193
|
+
const agentOutcomes = data.outcomes.filter((o) => o.agent === agentName);
|
|
194
|
+
|
|
195
|
+
const completions = agentOutcomes.filter((o) => o.result === 'success').length;
|
|
196
|
+
const failures = agentOutcomes.filter((o) => o.result === 'failure').length;
|
|
197
|
+
const totalOutcomes = agentOutcomes.length;
|
|
198
|
+
|
|
199
|
+
// Average cycle time
|
|
200
|
+
const successOutcomes = agentOutcomes.filter((o) => o.result === 'success' && o.cycleTimeMs > 0);
|
|
201
|
+
const avgCycleTimeMs = successOutcomes.length > 0
|
|
202
|
+
? Math.round(successOutcomes.reduce((sum, o) => sum + o.cycleTimeMs, 0) / successOutcomes.length)
|
|
203
|
+
: 0;
|
|
204
|
+
|
|
205
|
+
// Last active
|
|
206
|
+
const lastActive = agentOutcomes.length > 0
|
|
207
|
+
? agentOutcomes.sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime())[0].timestamp
|
|
208
|
+
: null;
|
|
209
|
+
|
|
210
|
+
// Consistency bonus
|
|
211
|
+
const consistencyBonus = calculateConsistencyBonus(agentOutcomes);
|
|
212
|
+
|
|
213
|
+
// Cold-start bonus — only for agents with at least 1 but fewer than threshold outcomes
|
|
214
|
+
const coldStartBonus = (totalOutcomes > 0 && totalOutcomes < COLD_START_THRESHOLD) ? COLD_START_BONUS : 0;
|
|
215
|
+
|
|
216
|
+
// Raw score
|
|
217
|
+
const rawScore = (completions * COMPLETION_WEIGHT) - (failures * FAILURE_WEIGHT) + consistencyBonus + coldStartBonus;
|
|
218
|
+
|
|
219
|
+
// Clamped score
|
|
220
|
+
const score = clamp(Math.round(rawScore), MIN_SCORE, MAX_SCORE);
|
|
221
|
+
|
|
222
|
+
// Trend — compare last 5 vs previous 5
|
|
223
|
+
const trend = calculateTrend(agentOutcomes);
|
|
224
|
+
|
|
225
|
+
// Reliability percentage
|
|
226
|
+
const reliability = totalOutcomes > 0 ? Math.round((completions / totalOutcomes) * 100) : 0;
|
|
227
|
+
|
|
228
|
+
return {
|
|
229
|
+
agent: agentName,
|
|
230
|
+
score,
|
|
231
|
+
completions,
|
|
232
|
+
failures,
|
|
233
|
+
avgCycleTimeMs,
|
|
234
|
+
lastActive,
|
|
235
|
+
trend,
|
|
236
|
+
reliability,
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Calculates trend from recent outcomes.
|
|
242
|
+
*
|
|
243
|
+
* @param {OutcomeRecord[]} outcomes - Agent outcomes
|
|
244
|
+
* @returns {string} '↑', '↓', or '→'
|
|
245
|
+
*/
|
|
246
|
+
function calculateTrend(outcomes) {
|
|
247
|
+
if (outcomes.length < 2) {
|
|
248
|
+
return '→';
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
const sorted = [...outcomes].sort(
|
|
252
|
+
(a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime()
|
|
253
|
+
);
|
|
254
|
+
|
|
255
|
+
const recentFive = sorted.slice(0, 5);
|
|
256
|
+
const previousFive = sorted.slice(5, 10);
|
|
257
|
+
|
|
258
|
+
if (previousFive.length === 0) {
|
|
259
|
+
return '→';
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
const recentSuccessRate = recentFive.filter((o) => o.result === 'success').length / recentFive.length;
|
|
263
|
+
const previousSuccessRate = previousFive.filter((o) => o.result === 'success').length / previousFive.length;
|
|
264
|
+
|
|
265
|
+
const delta = recentSuccessRate - previousSuccessRate;
|
|
266
|
+
|
|
267
|
+
if (delta > 0.1) {
|
|
268
|
+
return '↑';
|
|
269
|
+
}
|
|
270
|
+
if (delta < -0.1) {
|
|
271
|
+
return '↓';
|
|
272
|
+
}
|
|
273
|
+
return '→';
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* Returns all agents ranked by reputation score (descending).
|
|
278
|
+
* Agents with fewer than COLD_START_THRESHOLD outcomes are marked as 'new'.
|
|
279
|
+
*
|
|
280
|
+
* @param {string} projectRoot - Root directory
|
|
281
|
+
* @returns {AgentReputation[]}
|
|
282
|
+
*/
|
|
283
|
+
function getRankings(projectRoot) {
|
|
284
|
+
const data = loadReputationData(projectRoot);
|
|
285
|
+
|
|
286
|
+
// Get unique agent names
|
|
287
|
+
const agentNames = [...new Set(data.outcomes.map((o) => o.agent))];
|
|
288
|
+
|
|
289
|
+
const rankings = agentNames.map((name) => getReputation(projectRoot, name));
|
|
290
|
+
|
|
291
|
+
// Sort by score descending, then by completions descending
|
|
292
|
+
rankings.sort((a, b) => {
|
|
293
|
+
if (b.score !== a.score) {
|
|
294
|
+
return b.score - a.score;
|
|
295
|
+
}
|
|
296
|
+
return b.completions - a.completions;
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
return rankings;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* Applies time-decay to all outcome records.
|
|
304
|
+
* Removes outcomes older than 2× half-life to keep data manageable.
|
|
305
|
+
*
|
|
306
|
+
* @param {string} projectRoot - Root directory
|
|
307
|
+
* @param {object} [options] - Decay options
|
|
308
|
+
* @param {number} [options.halfLifeDays] - Half-life in days (default: 30)
|
|
309
|
+
* @returns {{ decayed: number, removed: number, remaining: number }}
|
|
310
|
+
*/
|
|
311
|
+
function decayScores(projectRoot, options = {}) {
|
|
312
|
+
const halfLifeDays = options.halfLifeDays || DEFAULT_HALF_LIFE_DAYS;
|
|
313
|
+
const maxAgeDays = halfLifeDays * 2;
|
|
314
|
+
const now = Date.now();
|
|
315
|
+
const maxAgeMs = maxAgeDays * 24 * 60 * 60 * 1000;
|
|
316
|
+
|
|
317
|
+
const data = loadReputationData(projectRoot);
|
|
318
|
+
const originalCount = data.outcomes.length;
|
|
319
|
+
|
|
320
|
+
// Remove outcomes older than 2× half-life
|
|
321
|
+
data.outcomes = data.outcomes.filter((outcome) => {
|
|
322
|
+
const ageMs = now - new Date(outcome.timestamp).getTime();
|
|
323
|
+
return ageMs < maxAgeMs;
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
const removedCount = originalCount - data.outcomes.length;
|
|
327
|
+
|
|
328
|
+
data.lastDecayed = new Date().toISOString();
|
|
329
|
+
writeReputationData(projectRoot, data);
|
|
330
|
+
|
|
331
|
+
return {
|
|
332
|
+
decayed: originalCount,
|
|
333
|
+
removed: removedCount,
|
|
334
|
+
remaining: data.outcomes.length,
|
|
335
|
+
};
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
module.exports = {
|
|
339
|
+
recordOutcome,
|
|
340
|
+
getReputation,
|
|
341
|
+
getRankings,
|
|
342
|
+
decayScores,
|
|
343
|
+
};
|