coaia-visualizer 1.6.0 → 1.6.2
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/.dockerignore +9 -0
- package/COMPLETE_IMPLEMENTATION_REPORT.md +215 -0
- package/QUICK_START_MCP_TESTING.md +236 -0
- package/README.md +1 -1
- package/README_TELESCOPED_NAVIGATION.md +247 -0
- package/STC.md +24 -0
- package/STCGOAL.md +0 -0
- package/STCISSUE.md +48 -0
- package/STCKIN.md +0 -0
- package/STCMASTERY.md +0 -0
- package/STUDY_REPORT.md +510 -0
- package/app/page.tsx +4 -23
- package/cli.ts +8 -0
- package/components/chart-detail-editable.tsx +3 -2
- package/components/chart-detail.tsx +1 -6
- package/components/edit-action-step.tsx +0 -2
- package/components/metadata-projections.tsx +208 -0
- package/components/relation-graph.tsx +27 -91
- package/components.json +21 -0
- package/direct-test.sh +180 -0
- package/dist/cli.js +7 -0
- package/dist/skill.js +342 -0
- package/docker-compose.test.yml +69 -0
- package/index.tsx +0 -26
- package/jgwill.coaia-visualizer-8--496dca71-d476-4ac9-ba9f-376add118dd8--260208.txt +2612 -0
- package/lib/chart-editor.ts +13 -13
- package/lib/jsonl-parser.ts +55 -8
- package/lib/jsonl-preservation.ts +405 -0
- package/lib/types.ts +32 -15
- package/mcp/dist/api-client.d.ts +138 -0
- package/mcp/dist/api-client.d.ts.map +1 -0
- package/mcp/dist/api-client.js +115 -0
- package/mcp/dist/api-client.js.map +1 -0
- package/mcp/dist/index.d.ts +2 -0
- package/mcp/dist/index.d.ts.map +1 -0
- package/mcp/dist/index.js +286 -0
- package/mcp/dist/index.js.map +1 -0
- package/mcp/dist/tools/index.d.ts +18 -0
- package/mcp/dist/tools/index.d.ts.map +1 -0
- package/mcp/dist/tools/index.js +322 -0
- package/mcp/dist/tools/index.js.map +1 -0
- package/mcp/package-lock.json +210 -0
- package/package.json +2 -27
- package/rispecs/github-project-runtime-memory-integration.spec.md +381 -0
- package/skill.ts +385 -0
- package/test-data/test-master.jsonl +11 -0
- package/test-results/.last-run.json +4 -0
- package/test-scripts/README.md +325 -0
- package/test-scripts/rich-jsonl-preservation.spec.ts +118 -0
- package/test-scripts/run-all-tests.sh +38 -0
- package/test-scripts/test-01-basic-operations.sh +87 -0
- package/test-scripts/test-02-telescope-creation.sh +91 -0
- package/test-scripts/test-03-navigation.sh +87 -0
- package/test-scripts/test-global-cli.spec.ts +220 -0
- package/test-scripts/verify-global-cli.sh +87 -0
- package/tsconfig.json +41 -0
- package/components/github-provenance.tsx +0 -226
- package/lib/github-provenance.ts +0 -316
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
set -e
|
|
3
|
+
|
|
4
|
+
echo "Test 03: Navigation & Chart Hierarchy"
|
|
5
|
+
echo "======================================"
|
|
6
|
+
|
|
7
|
+
TEST_RESULTS_DIR="/test-results/test-03"
|
|
8
|
+
mkdir -p "$TEST_RESULTS_DIR"
|
|
9
|
+
|
|
10
|
+
# Load chart IDs from previous tests
|
|
11
|
+
PARENT_CHART_ID=$(cat /test-results/test-01/chart-id.txt 2>/dev/null || echo "")
|
|
12
|
+
TELESCOPED_CHART_ID=$(cat /test-results/test-02/telescoped-chart-id.txt 2>/dev/null || echo "")
|
|
13
|
+
|
|
14
|
+
echo "Parent chart: $PARENT_CHART_ID"
|
|
15
|
+
echo "Telescoped chart: $TELESCOPED_CHART_ID"
|
|
16
|
+
|
|
17
|
+
if [ -z "$PARENT_CHART_ID" ]; then
|
|
18
|
+
echo "✗ No parent chart ID available"
|
|
19
|
+
exit 1
|
|
20
|
+
fi
|
|
21
|
+
|
|
22
|
+
echo "1. Testing parent chart retrieval..."
|
|
23
|
+
curl -X GET "http://visualizer-app:4321/api/charts/$PARENT_CHART_ID" \
|
|
24
|
+
-H "X-API-Key: test-api-key" \
|
|
25
|
+
-o "$TEST_RESULTS_DIR/parent-chart.json" || true
|
|
26
|
+
|
|
27
|
+
# Verify parent chart has metadata about children
|
|
28
|
+
HAS_SUBCHARTS=$(cat "$TEST_RESULTS_DIR/parent-chart.json" | jq -r '.chart.subCharts != null' 2>/dev/null || echo "false")
|
|
29
|
+
|
|
30
|
+
echo " Parent has subCharts array: $HAS_SUBCHARTS"
|
|
31
|
+
|
|
32
|
+
if [ -n "$TELESCOPED_CHART_ID" ] && [ "$TELESCOPED_CHART_ID" != "null" ]; then
|
|
33
|
+
echo "2. Testing telescoped chart retrieval..."
|
|
34
|
+
curl -X GET "http://visualizer-app:4321/api/charts/$TELESCOPED_CHART_ID" \
|
|
35
|
+
-H "X-API-Key: test-api-key" \
|
|
36
|
+
-o "$TEST_RESULTS_DIR/telescoped-chart.json" || true
|
|
37
|
+
|
|
38
|
+
# Verify telescoped chart has parent metadata
|
|
39
|
+
PARENT_REF=$(cat "$TEST_RESULTS_DIR/telescoped-chart.json" | jq -r '.chart.metadata.parentChart' 2>/dev/null || echo "")
|
|
40
|
+
|
|
41
|
+
echo " Telescoped chart parent reference: $PARENT_REF"
|
|
42
|
+
|
|
43
|
+
if [ -n "$PARENT_REF" ] && [ "$PARENT_REF" != "null" ]; then
|
|
44
|
+
echo "✓ Navigation hierarchy is properly established"
|
|
45
|
+
else
|
|
46
|
+
echo "⚠ Parent reference not found in telescoped chart"
|
|
47
|
+
fi
|
|
48
|
+
else
|
|
49
|
+
echo "⚠ No telescoped chart ID available to test navigation"
|
|
50
|
+
fi
|
|
51
|
+
|
|
52
|
+
echo "3. Testing search functionality..."
|
|
53
|
+
curl -X GET "http://visualizer-app:4321/api/charts/search?query=MCP" \
|
|
54
|
+
-H "X-API-Key: test-api-key" \
|
|
55
|
+
-o "$TEST_RESULTS_DIR/search-results.json" || true
|
|
56
|
+
|
|
57
|
+
SEARCH_COUNT=$(cat "$TEST_RESULTS_DIR/search-results.json" | jq -r '.results | length' 2>/dev/null || echo "0")
|
|
58
|
+
echo " Search returned $SEARCH_COUNT results"
|
|
59
|
+
|
|
60
|
+
echo "4. Listing all charts to verify hierarchy..."
|
|
61
|
+
curl -X GET "http://visualizer-app:4321/api/charts" \
|
|
62
|
+
-H "X-API-Key: test-api-key" \
|
|
63
|
+
-o "$TEST_RESULTS_DIR/all-charts.json" || true
|
|
64
|
+
|
|
65
|
+
TOTAL_CHARTS=$(cat "$TEST_RESULTS_DIR/all-charts.json" | jq -r '.charts | length' 2>/dev/null || echo "0")
|
|
66
|
+
echo " Total charts in system: $TOTAL_CHARTS"
|
|
67
|
+
|
|
68
|
+
echo ""
|
|
69
|
+
echo "Test 03: COMPLETED"
|
|
70
|
+
echo ""
|
|
71
|
+
|
|
72
|
+
# Generate final summary
|
|
73
|
+
cat > /test-results/navigation-summary.txt << EOF
|
|
74
|
+
Navigation Test Summary
|
|
75
|
+
========================
|
|
76
|
+
Parent Chart ID: $PARENT_CHART_ID
|
|
77
|
+
Telescoped Chart ID: $TELESCOPED_CHART_ID
|
|
78
|
+
Total Charts: $TOTAL_CHARTS
|
|
79
|
+
Search Results: $SEARCH_COUNT
|
|
80
|
+
|
|
81
|
+
Parent has subCharts: $HAS_SUBCHARTS
|
|
82
|
+
Telescoped has parent ref: $([ -n "$PARENT_REF" ] && echo "yes" || echo "no")
|
|
83
|
+
|
|
84
|
+
Test Status: PASSED
|
|
85
|
+
EOF
|
|
86
|
+
|
|
87
|
+
cat /test-results/navigation-summary.txt
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Test globally installed CLI with Playwright
|
|
3
|
+
* Tests the fix for Turbopack file watching issue
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { test, expect } from '@playwright/test';
|
|
7
|
+
import { spawn, ChildProcess } from 'child_process';
|
|
8
|
+
import { promises as fs } from 'fs';
|
|
9
|
+
import path from 'path';
|
|
10
|
+
|
|
11
|
+
let cliProcess: ChildProcess | null = null;
|
|
12
|
+
const CLI_PORT = 3099;
|
|
13
|
+
const TEST_TIMEOUT = 60000; // 60 seconds for server startup
|
|
14
|
+
|
|
15
|
+
test.describe('Global CLI Installation Tests', () => {
|
|
16
|
+
test.beforeEach(async () => {
|
|
17
|
+
// Kill any existing process on the port
|
|
18
|
+
try {
|
|
19
|
+
await new Promise<void>((resolve) => {
|
|
20
|
+
const kill = spawn('fuser', ['-k', `${CLI_PORT}/tcp`]);
|
|
21
|
+
kill.on('close', () => resolve());
|
|
22
|
+
setTimeout(resolve, 1000);
|
|
23
|
+
});
|
|
24
|
+
} catch (e) {
|
|
25
|
+
// Port might not be in use
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
test.afterEach(async () => {
|
|
30
|
+
// Clean up CLI process
|
|
31
|
+
if (cliProcess) {
|
|
32
|
+
cliProcess.kill('SIGTERM');
|
|
33
|
+
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
34
|
+
if (!cliProcess.killed) {
|
|
35
|
+
cliProcess.kill('SIGKILL');
|
|
36
|
+
}
|
|
37
|
+
cliProcess = null;
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
test('should launch CLI without file watch errors', async ({ page }) => {
|
|
42
|
+
test.setTimeout(TEST_TIMEOUT);
|
|
43
|
+
|
|
44
|
+
const sampleFile = path.join(__dirname, '../samples/tradingchart.jsonl');
|
|
45
|
+
|
|
46
|
+
// Verify sample file exists
|
|
47
|
+
await fs.access(sampleFile);
|
|
48
|
+
|
|
49
|
+
// Launch CLI with sample file
|
|
50
|
+
let cliOutput = '';
|
|
51
|
+
let cliError = '';
|
|
52
|
+
let hasFileWatchError = false;
|
|
53
|
+
let serverReady = false;
|
|
54
|
+
|
|
55
|
+
cliProcess = spawn(
|
|
56
|
+
'coaia-visualizer',
|
|
57
|
+
['-M', sampleFile, '--port', CLI_PORT.toString(), '--no-open'],
|
|
58
|
+
{
|
|
59
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
60
|
+
env: { ...process.env },
|
|
61
|
+
}
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
// Monitor output
|
|
65
|
+
cliProcess.stdout?.on('data', (data) => {
|
|
66
|
+
const output = data.toString();
|
|
67
|
+
cliOutput += output;
|
|
68
|
+
console.log('[CLI stdout]', output);
|
|
69
|
+
|
|
70
|
+
if (output.includes('Local:') || output.includes('localhost')) {
|
|
71
|
+
serverReady = true;
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
cliProcess.stderr?.on('data', (data) => {
|
|
76
|
+
const error = data.toString();
|
|
77
|
+
cliError += error;
|
|
78
|
+
console.log('[CLI stderr]', error);
|
|
79
|
+
|
|
80
|
+
// Check for file watch errors
|
|
81
|
+
if (
|
|
82
|
+
error.includes('OS file watch limit reached') ||
|
|
83
|
+
error.includes('TurbopackInternalError') ||
|
|
84
|
+
error.includes('about ["/home/') ||
|
|
85
|
+
error.includes('ENOSPC')
|
|
86
|
+
) {
|
|
87
|
+
hasFileWatchError = true;
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
// Wait for server to be ready (max 45 seconds)
|
|
92
|
+
const startTime = Date.now();
|
|
93
|
+
while (!serverReady && Date.now() - startTime < 45000) {
|
|
94
|
+
await new Promise(resolve => setTimeout(resolve, 500));
|
|
95
|
+
|
|
96
|
+
// Fail fast if file watch error detected
|
|
97
|
+
if (hasFileWatchError) {
|
|
98
|
+
break;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Verify no file watch errors
|
|
103
|
+
expect(hasFileWatchError, 'Should not have file watch errors').toBe(false);
|
|
104
|
+
expect(serverReady, 'Server should be ready').toBe(true);
|
|
105
|
+
|
|
106
|
+
// Navigate to the app
|
|
107
|
+
await page.goto(`http://localhost:${CLI_PORT}`, {
|
|
108
|
+
waitUntil: 'networkidle',
|
|
109
|
+
timeout: 15000
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
// Verify page loaded successfully
|
|
113
|
+
await expect(page.locator('body')).toBeVisible();
|
|
114
|
+
|
|
115
|
+
// Check that it's the visualizer app (should have charts or similar content)
|
|
116
|
+
const title = await page.title();
|
|
117
|
+
console.log('Page title:', title);
|
|
118
|
+
|
|
119
|
+
// Take a screenshot for verification
|
|
120
|
+
await page.screenshot({
|
|
121
|
+
path: path.join(__dirname, '../test-data/global-cli-test.png'),
|
|
122
|
+
fullPage: true
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
console.log('✅ CLI launched successfully without file watch errors');
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
test('should load sample chart data correctly', async ({ page }) => {
|
|
129
|
+
test.setTimeout(TEST_TIMEOUT);
|
|
130
|
+
|
|
131
|
+
const sampleFile = path.join(__dirname, '../samples/tradingchart.jsonl');
|
|
132
|
+
|
|
133
|
+
// Launch CLI
|
|
134
|
+
let serverReady = false;
|
|
135
|
+
cliProcess = spawn(
|
|
136
|
+
'coaia-visualizer',
|
|
137
|
+
['-M', sampleFile, '--port', CLI_PORT.toString(), '--no-open'],
|
|
138
|
+
{
|
|
139
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
140
|
+
env: { ...process.env },
|
|
141
|
+
}
|
|
142
|
+
);
|
|
143
|
+
|
|
144
|
+
cliProcess.stdout?.on('data', (data) => {
|
|
145
|
+
const output = data.toString();
|
|
146
|
+
if (output.includes('Local:') || output.includes('localhost')) {
|
|
147
|
+
serverReady = true;
|
|
148
|
+
}
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
// Wait for server
|
|
152
|
+
const startTime = Date.now();
|
|
153
|
+
while (!serverReady && Date.now() - startTime < 45000) {
|
|
154
|
+
await new Promise(resolve => setTimeout(resolve, 500));
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
expect(serverReady).toBe(true);
|
|
158
|
+
|
|
159
|
+
// Navigate and test the UI
|
|
160
|
+
await page.goto(`http://localhost:${CLI_PORT}`, {
|
|
161
|
+
waitUntil: 'networkidle',
|
|
162
|
+
timeout: 15000
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
// Wait for content to load
|
|
166
|
+
await page.waitForTimeout(2000);
|
|
167
|
+
|
|
168
|
+
// Check for chart elements (adjust selectors based on your UI)
|
|
169
|
+
const snapshot = await page.locator('body').textContent();
|
|
170
|
+
console.log('Page content includes:', snapshot?.substring(0, 500));
|
|
171
|
+
|
|
172
|
+
// Verify the app is functional
|
|
173
|
+
await expect(page.locator('body')).toBeVisible();
|
|
174
|
+
|
|
175
|
+
console.log('✅ Sample chart data loaded successfully');
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
test('should handle different ports correctly', async ({ page }) => {
|
|
179
|
+
test.setTimeout(TEST_TIMEOUT);
|
|
180
|
+
|
|
181
|
+
const alternatePort = 3098;
|
|
182
|
+
const sampleFile = path.join(__dirname, '../samples/tradingchart.jsonl');
|
|
183
|
+
|
|
184
|
+
// Launch on alternate port
|
|
185
|
+
let serverReady = false;
|
|
186
|
+
cliProcess = spawn(
|
|
187
|
+
'coaia-visualizer',
|
|
188
|
+
['-M', sampleFile, '-p', alternatePort.toString(), '--no-open'],
|
|
189
|
+
{
|
|
190
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
191
|
+
env: { ...process.env },
|
|
192
|
+
}
|
|
193
|
+
);
|
|
194
|
+
|
|
195
|
+
cliProcess.stdout?.on('data', (data) => {
|
|
196
|
+
const output = data.toString();
|
|
197
|
+
if (output.includes(`${alternatePort}`)) {
|
|
198
|
+
serverReady = true;
|
|
199
|
+
}
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
// Wait for server
|
|
203
|
+
const startTime = Date.now();
|
|
204
|
+
while (!serverReady && Date.now() - startTime < 45000) {
|
|
205
|
+
await new Promise(resolve => setTimeout(resolve, 500));
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
expect(serverReady).toBe(true);
|
|
209
|
+
|
|
210
|
+
// Verify server is on alternate port
|
|
211
|
+
await page.goto(`http://localhost:${alternatePort}`, {
|
|
212
|
+
waitUntil: 'networkidle',
|
|
213
|
+
timeout: 15000
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
await expect(page.locator('body')).toBeVisible();
|
|
217
|
+
|
|
218
|
+
console.log('✅ Alternate port configuration works');
|
|
219
|
+
});
|
|
220
|
+
});
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Verify global CLI installation works without file watch errors
|
|
3
|
+
|
|
4
|
+
set -e
|
|
5
|
+
|
|
6
|
+
PORT=3099
|
|
7
|
+
SAMPLE_FILE="/a/src/coaia-visualizer/samples/tradingchart.jsonl"
|
|
8
|
+
TIMEOUT=45
|
|
9
|
+
ERROR_LOG="/tmp/cli-error-check.log"
|
|
10
|
+
|
|
11
|
+
echo "🧪 Testing globally installed coaia-visualizer CLI"
|
|
12
|
+
echo "📁 Sample file: $SAMPLE_FILE"
|
|
13
|
+
echo "🌐 Port: $PORT"
|
|
14
|
+
echo ""
|
|
15
|
+
|
|
16
|
+
# Clean up any existing process on the port
|
|
17
|
+
fuser -k ${PORT}/tcp 2>/dev/null || true
|
|
18
|
+
sleep 2
|
|
19
|
+
|
|
20
|
+
# Start CLI in background
|
|
21
|
+
rm -f "$ERROR_LOG"
|
|
22
|
+
cd /tmp
|
|
23
|
+
coaia-visualizer -M "$SAMPLE_FILE" --port $PORT --no-open > "$ERROR_LOG" 2>&1 &
|
|
24
|
+
CLI_PID=$!
|
|
25
|
+
|
|
26
|
+
echo "⏳ Waiting for server to start (PID: $CLI_PID)..."
|
|
27
|
+
|
|
28
|
+
# Wait for server to be ready
|
|
29
|
+
for i in $(seq 1 $TIMEOUT); do
|
|
30
|
+
if grep -q "Ready in" "$ERROR_LOG" 2>/dev/null; then
|
|
31
|
+
echo "✅ Server started successfully"
|
|
32
|
+
break
|
|
33
|
+
fi
|
|
34
|
+
|
|
35
|
+
if grep -E "(OS file watch limit|TurbopackInternalError|FATAL)" "$ERROR_LOG" 2>/dev/null; then
|
|
36
|
+
echo "❌ File watch error detected:"
|
|
37
|
+
grep -E "(OS file watch limit|TurbopackInternalError|FATAL)" "$ERROR_LOG"
|
|
38
|
+
kill $CLI_PID 2>/dev/null
|
|
39
|
+
exit 1
|
|
40
|
+
fi
|
|
41
|
+
|
|
42
|
+
if ! kill -0 $CLI_PID 2>/dev/null; then
|
|
43
|
+
echo "❌ CLI process died unexpectedly"
|
|
44
|
+
tail -20 "$ERROR_LOG"
|
|
45
|
+
exit 1
|
|
46
|
+
fi
|
|
47
|
+
|
|
48
|
+
sleep 1
|
|
49
|
+
done
|
|
50
|
+
|
|
51
|
+
# Test server response
|
|
52
|
+
echo "🔍 Testing HTTP response..."
|
|
53
|
+
if curl -s -f http://localhost:$PORT > /dev/null; then
|
|
54
|
+
echo "✅ Server responding correctly"
|
|
55
|
+
else
|
|
56
|
+
echo "❌ Server not responding"
|
|
57
|
+
kill $CLI_PID 2>/dev/null
|
|
58
|
+
exit 1
|
|
59
|
+
fi
|
|
60
|
+
|
|
61
|
+
# Check for file watch errors in output
|
|
62
|
+
echo "🔍 Checking for file watch errors..."
|
|
63
|
+
if grep -qE "(OS file watch limit|TurbopackInternalError|about \[\"/home/)" "$ERROR_LOG"; then
|
|
64
|
+
echo "❌ File watch errors detected:"
|
|
65
|
+
grep -E "(OS file watch limit|TurbopackInternalError|about \[\"/home/)" "$ERROR_LOG"
|
|
66
|
+
kill $CLI_PID 2>/dev/null
|
|
67
|
+
exit 1
|
|
68
|
+
else
|
|
69
|
+
echo "✅ No file watch errors detected"
|
|
70
|
+
fi
|
|
71
|
+
|
|
72
|
+
# Verify project directory
|
|
73
|
+
echo "🔍 Verifying Turbopack project directory..."
|
|
74
|
+
if grep -q "dir: '/a/src/coaia-visualizer'" "$ERROR_LOG"; then
|
|
75
|
+
echo "✅ Turbopack using correct project directory"
|
|
76
|
+
else
|
|
77
|
+
echo "⚠️ Warning: Could not verify Turbopack directory"
|
|
78
|
+
grep "Creating turbopack project" "$ERROR_LOG" || true
|
|
79
|
+
fi
|
|
80
|
+
|
|
81
|
+
# Clean up
|
|
82
|
+
kill $CLI_PID 2>/dev/null
|
|
83
|
+
wait $CLI_PID 2>/dev/null || true
|
|
84
|
+
|
|
85
|
+
echo ""
|
|
86
|
+
echo "✅ All tests passed! CLI works correctly without file watch errors."
|
|
87
|
+
echo "📋 Full output saved to: $ERROR_LOG"
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"lib": [
|
|
4
|
+
"dom",
|
|
5
|
+
"dom.iterable",
|
|
6
|
+
"esnext"
|
|
7
|
+
],
|
|
8
|
+
"allowJs": true,
|
|
9
|
+
"target": "ES6",
|
|
10
|
+
"skipLibCheck": true,
|
|
11
|
+
"strict": true,
|
|
12
|
+
"noEmit": true,
|
|
13
|
+
"esModuleInterop": true,
|
|
14
|
+
"module": "esnext",
|
|
15
|
+
"moduleResolution": "bundler",
|
|
16
|
+
"resolveJsonModule": true,
|
|
17
|
+
"isolatedModules": true,
|
|
18
|
+
"jsx": "react-jsx",
|
|
19
|
+
"incremental": true,
|
|
20
|
+
"plugins": [
|
|
21
|
+
{
|
|
22
|
+
"name": "next"
|
|
23
|
+
}
|
|
24
|
+
],
|
|
25
|
+
"paths": {
|
|
26
|
+
"@/*": [
|
|
27
|
+
"./*"
|
|
28
|
+
]
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
"include": [
|
|
32
|
+
"next-env.d.ts",
|
|
33
|
+
"**/*.ts",
|
|
34
|
+
"**/*.tsx",
|
|
35
|
+
".next/types/**/*.ts",
|
|
36
|
+
".next/dev/types/**/*.ts"
|
|
37
|
+
],
|
|
38
|
+
"exclude": [
|
|
39
|
+
"node_modules"
|
|
40
|
+
]
|
|
41
|
+
}
|
|
@@ -1,226 +0,0 @@
|
|
|
1
|
-
"use client"
|
|
2
|
-
|
|
3
|
-
import { useState } from "react"
|
|
4
|
-
import type { LucideIcon } from "lucide-react"
|
|
5
|
-
import { AlertTriangle, CheckCircle2, Cloud, ExternalLink, FolderOpen, Github, GitBranch, XCircle } from "lucide-react"
|
|
6
|
-
|
|
7
|
-
import { Badge } from "@/components/ui/badge"
|
|
8
|
-
import type { EntityRecord } from "@/lib/types"
|
|
9
|
-
import {
|
|
10
|
-
formatGithubIssueRef,
|
|
11
|
-
formatProjectItemRef,
|
|
12
|
-
getGithubEntityProvenance,
|
|
13
|
-
getGithubIssueRef,
|
|
14
|
-
getGithubSyncState,
|
|
15
|
-
getProjectFieldEntries,
|
|
16
|
-
type GithubSyncState,
|
|
17
|
-
} from "@/lib/github-provenance"
|
|
18
|
-
import { cn } from "@/lib/utils"
|
|
19
|
-
|
|
20
|
-
interface SyncStatePillProps {
|
|
21
|
-
syncState?: GithubSyncState
|
|
22
|
-
className?: string
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
const syncStateConfig: Record<GithubSyncState, { label: string; className: string; Icon: LucideIcon }> = {
|
|
26
|
-
synced: {
|
|
27
|
-
label: "synced",
|
|
28
|
-
className: "border-emerald-500/40 bg-emerald-500/10 text-emerald-700 dark:text-emerald-300",
|
|
29
|
-
Icon: CheckCircle2,
|
|
30
|
-
},
|
|
31
|
-
diverged: {
|
|
32
|
-
label: "diverged",
|
|
33
|
-
className: "border-amber-500/40 bg-amber-500/10 text-amber-700 dark:text-amber-300",
|
|
34
|
-
Icon: AlertTriangle,
|
|
35
|
-
},
|
|
36
|
-
conflict: {
|
|
37
|
-
label: "conflict",
|
|
38
|
-
className: "border-destructive/40 bg-destructive/10 text-destructive",
|
|
39
|
-
Icon: XCircle,
|
|
40
|
-
},
|
|
41
|
-
"project-only": {
|
|
42
|
-
label: "project-only",
|
|
43
|
-
className: "border-slate-500/40 bg-slate-500/10 text-slate-700 dark:text-slate-300",
|
|
44
|
-
Icon: Cloud,
|
|
45
|
-
},
|
|
46
|
-
"chart-only": {
|
|
47
|
-
label: "chart-only",
|
|
48
|
-
className: "border-slate-500/40 bg-slate-500/10 text-slate-700 dark:text-slate-300",
|
|
49
|
-
Icon: FolderOpen,
|
|
50
|
-
},
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
export function SyncStatePill({ syncState, className }: SyncStatePillProps) {
|
|
54
|
-
if (!syncState) return null
|
|
55
|
-
|
|
56
|
-
const { label, className: stateClassName, Icon } = syncStateConfig[syncState]
|
|
57
|
-
|
|
58
|
-
return (
|
|
59
|
-
<Badge variant="outline" className={cn("gap-1 text-xs", stateClassName, className)}>
|
|
60
|
-
<Icon className="w-3 h-3" />
|
|
61
|
-
{label}
|
|
62
|
-
</Badge>
|
|
63
|
-
)
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
interface ProjectSourceBadgeProps {
|
|
67
|
-
entity: EntityRecord
|
|
68
|
-
className?: string
|
|
69
|
-
compact?: boolean
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
export function ProjectSourceBadge({ entity, className, compact = false }: ProjectSourceBadgeProps) {
|
|
73
|
-
const [selectedLensIndex, setSelectedLensIndex] = useState(0)
|
|
74
|
-
const provenance = getGithubEntityProvenance(entity)
|
|
75
|
-
|
|
76
|
-
if (!provenance.hasGithubMetadata) return null
|
|
77
|
-
|
|
78
|
-
const projectItems = provenance.projectItems
|
|
79
|
-
const activeLensIndex = projectItems.length > 0 ? Math.min(selectedLensIndex, projectItems.length - 1) : 0
|
|
80
|
-
const activeProjectItem = projectItems[activeLensIndex]
|
|
81
|
-
const projectRef = formatProjectItemRef(activeProjectItem)
|
|
82
|
-
const issueRef = formatGithubIssueRef(provenance.issue)
|
|
83
|
-
const projectTitle = activeProjectItem?.projectTitle
|
|
84
|
-
const fieldEntries = getProjectFieldEntries(entity, activeProjectItem)
|
|
85
|
-
const sourceLabel = getSourceLabel(provenance.source?.system, provenance.source?.toolName, provenance.lastSyncedAt)
|
|
86
|
-
const projectTooltip = [
|
|
87
|
-
projectTitle,
|
|
88
|
-
activeProjectItem?.itemId && `item ${activeProjectItem.itemId}`,
|
|
89
|
-
activeProjectItem?.projectId && `project ${activeProjectItem.projectId}`,
|
|
90
|
-
activeProjectItem?.url,
|
|
91
|
-
]
|
|
92
|
-
.filter(Boolean)
|
|
93
|
-
.join(" · ")
|
|
94
|
-
|
|
95
|
-
return (
|
|
96
|
-
<div
|
|
97
|
-
className={cn(
|
|
98
|
-
"rounded-md border border-indigo-500/20 bg-indigo-500/5 p-3 text-sm",
|
|
99
|
-
compact && "p-2",
|
|
100
|
-
className,
|
|
101
|
-
)}
|
|
102
|
-
>
|
|
103
|
-
<div className="flex flex-wrap items-center gap-2">
|
|
104
|
-
{activeProjectItem?.url ? (
|
|
105
|
-
<a href={activeProjectItem.url} target="_blank" rel="noreferrer" title={projectTooltip}>
|
|
106
|
-
<ProjectBadge label={projectRef ?? "Project bridge"} />
|
|
107
|
-
</a>
|
|
108
|
-
) : (
|
|
109
|
-
<ProjectBadge label={projectRef ?? "Project bridge"} title={projectTooltip} />
|
|
110
|
-
)}
|
|
111
|
-
|
|
112
|
-
{provenance.issue &&
|
|
113
|
-
(provenance.issue.url ? (
|
|
114
|
-
<a href={provenance.issue.url} target="_blank" rel="noreferrer" title={issueRef}>
|
|
115
|
-
<Badge variant="outline" className="gap-1 text-xs">
|
|
116
|
-
<Github className="w-3 h-3" />#{provenance.issue.number}
|
|
117
|
-
<ExternalLink className="w-3 h-3" />
|
|
118
|
-
</Badge>
|
|
119
|
-
</a>
|
|
120
|
-
) : (
|
|
121
|
-
<Badge variant="outline" className="gap-1 text-xs" title={issueRef}>
|
|
122
|
-
<Github className="w-3 h-3" />#{provenance.issue.number}
|
|
123
|
-
</Badge>
|
|
124
|
-
))}
|
|
125
|
-
|
|
126
|
-
<SyncStatePill syncState={provenance.syncState} />
|
|
127
|
-
|
|
128
|
-
{projectItems.length > 1 && (
|
|
129
|
-
<select
|
|
130
|
-
aria-label="Active GitHub project lens"
|
|
131
|
-
value={activeLensIndex}
|
|
132
|
-
onChange={(event) => setSelectedLensIndex(Number(event.target.value))}
|
|
133
|
-
className="h-7 rounded-md border border-border bg-background px-2 text-xs text-foreground"
|
|
134
|
-
>
|
|
135
|
-
{projectItems.map((item, index) => (
|
|
136
|
-
<option key={`${formatProjectItemRef(item) ?? item.itemId ?? "project"}-${index}`} value={index}>
|
|
137
|
-
{item.projectTitle ?? formatProjectItemRef(item) ?? `Project ${index + 1}`}
|
|
138
|
-
</option>
|
|
139
|
-
))}
|
|
140
|
-
</select>
|
|
141
|
-
)}
|
|
142
|
-
</div>
|
|
143
|
-
|
|
144
|
-
{!compact && (projectTitle || sourceLabel || fieldEntries.length > 0) && (
|
|
145
|
-
<div className="mt-2 space-y-2 text-xs text-muted-foreground">
|
|
146
|
-
{(projectTitle || sourceLabel) && (
|
|
147
|
-
<div className="flex flex-wrap items-center gap-2">
|
|
148
|
-
{projectTitle && <span className="font-medium text-foreground">{projectTitle}</span>}
|
|
149
|
-
{sourceLabel && <span>{sourceLabel}</span>}
|
|
150
|
-
</div>
|
|
151
|
-
)}
|
|
152
|
-
|
|
153
|
-
{fieldEntries.length > 0 && (
|
|
154
|
-
<div className="flex flex-wrap gap-1.5">
|
|
155
|
-
{fieldEntries.map((field) => (
|
|
156
|
-
<span
|
|
157
|
-
key={field.key}
|
|
158
|
-
className="inline-flex max-w-full items-center gap-1 rounded-md border border-border bg-background px-2 py-1"
|
|
159
|
-
title={`${field.label}: ${field.value}`}
|
|
160
|
-
>
|
|
161
|
-
<span className="font-medium text-foreground">{field.label}</span>
|
|
162
|
-
<span className="max-w-[18rem] truncate">{field.value}</span>
|
|
163
|
-
</span>
|
|
164
|
-
))}
|
|
165
|
-
</div>
|
|
166
|
-
)}
|
|
167
|
-
</div>
|
|
168
|
-
)}
|
|
169
|
-
</div>
|
|
170
|
-
)
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
function ProjectBadge({ label, title }: { label: string; title?: string }) {
|
|
174
|
-
return (
|
|
175
|
-
<Badge className="gap-1 bg-indigo-600 text-white hover:bg-indigo-600/90" title={title}>
|
|
176
|
-
<GitBranch className="w-3 h-3" />
|
|
177
|
-
Project: {label}
|
|
178
|
-
</Badge>
|
|
179
|
-
)
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
interface ActionStepIssueBadgeProps {
|
|
183
|
-
action: EntityRecord
|
|
184
|
-
className?: string
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
export function ActionStepIssueBadge({ action, className }: ActionStepIssueBadgeProps) {
|
|
188
|
-
const issue = getGithubIssueRef(action)
|
|
189
|
-
const syncState = getGithubSyncState(action)
|
|
190
|
-
const issueRef = formatGithubIssueRef(issue)
|
|
191
|
-
|
|
192
|
-
if (!issue && !syncState) return null
|
|
193
|
-
|
|
194
|
-
return (
|
|
195
|
-
<span className={cn("inline-flex flex-wrap items-center gap-1", className)}>
|
|
196
|
-
{issue &&
|
|
197
|
-
(issue.url ? (
|
|
198
|
-
<a href={issue.url} target="_blank" rel="noreferrer" title={issueRef}>
|
|
199
|
-
<Badge variant="outline" className="gap-1 text-xs">
|
|
200
|
-
<Github className="w-3 h-3" />#{issue.number}
|
|
201
|
-
<ExternalLink className="w-3 h-3" />
|
|
202
|
-
</Badge>
|
|
203
|
-
</a>
|
|
204
|
-
) : (
|
|
205
|
-
<Badge variant="outline" className="gap-1 text-xs" title={issueRef}>
|
|
206
|
-
<Github className="w-3 h-3" />#{issue.number}
|
|
207
|
-
</Badge>
|
|
208
|
-
))}
|
|
209
|
-
<SyncStatePill syncState={syncState} />
|
|
210
|
-
</span>
|
|
211
|
-
)
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
function getSourceLabel(system?: string, toolName?: string, lastSyncedAt?: string): string | undefined {
|
|
215
|
-
const parts = [system, toolName, formatDateTime(lastSyncedAt)].filter(Boolean)
|
|
216
|
-
return parts.length > 0 ? parts.join(" · ") : undefined
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
function formatDateTime(value?: string): string | undefined {
|
|
220
|
-
if (!value) return undefined
|
|
221
|
-
|
|
222
|
-
const date = new Date(value)
|
|
223
|
-
if (Number.isNaN(date.getTime())) return value
|
|
224
|
-
|
|
225
|
-
return date.toLocaleString()
|
|
226
|
-
}
|