@cliangdev/flux-plugin 0.0.0-dev.df9c61f → 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.
package/bin/install.cjs CHANGED
@@ -167,24 +167,24 @@ ${cyan} ███████╗██╗ ██╗ ██╗██╗
167
167
  }
168
168
  }
169
169
 
170
- const claudeJsonPath = isGlobal
170
+ const mcpConfigPath = isGlobal
171
171
  ? path.join(os.homedir(), ".claude.json")
172
- : path.join(process.cwd(), ".claude.json");
172
+ : path.join(process.cwd(), ".mcp.json");
173
173
 
174
- const claudeJson = readJson(claudeJsonPath);
174
+ const mcpConfig = readJson(mcpConfigPath);
175
175
 
176
- if (!claudeJson.mcpServers) {
177
- claudeJson.mcpServers = {};
176
+ if (!mcpConfig.mcpServers) {
177
+ mcpConfig.mcpServers = {};
178
178
  }
179
179
 
180
- claudeJson.mcpServers.flux = {
180
+ mcpConfig.mcpServers.flux = {
181
181
  command: "npx",
182
- args: ["-y", "@cliangdev/flux-plugin", "serve"],
182
+ args: ["-y", `@cliangdev/flux-plugin@${pkg.version}`, "serve"],
183
183
  };
184
184
 
185
- writeJson(claudeJsonPath, claudeJson);
185
+ writeJson(mcpConfigPath, mcpConfig);
186
186
  console.log(
187
- ` ${green}✓${reset} Configured MCP server in ${isGlobal ? "~/.claude.json" : "./.claude.json"}`
187
+ ` ${green}✓${reset} Configured MCP server in ${isGlobal ? "~/.claude.json" : "./.mcp.json"}`
188
188
  );
189
189
 
190
190
  const versionFile = path.join(claudeDir, "flux-version");
package/commands/flux.md CHANGED
@@ -1,113 +1,156 @@
1
1
  ---
2
2
  name: flux
3
3
  description: AI-first workflow orchestration for spec-driven development
4
- allowed-tools: mcp__flux__*
4
+ allowed-tools: mcp__plugin_flux_flux__*, AskUserQuestion, Read, Write
5
5
  ---
6
6
 
7
- # Flux Orchestrator
7
+ # Flux Command
8
+
9
+ You are the Flux orchestrator. Detect project state and guide the user to the appropriate next action.
10
+
11
+ ## Subcommands
8
12
 
9
- You are the Flux orchestrator. Your job is to detect the project state and guide the user through the appropriate workflow.
13
+ - `/flux version` - Show plugin version (call `get_version`)
14
+ - `/flux linear` - Connect to Linear (delegate to `/flux:linear`)
10
15
 
11
- ## Step 0: Check for Version Subcommand
16
+ ## Main Flow
17
+
18
+ ### Step 1: Get Project Context
12
19
 
13
- First, check if the user requested version information:
14
- - `/flux version` - Show version information
20
+ Call `get_project_context` to check project state.
15
21
 
16
- If the argument is "version":
22
+ ### Step 2: Route Based on State
17
23
 
18
- 1. Call the `get_version` MCP tool
19
- 2. Display the version information in a friendly format:
20
- ```
21
- Flux Plugin v{version}
22
- Package: @cliangdev/{name}
23
- ```
24
- 3. Exit (do not proceed to project state check)
24
+ **If `initialized: false`:**
25
+ Guide through initialization (see Initialization Flow below)
25
26
 
26
- ## Step 1: Check Project State
27
+ **If `initialized: true`:**
28
+ → Call `render_status` with `{view: "summary"}` to show current state
29
+ → Determine next action based on workflow state (see Workflow States)
27
30
 
28
- If no subcommand was provided, call the `get_project_context` MCP tool to check if this is a Flux project.
31
+ ## Initialization Flow
29
32
 
30
- ## Step 2: Handle Result
33
+ Use the `AskUserQuestion` tool for all questions during initialization.
31
34
 
32
- ### If `initialized: false` (New Project)
35
+ ### Step 1: Confirm Initialization
33
36
 
34
- This is a new project. Guide the user through initialization:
37
+ Use AskUserQuestion:
38
+ ```json
39
+ {
40
+ "questions": [{
41
+ "question": "No Flux project found. Would you like to initialize one?",
42
+ "header": "Initialize",
43
+ "options": [
44
+ {"label": "Yes", "description": "Create a new Flux project in this directory"},
45
+ {"label": "No", "description": "Cancel initialization"}
46
+ ],
47
+ "multiSelect": false
48
+ }]
49
+ }
50
+ ```
35
51
 
36
- 1. Ask: "No Flux project found in this directory. Would you like to initialize one?"
37
- - If no, end with: "Run `/flux` when you're ready to set up Flux."
52
+ If "No", exit with: "Run `/flux` when you're ready to set up Flux."
38
53
 
39
- 2. Ask: "What is the name of this project?"
40
- - Wait for response
54
+ ### Step 2: Collect Project Details
41
55
 
42
- 3. Ask: "Brief vision - what does this project do? (one sentence)"
43
- - Wait for response
56
+ Use AskUserQuestion with text input (user will select "Other" to type):
57
+ - Ask for project name
58
+ - Ask for project vision (brief description)
44
59
 
45
- 4. Call `init_project` tool with the name and vision
60
+ ### Step 3: Select Storage Backend
46
61
 
47
- 5. Display success message:
48
- ```
49
- Flux project initialized!
62
+ Use AskUserQuestion:
63
+ ```json
64
+ {
65
+ "questions": [{
66
+ "question": "Where should Flux store data?",
67
+ "header": "Storage",
68
+ "options": [
69
+ {"label": "Local (Recommended)", "description": "SQLite database in .flux/ - offline-first, no setup required"},
70
+ {"label": "Linear", "description": "Sync with Linear for team collaboration and issue tracking"}
71
+ ],
72
+ "multiSelect": false
73
+ }]
74
+ }
75
+ ```
50
76
 
51
- Project: {name}
52
- Vision: {vision}
53
- Reference prefix: {ref_prefix}
77
+ ### Step 4: Ask About Tool Permissions
54
78
 
55
- Created:
56
- - .flux/project.json
57
- - .flux/flux.db
79
+ Use AskUserQuestion:
80
+ ```json
81
+ {
82
+ "questions": [{
83
+ "question": "Add Flux tools to allow list? This prevents permission prompts for Flux operations.",
84
+ "header": "Permissions",
85
+ "options": [
86
+ {"label": "Yes (Recommended)", "description": "Allow all Flux MCP tools without prompting"},
87
+ {"label": "No", "description": "Ask for permission each time"}
88
+ ],
89
+ "multiSelect": false
90
+ }]
91
+ }
92
+ ```
58
93
 
59
- Next: Run /flux:prd to start planning your first PRD.
60
- ```
94
+ If "Yes", update the settings file:
61
95
 
62
- ### If Project Exists (Initialized)
96
+ 1. Read `.claude/settings.local.json` (create if doesn't exist)
97
+ 2. Parse JSON (or start with `{"permissions": {"allow": []}}` if empty/missing)
98
+ 3. Add `"mcp__plugin_flux_flux__*"` to `permissions.allow` array if not already present
99
+ 4. Write back to `.claude/settings.local.json`
63
100
 
64
- This is an existing Flux project. Show status and suggest next action:
101
+ Example result:
102
+ ```json
103
+ {
104
+ "permissions": {
105
+ "allow": ["mcp__plugin_flux_flux__*"]
106
+ }
107
+ }
108
+ ```
65
109
 
66
- 1. Call `get_stats` to get entity counts
67
-
68
- 2. Display status summary:
69
- ```
70
- Project: {name}
71
- Vision: {vision}
72
-
73
- Status:
74
- - PRDs: {total} ({draft} draft, {pending_review} in review, {approved} approved)
75
- - Epics: {total} ({pending} pending, {in_progress} active, {completed} done)
76
- - Tasks: {total} ({pending} pending, {in_progress} active, {completed} done)
77
- ```
78
-
79
- 3. Determine and suggest next action based on state:
80
-
81
- **If no PRDs exist:**
82
- > "No PRDs yet. Run `/flux:prd` to create your first product requirements document."
83
-
84
- **If PRDs exist with DRAFT status:**
85
- > "You have draft PRDs. Review them and run `/flux:prd refine` or submit for review."
86
-
87
- **If PRDs in PENDING_REVIEW:**
88
- > "PRDs pending review. The critique agent will analyze feasibility and risks."
89
-
90
- **If PRDs in REVIEWED status:**
91
- > "Critique complete. Review feedback, then approve or revise the PRD."
92
-
93
- **If PRDs APPROVED but no epics:**
94
- > "PRDs approved! Run `/flux:breakdown` to break them into epics and tasks."
95
-
96
- **If PRDs BREAKDOWN_READY with epics/tasks:**
97
- > "Ready to implement! Run `/flux:implement` to start working on tasks."
98
-
99
- **If tasks exist with PENDING status:**
100
- > "Tasks ready. Run `/flux:implement` to start working."
101
-
102
- **If tasks IN_PROGRESS:**
103
- > "Implementation in progress. Run `/flux:implement` to continue."
104
-
105
- **If all tasks COMPLETED:**
106
- > "All tasks complete! Review your work and create a PR."
110
+ Confirm to user: "Flux tools added to allow list. No more permission prompts for Flux operations."
111
+
112
+ ### Step 5: Initialize Project
113
+
114
+ Call `init_project` with collected values:
115
+ - `name`: project name
116
+ - `vision`: project vision
117
+ - `adapter`: "local" or "linear"
118
+
119
+ ### Step 6: Next Steps
120
+
121
+ Display success message, then:
122
+
123
+ - **If Local**: "Project initialized! Run `/flux:prd` to create your first PRD."
124
+ - **If Linear**: "Project initialized! Now run `/flux:linear` to connect to Linear."
125
+
126
+ ## Workflow States
127
+
128
+ Detect current state and suggest the appropriate next action:
129
+
130
+ | State | Detection | Next Action |
131
+ |-------|-----------|-------------|
132
+ | No PRDs | `prds.total == 0` | `/flux:prd` to create first PRD |
133
+ | Draft PRDs | PRDs in DRAFT | Review and refine or submit for review |
134
+ | Pending Review | PRDs in PENDING_REVIEW | Critique agent will analyze |
135
+ | Reviewed | PRDs in REVIEWED | Address feedback, approve or revise |
136
+ | Approved | PRDs in APPROVED, no epics | `/flux:breakdown` to create epics |
137
+ | Breakdown Ready | PRDs in BREAKDOWN_READY | `/flux:implement` to start coding |
138
+ | In Progress | Tasks IN_PROGRESS | Continue with `/flux:implement` |
139
+ | Complete | All tasks COMPLETED | Create PR |
140
+
141
+ ## Confidence-Based Autonomy
142
+
143
+ When determining actions:
144
+
145
+ | Confidence | Behavior |
146
+ |------------|----------|
147
+ | > 80% | Auto-execute, inform user |
148
+ | 50-80% | Suggest action, wait for confirmation |
149
+ | < 50% | Ask clarifying question |
107
150
 
108
151
  ## Guidelines
109
152
 
110
- - Be concise - users want quick status, not essays
111
- - One question at a time during initialization
112
- - Show actionable next steps
113
- - Use the MCP tools, don't read filesystem directly
153
+ - Use `AskUserQuestion` tool for all user choices during initialization
154
+ - Be concise - show status and one clear next action
155
+ - Use `render_status` for visual project overview
156
+ - Apply confidence-based autonomy for decisions
@@ -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
@@ -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;
@@ -22587,7 +22586,8 @@ CREATE INDEX IF NOT EXISTS idx_criteria_parent ON acceptance_criteria(parent_typ
22587
22586
  var isBun = typeof process !== "undefined" && !!process.versions?.bun;
22588
22587
  var DatabaseImpl;
22589
22588
  if (isBun) {
22590
- const { Database: BunDatabase } = await import("bun:sqlite");
22589
+ const bunSqlite = "bun:" + "sqlite";
22590
+ const { Database: BunDatabase } = await import(bunSqlite);
22591
22591
  DatabaseImpl = BunDatabase;
22592
22592
  } else {
22593
22593
  const BetterSqlite3 = (await import("better-sqlite3")).default;
@@ -83910,6 +83910,18 @@ var PRD_STATUSES_WITH_LABELS = {
83910
83910
  COMPLETED: null,
83911
83911
  ARCHIVED: null
83912
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
+ }
83913
83925
 
83914
83926
  // src/server/adapters/linear/mappers/epic.ts
83915
83927
  function toLinearEpicState(status) {
@@ -84061,6 +84073,7 @@ class LinearAdapter {
84061
84073
  title: issue2.title,
84062
84074
  description: issue2.description,
84063
84075
  status: toFluxPrdStatusFromIssue(issue2.stateName, issue2.labels),
84076
+ tag: extractTagFromLabels(issue2.labels),
84064
84077
  createdAt: issue2.createdAt.toISOString(),
84065
84078
  updatedAt: issue2.updatedAt.toISOString()
84066
84079
  };
@@ -84100,13 +84113,17 @@ class LinearAdapter {
84100
84113
  return issue2.labels.includes(this.config.defaultLabels.task);
84101
84114
  }
84102
84115
  async createPrd(input) {
84103
- 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
+ }
84104
84121
  const createResult = await this.client.execute(() => this.client.client.createIssue({
84105
84122
  title: input.title,
84106
84123
  description: input.description,
84107
84124
  teamId: this.config.teamId,
84108
84125
  projectId: this.config.projectId,
84109
- labelIds: [prdLabelId]
84126
+ labelIds
84110
84127
  }));
84111
84128
  const rawIssue = await this.client.execute(() => createResult.issue);
84112
84129
  if (!rawIssue) {
@@ -84128,7 +84145,10 @@ class LinearAdapter {
84128
84145
  updatePayload.description = input.description;
84129
84146
  if (input.status !== undefined) {
84130
84147
  updatePayload.stateId = await this.getStateId(toLinearPrdIssueState(input.status));
84131
- 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));
84132
84152
  }
84133
84153
  await this.client.execute(() => issue2._raw.update(updatePayload));
84134
84154
  const updated = await this.fetchIssue(ref);
@@ -84150,6 +84170,23 @@ class LinearAdapter {
84150
84170
  }
84151
84171
  return labelIds;
84152
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
+ }
84153
84190
  async getOrCreateLabel(labelName) {
84154
84191
  try {
84155
84192
  return await this.getLabelId(labelName);
@@ -84175,9 +84212,18 @@ class LinearAdapter {
84175
84212
  const limit = pagination?.limit ?? 50;
84176
84213
  const offset = pagination?.offset ?? 0;
84177
84214
  const linearFilter = {
84178
- labels: { name: { eq: this.config.defaultLabels.prd } },
84179
84215
  project: { id: { eq: this.config.projectId } }
84180
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
+ }
84181
84227
  if (filters?.status) {
84182
84228
  linearFilter.state = {
84183
84229
  name: { eq: toLinearPrdIssueState(filters.status) }
@@ -85467,14 +85513,20 @@ function clearAdapterCache() {
85467
85513
  var LINEAR_API_KEY_ENV = "LINEAR_API_KEY";
85468
85514
  var inputSchema = exports_external.object({
85469
85515
  apiKey: exports_external.string().optional(),
85470
- teamId: exports_external.string().min(1, "Team ID is required"),
85516
+ teamId: exports_external.string().optional(),
85471
85517
  projectName: exports_external.string().optional(),
85472
85518
  existingProjectId: exports_external.string().optional(),
85473
85519
  prdLabel: exports_external.string().optional().default("prd"),
85474
85520
  epicLabel: exports_external.string().optional().default("epic"),
85475
- taskLabel: exports_external.string().optional().default("task")
85476
- }).refine((data) => data.projectName || data.existingProjectId, {
85477
- 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)"
85478
85530
  });
85479
85531
  function resolveApiKey(inputApiKey) {
85480
85532
  if (inputApiKey) {
@@ -85552,19 +85604,72 @@ async function createDefaultView(client, teamId, projectId, projectName, labels)
85552
85604
  };
85553
85605
  }
85554
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
+ }
85555
85630
  async function handler(input) {
85556
85631
  const parsed = inputSchema.parse(input);
85557
85632
  const { apiKey, source: apiKeySource } = resolveApiKey(parsed.apiKey);
85558
85633
  const client = new LinearClient({ apiKey });
85634
+ let viewerName;
85635
+ let viewerEmail;
85559
85636
  try {
85560
85637
  const viewer = await client.viewer;
85561
85638
  if (!viewer) {
85562
85639
  throw new Error("Invalid API key");
85563
85640
  }
85641
+ viewerName = viewer.name;
85642
+ viewerEmail = viewer.email;
85564
85643
  } catch (error48) {
85565
85644
  const message = error48 instanceof Error ? error48.message : String(error48);
85566
85645
  throw new Error(`Invalid Linear API key (source: ${apiKeySource}): ${message}`);
85567
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
+ }
85568
85673
  const team = await client.team(parsed.teamId);
85569
85674
  if (!team) {
85570
85675
  throw new Error(`Team ${parsed.teamId} not found`);
@@ -85641,7 +85746,7 @@ async function handler(input) {
85641
85746
  }
85642
85747
  var configureLinearTool = {
85643
85748
  name: "configure_linear",
85644
- 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.",
85645
85750
  inputSchema,
85646
85751
  handler
85647
85752
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cliangdev/flux-plugin",
3
- "version": "0.0.0-dev.df9c61f",
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",
@@ -23,14 +23,16 @@
23
23
  "build:compile:server": "bun build --compile --outfile bin/flux-server src/server/index.ts",
24
24
  "build:compile:status": "bun build --compile --outfile bin/flux-status src/status-line/index.ts",
25
25
  "validate": "node scripts/validate-structure.cjs",
26
- "prepublishOnly": "bun run validate && bun run build",
26
+ "test:integration": "bun test scripts/__tests__/integration.test.ts --timeout 120000",
27
+ "prepublishOnly": "bun run validate && bun run build && bun run test:integration",
27
28
  "test": "bun test",
28
29
  "test:linear-description": "bun run src/server/adapters/__tests__/linear-description-test.ts",
29
30
  "typecheck": "tsc --noEmit",
30
31
  "lint": "biome check .",
31
32
  "lint:fix": "biome check --write .",
32
33
  "format": "biome format --write .",
33
- "verify-release": "bun run scripts/verify-release.ts"
34
+ "verify-release": "bun run scripts/verify-release.ts",
35
+ "release": "./scripts/release.sh"
34
36
  },
35
37
  "repository": {
36
38
  "type": "git",
@@ -6,18 +6,20 @@ user-invocable: false
6
6
 
7
7
  # Flux Orchestrator Skill
8
8
 
9
- This skill is automatically active when working in a Flux project. It provides context about available tools and workflow patterns.
9
+ This skill is automatically active when working in a Flux project. It provides context about available tools, workflow patterns, and integration options.
10
10
 
11
11
  ## Available MCP Tools
12
12
 
13
13
  ### Query Tools
14
- - `get_project_context` - Check if project initialized, get name/vision/prefix
14
+ - `get_project_context` - Check if project initialized, get name/vision/adapter type
15
15
  - `get_stats` - Get PRD/epic/task counts by status
16
16
  - `get_entity` - Fetch entity by ref with optional includes (criteria, tasks, dependencies)
17
17
  - `query_entities` - Search entities by type, status, parent ref
18
+ - `render_status` - Get formatted project status with progress bars
18
19
 
19
20
  ### Mutation Tools
20
21
  - `init_project` - Initialize new .flux/ directory with project.json and database
22
+ - `configure_linear` - Configure Linear integration (supports interactive mode)
21
23
  - `create_prd` - Create a new PRD
22
24
  - `create_epic` - Create an epic linked to a PRD
23
25
  - `create_task` - Create a task linked to an epic
@@ -31,102 +33,82 @@ This skill is automatically active when working in a Flux project. It provides c
31
33
  - `add_criteria` - Add acceptance criterion to epic or task
32
34
  - `mark_criteria_met` - Mark criterion as satisfied
33
35
 
34
- ## Workflow States
36
+ ## Available Commands
35
37
 
36
- The Flux project progresses through these states:
38
+ | Command | Purpose |
39
+ |---------|---------|
40
+ | `/flux` | Project init, status, and workflow routing |
41
+ | `/flux:linear` | Connect project to Linear (interactive setup) |
42
+ | `/flux:prd` | Create or refine PRDs |
43
+ | `/flux:breakdown` | Break approved PRD into epics and tasks |
44
+ | `/flux:implement` | Implement tasks with TDD workflow |
37
45
 
38
- 1. **Uninitialized** - No .flux/ directory
39
- - Action: Run `/flux` to initialize
46
+ ## Workflow States
40
47
 
41
- 2. **No PRDs** - Project initialized but empty
42
- - Action: Run `/flux:prd` to create first PRD
48
+ ```
49
+ Uninitialized Initialized PRD Draft Pending Review → Reviewed → Approved → Breakdown Ready → In Progress → Complete
50
+
51
+ (optional) Linear Connected
52
+ ```
43
53
 
44
- 3. **PRD Draft** - PRD created but needs review
45
- - Action: Review and submit for approval or refine
54
+ ### PRD Status Transitions
55
+ ```
56
+ DRAFT → PENDING_REVIEW → REVIEWED → APPROVED → BREAKDOWN_READY → COMPLETED
57
+ ↓ ↓
58
+ DRAFT (revise) DRAFT (revise)
59
+ ```
46
60
 
47
- 4. **PRD Pending Review** - PRD submitted for review
48
- - Action: Run critique agent, then approve or revise
61
+ ### Epic/Task Statuses
62
+ - `PENDING` `IN_PROGRESS` `COMPLETED`
63
+
64
+ ## Backend Adapters
49
65
 
50
- 5. **PRD Reviewed** - Critique complete
51
- - Action: Address feedback, then approve or revise to DRAFT
66
+ Flux supports multiple backends via the adapter pattern:
52
67
 
53
- 6. **PRD Approved** - Ready for epic breakdown
54
- - Action: Run `/flux:breakdown` to create epics
68
+ | Adapter | Storage | Use Case |
69
+ |---------|---------|----------|
70
+ | `local` | SQLite (.flux/flux.db) | Default, offline-first |
71
+ | `linear` | Linear API | Team collaboration, issue tracking |
55
72
 
56
- 7. **Breakdown Ready** - Epics and tasks created
57
- - Action: Run `/flux:implement` to start coding
73
+ ### Switching to Linear
58
74
 
59
- 8. **Implementation In Progress** - Tasks IN_PROGRESS
60
- - Action: Continue implementing current task
75
+ Use `configure_linear` with interactive mode:
76
+ 1. `{interactive: true}` returns teams list
77
+ 2. `{interactive: true, teamId: "..."}` → returns projects list
78
+ 3. `{teamId: "...", projectName/existingProjectId: "..."}` → configures
61
79
 
62
- 9. **Complete** - All tasks COMPLETED
63
- - Action: Review and create PR
80
+ Or run `/flux:linear` for guided setup.
64
81
 
65
82
  ## Entity References
66
83
 
67
- All entities have a reference format: `{PREFIX}-{TYPE}{NUMBER}`
84
+ Format: `{PREFIX}-{TYPE}{NUMBER}`
68
85
 
69
86
  - PRD: `MSA-P1`, `MSA-P2`
70
87
  - Epic: `MSA-E1`, `MSA-E2`
71
88
  - Task: `MSA-T1`, `MSA-T2`
72
89
 
73
- The prefix is generated from the project name during initialization.
74
-
75
- ## Status Values
76
-
77
- ### PRD Statuses (6-stage workflow)
78
- - `DRAFT` - Initial state, being created/refined
79
- - `PENDING_REVIEW` - Submitted for critique
80
- - `REVIEWED` - Critique complete, awaiting approval
81
- - `APPROVED` - Ready for epic breakdown
82
- - `BREAKDOWN_READY` - Epics and tasks created
83
- - `COMPLETED` - All epics done
84
-
85
- ### Valid PRD Transitions
86
- ```
87
- DRAFT → PENDING_REVIEW
88
- PENDING_REVIEW → REVIEWED | DRAFT (revise)
89
- REVIEWED → APPROVED | DRAFT (revise)
90
- APPROVED → BREAKDOWN_READY
91
- BREAKDOWN_READY → COMPLETED
92
- ```
93
-
94
- ### Epic/Task Statuses
95
- - `PENDING` - Not started
96
- - `IN_PROGRESS` - Currently being worked on
97
- - `COMPLETED` - Done
98
-
99
- ## Confidence-Based Autonomy
100
-
101
- The orchestrator uses confidence levels to determine autonomy:
102
-
103
- | Confidence | Behavior | Example |
104
- |------------|----------|---------|
105
- | > 80% | Auto-execute, inform user | "I'm creating the epic structure..." |
106
- | 50-80% | Suggest action, wait for confirmation | "Ready to break down into tasks. Proceed?" |
107
- | < 50% | Ask clarifying question | "Should we research this technology first?" |
108
-
109
- ### Confidence Indicators
110
- - **High confidence (>80%)**: Clear next step, no ambiguity, user has been responsive
111
- - **Medium confidence (50-80%)**: Reasonable next step, some uncertainty
112
- - **Low confidence (<50%)**: Multiple valid paths, unclear requirements, unfamiliar tech
113
-
114
90
  ## Available Subagents
115
91
 
116
- ### Research Agent
117
- - **Trigger**: Unfamiliar technology mentioned, confidence < 70%
118
- - **Purpose**: Gather information about libraries, frameworks, APIs
92
+ ### Research Agent (`flux:flux-researcher`)
93
+ - **Trigger**: Unfamiliar technology, confidence < 70%
119
94
  - **Tools**: Context7, WebSearch, WebFetch
120
95
 
121
- ### Critique Agent
122
- - **Trigger**: PRD status becomes PENDING_REVIEW
123
- - **Purpose**: Analyze feasibility, scope, risks
124
- - **Output**: Structured critique with recommendations
96
+ ### Critique Agent (`flux:flux-critic`)
97
+ - **Trigger**: PRD submitted for review
98
+ - **Output**: Feasibility analysis, risks, recommendations
99
+
100
+ ### Coder Agent (`flux:flux-coder`)
101
+ - **Trigger**: Task implementation
102
+ - **Workflow**: TDD - write tests, implement, verify
103
+
104
+ ### Verifier Agent (`flux:flux-verifier`)
105
+ - **Trigger**: After implementation
106
+ - **Purpose**: Verify acceptance criteria coverage
125
107
 
126
108
  ## Best Practices
127
109
 
128
- 1. **Check context first** - Always call `get_project_context` before taking actions
129
- 2. **Use refs, not IDs** - Tools accept human-readable refs like `MSA-E1`
130
- 3. **Validate status transitions** - Use `update_status` which enforces valid transitions
131
- 4. **Include related data** - Use `include` parameter to fetch nested entities in one call
132
- 5. **Handle errors gracefully** - Tools return errors with codes, display user-friendly messages
110
+ 1. **Check context first** - Call `get_project_context` before actions
111
+ 2. **Use refs, not IDs** - Tools accept `MSA-E1` format
112
+ 3. **Use render_status** - For visual project overview
113
+ 4. **Validate transitions** - `update_status` enforces valid transitions
114
+ 5. **Include related data** - Use `include` param to fetch nested entities