@cliangdev/flux-plugin 0.0.0-dev.df9c61f → 0.1.0-dev.588ae42

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (89) hide show
  1. package/README.md +8 -4
  2. package/bin/install.cjs +158 -24
  3. package/commands/flux.md +127 -84
  4. package/commands/linear.md +171 -0
  5. package/package.json +10 -12
  6. package/skills/flux-orchestrator/SKILL.md +58 -76
  7. package/src/__tests__/version.test.ts +37 -0
  8. package/src/adapters/local/.gitkeep +0 -0
  9. package/src/server/__tests__/config.test.ts +163 -0
  10. package/src/server/adapters/__tests__/a-client-linear.test.ts +197 -0
  11. package/src/server/adapters/__tests__/adapter-factory.test.ts +230 -0
  12. package/src/server/adapters/__tests__/dependency-ops.test.ts +395 -0
  13. package/src/server/adapters/__tests__/document-ops.test.ts +306 -0
  14. package/src/server/adapters/__tests__/linear-adapter.test.ts +91 -0
  15. package/src/server/adapters/__tests__/linear-config.test.ts +425 -0
  16. package/src/server/adapters/__tests__/linear-criteria-parser.test.ts +287 -0
  17. package/src/server/adapters/__tests__/linear-description-test.ts +238 -0
  18. package/src/server/adapters/__tests__/linear-epic-crud.test.ts +496 -0
  19. package/src/server/adapters/__tests__/linear-mappers-description.test.ts +276 -0
  20. package/src/server/adapters/__tests__/linear-mappers-epic.test.ts +294 -0
  21. package/src/server/adapters/__tests__/linear-mappers-prd.test.ts +300 -0
  22. package/src/server/adapters/__tests__/linear-mappers-task.test.ts +197 -0
  23. package/src/server/adapters/__tests__/linear-prd-crud.test.ts +620 -0
  24. package/src/server/adapters/__tests__/linear-stats.test.ts +450 -0
  25. package/src/server/adapters/__tests__/linear-task-crud.test.ts +534 -0
  26. package/src/server/adapters/__tests__/linear-types.test.ts +243 -0
  27. package/src/server/adapters/__tests__/status-ops.test.ts +441 -0
  28. package/src/server/adapters/factory.ts +90 -0
  29. package/src/server/adapters/index.ts +9 -0
  30. package/src/server/adapters/linear/adapter.ts +1136 -0
  31. package/src/server/adapters/linear/client.ts +169 -0
  32. package/src/server/adapters/linear/config.ts +152 -0
  33. package/src/server/adapters/linear/helpers/criteria-parser.ts +197 -0
  34. package/src/server/adapters/linear/helpers/index.ts +7 -0
  35. package/src/server/adapters/linear/index.ts +16 -0
  36. package/src/server/adapters/linear/mappers/description.ts +136 -0
  37. package/src/server/adapters/linear/mappers/epic.ts +81 -0
  38. package/src/server/adapters/linear/mappers/index.ts +27 -0
  39. package/src/server/adapters/linear/mappers/prd.ts +178 -0
  40. package/src/server/adapters/linear/mappers/task.ts +82 -0
  41. package/src/server/adapters/linear/types.ts +264 -0
  42. package/src/server/adapters/local-adapter.ts +968 -0
  43. package/src/server/adapters/types.ts +293 -0
  44. package/src/server/config.ts +73 -0
  45. package/src/server/db/__tests__/queries.test.ts +472 -0
  46. package/src/server/db/ids.ts +17 -0
  47. package/src/server/db/index.ts +69 -0
  48. package/src/server/db/queries.ts +142 -0
  49. package/src/server/db/refs.ts +60 -0
  50. package/src/server/db/schema.ts +88 -0
  51. package/src/server/db/sqlite.ts +10 -0
  52. package/src/server/index.ts +83 -0
  53. package/src/server/tools/__tests__/crud.test.ts +301 -0
  54. package/src/server/tools/__tests__/get-version.test.ts +27 -0
  55. package/src/server/tools/__tests__/mcp-interface.test.ts +388 -0
  56. package/src/server/tools/__tests__/query.test.ts +353 -0
  57. package/src/server/tools/__tests__/z-configure-linear.test.ts +511 -0
  58. package/src/server/tools/__tests__/z-get-linear-url.test.ts +108 -0
  59. package/src/server/tools/configure-linear.ts +373 -0
  60. package/src/server/tools/create-epic.ts +35 -0
  61. package/src/server/tools/create-prd.ts +31 -0
  62. package/src/server/tools/create-task.ts +38 -0
  63. package/src/server/tools/criteria.ts +50 -0
  64. package/src/server/tools/delete-entity.ts +76 -0
  65. package/src/server/tools/dependencies.ts +55 -0
  66. package/src/server/tools/get-entity.ts +238 -0
  67. package/src/server/tools/get-linear-url.ts +28 -0
  68. package/src/server/tools/get-project-context.ts +33 -0
  69. package/src/server/tools/get-stats.ts +52 -0
  70. package/src/server/tools/get-version.ts +20 -0
  71. package/src/server/tools/index.ts +114 -0
  72. package/src/server/tools/init-project.ts +108 -0
  73. package/src/server/tools/query-entities.ts +167 -0
  74. package/src/server/tools/render-status.ts +201 -0
  75. package/src/server/tools/update-entity.ts +140 -0
  76. package/src/server/tools/update-status.ts +166 -0
  77. package/src/server/utils/__tests__/mcp-response.test.ts +331 -0
  78. package/src/server/utils/logger.ts +9 -0
  79. package/src/server/utils/mcp-response.ts +254 -0
  80. package/src/server/utils/status-transitions.ts +160 -0
  81. package/src/status-line/__tests__/status-line.test.ts +215 -0
  82. package/src/status-line/index.ts +147 -0
  83. package/src/utils/__tests__/chalk-import.test.ts +32 -0
  84. package/src/utils/__tests__/display.test.ts +97 -0
  85. package/src/utils/__tests__/status-renderer.test.ts +310 -0
  86. package/src/utils/display.ts +62 -0
  87. package/src/utils/status-renderer.ts +188 -0
  88. package/src/version.ts +5 -0
  89. package/dist/server/index.js +0 -86958
@@ -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/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.1.0-dev.588ae42",
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",
@@ -8,8 +8,8 @@
8
8
  "flux-plugin": "./bin/install.cjs"
9
9
  },
10
10
  "files": [
11
- "bin/",
12
- "dist/",
11
+ "bin/install.cjs",
12
+ "src/",
13
13
  "skills/",
14
14
  "commands/",
15
15
  "agents/",
@@ -17,20 +17,20 @@
17
17
  ],
18
18
  "scripts": {
19
19
  "dev": "bun run src/server/index.ts",
20
- "build": "bun build src/server/index.ts --outdir dist/server --target node --external better-sqlite3",
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)}\"",
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",
23
- "build:compile:server": "bun build --compile --outfile bin/flux-server src/server/index.ts",
24
- "build:compile:status": "bun build --compile --outfile bin/flux-status src/status-line/index.ts",
20
+ "build": "bun build --compile --outfile bin/flux-server src/server/index.ts && bun build --compile --outfile bin/flux-status src/status-line/index.ts",
21
+ "build:server": "bun build --compile --outfile bin/flux-server src/server/index.ts",
22
+ "build:status": "bun build --compile --outfile bin/flux-status src/status-line/index.ts",
25
23
  "validate": "node scripts/validate-structure.cjs",
26
- "prepublishOnly": "bun run validate && bun run build",
24
+ "test:integration": "bun test scripts/__tests__/integration.test.ts --timeout 120000",
25
+ "prepublishOnly": "bun run validate && bun run test:integration",
27
26
  "test": "bun test",
28
27
  "test:linear-description": "bun run src/server/adapters/__tests__/linear-description-test.ts",
29
28
  "typecheck": "tsc --noEmit",
30
29
  "lint": "biome check .",
31
30
  "lint:fix": "biome check --write .",
32
31
  "format": "biome format --write .",
33
- "verify-release": "bun run scripts/verify-release.ts"
32
+ "verify-release": "bun run scripts/verify-release.ts",
33
+ "release": "./scripts/release.sh"
34
34
  },
35
35
  "repository": {
36
36
  "type": "git",
@@ -51,14 +51,12 @@
51
51
  "license": "MIT",
52
52
  "devDependencies": {
53
53
  "@biomejs/biome": "^2.3.11",
54
- "@types/better-sqlite3": "^7.6.13",
55
54
  "@types/bun": "^1.3.6",
56
55
  "typescript": "^5.0.0"
57
56
  },
58
57
  "dependencies": {
59
58
  "@linear/sdk": "^70.0.0",
60
59
  "@modelcontextprotocol/sdk": "^1.25.2",
61
- "better-sqlite3": "^12.6.2",
62
60
  "chalk": "^5.4.1",
63
61
  "zod": "^4.3.5"
64
62
  },
@@ -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
@@ -0,0 +1,37 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import { existsSync, readFileSync } from "node:fs";
3
+ import { join } from "node:path";
4
+
5
+ describe("version module", () => {
6
+ test("VERSION is exported from src/version.ts", async () => {
7
+ // Dynamic import to get the VERSION constant
8
+ const versionModule = await import("../version.js");
9
+
10
+ expect(versionModule.VERSION).toBeDefined();
11
+ expect(typeof versionModule.VERSION).toBe("string");
12
+ });
13
+
14
+ test("VERSION equals package.json version", async () => {
15
+ // Read package.json version
16
+ const packageJsonPath = join(process.cwd(), "package.json");
17
+ expect(existsSync(packageJsonPath)).toBe(true);
18
+
19
+ const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf-8"));
20
+ const packageVersion = packageJson.version;
21
+
22
+ expect(packageVersion).toBeDefined();
23
+ expect(typeof packageVersion).toBe("string");
24
+
25
+ // Import VERSION and compare
26
+ const versionModule = await import("../version.js");
27
+ expect(versionModule.VERSION).toBe(packageVersion);
28
+ });
29
+
30
+ test("VERSION is a valid semver format", async () => {
31
+ const versionModule = await import("../version.js");
32
+
33
+ // Basic semver format check (e.g., "0.1.0", "1.2.3-beta.1")
34
+ const semverPattern = /^\d+\.\d+\.\d+(-[\w.]+)?$/;
35
+ expect(semverPattern.test(versionModule.VERSION)).toBe(true);
36
+ });
37
+ });
File without changes
@@ -0,0 +1,163 @@
1
+ import { afterEach, beforeEach, describe, expect, test } from "bun:test";
2
+ import {
3
+ existsSync,
4
+ mkdirSync,
5
+ realpathSync,
6
+ rmSync,
7
+ writeFileSync,
8
+ } from "node:fs";
9
+ import { tmpdir } from "node:os";
10
+
11
+ describe("config", () => {
12
+ const originalEnv = process.env.FLUX_PROJECT_ROOT;
13
+ const originalCwd = process.cwd();
14
+ // Use os.tmpdir() to get the real temp path (handles /tmp -> /private/tmp on macOS)
15
+ const TEST_DIR = `${realpathSync(tmpdir())}/flux-config-test-${Date.now()}`;
16
+ const NESTED_DIR = `${TEST_DIR}/subdir/nested`;
17
+
18
+ beforeEach(async () => {
19
+ // Clean up any previous test directory
20
+ if (existsSync(TEST_DIR)) {
21
+ rmSync(TEST_DIR, { recursive: true });
22
+ }
23
+
24
+ // Create test directory structure
25
+ mkdirSync(NESTED_DIR, { recursive: true });
26
+
27
+ // Clear the config cache before each test
28
+ const { config } = await import("../config.js");
29
+ config.clearCache();
30
+ });
31
+
32
+ afterEach(async () => {
33
+ // Restore original env
34
+ if (originalEnv !== undefined) {
35
+ process.env.FLUX_PROJECT_ROOT = originalEnv;
36
+ } else {
37
+ delete process.env.FLUX_PROJECT_ROOT;
38
+ }
39
+
40
+ // Restore original cwd
41
+ process.chdir(originalCwd);
42
+
43
+ // Clear the config cache
44
+ const { config } = await import("../config.js");
45
+ config.clearCache();
46
+
47
+ // Clean up test directory
48
+ if (existsSync(TEST_DIR)) {
49
+ rmSync(TEST_DIR, { recursive: true });
50
+ }
51
+ });
52
+
53
+ test("uses env var when properly set", async () => {
54
+ process.env.FLUX_PROJECT_ROOT = "/some/valid/path";
55
+
56
+ const { config } = await import("../config.js");
57
+ config.clearCache();
58
+
59
+ expect(config.projectRoot).toBe("/some/valid/path");
60
+ });
61
+
62
+ test("ignores unresolved template variable and walks up directories", async () => {
63
+ // Create a .flux folder at TEST_DIR
64
+ mkdirSync(`${TEST_DIR}/.flux`, { recursive: true });
65
+ writeFileSync(
66
+ `${TEST_DIR}/.flux/project.json`,
67
+ JSON.stringify({ name: "test" }),
68
+ );
69
+
70
+ // Simulate Claude Code passing unresolved template variable
71
+ // biome-ignore lint/suspicious/noTemplateCurlyInString: intentionally testing literal template string
72
+ process.env.FLUX_PROJECT_ROOT = "${CLAUDE_PROJECT_DIR}";
73
+
74
+ // Change to nested directory
75
+ process.chdir(NESTED_DIR);
76
+
77
+ const { config } = await import("../config.js");
78
+ config.clearCache();
79
+
80
+ // Should walk up and find TEST_DIR, not use the literal "${CLAUDE_PROJECT_DIR}"
81
+ expect(config.projectRoot).not.toContain("${");
82
+ expect(config.projectRoot).toBe(TEST_DIR);
83
+ });
84
+
85
+ test("walks up directories to find .flux folder", async () => {
86
+ // Create a .flux folder at TEST_DIR (parent of NESTED_DIR)
87
+ mkdirSync(`${TEST_DIR}/.flux`, { recursive: true });
88
+ writeFileSync(
89
+ `${TEST_DIR}/.flux/project.json`,
90
+ JSON.stringify({ name: "test" }),
91
+ );
92
+
93
+ // No env var set
94
+ delete process.env.FLUX_PROJECT_ROOT;
95
+
96
+ // Change to nested directory
97
+ process.chdir(NESTED_DIR);
98
+
99
+ const { config } = await import("../config.js");
100
+ config.clearCache();
101
+
102
+ // Should walk up and find TEST_DIR
103
+ expect(config.projectRoot).toBe(TEST_DIR);
104
+ });
105
+
106
+ test("falls back to cwd when no .flux folder found", async () => {
107
+ // No .flux folder anywhere in TEST_DIR hierarchy
108
+ // No env var set
109
+ delete process.env.FLUX_PROJECT_ROOT;
110
+
111
+ // Change to test directory (which has no .flux)
112
+ process.chdir(TEST_DIR);
113
+
114
+ const { config } = await import("../config.js");
115
+ config.clearCache();
116
+
117
+ // Should fall back to cwd
118
+ expect(config.projectRoot).toBe(TEST_DIR);
119
+ });
120
+
121
+ test("projectExists returns true when project.json exists", async () => {
122
+ mkdirSync(`${TEST_DIR}/.flux`, { recursive: true });
123
+ writeFileSync(
124
+ `${TEST_DIR}/.flux/project.json`,
125
+ JSON.stringify({ name: "test" }),
126
+ );
127
+
128
+ process.env.FLUX_PROJECT_ROOT = TEST_DIR;
129
+
130
+ const { config } = await import("../config.js");
131
+ config.clearCache();
132
+
133
+ expect(config.projectExists).toBe(true);
134
+ });
135
+
136
+ test("projectExists returns false when project.json does not exist", async () => {
137
+ // No .flux folder
138
+ process.env.FLUX_PROJECT_ROOT = TEST_DIR;
139
+
140
+ const { config } = await import("../config.js");
141
+ config.clearCache();
142
+
143
+ expect(config.projectExists).toBe(false);
144
+ });
145
+
146
+ test("caches project root for performance", async () => {
147
+ process.env.FLUX_PROJECT_ROOT = "/first/path";
148
+
149
+ const { config } = await import("../config.js");
150
+ config.clearCache();
151
+
152
+ // First call caches the value
153
+ expect(config.projectRoot).toBe("/first/path");
154
+
155
+ // Changing env var should not affect cached value
156
+ process.env.FLUX_PROJECT_ROOT = "/second/path";
157
+ expect(config.projectRoot).toBe("/first/path");
158
+
159
+ // After clearing cache, should use new env var
160
+ config.clearCache();
161
+ expect(config.projectRoot).toBe("/second/path");
162
+ });
163
+ });