@devran-ai/kit 4.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (231) hide show
  1. package/.agent/CheatSheet.md +350 -0
  2. package/.agent/README.md +76 -0
  3. package/.agent/agents/README.md +155 -0
  4. package/.agent/agents/architect.md +185 -0
  5. package/.agent/agents/backend-specialist.md +276 -0
  6. package/.agent/agents/build-error-resolver.md +207 -0
  7. package/.agent/agents/code-reviewer.md +162 -0
  8. package/.agent/agents/database-architect.md +138 -0
  9. package/.agent/agents/devops-engineer.md +144 -0
  10. package/.agent/agents/doc-updater.md +229 -0
  11. package/.agent/agents/e2e-runner.md +145 -0
  12. package/.agent/agents/explorer-agent.md +143 -0
  13. package/.agent/agents/frontend-specialist.md +144 -0
  14. package/.agent/agents/go-reviewer.md +128 -0
  15. package/.agent/agents/knowledge-agent.md +197 -0
  16. package/.agent/agents/mobile-developer.md +150 -0
  17. package/.agent/agents/performance-optimizer.md +175 -0
  18. package/.agent/agents/planner.md +133 -0
  19. package/.agent/agents/pr-reviewer.md +148 -0
  20. package/.agent/agents/python-reviewer.md +123 -0
  21. package/.agent/agents/refactor-cleaner.md +201 -0
  22. package/.agent/agents/reliability-engineer.md +156 -0
  23. package/.agent/agents/security-reviewer.md +141 -0
  24. package/.agent/agents/sprint-orchestrator.md +124 -0
  25. package/.agent/agents/tdd-guide.md +179 -0
  26. package/.agent/agents/typescript-reviewer.md +110 -0
  27. package/.agent/checklists/README.md +102 -0
  28. package/.agent/checklists/pre-commit.md +93 -0
  29. package/.agent/checklists/session-end.md +99 -0
  30. package/.agent/checklists/session-start.md +102 -0
  31. package/.agent/checklists/task-complete.md +81 -0
  32. package/.agent/commands/README.md +130 -0
  33. package/.agent/commands/adr.md +29 -0
  34. package/.agent/commands/ask.md +28 -0
  35. package/.agent/commands/build.md +30 -0
  36. package/.agent/commands/changelog.md +40 -0
  37. package/.agent/commands/checkpoint.md +28 -0
  38. package/.agent/commands/code-review.md +65 -0
  39. package/.agent/commands/compact.md +28 -0
  40. package/.agent/commands/cook.md +30 -0
  41. package/.agent/commands/db.md +30 -0
  42. package/.agent/commands/debug.md +31 -0
  43. package/.agent/commands/deploy.md +37 -0
  44. package/.agent/commands/design.md +29 -0
  45. package/.agent/commands/doc.md +30 -0
  46. package/.agent/commands/eval.md +30 -0
  47. package/.agent/commands/fix.md +32 -0
  48. package/.agent/commands/git.md +32 -0
  49. package/.agent/commands/help.md +273 -0
  50. package/.agent/commands/implement.md +30 -0
  51. package/.agent/commands/integrate.md +32 -0
  52. package/.agent/commands/learn.md +29 -0
  53. package/.agent/commands/perf.md +31 -0
  54. package/.agent/commands/plan.md +56 -0
  55. package/.agent/commands/pr-describe.md +65 -0
  56. package/.agent/commands/pr-fix.md +45 -0
  57. package/.agent/commands/pr-merge.md +45 -0
  58. package/.agent/commands/pr-review.md +50 -0
  59. package/.agent/commands/pr-split.md +54 -0
  60. package/.agent/commands/pr-status.md +56 -0
  61. package/.agent/commands/pr.md +58 -0
  62. package/.agent/commands/refactor.md +32 -0
  63. package/.agent/commands/research.md +28 -0
  64. package/.agent/commands/scout.md +30 -0
  65. package/.agent/commands/security-scan.md +33 -0
  66. package/.agent/commands/setup.md +31 -0
  67. package/.agent/commands/status.md +59 -0
  68. package/.agent/commands/tdd.md +73 -0
  69. package/.agent/commands/verify.md +58 -0
  70. package/.agent/contexts/brainstorm.md +26 -0
  71. package/.agent/contexts/debug.md +28 -0
  72. package/.agent/contexts/implement.md +29 -0
  73. package/.agent/contexts/plan-quality-log.md +30 -0
  74. package/.agent/contexts/review.md +27 -0
  75. package/.agent/contexts/ship.md +28 -0
  76. package/.agent/decisions/001-trust-grade-governance.md +46 -0
  77. package/.agent/decisions/002-cross-ide-generation.md +15 -0
  78. package/.agent/engine/identity.json +4 -0
  79. package/.agent/engine/loading-rules.json +193 -0
  80. package/.agent/engine/marketplace-index.json +29 -0
  81. package/.agent/engine/mcp-servers/filesystem.json +9 -0
  82. package/.agent/engine/mcp-servers/github.json +11 -0
  83. package/.agent/engine/mcp-servers/postgres.json +11 -0
  84. package/.agent/engine/mcp-servers/supabase.json +11 -0
  85. package/.agent/engine/mcp-servers/vercel.json +11 -0
  86. package/.agent/engine/reliability-config.json +14 -0
  87. package/.agent/engine/sdlc-map.json +50 -0
  88. package/.agent/engine/workflow-state.json +167 -0
  89. package/.agent/hooks/README.md +101 -0
  90. package/.agent/hooks/hooks.json +104 -0
  91. package/.agent/hooks/templates/session-end.md +110 -0
  92. package/.agent/hooks/templates/session-start.md +95 -0
  93. package/.agent/manifest.json +466 -0
  94. package/.agent/rules/agent-upgrade-policy.md +56 -0
  95. package/.agent/rules/architecture.md +111 -0
  96. package/.agent/rules/coding-style.md +75 -0
  97. package/.agent/rules/documentation.md +74 -0
  98. package/.agent/rules/git-workflow.md +140 -0
  99. package/.agent/rules/quality-gate.md +117 -0
  100. package/.agent/rules/security.md +67 -0
  101. package/.agent/rules/sprint-tracking.md +103 -0
  102. package/.agent/rules/testing.md +80 -0
  103. package/.agent/rules/workflow-standards.md +30 -0
  104. package/.agent/rules.md +293 -0
  105. package/.agent/session-context.md +69 -0
  106. package/.agent/session-state.json +27 -0
  107. package/.agent/skills/README.md +135 -0
  108. package/.agent/skills/api-patterns/SKILL.md +117 -0
  109. package/.agent/skills/app-builder/SKILL.md +202 -0
  110. package/.agent/skills/architecture/SKILL.md +101 -0
  111. package/.agent/skills/behavioral-modes/SKILL.md +295 -0
  112. package/.agent/skills/brainstorming/SKILL.md +156 -0
  113. package/.agent/skills/clean-code/SKILL.md +142 -0
  114. package/.agent/skills/context-budget/SKILL.md +78 -0
  115. package/.agent/skills/continuous-learning/SKILL.md +145 -0
  116. package/.agent/skills/database-design/SKILL.md +303 -0
  117. package/.agent/skills/debugging-strategies/SKILL.md +158 -0
  118. package/.agent/skills/deployment-procedures/SKILL.md +191 -0
  119. package/.agent/skills/docker-patterns/SKILL.md +161 -0
  120. package/.agent/skills/eval-harness/SKILL.md +89 -0
  121. package/.agent/skills/frontend-patterns/SKILL.md +141 -0
  122. package/.agent/skills/git-workflow/SKILL.md +159 -0
  123. package/.agent/skills/i18n-localization/SKILL.md +191 -0
  124. package/.agent/skills/intelligent-routing/SKILL.md +180 -0
  125. package/.agent/skills/mcp-integration/SKILL.md +240 -0
  126. package/.agent/skills/mobile-design/SKILL.md +191 -0
  127. package/.agent/skills/nodejs-patterns/SKILL.md +164 -0
  128. package/.agent/skills/parallel-agents/SKILL.md +200 -0
  129. package/.agent/skills/performance-profiling/SKILL.md +134 -0
  130. package/.agent/skills/plan-validation/SKILL.md +192 -0
  131. package/.agent/skills/plan-writing/SKILL.md +183 -0
  132. package/.agent/skills/plan-writing/domain-enhancers.md +184 -0
  133. package/.agent/skills/plan-writing/plan-retrospective.md +116 -0
  134. package/.agent/skills/plan-writing/plan-schema.md +119 -0
  135. package/.agent/skills/pr-toolkit/SKILL.md +174 -0
  136. package/.agent/skills/production-readiness/SKILL.md +126 -0
  137. package/.agent/skills/security-practices/SKILL.md +109 -0
  138. package/.agent/skills/shell-conventions/SKILL.md +92 -0
  139. package/.agent/skills/strategic-compact/SKILL.md +62 -0
  140. package/.agent/skills/testing-patterns/SKILL.md +141 -0
  141. package/.agent/skills/typescript-expert/SKILL.md +160 -0
  142. package/.agent/skills/ui-ux-pro-max/SKILL.md +137 -0
  143. package/.agent/skills/ui-ux-pro-max/data/charts.csv +26 -0
  144. package/.agent/skills/ui-ux-pro-max/data/colors.csv +97 -0
  145. package/.agent/skills/ui-ux-pro-max/data/icons.csv +101 -0
  146. package/.agent/skills/ui-ux-pro-max/data/landing.csv +31 -0
  147. package/.agent/skills/ui-ux-pro-max/data/products.csv +97 -0
  148. package/.agent/skills/ui-ux-pro-max/data/react-performance.csv +45 -0
  149. package/.agent/skills/ui-ux-pro-max/data/stacks/astro.csv +54 -0
  150. package/.agent/skills/ui-ux-pro-max/data/stacks/flutter.csv +53 -0
  151. package/.agent/skills/ui-ux-pro-max/data/stacks/html-tailwind.csv +56 -0
  152. package/.agent/skills/ui-ux-pro-max/data/stacks/jetpack-compose.csv +53 -0
  153. package/.agent/skills/ui-ux-pro-max/data/stacks/nextjs.csv +53 -0
  154. package/.agent/skills/ui-ux-pro-max/data/stacks/nuxt-ui.csv +51 -0
  155. package/.agent/skills/ui-ux-pro-max/data/stacks/nuxtjs.csv +59 -0
  156. package/.agent/skills/ui-ux-pro-max/data/stacks/react-native.csv +52 -0
  157. package/.agent/skills/ui-ux-pro-max/data/stacks/react.csv +54 -0
  158. package/.agent/skills/ui-ux-pro-max/data/stacks/shadcn.csv +61 -0
  159. package/.agent/skills/ui-ux-pro-max/data/stacks/svelte.csv +54 -0
  160. package/.agent/skills/ui-ux-pro-max/data/stacks/swiftui.csv +51 -0
  161. package/.agent/skills/ui-ux-pro-max/data/stacks/vue.csv +50 -0
  162. package/.agent/skills/ui-ux-pro-max/data/styles.csv +68 -0
  163. package/.agent/skills/ui-ux-pro-max/data/typography.csv +58 -0
  164. package/.agent/skills/ui-ux-pro-max/data/ui-reasoning.csv +101 -0
  165. package/.agent/skills/ui-ux-pro-max/data/ux-guidelines.csv +100 -0
  166. package/.agent/skills/ui-ux-pro-max/data/web-interface.csv +31 -0
  167. package/.agent/skills/ui-ux-pro-max/scripts/core.py +253 -0
  168. package/.agent/skills/ui-ux-pro-max/scripts/design_system.py +1067 -0
  169. package/.agent/skills/ui-ux-pro-max/scripts/search.py +114 -0
  170. package/.agent/skills/verification-loop/SKILL.md +89 -0
  171. package/.agent/skills/webapp-testing/SKILL.md +175 -0
  172. package/.agent/templates/adr-template.md +32 -0
  173. package/.agent/templates/bug-report.md +37 -0
  174. package/.agent/templates/feature-request.md +32 -0
  175. package/.agent/workflows/README.md +101 -0
  176. package/.agent/workflows/brainstorm.md +86 -0
  177. package/.agent/workflows/create.md +85 -0
  178. package/.agent/workflows/debug.md +83 -0
  179. package/.agent/workflows/deploy.md +114 -0
  180. package/.agent/workflows/enhance.md +85 -0
  181. package/.agent/workflows/orchestrate.md +106 -0
  182. package/.agent/workflows/plan.md +105 -0
  183. package/.agent/workflows/pr-fix.md +163 -0
  184. package/.agent/workflows/pr-merge.md +117 -0
  185. package/.agent/workflows/pr-review.md +178 -0
  186. package/.agent/workflows/pr-split.md +118 -0
  187. package/.agent/workflows/pr.md +184 -0
  188. package/.agent/workflows/preflight.md +107 -0
  189. package/.agent/workflows/preview.md +95 -0
  190. package/.agent/workflows/quality-gate.md +103 -0
  191. package/.agent/workflows/retrospective.md +100 -0
  192. package/.agent/workflows/review.md +104 -0
  193. package/.agent/workflows/status.md +89 -0
  194. package/.agent/workflows/test.md +98 -0
  195. package/.agent/workflows/ui-ux-pro-max.md +93 -0
  196. package/.agent/workflows/upgrade.md +97 -0
  197. package/LICENSE +21 -0
  198. package/README.md +218 -0
  199. package/bin/kit.js +773 -0
  200. package/lib/agent-registry.js +228 -0
  201. package/lib/agent-reputation.js +343 -0
  202. package/lib/circuit-breaker.js +195 -0
  203. package/lib/cli-commands.js +322 -0
  204. package/lib/config-validator.js +274 -0
  205. package/lib/conflict-detector.js +252 -0
  206. package/lib/constants.js +47 -0
  207. package/lib/engineering-manager.js +336 -0
  208. package/lib/error-budget.js +370 -0
  209. package/lib/hook-system.js +256 -0
  210. package/lib/ide-generator.js +434 -0
  211. package/lib/identity.js +240 -0
  212. package/lib/io.js +146 -0
  213. package/lib/learning-engine.js +163 -0
  214. package/lib/loading-engine.js +421 -0
  215. package/lib/logger.js +118 -0
  216. package/lib/marketplace.js +321 -0
  217. package/lib/plugin-system.js +604 -0
  218. package/lib/plugin-verifier.js +197 -0
  219. package/lib/rate-limiter.js +113 -0
  220. package/lib/security-scanner.js +312 -0
  221. package/lib/self-healing.js +468 -0
  222. package/lib/session-manager.js +264 -0
  223. package/lib/skill-sandbox.js +244 -0
  224. package/lib/task-governance.js +522 -0
  225. package/lib/task-model.js +332 -0
  226. package/lib/updater.js +240 -0
  227. package/lib/verify.js +279 -0
  228. package/lib/workflow-engine.js +373 -0
  229. package/lib/workflow-events.js +166 -0
  230. package/lib/workflow-persistence.js +160 -0
  231. package/package.json +57 -0
@@ -0,0 +1,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
+ };