@cliangdev/flux-plugin 0.3.1 → 0.4.0
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/dashboard.md +1 -1
- package/package.json +3 -1
- package/src/server/adapters/factory.ts +6 -28
- package/src/server/adapters/github/__tests__/criteria-deps.test.ts +579 -0
- package/src/server/adapters/github/__tests__/documents-stats.test.ts +789 -0
- package/src/server/adapters/github/__tests__/epic-task-crud.test.ts +1072 -0
- package/src/server/adapters/github/__tests__/foundation.test.ts +537 -0
- package/src/server/adapters/github/__tests__/index-store.test.ts +319 -0
- package/src/server/adapters/github/__tests__/prd-crud.test.ts +836 -0
- package/src/server/adapters/github/adapter.ts +1574 -0
- package/src/server/adapters/github/client.ts +34 -0
- package/src/server/adapters/github/config.ts +59 -0
- package/src/server/adapters/github/helpers/criteria.ts +157 -0
- package/src/server/adapters/github/helpers/index-store.ts +79 -0
- package/src/server/adapters/github/helpers/meta.ts +26 -0
- package/src/server/adapters/github/index.ts +5 -0
- package/src/server/adapters/github/mappers/epic.ts +21 -0
- package/src/server/adapters/github/mappers/index.ts +15 -0
- package/src/server/adapters/github/mappers/prd.ts +50 -0
- package/src/server/adapters/github/mappers/task.ts +37 -0
- package/src/server/adapters/github/types.ts +27 -0
- package/src/server/adapters/linear/adapter.ts +121 -105
- package/src/server/adapters/linear/client.ts +21 -14
- package/src/server/adapters/types.ts +1 -1
- package/src/server/index.ts +2 -0
- package/src/server/tools/__tests__/mcp-interface.test.ts +6 -0
- package/src/server/tools/__tests__/z-configure-github.test.ts +513 -0
- package/src/server/tools/__tests__/z-get-linear-url.test.ts +2 -2
- package/src/server/tools/__tests__/z-init-project.test.ts +168 -0
- package/src/server/tools/configure-github.ts +422 -0
- package/src/server/tools/index.ts +2 -1
- package/src/server/tools/init-project.ts +26 -12
|
@@ -0,0 +1,422 @@
|
|
|
1
|
+
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { graphql } from "@octokit/graphql";
|
|
3
|
+
import { Octokit } from "@octokit/rest";
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
import { GITHUB_LABELS } from "../adapters/github/types.js";
|
|
6
|
+
import { clearAdapterCache } from "../adapters/index.js";
|
|
7
|
+
import { config } from "../config.js";
|
|
8
|
+
import type { ToolDefinition } from "./index.js";
|
|
9
|
+
|
|
10
|
+
const inputSchema = z.object({
|
|
11
|
+
token: z.string().optional(),
|
|
12
|
+
owner: z.string(),
|
|
13
|
+
repo: z.string(),
|
|
14
|
+
ref_prefix: z.string().optional().default("FLUX"),
|
|
15
|
+
visibility: z.enum(["private", "public"]).optional().default("private"),
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
type ConfigureMode = "setup" | "join" | "update";
|
|
19
|
+
|
|
20
|
+
interface SharedConfig {
|
|
21
|
+
owner: string;
|
|
22
|
+
repo: string;
|
|
23
|
+
projectId: string;
|
|
24
|
+
refPrefix: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
interface ConfigureResult {
|
|
28
|
+
mode: ConfigureMode;
|
|
29
|
+
board_url: string | null;
|
|
30
|
+
repo_url: string;
|
|
31
|
+
labels_created: number;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const LABEL_DEFINITIONS: Array<{ name: string; color: string }> = [
|
|
35
|
+
{ name: GITHUB_LABELS.ENTITY_PRD, color: "0075ca" },
|
|
36
|
+
{ name: GITHUB_LABELS.ENTITY_EPIC, color: "7057ff" },
|
|
37
|
+
{ name: GITHUB_LABELS.ENTITY_TASK, color: "0e8a16" },
|
|
38
|
+
{ name: GITHUB_LABELS.STATUS_DRAFT, color: "e4e669" },
|
|
39
|
+
{ name: GITHUB_LABELS.STATUS_PENDING_REVIEW, color: "fbca04" },
|
|
40
|
+
{ name: GITHUB_LABELS.STATUS_REVIEWED, color: "d4c5f9" },
|
|
41
|
+
{ name: GITHUB_LABELS.STATUS_APPROVED, color: "0e8a16" },
|
|
42
|
+
{ name: GITHUB_LABELS.STATUS_BREAKDOWN_READY, color: "1d76db" },
|
|
43
|
+
{ name: GITHUB_LABELS.STATUS_COMPLETED, color: "0075ca" },
|
|
44
|
+
{ name: GITHUB_LABELS.STATUS_IN_PROGRESS, color: "e99695" },
|
|
45
|
+
{ name: GITHUB_LABELS.PRIORITY_LOW, color: "c5def5" },
|
|
46
|
+
{ name: GITHUB_LABELS.PRIORITY_MEDIUM, color: "bfd4f2" },
|
|
47
|
+
{ name: GITHUB_LABELS.PRIORITY_HIGH, color: "b60205" },
|
|
48
|
+
];
|
|
49
|
+
|
|
50
|
+
async function validateToken(octokit: Octokit): Promise<string> {
|
|
51
|
+
try {
|
|
52
|
+
const { data } = await octokit.users.getAuthenticated();
|
|
53
|
+
return data.login;
|
|
54
|
+
} catch (error: unknown) {
|
|
55
|
+
const err = error as { status?: number; message?: string };
|
|
56
|
+
const status = err.status ?? 0;
|
|
57
|
+
throw new Error(
|
|
58
|
+
`GitHub token validation failed (HTTP ${status}): ${err.message}. Ensure the token has 'repo' scope.`,
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function detectUpdateMode(): boolean {
|
|
64
|
+
if (!existsSync(config.projectJsonPath)) return false;
|
|
65
|
+
try {
|
|
66
|
+
const content = readFileSync(config.projectJsonPath, "utf-8");
|
|
67
|
+
const project = JSON.parse(content);
|
|
68
|
+
return (
|
|
69
|
+
project?.adapter?.type === "github" &&
|
|
70
|
+
Boolean(project?.adapter?.config?.owner)
|
|
71
|
+
);
|
|
72
|
+
} catch {
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
async function fetchRemoteConfig(
|
|
78
|
+
octokit: Octokit,
|
|
79
|
+
owner: string,
|
|
80
|
+
repo: string,
|
|
81
|
+
): Promise<SharedConfig | null> {
|
|
82
|
+
try {
|
|
83
|
+
const response = await octokit.request(
|
|
84
|
+
"GET /repos/{owner}/{repo}/contents/{path}",
|
|
85
|
+
{
|
|
86
|
+
owner,
|
|
87
|
+
repo,
|
|
88
|
+
path: ".flux/github-config.json",
|
|
89
|
+
},
|
|
90
|
+
);
|
|
91
|
+
const data = response.data as { content: string; encoding: string };
|
|
92
|
+
const decoded = Buffer.from(data.content, "base64").toString("utf-8");
|
|
93
|
+
return JSON.parse(decoded) as SharedConfig;
|
|
94
|
+
} catch (error: unknown) {
|
|
95
|
+
const status = (error as { status?: number }).status;
|
|
96
|
+
if (status === 404 || status === 403) {
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
99
|
+
throw error;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
async function detectMode(
|
|
104
|
+
octokit: Octokit,
|
|
105
|
+
owner: string,
|
|
106
|
+
repo: string,
|
|
107
|
+
): Promise<{ mode: ConfigureMode; remoteConfig: SharedConfig | null }> {
|
|
108
|
+
if (detectUpdateMode()) {
|
|
109
|
+
return { mode: "update", remoteConfig: null };
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const remoteConfig = await fetchRemoteConfig(octokit, owner, repo);
|
|
113
|
+
if (remoteConfig) {
|
|
114
|
+
return { mode: "join", remoteConfig };
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return { mode: "setup", remoteConfig: null };
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
async function ensureRepo(
|
|
121
|
+
octokit: Octokit,
|
|
122
|
+
owner: string,
|
|
123
|
+
repo: string,
|
|
124
|
+
visibility: "private" | "public",
|
|
125
|
+
): Promise<string> {
|
|
126
|
+
try {
|
|
127
|
+
const { data } = await octokit.repos.get({ owner, repo });
|
|
128
|
+
return data.html_url;
|
|
129
|
+
} catch (error: unknown) {
|
|
130
|
+
if ((error as { status?: number }).status !== 404) throw error;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const { data } = await octokit.repos.createForAuthenticatedUser({
|
|
134
|
+
name: repo,
|
|
135
|
+
private: visibility === "private",
|
|
136
|
+
auto_init: true,
|
|
137
|
+
});
|
|
138
|
+
return data.html_url;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
async function ensureLabels(
|
|
142
|
+
octokit: Octokit,
|
|
143
|
+
owner: string,
|
|
144
|
+
repo: string,
|
|
145
|
+
): Promise<number> {
|
|
146
|
+
const { data: existingLabels } = await octokit.issues.listLabelsForRepo({
|
|
147
|
+
owner,
|
|
148
|
+
repo,
|
|
149
|
+
});
|
|
150
|
+
const existingNames = new Set(existingLabels.map((l) => l.name));
|
|
151
|
+
|
|
152
|
+
let created = 0;
|
|
153
|
+
for (const label of LABEL_DEFINITIONS) {
|
|
154
|
+
if (!existingNames.has(label.name)) {
|
|
155
|
+
await octokit.issues.createLabel({
|
|
156
|
+
owner,
|
|
157
|
+
repo,
|
|
158
|
+
name: label.name,
|
|
159
|
+
color: label.color,
|
|
160
|
+
});
|
|
161
|
+
created++;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
return created;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
async function createProjectsBoard(
|
|
168
|
+
gql: (
|
|
169
|
+
query: string,
|
|
170
|
+
vars: Record<string, unknown>,
|
|
171
|
+
) => Promise<Record<string, Record<string, unknown>>>,
|
|
172
|
+
ownerLogin: string,
|
|
173
|
+
title: string,
|
|
174
|
+
): Promise<{ id: string; url: string }> {
|
|
175
|
+
const ownerResult = await gql(
|
|
176
|
+
`query GetOwnerId($login: String!) { user(login: $login) { id } }`,
|
|
177
|
+
{ login: ownerLogin },
|
|
178
|
+
);
|
|
179
|
+
const ownerId = ownerResult.user.id;
|
|
180
|
+
|
|
181
|
+
const projectResult = await gql(
|
|
182
|
+
`mutation CreateProject($ownerId: ID!, $title: String!) {
|
|
183
|
+
createProjectV2(input: { ownerId: $ownerId, title: $title }) {
|
|
184
|
+
projectV2 { id url }
|
|
185
|
+
}
|
|
186
|
+
}`,
|
|
187
|
+
{ ownerId, title },
|
|
188
|
+
);
|
|
189
|
+
|
|
190
|
+
return projectResult.createProjectV2.projectV2 as unknown as {
|
|
191
|
+
id: string;
|
|
192
|
+
url: string;
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
async function commitSharedConfig(
|
|
197
|
+
octokit: Octokit,
|
|
198
|
+
owner: string,
|
|
199
|
+
repo: string,
|
|
200
|
+
sharedConfig: SharedConfig,
|
|
201
|
+
): Promise<void> {
|
|
202
|
+
const content = Buffer.from(JSON.stringify(sharedConfig, null, 2)).toString(
|
|
203
|
+
"base64",
|
|
204
|
+
);
|
|
205
|
+
await octokit.request("PUT /repos/{owner}/{repo}/contents/{path}", {
|
|
206
|
+
owner,
|
|
207
|
+
repo,
|
|
208
|
+
path: ".flux/github-config.json",
|
|
209
|
+
message: "chore: add Flux shared config",
|
|
210
|
+
content,
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function writeProjectJson(params: {
|
|
215
|
+
token: string;
|
|
216
|
+
owner: string;
|
|
217
|
+
repo: string;
|
|
218
|
+
projectId: string;
|
|
219
|
+
refPrefix: string;
|
|
220
|
+
}): void {
|
|
221
|
+
const projectJsonPath = config.projectJsonPath;
|
|
222
|
+
const existing = existsSync(projectJsonPath)
|
|
223
|
+
? JSON.parse(readFileSync(projectJsonPath, "utf-8"))
|
|
224
|
+
: {};
|
|
225
|
+
|
|
226
|
+
existing.adapter = {
|
|
227
|
+
type: "github",
|
|
228
|
+
config: {
|
|
229
|
+
token: params.token,
|
|
230
|
+
owner: params.owner,
|
|
231
|
+
repo: params.repo,
|
|
232
|
+
projectId: params.projectId,
|
|
233
|
+
refPrefix: params.refPrefix,
|
|
234
|
+
},
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
writeFileSync(projectJsonPath, JSON.stringify(existing, null, 2));
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
async function runSetup(params: {
|
|
241
|
+
octokit: Octokit;
|
|
242
|
+
gql: (
|
|
243
|
+
query: string,
|
|
244
|
+
vars: Record<string, unknown>,
|
|
245
|
+
) => Promise<Record<string, Record<string, unknown>>>;
|
|
246
|
+
ownerLogin: string;
|
|
247
|
+
owner: string;
|
|
248
|
+
repo: string;
|
|
249
|
+
refPrefix: string;
|
|
250
|
+
visibility: "private" | "public";
|
|
251
|
+
token: string;
|
|
252
|
+
}): Promise<ConfigureResult> {
|
|
253
|
+
const repoUrl = await ensureRepo(
|
|
254
|
+
params.octokit,
|
|
255
|
+
params.owner,
|
|
256
|
+
params.repo,
|
|
257
|
+
params.visibility,
|
|
258
|
+
);
|
|
259
|
+
const labelsCreated = await ensureLabels(
|
|
260
|
+
params.octokit,
|
|
261
|
+
params.owner,
|
|
262
|
+
params.repo,
|
|
263
|
+
);
|
|
264
|
+
const project = await createProjectsBoard(
|
|
265
|
+
params.gql,
|
|
266
|
+
params.ownerLogin,
|
|
267
|
+
`Flux: ${params.repo}`,
|
|
268
|
+
);
|
|
269
|
+
|
|
270
|
+
const sharedConfig: SharedConfig = {
|
|
271
|
+
owner: params.owner,
|
|
272
|
+
repo: params.repo,
|
|
273
|
+
projectId: project.id,
|
|
274
|
+
refPrefix: params.refPrefix,
|
|
275
|
+
};
|
|
276
|
+
|
|
277
|
+
await commitSharedConfig(
|
|
278
|
+
params.octokit,
|
|
279
|
+
params.owner,
|
|
280
|
+
params.repo,
|
|
281
|
+
sharedConfig,
|
|
282
|
+
);
|
|
283
|
+
|
|
284
|
+
writeProjectJson({
|
|
285
|
+
token: params.token,
|
|
286
|
+
owner: params.owner,
|
|
287
|
+
repo: params.repo,
|
|
288
|
+
projectId: project.id,
|
|
289
|
+
refPrefix: params.refPrefix,
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
return {
|
|
293
|
+
mode: "setup",
|
|
294
|
+
board_url: project.url,
|
|
295
|
+
repo_url: repoUrl,
|
|
296
|
+
labels_created: labelsCreated,
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
async function runJoin(params: {
|
|
301
|
+
token: string;
|
|
302
|
+
owner: string;
|
|
303
|
+
repo: string;
|
|
304
|
+
remoteConfig: SharedConfig;
|
|
305
|
+
}): Promise<ConfigureResult> {
|
|
306
|
+
writeProjectJson({
|
|
307
|
+
token: params.token,
|
|
308
|
+
owner: params.remoteConfig.owner,
|
|
309
|
+
repo: params.remoteConfig.repo,
|
|
310
|
+
projectId: params.remoteConfig.projectId,
|
|
311
|
+
refPrefix: params.remoteConfig.refPrefix,
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
const repoUrl = `https://github.com/${params.remoteConfig.owner}/${params.remoteConfig.repo}`;
|
|
315
|
+
|
|
316
|
+
return {
|
|
317
|
+
mode: "join",
|
|
318
|
+
board_url: null,
|
|
319
|
+
repo_url: repoUrl,
|
|
320
|
+
labels_created: 0,
|
|
321
|
+
};
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
async function runUpdate(params: {
|
|
325
|
+
token: string;
|
|
326
|
+
owner: string;
|
|
327
|
+
repo: string;
|
|
328
|
+
}): Promise<ConfigureResult> {
|
|
329
|
+
const projectJsonPath = config.projectJsonPath;
|
|
330
|
+
const existing = JSON.parse(readFileSync(projectJsonPath, "utf-8"));
|
|
331
|
+
existing.adapter.config.token = params.token;
|
|
332
|
+
writeFileSync(projectJsonPath, JSON.stringify(existing, null, 2));
|
|
333
|
+
|
|
334
|
+
const repoUrl = `https://github.com/${params.owner}/${params.repo}`;
|
|
335
|
+
|
|
336
|
+
return {
|
|
337
|
+
mode: "update",
|
|
338
|
+
board_url: null,
|
|
339
|
+
repo_url: repoUrl,
|
|
340
|
+
labels_created: 0,
|
|
341
|
+
};
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
function resolveToken(inputToken?: string): string {
|
|
345
|
+
if (inputToken) return inputToken;
|
|
346
|
+
const envToken = process.env.FLUX_GITHUB_TOKEN;
|
|
347
|
+
if (envToken) return envToken;
|
|
348
|
+
throw new Error(
|
|
349
|
+
"GitHub token not found. Set the FLUX_GITHUB_TOKEN environment variable " +
|
|
350
|
+
"(recommended: export FLUX_GITHUB_TOKEN=ghp_... in your shell before starting Claude Code), " +
|
|
351
|
+
"or pass it via the token parameter.",
|
|
352
|
+
);
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
async function handler(input: unknown) {
|
|
356
|
+
const parsed = inputSchema.parse(input);
|
|
357
|
+
const token = resolveToken(parsed.token);
|
|
358
|
+
|
|
359
|
+
const octokit = new Octokit({ auth: token });
|
|
360
|
+
const gqlWithAuth = graphql.defaults({
|
|
361
|
+
headers: { authorization: `token ${token}` },
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
const ownerLogin = await validateToken(octokit);
|
|
365
|
+
|
|
366
|
+
const { mode, remoteConfig } = await detectMode(
|
|
367
|
+
octokit,
|
|
368
|
+
parsed.owner,
|
|
369
|
+
parsed.repo,
|
|
370
|
+
);
|
|
371
|
+
|
|
372
|
+
let result: ConfigureResult;
|
|
373
|
+
|
|
374
|
+
if (mode === "update") {
|
|
375
|
+
result = await runUpdate({
|
|
376
|
+
token,
|
|
377
|
+
owner: parsed.owner,
|
|
378
|
+
repo: parsed.repo,
|
|
379
|
+
});
|
|
380
|
+
} else if (mode === "join" && remoteConfig) {
|
|
381
|
+
result = await runJoin({
|
|
382
|
+
token,
|
|
383
|
+
owner: parsed.owner,
|
|
384
|
+
repo: parsed.repo,
|
|
385
|
+
remoteConfig,
|
|
386
|
+
});
|
|
387
|
+
} else {
|
|
388
|
+
result = await runSetup({
|
|
389
|
+
octokit,
|
|
390
|
+
gql: gqlWithAuth,
|
|
391
|
+
ownerLogin,
|
|
392
|
+
owner: parsed.owner,
|
|
393
|
+
repo: parsed.repo,
|
|
394
|
+
refPrefix: parsed.ref_prefix,
|
|
395
|
+
visibility: parsed.visibility,
|
|
396
|
+
token,
|
|
397
|
+
});
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
clearAdapterCache();
|
|
401
|
+
config.clearCache();
|
|
402
|
+
|
|
403
|
+
return result;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
export const configureGithubTool: ToolDefinition = {
|
|
407
|
+
name: "configure_github",
|
|
408
|
+
description:
|
|
409
|
+
"Configure GitHub integration for the Flux project. " +
|
|
410
|
+
"Automatically detects the correct mode: " +
|
|
411
|
+
"'setup' (first-time: creates tracking repo, labels, Projects v2 board, commits shared config), " +
|
|
412
|
+
"'join' (remote .flux/github-config.json exists: reads shared config, writes local project.json), " +
|
|
413
|
+
"'update' (local project.json already has github adapter: rotates token only). " +
|
|
414
|
+
"Required: owner (GitHub username/org), repo (tracking repo name). " +
|
|
415
|
+
"Token resolution order: 1) FLUX_GITHUB_TOKEN environment variable (recommended — set with 'export FLUX_GITHUB_TOKEN=ghp_...' before starting Claude Code, never paste tokens in chat), " +
|
|
416
|
+
"2) token parameter (avoid — visible in chat history). " +
|
|
417
|
+
"To create a PAT: github.com → Settings → Developer settings → Personal access tokens → Tokens (classic) → Generate new token → select 'repo' scope. " +
|
|
418
|
+
"Optional: ref_prefix (default 'FLUX'), visibility (default 'private'). " +
|
|
419
|
+
"Returns {mode, board_url, repo_url, labels_created}.",
|
|
420
|
+
inputSchema,
|
|
421
|
+
handler,
|
|
422
|
+
};
|
|
@@ -64,7 +64,7 @@ export function createProjectNotInitializedError(
|
|
|
64
64
|
params: {
|
|
65
65
|
name: "Project name (required)",
|
|
66
66
|
vision: "Brief project description (required)",
|
|
67
|
-
adapter: "local | linear (default: local)",
|
|
67
|
+
adapter: "local | linear | github (default: local)",
|
|
68
68
|
},
|
|
69
69
|
},
|
|
70
70
|
],
|
|
@@ -140,6 +140,7 @@ export function registerTools(server: Server, tools: ToolDefinition[]) {
|
|
|
140
140
|
});
|
|
141
141
|
}
|
|
142
142
|
|
|
143
|
+
export { configureGithubTool } from "./configure-github.js";
|
|
143
144
|
export { configureLinearTool } from "./configure-linear.js";
|
|
144
145
|
export { createEpicTool } from "./create-epic.js";
|
|
145
146
|
export { createPrdTool } from "./create-prd.js";
|
|
@@ -48,7 +48,13 @@ function setupClaudeSettings(projectRoot: string): void {
|
|
|
48
48
|
}
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
-
const adapterTypes = [
|
|
51
|
+
const adapterTypes = [
|
|
52
|
+
"local",
|
|
53
|
+
"specflux",
|
|
54
|
+
"linear",
|
|
55
|
+
"notion",
|
|
56
|
+
"github",
|
|
57
|
+
] as const;
|
|
52
58
|
|
|
53
59
|
const inputSchema = z.object({
|
|
54
60
|
name: z.string().min(1, "Project name is required"),
|
|
@@ -80,29 +86,37 @@ async function handler(input: unknown) {
|
|
|
80
86
|
writeFileSync(config.projectJsonPath, JSON.stringify(projectJson, null, 2));
|
|
81
87
|
config.clearCache();
|
|
82
88
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
89
|
+
if (parsed.adapter === "local") {
|
|
90
|
+
initDb();
|
|
91
|
+
const projectId = generateId("proj");
|
|
92
|
+
const db = getDb();
|
|
93
|
+
insert(db, "projects", {
|
|
94
|
+
id: projectId,
|
|
95
|
+
name: parsed.name,
|
|
96
|
+
ref_prefix: refPrefix,
|
|
97
|
+
});
|
|
98
|
+
}
|
|
92
99
|
|
|
93
100
|
setupClaudeSettings(config.projectRoot);
|
|
94
101
|
|
|
102
|
+
const nextStep =
|
|
103
|
+
parsed.adapter === "github"
|
|
104
|
+
? "Run configure_github to connect your GitHub tracking repository."
|
|
105
|
+
: parsed.adapter === "linear"
|
|
106
|
+
? "Run configure_linear to connect your Linear workspace."
|
|
107
|
+
: "Run /flux:prd to start planning.";
|
|
108
|
+
|
|
95
109
|
return {
|
|
96
110
|
success: true,
|
|
97
111
|
project: projectJson,
|
|
98
|
-
message: `Flux project initialized.
|
|
112
|
+
message: `Flux project initialized. ${nextStep}`,
|
|
99
113
|
};
|
|
100
114
|
}
|
|
101
115
|
|
|
102
116
|
export const initProjectTool: ToolDefinition = {
|
|
103
117
|
name: "init_project",
|
|
104
118
|
description:
|
|
105
|
-
"Initialize a new Flux project. Required: name, vision. Optional: adapter (local|
|
|
119
|
+
"Initialize a new Flux project. Required: name, vision. Optional: adapter (local|linear|github, default 'local'). Creates .flux/ directory with project.json. For local adapter, also initializes SQLite database. For github or linear, run configure_github or configure_linear next. Returns {success, project, message}. Fails if .flux/ already exists.",
|
|
106
120
|
inputSchema,
|
|
107
121
|
handler,
|
|
108
122
|
};
|