@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.
- package/README.md +3 -3
- package/agents/coder.md +150 -25
- package/commands/breakdown.md +47 -10
- package/commands/flux.md +92 -12
- package/commands/implement.md +166 -17
- package/commands/linear.md +6 -5
- package/commands/prd.md +996 -82
- package/manifest.json +2 -1
- package/package.json +4 -2
- package/skills/flux-orchestrator/SKILL.md +11 -3
- package/skills/prd-writer/SKILL.md +761 -0
- 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/__tests__/dependency-ops.test.ts +52 -18
- package/src/server/adapters/linear/adapter.ts +19 -14
- package/src/server/adapters/local-adapter.ts +48 -7
- package/src/server/db/__tests__/queries.test.ts +2 -1
- package/src/server/db/schema.ts +9 -0
- package/src/server/index.ts +0 -2
- package/src/server/tools/__tests__/crud.test.ts +111 -1
- package/src/server/tools/__tests__/mcp-interface.test.ts +100 -9
- package/src/server/tools/__tests__/query.test.ts +73 -21
- 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/server/tools/create-epic.ts +11 -2
- package/src/server/tools/create-prd.ts +11 -2
- package/src/server/tools/create-task.ts +11 -2
- package/src/server/tools/dependencies.ts +2 -2
- package/src/server/tools/get-entity.ts +12 -10
- package/src/server/tools/index.ts +53 -9
- package/src/server/tools/init-project.ts +1 -1
- package/src/server/tools/render-status.ts +38 -20
- package/src/status-line/__tests__/status-line.test.ts +1 -1
- package/src/utils/status-renderer.ts +32 -6
- package/skills/prd-template/SKILL.md +0 -242
- 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
|
|
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
|
};
|
|
@@ -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
|
|
114
|
-
|
|
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
|
|
121
|
-
|
|
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
|
|
160
|
-
|
|
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
|
|
167
|
-
|
|
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:
|
|
176
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
};
|