@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
package/lib/identity.js
ADDED
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Devran AI Kit — Developer Identity System
|
|
3
|
+
*
|
|
4
|
+
* Local identity management for multi-developer scenarios.
|
|
5
|
+
* Auto-detects from git config with manual registration fallback.
|
|
6
|
+
*
|
|
7
|
+
* @module lib/identity
|
|
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
|
+
const { execSync } = require('child_process');
|
|
18
|
+
|
|
19
|
+
const { AGENT_DIR, ENGINE_DIR } = require('./constants');
|
|
20
|
+
const { writeJsonAtomic } = require('./io');
|
|
21
|
+
const IDENTITY_FILE = 'identity.json';
|
|
22
|
+
|
|
23
|
+
/** @type {readonly string[]} */
|
|
24
|
+
const VALID_ROLES = ['owner', 'contributor', 'reviewer'];
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* @typedef {object} Identity
|
|
28
|
+
* @property {string} id - Deterministic developer ID (SHA-256 truncated)
|
|
29
|
+
* @property {string} name - Developer name
|
|
30
|
+
* @property {string} email - Developer email
|
|
31
|
+
* @property {string} role - Developer role
|
|
32
|
+
* @property {string} registeredAt - ISO timestamp
|
|
33
|
+
* @property {string} lastActiveAt - ISO timestamp
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Resolves the identity file path.
|
|
38
|
+
*
|
|
39
|
+
* @param {string} projectRoot - Root directory of the project
|
|
40
|
+
* @returns {string} Absolute path to identity.json
|
|
41
|
+
*/
|
|
42
|
+
function resolveIdentityPath(projectRoot) {
|
|
43
|
+
return path.join(projectRoot, AGENT_DIR, ENGINE_DIR, IDENTITY_FILE);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Generates a deterministic developer ID from email.
|
|
48
|
+
*
|
|
49
|
+
* @param {string} email - Developer email
|
|
50
|
+
* @returns {string} 12-char hex ID
|
|
51
|
+
*/
|
|
52
|
+
function generateDeveloperId(email) {
|
|
53
|
+
return crypto
|
|
54
|
+
.createHash('sha256')
|
|
55
|
+
.update(email.toLowerCase().trim())
|
|
56
|
+
.digest('hex')
|
|
57
|
+
.slice(0, 12);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Loads the identity registry from disk.
|
|
62
|
+
*
|
|
63
|
+
* @param {string} projectRoot - Root directory of the project
|
|
64
|
+
* @returns {{ developers: Identity[], activeId: string | null }}
|
|
65
|
+
*/
|
|
66
|
+
function loadIdentityRegistry(projectRoot) {
|
|
67
|
+
const identityPath = resolveIdentityPath(projectRoot);
|
|
68
|
+
|
|
69
|
+
if (!fs.existsSync(identityPath)) {
|
|
70
|
+
return { developers: [], activeId: null };
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
try {
|
|
74
|
+
return JSON.parse(fs.readFileSync(identityPath, 'utf-8'));
|
|
75
|
+
} catch {
|
|
76
|
+
return { developers: [], activeId: null };
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Writes the identity registry to disk atomically.
|
|
82
|
+
*
|
|
83
|
+
* @param {string} projectRoot - Root directory of the project
|
|
84
|
+
* @param {object} registry - Registry data
|
|
85
|
+
* @returns {void}
|
|
86
|
+
*/
|
|
87
|
+
function writeIdentityRegistry(projectRoot, registry) {
|
|
88
|
+
const identityPath = resolveIdentityPath(projectRoot);
|
|
89
|
+
writeJsonAtomic(identityPath, registry);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Auto-detects developer identity from git config.
|
|
94
|
+
*
|
|
95
|
+
* @returns {{ name: string, email: string } | null}
|
|
96
|
+
*/
|
|
97
|
+
function detectFromGit() {
|
|
98
|
+
try {
|
|
99
|
+
const name = execSync('git config user.name', { encoding: 'utf-8' }).trim();
|
|
100
|
+
const email = execSync('git config user.email', { encoding: 'utf-8' }).trim();
|
|
101
|
+
|
|
102
|
+
if (name && email) {
|
|
103
|
+
return { name, email };
|
|
104
|
+
}
|
|
105
|
+
return null;
|
|
106
|
+
} catch {
|
|
107
|
+
return null;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Registers a new developer identity.
|
|
113
|
+
*
|
|
114
|
+
* @param {string} projectRoot - Root directory of the project
|
|
115
|
+
* @param {object} params - Identity parameters
|
|
116
|
+
* @param {string} params.name - Developer name
|
|
117
|
+
* @param {string} params.email - Developer email
|
|
118
|
+
* @param {string} [params.role] - Developer role (default: 'contributor')
|
|
119
|
+
* @returns {{ success: boolean, identity: Identity, isNew: boolean }}
|
|
120
|
+
*/
|
|
121
|
+
function registerIdentity(projectRoot, { name, email, role }) {
|
|
122
|
+
const registry = loadIdentityRegistry(projectRoot);
|
|
123
|
+
const developerId = generateDeveloperId(email);
|
|
124
|
+
const now = new Date().toISOString();
|
|
125
|
+
const identityRole = role && VALID_ROLES.includes(role) ? role : 'contributor';
|
|
126
|
+
|
|
127
|
+
// Check if already registered
|
|
128
|
+
const existingIndex = registry.developers.findIndex((d) => d.id === developerId);
|
|
129
|
+
|
|
130
|
+
if (existingIndex !== -1) {
|
|
131
|
+
// Update last active (immutable)
|
|
132
|
+
const updatedDev = { ...registry.developers[existingIndex], lastActiveAt: now, name };
|
|
133
|
+
const updatedRegistry = {
|
|
134
|
+
...registry,
|
|
135
|
+
developers: registry.developers.map((d, i) => (i === existingIndex ? updatedDev : d)),
|
|
136
|
+
activeId: developerId,
|
|
137
|
+
};
|
|
138
|
+
writeIdentityRegistry(projectRoot, updatedRegistry);
|
|
139
|
+
return { success: true, identity: updatedDev, isNew: false };
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// First developer becomes owner automatically
|
|
143
|
+
const assignedRole = registry.developers.length === 0 ? 'owner' : identityRole;
|
|
144
|
+
|
|
145
|
+
/** @type {Identity} */
|
|
146
|
+
const identity = {
|
|
147
|
+
id: developerId,
|
|
148
|
+
name,
|
|
149
|
+
email: email.toLowerCase().trim(),
|
|
150
|
+
role: assignedRole,
|
|
151
|
+
registeredAt: now,
|
|
152
|
+
lastActiveAt: now,
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
const updatedRegistry = {
|
|
156
|
+
...registry,
|
|
157
|
+
developers: [...registry.developers, identity],
|
|
158
|
+
activeId: developerId,
|
|
159
|
+
};
|
|
160
|
+
writeIdentityRegistry(projectRoot, updatedRegistry);
|
|
161
|
+
|
|
162
|
+
return { success: true, identity, isNew: true };
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Gets the current active developer identity.
|
|
167
|
+
* Auto-detects and registers from git if no identity exists.
|
|
168
|
+
*
|
|
169
|
+
* @param {string} projectRoot - Root directory of the project
|
|
170
|
+
* @returns {Identity | null}
|
|
171
|
+
*/
|
|
172
|
+
function getCurrentIdentity(projectRoot) {
|
|
173
|
+
const registry = loadIdentityRegistry(projectRoot);
|
|
174
|
+
|
|
175
|
+
if (registry.activeId) {
|
|
176
|
+
const identity = registry.developers.find((d) => d.id === registry.activeId);
|
|
177
|
+
if (identity) {
|
|
178
|
+
return identity;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// Auto-detect from git
|
|
183
|
+
const gitInfo = detectFromGit();
|
|
184
|
+
if (gitInfo) {
|
|
185
|
+
const result = registerIdentity(projectRoot, { name: gitInfo.name, email: gitInfo.email });
|
|
186
|
+
return result.identity;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
return null;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Validates an identity exists and is properly formed.
|
|
194
|
+
*
|
|
195
|
+
* @param {string} developerId - Developer ID to validate
|
|
196
|
+
* @param {string} projectRoot - Root directory of the project
|
|
197
|
+
* @returns {{ valid: boolean, errors: string[] }}
|
|
198
|
+
*/
|
|
199
|
+
function validateIdentity(developerId, projectRoot) {
|
|
200
|
+
const registry = loadIdentityRegistry(projectRoot);
|
|
201
|
+
/** @type {string[]} */
|
|
202
|
+
const errors = [];
|
|
203
|
+
|
|
204
|
+
const identity = registry.developers.find((d) => d.id === developerId);
|
|
205
|
+
|
|
206
|
+
if (!identity) {
|
|
207
|
+
return { valid: false, errors: [`Identity not found: ${developerId}`] };
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
if (!identity.name || identity.name.trim().length === 0) {
|
|
211
|
+
errors.push('Identity missing name');
|
|
212
|
+
}
|
|
213
|
+
if (!identity.email || !identity.email.includes('@')) {
|
|
214
|
+
errors.push('Identity missing valid email');
|
|
215
|
+
}
|
|
216
|
+
if (!VALID_ROLES.includes(identity.role)) {
|
|
217
|
+
errors.push(`Invalid role: ${identity.role}`);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
return { valid: errors.length === 0, errors };
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Lists all registered developer identities.
|
|
225
|
+
*
|
|
226
|
+
* @param {string} projectRoot - Root directory of the project
|
|
227
|
+
* @returns {{ developers: Identity[], activeId: string | null }}
|
|
228
|
+
*/
|
|
229
|
+
function listIdentities(projectRoot) {
|
|
230
|
+
return loadIdentityRegistry(projectRoot);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
module.exports = {
|
|
234
|
+
registerIdentity,
|
|
235
|
+
getCurrentIdentity,
|
|
236
|
+
validateIdentity,
|
|
237
|
+
listIdentities,
|
|
238
|
+
generateDeveloperId,
|
|
239
|
+
detectFromGit,
|
|
240
|
+
};
|
package/lib/io.js
ADDED
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Devran AI Kit — Shared I/O Utilities
|
|
3
|
+
*
|
|
4
|
+
* Provides atomic file write operations and safe JSON parsing
|
|
5
|
+
* used across all runtime modules. Single point for error
|
|
6
|
+
* handling around filesystem operations.
|
|
7
|
+
*
|
|
8
|
+
* @module lib/io
|
|
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 { createLogger } = require('./logger');
|
|
18
|
+
const log = createLogger('io');
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Writes JSON data to a file atomically (temp file + rename).
|
|
22
|
+
* Creates parent directories if they don't exist.
|
|
23
|
+
*
|
|
24
|
+
* @param {string} filePath - Target file path
|
|
25
|
+
* @param {object} data - Data to serialize as JSON
|
|
26
|
+
* @returns {void}
|
|
27
|
+
*/
|
|
28
|
+
function writeJsonAtomic(filePath, data) {
|
|
29
|
+
const dir = path.dirname(filePath);
|
|
30
|
+
if (!fs.existsSync(dir)) {
|
|
31
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const content = JSON.stringify(data, null, 2) + '\n';
|
|
35
|
+
const tempPath = `${filePath}.tmp`;
|
|
36
|
+
|
|
37
|
+
try {
|
|
38
|
+
fs.writeFileSync(tempPath, content, 'utf-8');
|
|
39
|
+
} catch (writeErr) {
|
|
40
|
+
throw writeErr;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Rename temp → target. On Windows, EPERM/EACCES can occur when
|
|
44
|
+
// another handle briefly holds the target file (antivirus, prior
|
|
45
|
+
// read, etc.). Retry up to 3 times with a small delay before
|
|
46
|
+
// falling back to a direct overwrite.
|
|
47
|
+
let renamed = false;
|
|
48
|
+
for (let attempt = 0; attempt < 3; attempt++) {
|
|
49
|
+
try {
|
|
50
|
+
fs.renameSync(tempPath, filePath);
|
|
51
|
+
renamed = true;
|
|
52
|
+
break;
|
|
53
|
+
} catch (renameErr) {
|
|
54
|
+
const isTransient = renameErr.code === 'EPERM' || renameErr.code === 'EACCES';
|
|
55
|
+
if (!isTransient || attempt === 2) {
|
|
56
|
+
// Final attempt failed — fall back to direct write
|
|
57
|
+
try {
|
|
58
|
+
fs.writeFileSync(filePath, content, 'utf-8');
|
|
59
|
+
renamed = true;
|
|
60
|
+
} catch (fallbackErr) {
|
|
61
|
+
// Clean up temp file before throwing
|
|
62
|
+
try { fs.unlinkSync(tempPath); } catch { /* non-critical */ }
|
|
63
|
+
throw fallbackErr;
|
|
64
|
+
}
|
|
65
|
+
break;
|
|
66
|
+
}
|
|
67
|
+
// Brief pause before retry (1ms, 5ms)
|
|
68
|
+
const delay = attempt === 0 ? 1 : 5;
|
|
69
|
+
const start = Date.now();
|
|
70
|
+
while (Date.now() - start < delay) { /* spin wait */ }
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Clean up temp file if direct-write fallback was used
|
|
75
|
+
if (renamed) {
|
|
76
|
+
try {
|
|
77
|
+
if (fs.existsSync(tempPath)) {
|
|
78
|
+
fs.unlinkSync(tempPath);
|
|
79
|
+
}
|
|
80
|
+
} catch {
|
|
81
|
+
// Cleanup failure is non-critical
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Safely parses a JSON file, returning a default value on failure.
|
|
88
|
+
*
|
|
89
|
+
* @param {string} filePath - Path to JSON file
|
|
90
|
+
* @param {*} defaultValue - Value to return if file doesn't exist or is invalid
|
|
91
|
+
* @returns {*} Parsed JSON or default value
|
|
92
|
+
*/
|
|
93
|
+
function readJsonSafe(filePath, defaultValue = null) {
|
|
94
|
+
if (!fs.existsSync(filePath)) {
|
|
95
|
+
return defaultValue;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
try {
|
|
99
|
+
return JSON.parse(fs.readFileSync(filePath, 'utf-8'));
|
|
100
|
+
} catch (error) {
|
|
101
|
+
log.debug('Failed to parse JSON file, returning default', { filePath, error: error.message });
|
|
102
|
+
return defaultValue;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Recursively copies a directory, skipping symbolic links for security.
|
|
108
|
+
*
|
|
109
|
+
* Symlinks are skipped because they could point outside the intended
|
|
110
|
+
* scope (e.g., outside .agent/), enabling path traversal attacks.
|
|
111
|
+
*
|
|
112
|
+
* @param {string} src - Source directory path
|
|
113
|
+
* @param {string} dest - Destination directory path
|
|
114
|
+
* @returns {void}
|
|
115
|
+
*/
|
|
116
|
+
function safeCopyDirSync(src, dest) {
|
|
117
|
+
if (!fs.existsSync(dest)) {
|
|
118
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const entries = fs.readdirSync(src, { withFileTypes: true });
|
|
122
|
+
|
|
123
|
+
for (const entry of entries) {
|
|
124
|
+
const srcPath = path.join(src, entry.name);
|
|
125
|
+
const destPath = path.join(dest, entry.name);
|
|
126
|
+
|
|
127
|
+
// Security: skip symlinks to prevent path traversal
|
|
128
|
+
const stat = fs.lstatSync(srcPath);
|
|
129
|
+
if (stat.isSymbolicLink()) {
|
|
130
|
+
log.debug('Skipping symlink for security', { path: srcPath });
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (entry.isDirectory()) {
|
|
135
|
+
safeCopyDirSync(srcPath, destPath);
|
|
136
|
+
} else {
|
|
137
|
+
fs.copyFileSync(srcPath, destPath);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
module.exports = {
|
|
143
|
+
writeJsonAtomic,
|
|
144
|
+
readJsonSafe,
|
|
145
|
+
safeCopyDirSync,
|
|
146
|
+
};
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Devran AI Kit — Learning Engine
|
|
3
|
+
*
|
|
4
|
+
* Confidence scoring, pattern clustering, and decay model
|
|
5
|
+
* for the continuous learning system. Inspired by Netflix's
|
|
6
|
+
* chaos engineering feedback loop: observe, score, cluster, promote, decay.
|
|
7
|
+
*
|
|
8
|
+
* All functions are pure — no I/O, no side effects.
|
|
9
|
+
*
|
|
10
|
+
* @module lib/learning-engine
|
|
11
|
+
* @since v4.1.0
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
'use strict';
|
|
15
|
+
|
|
16
|
+
/** Confidence tier boundaries */
|
|
17
|
+
const TIERS = Object.freeze([
|
|
18
|
+
{ min: 10, score: 5 },
|
|
19
|
+
{ min: 6, score: 4 },
|
|
20
|
+
{ min: 4, score: 3 },
|
|
21
|
+
{ min: 2, score: 2 },
|
|
22
|
+
{ min: 1, score: 1 },
|
|
23
|
+
]);
|
|
24
|
+
|
|
25
|
+
/** Sessions without reinforcement before 1 point of decay */
|
|
26
|
+
const DECAY_INTERVAL = 10;
|
|
27
|
+
|
|
28
|
+
/** Minimum high-confidence patterns for skill promotion */
|
|
29
|
+
const SKILL_PROMOTION_THRESHOLD = 3;
|
|
30
|
+
|
|
31
|
+
/** Minimum confidence score for promotion eligibility */
|
|
32
|
+
const PROMOTION_CONFIDENCE = 4;
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Score confidence for a pattern based on reinforcement count.
|
|
36
|
+
* Scale: 0 (never seen) to 5 (battle-tested, 10+ reinforcements).
|
|
37
|
+
*
|
|
38
|
+
* @param {number} reinforcementCount - Times pattern was observed
|
|
39
|
+
* @returns {number} Confidence score 0-5
|
|
40
|
+
*/
|
|
41
|
+
function scoreConfidence(reinforcementCount) {
|
|
42
|
+
if (!Number.isFinite(reinforcementCount) || reinforcementCount <= 0) {
|
|
43
|
+
return 0;
|
|
44
|
+
}
|
|
45
|
+
for (const tier of TIERS) {
|
|
46
|
+
if (reinforcementCount >= tier.min) {
|
|
47
|
+
return tier.score;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return 0;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Cluster patterns by domain using loading-rules keyword overlap.
|
|
55
|
+
*
|
|
56
|
+
* @param {Array<{id: string, keywords: string[], reinforcements: number}>} patterns
|
|
57
|
+
* @param {Array<{domain: string, keywords: string[]}>} domainRules
|
|
58
|
+
* @returns {Map<string, Array>} Patterns grouped by domain
|
|
59
|
+
*/
|
|
60
|
+
function clusterPatterns(patterns, domainRules) {
|
|
61
|
+
if (!Array.isArray(patterns)) return new Map();
|
|
62
|
+
if (!Array.isArray(domainRules)) return new Map();
|
|
63
|
+
|
|
64
|
+
const clusters = new Map();
|
|
65
|
+
|
|
66
|
+
for (const rule of domainRules) {
|
|
67
|
+
clusters.set(rule.domain, []);
|
|
68
|
+
}
|
|
69
|
+
clusters.set('uncategorized', []);
|
|
70
|
+
|
|
71
|
+
for (const pattern of patterns) {
|
|
72
|
+
let matched = false;
|
|
73
|
+
for (const rule of domainRules) {
|
|
74
|
+
const hasOverlap = pattern.keywords.some(pk =>
|
|
75
|
+
rule.keywords.some(rk =>
|
|
76
|
+
rk.toLowerCase() === pk.toLowerCase()
|
|
77
|
+
)
|
|
78
|
+
);
|
|
79
|
+
if (hasOverlap) {
|
|
80
|
+
clusters.get(rule.domain).push(pattern);
|
|
81
|
+
matched = true;
|
|
82
|
+
break;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
if (!matched) {
|
|
86
|
+
clusters.get('uncategorized').push(pattern);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return clusters;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Apply decay to patterns based on sessions since last reinforcement.
|
|
95
|
+
* Loses 1 confidence point per DECAY_INTERVAL unreinforced sessions.
|
|
96
|
+
* Score 0 marks the pattern as archived.
|
|
97
|
+
*
|
|
98
|
+
* @param {Array<{id: string, confidence: number, sessionsSinceReinforcement: number}>} patterns
|
|
99
|
+
* @returns {Array<{id: string, confidence: number, archived: boolean}>}
|
|
100
|
+
*/
|
|
101
|
+
function decayPatterns(patterns) {
|
|
102
|
+
if (!Array.isArray(patterns)) return [];
|
|
103
|
+
|
|
104
|
+
return patterns.map(p => {
|
|
105
|
+
const sessions = Number.isFinite(p.sessionsSinceReinforcement) ? p.sessionsSinceReinforcement : 0;
|
|
106
|
+
const confidence = Number.isFinite(p.confidence) ? p.confidence : 0;
|
|
107
|
+
const decayAmount = Math.floor(sessions / DECAY_INTERVAL);
|
|
108
|
+
const newConfidence = Math.max(0, confidence - decayAmount);
|
|
109
|
+
return {
|
|
110
|
+
...p,
|
|
111
|
+
confidence: newConfidence,
|
|
112
|
+
archived: newConfidence === 0,
|
|
113
|
+
};
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Generate recommendations for pattern promotion.
|
|
119
|
+
* Clusters with 3+ high-confidence patterns suggest skill creation.
|
|
120
|
+
* Clusters with 1-2 high-confidence patterns suggest rule creation.
|
|
121
|
+
*
|
|
122
|
+
* @param {Map<string, Array<{reinforcements: number}>>} clusters
|
|
123
|
+
* @returns {Array<{domain: string, action: string, reason: string, patterns: Array}>}
|
|
124
|
+
*/
|
|
125
|
+
function getRecommendations(clusters) {
|
|
126
|
+
const recommendations = [];
|
|
127
|
+
|
|
128
|
+
for (const [domain, patterns] of clusters) {
|
|
129
|
+
if (domain === 'uncategorized') continue;
|
|
130
|
+
|
|
131
|
+
const highConfidence = patterns.filter(p =>
|
|
132
|
+
scoreConfidence(p.reinforcements) >= PROMOTION_CONFIDENCE
|
|
133
|
+
);
|
|
134
|
+
|
|
135
|
+
if (highConfidence.length >= SKILL_PROMOTION_THRESHOLD) {
|
|
136
|
+
recommendations.push({
|
|
137
|
+
domain,
|
|
138
|
+
action: 'promote-to-skill',
|
|
139
|
+
reason: `${highConfidence.length} battle-tested patterns in ${domain}`,
|
|
140
|
+
patterns: highConfidence,
|
|
141
|
+
});
|
|
142
|
+
} else if (highConfidence.length >= 1) {
|
|
143
|
+
recommendations.push({
|
|
144
|
+
domain,
|
|
145
|
+
action: 'promote-to-rule',
|
|
146
|
+
reason: `${highConfidence.length} high-confidence pattern(s) in ${domain}`,
|
|
147
|
+
patterns: highConfidence,
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return recommendations;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
module.exports = Object.freeze({
|
|
156
|
+
scoreConfidence,
|
|
157
|
+
clusterPatterns,
|
|
158
|
+
decayPatterns,
|
|
159
|
+
getRecommendations,
|
|
160
|
+
DECAY_INTERVAL,
|
|
161
|
+
SKILL_PROMOTION_THRESHOLD,
|
|
162
|
+
PROMOTION_CONFIDENCE,
|
|
163
|
+
});
|