@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,468 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Devran AI Kit — Self-Healing Pipeline
|
|
3
|
+
*
|
|
4
|
+
* Detects CI failures, diagnoses root causes, generates
|
|
5
|
+
* JSON fix patches, and applies with confirmation.
|
|
6
|
+
*
|
|
7
|
+
* @module lib/self-healing
|
|
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 { writeJsonAtomic } = require('./io');
|
|
18
|
+
const { createLogger } = require('./logger');
|
|
19
|
+
const log = createLogger('self-healing');
|
|
20
|
+
|
|
21
|
+
const { AGENT_DIR, ENGINE_DIR } = require('./constants');
|
|
22
|
+
const HEALING_LOG_FILE = 'healing-log.json';
|
|
23
|
+
|
|
24
|
+
/** Maximum healing log entries before pruning */
|
|
25
|
+
const MAX_LOG_ENTRIES = 100;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Sanitizes a module path into a valid JavaScript variable name.
|
|
29
|
+
* Handles scoped packages (@scope/name), paths with slashes,
|
|
30
|
+
* and ensures the result starts with a letter.
|
|
31
|
+
*
|
|
32
|
+
* @param {string} modulePath - Raw module name or path
|
|
33
|
+
* @returns {string} Valid JS variable name
|
|
34
|
+
*/
|
|
35
|
+
function sanitizeVariableName(modulePath) {
|
|
36
|
+
// Extract the last segment (handle scoped packages and paths)
|
|
37
|
+
const segments = modulePath.replace('@', '').split(/[/\\]/);
|
|
38
|
+
const baseName = segments[segments.length - 1] || 'module';
|
|
39
|
+
|
|
40
|
+
// Convert to camelCase: strip non-alphanumeric, capitalize after separators
|
|
41
|
+
const sanitized = baseName
|
|
42
|
+
.replace(/[^a-zA-Z0-9]+(.)?/g, (_, chr) => (chr ? chr.toUpperCase() : ''))
|
|
43
|
+
.replace(/^[^a-zA-Z]/, '');
|
|
44
|
+
|
|
45
|
+
return sanitized || 'unknownModule';
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* @typedef {object} FailureDetection
|
|
50
|
+
* @property {string} type - Failure type: 'test' | 'build' | 'dependency' | 'lint'
|
|
51
|
+
* @property {string} message - Failure message
|
|
52
|
+
* @property {string | null} file - Affected file (if detectable)
|
|
53
|
+
* @property {number | null} line - Line number (if detectable)
|
|
54
|
+
* @property {string} severity - 'critical' | 'high' | 'medium' | 'low'
|
|
55
|
+
*/
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* @typedef {object} Diagnosis
|
|
59
|
+
* @property {string} category - Root cause: 'syntax' | 'import' | 'type' | 'config' | 'assertion' | 'unknown'
|
|
60
|
+
* @property {string} explanation - Human-readable diagnosis
|
|
61
|
+
* @property {boolean} autoFixable - Whether auto-fix is possible
|
|
62
|
+
*/
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* @typedef {object} FixPatch
|
|
66
|
+
* @property {string} patchId - Unique patch ID
|
|
67
|
+
* @property {string} file - Target file path
|
|
68
|
+
* @property {'insert' | 'replace' | 'delete'} type - Patch operation type
|
|
69
|
+
* @property {number | null} line - Target line number
|
|
70
|
+
* @property {string} original - Original content (for replace/delete)
|
|
71
|
+
* @property {string} replacement - New content (for insert/replace)
|
|
72
|
+
* @property {'high' | 'medium' | 'low'} confidence - Fix confidence level
|
|
73
|
+
*/
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Resolves the healing log file path.
|
|
77
|
+
*
|
|
78
|
+
* @param {string} projectRoot - Root directory
|
|
79
|
+
* @returns {string}
|
|
80
|
+
*/
|
|
81
|
+
function resolveHealingLogPath(projectRoot) {
|
|
82
|
+
return path.join(projectRoot, AGENT_DIR, ENGINE_DIR, HEALING_LOG_FILE);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Loads the healing log from disk.
|
|
87
|
+
*
|
|
88
|
+
* @param {string} projectRoot - Root directory
|
|
89
|
+
* @returns {{ entries: object[] }}
|
|
90
|
+
*/
|
|
91
|
+
function loadHealingLog(projectRoot) {
|
|
92
|
+
const filePath = resolveHealingLogPath(projectRoot);
|
|
93
|
+
|
|
94
|
+
if (!fs.existsSync(filePath)) {
|
|
95
|
+
return { entries: [] };
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
try {
|
|
99
|
+
return JSON.parse(fs.readFileSync(filePath, 'utf-8'));
|
|
100
|
+
} catch {
|
|
101
|
+
return { entries: [] };
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Writes the healing log atomically with pruning.
|
|
107
|
+
*
|
|
108
|
+
* @param {string} projectRoot - Root directory
|
|
109
|
+
* @param {{ entries: object[] }} data
|
|
110
|
+
* @returns {void}
|
|
111
|
+
*/
|
|
112
|
+
function writeHealingLog(projectRoot, data) {
|
|
113
|
+
const filePath = resolveHealingLogPath(projectRoot);
|
|
114
|
+
|
|
115
|
+
// Prune to last MAX_LOG_ENTRIES (immutable)
|
|
116
|
+
const prunedData = data.entries.length > MAX_LOG_ENTRIES
|
|
117
|
+
? { ...data, entries: data.entries.slice(-MAX_LOG_ENTRIES) }
|
|
118
|
+
: data;
|
|
119
|
+
|
|
120
|
+
writeJsonAtomic(filePath, prunedData);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// ═══════════════════════════════════════════════════
|
|
124
|
+
// Failure Detection Patterns
|
|
125
|
+
// ═══════════════════════════════════════════════════
|
|
126
|
+
|
|
127
|
+
/** @type {{ pattern: RegExp, type: string, severity: string }[]} */
|
|
128
|
+
const FAILURE_PATTERNS = [
|
|
129
|
+
// Test failures
|
|
130
|
+
{
|
|
131
|
+
pattern: /FAIL\s+([\w./\\-]+)\s*>/,
|
|
132
|
+
type: 'test',
|
|
133
|
+
severity: 'high',
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
pattern: /AssertionError:\s*(.+)/,
|
|
137
|
+
type: 'test',
|
|
138
|
+
severity: 'high',
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
pattern: /Expected\s*(.+)\s*to\s*(equal|be|match)/i,
|
|
142
|
+
type: 'test',
|
|
143
|
+
severity: 'high',
|
|
144
|
+
},
|
|
145
|
+
// Build failures
|
|
146
|
+
{
|
|
147
|
+
pattern: /error TS(\d+):\s*(.+)/,
|
|
148
|
+
type: 'build',
|
|
149
|
+
severity: 'critical',
|
|
150
|
+
},
|
|
151
|
+
{
|
|
152
|
+
pattern: /SyntaxError:\s*(.+)/,
|
|
153
|
+
type: 'build',
|
|
154
|
+
severity: 'critical',
|
|
155
|
+
},
|
|
156
|
+
// Import/dependency issues
|
|
157
|
+
{
|
|
158
|
+
pattern: /Cannot find module '([^']+)'/,
|
|
159
|
+
type: 'dependency',
|
|
160
|
+
severity: 'high',
|
|
161
|
+
},
|
|
162
|
+
{
|
|
163
|
+
pattern: /Module not found:\s*(.+)/,
|
|
164
|
+
type: 'dependency',
|
|
165
|
+
severity: 'high',
|
|
166
|
+
},
|
|
167
|
+
{
|
|
168
|
+
pattern: /ERR_MODULE_NOT_FOUND/,
|
|
169
|
+
type: 'dependency',
|
|
170
|
+
severity: 'high',
|
|
171
|
+
},
|
|
172
|
+
// Lint errors
|
|
173
|
+
{
|
|
174
|
+
pattern: /(\d+):(\d+)\s+error\s+(.+?)\s+([\w/-]+)$/m,
|
|
175
|
+
type: 'lint',
|
|
176
|
+
severity: 'medium',
|
|
177
|
+
},
|
|
178
|
+
{
|
|
179
|
+
pattern: /eslint.*error/i,
|
|
180
|
+
type: 'lint',
|
|
181
|
+
severity: 'medium',
|
|
182
|
+
},
|
|
183
|
+
];
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Detects failures from raw CI output text.
|
|
187
|
+
*
|
|
188
|
+
* @param {string} ciOutput - Raw CI log text
|
|
189
|
+
* @returns {FailureDetection[]}
|
|
190
|
+
*/
|
|
191
|
+
function detectFailure(ciOutput) {
|
|
192
|
+
if (!ciOutput || typeof ciOutput !== 'string') {
|
|
193
|
+
return [];
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/** @type {FailureDetection[]} */
|
|
197
|
+
const failures = [];
|
|
198
|
+
const lines = ciOutput.split('\n');
|
|
199
|
+
|
|
200
|
+
for (const line of lines) {
|
|
201
|
+
for (const { pattern, type, severity } of FAILURE_PATTERNS) {
|
|
202
|
+
const match = line.match(pattern);
|
|
203
|
+
if (match) {
|
|
204
|
+
// Try to extract file and line from context
|
|
205
|
+
const fileMatch = line.match(/([\w./\\-]+\.(js|ts|jsx|tsx|json))/);
|
|
206
|
+
const lineMatch = line.match(/:(\d+):/);
|
|
207
|
+
|
|
208
|
+
failures.push({
|
|
209
|
+
type,
|
|
210
|
+
message: match[0].trim(),
|
|
211
|
+
file: fileMatch ? fileMatch[1] : null,
|
|
212
|
+
line: lineMatch ? parseInt(lineMatch[1], 10) : null,
|
|
213
|
+
severity,
|
|
214
|
+
});
|
|
215
|
+
break; // One match per line
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
return failures;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// ═══════════════════════════════════════════════════
|
|
224
|
+
// Failure Diagnosis
|
|
225
|
+
// ═══════════════════════════════════════════════════
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Diagnoses the root cause of a detected failure.
|
|
229
|
+
*
|
|
230
|
+
* @param {FailureDetection} failure - Detected failure
|
|
231
|
+
* @returns {Diagnosis}
|
|
232
|
+
*/
|
|
233
|
+
function diagnoseFailure(failure) {
|
|
234
|
+
const message = failure.message.toLowerCase();
|
|
235
|
+
|
|
236
|
+
// Import/module issues
|
|
237
|
+
if (failure.type === 'dependency' || message.includes('cannot find module') || message.includes('module not found')) {
|
|
238
|
+
return {
|
|
239
|
+
category: 'import',
|
|
240
|
+
explanation: `Missing module or incorrect import path: ${failure.message}`,
|
|
241
|
+
autoFixable: true,
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// Syntax errors
|
|
246
|
+
if (failure.type === 'build' && message.includes('syntaxerror')) {
|
|
247
|
+
return {
|
|
248
|
+
category: 'syntax',
|
|
249
|
+
explanation: `Syntax error in source code: ${failure.message}`,
|
|
250
|
+
autoFixable: false,
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// TypeScript type errors
|
|
255
|
+
if (failure.type === 'build' && message.includes('error ts')) {
|
|
256
|
+
return {
|
|
257
|
+
category: 'type',
|
|
258
|
+
explanation: `TypeScript type error: ${failure.message}`,
|
|
259
|
+
autoFixable: false,
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// Test assertion failures
|
|
264
|
+
if (failure.type === 'test') {
|
|
265
|
+
return {
|
|
266
|
+
category: 'assertion',
|
|
267
|
+
explanation: `Test assertion failed — requires manual review: ${failure.message}`,
|
|
268
|
+
autoFixable: false,
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// Lint errors
|
|
273
|
+
if (failure.type === 'lint') {
|
|
274
|
+
return {
|
|
275
|
+
category: 'config',
|
|
276
|
+
explanation: `Lint rule violation: ${failure.message}`,
|
|
277
|
+
autoFixable: true,
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
return {
|
|
282
|
+
category: 'unknown',
|
|
283
|
+
explanation: `Unclassified failure: ${failure.message}`,
|
|
284
|
+
autoFixable: false,
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// ═══════════════════════════════════════════════════
|
|
289
|
+
// Fix Patch Generation
|
|
290
|
+
// ═══════════════════════════════════════════════════
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* Generates a fix patch for a diagnosed failure.
|
|
294
|
+
* Only generates patches for auto-fixable issues.
|
|
295
|
+
*
|
|
296
|
+
* @param {FailureDetection} failure - Detected failure
|
|
297
|
+
* @param {Diagnosis} diagnosis - Diagnosis result
|
|
298
|
+
* @returns {FixPatch | null} Generated patch, or null if not auto-fixable
|
|
299
|
+
*/
|
|
300
|
+
function generateFixPatch(failure, diagnosis) {
|
|
301
|
+
if (!diagnosis.autoFixable) {
|
|
302
|
+
return null;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
const patchId = `HEAL-${crypto.randomUUID().slice(0, 8).toUpperCase()}`;
|
|
306
|
+
|
|
307
|
+
// Missing import → suggest adding import
|
|
308
|
+
if (diagnosis.category === 'import') {
|
|
309
|
+
const moduleMatch = failure.message.match(/(?:Cannot find module|Module not found)[:\s]*'?([^'"\s]+)/i);
|
|
310
|
+
const rawModuleName = moduleMatch ? moduleMatch[1] : 'unknown-module';
|
|
311
|
+
// Sanitize module name to prevent injection via crafted CI output (M-11)
|
|
312
|
+
const safeModuleName = rawModuleName.replace(/[^a-zA-Z0-9@/_.-]/g, '');
|
|
313
|
+
|
|
314
|
+
return {
|
|
315
|
+
patchId,
|
|
316
|
+
file: failure.file || 'unknown',
|
|
317
|
+
type: 'insert',
|
|
318
|
+
line: 1,
|
|
319
|
+
original: '',
|
|
320
|
+
replacement: `const ${sanitizeVariableName(safeModuleName)} = require('${safeModuleName}');`,
|
|
321
|
+
confidence: 'medium',
|
|
322
|
+
};
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// Lint fix → suggest formatting change
|
|
326
|
+
if (diagnosis.category === 'config') {
|
|
327
|
+
return {
|
|
328
|
+
patchId,
|
|
329
|
+
file: failure.file || 'unknown',
|
|
330
|
+
type: 'replace',
|
|
331
|
+
line: failure.line,
|
|
332
|
+
original: failure.message,
|
|
333
|
+
replacement: `// TODO: Fix lint rule — ${failure.message}`,
|
|
334
|
+
confidence: 'low',
|
|
335
|
+
};
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
return null;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
/**
|
|
342
|
+
* Applies a fix patch with confirmation safeguard.
|
|
343
|
+
* Dry-run by default — requires explicit opt-in for file writes.
|
|
344
|
+
*
|
|
345
|
+
* @param {string} projectRoot - Root directory
|
|
346
|
+
* @param {FixPatch} patch - Patch to apply
|
|
347
|
+
* @param {object} [options] - Apply options
|
|
348
|
+
* @param {boolean} [options.dryRun] - If true (default), just preview
|
|
349
|
+
* @returns {{ applied: boolean, preview: string, patchId: string }}
|
|
350
|
+
*/
|
|
351
|
+
function applyFixWithConfirmation(projectRoot, patch, options = {}) {
|
|
352
|
+
const dryRun = options.dryRun !== false; // Default: true
|
|
353
|
+
|
|
354
|
+
const logEntry = {
|
|
355
|
+
patchId: patch.patchId,
|
|
356
|
+
file: patch.file,
|
|
357
|
+
type: patch.type,
|
|
358
|
+
applied: false,
|
|
359
|
+
dryRun,
|
|
360
|
+
timestamp: new Date().toISOString(),
|
|
361
|
+
rollbackData: {
|
|
362
|
+
original: patch.original,
|
|
363
|
+
line: patch.line,
|
|
364
|
+
},
|
|
365
|
+
};
|
|
366
|
+
|
|
367
|
+
const preview = [
|
|
368
|
+
`Patch: ${patch.patchId}`,
|
|
369
|
+
`File: ${patch.file}`,
|
|
370
|
+
`Type: ${patch.type}`,
|
|
371
|
+
`Line: ${patch.line || 'N/A'}`,
|
|
372
|
+
`Confidence: ${patch.confidence}`,
|
|
373
|
+
`Original: ${patch.original || '(empty)'}`,
|
|
374
|
+
`Replacement: ${patch.replacement}`,
|
|
375
|
+
dryRun ? '[DRY RUN — no changes applied]' : '[APPLIED]',
|
|
376
|
+
].join('\n');
|
|
377
|
+
|
|
378
|
+
if (!dryRun && patch.file !== 'unknown') {
|
|
379
|
+
const targetPath = path.join(projectRoot, patch.file);
|
|
380
|
+
|
|
381
|
+
// Validate target stays within project root (prevent path traversal via crafted CI logs)
|
|
382
|
+
const resolvedTarget = path.resolve(targetPath);
|
|
383
|
+
const resolvedRoot = path.resolve(projectRoot);
|
|
384
|
+
if (!resolvedTarget.startsWith(resolvedRoot + path.sep) && resolvedTarget !== resolvedRoot) {
|
|
385
|
+
logEntry.applied = false;
|
|
386
|
+
// Load healing log and record the blocked attempt (H-6: fixed variable shadowing)
|
|
387
|
+
const healingLog = loadHealingLog(projectRoot);
|
|
388
|
+
writeHealingLog(projectRoot, { ...healingLog, entries: [...healingLog.entries, logEntry] });
|
|
389
|
+
return { applied: false, preview, patchId: patch.patchId };
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
if (fs.existsSync(targetPath)) {
|
|
393
|
+
try {
|
|
394
|
+
const content = fs.readFileSync(targetPath, 'utf-8');
|
|
395
|
+
|
|
396
|
+
// Create backup before applying patch
|
|
397
|
+
const backupPath = `${targetPath}.bak`;
|
|
398
|
+
fs.writeFileSync(backupPath, content, 'utf-8');
|
|
399
|
+
logEntry.rollbackData.backupPath = backupPath;
|
|
400
|
+
|
|
401
|
+
const lines = content.split('\n');
|
|
402
|
+
|
|
403
|
+
if (patch.type === 'insert' && patch.line !== null) {
|
|
404
|
+
const insertIndex = Math.max(0, (patch.line || 1) - 1);
|
|
405
|
+
lines.splice(insertIndex, 0, patch.replacement);
|
|
406
|
+
} else if (patch.type === 'replace' && patch.line !== null) {
|
|
407
|
+
const replaceIndex = (patch.line || 1) - 1;
|
|
408
|
+
if (replaceIndex >= 0 && replaceIndex < lines.length) {
|
|
409
|
+
lines[replaceIndex] = patch.replacement;
|
|
410
|
+
}
|
|
411
|
+
} else if (patch.type === 'delete' && patch.line !== null) {
|
|
412
|
+
const deleteIndex = (patch.line || 1) - 1;
|
|
413
|
+
if (deleteIndex >= 0 && deleteIndex < lines.length) {
|
|
414
|
+
lines.splice(deleteIndex, 1);
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
const tempPath = `${targetPath}.tmp`;
|
|
419
|
+
fs.writeFileSync(tempPath, lines.join('\n'), 'utf-8');
|
|
420
|
+
fs.renameSync(tempPath, targetPath);
|
|
421
|
+
|
|
422
|
+
logEntry.applied = true;
|
|
423
|
+
} catch {
|
|
424
|
+
logEntry.applied = false;
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
// Log the action (using distinct variable name to avoid shadowing logger)
|
|
430
|
+
const healingLog = loadHealingLog(projectRoot);
|
|
431
|
+
writeHealingLog(projectRoot, { ...healingLog, entries: [...healingLog.entries, logEntry] });
|
|
432
|
+
|
|
433
|
+
return {
|
|
434
|
+
applied: logEntry.applied,
|
|
435
|
+
preview,
|
|
436
|
+
patchId: patch.patchId,
|
|
437
|
+
};
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
/**
|
|
441
|
+
* Returns the healing report: recent heal activities and stats.
|
|
442
|
+
*
|
|
443
|
+
* @param {string} projectRoot - Root directory
|
|
444
|
+
* @returns {{ totalHeals: number, successRate: number, recentEntries: object[], pendingPatches: number }}
|
|
445
|
+
*/
|
|
446
|
+
function getHealingReport(projectRoot) {
|
|
447
|
+
const log = loadHealingLog(projectRoot);
|
|
448
|
+
const entries = log.entries || [];
|
|
449
|
+
|
|
450
|
+
const appliedEntries = entries.filter((e) => !e.dryRun);
|
|
451
|
+
const applied = appliedEntries.filter((e) => e.applied).length;
|
|
452
|
+
const dryRuns = entries.filter((e) => e.dryRun).length;
|
|
453
|
+
|
|
454
|
+
return {
|
|
455
|
+
totalHeals: entries.length,
|
|
456
|
+
successRate: appliedEntries.length > 0 ? Math.round((applied / appliedEntries.length) * 100) : 0,
|
|
457
|
+
recentEntries: entries.slice(-5),
|
|
458
|
+
pendingPatches: dryRuns,
|
|
459
|
+
};
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
module.exports = {
|
|
463
|
+
detectFailure,
|
|
464
|
+
diagnoseFailure,
|
|
465
|
+
generateFixPatch,
|
|
466
|
+
applyFixWithConfirmation,
|
|
467
|
+
getHealingReport,
|
|
468
|
+
};
|