@pennyfarthing/cyclist 10.0.3 → 10.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.
Files changed (91) hide show
  1. package/dist/api/agent-load.d.ts +3 -0
  2. package/dist/api/agent-load.d.ts.map +1 -0
  3. package/dist/api/agent-load.js +124 -0
  4. package/dist/api/agent-load.js.map +1 -0
  5. package/dist/api/code-markers.d.ts +9 -0
  6. package/dist/api/code-markers.d.ts.map +1 -0
  7. package/dist/api/code-markers.js +62 -0
  8. package/dist/api/code-markers.js.map +1 -0
  9. package/dist/api/complexity.d.ts +3 -0
  10. package/dist/api/complexity.d.ts.map +1 -0
  11. package/dist/api/complexity.js +47 -0
  12. package/dist/api/complexity.js.map +1 -0
  13. package/dist/api/dead-code.d.ts +3 -0
  14. package/dist/api/dead-code.d.ts.map +1 -0
  15. package/dist/api/dead-code.js +70 -0
  16. package/dist/api/dead-code.js.map +1 -0
  17. package/dist/api/dependencies.d.ts +3 -0
  18. package/dist/api/dependencies.d.ts.map +1 -0
  19. package/dist/api/dependencies.js +43 -0
  20. package/dist/api/dependencies.js.map +1 -0
  21. package/dist/api/git.d.ts +3 -2
  22. package/dist/api/git.d.ts.map +1 -1
  23. package/dist/api/git.js +11 -6
  24. package/dist/api/git.js.map +1 -1
  25. package/dist/api/health-score.d.ts +3 -0
  26. package/dist/api/health-score.d.ts.map +1 -0
  27. package/dist/api/health-score.js +38 -0
  28. package/dist/api/health-score.js.map +1 -0
  29. package/dist/api/hotspots.d.ts.map +1 -1
  30. package/dist/api/hotspots.js +9 -1
  31. package/dist/api/hotspots.js.map +1 -1
  32. package/dist/api/index.d.ts +6 -0
  33. package/dist/api/index.d.ts.map +1 -1
  34. package/dist/api/index.js +11 -0
  35. package/dist/api/index.js.map +1 -1
  36. package/dist/git-diff.d.ts.map +1 -1
  37. package/dist/git-diff.js +6 -5
  38. package/dist/git-diff.js.map +1 -1
  39. package/dist/preload.js +11 -0
  40. package/dist/preload.js.map +1 -1
  41. package/dist/prime.d.ts +3 -2
  42. package/dist/prime.d.ts.map +1 -1
  43. package/dist/prime.js +25 -8
  44. package/dist/prime.js.map +1 -1
  45. package/dist/public/css/react.css +1 -1
  46. package/dist/public/js/react/react.js +50 -39
  47. package/dist/server.d.ts.map +1 -1
  48. package/dist/server.js +12 -1
  49. package/dist/server.js.map +1 -1
  50. package/dist/sprint-data.d.ts +6 -0
  51. package/dist/sprint-data.d.ts.map +1 -1
  52. package/dist/sprint-data.js +80 -66
  53. package/dist/sprint-data.js.map +1 -1
  54. package/dist/websocket.d.ts.map +1 -1
  55. package/dist/websocket.js +6 -5
  56. package/dist/websocket.js.map +1 -1
  57. package/package.json +1 -1
  58. package/src/public/App.tsx +0 -2
  59. package/src/public/components/AgentLoadDialog.tsx +202 -0
  60. package/src/public/components/ControlBar.tsx +4 -3
  61. package/src/public/components/DeadCodeDialog.tsx +169 -0
  62. package/src/public/components/DockviewWorkspace.tsx +0 -3
  63. package/src/public/components/FullFileTree.tsx +18 -4
  64. package/src/public/components/HealthGauge.tsx +144 -0
  65. package/src/public/components/MessageView.tsx +23 -6
  66. package/src/public/components/ToolCallBlock.tsx +21 -6
  67. package/src/public/components/dialogs/CodeMarkersDialog.tsx +169 -0
  68. package/src/public/components/dialogs/ComplexityDialog.tsx +163 -0
  69. package/src/public/components/dialogs/DependenciesDialog.tsx +120 -0
  70. package/src/public/components/dialogs/HotspotsDialog.tsx +451 -0
  71. package/src/public/components/dialogs/ToolDialog.tsx +43 -0
  72. package/src/public/components/panels/AcceptanceCriteriaPanel.tsx +15 -30
  73. package/src/public/components/panels/DebugPanel.tsx +83 -0
  74. package/src/public/components/panels/GitPanel.tsx +12 -18
  75. package/src/public/components/panels/SprintPanel.tsx +84 -15
  76. package/src/public/components/panels/index.ts +0 -1
  77. package/src/public/components/ui/dialog.tsx +3 -3
  78. package/src/public/css/theme-system.css +5 -11
  79. package/src/public/hooks/index.ts +4 -0
  80. package/src/public/hooks/useAgentLoad.ts +105 -0
  81. package/src/public/hooks/useCodeMarkers.ts +101 -0
  82. package/src/public/hooks/useColorScheme.ts +25 -10
  83. package/src/public/hooks/useComplexity.ts +80 -0
  84. package/src/public/hooks/useDeadCode.ts +99 -0
  85. package/src/public/hooks/useDependencies.ts +82 -0
  86. package/src/public/hooks/useHealthScore.ts +77 -0
  87. package/src/public/hooks/useHotspots.ts +11 -1
  88. package/src/public/hooks/useSprint.ts +6 -0
  89. package/src/public/styles/tailwind.css +90 -78
  90. package/src/public/utils/messageFilters.ts +77 -6
  91. package/src/public/utils/slash-commands.ts +2 -18
@@ -0,0 +1,163 @@
1
+ import React, { useState, useMemo, useCallback, useEffect } from 'react';
2
+ import { Badge } from '@/components/ui/badge';
3
+ import { Button } from '@/components/ui/button';
4
+ import { Skeleton } from '@/components/ui/skeleton';
5
+ import { ToolDialog } from './ToolDialog';
6
+ import { useComplexity, FileComplexity } from '../../hooks/useComplexity';
7
+
8
+ export interface ComplexityDialogProps {
9
+ open: boolean;
10
+ onOpenChange: (open: boolean) => void;
11
+ }
12
+
13
+ type SortField = 'avg_cyclomatic_complexity' | 'max_nesting_depth' | 'longest_function' | 'total_lines' | 'function_count' | 'path';
14
+ type SortDirection = 'asc' | 'desc';
15
+
16
+ function SortableHeader({
17
+ label,
18
+ field,
19
+ currentSort,
20
+ currentDirection,
21
+ onSort,
22
+ align = 'right',
23
+ }: {
24
+ label: string;
25
+ field: SortField;
26
+ currentSort: SortField;
27
+ currentDirection: SortDirection;
28
+ onSort: (field: SortField) => void;
29
+ align?: 'left' | 'right';
30
+ }) {
31
+ const isActive = currentSort === field;
32
+ const arrow = isActive ? (currentDirection === 'desc' ? ' v' : ' ^') : '';
33
+
34
+ return (
35
+ <th
36
+ className={`text-${align} ${isActive ? 'active' : ''}`}
37
+ onClick={() => onSort(field)}
38
+ role="columnheader"
39
+ aria-sort={isActive ? (currentDirection === 'desc' ? 'descending' : 'ascending') : 'none'}
40
+ style={{ cursor: 'pointer', userSelect: 'none', padding: '4px 8px' }}
41
+ >
42
+ {label}{arrow}
43
+ </th>
44
+ );
45
+ }
46
+
47
+ export function ComplexityDialog({ open, onOpenChange }: ComplexityDialogProps): React.ReactElement {
48
+ const [sortField, setSortField] = useState<SortField>('avg_cyclomatic_complexity');
49
+ const [sortDirection, setSortDirection] = useState<SortDirection>('desc');
50
+
51
+ const { data, isLoading, error, refresh } = useComplexity({});
52
+
53
+ useEffect(() => {
54
+ if (open) refresh();
55
+ }, [open, refresh]);
56
+
57
+ const handleSort = useCallback((field: SortField) => {
58
+ setSortField((prev) => {
59
+ if (prev === field) {
60
+ setSortDirection((d) => (d === 'desc' ? 'asc' : 'desc'));
61
+ return prev;
62
+ }
63
+ setSortDirection('desc');
64
+ return field;
65
+ });
66
+ }, []);
67
+
68
+ const sorted = useMemo(() => {
69
+ if (!data?.files) return [];
70
+ const items = [...data.files];
71
+ items.sort((a, b) => {
72
+ const aVal = a[sortField as keyof FileComplexity];
73
+ const bVal = b[sortField as keyof FileComplexity];
74
+ if (typeof aVal === 'number' && typeof bVal === 'number') {
75
+ return sortDirection === 'desc' ? bVal - aVal : aVal - bVal;
76
+ }
77
+ return sortDirection === 'desc'
78
+ ? String(bVal).localeCompare(String(aVal))
79
+ : String(aVal).localeCompare(String(bVal));
80
+ });
81
+ return items;
82
+ }, [data, sortField, sortDirection]);
83
+
84
+ const renderContent = () => {
85
+ if (isLoading) {
86
+ return (
87
+ <div className="complexity-panel loading" data-testid="complexity-panel">
88
+ <div className="space-y-3 p-2">
89
+ <Skeleton className="h-4 w-40" />
90
+ <Skeleton className="h-3 w-full" />
91
+ <Skeleton className="h-3 w-full" />
92
+ <Skeleton className="h-3 w-3/4" />
93
+ </div>
94
+ </div>
95
+ );
96
+ }
97
+
98
+ if (error) {
99
+ return (
100
+ <div className="complexity-panel error" data-testid="complexity-panel">
101
+ <div className="error-message">Error: {error.message}</div>
102
+ <Button variant="outline" size="sm" onClick={refresh}>Retry</Button>
103
+ </div>
104
+ );
105
+ }
106
+
107
+ if (!data) {
108
+ return (
109
+ <div className="complexity-panel" data-testid="complexity-panel">
110
+ <p>Click <strong>Analyze</strong> to run complexity analysis</p>
111
+ </div>
112
+ );
113
+ }
114
+
115
+ return (
116
+ <div className="complexity-panel" data-testid="complexity-panel">
117
+ <div className="complexity-summary" style={{ marginBottom: '8px' }}>
118
+ <span>{data.file_count} files analyzed</span>
119
+ </div>
120
+
121
+ <table className="complexity-table" role="table" style={{ width: '100%', borderCollapse: 'collapse' }}>
122
+ <thead>
123
+ <tr>
124
+ <SortableHeader label="Complexity" field="avg_cyclomatic_complexity" currentSort={sortField} currentDirection={sortDirection} onSort={handleSort} />
125
+ <SortableHeader label="Nesting" field="max_nesting_depth" currentSort={sortField} currentDirection={sortDirection} onSort={handleSort} />
126
+ <SortableHeader label="Longest Fn" field="longest_function" currentSort={sortField} currentDirection={sortDirection} onSort={handleSort} />
127
+ <SortableHeader label="Lines" field="total_lines" currentSort={sortField} currentDirection={sortDirection} onSort={handleSort} />
128
+ <SortableHeader label="Functions" field="function_count" currentSort={sortField} currentDirection={sortDirection} onSort={handleSort} />
129
+ <SortableHeader label="File" field="path" currentSort={sortField} currentDirection={sortDirection} onSort={handleSort} align="left" />
130
+ </tr>
131
+ </thead>
132
+ <tbody>
133
+ {sorted.map((f) => (
134
+ <tr key={f.path}>
135
+ <td className="text-right" style={{ padding: '4px 8px' }}>
136
+ <Badge variant={f.avg_cyclomatic_complexity >= 7 ? 'destructive' : f.avg_cyclomatic_complexity >= 4 ? 'outline' : 'secondary'}>
137
+ {Number(f.avg_cyclomatic_complexity).toFixed(1)}
138
+ </Badge>
139
+ </td>
140
+ <td className="text-right" style={{ padding: '4px 8px' }}>{f.max_nesting_depth}</td>
141
+ <td className="text-right" style={{ padding: '4px 8px' }}>{f.longest_function}</td>
142
+ <td className="text-right" style={{ padding: '4px 8px' }}>{f.total_lines}</td>
143
+ <td className="text-right" style={{ padding: '4px 8px' }}>{f.function_count}</td>
144
+ <td className="text-left" style={{ padding: '4px 8px' }}>{f.path}</td>
145
+ </tr>
146
+ ))}
147
+ </tbody>
148
+ </table>
149
+ </div>
150
+ );
151
+ };
152
+
153
+ return (
154
+ <ToolDialog
155
+ open={open}
156
+ onOpenChange={onOpenChange}
157
+ title="Complexity"
158
+ description="Cyclomatic complexity analysis"
159
+ >
160
+ {renderContent()}
161
+ </ToolDialog>
162
+ );
163
+ }
@@ -0,0 +1,120 @@
1
+ import React, { useEffect } from 'react';
2
+ import { Badge } from '@/components/ui/badge';
3
+ import { Button } from '@/components/ui/button';
4
+ import { Skeleton } from '@/components/ui/skeleton';
5
+ import { ToolDialog } from './ToolDialog';
6
+ import { useDependencies } from '../../hooks/useDependencies';
7
+
8
+ export interface DependenciesDialogProps {
9
+ open: boolean;
10
+ onOpenChange: (open: boolean) => void;
11
+ }
12
+
13
+ function severityVariant(severity: string): 'destructive' | 'outline' | 'secondary' {
14
+ if (severity === 'high' || severity === 'critical') return 'destructive';
15
+ if (severity === 'moderate') return 'outline';
16
+ return 'secondary';
17
+ }
18
+
19
+ export function DependenciesDialog({ open, onOpenChange }: DependenciesDialogProps): React.ReactElement {
20
+ const { data, isLoading, error, refresh } = useDependencies({});
21
+
22
+ useEffect(() => {
23
+ if (open) refresh();
24
+ }, [open, refresh]);
25
+
26
+ const renderContent = () => {
27
+ if (isLoading) {
28
+ return (
29
+ <div className="dependencies-panel loading" data-testid="dependencies-panel">
30
+ <div className="space-y-3 p-2">
31
+ <Skeleton className="h-4 w-40" />
32
+ <Skeleton className="h-3 w-full" />
33
+ <Skeleton className="h-3 w-full" />
34
+ <Skeleton className="h-3 w-3/4" />
35
+ </div>
36
+ </div>
37
+ );
38
+ }
39
+
40
+ if (error) {
41
+ return (
42
+ <div className="dependencies-panel error" data-testid="dependencies-panel">
43
+ <div className="error-message">Error: {error.message}</div>
44
+ <Button variant="outline" size="sm" onClick={refresh}>Retry</Button>
45
+ </div>
46
+ );
47
+ }
48
+
49
+ if (!data) {
50
+ return (
51
+ <div className="dependencies-panel" data-testid="dependencies-panel">
52
+ <p>Click <strong>Analyze</strong> to check dependencies</p>
53
+ </div>
54
+ );
55
+ }
56
+
57
+ return (
58
+ <div className="dependencies-panel" data-testid="dependencies-panel">
59
+ {data.outdated.length > 0 && (
60
+ <div className="outdated-section" style={{ marginBottom: '16px' }}>
61
+ <h4>Outdated Packages ({data.outdated.length})</h4>
62
+ <table role="table" style={{ width: '100%', borderCollapse: 'collapse' }}>
63
+ <thead>
64
+ <tr>
65
+ <th className="text-left" style={{ padding: '4px 8px' }}>Package</th>
66
+ <th className="text-left" style={{ padding: '4px 8px' }}>Current</th>
67
+ <th className="text-left" style={{ padding: '4px 8px' }}>Wanted</th>
68
+ <th className="text-left" style={{ padding: '4px 8px' }}>Latest</th>
69
+ <th className="text-left" style={{ padding: '4px 8px' }}>Type</th>
70
+ </tr>
71
+ </thead>
72
+ <tbody>
73
+ {data.outdated.map((pkg) => (
74
+ <tr key={pkg.name}>
75
+ <td style={{ padding: '4px 8px' }}>{pkg.name}</td>
76
+ <td style={{ padding: '4px 8px' }}>{pkg.current}</td>
77
+ <td style={{ padding: '4px 8px' }}>{pkg.wanted}</td>
78
+ <td style={{ padding: '4px 8px' }}>{pkg.latest}</td>
79
+ <td style={{ padding: '4px 8px' }}>{pkg.type}</td>
80
+ </tr>
81
+ ))}
82
+ </tbody>
83
+ </table>
84
+ </div>
85
+ )}
86
+
87
+ {data.advisories.length > 0 && (
88
+ <div className="security-section">
89
+ <h4>Security Advisories</h4>
90
+ <div style={{ display: 'flex', gap: '8px', flexWrap: 'wrap' }}>
91
+ {data.advisories.map((adv) => (
92
+ <div key={adv.severity} style={{ display: 'flex', alignItems: 'center', gap: '4px' }}>
93
+ <Badge variant={severityVariant(adv.severity)}>
94
+ {adv.severity}
95
+ </Badge>
96
+ <span>{adv.count}</span>
97
+ </div>
98
+ ))}
99
+ </div>
100
+ </div>
101
+ )}
102
+
103
+ {data.outdated.length === 0 && data.advisories.length === 0 && (
104
+ <p>All dependencies are up to date with no known vulnerabilities.</p>
105
+ )}
106
+ </div>
107
+ );
108
+ };
109
+
110
+ return (
111
+ <ToolDialog
112
+ open={open}
113
+ onOpenChange={onOpenChange}
114
+ title="Dependencies"
115
+ description="Package staleness and security analysis"
116
+ >
117
+ {renderContent()}
118
+ </ToolDialog>
119
+ );
120
+ }