@cliangdev/flux-plugin 0.2.0-dev.dc5e2c4 → 0.2.0-dev.f718bcf

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 (46) hide show
  1. package/README.md +3 -3
  2. package/agents/coder.md +150 -25
  3. package/commands/breakdown.md +47 -10
  4. package/commands/flux.md +92 -12
  5. package/commands/implement.md +166 -17
  6. package/commands/linear.md +6 -5
  7. package/commands/prd.md +996 -82
  8. package/manifest.json +2 -1
  9. package/package.json +4 -2
  10. package/skills/flux-orchestrator/SKILL.md +11 -3
  11. package/skills/prd-writer/SKILL.md +761 -0
  12. package/skills/ux-ui-design/SKILL.md +346 -0
  13. package/skills/ux-ui-design/references/design-tokens.md +359 -0
  14. package/src/dashboard/__tests__/api.test.ts +211 -0
  15. package/src/dashboard/browser.ts +35 -0
  16. package/src/dashboard/public/app.js +869 -0
  17. package/src/dashboard/public/index.html +90 -0
  18. package/src/dashboard/public/styles.css +807 -0
  19. package/src/dashboard/public/vendor/highlight.css +10 -0
  20. package/src/dashboard/public/vendor/highlight.min.js +8422 -0
  21. package/src/dashboard/public/vendor/marked.min.js +2210 -0
  22. package/src/dashboard/server.ts +296 -0
  23. package/src/dashboard/watchers.ts +83 -0
  24. package/src/server/adapters/__tests__/dependency-ops.test.ts +52 -18
  25. package/src/server/adapters/linear/adapter.ts +19 -14
  26. package/src/server/adapters/local-adapter.ts +48 -7
  27. package/src/server/db/__tests__/queries.test.ts +2 -1
  28. package/src/server/db/schema.ts +9 -0
  29. package/src/server/index.ts +0 -2
  30. package/src/server/tools/__tests__/crud.test.ts +111 -1
  31. package/src/server/tools/__tests__/mcp-interface.test.ts +100 -9
  32. package/src/server/tools/__tests__/query.test.ts +73 -21
  33. package/src/server/tools/__tests__/z-configure-linear.test.ts +1 -1
  34. package/src/server/tools/__tests__/z-get-linear-url.test.ts +1 -1
  35. package/src/server/tools/create-epic.ts +11 -2
  36. package/src/server/tools/create-prd.ts +11 -2
  37. package/src/server/tools/create-task.ts +11 -2
  38. package/src/server/tools/dependencies.ts +2 -2
  39. package/src/server/tools/get-entity.ts +12 -10
  40. package/src/server/tools/index.ts +53 -9
  41. package/src/server/tools/init-project.ts +1 -1
  42. package/src/server/tools/render-status.ts +38 -20
  43. package/src/status-line/__tests__/status-line.test.ts +1 -1
  44. package/src/utils/status-renderer.ts +32 -6
  45. package/skills/prd-template/SKILL.md +0 -242
  46. package/src/server/tools/get-project-context.ts +0 -33
@@ -85,6 +85,10 @@ async function handler(input: unknown) {
85
85
  // Add available status transitions
86
86
  result.available_transitions = getValidTransitions(entityType, prd.status);
87
87
 
88
+ // Always include dependencies by default
89
+ const deps = await adapter.getDependencies(parsed.ref);
90
+ result.dependencies = deps;
91
+
88
92
  // Process includes
89
93
  for (const inc of includes) {
90
94
  if (!validIncludes.includes(inc)) continue;
@@ -166,6 +170,10 @@ async function handler(input: unknown) {
166
170
  result = { ...toMcpEpic(epic) };
167
171
  result.available_transitions = getValidTransitions(entityType, epic.status);
168
172
 
173
+ // Always include dependencies by default
174
+ const deps = await adapter.getDependencies(parsed.ref);
175
+ result.dependencies = deps;
176
+
169
177
  for (const inc of includes) {
170
178
  if (!validIncludes.includes(inc)) continue;
171
179
 
@@ -192,11 +200,6 @@ async function handler(input: unknown) {
192
200
  result.criteria_count = criteria.length;
193
201
  result.criteria_met = criteria.filter((c) => c.isMet).length;
194
202
  }
195
-
196
- if (inc === "dependencies") {
197
- const deps = await adapter.getDependencies(parsed.ref);
198
- result.dependencies = deps;
199
- }
200
203
  }
201
204
  } else {
202
205
  const task = await adapter.getTask(parsed.ref);
@@ -205,6 +208,10 @@ async function handler(input: unknown) {
205
208
  result = { ...toMcpTask(task) };
206
209
  result.available_transitions = getValidTransitions(entityType, task.status);
207
210
 
211
+ // Always include dependencies by default
212
+ const deps = await adapter.getDependencies(parsed.ref);
213
+ result.dependencies = deps;
214
+
208
215
  for (const inc of includes) {
209
216
  if (!validIncludes.includes(inc)) continue;
210
217
 
@@ -218,11 +225,6 @@ async function handler(input: unknown) {
218
225
  result.criteria_count = criteria.length;
219
226
  result.criteria_met = criteria.filter((c) => c.isMet).length;
220
227
  }
221
-
222
- if (inc === "dependencies") {
223
- const deps = await adapter.getDependencies(parsed.ref);
224
- result.dependencies = deps;
225
- }
226
228
  }
227
229
  }
228
230
 
@@ -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 message = `No Flux project found. Run init_project first or ensure you're in a directory with a .flux folder (or a subdirectory of one). Current directory: ${process.cwd()}, resolved project root: ${config.projectRoot}`;
64
- logger.error(message);
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(createError(message, "PROJECT_NOT_FOUND")),
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. Run get_project_context first to check.",
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
  };
@@ -23,6 +23,7 @@ interface EpicForSummary {
23
23
  status: string;
24
24
  task_count: number;
25
25
  tasks_completed: number;
26
+ dependencies: string[];
26
27
  }
27
28
 
28
29
  interface PrdForSummary {
@@ -30,12 +31,14 @@ interface PrdForSummary {
30
31
  title: string;
31
32
  status: string;
32
33
  epics: EpicForSummary[];
34
+ dependencies: string[];
33
35
  }
34
36
 
35
37
  interface TaskForFull {
36
38
  ref: string;
37
39
  title: string;
38
40
  status: string;
41
+ dependencies: string[];
39
42
  }
40
43
 
41
44
  interface EpicForFull {
@@ -43,6 +46,7 @@ interface EpicForFull {
43
46
  title: string;
44
47
  status: string;
45
48
  tasks: TaskForFull[];
49
+ dependencies: string[];
46
50
  }
47
51
 
48
52
  interface PrdForFull {
@@ -50,6 +54,7 @@ interface PrdForFull {
50
54
  title: string;
51
55
  status: string;
52
56
  epics: EpicForFull[];
57
+ dependencies: string[];
53
58
  }
54
59
 
55
60
  function getProjectInfo(): { name: string; ref_prefix: string } | null {
@@ -110,17 +115,17 @@ async function getSummaryData(adapter: Adapter): Promise<PrdForSummary[]> {
110
115
  const result: PrdForSummary[] = [];
111
116
 
112
117
  for (const prd of nonArchivedPrds) {
113
- const epicsResult = await adapter.listEpics(
114
- { prdRef: prd.ref },
115
- { limit: 100 },
116
- );
118
+ const [prdDeps, epicsResult] = await Promise.all([
119
+ adapter.getDependencies(prd.ref),
120
+ adapter.listEpics({ prdRef: prd.ref }, { limit: 100 }),
121
+ ]);
117
122
 
118
123
  const epicsWithCounts: EpicForSummary[] = await Promise.all(
119
124
  epicsResult.items.map(async (epic) => {
120
- const tasksResult = await adapter.listTasks(
121
- { epicRef: epic.ref },
122
- { limit: 100 },
123
- );
125
+ const [epicDeps, tasksResult] = await Promise.all([
126
+ adapter.getDependencies(epic.ref),
127
+ adapter.listTasks({ epicRef: epic.ref }, { limit: 100 }),
128
+ ]);
124
129
  const completedCount = tasksResult.items.filter(
125
130
  (t) => t.status === "COMPLETED",
126
131
  ).length;
@@ -131,6 +136,7 @@ async function getSummaryData(adapter: Adapter): Promise<PrdForSummary[]> {
131
136
  status: epic.status,
132
137
  task_count: tasksResult.total,
133
138
  tasks_completed: completedCount,
139
+ dependencies: epicDeps,
134
140
  };
135
141
  }),
136
142
  );
@@ -140,6 +146,7 @@ async function getSummaryData(adapter: Adapter): Promise<PrdForSummary[]> {
140
146
  title: prd.title,
141
147
  status: prd.status,
142
148
  epics: epicsWithCounts,
149
+ dependencies: prdDeps,
143
150
  });
144
151
  }
145
152
 
@@ -156,27 +163,37 @@ async function getFullTreeData(adapter: Adapter): Promise<PrdForFull[]> {
156
163
  const result: PrdForFull[] = [];
157
164
 
158
165
  for (const prd of allPrds) {
159
- const epicsResult = await adapter.listEpics(
160
- { prdRef: prd.ref },
161
- { limit: 100 },
162
- );
166
+ const [prdDeps, epicsResult] = await Promise.all([
167
+ adapter.getDependencies(prd.ref),
168
+ adapter.listEpics({ prdRef: prd.ref }, { limit: 100 }),
169
+ ]);
163
170
 
164
171
  const epicsWithTasks: EpicForFull[] = await Promise.all(
165
172
  epicsResult.items.map(async (epic) => {
166
- const tasksResult = await adapter.listTasks(
167
- { epicRef: epic.ref },
168
- { limit: 100 },
173
+ const [epicDeps, tasksResult] = await Promise.all([
174
+ adapter.getDependencies(epic.ref),
175
+ adapter.listTasks({ epicRef: epic.ref }, { limit: 100 }),
176
+ ]);
177
+
178
+ // Get dependencies for each task
179
+ const tasksWithDeps: TaskForFull[] = await Promise.all(
180
+ tasksResult.items.map(async (t) => {
181
+ const taskDeps = await adapter.getDependencies(t.ref);
182
+ return {
183
+ ref: t.ref,
184
+ title: t.title,
185
+ status: t.status,
186
+ dependencies: taskDeps,
187
+ };
188
+ }),
169
189
  );
170
190
 
171
191
  return {
172
192
  ref: epic.ref,
173
193
  title: epic.title,
174
194
  status: epic.status,
175
- tasks: tasksResult.items.map((t) => ({
176
- ref: t.ref,
177
- title: t.title,
178
- status: t.status,
179
- })),
195
+ tasks: tasksWithDeps,
196
+ dependencies: epicDeps,
180
197
  };
181
198
  }),
182
199
  );
@@ -186,6 +203,7 @@ async function getFullTreeData(adapter: Adapter): Promise<PrdForFull[]> {
186
203
  title: prd.title,
187
204
  status: prd.status,
188
205
  epics: epicsWithTasks,
206
+ dependencies: prdDeps,
189
207
  });
190
208
  }
191
209
 
@@ -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)) {
@@ -34,12 +34,14 @@ interface Epic {
34
34
  status: string;
35
35
  task_count: number;
36
36
  tasks_completed: number;
37
+ dependencies?: string[];
37
38
  }
38
39
 
39
40
  interface Task {
40
41
  ref: string;
41
42
  title: string;
42
43
  status: string;
44
+ dependencies?: string[];
43
45
  }
44
46
 
45
47
  interface EpicWithTasks {
@@ -47,6 +49,7 @@ interface EpicWithTasks {
47
49
  title: string;
48
50
  status: string;
49
51
  tasks: Task[];
52
+ dependencies?: string[];
50
53
  }
51
54
 
52
55
  interface Prd {
@@ -54,6 +57,7 @@ interface Prd {
54
57
  title: string;
55
58
  status: string;
56
59
  epics: Epic[];
60
+ dependencies?: string[];
57
61
  }
58
62
 
59
63
  interface PrdWithTasks {
@@ -61,6 +65,7 @@ interface PrdWithTasks {
61
65
  title: string;
62
66
  status: string;
63
67
  epics: EpicWithTasks[];
68
+ dependencies?: string[];
64
69
  }
65
70
 
66
71
  const STATUS_ORDER: Record<string, number> = {
@@ -117,13 +122,22 @@ export function renderSummaryView(
117
122
 
118
123
  for (const prd of sortedPrds) {
119
124
  const badge = getStatusBadge(prd.status);
120
- lines.push(`${prd.ref} ${prd.title} ${badge}`);
125
+ const prdBlocked = prd.dependencies && prd.dependencies.length > 0;
126
+ const prdBlockedBy = prdBlocked
127
+ ? ` ⚠️ blocked by: ${prd.dependencies?.join(", ")}`
128
+ : "";
129
+ lines.push(`${prd.ref} ${prd.title} ${badge}${prdBlockedBy}`);
121
130
 
122
131
  for (const epic of prd.epics) {
123
132
  const icon = getStatusIcon(epic.status);
133
+ const epicBlocked = epic.dependencies && epic.dependencies.length > 0;
134
+ const epicBlockedBy = epicBlocked
135
+ ? ` ⚠️ blocked by: ${epic.dependencies?.join(", ")}`
136
+ : "";
137
+
124
138
  if (epic.task_count === 0) {
125
139
  lines.push(
126
- ` ${icon} ${epic.ref} ${epic.title} ·········· (no tasks)`,
140
+ ` ${icon} ${epic.ref} ${epic.title} ·········· (no tasks)${epicBlockedBy}`,
127
141
  );
128
142
  } else {
129
143
  const epicProgress = renderProgressBar(
@@ -131,7 +145,7 @@ export function renderSummaryView(
131
145
  10,
132
146
  );
133
147
  lines.push(
134
- ` ${icon} ${epic.ref} ${epic.title} ${epicProgress} ${epic.tasks_completed}/${epic.task_count}`,
148
+ ` ${icon} ${epic.ref} ${epic.title} ${epicProgress} ${epic.tasks_completed}/${epic.task_count}${epicBlockedBy}`,
135
149
  );
136
150
  }
137
151
  }
@@ -157,23 +171,35 @@ export function renderFullTreeView(prds: PrdWithTasks[]): string {
157
171
 
158
172
  for (const prd of sortedPrds) {
159
173
  const prdBadge = getStatusBadge(prd.status);
174
+ const prdBlocked = prd.dependencies && prd.dependencies.length > 0;
175
+ const prdBlockedBy = prdBlocked
176
+ ? ` ⚠️ blocked by: ${prd.dependencies?.join(", ")}`
177
+ : "";
160
178
  lines.push(`${prd.ref} ${prd.title}`);
161
- lines.push(`${prdBadge}`);
179
+ lines.push(`${prdBadge}${prdBlockedBy}`);
162
180
  lines.push("");
163
181
 
164
182
  for (const epic of prd.epics) {
165
183
  const epicIcon = getStatusIcon(epic.status);
166
- lines.push(` ${epicIcon} ${epic.ref} ${epic.title}`);
184
+ const epicBlocked = epic.dependencies && epic.dependencies.length > 0;
185
+ const epicBlockedBy = epicBlocked
186
+ ? ` ⚠️ blocked by: ${epic.dependencies?.join(", ")}`
187
+ : "";
188
+ lines.push(` ${epicIcon} ${epic.ref} ${epic.title}${epicBlockedBy}`);
167
189
 
168
190
  if (epic.tasks.length > 0) {
169
191
  lines.push(` ┌${"─".repeat(50)}┐`);
170
192
 
171
193
  for (const task of epic.tasks) {
172
194
  const taskIcon = getStatusIcon(task.status);
195
+ const taskBlocked = task.dependencies && task.dependencies.length > 0;
196
+ const taskBlockedBy = taskBlocked
197
+ ? ` ⚠️ blocked by: ${task.dependencies?.join(", ")}`
198
+ : "";
173
199
  const currentMarker =
174
200
  task.status === "IN_PROGRESS" ? " ← CURRENT" : "";
175
201
  lines.push(
176
- ` │ ${taskIcon} ${task.ref} ${task.title}${currentMarker}`,
202
+ ` │ ${taskIcon} ${task.ref} ${task.title}${currentMarker}${taskBlockedBy}`,
177
203
  );
178
204
  }
179
205
 
@@ -1,242 +0,0 @@
1
- ---
2
- name: flux:prd-template
3
- description: PRD structure and patterns for Flux. Use when creating or refining product requirement documents. PRDs should be concise for humans but detailed enough for AI agents to implement.
4
- user-invocable: false
5
- ---
6
-
7
- # PRD Template Skill
8
-
9
- PRDs should be **concise for human review** but contain **enough detail for AI implementation**.
10
-
11
- ## PRD Structure (Required)
12
-
13
- ```markdown
14
- # {Project Name}
15
-
16
- ## Table of Contents
17
- - [Problem](#problem)
18
- - [Users](#users)
19
- - [Solution](#solution)
20
- - [Features (MVP)](#features-mvp)
21
- - [P0: Must Have](#p0-must-have)
22
- - [P1: Should Have](#p1-should-have)
23
- - [Out of Scope](#out-of-scope)
24
- - [Constraints](#constraints)
25
- - [Open Questions](#open-questions)
26
-
27
- ## Problem
28
- {2-3 sentences: What problem are we solving? Why does it matter?}
29
-
30
- ## Users
31
- {Who has this problem? 1-2 user segments.}
32
-
33
- ## Solution
34
- {1 paragraph: What are we building to solve this?}
35
-
36
- ## Features (MVP)
37
-
38
- ### P0: Must Have
39
- - **{Feature 1}**: {One sentence description}
40
- - {Acceptance criterion 1}
41
- - {Acceptance criterion 2}
42
- - **{Feature 2}**: {One sentence description}
43
- - {Acceptance criterion}
44
-
45
- ### P1: Should Have
46
- - **{Feature}**: {Description}
47
-
48
- ### Out of Scope
49
- - {What we're NOT building}
50
- - {Future considerations}
51
-
52
- ## Constraints
53
- - **Tech Stack**: {Required technologies, if any}
54
- - **Timeline**: {Any deadlines}
55
- - **Other**: {Budget, compliance, etc.}
56
-
57
- ## Open Questions
58
- - {Unresolved decisions needing input}
59
- ```
60
-
61
- ### TOC Guidelines
62
-
63
- **For Local Adapter** (files in `.flux/prds/`):
64
- - Include TOC with anchor links (e.g., `[Problem](#problem)`)
65
- - Use lowercase with hyphens for anchors
66
- - Nest sub-sections under parent sections
67
-
68
- **For External Adapters** (Linear, Notion):
69
- - **Skip TOC** - External systems have their own navigation (Linear's outline, collapsible sections)
70
- - Linear generates unique anchor suffixes making pre-built links impossible
71
-
72
- ## Guidelines
73
-
74
- ### Keep It Short
75
- - Problem: 2-3 sentences max
76
- - Features: 3-5 P0 features for MVP
77
- - Each feature: 1 sentence + 2-4 acceptance criteria
78
- - Total PRD: 1-2 pages
79
-
80
- ### Write for AI Implementation
81
- - Acceptance criteria should be testable
82
- - Use specific, unambiguous language
83
- - Include edge cases in criteria when important
84
-
85
- ### Bad vs Good Examples
86
-
87
- **Bad feature:**
88
- > - User authentication
89
-
90
- **Good feature:**
91
- > - **User Authentication**: Users can sign up and log in with email/password
92
- > - Sign up requires email, password (min 8 chars), and name
93
- > - Login with email + password returns JWT token
94
- > - Invalid credentials show error message
95
- > - Forgot password sends reset email
96
-
97
- ## Supporting Documents (Optional)
98
-
99
- Generate based on **agent confidence** and **user request**.
100
-
101
- | Document | When to Generate |
102
- |----------|------------------|
103
- | `architecture.md` | Complex systems, multiple components, API integrations |
104
- | `wireframes.md` | UI-heavy features, user specifically requests |
105
- | `data-model.md` | Custom data storage, complex relationships |
106
- | `user-flows.md` | Multi-step processes, complex user journeys |
107
-
108
- ### Asking About Supporting Docs
109
-
110
- After PRD outline approval, ask:
111
-
112
- ```
113
- PRD outline looks good. I can also generate:
114
- - Architecture diagram (recommended for this project)
115
- - UI wireframes
116
- - Data model
117
-
118
- Which would you like? Or should I proceed with just the PRD?
119
- ```
120
-
121
- Recommend based on project type:
122
- - **Web App**: architecture, wireframes
123
- - **API/Backend**: architecture, data-model
124
- - **CLI Tool**: usually just PRD
125
- - **Mobile App**: architecture, wireframes
126
-
127
- ## Supporting Doc Templates
128
-
129
- ### architecture.md
130
-
131
- ```markdown
132
- # Architecture
133
-
134
- ## Overview
135
- {One paragraph system description}
136
-
137
- ## Components
138
-
139
- ```mermaid
140
- graph TB
141
- Client[Client] --> API[API Server]
142
- API --> DB[(Database)]
143
- API --> Cache[(Redis)]
144
- ```
145
-
146
- ## API Endpoints
147
-
148
- | Method | Path | Description |
149
- |--------|------|-------------|
150
- | POST | /api/users | Create user |
151
- | GET | /api/users/:id | Get user |
152
-
153
- ## Data Flow
154
- {Describe key data flows}
155
-
156
- ## Tech Stack
157
- - **Frontend**: {tech}
158
- - **Backend**: {tech}
159
- - **Database**: {tech}
160
- ```
161
-
162
- ### wireframes.md
163
-
164
- ```markdown
165
- # Wireframes
166
-
167
- ## Screen: {Name}
168
-
169
- ```
170
- +----------------------------------+
171
- | Logo [Login] [Sign Up]
172
- +----------------------------------+
173
- | |
174
- | Welcome to {App Name} |
175
- | |
176
- | [ Email ] |
177
- | [Password ] |
178
- | [ Login ] |
179
- | |
180
- | Forgot password? |
181
- +----------------------------------+
182
- ```
183
-
184
- **Elements:**
185
- - Logo: links to home
186
- - Login button: submits form
187
- - Forgot password: opens reset flow
188
- ```
189
-
190
- ### data-model.md
191
-
192
- ```markdown
193
- # Data Model
194
-
195
- ## ERD
196
-
197
- ```mermaid
198
- erDiagram
199
- User ||--o{ Post : creates
200
- Post ||--o{ Comment : has
201
- ```
202
-
203
- ## Tables
204
-
205
- ### users
206
- | Column | Type | Constraints |
207
- |--------|------|-------------|
208
- | id | uuid | PK |
209
- | email | varchar | unique, not null |
210
- | created_at | timestamp | default now() |
211
-
212
- ### posts
213
- | Column | Type | Constraints |
214
- |--------|------|-------------|
215
- | id | uuid | PK |
216
- | user_id | uuid | FK users.id |
217
- | content | text | not null |
218
- ```
219
-
220
- ## Workflow
221
-
222
- Check adapter type via `get_project_context` to determine storage behavior.
223
-
224
- ### Local Adapter (`adapter.type === "local"`)
225
-
226
- 1. **Interview** → Collect answers via AskUserQuestion
227
- 2. **Outline** → Generate brief outline, ask for approval
228
- 3. **Create entity** → Call `create_prd` with title and short description
229
- 4. **Write PRD** → Write full PRD to `.flux/prds/{slug}/prd.md`
230
- 5. **Set folder_path** → Call `update_entity` with `folder_path`: `.flux/prds/{slug}`
231
- 6. **Ask about docs** → Offer relevant supporting docs
232
- 7. **Generate docs** → Write requested docs to same folder
233
-
234
- ### External Adapters (`adapter.type === "linear"`, `"notion"`, etc.)
235
-
236
- 1. **Interview** → Collect answers via AskUserQuestion
237
- 2. **Outline** → Generate brief outline, ask for approval
238
- 3. **Create entity** → Call `create_prd` with title and short description
239
- 4. **Sync content** → Call `update_entity` with `description`: Full PRD markdown content
240
- 5. **Ask about docs** → Offer relevant supporting docs (stored as attachments in external system)
241
-
242
- **Note**: External adapters store all content in the external system. No local `.flux/prds/` files are created.
@@ -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
- };