@cliangdev/flux-plugin 0.1.0-dev.588ae42 → 0.2.0-dev.4f12f3f
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/commands/breakdown.md +3 -3
- package/commands/flux.md +92 -12
- package/commands/implement.md +1 -2
- package/commands/linear.md +6 -5
- package/commands/prd.md +3 -3
- package/package.json +1 -1
- package/skills/flux-orchestrator/SKILL.md +2 -2
- package/skills/prd-template/SKILL.md +1 -1
- package/src/server/index.ts +0 -2
- package/src/server/tools/__tests__/mcp-interface.test.ts +98 -8
- package/src/server/tools/__tests__/query.test.ts +0 -19
- package/src/server/tools/index.ts +53 -9
- package/src/server/tools/init-project.ts +1 -1
- package/src/server/tools/get-project-context.ts +0 -33
package/commands/breakdown.md
CHANGED
|
@@ -16,10 +16,10 @@ Check if arguments were provided:
|
|
|
16
16
|
|
|
17
17
|
## Pre-checks
|
|
18
18
|
|
|
19
|
-
1.
|
|
20
|
-
- If
|
|
19
|
+
1. If no ref provided, call `query_entities` with type=prd, status=APPROVED
|
|
20
|
+
- If error with `code: "PROJECT_NOT_INITIALIZED"`, tell user: "Run `/flux` first to initialize the project." and exit.
|
|
21
21
|
|
|
22
|
-
2. If
|
|
22
|
+
2. If query successful but no approved PRDs found:
|
|
23
23
|
- If no approved PRDs, tell user: "No approved PRDs found. Approve a PRD first or run `/flux:prd` to create one."
|
|
24
24
|
- If multiple approved PRDs, use AskUserQuestion to let user select which one
|
|
25
25
|
|
package/commands/flux.md
CHANGED
|
@@ -6,27 +6,78 @@ allowed-tools: mcp__plugin_flux_flux__*, AskUserQuestion, Read, Write
|
|
|
6
6
|
|
|
7
7
|
# Flux Command
|
|
8
8
|
|
|
9
|
-
You are the Flux orchestrator
|
|
9
|
+
You are the Flux orchestrator - the main entry point for all Flux operations. Your job is to:
|
|
10
|
+
1. Detect project state and guide users to the appropriate next action
|
|
11
|
+
2. Route to specialized commands based on user input
|
|
12
|
+
3. Provide intelligent suggestions based on workflow state
|
|
13
|
+
|
|
14
|
+
## Available Commands
|
|
15
|
+
|
|
16
|
+
| Command | Description |
|
|
17
|
+
|---------|-------------|
|
|
18
|
+
| `/flux` | Show project status and suggest next action |
|
|
19
|
+
| `/flux:prd` | Create or refine PRDs through guided interview |
|
|
20
|
+
| `/flux:breakdown` | Break approved PRD into dependency-ordered epics and tasks |
|
|
21
|
+
| `/flux:implement` | Implement tasks with TDD workflow using specialized coding agents |
|
|
22
|
+
| `/flux:linear` | Connect Flux project to Linear for issue tracking |
|
|
23
|
+
|
|
24
|
+
## Subcommand Routing
|
|
25
|
+
|
|
26
|
+
When user provides arguments, route to the appropriate command:
|
|
27
|
+
|
|
28
|
+
| User Input | Action |
|
|
29
|
+
|------------|--------|
|
|
30
|
+
| `/flux` | Show status (see Main Flow) |
|
|
31
|
+
| `/flux version` | Call `get_version` and display result |
|
|
32
|
+
| `/flux status` | Call `render_status` with `{view: "full"}` |
|
|
33
|
+
| `/flux prd` or `/flux prd ...` | Delegate to `/flux:prd` with any additional args |
|
|
34
|
+
| `/flux breakdown` or `/flux breakdown ...` | Delegate to `/flux:breakdown` with any additional args |
|
|
35
|
+
| `/flux implement` or `/flux implement ...` | Delegate to `/flux:implement` with any additional args |
|
|
36
|
+
| `/flux linear` | Delegate to `/flux:linear` |
|
|
37
|
+
| `/flux help` | Show available commands and their purposes |
|
|
38
|
+
|
|
39
|
+
## Available MCP Tools
|
|
40
|
+
|
|
41
|
+
These tools are available for programmatic access:
|
|
42
|
+
|
|
43
|
+
**Entity Management:**
|
|
44
|
+
- `create_prd`, `create_epic`, `create_task` - Create entities
|
|
45
|
+
- `update_entity`, `update_status`, `delete_entity` - Modify entities
|
|
46
|
+
- `get_entity`, `query_entities` - Retrieve entities
|
|
47
|
+
|
|
48
|
+
**Project:**
|
|
49
|
+
- `init_project` - Initialize new Flux project
|
|
50
|
+
- `get_stats` - Get entity counts by status
|
|
51
|
+
- `get_version` - Get plugin version
|
|
52
|
+
- `render_status` - Visual project status with progress bars
|
|
53
|
+
|
|
54
|
+
**Relationships:**
|
|
55
|
+
- `add_dependency`, `remove_dependency` - Task/epic dependencies
|
|
56
|
+
- `add_criteria`, `mark_criteria_met` - Acceptance criteria
|
|
57
|
+
|
|
58
|
+
**Integration:**
|
|
59
|
+
- `configure_linear` - Connect to Linear (interactive mode supported)
|
|
10
60
|
|
|
11
|
-
##
|
|
61
|
+
## Main Flow
|
|
12
62
|
|
|
13
|
-
|
|
14
|
-
- `/flux linear` - Connect to Linear (delegate to `/flux:linear`)
|
|
63
|
+
### Step 0: Check for Subcommands
|
|
15
64
|
|
|
16
|
-
|
|
65
|
+
First, check if the user provided arguments (e.g., `/flux prd`, `/flux implement FP-T1`).
|
|
66
|
+
If so, route to the appropriate command as described in Subcommand Routing above.
|
|
17
67
|
|
|
18
|
-
### Step 1:
|
|
68
|
+
### Step 1: Check Project State
|
|
19
69
|
|
|
20
|
-
|
|
70
|
+
If no subcommand, call `render_status` with `{view: "summary"}` to show current state.
|
|
21
71
|
|
|
22
|
-
### Step 2: Route Based on
|
|
72
|
+
### Step 2: Route Based on Response
|
|
23
73
|
|
|
24
|
-
**If `
|
|
74
|
+
**If error with `code: "PROJECT_NOT_INITIALIZED"`:**
|
|
25
75
|
→ Guide through initialization (see Initialization Flow below)
|
|
26
76
|
|
|
27
|
-
**If
|
|
28
|
-
→
|
|
29
|
-
→
|
|
77
|
+
**If success:**
|
|
78
|
+
→ Display the rendered status
|
|
79
|
+
→ Analyze workflow state and suggest the most appropriate next action (see Workflow States)
|
|
80
|
+
→ If multiple actions are possible, use AskUserQuestion to let user choose
|
|
30
81
|
|
|
31
82
|
## Initialization Flow
|
|
32
83
|
|
|
@@ -148,9 +199,38 @@ When determining actions:
|
|
|
148
199
|
| 50-80% | Suggest action, wait for confirmation |
|
|
149
200
|
| < 50% | Ask clarifying question |
|
|
150
201
|
|
|
202
|
+
## Help Output
|
|
203
|
+
|
|
204
|
+
When user runs `/flux help`, display:
|
|
205
|
+
|
|
206
|
+
```
|
|
207
|
+
Flux - AI-first workflow orchestration
|
|
208
|
+
|
|
209
|
+
Commands:
|
|
210
|
+
/flux Show project status and next action
|
|
211
|
+
/flux:prd Create or refine PRDs
|
|
212
|
+
/flux:breakdown Break PRD into epics and tasks
|
|
213
|
+
/flux:implement Implement tasks with TDD
|
|
214
|
+
/flux:linear Connect to Linear
|
|
215
|
+
|
|
216
|
+
Shortcuts:
|
|
217
|
+
/flux prd Same as /flux:prd
|
|
218
|
+
/flux breakdown Same as /flux:breakdown
|
|
219
|
+
/flux implement Same as /flux:implement
|
|
220
|
+
/flux status Show detailed project status
|
|
221
|
+
/flux version Show plugin version
|
|
222
|
+
|
|
223
|
+
Workflow:
|
|
224
|
+
1. /flux Initialize project (first time)
|
|
225
|
+
2. /flux:prd Create your first PRD
|
|
226
|
+
3. /flux:breakdown Break PRD into tasks
|
|
227
|
+
4. /flux:implement Start coding with TDD
|
|
228
|
+
```
|
|
229
|
+
|
|
151
230
|
## Guidelines
|
|
152
231
|
|
|
153
232
|
- Use `AskUserQuestion` tool for all user choices during initialization
|
|
154
233
|
- Be concise - show status and one clear next action
|
|
155
234
|
- Use `render_status` for visual project overview
|
|
156
235
|
- Apply confidence-based autonomy for decisions
|
|
236
|
+
- When user input matches a subcommand pattern, delegate immediately without calling render_status first
|
package/commands/implement.md
CHANGED
|
@@ -53,8 +53,7 @@ The orchestrator resolves all refs to tasks, builds a dependency-ordered queue,
|
|
|
53
53
|
|
|
54
54
|
## Pre-checks
|
|
55
55
|
|
|
56
|
-
1.
|
|
57
|
-
2. Parse arguments and resolve to tasks:
|
|
56
|
+
1. Parse arguments and resolve to tasks (if any tool returns `PROJECT_NOT_INITIALIZED` error, tell user: "Run `/flux` first to initialize the project." and exit):
|
|
58
57
|
- No args: Query for next PENDING task with no blockers
|
|
59
58
|
- PRD ref(s): Expand to all epics → all tasks
|
|
60
59
|
- Epic ref(s): Expand to all tasks
|
package/commands/linear.md
CHANGED
|
@@ -21,13 +21,14 @@ The `configure_linear` tool supports progressive discovery:
|
|
|
21
21
|
|
|
22
22
|
## Flow
|
|
23
23
|
|
|
24
|
-
### Step 1: Verify Project
|
|
24
|
+
### Step 1: Verify Project & Fetch Teams
|
|
25
25
|
|
|
26
|
-
Call `
|
|
26
|
+
Call `configure_linear` with `{interactive: true}`.
|
|
27
27
|
|
|
28
|
-
- If `
|
|
29
|
-
- If
|
|
30
|
-
-
|
|
28
|
+
- If error with `code: "PROJECT_NOT_INITIALIZED"` → Tell user to run `/flux` first, then exit.
|
|
29
|
+
- If error about Linear API key → Show instructions (see Step 2 error handling).
|
|
30
|
+
- If success with `step: "already_configured"` → Already configured, show info and exit.
|
|
31
|
+
- Otherwise → Continue with team selection (response contains teams list).
|
|
31
32
|
|
|
32
33
|
### Step 2: Fetch Teams
|
|
33
34
|
|
package/commands/prd.md
CHANGED
|
@@ -20,8 +20,8 @@ Check if arguments were provided:
|
|
|
20
20
|
|
|
21
21
|
### Pre-check
|
|
22
22
|
|
|
23
|
-
1. Call `
|
|
24
|
-
- If
|
|
23
|
+
1. Call `query_entities` with `type: "prd"` to verify project is initialized
|
|
24
|
+
- If error with `code: "PROJECT_NOT_INITIALIZED"`, tell user: "Run `/flux` first to initialize the project." and exit.
|
|
25
25
|
|
|
26
26
|
2. Call `get_interview` to check for any in-progress interview
|
|
27
27
|
- If exists, ask: "You have an unfinished interview. Resume it or start fresh?"
|
|
@@ -96,7 +96,7 @@ Use AskUserQuestion to confirm:
|
|
|
96
96
|
|
|
97
97
|
## After Interview Complete
|
|
98
98
|
|
|
99
|
-
The `
|
|
99
|
+
The `create_prd` response includes project context. Use it to determine storage behavior, or call `get_entity` on the created PRD to check the adapter type.
|
|
100
100
|
|
|
101
101
|
### For Local Adapter (`adapter.type === "local"`):
|
|
102
102
|
|
package/package.json
CHANGED
|
@@ -11,11 +11,11 @@ This skill is automatically active when working in a Flux project. It provides c
|
|
|
11
11
|
## Available MCP Tools
|
|
12
12
|
|
|
13
13
|
### Query Tools
|
|
14
|
-
- `get_project_context` - Check if project initialized, get name/vision/adapter type
|
|
15
14
|
- `get_stats` - Get PRD/epic/task counts by status
|
|
16
15
|
- `get_entity` - Fetch entity by ref with optional includes (criteria, tasks, dependencies)
|
|
17
16
|
- `query_entities` - Search entities by type, status, parent ref
|
|
18
17
|
- `render_status` - Get formatted project status with progress bars
|
|
18
|
+
- `get_version` - Get Flux plugin version
|
|
19
19
|
|
|
20
20
|
### Mutation Tools
|
|
21
21
|
- `init_project` - Initialize new .flux/ directory with project.json and database
|
|
@@ -107,7 +107,7 @@ Format: `{PREFIX}-{TYPE}{NUMBER}`
|
|
|
107
107
|
|
|
108
108
|
## Best Practices
|
|
109
109
|
|
|
110
|
-
1. **
|
|
110
|
+
1. **Handle PROJECT_NOT_INITIALIZED** - If any tool returns this error, direct user to run `/flux` to initialize the project
|
|
111
111
|
2. **Use refs, not IDs** - Tools accept `MSA-E1` format
|
|
112
112
|
3. **Use render_status** - For visual project overview
|
|
113
113
|
4. **Validate transitions** - `update_status` enforces valid transitions
|
|
@@ -219,7 +219,7 @@ erDiagram
|
|
|
219
219
|
|
|
220
220
|
## Workflow
|
|
221
221
|
|
|
222
|
-
|
|
222
|
+
After creating a PRD, the response includes adapter information. Use this to determine storage behavior.
|
|
223
223
|
|
|
224
224
|
### Local Adapter (`adapter.type === "local"`)
|
|
225
225
|
|
package/src/server/index.ts
CHANGED
|
@@ -11,7 +11,6 @@ import {
|
|
|
11
11
|
createTaskTool,
|
|
12
12
|
deleteEntityTool,
|
|
13
13
|
getEntityTool,
|
|
14
|
-
getProjectContextTool,
|
|
15
14
|
getStatsTool,
|
|
16
15
|
getVersionTool,
|
|
17
16
|
initProjectTool,
|
|
@@ -47,7 +46,6 @@ const tools: ToolDefinition[] = [
|
|
|
47
46
|
// Query tools
|
|
48
47
|
getEntityTool,
|
|
49
48
|
queryEntitiesTool,
|
|
50
|
-
getProjectContextTool,
|
|
51
49
|
initProjectTool,
|
|
52
50
|
getStatsTool,
|
|
53
51
|
getVersionTool,
|
|
@@ -10,7 +10,12 @@ process.env.FLUX_PROJECT_ROOT = TEST_DIR;
|
|
|
10
10
|
import { z } from "zod";
|
|
11
11
|
import { config } from "../../config.js";
|
|
12
12
|
import { closeDb, initDb } from "../../db/index.js";
|
|
13
|
-
import {
|
|
13
|
+
import {
|
|
14
|
+
createError,
|
|
15
|
+
createProjectNotInitializedError,
|
|
16
|
+
registerTools,
|
|
17
|
+
type ToolDefinition,
|
|
18
|
+
} from "../index.js";
|
|
14
19
|
|
|
15
20
|
describe("MCP Interface", () => {
|
|
16
21
|
beforeEach(() => {
|
|
@@ -44,6 +49,42 @@ describe("MCP Interface", () => {
|
|
|
44
49
|
});
|
|
45
50
|
});
|
|
46
51
|
|
|
52
|
+
describe("createProjectNotInitializedError", () => {
|
|
53
|
+
test("creates error with setup instructions", () => {
|
|
54
|
+
const error = createProjectNotInitializedError("/test/cwd", "/test/root");
|
|
55
|
+
|
|
56
|
+
expect(error.error).toBe(true);
|
|
57
|
+
expect(error.code).toBe("PROJECT_NOT_INITIALIZED");
|
|
58
|
+
expect(error.message).toContain("/test/cwd");
|
|
59
|
+
expect(error.message).toContain("/test/root");
|
|
60
|
+
expect(error.setup.instructions).toBeDefined();
|
|
61
|
+
expect(error.setup.options).toHaveLength(2);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
test("includes command option for interactive setup", () => {
|
|
65
|
+
const error = createProjectNotInitializedError("/cwd", "/root");
|
|
66
|
+
const commandOption = error.setup.options.find(
|
|
67
|
+
(o) => o.method === "command",
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
expect(commandOption).toBeDefined();
|
|
71
|
+
expect(commandOption?.name).toBe("/flux");
|
|
72
|
+
expect(commandOption?.description).toContain("Interactive");
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
test("includes tool option with required params", () => {
|
|
76
|
+
const error = createProjectNotInitializedError("/cwd", "/root");
|
|
77
|
+
const toolOption = error.setup.options.find((o) => o.method === "tool");
|
|
78
|
+
|
|
79
|
+
expect(toolOption).toBeDefined();
|
|
80
|
+
expect(toolOption?.name).toBe("init_project");
|
|
81
|
+
expect(toolOption?.params).toBeDefined();
|
|
82
|
+
expect(toolOption?.params?.name).toBeDefined();
|
|
83
|
+
expect(toolOption?.params?.vision).toBeDefined();
|
|
84
|
+
expect(toolOption?.params?.adapter).toBeDefined();
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
|
|
47
88
|
describe("registerTools", () => {
|
|
48
89
|
test("sets up list tools handler", () => {
|
|
49
90
|
const handlers: Record<string, Function> = {};
|
|
@@ -287,7 +328,7 @@ describe("Project Validation", () => {
|
|
|
287
328
|
}
|
|
288
329
|
});
|
|
289
330
|
|
|
290
|
-
test("returns
|
|
331
|
+
test("returns PROJECT_NOT_INITIALIZED with setup instructions for tools requiring project", async () => {
|
|
291
332
|
const handlers: Record<string, Function> = {};
|
|
292
333
|
const mockServer = {
|
|
293
334
|
setRequestHandler: mock((schema: any, handler: Function) => {
|
|
@@ -317,7 +358,11 @@ describe("Project Validation", () => {
|
|
|
317
358
|
expect(result.isError).toBe(true);
|
|
318
359
|
const parsedError = JSON.parse(result.content[0].text);
|
|
319
360
|
expect(parsedError.error).toBe(true);
|
|
320
|
-
expect(parsedError.code).toBe("
|
|
361
|
+
expect(parsedError.code).toBe("PROJECT_NOT_INITIALIZED");
|
|
362
|
+
expect(parsedError.setup).toBeDefined();
|
|
363
|
+
expect(parsedError.setup.instructions).toBeDefined();
|
|
364
|
+
expect(parsedError.setup.options).toBeDefined();
|
|
365
|
+
expect(parsedError.setup.options.length).toBe(2);
|
|
321
366
|
});
|
|
322
367
|
|
|
323
368
|
test("allows init_project without existing project", async () => {
|
|
@@ -353,7 +398,7 @@ describe("Project Validation", () => {
|
|
|
353
398
|
expect(handlerMock).toHaveBeenCalled();
|
|
354
399
|
});
|
|
355
400
|
|
|
356
|
-
test("allows
|
|
401
|
+
test("allows get_version without existing project", async () => {
|
|
357
402
|
const handlers: Record<string, Function> = {};
|
|
358
403
|
const mockServer = {
|
|
359
404
|
setRequestHandler: mock((schema: any, handler: Function) => {
|
|
@@ -363,11 +408,11 @@ describe("Project Validation", () => {
|
|
|
363
408
|
}),
|
|
364
409
|
};
|
|
365
410
|
|
|
366
|
-
const handlerMock = mock(async () => ({
|
|
411
|
+
const handlerMock = mock(async () => ({ version: "1.0.0" }));
|
|
367
412
|
|
|
368
413
|
const testTool: ToolDefinition = {
|
|
369
|
-
name: "
|
|
370
|
-
description: "Get
|
|
414
|
+
name: "get_version",
|
|
415
|
+
description: "Get version",
|
|
371
416
|
inputSchema: z.object({}),
|
|
372
417
|
handler: handlerMock,
|
|
373
418
|
};
|
|
@@ -377,7 +422,7 @@ describe("Project Validation", () => {
|
|
|
377
422
|
const callHandler = handlers.call;
|
|
378
423
|
const result = await callHandler({
|
|
379
424
|
params: {
|
|
380
|
-
name: "
|
|
425
|
+
name: "get_version",
|
|
381
426
|
arguments: {},
|
|
382
427
|
},
|
|
383
428
|
});
|
|
@@ -385,4 +430,49 @@ describe("Project Validation", () => {
|
|
|
385
430
|
expect(result.isError).toBeUndefined();
|
|
386
431
|
expect(handlerMock).toHaveBeenCalled();
|
|
387
432
|
});
|
|
433
|
+
|
|
434
|
+
test("setup options have correct structure", async () => {
|
|
435
|
+
const handlers: Record<string, Function> = {};
|
|
436
|
+
const mockServer = {
|
|
437
|
+
setRequestHandler: mock((schema: any, handler: Function) => {
|
|
438
|
+
if (schema.shape?.method?.value === "tools/call") {
|
|
439
|
+
handlers.call = handler;
|
|
440
|
+
}
|
|
441
|
+
}),
|
|
442
|
+
};
|
|
443
|
+
|
|
444
|
+
const testTool: ToolDefinition = {
|
|
445
|
+
name: "query_entities",
|
|
446
|
+
description: "Query entities",
|
|
447
|
+
inputSchema: z.object({ type: z.string() }),
|
|
448
|
+
handler: async () => ({ items: [] }),
|
|
449
|
+
};
|
|
450
|
+
|
|
451
|
+
registerTools(mockServer as any, [testTool]);
|
|
452
|
+
|
|
453
|
+
const callHandler = handlers.call;
|
|
454
|
+
const result = await callHandler({
|
|
455
|
+
params: {
|
|
456
|
+
name: "query_entities",
|
|
457
|
+
arguments: { type: "prd" },
|
|
458
|
+
},
|
|
459
|
+
});
|
|
460
|
+
|
|
461
|
+
expect(result.isError).toBe(true);
|
|
462
|
+
const parsedError = JSON.parse(result.content[0].text);
|
|
463
|
+
|
|
464
|
+
const commandOption = parsedError.setup.options.find(
|
|
465
|
+
(o: any) => o.method === "command",
|
|
466
|
+
);
|
|
467
|
+
expect(commandOption.name).toBe("/flux");
|
|
468
|
+
expect(commandOption.description).toBeTruthy();
|
|
469
|
+
|
|
470
|
+
const toolOption = parsedError.setup.options.find(
|
|
471
|
+
(o: any) => o.method === "tool",
|
|
472
|
+
);
|
|
473
|
+
expect(toolOption.name).toBe("init_project");
|
|
474
|
+
expect(toolOption.params.name).toBeTruthy();
|
|
475
|
+
expect(toolOption.params.vision).toBeTruthy();
|
|
476
|
+
expect(toolOption.params.adapter).toBeTruthy();
|
|
477
|
+
});
|
|
388
478
|
});
|
|
@@ -16,7 +16,6 @@ import { createPrdTool } from "../create-prd.js";
|
|
|
16
16
|
import { createTaskTool } from "../create-task.js";
|
|
17
17
|
import { addDependencyTool } from "../dependencies.js";
|
|
18
18
|
import { getEntityTool } from "../get-entity.js";
|
|
19
|
-
import { getProjectContextTool } from "../get-project-context.js";
|
|
20
19
|
import { getStatsTool } from "../get-stats.js";
|
|
21
20
|
import { initProjectTool } from "../init-project.js";
|
|
22
21
|
import { queryEntitiesTool } from "../query-entities.js";
|
|
@@ -242,24 +241,6 @@ describe("Query MCP Tools", () => {
|
|
|
242
241
|
});
|
|
243
242
|
});
|
|
244
243
|
|
|
245
|
-
describe("get_project_context", () => {
|
|
246
|
-
test("returns project context when initialized", async () => {
|
|
247
|
-
const result = (await getProjectContextTool.handler({})) as any;
|
|
248
|
-
|
|
249
|
-
expect(result.initialized).toBe(true);
|
|
250
|
-
expect(result.name).toBe("test-project");
|
|
251
|
-
expect(result.ref_prefix).toBe("TEST");
|
|
252
|
-
});
|
|
253
|
-
|
|
254
|
-
test("returns initialized false when no project", async () => {
|
|
255
|
-
// Remove the project.json
|
|
256
|
-
rmSync(join(FLUX_DIR, "project.json"));
|
|
257
|
-
|
|
258
|
-
const result = (await getProjectContextTool.handler({})) as any;
|
|
259
|
-
expect(result.initialized).toBe(false);
|
|
260
|
-
});
|
|
261
|
-
});
|
|
262
|
-
|
|
263
244
|
describe("get_stats", () => {
|
|
264
245
|
test("returns zeroes for empty project", async () => {
|
|
265
246
|
const result = (await getStatsTool.handler({})) as any;
|
|
@@ -7,11 +7,7 @@ import { z } from "zod";
|
|
|
7
7
|
import { config } from "../config.js";
|
|
8
8
|
import { logger } from "../utils/logger.js";
|
|
9
9
|
|
|
10
|
-
const TOOLS_WITHOUT_PROJECT = [
|
|
11
|
-
"init_project",
|
|
12
|
-
"get_project_context",
|
|
13
|
-
"get_version",
|
|
14
|
-
];
|
|
10
|
+
const TOOLS_WITHOUT_PROJECT = ["init_project", "get_version"];
|
|
15
11
|
|
|
16
12
|
export interface ToolDefinition {
|
|
17
13
|
name: string;
|
|
@@ -26,10 +22,56 @@ export interface ToolError {
|
|
|
26
22
|
code: string;
|
|
27
23
|
}
|
|
28
24
|
|
|
25
|
+
export interface ProjectNotInitializedError extends ToolError {
|
|
26
|
+
code: "PROJECT_NOT_INITIALIZED";
|
|
27
|
+
setup: {
|
|
28
|
+
instructions: string;
|
|
29
|
+
options: Array<{
|
|
30
|
+
method: "command" | "tool";
|
|
31
|
+
name: string;
|
|
32
|
+
description: string;
|
|
33
|
+
params?: Record<string, string>;
|
|
34
|
+
}>;
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
29
38
|
export function createError(message: string, code: string): ToolError {
|
|
30
39
|
return { error: true, message, code };
|
|
31
40
|
}
|
|
32
41
|
|
|
42
|
+
export function createProjectNotInitializedError(
|
|
43
|
+
cwd: string,
|
|
44
|
+
projectRoot: string,
|
|
45
|
+
): ProjectNotInitializedError {
|
|
46
|
+
return {
|
|
47
|
+
error: true,
|
|
48
|
+
code: "PROJECT_NOT_INITIALIZED",
|
|
49
|
+
message: `No Flux project found. Current directory: ${cwd}, resolved project root: ${projectRoot}`,
|
|
50
|
+
setup: {
|
|
51
|
+
instructions:
|
|
52
|
+
"Initialize a Flux project before using Flux tools. Use one of the following options:",
|
|
53
|
+
options: [
|
|
54
|
+
{
|
|
55
|
+
method: "command",
|
|
56
|
+
name: "/flux",
|
|
57
|
+
description:
|
|
58
|
+
"Interactive setup with guided prompts (recommended for first-time setup)",
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
method: "tool",
|
|
62
|
+
name: "init_project",
|
|
63
|
+
description: "Direct initialization via MCP tool",
|
|
64
|
+
params: {
|
|
65
|
+
name: "Project name (required)",
|
|
66
|
+
vision: "Brief project description (required)",
|
|
67
|
+
adapter: "local | linear (default: local)",
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
],
|
|
71
|
+
},
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
33
75
|
export function registerTools(server: Server, tools: ToolDefinition[]) {
|
|
34
76
|
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
35
77
|
tools: tools.map((t) => ({
|
|
@@ -60,13 +102,16 @@ export function registerTools(server: Server, tools: ToolDefinition[]) {
|
|
|
60
102
|
}
|
|
61
103
|
|
|
62
104
|
if (!TOOLS_WITHOUT_PROJECT.includes(toolName) && !config.projectExists) {
|
|
63
|
-
const
|
|
64
|
-
|
|
105
|
+
const error = createProjectNotInitializedError(
|
|
106
|
+
process.cwd(),
|
|
107
|
+
config.projectRoot,
|
|
108
|
+
);
|
|
109
|
+
logger.error(error.message);
|
|
65
110
|
return {
|
|
66
111
|
content: [
|
|
67
112
|
{
|
|
68
113
|
type: "text",
|
|
69
|
-
text: JSON.stringify(
|
|
114
|
+
text: JSON.stringify(error, null, 2),
|
|
70
115
|
},
|
|
71
116
|
],
|
|
72
117
|
isError: true,
|
|
@@ -104,7 +149,6 @@ export { deleteEntityTool } from "./delete-entity.js";
|
|
|
104
149
|
export { addDependencyTool, removeDependencyTool } from "./dependencies.js";
|
|
105
150
|
export { getEntityTool } from "./get-entity.js";
|
|
106
151
|
export { getLinearUrlTool } from "./get-linear-url.js";
|
|
107
|
-
export { getProjectContextTool } from "./get-project-context.js";
|
|
108
152
|
export { getStatsTool } from "./get-stats.js";
|
|
109
153
|
export { getVersionTool } from "./get-version.js";
|
|
110
154
|
export { initProjectTool } from "./init-project.js";
|
|
@@ -102,7 +102,7 @@ async function handler(input: unknown) {
|
|
|
102
102
|
export const initProjectTool: ToolDefinition = {
|
|
103
103
|
name: "init_project",
|
|
104
104
|
description:
|
|
105
|
-
"Initialize a new Flux project. Required: name, vision. Optional: adapter (local|specflux|linear|notion, default 'local'). Creates .flux/ directory with project.json and SQLite database. Returns {success, project, message}. Fails if .flux/ already exists.
|
|
105
|
+
"Initialize a new Flux project. Required: name, vision. Optional: adapter (local|specflux|linear|notion, default 'local'). Creates .flux/ directory with project.json and SQLite database. Returns {success, project, message}. Fails if .flux/ already exists.",
|
|
106
106
|
inputSchema,
|
|
107
107
|
handler,
|
|
108
108
|
};
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
-
import { z } from "zod";
|
|
3
|
-
import { config } from "../config.js";
|
|
4
|
-
import type { ToolDefinition } from "./index.js";
|
|
5
|
-
|
|
6
|
-
const inputSchema = z.object({});
|
|
7
|
-
|
|
8
|
-
async function handler(_input: unknown) {
|
|
9
|
-
const projectJsonPath = config.projectJsonPath;
|
|
10
|
-
|
|
11
|
-
if (!existsSync(projectJsonPath)) {
|
|
12
|
-
return { initialized: false };
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
try {
|
|
16
|
-
const content = readFileSync(projectJsonPath, "utf-8");
|
|
17
|
-
const project = JSON.parse(content);
|
|
18
|
-
return { initialized: true, ...project };
|
|
19
|
-
} catch (_err) {
|
|
20
|
-
return {
|
|
21
|
-
initialized: false,
|
|
22
|
-
error: "Failed to read project.json",
|
|
23
|
-
};
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
export const getProjectContextTool: ToolDefinition = {
|
|
28
|
-
name: "get_project_context",
|
|
29
|
-
description:
|
|
30
|
-
"Get project context from .flux/project.json. No parameters required. Returns {initialized: boolean, name?, vision?, ref_prefix?, adapter?: {type: 'local'|'specflux'|'linear'|'notion'}, created_at?}. Use this to check if a Flux project exists before other operations.",
|
|
31
|
-
inputSchema,
|
|
32
|
-
handler,
|
|
33
|
-
};
|