tlc-claude-code 1.2.27 → 1.2.28

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 (172) hide show
  1. package/dashboard/dist/components/ActivityFeed.d.ts +17 -0
  2. package/dashboard/dist/components/ActivityFeed.js +42 -0
  3. package/dashboard/dist/components/ActivityFeed.test.d.ts +1 -0
  4. package/dashboard/dist/components/ActivityFeed.test.js +162 -0
  5. package/dashboard/dist/components/BranchSelector.d.ts +16 -0
  6. package/dashboard/dist/components/BranchSelector.js +49 -0
  7. package/dashboard/dist/components/BranchSelector.test.d.ts +1 -0
  8. package/dashboard/dist/components/BranchSelector.test.js +166 -0
  9. package/dashboard/dist/components/CommandPalette.d.ts +17 -0
  10. package/dashboard/dist/components/CommandPalette.js +118 -0
  11. package/dashboard/dist/components/CommandPalette.test.d.ts +1 -0
  12. package/dashboard/dist/components/CommandPalette.test.js +181 -0
  13. package/dashboard/dist/components/ConnectionStatus.d.ts +16 -0
  14. package/dashboard/dist/components/ConnectionStatus.js +27 -0
  15. package/dashboard/dist/components/ConnectionStatus.test.d.ts +1 -0
  16. package/dashboard/dist/components/ConnectionStatus.test.js +121 -0
  17. package/dashboard/dist/components/DeviceFrame.d.ts +19 -0
  18. package/dashboard/dist/components/DeviceFrame.js +52 -0
  19. package/dashboard/dist/components/DeviceFrame.test.d.ts +1 -0
  20. package/dashboard/dist/components/DeviceFrame.test.js +118 -0
  21. package/dashboard/dist/components/EnvironmentBadge.d.ts +11 -0
  22. package/dashboard/dist/components/EnvironmentBadge.js +16 -0
  23. package/dashboard/dist/components/EnvironmentBadge.test.d.ts +1 -0
  24. package/dashboard/dist/components/EnvironmentBadge.test.js +102 -0
  25. package/dashboard/dist/components/FocusIndicator.d.ts +19 -0
  26. package/dashboard/dist/components/FocusIndicator.js +47 -0
  27. package/dashboard/dist/components/FocusIndicator.test.d.ts +1 -0
  28. package/dashboard/dist/components/FocusIndicator.test.js +117 -0
  29. package/dashboard/dist/components/KeyboardHelp.d.ts +15 -0
  30. package/dashboard/dist/components/KeyboardHelp.js +61 -0
  31. package/dashboard/dist/components/KeyboardHelp.test.d.ts +1 -0
  32. package/dashboard/dist/components/KeyboardHelp.test.js +131 -0
  33. package/dashboard/dist/components/LogSearch.d.ts +13 -0
  34. package/dashboard/dist/components/LogSearch.js +43 -0
  35. package/dashboard/dist/components/LogSearch.test.d.ts +1 -0
  36. package/dashboard/dist/components/LogSearch.test.js +100 -0
  37. package/dashboard/dist/components/LogStream.d.ts +21 -0
  38. package/dashboard/dist/components/LogStream.js +123 -0
  39. package/dashboard/dist/components/LogStream.test.d.ts +1 -0
  40. package/dashboard/dist/components/LogStream.test.js +159 -0
  41. package/dashboard/dist/components/PreviewPanel.d.ts +18 -0
  42. package/dashboard/dist/components/PreviewPanel.js +73 -0
  43. package/dashboard/dist/components/PreviewPanel.test.d.ts +1 -0
  44. package/dashboard/dist/components/PreviewPanel.test.js +124 -0
  45. package/dashboard/dist/components/ProjectCard.d.ts +18 -0
  46. package/dashboard/dist/components/ProjectCard.js +19 -0
  47. package/dashboard/dist/components/ProjectCard.test.d.ts +1 -0
  48. package/dashboard/dist/components/ProjectCard.test.js +53 -0
  49. package/dashboard/dist/components/ProjectDetail.d.ts +44 -0
  50. package/dashboard/dist/components/ProjectDetail.js +65 -0
  51. package/dashboard/dist/components/ProjectDetail.test.d.ts +1 -0
  52. package/dashboard/dist/components/ProjectDetail.test.js +196 -0
  53. package/dashboard/dist/components/ProjectList.d.ts +11 -0
  54. package/dashboard/dist/components/ProjectList.js +62 -0
  55. package/dashboard/dist/components/ProjectList.test.d.ts +1 -0
  56. package/dashboard/dist/components/ProjectList.test.js +93 -0
  57. package/dashboard/dist/components/SettingsPanel.d.ts +32 -0
  58. package/dashboard/dist/components/SettingsPanel.js +154 -0
  59. package/dashboard/dist/components/SettingsPanel.test.d.ts +1 -0
  60. package/dashboard/dist/components/SettingsPanel.test.js +196 -0
  61. package/dashboard/dist/components/StatusBar.d.ts +16 -0
  62. package/dashboard/dist/components/StatusBar.js +47 -0
  63. package/dashboard/dist/components/StatusBar.test.d.ts +1 -0
  64. package/dashboard/dist/components/StatusBar.test.js +123 -0
  65. package/dashboard/dist/components/TaskBoard.d.ts +22 -0
  66. package/dashboard/dist/components/TaskBoard.js +102 -0
  67. package/dashboard/dist/components/TaskBoard.test.d.ts +1 -0
  68. package/dashboard/dist/components/TaskBoard.test.js +113 -0
  69. package/dashboard/dist/components/TaskCard.d.ts +17 -0
  70. package/dashboard/dist/components/TaskCard.js +29 -0
  71. package/dashboard/dist/components/TaskCard.test.d.ts +1 -0
  72. package/dashboard/dist/components/TaskCard.test.js +109 -0
  73. package/dashboard/dist/components/TaskDetail.d.ts +36 -0
  74. package/dashboard/dist/components/TaskDetail.js +41 -0
  75. package/dashboard/dist/components/TaskDetail.test.d.ts +1 -0
  76. package/dashboard/dist/components/TaskDetail.test.js +164 -0
  77. package/dashboard/dist/components/TaskFilter.d.ts +12 -0
  78. package/dashboard/dist/components/TaskFilter.js +138 -0
  79. package/dashboard/dist/components/TaskFilter.test.d.ts +1 -0
  80. package/dashboard/dist/components/TaskFilter.test.js +109 -0
  81. package/dashboard/dist/components/TeamPanel.d.ts +15 -0
  82. package/dashboard/dist/components/TeamPanel.js +24 -0
  83. package/dashboard/dist/components/TeamPanel.test.d.ts +1 -0
  84. package/dashboard/dist/components/TeamPanel.test.js +109 -0
  85. package/dashboard/dist/components/TeamPresence.d.ts +14 -0
  86. package/dashboard/dist/components/TeamPresence.js +31 -0
  87. package/dashboard/dist/components/TeamPresence.test.d.ts +1 -0
  88. package/dashboard/dist/components/TeamPresence.test.js +144 -0
  89. package/dashboard/dist/components/layout/Header.d.ts +9 -0
  90. package/dashboard/dist/components/layout/Header.js +11 -0
  91. package/dashboard/dist/components/layout/Header.test.d.ts +1 -0
  92. package/dashboard/dist/components/layout/Header.test.js +35 -0
  93. package/dashboard/dist/components/layout/Shell.d.ts +10 -0
  94. package/dashboard/dist/components/layout/Shell.js +5 -0
  95. package/dashboard/dist/components/layout/Shell.test.d.ts +1 -0
  96. package/dashboard/dist/components/layout/Shell.test.js +34 -0
  97. package/dashboard/dist/components/layout/Sidebar.d.ts +14 -0
  98. package/dashboard/dist/components/layout/Sidebar.js +8 -0
  99. package/dashboard/dist/components/layout/Sidebar.test.d.ts +1 -0
  100. package/dashboard/dist/components/layout/Sidebar.test.js +40 -0
  101. package/dashboard/dist/components/ui/Badge.d.ts +9 -0
  102. package/dashboard/dist/components/ui/Badge.js +13 -0
  103. package/dashboard/dist/components/ui/Badge.test.d.ts +1 -0
  104. package/dashboard/dist/components/ui/Badge.test.js +69 -0
  105. package/dashboard/dist/components/ui/Button.d.ts +12 -0
  106. package/dashboard/dist/components/ui/Button.js +14 -0
  107. package/dashboard/dist/components/ui/Button.test.d.ts +1 -0
  108. package/dashboard/dist/components/ui/Button.test.js +81 -0
  109. package/dashboard/dist/components/ui/Card.d.ts +21 -0
  110. package/dashboard/dist/components/ui/Card.js +20 -0
  111. package/dashboard/dist/components/ui/Card.test.d.ts +1 -0
  112. package/dashboard/dist/components/ui/Card.test.js +82 -0
  113. package/dashboard/dist/components/ui/Input.d.ts +13 -0
  114. package/dashboard/dist/components/ui/Input.js +8 -0
  115. package/dashboard/dist/components/ui/Input.test.d.ts +1 -0
  116. package/dashboard/dist/components/ui/Input.test.js +68 -0
  117. package/dashboard/dist/styles/tokens.d.ts +150 -0
  118. package/dashboard/dist/styles/tokens.js +184 -0
  119. package/dashboard/dist/styles/tokens.test.d.ts +1 -0
  120. package/dashboard/dist/styles/tokens.test.js +95 -0
  121. package/dashboard/dist/test/setup.d.ts +1 -0
  122. package/dashboard/dist/test/setup.js +1 -0
  123. package/dashboard/package.json +3 -0
  124. package/package.json +1 -1
  125. package/server/lib/adapters/base-adapter.js +114 -0
  126. package/server/lib/adapters/base-adapter.test.js +90 -0
  127. package/server/lib/adapters/claude-adapter.js +141 -0
  128. package/server/lib/adapters/claude-adapter.test.js +180 -0
  129. package/server/lib/adapters/deepseek-adapter.js +153 -0
  130. package/server/lib/adapters/deepseek-adapter.test.js +193 -0
  131. package/server/lib/adapters/openai-adapter.js +190 -0
  132. package/server/lib/adapters/openai-adapter.test.js +231 -0
  133. package/server/lib/budget-tracker.js +169 -0
  134. package/server/lib/budget-tracker.test.js +165 -0
  135. package/server/lib/claude-injector.js +85 -0
  136. package/server/lib/claude-injector.test.js +161 -0
  137. package/server/lib/consensus-engine.js +135 -0
  138. package/server/lib/consensus-engine.test.js +152 -0
  139. package/server/lib/context-builder.js +112 -0
  140. package/server/lib/context-builder.test.js +120 -0
  141. package/server/lib/file-collector.js +322 -0
  142. package/server/lib/file-collector.test.js +307 -0
  143. package/server/lib/memory-classifier.js +175 -0
  144. package/server/lib/memory-classifier.test.js +169 -0
  145. package/server/lib/memory-committer.js +138 -0
  146. package/server/lib/memory-committer.test.js +136 -0
  147. package/server/lib/memory-hooks.js +127 -0
  148. package/server/lib/memory-hooks.test.js +136 -0
  149. package/server/lib/memory-init.js +104 -0
  150. package/server/lib/memory-init.test.js +119 -0
  151. package/server/lib/memory-observer.js +149 -0
  152. package/server/lib/memory-observer.test.js +158 -0
  153. package/server/lib/memory-reader.js +243 -0
  154. package/server/lib/memory-reader.test.js +216 -0
  155. package/server/lib/memory-storage.js +120 -0
  156. package/server/lib/memory-storage.test.js +136 -0
  157. package/server/lib/memory-writer.js +176 -0
  158. package/server/lib/memory-writer.test.js +231 -0
  159. package/server/lib/overdrive-command.js +30 -6
  160. package/server/lib/overdrive-command.test.js +8 -1
  161. package/server/lib/pattern-detector.js +216 -0
  162. package/server/lib/pattern-detector.test.js +241 -0
  163. package/server/lib/relevance-scorer.js +175 -0
  164. package/server/lib/relevance-scorer.test.js +107 -0
  165. package/server/lib/review-command.js +238 -0
  166. package/server/lib/review-command.test.js +245 -0
  167. package/server/lib/review-orchestrator.js +273 -0
  168. package/server/lib/review-orchestrator.test.js +300 -0
  169. package/server/lib/review-reporter.js +288 -0
  170. package/server/lib/review-reporter.test.js +240 -0
  171. package/server/lib/session-summary.js +90 -0
  172. package/server/lib/session-summary.test.js +156 -0
@@ -0,0 +1,19 @@
1
+ import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
2
+ import { Box, Text } from 'ink';
3
+ import { Badge } from './ui/Badge.js';
4
+ import { Card } from './ui/Card.js';
5
+ export function ProjectCard({ name, description, phase, tests, coverage, lastActivity, isSelected = false, }) {
6
+ const testStatus = tests
7
+ ? tests.failing > 0
8
+ ? 'error'
9
+ : 'success'
10
+ : 'neutral';
11
+ const coverageStatus = coverage !== undefined
12
+ ? coverage >= 80
13
+ ? 'success'
14
+ : coverage >= 60
15
+ ? 'warning'
16
+ : 'error'
17
+ : 'neutral';
18
+ return (_jsx(Card, { variant: isSelected ? 'elevated' : 'default', children: _jsxs(Box, { flexDirection: "column", children: [_jsx(Box, { marginBottom: 1, children: _jsxs(Text, { bold: true, color: isSelected ? 'cyan' : 'white', children: [isSelected ? '▶ ' : ' ', name] }) }), description && (_jsx(Box, { marginBottom: 1, children: _jsx(Text, { dimColor: true, wrap: "truncate-end", children: description }) })), phase && (_jsxs(Box, { marginBottom: 1, children: [_jsx(Text, { dimColor: true, children: "Phase: " }), _jsxs(Text, { color: "cyan", children: [phase.current, "/", phase.total] }), _jsxs(Text, { dimColor: true, children: [" - ", phase.name] })] })), _jsxs(Box, { children: [tests && (_jsx(Box, { marginRight: 2, children: _jsxs(Badge, { variant: testStatus, size: "sm", children: [tests.passing, "/", tests.total, " tests"] }) })), coverage !== undefined && (_jsx(Box, { marginRight: 2, children: _jsxs(Badge, { variant: coverageStatus, size: "sm", children: [coverage, "% cov"] }) })), lastActivity && (_jsx(Box, { children: _jsx(Text, { dimColor: true, children: lastActivity }) }))] })] }) }));
19
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,53 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { describe, it, expect } from 'vitest';
3
+ import { render } from 'ink-testing-library';
4
+ import { ProjectCard } from './ProjectCard.js';
5
+ describe('ProjectCard', () => {
6
+ it('renders project name', () => {
7
+ const { lastFrame } = render(_jsx(ProjectCard, { name: "My Project" }));
8
+ expect(lastFrame()).toContain('My Project');
9
+ });
10
+ it('renders description', () => {
11
+ const { lastFrame } = render(_jsx(ProjectCard, { name: "Project", description: "A test project" }));
12
+ expect(lastFrame()).toContain('A test project');
13
+ });
14
+ it('renders phase progress', () => {
15
+ const { lastFrame } = render(_jsx(ProjectCard, { name: "Project", phase: { current: 3, total: 5, name: 'Authentication' } }));
16
+ expect(lastFrame()).toContain('3/5');
17
+ expect(lastFrame()).toContain('Authentication');
18
+ });
19
+ it('renders test counts', () => {
20
+ const { lastFrame } = render(_jsx(ProjectCard, { name: "Project", tests: { passing: 45, failing: 2, total: 47 } }));
21
+ expect(lastFrame()).toContain('45/47');
22
+ expect(lastFrame()).toContain('tests');
23
+ });
24
+ it('renders coverage percentage', () => {
25
+ const { lastFrame } = render(_jsx(ProjectCard, { name: "Project", coverage: 85 }));
26
+ expect(lastFrame()).toContain('85%');
27
+ expect(lastFrame()).toContain('cov');
28
+ });
29
+ it('renders last activity', () => {
30
+ const { lastFrame } = render(_jsx(ProjectCard, { name: "Project", lastActivity: "2 hours ago" }));
31
+ expect(lastFrame()).toContain('2 hours ago');
32
+ });
33
+ it('shows selection indicator when selected', () => {
34
+ const { lastFrame } = render(_jsx(ProjectCard, { name: "Project", isSelected: true }));
35
+ expect(lastFrame()).toContain('▶');
36
+ });
37
+ it('does not show selection indicator when not selected', () => {
38
+ const { lastFrame } = render(_jsx(ProjectCard, { name: "Project", isSelected: false }));
39
+ expect(lastFrame()).not.toContain('▶');
40
+ });
41
+ it('handles zero coverage', () => {
42
+ const { lastFrame } = render(_jsx(ProjectCard, { name: "Project", coverage: 0 }));
43
+ expect(lastFrame()).toContain('0%');
44
+ });
45
+ it('handles all tests passing', () => {
46
+ const { lastFrame } = render(_jsx(ProjectCard, { name: "Project", tests: { passing: 100, failing: 0, total: 100 } }));
47
+ expect(lastFrame()).toContain('100/100');
48
+ });
49
+ it('renders with minimal props', () => {
50
+ const { lastFrame } = render(_jsx(ProjectCard, { name: "Minimal" }));
51
+ expect(lastFrame()).toContain('Minimal');
52
+ });
53
+ });
@@ -0,0 +1,44 @@
1
+ export interface Phase {
2
+ number: number;
3
+ name: string;
4
+ status: 'completed' | 'in_progress' | 'pending';
5
+ }
6
+ export interface Task {
7
+ id: string;
8
+ title: string;
9
+ status: 'completed' | 'in_progress' | 'pending';
10
+ assignee?: string;
11
+ }
12
+ export interface TestRun {
13
+ id: string;
14
+ timestamp: string;
15
+ passed: number;
16
+ failed: number;
17
+ duration: string;
18
+ }
19
+ export interface LogEntry {
20
+ id: string;
21
+ timestamp: string;
22
+ level: 'info' | 'warn' | 'error';
23
+ message: string;
24
+ }
25
+ export interface ProjectDetailData {
26
+ id: string;
27
+ name: string;
28
+ description?: string;
29
+ phases: Phase[];
30
+ tasks: Task[];
31
+ tests: {
32
+ passing: number;
33
+ failing: number;
34
+ total: number;
35
+ recentRuns: TestRun[];
36
+ };
37
+ logs: LogEntry[];
38
+ }
39
+ export interface ProjectDetailProps {
40
+ project: ProjectDetailData;
41
+ initialTab?: 'overview' | 'tasks' | 'tests' | 'logs';
42
+ onBack?: () => void;
43
+ }
44
+ export declare function ProjectDetail({ project, initialTab, onBack, }: ProjectDetailProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,65 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useState } from 'react';
3
+ import { Box, Text, useInput } from 'ink';
4
+ import { Card } from './ui/Card.js';
5
+ import { Badge } from './ui/Badge.js';
6
+ const tabs = [
7
+ { key: 'overview', label: 'Overview', num: '1' },
8
+ { key: 'tasks', label: 'Tasks', num: '2' },
9
+ { key: 'tests', label: 'Tests', num: '3' },
10
+ { key: 'logs', label: 'Logs', num: '4' },
11
+ ];
12
+ export function ProjectDetail({ project, initialTab = 'overview', onBack, }) {
13
+ const [activeTab, setActiveTab] = useState(initialTab);
14
+ useInput((input, key) => {
15
+ if (input === '1')
16
+ setActiveTab('overview');
17
+ else if (input === '2')
18
+ setActiveTab('tasks');
19
+ else if (input === '3')
20
+ setActiveTab('tests');
21
+ else if (input === '4')
22
+ setActiveTab('logs');
23
+ else if (key.escape && onBack)
24
+ onBack();
25
+ });
26
+ return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { marginBottom: 1, children: [_jsx(Text, { bold: true, color: "cyan", children: project.name }), project.description && (_jsxs(Text, { dimColor: true, children: [" - ", project.description] }))] }), _jsx(Box, { marginBottom: 1, children: tabs.map((tab, i) => (_jsx(Box, { marginRight: 2, children: _jsxs(Text, { color: activeTab === tab.key ? 'cyan' : 'gray', bold: activeTab === tab.key, children: ["[", tab.num, "] ", tab.label] }) }, tab.key))) }), _jsxs(Card, { variant: "outlined", children: [activeTab === 'overview' && _jsx(OverviewTab, { project: project }), activeTab === 'tasks' && _jsx(TasksTab, { tasks: project.tasks }), activeTab === 'tests' && _jsx(TestsTab, { tests: project.tests }), activeTab === 'logs' && _jsx(LogsTab, { logs: project.logs })] }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: "1-4 switch tabs \u2022 Esc back" }) })] }));
27
+ }
28
+ function OverviewTab({ project }) {
29
+ return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { bold: true, children: "Phases" }) }), project.phases.length === 0 ? (_jsx(Text, { dimColor: true, children: "No phases defined" })) : (project.phases.map((phase) => (_jsxs(Box, { children: [_jsx(Text, { color: phase.status === 'completed'
30
+ ? 'green'
31
+ : phase.status === 'in_progress'
32
+ ? 'yellow'
33
+ : 'gray', children: phase.status === 'completed'
34
+ ? '[x] '
35
+ : phase.status === 'in_progress'
36
+ ? '[>] '
37
+ : '[ ] ' }), _jsxs(Text, { children: [phase.number, ". ", phase.name] })] }, phase.number)))), _jsxs(Box, { marginTop: 1, children: [_jsx(Text, { bold: true, children: "Tests: " }), _jsxs(Text, { color: project.tests.failing > 0 ? 'red' : 'green', children: [project.tests.passing, "/", project.tests.total] }), project.tests.failing > 0 && (_jsxs(Text, { color: "red", children: [" (", project.tests.failing, " failing)"] }))] })] }));
38
+ }
39
+ function TasksTab({ tasks }) {
40
+ if (tasks.length === 0) {
41
+ return _jsx(Text, { dimColor: true, children: "No tasks in current phase" });
42
+ }
43
+ return (_jsx(Box, { flexDirection: "column", children: tasks.map((task) => (_jsxs(Box, { marginBottom: 1, children: [_jsx(Text, { color: task.status === 'completed'
44
+ ? 'green'
45
+ : task.status === 'in_progress'
46
+ ? 'yellow'
47
+ : 'gray', children: task.status === 'completed'
48
+ ? '✓ '
49
+ : task.status === 'in_progress'
50
+ ? '▶ '
51
+ : '○ ' }), _jsx(Text, { children: task.title }), task.assignee && (_jsxs(Text, { dimColor: true, children: [" @", task.assignee] }))] }, task.id))) }));
52
+ }
53
+ function TestsTab({ tests, }) {
54
+ return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { marginBottom: 1, children: [_jsxs(Badge, { variant: tests.failing > 0 ? 'error' : 'success', children: [tests.passing, "/", tests.total, " passing"] }), tests.failing > 0 && (_jsx(Box, { marginLeft: 1, children: _jsxs(Badge, { variant: "error", children: [tests.failing, " failing"] }) }))] }), _jsx(Box, { marginBottom: 1, children: _jsx(Text, { bold: true, children: "Recent Runs" }) }), tests.recentRuns.length === 0 ? (_jsx(Text, { dimColor: true, children: "No recent runs" })) : (tests.recentRuns.map((run) => (_jsxs(Box, { children: [_jsx(Text, { dimColor: true, children: run.timestamp }), _jsx(Text, { children: " - " }), _jsxs(Text, { color: run.failed > 0 ? 'red' : 'green', children: [run.passed, "/", run.passed + run.failed] }), _jsxs(Text, { dimColor: true, children: [" (", run.duration, ")"] })] }, run.id))))] }));
55
+ }
56
+ function LogsTab({ logs }) {
57
+ if (logs.length === 0) {
58
+ return _jsx(Text, { dimColor: true, children: "No logs available" });
59
+ }
60
+ return (_jsx(Box, { flexDirection: "column", children: logs.map((log) => (_jsxs(Box, { children: [_jsxs(Text, { dimColor: true, children: [log.timestamp, " "] }), _jsxs(Text, { color: log.level === 'error'
61
+ ? 'red'
62
+ : log.level === 'warn'
63
+ ? 'yellow'
64
+ : 'gray', children: ["[", log.level, "]"] }), _jsxs(Text, { color: log.level === 'error' ? 'red' : undefined, children: [' ', log.message] })] }, log.id))) }));
65
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,196 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { describe, it, expect, vi } from 'vitest';
3
+ import { render } from 'ink-testing-library';
4
+ import { ProjectDetail } from './ProjectDetail.js';
5
+ const sampleProject = {
6
+ id: '1',
7
+ name: 'Test Project',
8
+ description: 'A sample project for testing',
9
+ phases: [
10
+ { number: 1, name: 'Setup', status: 'completed' },
11
+ { number: 2, name: 'Authentication', status: 'in_progress' },
12
+ { number: 3, name: 'Dashboard', status: 'pending' },
13
+ ],
14
+ tasks: [
15
+ { id: 't1', title: 'Create schema', status: 'completed', assignee: 'alice' },
16
+ { id: 't2', title: 'Add validation', status: 'in_progress', assignee: 'bob' },
17
+ { id: 't3', title: 'Write tests', status: 'pending' },
18
+ ],
19
+ tests: {
20
+ passing: 45,
21
+ failing: 2,
22
+ total: 47,
23
+ recentRuns: [
24
+ { id: 'r1', timestamp: '2 min ago', passed: 45, failed: 2, duration: '12s' },
25
+ { id: 'r2', timestamp: '1 hour ago', passed: 44, failed: 3, duration: '15s' },
26
+ ],
27
+ },
28
+ logs: [
29
+ { id: 'l1', timestamp: '10:30:00', level: 'info', message: 'Build started' },
30
+ { id: 'l2', timestamp: '10:30:05', level: 'error', message: 'Test failed: auth.test.ts' },
31
+ { id: 'l3', timestamp: '10:30:10', level: 'info', message: 'Build completed' },
32
+ ],
33
+ };
34
+ describe('ProjectDetail', () => {
35
+ describe('Header', () => {
36
+ it('renders project name', () => {
37
+ const { lastFrame } = render(_jsx(ProjectDetail, { project: sampleProject }));
38
+ expect(lastFrame()).toContain('Test Project');
39
+ });
40
+ it('renders project description', () => {
41
+ const { lastFrame } = render(_jsx(ProjectDetail, { project: sampleProject }));
42
+ expect(lastFrame()).toContain('A sample project for testing');
43
+ });
44
+ it('renders back navigation hint', () => {
45
+ const { lastFrame } = render(_jsx(ProjectDetail, { project: sampleProject }));
46
+ expect(lastFrame()).toContain('Esc');
47
+ });
48
+ });
49
+ describe('Tabs', () => {
50
+ it('shows all four tabs', () => {
51
+ const { lastFrame } = render(_jsx(ProjectDetail, { project: sampleProject }));
52
+ expect(lastFrame()).toContain('Overview');
53
+ expect(lastFrame()).toContain('Tasks');
54
+ expect(lastFrame()).toContain('Tests');
55
+ expect(lastFrame()).toContain('Logs');
56
+ });
57
+ it('shows tab numbers', () => {
58
+ const { lastFrame } = render(_jsx(ProjectDetail, { project: sampleProject }));
59
+ expect(lastFrame()).toContain('1');
60
+ expect(lastFrame()).toContain('2');
61
+ expect(lastFrame()).toContain('3');
62
+ expect(lastFrame()).toContain('4');
63
+ });
64
+ it('shows Overview tab by default', () => {
65
+ const { lastFrame } = render(_jsx(ProjectDetail, { project: sampleProject }));
66
+ // Overview content should be visible - phases
67
+ expect(lastFrame()).toContain('Setup');
68
+ expect(lastFrame()).toContain('Authentication');
69
+ });
70
+ it('accepts initialTab prop', () => {
71
+ const { lastFrame } = render(_jsx(ProjectDetail, { project: sampleProject, initialTab: "tasks" }));
72
+ // Tasks content should be visible
73
+ expect(lastFrame()).toContain('Create schema');
74
+ expect(lastFrame()).toContain('Add validation');
75
+ });
76
+ });
77
+ describe('Overview Tab', () => {
78
+ it('shows phases progress', () => {
79
+ const { lastFrame } = render(_jsx(ProjectDetail, { project: sampleProject, initialTab: "overview" }));
80
+ expect(lastFrame()).toContain('1. Setup');
81
+ expect(lastFrame()).toContain('2. Authentication');
82
+ expect(lastFrame()).toContain('3. Dashboard');
83
+ });
84
+ it('shows completed phase marker', () => {
85
+ const { lastFrame } = render(_jsx(ProjectDetail, { project: sampleProject, initialTab: "overview" }));
86
+ expect(lastFrame()).toContain('[x]');
87
+ });
88
+ it('shows in-progress phase marker', () => {
89
+ const { lastFrame } = render(_jsx(ProjectDetail, { project: sampleProject, initialTab: "overview" }));
90
+ expect(lastFrame()).toContain('[>]');
91
+ });
92
+ it('shows pending phase marker', () => {
93
+ const { lastFrame } = render(_jsx(ProjectDetail, { project: sampleProject, initialTab: "overview" }));
94
+ expect(lastFrame()).toContain('[ ]');
95
+ });
96
+ it('shows test summary', () => {
97
+ const { lastFrame } = render(_jsx(ProjectDetail, { project: sampleProject, initialTab: "overview" }));
98
+ expect(lastFrame()).toContain('45');
99
+ expect(lastFrame()).toContain('47');
100
+ });
101
+ });
102
+ describe('Tasks Tab', () => {
103
+ it('shows task list', () => {
104
+ const { lastFrame } = render(_jsx(ProjectDetail, { project: sampleProject, initialTab: "tasks" }));
105
+ expect(lastFrame()).toContain('Create schema');
106
+ expect(lastFrame()).toContain('Add validation');
107
+ expect(lastFrame()).toContain('Write tests');
108
+ });
109
+ it('shows task status', () => {
110
+ const { lastFrame } = render(_jsx(ProjectDetail, { project: sampleProject, initialTab: "tasks" }));
111
+ // Should show status indicators
112
+ expect(lastFrame()).toMatch(/completed|✓/i);
113
+ });
114
+ it('shows assignee when present', () => {
115
+ const { lastFrame } = render(_jsx(ProjectDetail, { project: sampleProject, initialTab: "tasks" }));
116
+ expect(lastFrame()).toContain('alice');
117
+ expect(lastFrame()).toContain('bob');
118
+ });
119
+ it('handles empty tasks', () => {
120
+ const emptyProject = { ...sampleProject, tasks: [] };
121
+ const { lastFrame } = render(_jsx(ProjectDetail, { project: emptyProject, initialTab: "tasks" }));
122
+ expect(lastFrame()).toContain('No tasks');
123
+ });
124
+ });
125
+ describe('Tests Tab', () => {
126
+ it('shows test summary', () => {
127
+ const { lastFrame } = render(_jsx(ProjectDetail, { project: sampleProject, initialTab: "tests" }));
128
+ expect(lastFrame()).toContain('45');
129
+ expect(lastFrame()).toContain('47');
130
+ });
131
+ it('shows recent test runs', () => {
132
+ const { lastFrame } = render(_jsx(ProjectDetail, { project: sampleProject, initialTab: "tests" }));
133
+ expect(lastFrame()).toContain('2 min ago');
134
+ expect(lastFrame()).toContain('1 hour ago');
135
+ });
136
+ it('shows run duration', () => {
137
+ const { lastFrame } = render(_jsx(ProjectDetail, { project: sampleProject, initialTab: "tests" }));
138
+ expect(lastFrame()).toContain('12s');
139
+ });
140
+ it('handles no test runs', () => {
141
+ const noRunsProject = {
142
+ ...sampleProject,
143
+ tests: { ...sampleProject.tests, recentRuns: [] },
144
+ };
145
+ const { lastFrame } = render(_jsx(ProjectDetail, { project: noRunsProject, initialTab: "tests" }));
146
+ expect(lastFrame()).toContain('No recent runs');
147
+ });
148
+ });
149
+ describe('Logs Tab', () => {
150
+ it('shows log entries', () => {
151
+ const { lastFrame } = render(_jsx(ProjectDetail, { project: sampleProject, initialTab: "logs" }));
152
+ expect(lastFrame()).toContain('Build started');
153
+ expect(lastFrame()).toContain('Test failed');
154
+ expect(lastFrame()).toContain('Build completed');
155
+ });
156
+ it('shows log timestamps', () => {
157
+ const { lastFrame } = render(_jsx(ProjectDetail, { project: sampleProject, initialTab: "logs" }));
158
+ expect(lastFrame()).toContain('10:30:00');
159
+ });
160
+ it('shows error logs differently', () => {
161
+ const { lastFrame } = render(_jsx(ProjectDetail, { project: sampleProject, initialTab: "logs" }));
162
+ // Error messages should be present
163
+ expect(lastFrame()).toContain('error');
164
+ });
165
+ it('handles empty logs', () => {
166
+ const emptyLogsProject = { ...sampleProject, logs: [] };
167
+ const { lastFrame } = render(_jsx(ProjectDetail, { project: emptyLogsProject, initialTab: "logs" }));
168
+ expect(lastFrame()).toContain('No logs');
169
+ });
170
+ });
171
+ describe('Navigation', () => {
172
+ it('shows navigation hint for number keys', () => {
173
+ const { lastFrame } = render(_jsx(ProjectDetail, { project: sampleProject }));
174
+ expect(lastFrame()).toContain('1-4');
175
+ });
176
+ it('calls onBack when provided', () => {
177
+ const onBack = vi.fn();
178
+ const { lastFrame } = render(_jsx(ProjectDetail, { project: sampleProject, onBack: onBack }));
179
+ expect(lastFrame()).toContain('Esc');
180
+ });
181
+ });
182
+ describe('Minimal Project', () => {
183
+ it('renders with minimal data', () => {
184
+ const minimalProject = {
185
+ id: 'min',
186
+ name: 'Minimal',
187
+ phases: [],
188
+ tasks: [],
189
+ tests: { passing: 0, failing: 0, total: 0, recentRuns: [] },
190
+ logs: [],
191
+ };
192
+ const { lastFrame } = render(_jsx(ProjectDetail, { project: minimalProject }));
193
+ expect(lastFrame()).toContain('Minimal');
194
+ });
195
+ });
196
+ });
@@ -0,0 +1,11 @@
1
+ import { ProjectCardProps } from './ProjectCard.js';
2
+ export interface Project extends Omit<ProjectCardProps, 'isSelected'> {
3
+ id: string;
4
+ }
5
+ export interface ProjectListProps {
6
+ projects: Project[];
7
+ onSelect?: (project: Project) => void;
8
+ sortBy?: 'name' | 'activity' | 'status';
9
+ filter?: string;
10
+ }
11
+ export declare function ProjectList({ projects, onSelect, sortBy, filter, }: ProjectListProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,62 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useState, useMemo } from 'react';
3
+ import { Box, Text, useInput } from 'ink';
4
+ import { ProjectCard } from './ProjectCard.js';
5
+ export function ProjectList({ projects, onSelect, sortBy = 'name', filter = '', }) {
6
+ const [selectedIndex, setSelectedIndex] = useState(0);
7
+ // Filter projects
8
+ const filteredProjects = useMemo(() => {
9
+ if (!filter)
10
+ return projects;
11
+ const lowerFilter = filter.toLowerCase();
12
+ return projects.filter(p => p.name.toLowerCase().includes(lowerFilter) ||
13
+ p.description?.toLowerCase().includes(lowerFilter));
14
+ }, [projects, filter]);
15
+ // Sort projects
16
+ const sortedProjects = useMemo(() => {
17
+ const sorted = [...filteredProjects];
18
+ switch (sortBy) {
19
+ case 'name':
20
+ sorted.sort((a, b) => a.name.localeCompare(b.name));
21
+ break;
22
+ case 'activity':
23
+ // Sort by lastActivity (assuming ISO date string or relative time)
24
+ sorted.sort((a, b) => {
25
+ if (!a.lastActivity)
26
+ return 1;
27
+ if (!b.lastActivity)
28
+ return -1;
29
+ return b.lastActivity.localeCompare(a.lastActivity);
30
+ });
31
+ break;
32
+ case 'status':
33
+ // Sort by test status (failing first)
34
+ sorted.sort((a, b) => {
35
+ const aFailing = a.tests?.failing || 0;
36
+ const bFailing = b.tests?.failing || 0;
37
+ return bFailing - aFailing;
38
+ });
39
+ break;
40
+ }
41
+ return sorted;
42
+ }, [filteredProjects, sortBy]);
43
+ // Handle keyboard navigation
44
+ useInput((input, key) => {
45
+ if (sortedProjects.length === 0)
46
+ return;
47
+ if (key.downArrow || input === 'j') {
48
+ setSelectedIndex(prev => Math.min(prev + 1, sortedProjects.length - 1));
49
+ }
50
+ else if (key.upArrow || input === 'k') {
51
+ setSelectedIndex(prev => Math.max(prev - 1, 0));
52
+ }
53
+ else if (key.return && onSelect) {
54
+ onSelect(sortedProjects[selectedIndex]);
55
+ }
56
+ });
57
+ // Empty state
58
+ if (sortedProjects.length === 0) {
59
+ return (_jsxs(Box, { flexDirection: "column", padding: 1, children: [_jsx(Text, { dimColor: true, children: filter ? `No projects matching "${filter}"` : 'No projects found' }), _jsx(Text, { dimColor: true, children: filter ? 'Try a different search term' : 'Run /tlc:new-project to create one' })] }));
60
+ }
61
+ return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Box, { marginBottom: 1, children: _jsxs(Text, { dimColor: true, children: [sortedProjects.length, " project", sortedProjects.length !== 1 ? 's' : '', filter && ` matching "${filter}"`] }) }), sortedProjects.map((project, index) => (_jsx(Box, { marginBottom: 1, children: _jsx(ProjectCard, { ...project, isSelected: index === selectedIndex }) }, project.id))), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: "\u2191/k \u2193/j navigate \u2022 Enter select" }) })] }));
62
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,93 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { describe, it, expect } from 'vitest';
3
+ import { render } from 'ink-testing-library';
4
+ import { ProjectList } from './ProjectList.js';
5
+ const sampleProjects = [
6
+ {
7
+ id: '1',
8
+ name: 'Alpha Project',
9
+ description: 'First project',
10
+ tests: { passing: 10, failing: 0, total: 10 },
11
+ coverage: 85,
12
+ lastActivity: '1 hour ago',
13
+ },
14
+ {
15
+ id: '2',
16
+ name: 'Beta Project',
17
+ description: 'Second project',
18
+ tests: { passing: 8, failing: 2, total: 10 },
19
+ coverage: 65,
20
+ lastActivity: '2 hours ago',
21
+ },
22
+ {
23
+ id: '3',
24
+ name: 'Gamma Project',
25
+ description: 'Third project',
26
+ tests: { passing: 5, failing: 5, total: 10 },
27
+ coverage: 50,
28
+ lastActivity: '3 hours ago',
29
+ },
30
+ ];
31
+ describe('ProjectList', () => {
32
+ it('renders list of projects', () => {
33
+ const { lastFrame } = render(_jsx(ProjectList, { projects: sampleProjects }));
34
+ expect(lastFrame()).toContain('Alpha Project');
35
+ expect(lastFrame()).toContain('Beta Project');
36
+ expect(lastFrame()).toContain('Gamma Project');
37
+ });
38
+ it('shows project count', () => {
39
+ const { lastFrame } = render(_jsx(ProjectList, { projects: sampleProjects }));
40
+ expect(lastFrame()).toContain('3 projects');
41
+ });
42
+ it('shows single project count correctly', () => {
43
+ const { lastFrame } = render(_jsx(ProjectList, { projects: [sampleProjects[0]] }));
44
+ expect(lastFrame()).toContain('1 project');
45
+ });
46
+ it('filters projects by name', () => {
47
+ const { lastFrame } = render(_jsx(ProjectList, { projects: sampleProjects, filter: "Alpha" }));
48
+ expect(lastFrame()).toContain('Alpha Project');
49
+ expect(lastFrame()).not.toContain('Beta Project');
50
+ expect(lastFrame()).toContain('matching "Alpha"');
51
+ });
52
+ it('filters projects by description', () => {
53
+ const { lastFrame } = render(_jsx(ProjectList, { projects: sampleProjects, filter: "Second" }));
54
+ expect(lastFrame()).toContain('Beta Project');
55
+ expect(lastFrame()).not.toContain('Alpha Project');
56
+ });
57
+ it('shows empty state when no projects', () => {
58
+ const { lastFrame } = render(_jsx(ProjectList, { projects: [] }));
59
+ expect(lastFrame()).toContain('No projects found');
60
+ expect(lastFrame()).toContain('/tlc:new-project');
61
+ });
62
+ it('shows empty state when filter matches nothing', () => {
63
+ const { lastFrame } = render(_jsx(ProjectList, { projects: sampleProjects, filter: "nonexistent" }));
64
+ expect(lastFrame()).toContain('No projects matching');
65
+ expect(lastFrame()).toContain('Try a different search term');
66
+ });
67
+ it('sorts by name by default', () => {
68
+ const { lastFrame } = render(_jsx(ProjectList, { projects: sampleProjects }));
69
+ const output = lastFrame() || '';
70
+ const alphaIndex = output.indexOf('Alpha');
71
+ const betaIndex = output.indexOf('Beta');
72
+ const gammaIndex = output.indexOf('Gamma');
73
+ expect(alphaIndex).toBeLessThan(betaIndex);
74
+ expect(betaIndex).toBeLessThan(gammaIndex);
75
+ });
76
+ it('sorts by status (failing first)', () => {
77
+ const { lastFrame } = render(_jsx(ProjectList, { projects: sampleProjects, sortBy: "status" }));
78
+ const output = lastFrame() || '';
79
+ // Gamma has most failures (5), should be first
80
+ const gammaIndex = output.indexOf('Gamma');
81
+ const alphaIndex = output.indexOf('Alpha');
82
+ expect(gammaIndex).toBeLessThan(alphaIndex);
83
+ });
84
+ it('shows navigation hint', () => {
85
+ const { lastFrame } = render(_jsx(ProjectList, { projects: sampleProjects }));
86
+ expect(lastFrame()).toContain('↑/k ↓/j navigate');
87
+ expect(lastFrame()).toContain('Enter select');
88
+ });
89
+ it('first project is selected by default', () => {
90
+ const { lastFrame } = render(_jsx(ProjectList, { projects: sampleProjects }));
91
+ expect(lastFrame()).toContain('▶');
92
+ });
93
+ });
@@ -0,0 +1,32 @@
1
+ export interface TLCConfig {
2
+ project?: string;
3
+ version?: string;
4
+ quality?: {
5
+ coverageThreshold?: number;
6
+ qualityScoreThreshold?: number;
7
+ };
8
+ git?: {
9
+ mainBranch?: string;
10
+ };
11
+ paths?: {
12
+ planning?: string;
13
+ tests?: string;
14
+ };
15
+ team?: {
16
+ enabled?: boolean;
17
+ };
18
+ testFrameworks?: {
19
+ primary?: string;
20
+ e2e?: string;
21
+ };
22
+ }
23
+ export interface SettingsPanelProps {
24
+ config: TLCConfig;
25
+ configPath?: string;
26
+ isEditing?: boolean;
27
+ isActive?: boolean;
28
+ onEdit?: () => void;
29
+ onSave?: (config: TLCConfig) => void;
30
+ onCancel?: () => void;
31
+ }
32
+ export declare function SettingsPanel({ config, configPath, isEditing, isActive, onEdit, onSave, onCancel, }: SettingsPanelProps): import("react/jsx-runtime").JSX.Element;