agileflow 2.90.7 → 2.91.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/CHANGELOG.md +5 -0
- package/README.md +6 -6
- package/lib/codebase-indexer.js +810 -0
- package/lib/validate-names.js +3 -3
- package/package.json +4 -1
- package/scripts/obtain-context.js +238 -0
- package/scripts/precompact-context.sh +13 -1
- package/scripts/query-codebase.js +430 -0
- package/scripts/tui/blessed/data/watcher.js +175 -0
- package/scripts/tui/blessed/index.js +244 -0
- package/scripts/tui/blessed/panels/output.js +95 -0
- package/scripts/tui/blessed/panels/sessions.js +143 -0
- package/scripts/tui/blessed/panels/trace.js +91 -0
- package/scripts/tui/blessed/ui/help.js +77 -0
- package/scripts/tui/blessed/ui/screen.js +52 -0
- package/scripts/tui/blessed/ui/statusbar.js +51 -0
- package/scripts/tui/blessed/ui/tabbar.js +99 -0
- package/scripts/tui/index.js +38 -30
- package/scripts/validators/README.md +143 -0
- package/scripts/validators/component-validator.js +212 -0
- package/scripts/validators/json-schema-validator.js +179 -0
- package/scripts/validators/markdown-validator.js +153 -0
- package/scripts/validators/migration-validator.js +117 -0
- package/scripts/validators/security-validator.js +276 -0
- package/scripts/validators/story-format-validator.js +176 -0
- package/scripts/validators/test-result-validator.js +99 -0
- package/scripts/validators/workflow-validator.js +240 -0
- package/src/core/agents/accessibility.md +6 -0
- package/src/core/agents/adr-writer.md +6 -0
- package/src/core/agents/analytics.md +6 -0
- package/src/core/agents/api.md +6 -0
- package/src/core/agents/ci.md +6 -0
- package/src/core/agents/codebase-query.md +237 -0
- package/src/core/agents/compliance.md +6 -0
- package/src/core/agents/configuration-damage-control.md +6 -0
- package/src/core/agents/configuration-visual-e2e.md +6 -0
- package/src/core/agents/database.md +10 -0
- package/src/core/agents/datamigration.md +6 -0
- package/src/core/agents/design.md +6 -0
- package/src/core/agents/devops.md +6 -0
- package/src/core/agents/documentation.md +6 -0
- package/src/core/agents/epic-planner.md +6 -0
- package/src/core/agents/integrations.md +6 -0
- package/src/core/agents/mentor.md +6 -0
- package/src/core/agents/mobile.md +6 -0
- package/src/core/agents/monitoring.md +6 -0
- package/src/core/agents/multi-expert.md +6 -0
- package/src/core/agents/performance.md +6 -0
- package/src/core/agents/product.md +6 -0
- package/src/core/agents/qa.md +6 -0
- package/src/core/agents/readme-updater.md +6 -0
- package/src/core/agents/refactor.md +6 -0
- package/src/core/agents/research.md +6 -0
- package/src/core/agents/security.md +6 -0
- package/src/core/agents/testing.md +10 -0
- package/src/core/agents/ui.md +6 -0
- package/src/core/commands/audit.md +401 -0
- package/src/core/commands/board.md +1 -0
- package/src/core/commands/epic.md +92 -1
- package/src/core/commands/help.md +1 -0
- package/src/core/commands/metrics.md +1 -0
- package/src/core/commands/research/analyze.md +1 -0
- package/src/core/commands/research/ask.md +2 -0
- package/src/core/commands/research/import.md +1 -0
- package/src/core/commands/research/list.md +2 -0
- package/src/core/commands/research/synthesize.md +584 -0
- package/src/core/commands/research/view.md +2 -0
- package/src/core/commands/status.md +126 -1
- package/src/core/commands/story/list.md +9 -9
- package/src/core/commands/story/view.md +1 -0
- package/src/core/experts/codebase-query/expertise.yaml +190 -0
- package/src/core/experts/codebase-query/question.md +73 -0
- package/src/core/experts/codebase-query/self-improve.md +105 -0
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* JSON Schema Validator
|
|
4
|
+
*
|
|
5
|
+
* Validates JSON files for proper structure after Write operations.
|
|
6
|
+
*
|
|
7
|
+
* Exit codes:
|
|
8
|
+
* 0 = Success
|
|
9
|
+
* 2 = Error (Claude will attempt to fix)
|
|
10
|
+
* 1 = Warning (logged but not blocking)
|
|
11
|
+
*
|
|
12
|
+
* Usage in agent hooks:
|
|
13
|
+
* hooks:
|
|
14
|
+
* PostToolUse:
|
|
15
|
+
* - matcher: "Write"
|
|
16
|
+
* hooks:
|
|
17
|
+
* - type: command
|
|
18
|
+
* command: "node .agileflow/hooks/validators/json-schema-validator.js"
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
const fs = require('fs');
|
|
22
|
+
const path = require('path');
|
|
23
|
+
|
|
24
|
+
let input = '';
|
|
25
|
+
process.stdin.on('data', chunk => input += chunk);
|
|
26
|
+
process.stdin.on('end', () => {
|
|
27
|
+
try {
|
|
28
|
+
const context = JSON.parse(input);
|
|
29
|
+
const filePath = context.tool_input?.file_path;
|
|
30
|
+
|
|
31
|
+
// Only validate JSON files
|
|
32
|
+
if (!filePath || !filePath.endsWith('.json')) {
|
|
33
|
+
process.exit(0);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Skip if file doesn't exist (might be a create failure)
|
|
37
|
+
if (!fs.existsSync(filePath)) {
|
|
38
|
+
console.log(`File not found: ${filePath} (skipping validation)`);
|
|
39
|
+
process.exit(0);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const issues = validateJson(filePath);
|
|
43
|
+
|
|
44
|
+
if (issues.length > 0) {
|
|
45
|
+
console.error(`Resolve these JSON issues in ${filePath}:`);
|
|
46
|
+
issues.forEach(i => console.error(` - ${i}`));
|
|
47
|
+
process.exit(2); // Claude will fix
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
console.log(`JSON validation passed: ${filePath}`);
|
|
51
|
+
process.exit(0);
|
|
52
|
+
} catch (e) {
|
|
53
|
+
console.error(`Validator error: ${e.message}`);
|
|
54
|
+
process.exit(1);
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
function validateJson(filePath) {
|
|
59
|
+
const issues = [];
|
|
60
|
+
|
|
61
|
+
try {
|
|
62
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
63
|
+
|
|
64
|
+
// Check for empty file
|
|
65
|
+
if (!content.trim()) {
|
|
66
|
+
issues.push('File is empty');
|
|
67
|
+
return issues;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Try to parse JSON
|
|
71
|
+
const data = JSON.parse(content);
|
|
72
|
+
|
|
73
|
+
// Check for common JSON issues
|
|
74
|
+
if (typeof data !== 'object' || data === null) {
|
|
75
|
+
issues.push('Root must be an object or array');
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Specific checks for known files
|
|
79
|
+
const fileName = path.basename(filePath);
|
|
80
|
+
|
|
81
|
+
if (fileName === 'status.json') {
|
|
82
|
+
issues.push(...validateStatusJson(data));
|
|
83
|
+
} else if (fileName === 'package.json') {
|
|
84
|
+
issues.push(...validatePackageJson(data));
|
|
85
|
+
} else if (fileName === 'tsconfig.json') {
|
|
86
|
+
issues.push(...validateTsConfig(data));
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
} catch (e) {
|
|
90
|
+
if (e instanceof SyntaxError) {
|
|
91
|
+
issues.push(`Invalid JSON syntax: ${e.message}`);
|
|
92
|
+
} else {
|
|
93
|
+
issues.push(`Read error: ${e.message}`);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return issues;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function validateStatusJson(data) {
|
|
101
|
+
const issues = [];
|
|
102
|
+
|
|
103
|
+
// Check for required structure - status.json has epics object with embedded stories
|
|
104
|
+
if (!data.epics && !data.current_story && !data.updated) {
|
|
105
|
+
issues.push('status.json should have epics, current_story, or updated field');
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Validate epics object if present
|
|
109
|
+
if (data.epics) {
|
|
110
|
+
if (typeof data.epics !== 'object' || Array.isArray(data.epics)) {
|
|
111
|
+
issues.push('epics must be an object (not array)');
|
|
112
|
+
} else {
|
|
113
|
+
Object.entries(data.epics).forEach(([epicId, epic]) => {
|
|
114
|
+
if (!epic.title) {
|
|
115
|
+
issues.push(`Epic ${epicId} missing 'title' field`);
|
|
116
|
+
}
|
|
117
|
+
if (epic.stories && !Array.isArray(epic.stories)) {
|
|
118
|
+
issues.push(`Epic ${epicId} stories must be an array`);
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Validate stories - can be object (map by ID) or array
|
|
125
|
+
if (data.stories) {
|
|
126
|
+
if (Array.isArray(data.stories)) {
|
|
127
|
+
// Array format
|
|
128
|
+
data.stories.forEach((story, index) => {
|
|
129
|
+
if (!story.id) {
|
|
130
|
+
issues.push(`Story at index ${index} missing required 'id' field`);
|
|
131
|
+
}
|
|
132
|
+
if (!story.title && !story.name) {
|
|
133
|
+
issues.push(`Story ${story.id || index} missing 'title' or 'name' field`);
|
|
134
|
+
}
|
|
135
|
+
if (story.status && !['pending', 'in_progress', 'completed', 'done', 'blocked', 'ready', 'in-progress', 'in_review', 'in-review', 'archived'].includes(story.status)) {
|
|
136
|
+
issues.push(`Story ${story.id || index} has invalid status: ${story.status}`);
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
} else if (typeof data.stories === 'object') {
|
|
140
|
+
// Object/map format (keyed by story ID)
|
|
141
|
+
Object.entries(data.stories).forEach(([storyId, story]) => {
|
|
142
|
+
if (!story.title && !story.name) {
|
|
143
|
+
issues.push(`Story ${storyId} missing 'title' or 'name' field`);
|
|
144
|
+
}
|
|
145
|
+
if (story.status && !['pending', 'in_progress', 'completed', 'done', 'blocked', 'ready', 'in-progress', 'in_review', 'in-review', 'archived'].includes(story.status)) {
|
|
146
|
+
issues.push(`Story ${storyId} has invalid status: ${story.status}`);
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return issues;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function validatePackageJson(data) {
|
|
156
|
+
const issues = [];
|
|
157
|
+
|
|
158
|
+
if (!data.name) {
|
|
159
|
+
issues.push('package.json requires "name" field');
|
|
160
|
+
}
|
|
161
|
+
if (!data.version) {
|
|
162
|
+
issues.push('package.json requires "version" field');
|
|
163
|
+
}
|
|
164
|
+
if (data.version && !/^\d+\.\d+\.\d+/.test(data.version)) {
|
|
165
|
+
issues.push(`Invalid version format: ${data.version} (expected semver)`);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
return issues;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function validateTsConfig(data) {
|
|
172
|
+
const issues = [];
|
|
173
|
+
|
|
174
|
+
if (!data.compilerOptions) {
|
|
175
|
+
issues.push('tsconfig.json should have compilerOptions');
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
return issues;
|
|
179
|
+
}
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Markdown Validator
|
|
4
|
+
*
|
|
5
|
+
* Validates markdown files for proper structure after Write operations.
|
|
6
|
+
*
|
|
7
|
+
* Exit codes:
|
|
8
|
+
* 0 = Success
|
|
9
|
+
* 2 = Error (Claude will attempt to fix)
|
|
10
|
+
* 1 = Warning (logged but not blocking)
|
|
11
|
+
*
|
|
12
|
+
* Usage in agent hooks:
|
|
13
|
+
* hooks:
|
|
14
|
+
* PostToolUse:
|
|
15
|
+
* - matcher: "Write"
|
|
16
|
+
* hooks:
|
|
17
|
+
* - type: command
|
|
18
|
+
* command: "node .agileflow/hooks/validators/markdown-validator.js"
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
const fs = require('fs');
|
|
22
|
+
const path = require('path');
|
|
23
|
+
|
|
24
|
+
let input = '';
|
|
25
|
+
process.stdin.on('data', chunk => input += chunk);
|
|
26
|
+
process.stdin.on('end', () => {
|
|
27
|
+
try {
|
|
28
|
+
const context = JSON.parse(input);
|
|
29
|
+
const filePath = context.tool_input?.file_path;
|
|
30
|
+
|
|
31
|
+
// Only validate markdown files
|
|
32
|
+
if (!filePath || !filePath.endsWith('.md')) {
|
|
33
|
+
process.exit(0);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Skip if file doesn't exist
|
|
37
|
+
if (!fs.existsSync(filePath)) {
|
|
38
|
+
console.log(`File not found: ${filePath} (skipping validation)`);
|
|
39
|
+
process.exit(0);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const issues = validateMarkdown(filePath);
|
|
43
|
+
|
|
44
|
+
if (issues.length > 0) {
|
|
45
|
+
console.error(`Resolve these markdown issues in ${filePath}:`);
|
|
46
|
+
issues.forEach(i => console.error(` - ${i}`));
|
|
47
|
+
process.exit(2); // Claude will fix
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
console.log(`Markdown validation passed: ${filePath}`);
|
|
51
|
+
process.exit(0);
|
|
52
|
+
} catch (e) {
|
|
53
|
+
console.error(`Validator error: ${e.message}`);
|
|
54
|
+
process.exit(1);
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
function validateMarkdown(filePath) {
|
|
59
|
+
const issues = [];
|
|
60
|
+
|
|
61
|
+
try {
|
|
62
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
63
|
+
const lines = content.split('\n');
|
|
64
|
+
const fileName = path.basename(filePath);
|
|
65
|
+
|
|
66
|
+
// Check for empty file
|
|
67
|
+
if (!content.trim()) {
|
|
68
|
+
issues.push('File is empty');
|
|
69
|
+
return issues;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Check for title (first line should be # heading)
|
|
73
|
+
const firstContentLine = lines.find(l => l.trim());
|
|
74
|
+
if (!firstContentLine?.startsWith('#')) {
|
|
75
|
+
issues.push('Document should start with a heading (#)');
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Check for broken links (basic check)
|
|
79
|
+
const brokenLinkPattern = /\[([^\]]*)\]\(\s*\)/g;
|
|
80
|
+
let match;
|
|
81
|
+
while ((match = brokenLinkPattern.exec(content)) !== null) {
|
|
82
|
+
issues.push(`Empty link found: [${match[1]}]()`);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Check for unclosed code blocks
|
|
86
|
+
const codeBlockCount = (content.match(/```/g) || []).length;
|
|
87
|
+
if (codeBlockCount % 2 !== 0) {
|
|
88
|
+
issues.push('Unclosed code block (odd number of ``` markers)');
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Check for heading hierarchy issues
|
|
92
|
+
let lastHeadingLevel = 0;
|
|
93
|
+
lines.forEach((line, index) => {
|
|
94
|
+
const headingMatch = line.match(/^(#{1,6})\s/);
|
|
95
|
+
if (headingMatch) {
|
|
96
|
+
const level = headingMatch[1].length;
|
|
97
|
+
// Skip from level 0 to level 1 is OK
|
|
98
|
+
if (lastHeadingLevel > 0 && level > lastHeadingLevel + 1) {
|
|
99
|
+
issues.push(`Line ${index + 1}: Heading level jump (h${lastHeadingLevel} to h${level})`);
|
|
100
|
+
}
|
|
101
|
+
lastHeadingLevel = level;
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
// Check for specific file types
|
|
106
|
+
if (fileName.startsWith('adr-') || fileName.startsWith('ADR-')) {
|
|
107
|
+
issues.push(...validateADR(content));
|
|
108
|
+
} else if (filePath.includes('/10-research/')) {
|
|
109
|
+
issues.push(...validateResearchNote(content));
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
} catch (e) {
|
|
113
|
+
issues.push(`Read error: ${e.message}`);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return issues;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function validateADR(content) {
|
|
120
|
+
const issues = [];
|
|
121
|
+
|
|
122
|
+
const requiredSections = ['## Context', '## Decision', '## Consequences'];
|
|
123
|
+
requiredSections.forEach(section => {
|
|
124
|
+
if (!content.includes(section)) {
|
|
125
|
+
issues.push(`ADR missing required section: ${section}`);
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
if (!content.includes('**Status**:')) {
|
|
130
|
+
issues.push('ADR missing Status field');
|
|
131
|
+
}
|
|
132
|
+
if (!content.includes('**Date**:')) {
|
|
133
|
+
issues.push('ADR missing Date field');
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return issues;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function validateResearchNote(content) {
|
|
140
|
+
const issues = [];
|
|
141
|
+
|
|
142
|
+
if (!content.includes('**Import Date**:')) {
|
|
143
|
+
issues.push('Research note missing Import Date');
|
|
144
|
+
}
|
|
145
|
+
if (!content.includes('## Summary')) {
|
|
146
|
+
issues.push('Research note missing Summary section');
|
|
147
|
+
}
|
|
148
|
+
if (!content.includes('## Key Findings')) {
|
|
149
|
+
issues.push('Research note missing Key Findings section');
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return issues;
|
|
153
|
+
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Migration Validator
|
|
4
|
+
*
|
|
5
|
+
* Validates database migration commands for proper patterns.
|
|
6
|
+
*
|
|
7
|
+
* Exit codes:
|
|
8
|
+
* 0 = Success
|
|
9
|
+
* 2 = Error (Claude will attempt to fix)
|
|
10
|
+
* 1 = Warning (logged but not blocking)
|
|
11
|
+
*
|
|
12
|
+
* Usage in agent hooks:
|
|
13
|
+
* hooks:
|
|
14
|
+
* PostToolUse:
|
|
15
|
+
* - matcher: "Bash"
|
|
16
|
+
* hooks:
|
|
17
|
+
* - type: command
|
|
18
|
+
* command: "node .agileflow/hooks/validators/migration-validator.js"
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
let input = '';
|
|
22
|
+
process.stdin.on('data', chunk => input += chunk);
|
|
23
|
+
process.stdin.on('end', () => {
|
|
24
|
+
try {
|
|
25
|
+
const context = JSON.parse(input);
|
|
26
|
+
const command = context.tool_input?.command;
|
|
27
|
+
const result = context.result || '';
|
|
28
|
+
|
|
29
|
+
// Only validate migration-related commands
|
|
30
|
+
if (!command || !isMigrationCommand(command)) {
|
|
31
|
+
process.exit(0);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const issues = validateMigration(command, result);
|
|
35
|
+
|
|
36
|
+
if (issues.length > 0) {
|
|
37
|
+
console.error(`Migration validation issues:`);
|
|
38
|
+
issues.forEach(i => console.error(` - ${i}`));
|
|
39
|
+
process.exit(2); // Claude will fix
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
console.log(`Migration validation passed: ${command.substring(0, 50)}...`);
|
|
43
|
+
process.exit(0);
|
|
44
|
+
} catch (e) {
|
|
45
|
+
console.error(`Validator error: ${e.message}`);
|
|
46
|
+
process.exit(1);
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
function isMigrationCommand(command) {
|
|
51
|
+
const migrationPatterns = [
|
|
52
|
+
/prisma\s+migrate/i,
|
|
53
|
+
/drizzle-kit/i,
|
|
54
|
+
/typeorm\s+migration/i,
|
|
55
|
+
/knex\s+migrate/i,
|
|
56
|
+
/sequelize\s+db:migrate/i,
|
|
57
|
+
/alembic/i,
|
|
58
|
+
/flyway/i,
|
|
59
|
+
/liquibase/i,
|
|
60
|
+
/rails\s+db:migrate/i,
|
|
61
|
+
/django.*migrate/i,
|
|
62
|
+
/psql.*-f.*\.sql/i,
|
|
63
|
+
/mysql.*<.*\.sql/i,
|
|
64
|
+
];
|
|
65
|
+
|
|
66
|
+
return migrationPatterns.some(pattern => pattern.test(command));
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function validateMigration(command, result) {
|
|
70
|
+
const issues = [];
|
|
71
|
+
|
|
72
|
+
// Check for destructive operations without safeguards
|
|
73
|
+
const destructivePatterns = [
|
|
74
|
+
{ pattern: /DROP\s+TABLE/i, message: 'DROP TABLE detected - ensure backup exists and this is intentional' },
|
|
75
|
+
{ pattern: /DROP\s+DATABASE/i, message: 'DROP DATABASE detected - this is extremely destructive!' },
|
|
76
|
+
{ pattern: /TRUNCATE/i, message: 'TRUNCATE detected - this removes all data permanently' },
|
|
77
|
+
{ pattern: /DELETE\s+FROM.*WHERE\s*$/i, message: 'DELETE without WHERE clause detected - will delete all rows' },
|
|
78
|
+
{ pattern: /--force|--skip-safe/i, message: 'Force flag used - bypassing safety checks' },
|
|
79
|
+
];
|
|
80
|
+
|
|
81
|
+
for (const { pattern, message } of destructivePatterns) {
|
|
82
|
+
if (pattern.test(command) || pattern.test(result)) {
|
|
83
|
+
issues.push(message);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Check for production environment safeguards
|
|
88
|
+
if (/production|prod/i.test(command) && !/--dry-run/i.test(command)) {
|
|
89
|
+
issues.push('Production migration without --dry-run flag - consider dry run first');
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Check for failed migrations in output
|
|
93
|
+
const failurePatterns = [
|
|
94
|
+
{ pattern: /error.*migration/i, message: 'Migration error detected in output' },
|
|
95
|
+
{ pattern: /rollback.*failed/i, message: 'Rollback failure detected' },
|
|
96
|
+
{ pattern: /constraint.*violation/i, message: 'Database constraint violation' },
|
|
97
|
+
{ pattern: /duplicate.*key/i, message: 'Duplicate key error - migration may have partially applied' },
|
|
98
|
+
{ pattern: /already exists/i, message: 'Object already exists - migration may need cleanup' },
|
|
99
|
+
];
|
|
100
|
+
|
|
101
|
+
for (const { pattern, message } of failurePatterns) {
|
|
102
|
+
if (pattern.test(result)) {
|
|
103
|
+
issues.push(message);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Check for missing rollback strategy
|
|
108
|
+
if (/migrate.*dev|migrate.*run/i.test(command)) {
|
|
109
|
+
// Prisma and similar ORMs
|
|
110
|
+
if (!/down|rollback|reset/i.test(result) && result.includes('migration')) {
|
|
111
|
+
// Just a warning, not blocking
|
|
112
|
+
console.log('Note: Ensure rollback migration exists for this change');
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return issues;
|
|
117
|
+
}
|