agileflow 2.91.0 → 2.92.1
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 +10 -0
- package/README.md +6 -6
- package/lib/README.md +178 -0
- package/lib/codebase-indexer.js +32 -23
- package/lib/colors.js +190 -12
- package/lib/consent.js +232 -0
- package/lib/correlation.js +277 -0
- package/lib/error-codes.js +46 -0
- package/lib/errors.js +48 -6
- package/lib/file-cache.js +182 -0
- package/lib/format-error.js +156 -0
- package/lib/path-resolver.js +155 -7
- package/lib/paths.js +212 -20
- package/lib/placeholder-registry.js +205 -0
- package/lib/registry-di.js +358 -0
- package/lib/result-schema.js +363 -0
- package/lib/result.js +210 -0
- package/lib/session-registry.js +13 -0
- package/lib/session-state-machine.js +465 -0
- package/lib/validate-commands.js +308 -0
- package/lib/validate.js +116 -52
- package/package.json +1 -1
- package/scripts/af +34 -0
- package/scripts/agent-loop.js +63 -9
- package/scripts/agileflow-configure.js +2 -2
- package/scripts/agileflow-welcome.js +491 -23
- package/scripts/archive-completed-stories.sh +57 -11
- package/scripts/claude-tmux.sh +102 -0
- package/scripts/damage-control-bash.js +3 -70
- package/scripts/damage-control-edit.js +3 -20
- package/scripts/damage-control-write.js +3 -20
- package/scripts/dependency-check.js +310 -0
- package/scripts/get-env.js +11 -4
- package/scripts/lib/configure-detect.js +23 -1
- package/scripts/lib/configure-features.js +50 -2
- package/scripts/lib/context-formatter.js +771 -0
- package/scripts/lib/context-loader.js +699 -0
- package/scripts/lib/damage-control-utils.js +107 -0
- package/scripts/lib/json-utils.sh +162 -0
- package/scripts/lib/state-migrator.js +353 -0
- package/scripts/lib/story-state-machine.js +437 -0
- package/scripts/obtain-context.js +80 -1248
- package/scripts/pre-push-check.sh +46 -0
- package/scripts/precompact-context.sh +23 -10
- package/scripts/query-codebase.js +127 -14
- package/scripts/ralph-loop.js +5 -5
- package/scripts/session-manager.js +408 -55
- package/scripts/spawn-parallel.js +666 -0
- package/scripts/tui/blessed/data/watcher.js +20 -15
- package/scripts/tui/blessed/index.js +2 -2
- package/scripts/tui/blessed/panels/output.js +14 -8
- package/scripts/tui/blessed/panels/sessions.js +22 -15
- package/scripts/tui/blessed/panels/trace.js +14 -8
- package/scripts/tui/blessed/ui/help.js +3 -3
- package/scripts/tui/blessed/ui/screen.js +4 -4
- package/scripts/tui/blessed/ui/statusbar.js +5 -9
- package/scripts/tui/blessed/ui/tabbar.js +11 -11
- package/scripts/validators/component-validator.js +41 -14
- package/scripts/validators/json-schema-validator.js +11 -4
- package/scripts/validators/markdown-validator.js +1 -2
- package/scripts/validators/migration-validator.js +17 -5
- package/scripts/validators/security-validator.js +137 -33
- package/scripts/validators/story-format-validator.js +31 -10
- package/scripts/validators/test-result-validator.js +19 -4
- package/scripts/validators/workflow-validator.js +12 -5
- package/src/core/agents/codebase-query.md +24 -0
- package/src/core/commands/adr.md +114 -0
- package/src/core/commands/agent.md +120 -0
- package/src/core/commands/assign.md +145 -0
- package/src/core/commands/babysit.md +32 -5
- package/src/core/commands/changelog.md +118 -0
- package/src/core/commands/configure.md +42 -6
- package/src/core/commands/diagnose.md +114 -0
- package/src/core/commands/epic.md +113 -0
- package/src/core/commands/handoff.md +128 -0
- package/src/core/commands/help.md +75 -0
- package/src/core/commands/pr.md +96 -0
- package/src/core/commands/roadmap/analyze.md +400 -0
- package/src/core/commands/session/new.md +132 -6
- package/src/core/commands/session/spawn.md +197 -0
- package/src/core/commands/sprint.md +22 -0
- package/src/core/commands/status.md +74 -0
- package/src/core/commands/story.md +143 -4
- package/src/core/templates/agileflow-metadata.json +55 -2
- package/src/core/templates/plan-template.md +125 -0
- package/src/core/templates/story-lifecycle.md +213 -0
- package/src/core/templates/story-template.md +4 -0
- package/src/core/templates/tdd-test-template.js +241 -0
- package/tools/cli/commands/setup.js +95 -0
- package/tools/cli/installers/core/installer.js +94 -0
- package/tools/cli/installers/ide/_base-ide.js +20 -11
- package/tools/cli/installers/ide/codex.js +29 -47
- package/tools/cli/installers/ide/windsurf.js +1 -1
- package/tools/cli/lib/config-manager.js +17 -2
- package/tools/cli/lib/content-transformer.js +271 -0
- package/tools/cli/lib/error-handler.js +14 -22
- package/tools/cli/lib/ide-error-factory.js +421 -0
- package/tools/cli/lib/ide-health-monitor.js +364 -0
- package/tools/cli/lib/ide-registry.js +113 -2
- package/tools/cli/lib/ui.js +15 -25
|
@@ -4,6 +4,9 @@ const fs = require('fs');
|
|
|
4
4
|
const path = require('path');
|
|
5
5
|
const chokidar = require('chokidar');
|
|
6
6
|
|
|
7
|
+
// Import centralized path utilities
|
|
8
|
+
const { getStatusPath, getBusLogPath, getSessionStatePath } = require('../../../lib/paths');
|
|
9
|
+
|
|
7
10
|
/**
|
|
8
11
|
* DataWatcher - watches status.json and log files for changes
|
|
9
12
|
* Triggers refresh callback when data updates
|
|
@@ -15,10 +18,10 @@ class DataWatcher {
|
|
|
15
18
|
this.watcher = null;
|
|
16
19
|
this.cwd = process.cwd();
|
|
17
20
|
|
|
18
|
-
// Paths to watch
|
|
19
|
-
this.statusPath =
|
|
20
|
-
this.logPath =
|
|
21
|
-
this.sessionStatePath =
|
|
21
|
+
// Paths to watch (using centralized path utilities)
|
|
22
|
+
this.statusPath = getStatusPath(this.cwd);
|
|
23
|
+
this.logPath = getBusLogPath(this.cwd);
|
|
24
|
+
this.sessionStatePath = getSessionStatePath(this.cwd);
|
|
22
25
|
}
|
|
23
26
|
|
|
24
27
|
start() {
|
|
@@ -50,8 +53,8 @@ class DataWatcher {
|
|
|
50
53
|
usePolling: false,
|
|
51
54
|
awaitWriteFinish: {
|
|
52
55
|
stabilityThreshold: 100,
|
|
53
|
-
pollInterval: 50
|
|
54
|
-
}
|
|
56
|
+
pollInterval: 50,
|
|
57
|
+
},
|
|
55
58
|
});
|
|
56
59
|
|
|
57
60
|
this.watcher.on('change', filePath => {
|
|
@@ -85,20 +88,22 @@ class DataWatcher {
|
|
|
85
88
|
story: s.id || s.title || 'Unknown',
|
|
86
89
|
status: 'active',
|
|
87
90
|
duration: this.formatDuration(s.started_at),
|
|
88
|
-
progress: s.progress || '--'
|
|
91
|
+
progress: s.progress || '--',
|
|
89
92
|
}));
|
|
90
93
|
|
|
91
94
|
// If no active stories, show all stories summary
|
|
92
95
|
if (this.state.sessions.length === 0) {
|
|
93
96
|
const ready = stories.filter(s => s.status === 'ready').length;
|
|
94
97
|
const completed = stories.filter(s => s.status === 'completed').length;
|
|
95
|
-
this.state.sessions = [
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
98
|
+
this.state.sessions = [
|
|
99
|
+
{
|
|
100
|
+
id: '--',
|
|
101
|
+
story: `${ready} ready, ${completed} completed`,
|
|
102
|
+
status: 'idle',
|
|
103
|
+
duration: '--',
|
|
104
|
+
progress: '--',
|
|
105
|
+
},
|
|
106
|
+
];
|
|
102
107
|
}
|
|
103
108
|
}
|
|
104
109
|
} catch (err) {
|
|
@@ -138,7 +143,7 @@ class DataWatcher {
|
|
|
138
143
|
action: cmd.command || cmd,
|
|
139
144
|
status: 'running',
|
|
140
145
|
duration: '--',
|
|
141
|
-
details: cmd.args || ''
|
|
146
|
+
details: cmd.args || '',
|
|
142
147
|
}));
|
|
143
148
|
}
|
|
144
149
|
}
|
|
@@ -39,7 +39,7 @@ class AgileFlowTUI {
|
|
|
39
39
|
sessions: [],
|
|
40
40
|
logs: [],
|
|
41
41
|
traces: [],
|
|
42
|
-
showHelp: false
|
|
42
|
+
showHelp: false,
|
|
43
43
|
};
|
|
44
44
|
this.components = {};
|
|
45
45
|
this.watcher = null;
|
|
@@ -90,7 +90,7 @@ class AgileFlowTUI {
|
|
|
90
90
|
});
|
|
91
91
|
|
|
92
92
|
// Tab switching with number keys
|
|
93
|
-
this.screen.key(['1', '2', '3'],
|
|
93
|
+
this.screen.key(['1', '2', '3'], ch => {
|
|
94
94
|
if (this.components.help.isVisible()) return;
|
|
95
95
|
this.switchTab(parseInt(ch) - 1);
|
|
96
96
|
});
|
|
@@ -16,22 +16,22 @@ module.exports = function createOutputPanel(grid, state) {
|
|
|
16
16
|
tags: true,
|
|
17
17
|
border: {
|
|
18
18
|
type: 'line',
|
|
19
|
-
fg: 'green'
|
|
19
|
+
fg: 'green',
|
|
20
20
|
},
|
|
21
21
|
style: {
|
|
22
22
|
fg: 'white',
|
|
23
23
|
bg: 'black',
|
|
24
|
-
border: { fg: 'green' }
|
|
24
|
+
border: { fg: 'green' },
|
|
25
25
|
},
|
|
26
26
|
scrollable: true,
|
|
27
27
|
alwaysScroll: true,
|
|
28
28
|
scrollbar: {
|
|
29
29
|
ch: '│',
|
|
30
|
-
style: { fg: 'green' }
|
|
30
|
+
style: { fg: 'green' },
|
|
31
31
|
},
|
|
32
32
|
keys: true,
|
|
33
33
|
vi: true,
|
|
34
|
-
mouse: true
|
|
34
|
+
mouse: true,
|
|
35
35
|
});
|
|
36
36
|
|
|
37
37
|
let logs = [];
|
|
@@ -75,9 +75,15 @@ module.exports = function createOutputPanel(grid, state) {
|
|
|
75
75
|
|
|
76
76
|
return {
|
|
77
77
|
element: box,
|
|
78
|
-
show() {
|
|
79
|
-
|
|
80
|
-
|
|
78
|
+
show() {
|
|
79
|
+
box.show();
|
|
80
|
+
},
|
|
81
|
+
hide() {
|
|
82
|
+
box.hide();
|
|
83
|
+
},
|
|
84
|
+
focus() {
|
|
85
|
+
box.focus();
|
|
86
|
+
},
|
|
81
87
|
setData(data) {
|
|
82
88
|
logs = data || [];
|
|
83
89
|
render();
|
|
@@ -90,6 +96,6 @@ module.exports = function createOutputPanel(grid, state) {
|
|
|
90
96
|
clear() {
|
|
91
97
|
logs = [];
|
|
92
98
|
render();
|
|
93
|
-
}
|
|
99
|
+
},
|
|
94
100
|
};
|
|
95
101
|
};
|
|
@@ -17,21 +17,21 @@ module.exports = function createSessionsPanel(grid, state) {
|
|
|
17
17
|
tags: true,
|
|
18
18
|
border: {
|
|
19
19
|
type: 'line',
|
|
20
|
-
fg: 'cyan'
|
|
20
|
+
fg: 'cyan',
|
|
21
21
|
},
|
|
22
22
|
style: {
|
|
23
23
|
fg: 'white',
|
|
24
24
|
bg: 'black',
|
|
25
|
-
border: { fg: 'cyan' }
|
|
25
|
+
border: { fg: 'cyan' },
|
|
26
26
|
},
|
|
27
27
|
scrollable: true,
|
|
28
28
|
alwaysScroll: true,
|
|
29
29
|
scrollbar: {
|
|
30
30
|
ch: '│',
|
|
31
|
-
style: { fg: 'cyan' }
|
|
31
|
+
style: { fg: 'cyan' },
|
|
32
32
|
},
|
|
33
33
|
keys: true,
|
|
34
|
-
vi: true
|
|
34
|
+
vi: true,
|
|
35
35
|
});
|
|
36
36
|
|
|
37
37
|
// Header row
|
|
@@ -41,11 +41,12 @@ module.exports = function createSessionsPanel(grid, state) {
|
|
|
41
41
|
left: 0,
|
|
42
42
|
width: '100%-2',
|
|
43
43
|
height: 1,
|
|
44
|
-
content:
|
|
44
|
+
content:
|
|
45
|
+
'{bold}{cyan-fg} ID Story Status Time{/}{/}',
|
|
45
46
|
tags: true,
|
|
46
47
|
style: {
|
|
47
|
-
bg: '#222222'
|
|
48
|
-
}
|
|
48
|
+
bg: '#222222',
|
|
49
|
+
},
|
|
49
50
|
});
|
|
50
51
|
|
|
51
52
|
// Divider
|
|
@@ -57,8 +58,8 @@ module.exports = function createSessionsPanel(grid, state) {
|
|
|
57
58
|
height: 1,
|
|
58
59
|
content: ' ────────── ───────────────────────────────── ──────────── ──────',
|
|
59
60
|
style: {
|
|
60
|
-
fg: 'gray'
|
|
61
|
-
}
|
|
61
|
+
fg: 'gray',
|
|
62
|
+
},
|
|
62
63
|
});
|
|
63
64
|
|
|
64
65
|
// Content area
|
|
@@ -70,8 +71,8 @@ module.exports = function createSessionsPanel(grid, state) {
|
|
|
70
71
|
height: '100%-3',
|
|
71
72
|
tags: true,
|
|
72
73
|
style: {
|
|
73
|
-
fg: 'white'
|
|
74
|
-
}
|
|
74
|
+
fg: 'white',
|
|
75
|
+
},
|
|
75
76
|
});
|
|
76
77
|
|
|
77
78
|
let sessions = [];
|
|
@@ -117,9 +118,15 @@ module.exports = function createSessionsPanel(grid, state) {
|
|
|
117
118
|
|
|
118
119
|
return {
|
|
119
120
|
element: box,
|
|
120
|
-
show() {
|
|
121
|
-
|
|
122
|
-
|
|
121
|
+
show() {
|
|
122
|
+
box.show();
|
|
123
|
+
},
|
|
124
|
+
hide() {
|
|
125
|
+
box.hide();
|
|
126
|
+
},
|
|
127
|
+
focus() {
|
|
128
|
+
box.focus();
|
|
129
|
+
},
|
|
123
130
|
setData(data) {
|
|
124
131
|
sessions = data || [];
|
|
125
132
|
render();
|
|
@@ -138,6 +145,6 @@ module.exports = function createSessionsPanel(grid, state) {
|
|
|
138
145
|
selectedIndex--;
|
|
139
146
|
render();
|
|
140
147
|
}
|
|
141
|
-
}
|
|
148
|
+
},
|
|
142
149
|
};
|
|
143
150
|
};
|
|
@@ -16,21 +16,21 @@ module.exports = function createTracePanel(grid, state) {
|
|
|
16
16
|
tags: true,
|
|
17
17
|
border: {
|
|
18
18
|
type: 'line',
|
|
19
|
-
fg: 'yellow'
|
|
19
|
+
fg: 'yellow',
|
|
20
20
|
},
|
|
21
21
|
style: {
|
|
22
22
|
fg: 'white',
|
|
23
23
|
bg: 'black',
|
|
24
|
-
border: { fg: 'yellow' }
|
|
24
|
+
border: { fg: 'yellow' },
|
|
25
25
|
},
|
|
26
26
|
scrollable: true,
|
|
27
27
|
alwaysScroll: true,
|
|
28
28
|
scrollbar: {
|
|
29
29
|
ch: '│',
|
|
30
|
-
style: { fg: 'yellow' }
|
|
30
|
+
style: { fg: 'yellow' },
|
|
31
31
|
},
|
|
32
32
|
keys: true,
|
|
33
|
-
vi: true
|
|
33
|
+
vi: true,
|
|
34
34
|
});
|
|
35
35
|
|
|
36
36
|
let traces = [];
|
|
@@ -76,9 +76,15 @@ module.exports = function createTracePanel(grid, state) {
|
|
|
76
76
|
|
|
77
77
|
return {
|
|
78
78
|
element: box,
|
|
79
|
-
show() {
|
|
80
|
-
|
|
81
|
-
|
|
79
|
+
show() {
|
|
80
|
+
box.show();
|
|
81
|
+
},
|
|
82
|
+
hide() {
|
|
83
|
+
box.hide();
|
|
84
|
+
},
|
|
85
|
+
focus() {
|
|
86
|
+
box.focus();
|
|
87
|
+
},
|
|
82
88
|
setData(data) {
|
|
83
89
|
traces = data || [];
|
|
84
90
|
render();
|
|
@@ -86,6 +92,6 @@ module.exports = function createTracePanel(grid, state) {
|
|
|
86
92
|
addStep(action, status = 'running', details = '') {
|
|
87
93
|
traces.push({ action, status, details, duration: '--' });
|
|
88
94
|
render();
|
|
89
|
-
}
|
|
95
|
+
},
|
|
90
96
|
};
|
|
91
97
|
};
|
|
@@ -18,7 +18,7 @@ module.exports = function createHelpOverlay(screen, state) {
|
|
|
18
18
|
style: {
|
|
19
19
|
fg: 'white',
|
|
20
20
|
bg: 'black',
|
|
21
|
-
border: { fg: 'yellow' }
|
|
21
|
+
border: { fg: 'yellow' },
|
|
22
22
|
},
|
|
23
23
|
label: ' {yellow-fg}Help{/yellow-fg} ',
|
|
24
24
|
hidden: true,
|
|
@@ -44,7 +44,7 @@ module.exports = function createHelpOverlay(screen, state) {
|
|
|
44
44
|
{cyan-fg}q{/} Quit TUI
|
|
45
45
|
{cyan-fg}Ctrl+C{/} Force quit
|
|
46
46
|
|
|
47
|
-
{gray-fg}Press Escape or ? to close{/gray-fg}
|
|
47
|
+
{gray-fg}Press Escape or ? to close{/gray-fg}`,
|
|
48
48
|
});
|
|
49
49
|
|
|
50
50
|
// Close help on various keys
|
|
@@ -72,6 +72,6 @@ module.exports = function createHelpOverlay(screen, state) {
|
|
|
72
72
|
},
|
|
73
73
|
isVisible() {
|
|
74
74
|
return !help.hidden;
|
|
75
|
-
}
|
|
75
|
+
},
|
|
76
76
|
};
|
|
77
77
|
};
|
|
@@ -11,18 +11,18 @@ const blessed = require('blessed');
|
|
|
11
11
|
*/
|
|
12
12
|
module.exports = function createScreen() {
|
|
13
13
|
const screen = blessed.screen({
|
|
14
|
-
smartCSR: true,
|
|
15
|
-
fullUnicode: true,
|
|
14
|
+
smartCSR: true, // Key for flicker-free differential rendering
|
|
15
|
+
fullUnicode: true, // Support Unicode characters
|
|
16
16
|
title: 'AgileFlow TUI',
|
|
17
17
|
cursor: {
|
|
18
18
|
artificial: true,
|
|
19
19
|
blink: true,
|
|
20
|
-
shape: 'line'
|
|
20
|
+
shape: 'line',
|
|
21
21
|
},
|
|
22
22
|
debug: false,
|
|
23
23
|
warnings: false,
|
|
24
24
|
autoPadding: true,
|
|
25
|
-
dockBorders: true
|
|
25
|
+
dockBorders: true,
|
|
26
26
|
});
|
|
27
27
|
|
|
28
28
|
// Enable synchronized output mode for atomic updates
|
|
@@ -16,8 +16,8 @@ module.exports = function createStatusBar(screen, state) {
|
|
|
16
16
|
tags: true,
|
|
17
17
|
style: {
|
|
18
18
|
fg: 'white',
|
|
19
|
-
bg: 'blue'
|
|
20
|
-
}
|
|
19
|
+
bg: 'blue',
|
|
20
|
+
},
|
|
21
21
|
});
|
|
22
22
|
|
|
23
23
|
// Always-visible key hints (nano-style for user-friendliness)
|
|
@@ -27,7 +27,7 @@ module.exports = function createStatusBar(screen, state) {
|
|
|
27
27
|
'{bold}j/k{/bold}:Nav',
|
|
28
28
|
'{bold}r{/bold}:Refresh',
|
|
29
29
|
'{bold}?{/bold}:Help',
|
|
30
|
-
'{bold}q{/bold}:Quit'
|
|
30
|
+
'{bold}q{/bold}:Quit',
|
|
31
31
|
];
|
|
32
32
|
|
|
33
33
|
const hintText = ' ' + hints.join(' ');
|
|
@@ -37,15 +37,11 @@ module.exports = function createStatusBar(screen, state) {
|
|
|
37
37
|
element: statusBar,
|
|
38
38
|
setStatus(text) {
|
|
39
39
|
// Show custom status with key hints
|
|
40
|
-
const shortHints = [
|
|
41
|
-
'{bold}r{/bold}:Refresh',
|
|
42
|
-
'{bold}?{/bold}:Help',
|
|
43
|
-
'{bold}q{/bold}:Quit'
|
|
44
|
-
];
|
|
40
|
+
const shortHints = ['{bold}r{/bold}:Refresh', '{bold}?{/bold}:Help', '{bold}q{/bold}:Quit'];
|
|
45
41
|
statusBar.setContent(` ${text} | ${shortHints.join(' ')}`);
|
|
46
42
|
},
|
|
47
43
|
resetHints() {
|
|
48
44
|
statusBar.setContent(hintText);
|
|
49
|
-
}
|
|
45
|
+
},
|
|
50
46
|
};
|
|
51
47
|
};
|
|
@@ -14,8 +14,8 @@ module.exports = function createTabBar(screen, state) {
|
|
|
14
14
|
width: '100%',
|
|
15
15
|
height: 3,
|
|
16
16
|
style: {
|
|
17
|
-
bg: 'black'
|
|
18
|
-
}
|
|
17
|
+
bg: 'black',
|
|
18
|
+
},
|
|
19
19
|
});
|
|
20
20
|
|
|
21
21
|
// Logo/title
|
|
@@ -28,8 +28,8 @@ module.exports = function createTabBar(screen, state) {
|
|
|
28
28
|
content: '{bold}{#e8683a-fg}▄▀▄ AgileFlow{/}',
|
|
29
29
|
tags: true,
|
|
30
30
|
style: {
|
|
31
|
-
bg: 'black'
|
|
32
|
-
}
|
|
31
|
+
bg: 'black',
|
|
32
|
+
},
|
|
33
33
|
});
|
|
34
34
|
|
|
35
35
|
// Tab container
|
|
@@ -40,8 +40,8 @@ module.exports = function createTabBar(screen, state) {
|
|
|
40
40
|
width: '100%-20',
|
|
41
41
|
height: 3,
|
|
42
42
|
style: {
|
|
43
|
-
bg: 'black'
|
|
44
|
-
}
|
|
43
|
+
bg: 'black',
|
|
44
|
+
},
|
|
45
45
|
});
|
|
46
46
|
|
|
47
47
|
// Create styled tabs
|
|
@@ -56,8 +56,8 @@ module.exports = function createTabBar(screen, state) {
|
|
|
56
56
|
tags: true,
|
|
57
57
|
style: {
|
|
58
58
|
fg: 'white',
|
|
59
|
-
bg: 'black'
|
|
60
|
-
}
|
|
59
|
+
bg: 'black',
|
|
60
|
+
},
|
|
61
61
|
});
|
|
62
62
|
return tab;
|
|
63
63
|
});
|
|
@@ -72,8 +72,8 @@ module.exports = function createTabBar(screen, state) {
|
|
|
72
72
|
content: '{gray-fg}v2.90.7{/}',
|
|
73
73
|
tags: true,
|
|
74
74
|
style: {
|
|
75
|
-
bg: 'black'
|
|
76
|
-
}
|
|
75
|
+
bg: 'black',
|
|
76
|
+
},
|
|
77
77
|
});
|
|
78
78
|
|
|
79
79
|
return {
|
|
@@ -94,6 +94,6 @@ module.exports = function createTabBar(screen, state) {
|
|
|
94
94
|
tab.setContent(`[${i + 1}] ${state.tabs[i]}`);
|
|
95
95
|
}
|
|
96
96
|
});
|
|
97
|
-
}
|
|
97
|
+
},
|
|
98
98
|
};
|
|
99
99
|
};
|
|
@@ -22,7 +22,7 @@ const fs = require('fs');
|
|
|
22
22
|
const path = require('path');
|
|
23
23
|
|
|
24
24
|
let input = '';
|
|
25
|
-
process.stdin.on('data', chunk => input += chunk);
|
|
25
|
+
process.stdin.on('data', chunk => (input += chunk));
|
|
26
26
|
process.stdin.on('end', () => {
|
|
27
27
|
try {
|
|
28
28
|
const context = JSON.parse(input);
|
|
@@ -62,9 +62,11 @@ function isComponentFile(filePath) {
|
|
|
62
62
|
// Also check for .js/.ts files in component directories
|
|
63
63
|
if (['.js', '.ts'].includes(ext)) {
|
|
64
64
|
const normalizedPath = filePath.toLowerCase();
|
|
65
|
-
return
|
|
66
|
-
|
|
67
|
-
|
|
65
|
+
return (
|
|
66
|
+
normalizedPath.includes('/components/') ||
|
|
67
|
+
normalizedPath.includes('/pages/') ||
|
|
68
|
+
normalizedPath.includes('/views/')
|
|
69
|
+
);
|
|
68
70
|
}
|
|
69
71
|
|
|
70
72
|
return componentExtensions.includes(ext);
|
|
@@ -101,7 +103,6 @@ function validateComponent(filePath) {
|
|
|
101
103
|
|
|
102
104
|
// General accessibility checks
|
|
103
105
|
issues.push(...validateAccessibility(content));
|
|
104
|
-
|
|
105
106
|
} catch (e) {
|
|
106
107
|
issues.push(`Read error: ${e.message}`);
|
|
107
108
|
}
|
|
@@ -113,12 +114,22 @@ function validateReactComponent(content, fileName) {
|
|
|
113
114
|
const issues = [];
|
|
114
115
|
|
|
115
116
|
// Check for component export
|
|
116
|
-
if (
|
|
117
|
-
|
|
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
|
+
);
|
|
118
125
|
}
|
|
119
126
|
|
|
120
127
|
// Check for React import in JSX files
|
|
121
|
-
if (
|
|
128
|
+
if (
|
|
129
|
+
content.includes('React.') &&
|
|
130
|
+
!content.includes("from 'react'") &&
|
|
131
|
+
!content.includes('from "react"')
|
|
132
|
+
) {
|
|
122
133
|
issues.push('Using React. prefix but React is not imported');
|
|
123
134
|
}
|
|
124
135
|
|
|
@@ -132,12 +143,18 @@ function validateReactComponent(content, fileName) {
|
|
|
132
143
|
// Check for inline styles (prefer CSS modules or styled-components)
|
|
133
144
|
const inlineStyleCount = (content.match(/style=\{\{/g) || []).length;
|
|
134
145
|
if (inlineStyleCount > 5) {
|
|
135
|
-
issues.push(
|
|
146
|
+
issues.push(
|
|
147
|
+
`Too many inline styles (${inlineStyleCount}) - consider using CSS modules or styled-components`
|
|
148
|
+
);
|
|
136
149
|
}
|
|
137
150
|
|
|
138
151
|
// Check for console.log in production code
|
|
139
|
-
if (
|
|
140
|
-
|
|
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");
|
|
141
158
|
}
|
|
142
159
|
|
|
143
160
|
// Check for missing key prop in map
|
|
@@ -163,7 +180,11 @@ function validateVueComponent(content) {
|
|
|
163
180
|
}
|
|
164
181
|
|
|
165
182
|
// Check for scoped styles (recommended)
|
|
166
|
-
if (
|
|
183
|
+
if (
|
|
184
|
+
content.includes('<style>') &&
|
|
185
|
+
!content.includes('<style scoped') &&
|
|
186
|
+
!content.includes('scoped>')
|
|
187
|
+
) {
|
|
167
188
|
console.log('Note: Consider using scoped styles to prevent CSS leaks');
|
|
168
189
|
}
|
|
169
190
|
|
|
@@ -200,11 +221,17 @@ function validateAccessibility(content) {
|
|
|
200
221
|
// Check for click handlers on non-interactive elements
|
|
201
222
|
const clickOnDiv = content.match(/onClick[^>]*>[^<]*<\/div>/gi) || [];
|
|
202
223
|
if (clickOnDiv.length > 0) {
|
|
203
|
-
issues.push(
|
|
224
|
+
issues.push(
|
|
225
|
+
'onClick on <div> detected - use <button> for interactive elements (accessibility)'
|
|
226
|
+
);
|
|
204
227
|
}
|
|
205
228
|
|
|
206
229
|
// Check for form inputs without labels
|
|
207
|
-
if (
|
|
230
|
+
if (
|
|
231
|
+
content.includes('<input') &&
|
|
232
|
+
!content.includes('<label') &&
|
|
233
|
+
!content.includes('aria-label')
|
|
234
|
+
) {
|
|
208
235
|
console.log('Note: <input> elements should have associated <label> or aria-label');
|
|
209
236
|
}
|
|
210
237
|
|
|
@@ -21,8 +21,16 @@
|
|
|
21
21
|
const fs = require('fs');
|
|
22
22
|
const path = require('path');
|
|
23
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
|
+
|
|
24
32
|
let input = '';
|
|
25
|
-
process.stdin.on('data', chunk => input += chunk);
|
|
33
|
+
process.stdin.on('data', chunk => (input += chunk));
|
|
26
34
|
process.stdin.on('end', () => {
|
|
27
35
|
try {
|
|
28
36
|
const context = JSON.parse(input);
|
|
@@ -85,7 +93,6 @@ function validateJson(filePath) {
|
|
|
85
93
|
} else if (fileName === 'tsconfig.json') {
|
|
86
94
|
issues.push(...validateTsConfig(data));
|
|
87
95
|
}
|
|
88
|
-
|
|
89
96
|
} catch (e) {
|
|
90
97
|
if (e instanceof SyntaxError) {
|
|
91
98
|
issues.push(`Invalid JSON syntax: ${e.message}`);
|
|
@@ -132,7 +139,7 @@ function validateStatusJson(data) {
|
|
|
132
139
|
if (!story.title && !story.name) {
|
|
133
140
|
issues.push(`Story ${story.id || index} missing 'title' or 'name' field`);
|
|
134
141
|
}
|
|
135
|
-
if (story.status && !
|
|
142
|
+
if (story.status && !ALL_ACCEPTED_STATUSES.includes(story.status)) {
|
|
136
143
|
issues.push(`Story ${story.id || index} has invalid status: ${story.status}`);
|
|
137
144
|
}
|
|
138
145
|
});
|
|
@@ -142,7 +149,7 @@ function validateStatusJson(data) {
|
|
|
142
149
|
if (!story.title && !story.name) {
|
|
143
150
|
issues.push(`Story ${storyId} missing 'title' or 'name' field`);
|
|
144
151
|
}
|
|
145
|
-
if (story.status && !
|
|
152
|
+
if (story.status && !ALL_ACCEPTED_STATUSES.includes(story.status)) {
|
|
146
153
|
issues.push(`Story ${storyId} has invalid status: ${story.status}`);
|
|
147
154
|
}
|
|
148
155
|
});
|
|
@@ -22,7 +22,7 @@ const fs = require('fs');
|
|
|
22
22
|
const path = require('path');
|
|
23
23
|
|
|
24
24
|
let input = '';
|
|
25
|
-
process.stdin.on('data', chunk => input += chunk);
|
|
25
|
+
process.stdin.on('data', chunk => (input += chunk));
|
|
26
26
|
process.stdin.on('end', () => {
|
|
27
27
|
try {
|
|
28
28
|
const context = JSON.parse(input);
|
|
@@ -108,7 +108,6 @@ function validateMarkdown(filePath) {
|
|
|
108
108
|
} else if (filePath.includes('/10-research/')) {
|
|
109
109
|
issues.push(...validateResearchNote(content));
|
|
110
110
|
}
|
|
111
|
-
|
|
112
111
|
} catch (e) {
|
|
113
112
|
issues.push(`Read error: ${e.message}`);
|
|
114
113
|
}
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
*/
|
|
20
20
|
|
|
21
21
|
let input = '';
|
|
22
|
-
process.stdin.on('data', chunk => input += chunk);
|
|
22
|
+
process.stdin.on('data', chunk => (input += chunk));
|
|
23
23
|
process.stdin.on('end', () => {
|
|
24
24
|
try {
|
|
25
25
|
const context = JSON.parse(input);
|
|
@@ -71,10 +71,19 @@ function validateMigration(command, result) {
|
|
|
71
71
|
|
|
72
72
|
// Check for destructive operations without safeguards
|
|
73
73
|
const destructivePatterns = [
|
|
74
|
-
{
|
|
75
|
-
|
|
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
|
+
},
|
|
76
82
|
{ pattern: /TRUNCATE/i, message: 'TRUNCATE detected - this removes all data permanently' },
|
|
77
|
-
{
|
|
83
|
+
{
|
|
84
|
+
pattern: /DELETE\s+FROM.*WHERE\s*$/i,
|
|
85
|
+
message: 'DELETE without WHERE clause detected - will delete all rows',
|
|
86
|
+
},
|
|
78
87
|
{ pattern: /--force|--skip-safe/i, message: 'Force flag used - bypassing safety checks' },
|
|
79
88
|
];
|
|
80
89
|
|
|
@@ -94,7 +103,10 @@ function validateMigration(command, result) {
|
|
|
94
103
|
{ pattern: /error.*migration/i, message: 'Migration error detected in output' },
|
|
95
104
|
{ pattern: /rollback.*failed/i, message: 'Rollback failure detected' },
|
|
96
105
|
{ pattern: /constraint.*violation/i, message: 'Database constraint violation' },
|
|
97
|
-
{
|
|
106
|
+
{
|
|
107
|
+
pattern: /duplicate.*key/i,
|
|
108
|
+
message: 'Duplicate key error - migration may have partially applied',
|
|
109
|
+
},
|
|
98
110
|
{ pattern: /already exists/i, message: 'Object already exists - migration may need cleanup' },
|
|
99
111
|
];
|
|
100
112
|
|