@cliangdev/flux-plugin 0.2.0-dev.2b9c207 → 0.2.0-dev.359209a
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/bin/install.cjs +22 -1
- package/commands/dashboard.md +29 -0
- package/manifest.json +2 -1
- package/package.json +4 -2
- package/skills/ux-ui-design/SKILL.md +346 -0
- package/skills/ux-ui-design/references/design-tokens.md +359 -0
- package/src/dashboard/__tests__/api.test.ts +211 -0
- package/src/dashboard/browser.ts +35 -0
- package/src/dashboard/public/app.js +869 -0
- package/src/dashboard/public/index.html +90 -0
- package/src/dashboard/public/styles.css +807 -0
- package/src/dashboard/public/vendor/highlight.css +10 -0
- package/src/dashboard/public/vendor/highlight.min.js +8422 -0
- package/src/dashboard/public/vendor/marked.min.js +2210 -0
- package/src/dashboard/server.ts +296 -0
- package/src/dashboard/watchers.ts +83 -0
- package/src/server/adapters/local-adapter.ts +8 -2
- package/src/server/db/__tests__/queries.test.ts +2 -1
- package/src/server/tools/__tests__/crud.test.ts +2 -1
- package/src/server/tools/__tests__/mcp-interface.test.ts +2 -1
- package/src/server/tools/__tests__/query.test.ts +4 -2
- package/src/server/tools/__tests__/z-configure-linear.test.ts +1 -1
- package/src/server/tools/__tests__/z-get-linear-url.test.ts +1 -1
- package/src/status-line/__tests__/status-line.test.ts +1 -1
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Flux Dashboard Server
|
|
3
|
+
*
|
|
4
|
+
* HTTP + WebSocket server for the Flux Dashboard.
|
|
5
|
+
* Serves static files and API endpoints for viewing PRD/Epic/Task data.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { getAdapter } from "../server/adapters/index.js";
|
|
9
|
+
import type { Epic, Prd, Task } from "../server/adapters/types.js";
|
|
10
|
+
import { initDb } from "../server/db/index.js";
|
|
11
|
+
import { openBrowser } from "./browser.js";
|
|
12
|
+
import { startWatchers, stopWatchers } from "./watchers.js";
|
|
13
|
+
|
|
14
|
+
const DEFAULT_PORT = 3333;
|
|
15
|
+
const MAX_PORT_ATTEMPTS = 10;
|
|
16
|
+
|
|
17
|
+
interface DashboardServer {
|
|
18
|
+
server: ReturnType<typeof Bun.serve>;
|
|
19
|
+
port: number;
|
|
20
|
+
stop: () => void;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function getPort(): number {
|
|
24
|
+
const envPort = process.env.FLUX_DASHBOARD_PORT;
|
|
25
|
+
if (envPort) {
|
|
26
|
+
const parsed = Number.parseInt(envPort, 10);
|
|
27
|
+
if (!Number.isNaN(parsed) && parsed > 0 && parsed < 65536) {
|
|
28
|
+
return parsed;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return DEFAULT_PORT;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async function findAvailablePort(startPort: number): Promise<number> {
|
|
35
|
+
for (let port = startPort; port < startPort + MAX_PORT_ATTEMPTS; port++) {
|
|
36
|
+
try {
|
|
37
|
+
const testServer = Bun.serve({
|
|
38
|
+
port,
|
|
39
|
+
fetch: () => new Response("test"),
|
|
40
|
+
});
|
|
41
|
+
testServer.stop();
|
|
42
|
+
return port;
|
|
43
|
+
} catch {}
|
|
44
|
+
}
|
|
45
|
+
throw new Error(
|
|
46
|
+
`No available port found between ${startPort} and ${startPort + MAX_PORT_ATTEMPTS - 1}`,
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function getContentType(path: string): string {
|
|
51
|
+
if (path.endsWith(".html")) return "text/html";
|
|
52
|
+
if (path.endsWith(".css")) return "text/css";
|
|
53
|
+
if (path.endsWith(".js")) return "application/javascript";
|
|
54
|
+
if (path.endsWith(".json")) return "application/json";
|
|
55
|
+
if (path.endsWith(".svg")) return "image/svg+xml";
|
|
56
|
+
return "application/octet-stream";
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export async function startDashboard(): Promise<DashboardServer> {
|
|
60
|
+
const preferredPort = getPort();
|
|
61
|
+
const port = await findAvailablePort(preferredPort);
|
|
62
|
+
|
|
63
|
+
const publicDir = new URL("./public/", import.meta.url).pathname;
|
|
64
|
+
|
|
65
|
+
const server = Bun.serve({
|
|
66
|
+
port,
|
|
67
|
+
|
|
68
|
+
async fetch(req, server) {
|
|
69
|
+
const url = new URL(req.url);
|
|
70
|
+
const path = url.pathname;
|
|
71
|
+
|
|
72
|
+
// WebSocket upgrade
|
|
73
|
+
if (path === "/ws") {
|
|
74
|
+
const upgraded = server.upgrade(req);
|
|
75
|
+
return upgraded
|
|
76
|
+
? undefined
|
|
77
|
+
: new Response("WebSocket upgrade failed", { status: 400 });
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// API endpoints
|
|
81
|
+
if (path.startsWith("/api/")) {
|
|
82
|
+
return handleApiRequest(path, url);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Static files
|
|
86
|
+
const filePath = path === "/" ? "/index.html" : path;
|
|
87
|
+
const fullPath = `${publicDir}${filePath}`;
|
|
88
|
+
|
|
89
|
+
try {
|
|
90
|
+
const file = Bun.file(fullPath);
|
|
91
|
+
if (await file.exists()) {
|
|
92
|
+
return new Response(file, {
|
|
93
|
+
headers: { "Content-Type": getContentType(fullPath) },
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
} catch {}
|
|
97
|
+
|
|
98
|
+
return new Response("Not found", { status: 404 });
|
|
99
|
+
},
|
|
100
|
+
|
|
101
|
+
websocket: {
|
|
102
|
+
open(ws) {
|
|
103
|
+
ws.subscribe("updates");
|
|
104
|
+
},
|
|
105
|
+
message(_ws, _message) {
|
|
106
|
+
// Client messages not needed for read-only dashboard
|
|
107
|
+
},
|
|
108
|
+
close(ws) {
|
|
109
|
+
ws.unsubscribe("updates");
|
|
110
|
+
},
|
|
111
|
+
},
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
// Start file watchers
|
|
115
|
+
startWatchers(server);
|
|
116
|
+
|
|
117
|
+
// Graceful shutdown
|
|
118
|
+
const cleanup = () => {
|
|
119
|
+
console.log("\nShutting down dashboard...");
|
|
120
|
+
stopWatchers();
|
|
121
|
+
server.stop();
|
|
122
|
+
process.exit(0);
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
process.on("SIGINT", cleanup);
|
|
126
|
+
process.on("SIGTERM", cleanup);
|
|
127
|
+
|
|
128
|
+
return {
|
|
129
|
+
server,
|
|
130
|
+
port,
|
|
131
|
+
stop: () => {
|
|
132
|
+
stopWatchers();
|
|
133
|
+
server.stop();
|
|
134
|
+
},
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
interface TreePrd extends Prd {
|
|
139
|
+
epics: TreeEpic[];
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
interface TreeEpic extends Epic {
|
|
143
|
+
tasks: Task[];
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
async function handleApiRequest(path: string, _url: URL): Promise<Response> {
|
|
147
|
+
try {
|
|
148
|
+
const adapter = getAdapter();
|
|
149
|
+
|
|
150
|
+
// GET /api/tree
|
|
151
|
+
if (path === "/api/tree") {
|
|
152
|
+
const prds = await adapter.listPrds({}, { limit: 100, offset: 0 });
|
|
153
|
+
const tree: TreePrd[] = await Promise.all(
|
|
154
|
+
prds.items.map(async (prd) => {
|
|
155
|
+
const epics = await adapter.listEpics(
|
|
156
|
+
{ prdRef: prd.ref },
|
|
157
|
+
{ limit: 100, offset: 0 },
|
|
158
|
+
);
|
|
159
|
+
const epicsWithTasks: TreeEpic[] = await Promise.all(
|
|
160
|
+
epics.items.map(async (epic) => {
|
|
161
|
+
const tasks = await adapter.listTasks(
|
|
162
|
+
{ epicRef: epic.ref },
|
|
163
|
+
{ limit: 100, offset: 0 },
|
|
164
|
+
);
|
|
165
|
+
return { ...epic, tasks: tasks.items };
|
|
166
|
+
}),
|
|
167
|
+
);
|
|
168
|
+
return { ...prd, epics: epicsWithTasks };
|
|
169
|
+
}),
|
|
170
|
+
);
|
|
171
|
+
return Response.json(tree);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// GET /api/prd/:ref
|
|
175
|
+
if (path.startsWith("/api/prd/")) {
|
|
176
|
+
const ref = path.slice("/api/prd/".length);
|
|
177
|
+
const prd = await adapter.getPrd(ref);
|
|
178
|
+
if (!prd) {
|
|
179
|
+
return Response.json({ error: "PRD not found" }, { status: 404 });
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
let markdown = prd.description || "";
|
|
183
|
+
if (prd.folderPath) {
|
|
184
|
+
try {
|
|
185
|
+
const prdPath = `${prd.folderPath}/prd.md`;
|
|
186
|
+
const file = Bun.file(prdPath);
|
|
187
|
+
if (await file.exists()) {
|
|
188
|
+
markdown = await file.text();
|
|
189
|
+
}
|
|
190
|
+
} catch {}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
return Response.json({ ...prd, markdown });
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// GET /api/epic/:ref
|
|
197
|
+
if (path.startsWith("/api/epic/")) {
|
|
198
|
+
const ref = path.slice("/api/epic/".length);
|
|
199
|
+
const epic = await adapter.getEpic(ref);
|
|
200
|
+
if (!epic) {
|
|
201
|
+
return Response.json({ error: "Epic not found" }, { status: 404 });
|
|
202
|
+
}
|
|
203
|
+
const criteria = await adapter.getCriteria(ref);
|
|
204
|
+
return Response.json({ ...epic, criteria });
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// GET /api/task/:ref
|
|
208
|
+
if (path.startsWith("/api/task/")) {
|
|
209
|
+
const ref = path.slice("/api/task/".length);
|
|
210
|
+
const task = await adapter.getTask(ref);
|
|
211
|
+
if (!task) {
|
|
212
|
+
return Response.json({ error: "Task not found" }, { status: 404 });
|
|
213
|
+
}
|
|
214
|
+
const criteria = await adapter.getCriteria(ref);
|
|
215
|
+
const deps = await adapter.getDependencies(ref);
|
|
216
|
+
return Response.json({ ...task, criteria, dependencies: deps });
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// GET /api/tags
|
|
220
|
+
if (path === "/api/tags") {
|
|
221
|
+
const prds = await adapter.listPrds({}, { limit: 1000, offset: 0 });
|
|
222
|
+
const tagCounts = new Map<string, number>();
|
|
223
|
+
let total = 0;
|
|
224
|
+
|
|
225
|
+
for (const prd of prds.items) {
|
|
226
|
+
total++;
|
|
227
|
+
if (prd.tag) {
|
|
228
|
+
tagCounts.set(prd.tag, (tagCounts.get(prd.tag) || 0) + 1);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
const tags = [
|
|
233
|
+
{ tag: "All", count: total },
|
|
234
|
+
...Array.from(tagCounts.entries())
|
|
235
|
+
.map(([tag, count]) => ({ tag, count }))
|
|
236
|
+
.sort((a, b) => a.tag.localeCompare(b.tag)),
|
|
237
|
+
];
|
|
238
|
+
|
|
239
|
+
return Response.json(tags);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// GET /api/dependencies
|
|
243
|
+
if (path === "/api/dependencies") {
|
|
244
|
+
const edges: Array<{ from: string; to: string; type: string }> = [];
|
|
245
|
+
|
|
246
|
+
const prds = await adapter.listPrds({}, { limit: 1000, offset: 0 });
|
|
247
|
+
for (const prd of prds.items) {
|
|
248
|
+
const deps = await adapter.getDependencies(prd.ref);
|
|
249
|
+
for (const depRef of deps) {
|
|
250
|
+
edges.push({ from: prd.ref, to: depRef, type: "prd" });
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
const epics = await adapter.listEpics({}, { limit: 1000, offset: 0 });
|
|
255
|
+
for (const epic of epics.items) {
|
|
256
|
+
const deps = await adapter.getDependencies(epic.ref);
|
|
257
|
+
for (const depRef of deps) {
|
|
258
|
+
edges.push({ from: epic.ref, to: depRef, type: "epic" });
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
const tasks = await adapter.listTasks({}, { limit: 1000, offset: 0 });
|
|
263
|
+
for (const task of tasks.items) {
|
|
264
|
+
const deps = await adapter.getDependencies(task.ref);
|
|
265
|
+
for (const depRef of deps) {
|
|
266
|
+
edges.push({ from: task.ref, to: depRef, type: "task" });
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
return Response.json({ edges });
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
return Response.json({ error: "Unknown endpoint" }, { status: 404 });
|
|
274
|
+
} catch (error) {
|
|
275
|
+
console.error("API error:", error);
|
|
276
|
+
return Response.json(
|
|
277
|
+
{ error: error instanceof Error ? error.message : "Internal error" },
|
|
278
|
+
{ status: 500 },
|
|
279
|
+
);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// Main entry point
|
|
284
|
+
if (import.meta.main) {
|
|
285
|
+
try {
|
|
286
|
+
// Initialize local database (no-op if using external adapter like Linear)
|
|
287
|
+
initDb();
|
|
288
|
+
const { port } = await startDashboard();
|
|
289
|
+
const url = `http://localhost:${port}`;
|
|
290
|
+
console.log(`\n Flux Dashboard running at ${url}\n`);
|
|
291
|
+
await openBrowser(url);
|
|
292
|
+
} catch (error) {
|
|
293
|
+
console.error("Failed to start dashboard:", error);
|
|
294
|
+
process.exit(1);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* File Watchers for Real-time Updates
|
|
3
|
+
*
|
|
4
|
+
* Watches .flux/flux.db and .flux/prds/ for changes and broadcasts
|
|
5
|
+
* updates to connected WebSocket clients.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { type FSWatcher, watch } from "node:fs";
|
|
9
|
+
import { resolve } from "node:path";
|
|
10
|
+
|
|
11
|
+
let dbWatcher: FSWatcher | null = null;
|
|
12
|
+
let prdsWatcher: FSWatcher | null = null;
|
|
13
|
+
let debounceTimer: Timer | null = null;
|
|
14
|
+
|
|
15
|
+
const DEBOUNCE_MS = 500;
|
|
16
|
+
|
|
17
|
+
export function startWatchers(server: ReturnType<typeof Bun.serve>): void {
|
|
18
|
+
const projectRoot = process.env.FLUX_PROJECT_ROOT || process.cwd();
|
|
19
|
+
const fluxDir = resolve(projectRoot, ".flux");
|
|
20
|
+
const dbPath = resolve(fluxDir, "flux.db");
|
|
21
|
+
const prdsDir = resolve(fluxDir, "prds");
|
|
22
|
+
|
|
23
|
+
const broadcast = (type: "db" | "file", path?: string) => {
|
|
24
|
+
if (debounceTimer) {
|
|
25
|
+
clearTimeout(debounceTimer);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
debounceTimer = setTimeout(() => {
|
|
29
|
+
server.publish(
|
|
30
|
+
"updates",
|
|
31
|
+
JSON.stringify({
|
|
32
|
+
type: "update",
|
|
33
|
+
source: type,
|
|
34
|
+
path,
|
|
35
|
+
timestamp: Date.now(),
|
|
36
|
+
}),
|
|
37
|
+
);
|
|
38
|
+
}, DEBOUNCE_MS);
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
// Watch database file
|
|
42
|
+
try {
|
|
43
|
+
dbWatcher = watch(dbPath, (eventType) => {
|
|
44
|
+
if (eventType === "change") {
|
|
45
|
+
broadcast("db");
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
} catch (error) {
|
|
49
|
+
console.warn("Could not watch database file:", error);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Watch PRDs directory recursively
|
|
53
|
+
try {
|
|
54
|
+
prdsWatcher = watch(
|
|
55
|
+
prdsDir,
|
|
56
|
+
{ recursive: true },
|
|
57
|
+
(_eventType, filename) => {
|
|
58
|
+
if (filename?.endsWith(".md")) {
|
|
59
|
+
broadcast("file", filename);
|
|
60
|
+
}
|
|
61
|
+
},
|
|
62
|
+
);
|
|
63
|
+
} catch (error) {
|
|
64
|
+
console.warn("Could not watch PRDs directory:", error);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function stopWatchers(): void {
|
|
69
|
+
if (debounceTimer) {
|
|
70
|
+
clearTimeout(debounceTimer);
|
|
71
|
+
debounceTimer = null;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (dbWatcher) {
|
|
75
|
+
dbWatcher.close();
|
|
76
|
+
dbWatcher = null;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (prdsWatcher) {
|
|
80
|
+
prdsWatcher.close();
|
|
81
|
+
prdsWatcher = null;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
rmSync,
|
|
13
13
|
writeFileSync,
|
|
14
14
|
} from "node:fs";
|
|
15
|
-
import { join } from "node:path";
|
|
15
|
+
import { isAbsolute, join } from "node:path";
|
|
16
16
|
import { config } from "../config.js";
|
|
17
17
|
import {
|
|
18
18
|
count,
|
|
@@ -108,6 +108,12 @@ interface ProjectRow {
|
|
|
108
108
|
// =============================================================================
|
|
109
109
|
|
|
110
110
|
function toPrd(row: PrdRow): Prd {
|
|
111
|
+
// Normalize folderPath to absolute path for consistency
|
|
112
|
+
let folderPath = row.folder_path ?? undefined;
|
|
113
|
+
if (folderPath && !isAbsolute(folderPath)) {
|
|
114
|
+
folderPath = join(config.projectRoot, folderPath);
|
|
115
|
+
}
|
|
116
|
+
|
|
111
117
|
return {
|
|
112
118
|
id: row.id,
|
|
113
119
|
projectId: row.project_id,
|
|
@@ -116,7 +122,7 @@ function toPrd(row: PrdRow): Prd {
|
|
|
116
122
|
description: row.description ?? undefined,
|
|
117
123
|
status: row.status as Prd["status"],
|
|
118
124
|
tag: row.tag ?? undefined,
|
|
119
|
-
folderPath
|
|
125
|
+
folderPath,
|
|
120
126
|
createdAt: row.created_at,
|
|
121
127
|
updatedAt: row.updated_at,
|
|
122
128
|
};
|
|
@@ -3,7 +3,7 @@ import { existsSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
|
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
|
|
5
5
|
// Set up test environment BEFORE any imports
|
|
6
|
-
const TEST_DIR = `/tmp/flux-test-db-${Date.now()}`;
|
|
6
|
+
const TEST_DIR = `/tmp/flux-test-db-${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
7
7
|
const FLUX_DIR = join(TEST_DIR, ".flux");
|
|
8
8
|
process.env.FLUX_PROJECT_ROOT = TEST_DIR;
|
|
9
9
|
|
|
@@ -28,6 +28,7 @@ describe("Database Queries", () => {
|
|
|
28
28
|
let projectId: string;
|
|
29
29
|
|
|
30
30
|
beforeEach(() => {
|
|
31
|
+
closeDb();
|
|
31
32
|
config.clearCache();
|
|
32
33
|
process.env.FLUX_PROJECT_ROOT = TEST_DIR;
|
|
33
34
|
|
|
@@ -3,7 +3,7 @@ import { existsSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
|
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
|
|
5
5
|
// Set up test environment BEFORE any imports
|
|
6
|
-
const TEST_DIR = `/tmp/flux-test-${Date.now()}`;
|
|
6
|
+
const TEST_DIR = `/tmp/flux-test-${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
7
7
|
const FLUX_DIR = join(TEST_DIR, ".flux");
|
|
8
8
|
process.env.FLUX_PROJECT_ROOT = TEST_DIR;
|
|
9
9
|
|
|
@@ -22,6 +22,7 @@ import { updateStatusTool } from "../update-status.js";
|
|
|
22
22
|
|
|
23
23
|
describe("CRUD MCP Tools", () => {
|
|
24
24
|
beforeEach(() => {
|
|
25
|
+
closeDb();
|
|
25
26
|
config.clearCache();
|
|
26
27
|
clearAdapterCache();
|
|
27
28
|
process.env.FLUX_PROJECT_ROOT = TEST_DIR;
|
|
@@ -3,7 +3,7 @@ import { existsSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
|
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
|
|
5
5
|
// Set up test environment BEFORE any imports
|
|
6
|
-
const TEST_DIR = `/tmp/flux-test-mcp-${Date.now()}`;
|
|
6
|
+
const TEST_DIR = `/tmp/flux-test-mcp-${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
7
7
|
const FLUX_DIR = join(TEST_DIR, ".flux");
|
|
8
8
|
process.env.FLUX_PROJECT_ROOT = TEST_DIR;
|
|
9
9
|
|
|
@@ -19,6 +19,7 @@ import {
|
|
|
19
19
|
|
|
20
20
|
describe("MCP Interface", () => {
|
|
21
21
|
beforeEach(() => {
|
|
22
|
+
closeDb();
|
|
22
23
|
config.clearCache();
|
|
23
24
|
process.env.FLUX_PROJECT_ROOT = TEST_DIR;
|
|
24
25
|
|
|
@@ -3,7 +3,7 @@ import { existsSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
|
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
|
|
5
5
|
// Set up test environment BEFORE any imports
|
|
6
|
-
const TEST_DIR = `/tmp/flux-test-query-${Date.now()}`;
|
|
6
|
+
const TEST_DIR = `/tmp/flux-test-query-${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
7
7
|
const FLUX_DIR = join(TEST_DIR, ".flux");
|
|
8
8
|
process.env.FLUX_PROJECT_ROOT = TEST_DIR;
|
|
9
9
|
|
|
@@ -22,6 +22,7 @@ import { queryEntitiesTool } from "../query-entities.js";
|
|
|
22
22
|
|
|
23
23
|
describe("Query MCP Tools", () => {
|
|
24
24
|
beforeEach(() => {
|
|
25
|
+
closeDb();
|
|
25
26
|
config.clearCache();
|
|
26
27
|
clearAdapterCache();
|
|
27
28
|
process.env.FLUX_PROJECT_ROOT = TEST_DIR;
|
|
@@ -341,9 +342,10 @@ describe("Query MCP Tools", () => {
|
|
|
341
342
|
});
|
|
342
343
|
|
|
343
344
|
describe("init_project", () => {
|
|
344
|
-
const INIT_TEST_DIR = `/tmp/flux-init-test-${Date.now()}`;
|
|
345
|
+
const INIT_TEST_DIR = `/tmp/flux-init-test-${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
345
346
|
|
|
346
347
|
beforeEach(() => {
|
|
348
|
+
closeDb();
|
|
347
349
|
config.clearCache();
|
|
348
350
|
clearAdapterCache();
|
|
349
351
|
process.env.FLUX_PROJECT_ROOT = INIT_TEST_DIR;
|
|
@@ -2,7 +2,7 @@ import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test";
|
|
|
2
2
|
import { existsSync, mkdirSync, readFileSync, rmSync } from "node:fs";
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
|
|
5
|
-
const TEST_DIR = `/tmp/flux-test-${Date.now()}`;
|
|
5
|
+
const TEST_DIR = `/tmp/flux-test-${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
6
6
|
const FLUX_DIR = join(TEST_DIR, ".flux");
|
|
7
7
|
process.env.FLUX_PROJECT_ROOT = TEST_DIR;
|
|
8
8
|
|
|
@@ -3,7 +3,7 @@ import { existsSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
|
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
|
|
5
5
|
// Set up test environment BEFORE any imports
|
|
6
|
-
const TEST_DIR = `/tmp/flux-test-${Date.now()}`;
|
|
6
|
+
const TEST_DIR = `/tmp/flux-test-${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
7
7
|
const FLUX_DIR = join(TEST_DIR, ".flux");
|
|
8
8
|
process.env.FLUX_PROJECT_ROOT = TEST_DIR;
|
|
9
9
|
|
|
@@ -31,7 +31,7 @@ function setupTestProject(testDir: string, projectName = "test-project") {
|
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
describe("status-line script", () => {
|
|
34
|
-
const TEST_DIR = `/tmp/flux-status-line-test-${Date.now()}`;
|
|
34
|
+
const TEST_DIR = `/tmp/flux-status-line-test-${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
35
35
|
|
|
36
36
|
beforeEach(() => {
|
|
37
37
|
if (existsSync(TEST_DIR)) {
|