@cliangdev/flux-plugin 0.1.0 → 0.2.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.
@@ -0,0 +1,171 @@
1
+ ---
2
+ name: flux:linear
3
+ description: Connect Flux project to Linear for issue tracking
4
+ allowed-tools: mcp__plugin_flux_flux__*, AskUserQuestion
5
+ ---
6
+
7
+ # Linear Integration Setup
8
+
9
+ Connect a Flux project to Linear using the interactive configuration flow.
10
+
11
+ ## How Interactive Mode Works
12
+
13
+ The `configure_linear` tool supports progressive discovery:
14
+
15
+ | Call | Response |
16
+ |------|----------|
17
+ | `{interactive: true}` | Returns `{step: "select_team", teams: [...], user: {...}}` |
18
+ | `{interactive: true, teamId: "xxx"}` | Returns `{step: "select_project", projects: [...], team: {...}}` |
19
+ | `{teamId: "xxx", projectName: "..."}` | Creates new project and configures |
20
+ | `{teamId: "xxx", existingProjectId: "..."}` | Uses existing project and configures |
21
+
22
+ ## Flow
23
+
24
+ ### Step 1: Verify Project
25
+
26
+ Call `get_project_context`.
27
+
28
+ - If `initialized: false` → Tell user to run `/flux` first, then exit.
29
+ - If `adapter.type === "linear"` and config exists → Already configured, show info and exit.
30
+ - Otherwise → Continue to Step 2.
31
+
32
+ ### Step 2: Fetch Teams
33
+
34
+ **IMPORTANT**: Call `configure_linear` with ONLY `interactive: true`. Do NOT pass teamId, projectName, or any other params.
35
+
36
+ ```json
37
+ {"interactive": true}
38
+ ```
39
+
40
+ This is the ONLY parameter needed for the first call. The tool will return available teams.
41
+
42
+ **If error** (e.g., "Linear API key not found"):
43
+ ```
44
+ Linear API key required.
45
+
46
+ 1. Get your key: Linear → Settings → API → Personal API keys
47
+ 2. Set it: export LINEAR_API_KEY=lin_api_xxx
48
+ 3. Run /flux:linear again
49
+ ```
50
+ Then exit.
51
+
52
+ **If success**, response will be:
53
+ ```json
54
+ {
55
+ "step": "select_team",
56
+ "user": {"name": "...", "email": "..."},
57
+ "teams": [
58
+ {"id": "team-abc", "name": "Engineering", "key": "ENG"},
59
+ {"id": "team-def", "name": "Product", "key": "PROD"}
60
+ ]
61
+ }
62
+ ```
63
+
64
+ Display: "Connected as {user.name} ({user.email})"
65
+
66
+ Use AskUserQuestion to let user select a team:
67
+ ```json
68
+ {
69
+ "questions": [{
70
+ "question": "Which Linear team should Flux use?",
71
+ "header": "Team",
72
+ "options": [
73
+ {"label": "Engineering (ENG)", "description": "team-abc"},
74
+ {"label": "Product (PROD)", "description": "team-def"}
75
+ ],
76
+ "multiSelect": false
77
+ }]
78
+ }
79
+ ```
80
+
81
+ Note: Put the team ID in the description field so you can retrieve it from the response.
82
+
83
+ ### Step 3: Fetch Projects
84
+
85
+ Call `configure_linear` with:
86
+ ```json
87
+ {"interactive": true, "teamId": "<selected_team_id>"}
88
+ ```
89
+
90
+ Response will be:
91
+ ```json
92
+ {
93
+ "step": "select_project",
94
+ "team": {"id": "...", "name": "...", "key": "..."},
95
+ "projects": [
96
+ {"id": "proj-123", "name": "Q1 Sprint", "state": "started"},
97
+ {"id": "proj-456", "name": "Backlog", "state": "planned"}
98
+ ]
99
+ }
100
+ ```
101
+
102
+ Use AskUserQuestion:
103
+ ```json
104
+ {
105
+ "questions": [{
106
+ "question": "Which project should Flux sync to?",
107
+ "header": "Project",
108
+ "options": [
109
+ {"label": "Create New Project", "description": "new"},
110
+ {"label": "Q1 Sprint", "description": "proj-123"},
111
+ {"label": "Backlog", "description": "proj-456"}
112
+ ],
113
+ "multiSelect": false
114
+ }]
115
+ }
116
+ ```
117
+
118
+ ### Step 4: Configure
119
+
120
+ **If user selected "Create New Project":**
121
+
122
+ Ask for project name, then call:
123
+ ```json
124
+ {
125
+ "teamId": "<team_id>",
126
+ "projectName": "<user_provided_name>"
127
+ }
128
+ ```
129
+
130
+ **If user selected existing project:**
131
+
132
+ Call:
133
+ ```json
134
+ {
135
+ "teamId": "<team_id>",
136
+ "existingProjectId": "<project_id>"
137
+ }
138
+ ```
139
+
140
+ ### Step 5: Success
141
+
142
+ Response will include:
143
+ ```json
144
+ {
145
+ "success": true,
146
+ "team": "Engineering",
147
+ "project": {"id": "...", "name": "..."},
148
+ "labels": {...},
149
+ "view": {"created": "...", "setup_hint": "..."}
150
+ }
151
+ ```
152
+
153
+ Display:
154
+ ```
155
+ Linear connected!
156
+
157
+ Team: {team}
158
+ Project: {project.name}
159
+
160
+ All PRDs, epics, and tasks will sync to Linear.
161
+ Run /flux:prd to create your first PRD.
162
+ ```
163
+
164
+ If `view.setup_hint` exists, show it as a tip.
165
+
166
+ ## Key Points
167
+
168
+ - Always use `{"interactive": true}` (boolean) not a string
169
+ - The response `step` field tells you what stage you're at
170
+ - Use AskUserQuestion for team/project selection
171
+ - Store the selected IDs from previous responses to use in next calls
package/commands/prd.md CHANGED
@@ -1,4 +1,5 @@
1
1
  ---
2
+ name: flux:prd
2
3
  description: Create or refine PRDs through guided interview
3
4
  allowed-tools: mcp__flux__*, AskUserQuestion
4
5
  ---
@@ -1,4 +1,3 @@
1
- #!/usr/bin/env node
2
1
  import { createRequire } from "node:module";
3
2
  var __create = Object.create;
4
3
  var __getProtoOf = Object.getPrototypeOf;
@@ -22481,7 +22480,6 @@ var config2 = {
22481
22480
  };
22482
22481
 
22483
22482
  // src/server/db/index.ts
22484
- import { Database } from "bun:sqlite";
22485
22483
  import { existsSync as existsSync2, mkdirSync } from "node:fs";
22486
22484
 
22487
22485
  // src/server/utils/logger.ts
@@ -22584,6 +22582,44 @@ CREATE INDEX IF NOT EXISTS idx_tasks_status ON tasks(status);
22584
22582
  CREATE INDEX IF NOT EXISTS idx_criteria_parent ON acceptance_criteria(parent_type, parent_id);
22585
22583
  `;
22586
22584
 
22585
+ // src/server/db/sqlite.ts
22586
+ var isBun = typeof process !== "undefined" && !!process.versions?.bun;
22587
+ var DatabaseImpl;
22588
+ if (isBun) {
22589
+ const bunSqlite = "bun:" + "sqlite";
22590
+ const { Database: BunDatabase } = await import(bunSqlite);
22591
+ DatabaseImpl = BunDatabase;
22592
+ } else {
22593
+ const BetterSqlite3 = (await import("better-sqlite3")).default;
22594
+
22595
+ class BetterSqlite3Wrapper {
22596
+ db;
22597
+ constructor(path) {
22598
+ this.db = new BetterSqlite3(path);
22599
+ }
22600
+ query(sql) {
22601
+ const stmt = this.db.prepare(sql);
22602
+ return {
22603
+ get: (...params) => stmt.get(...params),
22604
+ all: (...params) => stmt.all(...params),
22605
+ run: (...params) => {
22606
+ stmt.run(...params);
22607
+ }
22608
+ };
22609
+ }
22610
+ exec(sql) {
22611
+ this.db.exec(sql);
22612
+ }
22613
+ run(sql) {
22614
+ this.db.exec(sql);
22615
+ }
22616
+ close() {
22617
+ this.db.close();
22618
+ }
22619
+ }
22620
+ DatabaseImpl = BetterSqlite3Wrapper;
22621
+ }
22622
+
22587
22623
  // src/server/db/ids.ts
22588
22624
  function generateId(prefix = "") {
22589
22625
  const timestamp = Date.now().toString(36);
@@ -22686,7 +22722,7 @@ function initDb() {
22686
22722
  mkdirSync(config2.fluxPath, { recursive: true });
22687
22723
  }
22688
22724
  logger.info(`Opening database: ${config2.dbPath}`);
22689
- db = new Database(config2.dbPath);
22725
+ db = new DatabaseImpl(config2.dbPath);
22690
22726
  db.run("PRAGMA foreign_keys = ON");
22691
22727
  db.exec(SCHEMA);
22692
22728
  logger.info("Database initialized successfully");
@@ -83874,6 +83910,18 @@ var PRD_STATUSES_WITH_LABELS = {
83874
83910
  COMPLETED: null,
83875
83911
  ARCHIVED: null
83876
83912
  };
83913
+ var FLUX_MILESTONE_LABEL_PREFIX = "flux:milestone:";
83914
+ function getMilestoneLabel(tag) {
83915
+ return `${FLUX_MILESTONE_LABEL_PREFIX}${tag}`;
83916
+ }
83917
+ function extractTagFromLabels(labels) {
83918
+ for (const label of labels) {
83919
+ if (label.startsWith(FLUX_MILESTONE_LABEL_PREFIX)) {
83920
+ return label.substring(FLUX_MILESTONE_LABEL_PREFIX.length);
83921
+ }
83922
+ }
83923
+ return;
83924
+ }
83877
83925
 
83878
83926
  // src/server/adapters/linear/mappers/epic.ts
83879
83927
  function toLinearEpicState(status) {
@@ -84025,6 +84073,7 @@ class LinearAdapter {
84025
84073
  title: issue2.title,
84026
84074
  description: issue2.description,
84027
84075
  status: toFluxPrdStatusFromIssue(issue2.stateName, issue2.labels),
84076
+ tag: extractTagFromLabels(issue2.labels),
84028
84077
  createdAt: issue2.createdAt.toISOString(),
84029
84078
  updatedAt: issue2.updatedAt.toISOString()
84030
84079
  };
@@ -84064,13 +84113,17 @@ class LinearAdapter {
84064
84113
  return issue2.labels.includes(this.config.defaultLabels.task);
84065
84114
  }
84066
84115
  async createPrd(input) {
84067
- const prdLabelId = await this.getLabelId(this.config.defaultLabels.prd);
84116
+ const labelIds = [await this.getLabelId(this.config.defaultLabels.prd)];
84117
+ if (input.tag) {
84118
+ const tagLabelId = await this.getOrCreateLabel(getMilestoneLabel(input.tag));
84119
+ labelIds.push(tagLabelId);
84120
+ }
84068
84121
  const createResult = await this.client.execute(() => this.client.client.createIssue({
84069
84122
  title: input.title,
84070
84123
  description: input.description,
84071
84124
  teamId: this.config.teamId,
84072
84125
  projectId: this.config.projectId,
84073
- labelIds: [prdLabelId]
84126
+ labelIds
84074
84127
  }));
84075
84128
  const rawIssue = await this.client.execute(() => createResult.issue);
84076
84129
  if (!rawIssue) {
@@ -84092,7 +84145,10 @@ class LinearAdapter {
84092
84145
  updatePayload.description = input.description;
84093
84146
  if (input.status !== undefined) {
84094
84147
  updatePayload.stateId = await this.getStateId(toLinearPrdIssueState(input.status));
84095
- updatePayload.labelIds = await this.buildPrdLabelIds(issue2.labels, input.status);
84148
+ }
84149
+ if (input.status !== undefined || input.tag !== undefined) {
84150
+ const currentStatus = toFluxPrdStatusFromIssue(issue2.stateName, issue2.labels);
84151
+ updatePayload.labelIds = await this.buildPrdLabelIdsWithTag(issue2.labels, input.status ?? currentStatus, input.tag !== undefined ? input.tag : extractTagFromLabels(issue2.labels));
84096
84152
  }
84097
84153
  await this.client.execute(() => issue2._raw.update(updatePayload));
84098
84154
  const updated = await this.fetchIssue(ref);
@@ -84114,6 +84170,23 @@ class LinearAdapter {
84114
84170
  }
84115
84171
  return labelIds;
84116
84172
  }
84173
+ async buildPrdLabelIdsWithTag(currentLabels, newStatus, newTag) {
84174
+ const statusLabels = getAllStatusLabels();
84175
+ const newStatusLabel = getStatusLabelForPrdStatus(newStatus);
84176
+ const labelsToKeep = currentLabels.filter((l) => !statusLabels.includes(l) && !l.startsWith(FLUX_MILESTONE_LABEL_PREFIX));
84177
+ if (newStatusLabel) {
84178
+ labelsToKeep.push(newStatusLabel);
84179
+ }
84180
+ if (newTag) {
84181
+ labelsToKeep.push(getMilestoneLabel(newTag));
84182
+ }
84183
+ const labelIds = [];
84184
+ for (const labelName of labelsToKeep) {
84185
+ const id = await this.getOrCreateLabel(labelName);
84186
+ labelIds.push(id);
84187
+ }
84188
+ return labelIds;
84189
+ }
84117
84190
  async getOrCreateLabel(labelName) {
84118
84191
  try {
84119
84192
  return await this.getLabelId(labelName);
@@ -84139,9 +84212,18 @@ class LinearAdapter {
84139
84212
  const limit = pagination?.limit ?? 50;
84140
84213
  const offset = pagination?.offset ?? 0;
84141
84214
  const linearFilter = {
84142
- labels: { name: { eq: this.config.defaultLabels.prd } },
84143
84215
  project: { id: { eq: this.config.projectId } }
84144
84216
  };
84217
+ if (filters?.tag) {
84218
+ linearFilter.labels = {
84219
+ and: [
84220
+ { name: { eq: this.config.defaultLabels.prd } },
84221
+ { name: { eq: getMilestoneLabel(filters.tag) } }
84222
+ ]
84223
+ };
84224
+ } else {
84225
+ linearFilter.labels = { name: { eq: this.config.defaultLabels.prd } };
84226
+ }
84145
84227
  if (filters?.status) {
84146
84228
  linearFilter.state = {
84147
84229
  name: { eq: toLinearPrdIssueState(filters.status) }
@@ -85431,14 +85513,20 @@ function clearAdapterCache() {
85431
85513
  var LINEAR_API_KEY_ENV = "LINEAR_API_KEY";
85432
85514
  var inputSchema = exports_external.object({
85433
85515
  apiKey: exports_external.string().optional(),
85434
- teamId: exports_external.string().min(1, "Team ID is required"),
85516
+ teamId: exports_external.string().optional(),
85435
85517
  projectName: exports_external.string().optional(),
85436
85518
  existingProjectId: exports_external.string().optional(),
85437
85519
  prdLabel: exports_external.string().optional().default("prd"),
85438
85520
  epicLabel: exports_external.string().optional().default("epic"),
85439
- taskLabel: exports_external.string().optional().default("task")
85440
- }).refine((data) => data.projectName || data.existingProjectId, {
85441
- message: "Either projectName or existingProjectId must be provided"
85521
+ taskLabel: exports_external.string().optional().default("task"),
85522
+ interactive: exports_external.boolean().optional().default(false)
85523
+ }).refine((data) => {
85524
+ if (data.interactive) {
85525
+ return true;
85526
+ }
85527
+ return data.teamId && data.teamId.length > 0 && (data.projectName || data.existingProjectId);
85528
+ }, {
85529
+ message: "teamId and either projectName or existingProjectId are required (unless interactive mode)"
85442
85530
  });
85443
85531
  function resolveApiKey(inputApiKey) {
85444
85532
  if (inputApiKey) {
@@ -85516,19 +85604,72 @@ async function createDefaultView(client, teamId, projectId, projectName, labels)
85516
85604
  };
85517
85605
  }
85518
85606
  }
85607
+ async function listTeams(client) {
85608
+ const teamsResult = await client.teams();
85609
+ return teamsResult.nodes.map((team) => ({
85610
+ id: team.id,
85611
+ name: team.name,
85612
+ key: team.key
85613
+ }));
85614
+ }
85615
+ async function listProjects(client, teamId) {
85616
+ const projectsResult = await client.projects({
85617
+ filter: {
85618
+ accessibleTeams: { some: { id: { eq: teamId } } },
85619
+ state: { nin: ["canceled"] }
85620
+ }
85621
+ });
85622
+ return projectsResult.nodes.map((project) => ({
85623
+ id: project.id,
85624
+ name: project.name,
85625
+ description: project.description ?? null,
85626
+ state: project.state,
85627
+ updatedAt: project.updatedAt.toISOString()
85628
+ }));
85629
+ }
85519
85630
  async function handler(input) {
85520
85631
  const parsed = inputSchema.parse(input);
85521
85632
  const { apiKey, source: apiKeySource } = resolveApiKey(parsed.apiKey);
85522
85633
  const client = new LinearClient({ apiKey });
85634
+ let viewerName;
85635
+ let viewerEmail;
85523
85636
  try {
85524
85637
  const viewer = await client.viewer;
85525
85638
  if (!viewer) {
85526
85639
  throw new Error("Invalid API key");
85527
85640
  }
85641
+ viewerName = viewer.name;
85642
+ viewerEmail = viewer.email;
85528
85643
  } catch (error48) {
85529
85644
  const message = error48 instanceof Error ? error48.message : String(error48);
85530
85645
  throw new Error(`Invalid Linear API key (source: ${apiKeySource}): ${message}`);
85531
85646
  }
85647
+ if (parsed.interactive && !parsed.teamId) {
85648
+ const teams = await listTeams(client);
85649
+ return {
85650
+ step: "select_team",
85651
+ user: { name: viewerName, email: viewerEmail },
85652
+ api_key_source: apiKeySource,
85653
+ teams
85654
+ };
85655
+ }
85656
+ if (parsed.interactive && parsed.teamId && !parsed.projectName && !parsed.existingProjectId) {
85657
+ const team2 = await client.team(parsed.teamId);
85658
+ if (!team2) {
85659
+ throw new Error(`Team ${parsed.teamId} not found`);
85660
+ }
85661
+ const projects = await listProjects(client, parsed.teamId);
85662
+ return {
85663
+ step: "select_project",
85664
+ user: { name: viewerName, email: viewerEmail },
85665
+ api_key_source: apiKeySource,
85666
+ team: { id: team2.id, name: team2.name, key: team2.key },
85667
+ projects
85668
+ };
85669
+ }
85670
+ if (!parsed.teamId) {
85671
+ throw new Error("teamId is required");
85672
+ }
85532
85673
  const team = await client.team(parsed.teamId);
85533
85674
  if (!team) {
85534
85675
  throw new Error(`Team ${parsed.teamId} not found`);
@@ -85605,7 +85746,7 @@ async function handler(input) {
85605
85746
  }
85606
85747
  var configureLinearTool = {
85607
85748
  name: "configure_linear",
85608
- description: "Configure Linear integration for the Flux project. " + "API Key Resolution (priority order): 1) LINEAR_API_KEY environment variable (recommended), 2) Existing .flux/linear-config.json, 3) apiKey parameter (not recommended - visible in logs). " + "Required: teamId (Linear team ID), and either projectName (to create new) or existingProjectId (to use existing). " + "Optional: apiKey, prdLabel (default 'prd'), epicLabel (default 'epic'), taskLabel (default 'task'). " + "Creates a Linear Project, ensures labels exist, creates 'Flux' custom view for new projects, saves config to .flux/linear-config.json. " + "Returns {success, message, api_key_source, team, project, labels, view}. " + "Setup: export LINEAR_API_KEY=lin_api_xxx before running.",
85749
+ description: "Configure Linear integration for the Flux project. " + "Use interactive mode for guided setup: " + "1) Call with {interactive: true} to get list of teams. " + "2) Call with {interactive: true, teamId: '...'} to get list of projects. " + "3) Call with full params to configure. " + "API Key Resolution (priority order): 1) LINEAR_API_KEY environment variable (recommended), 2) Existing .flux/linear-config.json, 3) apiKey parameter (not recommended - visible in logs). " + "Required (non-interactive): teamId (Linear team ID), and either projectName (to create new) or existingProjectId (to use existing). " + "Optional: apiKey, prdLabel (default 'prd'), epicLabel (default 'epic'), taskLabel (default 'task'). " + "Creates a Linear Project, ensures labels exist, creates 'Flux' custom view for new projects, saves config to .flux/linear-config.json. " + "Returns {success, message, api_key_source, team, project, labels, view} on final configuration. " + "Setup: export LINEAR_API_KEY=lin_api_xxx before running.",
85609
85750
  inputSchema,
85610
85751
  handler
85611
85752
  };
@@ -86184,14 +86325,7 @@ var getStatsTool = {
86184
86325
  handler: handler8
86185
86326
  };
86186
86327
  // src/version.ts
86187
- import { readFileSync as readFileSync6 } from "node:fs";
86188
- import { dirname as dirname2, join as join2 } from "node:path";
86189
- import { fileURLToPath } from "node:url";
86190
- var __filename2 = fileURLToPath(import.meta.url);
86191
- var __dirname2 = dirname2(__filename2);
86192
- var packageJsonPath = join2(__dirname2, "..", "package.json");
86193
- var packageJson = JSON.parse(readFileSync6(packageJsonPath, "utf-8"));
86194
- var VERSION = packageJson.version;
86328
+ var VERSION = "0.1.0";
86195
86329
 
86196
86330
  // src/server/tools/get-version.ts
86197
86331
  var inputSchema10 = exports_external.object({});
@@ -86208,15 +86342,15 @@ var getVersionTool = {
86208
86342
  handler: handler9
86209
86343
  };
86210
86344
  // src/server/tools/init-project.ts
86211
- import { existsSync as existsSync8, mkdirSync as mkdirSync4, readFileSync as readFileSync7, writeFileSync as writeFileSync4 } from "node:fs";
86212
- import { dirname as dirname3, join as join3 } from "node:path";
86345
+ import { existsSync as existsSync8, mkdirSync as mkdirSync4, readFileSync as readFileSync6, writeFileSync as writeFileSync4 } from "node:fs";
86346
+ import { dirname as dirname2, join as join2 } from "node:path";
86213
86347
  function getStatusLineBinaryPath() {
86214
- const thisDir = dirname3(new URL(import.meta.url).pathname);
86215
- return join3(thisDir, "..", "..", "..", "bin", "flux-status");
86348
+ const thisDir = dirname2(new URL(import.meta.url).pathname);
86349
+ return join2(thisDir, "..", "..", "..", "bin", "flux-status");
86216
86350
  }
86217
86351
  function setupClaudeSettings(projectRoot) {
86218
- const claudeDir = join3(projectRoot, ".claude");
86219
- const settingsPath = join3(claudeDir, "settings.local.json");
86352
+ const claudeDir = join2(projectRoot, ".claude");
86353
+ const settingsPath = join2(claudeDir, "settings.local.json");
86220
86354
  const statusBinaryPath = getStatusLineBinaryPath();
86221
86355
  if (!existsSync8(statusBinaryPath)) {
86222
86356
  return;
@@ -86227,7 +86361,7 @@ function setupClaudeSettings(projectRoot) {
86227
86361
  let settings = {};
86228
86362
  if (existsSync8(settingsPath)) {
86229
86363
  try {
86230
- settings = JSON.parse(readFileSync7(settingsPath, "utf-8"));
86364
+ settings = JSON.parse(readFileSync6(settingsPath, "utf-8"));
86231
86365
  } catch {
86232
86366
  settings = {};
86233
86367
  }
@@ -86393,7 +86527,7 @@ var queryEntitiesTool = {
86393
86527
  handler: handler11
86394
86528
  };
86395
86529
  // src/server/tools/render-status.ts
86396
- import { existsSync as existsSync9, readFileSync as readFileSync8 } from "node:fs";
86530
+ import { existsSync as existsSync9, readFileSync as readFileSync7 } from "node:fs";
86397
86531
 
86398
86532
  // src/utils/display.ts
86399
86533
  function renderProgressBar(percentage, width) {
@@ -86506,7 +86640,7 @@ function getProjectInfo() {
86506
86640
  return null;
86507
86641
  }
86508
86642
  try {
86509
- const content = readFileSync8(config2.projectJsonPath, "utf-8");
86643
+ const content = readFileSync7(config2.projectJsonPath, "utf-8");
86510
86644
  const project = JSON.parse(content);
86511
86645
  return {
86512
86646
  name: project.name || "Unnamed Project",
package/manifest.json ADDED
@@ -0,0 +1,15 @@
1
+ {
2
+ "$schema": "./manifest.schema.json",
3
+ "version": "1.0.0",
4
+ "structure": {
5
+ "commands": ["flux.md", "prd.md", "breakdown.md", "implement.md"],
6
+ "skills": [
7
+ "agent-creator",
8
+ "epic-template",
9
+ "flux-orchestrator",
10
+ "prd-template"
11
+ ],
12
+ "agents": ["coder.md", "critic.md", "researcher.md", "verifier.md"],
13
+ "hooks": []
14
+ }
15
+ }
package/package.json CHANGED
@@ -1,32 +1,38 @@
1
1
  {
2
2
  "name": "@cliangdev/flux-plugin",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Claude Code plugin for AI-first workflow orchestration with MCP server",
5
5
  "type": "module",
6
6
  "main": "./dist/server/index.js",
7
7
  "bin": {
8
- "flux-plugin": "./dist/server/index.js"
8
+ "flux-plugin": "./bin/install.cjs"
9
9
  },
10
10
  "files": [
11
+ "bin/",
11
12
  "dist/",
12
13
  "skills/",
13
- "commands/"
14
+ "commands/",
15
+ "agents/",
16
+ "manifest.json"
14
17
  ],
15
18
  "scripts": {
16
19
  "dev": "bun run src/server/index.ts",
17
- "build": "bun build src/server/index.ts --outdir dist/server --target node",
20
+ "build": "bun build src/server/index.ts --outdir dist/server --target node --external better-sqlite3",
18
21
  "postbuild": "node -e \"const fs=require('fs');const f='dist/server/index.js';const c=fs.readFileSync(f,'utf-8');if(!c.startsWith('#!/usr/bin/env node')){fs.writeFileSync(f,'#!/usr/bin/env node\\n'+c)}\"",
19
22
  "build:compile": "bun build --compile --outfile bin/flux-server src/server/index.ts && bun build --compile --outfile bin/flux-status src/status-line/index.ts",
20
23
  "build:compile:server": "bun build --compile --outfile bin/flux-server src/server/index.ts",
21
24
  "build:compile:status": "bun build --compile --outfile bin/flux-status src/status-line/index.ts",
22
- "prepublishOnly": "bun run build",
25
+ "validate": "node scripts/validate-structure.cjs",
26
+ "test:integration": "bun test scripts/__tests__/integration.test.ts --timeout 120000",
27
+ "prepublishOnly": "bun run validate && bun run build && bun run test:integration",
23
28
  "test": "bun test",
24
29
  "test:linear-description": "bun run src/server/adapters/__tests__/linear-description-test.ts",
25
30
  "typecheck": "tsc --noEmit",
26
31
  "lint": "biome check .",
27
32
  "lint:fix": "biome check --write .",
28
33
  "format": "biome format --write .",
29
- "verify-release": "bun run scripts/verify-release.ts"
34
+ "verify-release": "bun run scripts/verify-release.ts",
35
+ "release": "./scripts/release.sh"
30
36
  },
31
37
  "repository": {
32
38
  "type": "git",
@@ -47,12 +53,14 @@
47
53
  "license": "MIT",
48
54
  "devDependencies": {
49
55
  "@biomejs/biome": "^2.3.11",
56
+ "@types/better-sqlite3": "^7.6.13",
50
57
  "@types/bun": "^1.3.6",
51
58
  "typescript": "^5.0.0"
52
59
  },
53
60
  "dependencies": {
54
61
  "@linear/sdk": "^70.0.0",
55
62
  "@modelcontextprotocol/sdk": "^1.25.2",
63
+ "better-sqlite3": "^12.6.2",
56
64
  "chalk": "^5.4.1",
57
65
  "zod": "^4.3.5"
58
66
  },
@@ -1,5 +1,7 @@
1
1
  ---
2
+ name: flux:agent-creator
2
3
  description: Guide for creating effective subagents. Use when users want to create a new agent that extends Claude's capabilities with specialized knowledge, workflows, or tool integrations.
4
+ user-invocable: false
3
5
  ---
4
6
 
5
7
  # Agent Creator Skill
@@ -1,5 +1,7 @@
1
1
  ---
2
+ name: flux:epic-template
2
3
  description: Epic and task structure patterns for Flux. Use when breaking PRDs into epics and tasks. Epics should be self-contained with clear acceptance criteria.
4
+ user-invocable: false
3
5
  ---
4
6
 
5
7
  # Epic Template Skill