agileflow 2.90.7 → 2.92.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 (144) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/README.md +6 -6
  3. package/lib/README.md +178 -0
  4. package/lib/codebase-indexer.js +818 -0
  5. package/lib/colors.js +190 -12
  6. package/lib/consent.js +232 -0
  7. package/lib/correlation.js +277 -0
  8. package/lib/error-codes.js +46 -0
  9. package/lib/errors.js +48 -6
  10. package/lib/file-cache.js +182 -0
  11. package/lib/format-error.js +156 -0
  12. package/lib/path-resolver.js +155 -7
  13. package/lib/paths.js +212 -20
  14. package/lib/placeholder-registry.js +205 -0
  15. package/lib/registry-di.js +358 -0
  16. package/lib/result-schema.js +363 -0
  17. package/lib/result.js +210 -0
  18. package/lib/session-registry.js +13 -0
  19. package/lib/session-state-machine.js +465 -0
  20. package/lib/validate-commands.js +308 -0
  21. package/lib/validate-names.js +3 -3
  22. package/lib/validate.js +116 -52
  23. package/package.json +4 -1
  24. package/scripts/af +34 -0
  25. package/scripts/agent-loop.js +63 -9
  26. package/scripts/agileflow-configure.js +2 -2
  27. package/scripts/agileflow-welcome.js +435 -23
  28. package/scripts/archive-completed-stories.sh +57 -11
  29. package/scripts/claude-tmux.sh +102 -0
  30. package/scripts/damage-control-bash.js +3 -70
  31. package/scripts/damage-control-edit.js +3 -20
  32. package/scripts/damage-control-write.js +3 -20
  33. package/scripts/dependency-check.js +310 -0
  34. package/scripts/get-env.js +11 -4
  35. package/scripts/lib/configure-detect.js +23 -1
  36. package/scripts/lib/configure-features.js +43 -2
  37. package/scripts/lib/context-formatter.js +771 -0
  38. package/scripts/lib/context-loader.js +699 -0
  39. package/scripts/lib/damage-control-utils.js +107 -0
  40. package/scripts/lib/json-utils.sh +162 -0
  41. package/scripts/lib/state-migrator.js +353 -0
  42. package/scripts/lib/story-state-machine.js +437 -0
  43. package/scripts/obtain-context.js +118 -1048
  44. package/scripts/pre-push-check.sh +46 -0
  45. package/scripts/precompact-context.sh +36 -11
  46. package/scripts/query-codebase.js +538 -0
  47. package/scripts/ralph-loop.js +5 -5
  48. package/scripts/session-manager.js +220 -42
  49. package/scripts/spawn-parallel.js +651 -0
  50. package/scripts/tui/blessed/data/watcher.js +180 -0
  51. package/scripts/tui/blessed/index.js +244 -0
  52. package/scripts/tui/blessed/panels/output.js +101 -0
  53. package/scripts/tui/blessed/panels/sessions.js +150 -0
  54. package/scripts/tui/blessed/panels/trace.js +97 -0
  55. package/scripts/tui/blessed/ui/help.js +77 -0
  56. package/scripts/tui/blessed/ui/screen.js +52 -0
  57. package/scripts/tui/blessed/ui/statusbar.js +47 -0
  58. package/scripts/tui/blessed/ui/tabbar.js +99 -0
  59. package/scripts/tui/index.js +38 -30
  60. package/scripts/validators/README.md +143 -0
  61. package/scripts/validators/component-validator.js +239 -0
  62. package/scripts/validators/json-schema-validator.js +186 -0
  63. package/scripts/validators/markdown-validator.js +152 -0
  64. package/scripts/validators/migration-validator.js +129 -0
  65. package/scripts/validators/security-validator.js +380 -0
  66. package/scripts/validators/story-format-validator.js +197 -0
  67. package/scripts/validators/test-result-validator.js +114 -0
  68. package/scripts/validators/workflow-validator.js +247 -0
  69. package/src/core/agents/accessibility.md +6 -0
  70. package/src/core/agents/adr-writer.md +6 -0
  71. package/src/core/agents/analytics.md +6 -0
  72. package/src/core/agents/api.md +6 -0
  73. package/src/core/agents/ci.md +6 -0
  74. package/src/core/agents/codebase-query.md +261 -0
  75. package/src/core/agents/compliance.md +6 -0
  76. package/src/core/agents/configuration-damage-control.md +6 -0
  77. package/src/core/agents/configuration-visual-e2e.md +6 -0
  78. package/src/core/agents/database.md +10 -0
  79. package/src/core/agents/datamigration.md +6 -0
  80. package/src/core/agents/design.md +6 -0
  81. package/src/core/agents/devops.md +6 -0
  82. package/src/core/agents/documentation.md +6 -0
  83. package/src/core/agents/epic-planner.md +6 -0
  84. package/src/core/agents/integrations.md +6 -0
  85. package/src/core/agents/mentor.md +6 -0
  86. package/src/core/agents/mobile.md +6 -0
  87. package/src/core/agents/monitoring.md +6 -0
  88. package/src/core/agents/multi-expert.md +6 -0
  89. package/src/core/agents/performance.md +6 -0
  90. package/src/core/agents/product.md +6 -0
  91. package/src/core/agents/qa.md +6 -0
  92. package/src/core/agents/readme-updater.md +6 -0
  93. package/src/core/agents/refactor.md +6 -0
  94. package/src/core/agents/research.md +6 -0
  95. package/src/core/agents/security.md +6 -0
  96. package/src/core/agents/testing.md +10 -0
  97. package/src/core/agents/ui.md +6 -0
  98. package/src/core/commands/adr.md +114 -0
  99. package/src/core/commands/agent.md +120 -0
  100. package/src/core/commands/assign.md +145 -0
  101. package/src/core/commands/audit.md +401 -0
  102. package/src/core/commands/babysit.md +32 -5
  103. package/src/core/commands/board.md +1 -0
  104. package/src/core/commands/changelog.md +118 -0
  105. package/src/core/commands/configure.md +42 -6
  106. package/src/core/commands/diagnose.md +114 -0
  107. package/src/core/commands/epic.md +205 -1
  108. package/src/core/commands/handoff.md +128 -0
  109. package/src/core/commands/help.md +76 -0
  110. package/src/core/commands/metrics.md +1 -0
  111. package/src/core/commands/pr.md +96 -0
  112. package/src/core/commands/research/analyze.md +1 -0
  113. package/src/core/commands/research/ask.md +2 -0
  114. package/src/core/commands/research/import.md +1 -0
  115. package/src/core/commands/research/list.md +2 -0
  116. package/src/core/commands/research/synthesize.md +584 -0
  117. package/src/core/commands/research/view.md +2 -0
  118. package/src/core/commands/roadmap/analyze.md +400 -0
  119. package/src/core/commands/session/new.md +113 -6
  120. package/src/core/commands/session/spawn.md +197 -0
  121. package/src/core/commands/sprint.md +22 -0
  122. package/src/core/commands/status.md +200 -1
  123. package/src/core/commands/story/list.md +9 -9
  124. package/src/core/commands/story/view.md +1 -0
  125. package/src/core/commands/story.md +143 -4
  126. package/src/core/experts/codebase-query/expertise.yaml +190 -0
  127. package/src/core/experts/codebase-query/question.md +73 -0
  128. package/src/core/experts/codebase-query/self-improve.md +105 -0
  129. package/src/core/templates/agileflow-metadata.json +55 -2
  130. package/src/core/templates/plan-template.md +125 -0
  131. package/src/core/templates/story-lifecycle.md +213 -0
  132. package/src/core/templates/story-template.md +4 -0
  133. package/src/core/templates/tdd-test-template.js +241 -0
  134. package/tools/cli/commands/setup.js +86 -0
  135. package/tools/cli/installers/core/installer.js +94 -0
  136. package/tools/cli/installers/ide/_base-ide.js +20 -11
  137. package/tools/cli/installers/ide/codex.js +29 -47
  138. package/tools/cli/lib/config-manager.js +17 -2
  139. package/tools/cli/lib/content-transformer.js +271 -0
  140. package/tools/cli/lib/error-handler.js +14 -22
  141. package/tools/cli/lib/ide-error-factory.js +421 -0
  142. package/tools/cli/lib/ide-health-monitor.js +364 -0
  143. package/tools/cli/lib/ide-registry.js +114 -1
  144. package/tools/cli/lib/ui.js +14 -25
@@ -0,0 +1,239 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Component Validator
4
+ *
5
+ * Validates React/Vue/Svelte component files for common issues.
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/component-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 component files
32
+ if (!filePath || !isComponentFile(filePath)) {
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 = validateComponent(filePath);
43
+
44
+ if (issues.length > 0) {
45
+ console.error(`Fix these component issues in ${filePath}:`);
46
+ issues.forEach(i => console.error(` - ${i}`));
47
+ process.exit(2); // Claude will fix
48
+ }
49
+
50
+ console.log(`Component 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 isComponentFile(filePath) {
59
+ const ext = path.extname(filePath).toLowerCase();
60
+ const componentExtensions = ['.jsx', '.tsx', '.vue', '.svelte'];
61
+
62
+ // Also check for .js/.ts files in component directories
63
+ if (['.js', '.ts'].includes(ext)) {
64
+ const normalizedPath = filePath.toLowerCase();
65
+ return (
66
+ normalizedPath.includes('/components/') ||
67
+ normalizedPath.includes('/pages/') ||
68
+ normalizedPath.includes('/views/')
69
+ );
70
+ }
71
+
72
+ return componentExtensions.includes(ext);
73
+ }
74
+
75
+ function validateComponent(filePath) {
76
+ const issues = [];
77
+
78
+ try {
79
+ const content = fs.readFileSync(filePath, 'utf8');
80
+ const ext = path.extname(filePath).toLowerCase();
81
+ const fileName = path.basename(filePath, ext);
82
+
83
+ // Check for empty component
84
+ if (!content.trim()) {
85
+ issues.push('Component file is empty');
86
+ return issues;
87
+ }
88
+
89
+ // React/JSX/TSX validation
90
+ if (['.jsx', '.tsx'].includes(ext) || content.includes('React')) {
91
+ issues.push(...validateReactComponent(content, fileName));
92
+ }
93
+
94
+ // Vue validation
95
+ if (ext === '.vue') {
96
+ issues.push(...validateVueComponent(content));
97
+ }
98
+
99
+ // Svelte validation
100
+ if (ext === '.svelte') {
101
+ issues.push(...validateSvelteComponent(content));
102
+ }
103
+
104
+ // General accessibility checks
105
+ issues.push(...validateAccessibility(content));
106
+ } catch (e) {
107
+ issues.push(`Read error: ${e.message}`);
108
+ }
109
+
110
+ return issues;
111
+ }
112
+
113
+ function validateReactComponent(content, fileName) {
114
+ const issues = [];
115
+
116
+ // Check for component export
117
+ if (
118
+ !content.includes('export default') &&
119
+ !content.includes('export function') &&
120
+ !content.includes('export const')
121
+ ) {
122
+ issues.push(
123
+ 'Component should have an export (export default, export function, or export const)'
124
+ );
125
+ }
126
+
127
+ // Check for React import in JSX files
128
+ if (
129
+ content.includes('React.') &&
130
+ !content.includes("from 'react'") &&
131
+ !content.includes('from "react"')
132
+ ) {
133
+ issues.push('Using React. prefix but React is not imported');
134
+ }
135
+
136
+ // Check for proper function/component naming (should match filename for default exports)
137
+ const pascalCaseRegex = /^[A-Z][a-zA-Z0-9]*$/;
138
+ if (!pascalCaseRegex.test(fileName) && !fileName.startsWith('use')) {
139
+ // Only warn, don't block - could be a utility file
140
+ console.log(`Note: Component filename "${fileName}" should be PascalCase`);
141
+ }
142
+
143
+ // Check for inline styles (prefer CSS modules or styled-components)
144
+ const inlineStyleCount = (content.match(/style=\{\{/g) || []).length;
145
+ if (inlineStyleCount > 5) {
146
+ issues.push(
147
+ `Too many inline styles (${inlineStyleCount}) - consider using CSS modules or styled-components`
148
+ );
149
+ }
150
+
151
+ // Check for console.log in production code
152
+ if (
153
+ content.includes('console.log') &&
154
+ !content.includes('// debug') &&
155
+ !content.includes('// DEBUG')
156
+ ) {
157
+ console.log("Note: console.log found - ensure it's removed before production");
158
+ }
159
+
160
+ // Check for missing key prop in map
161
+ if (content.includes('.map(') && content.includes('<') && !content.includes('key=')) {
162
+ issues.push('Array .map() rendering elements should include key prop');
163
+ }
164
+
165
+ return issues;
166
+ }
167
+
168
+ function validateVueComponent(content) {
169
+ const issues = [];
170
+
171
+ // Check for required template section
172
+ if (!content.includes('<template>') && !content.includes('<template ')) {
173
+ issues.push('Vue component must have a <template> section');
174
+ }
175
+
176
+ // Check for script section
177
+ if (!content.includes('<script') && !content.includes('<script>')) {
178
+ // Not strictly required but recommended
179
+ console.log('Note: Vue component has no <script> section');
180
+ }
181
+
182
+ // Check for scoped styles (recommended)
183
+ if (
184
+ content.includes('<style>') &&
185
+ !content.includes('<style scoped') &&
186
+ !content.includes('scoped>')
187
+ ) {
188
+ console.log('Note: Consider using scoped styles to prevent CSS leaks');
189
+ }
190
+
191
+ return issues;
192
+ }
193
+
194
+ function validateSvelteComponent(content) {
195
+ const issues = [];
196
+
197
+ // Check for script section
198
+ if (!content.includes('<script') && !content.includes('<script>')) {
199
+ console.log('Note: Svelte component has no <script> section');
200
+ }
201
+
202
+ return issues;
203
+ }
204
+
205
+ function validateAccessibility(content) {
206
+ const issues = [];
207
+
208
+ // Check for images without alt
209
+ const imgWithoutAlt = content.match(/<img[^>]*(?!alt=)[^>]*>/gi) || [];
210
+ const imgWithEmptyAlt = content.match(/<img[^>]*alt=["']["'][^>]*>/gi) || [];
211
+
212
+ if (imgWithoutAlt.length > 0) {
213
+ issues.push(`Found ${imgWithoutAlt.length} <img> tag(s) without alt attribute`);
214
+ }
215
+
216
+ // Check for button without type
217
+ if (content.includes('<button') && !content.includes('type=')) {
218
+ console.log('Note: <button> elements should have explicit type attribute');
219
+ }
220
+
221
+ // Check for click handlers on non-interactive elements
222
+ const clickOnDiv = content.match(/onClick[^>]*>[^<]*<\/div>/gi) || [];
223
+ if (clickOnDiv.length > 0) {
224
+ issues.push(
225
+ 'onClick on <div> detected - use <button> for interactive elements (accessibility)'
226
+ );
227
+ }
228
+
229
+ // Check for form inputs without labels
230
+ if (
231
+ content.includes('<input') &&
232
+ !content.includes('<label') &&
233
+ !content.includes('aria-label')
234
+ ) {
235
+ console.log('Note: <input> elements should have associated <label> or aria-label');
236
+ }
237
+
238
+ return issues;
239
+ }
@@ -0,0 +1,186 @@
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
+ // Import status constants from single source of truth
25
+ const { VALID_STATUSES } = require('../lib/story-state-machine');
26
+
27
+ // Extended statuses for backward compatibility (maps to canonical values)
28
+ // These legacy values are accepted but should be migrated to canonical values
29
+ const LEGACY_STATUSES = ['pending', 'done', 'in-progress', 'in-review'];
30
+ const ALL_ACCEPTED_STATUSES = [...VALID_STATUSES, ...LEGACY_STATUSES];
31
+
32
+ let input = '';
33
+ process.stdin.on('data', chunk => (input += chunk));
34
+ process.stdin.on('end', () => {
35
+ try {
36
+ const context = JSON.parse(input);
37
+ const filePath = context.tool_input?.file_path;
38
+
39
+ // Only validate JSON files
40
+ if (!filePath || !filePath.endsWith('.json')) {
41
+ process.exit(0);
42
+ }
43
+
44
+ // Skip if file doesn't exist (might be a create failure)
45
+ if (!fs.existsSync(filePath)) {
46
+ console.log(`File not found: ${filePath} (skipping validation)`);
47
+ process.exit(0);
48
+ }
49
+
50
+ const issues = validateJson(filePath);
51
+
52
+ if (issues.length > 0) {
53
+ console.error(`Resolve these JSON issues in ${filePath}:`);
54
+ issues.forEach(i => console.error(` - ${i}`));
55
+ process.exit(2); // Claude will fix
56
+ }
57
+
58
+ console.log(`JSON validation passed: ${filePath}`);
59
+ process.exit(0);
60
+ } catch (e) {
61
+ console.error(`Validator error: ${e.message}`);
62
+ process.exit(1);
63
+ }
64
+ });
65
+
66
+ function validateJson(filePath) {
67
+ const issues = [];
68
+
69
+ try {
70
+ const content = fs.readFileSync(filePath, 'utf8');
71
+
72
+ // Check for empty file
73
+ if (!content.trim()) {
74
+ issues.push('File is empty');
75
+ return issues;
76
+ }
77
+
78
+ // Try to parse JSON
79
+ const data = JSON.parse(content);
80
+
81
+ // Check for common JSON issues
82
+ if (typeof data !== 'object' || data === null) {
83
+ issues.push('Root must be an object or array');
84
+ }
85
+
86
+ // Specific checks for known files
87
+ const fileName = path.basename(filePath);
88
+
89
+ if (fileName === 'status.json') {
90
+ issues.push(...validateStatusJson(data));
91
+ } else if (fileName === 'package.json') {
92
+ issues.push(...validatePackageJson(data));
93
+ } else if (fileName === 'tsconfig.json') {
94
+ issues.push(...validateTsConfig(data));
95
+ }
96
+ } catch (e) {
97
+ if (e instanceof SyntaxError) {
98
+ issues.push(`Invalid JSON syntax: ${e.message}`);
99
+ } else {
100
+ issues.push(`Read error: ${e.message}`);
101
+ }
102
+ }
103
+
104
+ return issues;
105
+ }
106
+
107
+ function validateStatusJson(data) {
108
+ const issues = [];
109
+
110
+ // Check for required structure - status.json has epics object with embedded stories
111
+ if (!data.epics && !data.current_story && !data.updated) {
112
+ issues.push('status.json should have epics, current_story, or updated field');
113
+ }
114
+
115
+ // Validate epics object if present
116
+ if (data.epics) {
117
+ if (typeof data.epics !== 'object' || Array.isArray(data.epics)) {
118
+ issues.push('epics must be an object (not array)');
119
+ } else {
120
+ Object.entries(data.epics).forEach(([epicId, epic]) => {
121
+ if (!epic.title) {
122
+ issues.push(`Epic ${epicId} missing 'title' field`);
123
+ }
124
+ if (epic.stories && !Array.isArray(epic.stories)) {
125
+ issues.push(`Epic ${epicId} stories must be an array`);
126
+ }
127
+ });
128
+ }
129
+ }
130
+
131
+ // Validate stories - can be object (map by ID) or array
132
+ if (data.stories) {
133
+ if (Array.isArray(data.stories)) {
134
+ // Array format
135
+ data.stories.forEach((story, index) => {
136
+ if (!story.id) {
137
+ issues.push(`Story at index ${index} missing required 'id' field`);
138
+ }
139
+ if (!story.title && !story.name) {
140
+ issues.push(`Story ${story.id || index} missing 'title' or 'name' field`);
141
+ }
142
+ if (story.status && !ALL_ACCEPTED_STATUSES.includes(story.status)) {
143
+ issues.push(`Story ${story.id || index} has invalid status: ${story.status}`);
144
+ }
145
+ });
146
+ } else if (typeof data.stories === 'object') {
147
+ // Object/map format (keyed by story ID)
148
+ Object.entries(data.stories).forEach(([storyId, story]) => {
149
+ if (!story.title && !story.name) {
150
+ issues.push(`Story ${storyId} missing 'title' or 'name' field`);
151
+ }
152
+ if (story.status && !ALL_ACCEPTED_STATUSES.includes(story.status)) {
153
+ issues.push(`Story ${storyId} has invalid status: ${story.status}`);
154
+ }
155
+ });
156
+ }
157
+ }
158
+
159
+ return issues;
160
+ }
161
+
162
+ function validatePackageJson(data) {
163
+ const issues = [];
164
+
165
+ if (!data.name) {
166
+ issues.push('package.json requires "name" field');
167
+ }
168
+ if (!data.version) {
169
+ issues.push('package.json requires "version" field');
170
+ }
171
+ if (data.version && !/^\d+\.\d+\.\d+/.test(data.version)) {
172
+ issues.push(`Invalid version format: ${data.version} (expected semver)`);
173
+ }
174
+
175
+ return issues;
176
+ }
177
+
178
+ function validateTsConfig(data) {
179
+ const issues = [];
180
+
181
+ if (!data.compilerOptions) {
182
+ issues.push('tsconfig.json should have compilerOptions');
183
+ }
184
+
185
+ return issues;
186
+ }
@@ -0,0 +1,152 @@
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
+ } catch (e) {
112
+ issues.push(`Read error: ${e.message}`);
113
+ }
114
+
115
+ return issues;
116
+ }
117
+
118
+ function validateADR(content) {
119
+ const issues = [];
120
+
121
+ const requiredSections = ['## Context', '## Decision', '## Consequences'];
122
+ requiredSections.forEach(section => {
123
+ if (!content.includes(section)) {
124
+ issues.push(`ADR missing required section: ${section}`);
125
+ }
126
+ });
127
+
128
+ if (!content.includes('**Status**:')) {
129
+ issues.push('ADR missing Status field');
130
+ }
131
+ if (!content.includes('**Date**:')) {
132
+ issues.push('ADR missing Date field');
133
+ }
134
+
135
+ return issues;
136
+ }
137
+
138
+ function validateResearchNote(content) {
139
+ const issues = [];
140
+
141
+ if (!content.includes('**Import Date**:')) {
142
+ issues.push('Research note missing Import Date');
143
+ }
144
+ if (!content.includes('## Summary')) {
145
+ issues.push('Research note missing Summary section');
146
+ }
147
+ if (!content.includes('## Key Findings')) {
148
+ issues.push('Research note missing Key Findings section');
149
+ }
150
+
151
+ return issues;
152
+ }
@@ -0,0 +1,129 @@
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
+ {
75
+ pattern: /DROP\s+TABLE/i,
76
+ message: 'DROP TABLE detected - ensure backup exists and this is intentional',
77
+ },
78
+ {
79
+ pattern: /DROP\s+DATABASE/i,
80
+ message: 'DROP DATABASE detected - this is extremely destructive!',
81
+ },
82
+ { pattern: /TRUNCATE/i, message: 'TRUNCATE detected - this removes all data permanently' },
83
+ {
84
+ pattern: /DELETE\s+FROM.*WHERE\s*$/i,
85
+ message: 'DELETE without WHERE clause detected - will delete all rows',
86
+ },
87
+ { pattern: /--force|--skip-safe/i, message: 'Force flag used - bypassing safety checks' },
88
+ ];
89
+
90
+ for (const { pattern, message } of destructivePatterns) {
91
+ if (pattern.test(command) || pattern.test(result)) {
92
+ issues.push(message);
93
+ }
94
+ }
95
+
96
+ // Check for production environment safeguards
97
+ if (/production|prod/i.test(command) && !/--dry-run/i.test(command)) {
98
+ issues.push('Production migration without --dry-run flag - consider dry run first');
99
+ }
100
+
101
+ // Check for failed migrations in output
102
+ const failurePatterns = [
103
+ { pattern: /error.*migration/i, message: 'Migration error detected in output' },
104
+ { pattern: /rollback.*failed/i, message: 'Rollback failure detected' },
105
+ { pattern: /constraint.*violation/i, message: 'Database constraint violation' },
106
+ {
107
+ pattern: /duplicate.*key/i,
108
+ message: 'Duplicate key error - migration may have partially applied',
109
+ },
110
+ { pattern: /already exists/i, message: 'Object already exists - migration may need cleanup' },
111
+ ];
112
+
113
+ for (const { pattern, message } of failurePatterns) {
114
+ if (pattern.test(result)) {
115
+ issues.push(message);
116
+ }
117
+ }
118
+
119
+ // Check for missing rollback strategy
120
+ if (/migrate.*dev|migrate.*run/i.test(command)) {
121
+ // Prisma and similar ORMs
122
+ if (!/down|rollback|reset/i.test(result) && result.includes('migration')) {
123
+ // Just a warning, not blocking
124
+ console.log('Note: Ensure rollback migration exists for this change');
125
+ }
126
+ }
127
+
128
+ return issues;
129
+ }