@programinglive/dev-workflow-mcp-server 1.1.6 β†’ 1.1.11

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/CHANGELOG.md CHANGED
@@ -2,6 +2,57 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
4
4
 
5
+ ### [1.1.11](https://github.com/programinglive/dev-workflow-mcp-server/compare/v1.1.10...v1.1.11) (2025-10-30)
6
+
7
+
8
+ ### 🧹 Chores
9
+
10
+ * verify run_full_workflow with persisted project summary ([6fdb475](https://github.com/programinglive/dev-workflow-mcp-server/commit/6fdb475f65ee6ef2c061b0b1cf954a282406e2a1))
11
+
12
+
13
+ ### ✨ Features
14
+
15
+ * add per-user state, SQLite project summary, and dashboard ([7b57b6e](https://github.com/programinglive/dev-workflow-mcp-server/commit/7b57b6e067ad59f2999a2badcb51b1293192c96b))
16
+ * per-project workflow state with unique per-user IDs; smarter workflow ([b710a52](https://github.com/programinglive/dev-workflow-mcp-server/commit/b710a524997583f58cd6da2567ba9fd14abdd3c9))
17
+
18
+ ### [1.1.10](https://github.com/programinglive/dev-workflow-mcp-server/compare/v1.1.9...v1.1.10) (2025-10-29)
19
+
20
+
21
+ ### ✨ Features
22
+
23
+ * add project knowledge library with persisted summary file for future database integration ([b9dc13f](https://github.com/programinglive/dev-workflow-mcp-server/commit/b9dc13f4b25dec756088a55185ccbfdb3988a644))
24
+
25
+ ### [1.1.9](https://github.com/programinglive/dev-workflow-mcp-server/compare/v1.1.8...v1.1.9) (2025-10-29)
26
+
27
+
28
+ ### 🧹 Chores
29
+
30
+ * interim commit ([b1aead0](https://github.com/programinglive/dev-workflow-mcp-server/commit/b1aead05920c766a183988fb93d3f0edc3e1eafc))
31
+
32
+
33
+ ### βœ… Tests
34
+
35
+ * update README.md, exec.js, package-lock.json +7 more ([894fdf1](https://github.com/programinglive/dev-workflow-mcp-server/commit/894fdf1eace33ddf0081c565028d214e1c7697bf))
36
+
37
+
38
+ ### ✨ Features
39
+
40
+ * make run_full_workflow smart and resumable from current phase ([930eb3c](https://github.com/programinglive/dev-workflow-mcp-server/commit/930eb3cb84c241b14f0e194cf490313e1e99a7ce))
41
+
42
+ ### [1.1.8](https://github.com/programinglive/dev-workflow-mcp-server/compare/v1.1.7...v1.1.8) (2025-10-27)
43
+
44
+
45
+ ### πŸ“ Documentation
46
+
47
+ * note DEV_WORKFLOW_STATE_FILE override ([bbeb11b](https://github.com/programinglive/dev-workflow-mcp-server/commit/bbeb11bc7f8be33f7c1a51b336283a3b2a48d3ea))
48
+
49
+ ### [1.1.7](https://github.com/programinglive/dev-workflow-mcp-server/compare/v1.1.6...v1.1.7) (2025-10-26)
50
+
51
+
52
+ ### ✨ Features
53
+
54
+ * prompt for documentation after tests pass ([a87e44f](https://github.com/programinglive/dev-workflow-mcp-server/commit/a87e44f2a78ea4c9220d62868ee1f9c7ad3b149b))
55
+
5
56
  ### [1.1.6](https://github.com/programinglive/dev-workflow-mcp-server/compare/v1.1.5...v1.1.6) (2025-10-26)
6
57
 
7
58
 
package/README.md CHANGED
@@ -24,7 +24,7 @@ Each project gets its own isolated workflow state file.
24
24
  npm install @programinglive/dev-workflow-mcp-server
25
25
  ```
26
26
 
27
- This will automatically create a `.workflow-state.json` file in the **project where you ran `npm install`** (using npm's `INIT_CWD`), keeping workflow history separate per project. If you're installing the package itself (inside `node_modules`), the script skips creation so it never pollutes the shared package directory.
27
+ This will automatically create a `.state/workflow-state.json` file in the **project where you ran `npm install`** (using npm's `INIT_CWD`), keeping workflow history separate per project. If you're installing the package itself (inside `node_modules`), the script skips creation so it never pollutes the shared package directory.
28
28
 
29
29
  ### Option 2: Install from Source
30
30
 
@@ -34,6 +34,11 @@ cd dev-workflow-mcp-server
34
34
  npm install
35
35
  ```
36
36
 
37
+ #### Two Usage Modes
38
+
39
+ - **Local (source)**: Point your MCP client to `index.js`. This runs directly from source and requires no build step. Recommended for MCP usage.
40
+ - **Production (built)**: Run `npm run build` once to generate `dist/`. This creates an optimized bundle but isn’t needed for MCP usage.
41
+
37
42
  ### 3. Configure in Windsurf/Claude Desktop
38
43
 
39
44
  Point your MCP client to the server entry point. Replace `<PROJECT_ROOT>` with the absolute path to this repository on your machine.
@@ -128,27 +133,109 @@ Point your MCP client to the server entry point. Replace `<PROJECT_ROOT>` with t
128
133
 
129
134
  After adding the configuration, restart the application to load the MCP server.
130
135
 
136
+ ## πŸ—οΈ Build System
137
+
138
+ This project includes a Vite-based build system for creating optimized distributions.
139
+
140
+ ### Scripts
141
+
142
+ - `npm run build` - Bundle the source into `dist/index.mjs` for distribution
143
+ - `npm run dev` - Run in development mode with file watching
144
+ - `npm run local` - Alias for running from source (same as `npm start`)
145
+
146
+ ### Build Output
147
+
148
+ Running `npm run build` generates:
149
+ - `dist/index.mjs` - Optimized ES module bundle
150
+ - Source maps and other build artifacts
151
+
152
+ The build bundles all source files while externalizing Node.js built-in modules and dependencies, resulting in a single file distribution.
153
+
154
+ ### Usage
155
+
156
+ For MCP server usage, point your client at `index.js` (source) to avoid stdio transport compatibility issues. The built `dist/index.mjs` is primarily for:
157
+ - npm package distribution
158
+ - Performance optimization
159
+ - Embedding in other projects
160
+
131
161
  ## πŸ“ Project-Specific Workflow State
132
162
 
133
- When you install this package in a project, a `.workflow-state.json` file is automatically created in your project root. This file:
163
+ When you install this package in a project, a `.state/workflow-state.json` file is automatically created in your project root. This file:
134
164
 
135
165
  - **Stores workflow history** specific to that project
136
166
  - **Tracks task progress** independently per project
137
167
  - **Should be gitignored** (already in `.gitignore` by default)
138
168
  - **Persists across sessions** so your workflow state is preserved
169
+ - **Stays centralized** even if you run the server from nested build outputs like `dist/`. The MCP server walks back to the project root (looking for `.git` or `package.json`) before reading or writing workflow state, so you never need duplicate copies under build directories.
139
170
 
140
- Each project maintains its own isolated workflow history, so you can work on multiple projects without mixing their histories.
171
+ Each project maintains its own isolated workflow history, so you can work on multiple projects without mixing their histories. Within that `.state` directory, the MCP server automatically creates a unique per-user subdirectory (e.g., `.state/users/user-abc123/`). The generated identifier persists locally so multiple developers sharing the same repository never clobber each other’s workflow files. If you prefer a specific name, set `DEV_WORKFLOW_USER_ID` before launching the server and that value will be used instead of the auto-generated ID.
141
172
 
142
173
  ### Adding to .gitignore
143
174
 
144
175
  If you're using this package, add this to your project's `.gitignore`:
145
176
 
146
177
  ```
147
- .workflow-state.json
178
+ .state/
148
179
  ```
149
180
 
150
181
  This keeps workflow state local to each developer's machine.
151
182
 
183
+ > **Need to override the location?** Set `DEV_WORKFLOW_STATE_FILE=/absolute/path/to/your/project/.state/workflow-state.json` before launching the server (or inside your MCP client config). The server will honor that path, letting you keep the package installed centrally while maintaining per-project workflow history.
184
+
185
+ ## πŸ› οΈ Available Tools
186
+
187
+ - `start_task` - Begin a new coding task
188
+ - `mark_bug_fixed` - Mark the feature/bug as fixed (requires tests next)
189
+ - `create_tests` - Mark that tests have been created
190
+ - `skip_tests` - Skip tests with justification
191
+ - `run_tests` - Record test results (must pass to proceed)
192
+ - `create_documentation` - Mark documentation as created
193
+ - `check_ready_to_commit` - Verify all steps are complete
194
+ - `commit_and_push` - Commit and push changes
195
+ - `perform_release` - Record release details
196
+ - `complete_task` - Mark task as complete and reset
197
+ - `force_complete_task` - Force completion with reason
198
+ - `drop_task` - Abandon current task
199
+ - `get_workflow_status` - Show current status
200
+ - `view_history` - View completed tasks
201
+ - `continue_workflow` - Get next-step guidance
202
+ - `rerun_workflow` - Reset and restart the current task from the beginning
203
+ - `run_full_workflow` - Execute every workflow step in sequence with a single command (requires supplying the details for each phase)
204
+
205
+ ### `run_full_workflow`
206
+
207
+ Use this when you already have all the information needed for each workflow phase and want to execute them in one go.
208
+
209
+ ```json
210
+ {
211
+ "summary": "Add payment webhooks",
212
+ "testCommand": "npm test",
213
+ "documentationType": "README",
214
+ "documentationSummary": "Document webhook configuration",
215
+ "commitMessage": "feat: add payment webhooks",
216
+ "releaseCommand": "npm run release:minor",
217
+ "releaseNotes": "Release webhook support",
218
+ "branch": "feature/payments",
219
+ "testsPassed": true,
220
+ "testDetails": "node --test; 42 tests",
221
+ "releaseType": "minor",
222
+ "preset": "minor"
223
+ }
224
+ ```
225
+
226
+ The tool will:
227
+
228
+ 1. `mark_bug_fixed` using `summary`
229
+ 2. `create_tests`
230
+ 3. `run_tests` with `testsPassed`, `testCommand`, and optional `testDetails`
231
+ 4. `create_documentation` with `documentationType` and `documentationSummary`
232
+ 5. `check_ready_to_commit`
233
+ 6. `commit_and_push` with `commitMessage` and optional `branch`
234
+ 7. `perform_release` with `releaseCommand`, plus optional `releaseNotes`, `releaseType`, and `preset`
235
+ 8. `complete_task` reusing `commitMessage`
236
+
237
+ All arguments except the optional flags are required and must be non-empty strings.
238
+
152
239
  ## 🚫 Releasing Without the Workflow Steps
153
240
 
154
241
  The package ships with a release guard (`release-wrapper.js`) that backs the `npm run release:*` scripts. The guard refuses to run unless:
@@ -372,7 +459,7 @@ You can modify the workflow in `index.js`:
372
459
 
373
460
  ## πŸ“ State Management
374
461
 
375
- The server maintains state in `.workflow-state.json`:
462
+ The server maintains state in `.state/workflow-state.json`:
376
463
  - Current phase
377
464
  - Task description
378
465
  - Completion status of each step
package/db/index.js ADDED
@@ -0,0 +1,204 @@
1
+ import Database from "better-sqlite3";
2
+ import { existsSync } from "fs";
3
+ import path from "path";
4
+ import { fileURLToPath } from "url";
5
+
6
+ const __filename = fileURLToPath(import.meta.url);
7
+ const __dirname = path.dirname(__filename);
8
+ const DB_FILENAME = "dev-workflow.db";
9
+ const FREQUENCY_FORMATS = {
10
+ daily: "%Y-%m-%d",
11
+ monthly: "%Y-%m",
12
+ yearly: "%Y",
13
+ };
14
+
15
+ function getDbPath() {
16
+ const initCwd = process.env.INIT_CWD || process.cwd();
17
+ let projectRoot = initCwd;
18
+ while (projectRoot !== path.dirname(projectRoot)) {
19
+ if (existsSync(path.join(projectRoot, "package.json"))) break;
20
+ projectRoot = path.dirname(projectRoot);
21
+ }
22
+ return path.join(projectRoot, ".state", DB_FILENAME);
23
+ }
24
+
25
+ let db;
26
+ export function getDb() {
27
+ if (!db) {
28
+ const dbPath = getDbPath();
29
+ db = new Database(dbPath);
30
+ db.pragma("journal_mode = WAL");
31
+ migrate();
32
+ }
33
+ return db;
34
+ }
35
+
36
+ function migrate() {
37
+ const createHistory = `
38
+ CREATE TABLE IF NOT EXISTS workflow_history (
39
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
40
+ user_id TEXT NOT NULL,
41
+ task_description TEXT NOT NULL,
42
+ task_type TEXT NOT NULL,
43
+ commit_message TEXT,
44
+ timestamp DATETIME DEFAULT CURRENT_TIMESTAMP,
45
+ forced INTEGER DEFAULT 0,
46
+ force_reason TEXT,
47
+ dropped INTEGER DEFAULT 0,
48
+ drop_reason TEXT,
49
+ UNIQUE(user_id, timestamp, task_description)
50
+ )
51
+ `;
52
+ const createSummary = `
53
+ CREATE TABLE IF NOT EXISTS project_summary (
54
+ user_id TEXT PRIMARY KEY,
55
+ total_tasks INTEGER DEFAULT 0,
56
+ task_types TEXT DEFAULT '{}',
57
+ last_active DATETIME,
58
+ updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
59
+ recent_tasks TEXT DEFAULT '[]'
60
+ )
61
+ `;
62
+ const idxHistory = `CREATE INDEX IF NOT EXISTS idx_workflow_history_user ON workflow_history(user_id)`;
63
+ const idxSummary = `CREATE INDEX IF NOT EXISTS idx_project_summary_user ON project_summary(user_id)`;
64
+
65
+ db.exec(createHistory);
66
+ db.exec(createSummary);
67
+ db.exec(idxHistory);
68
+ db.exec(idxSummary);
69
+ }
70
+
71
+ export function insertHistoryEntry(userId, entry) {
72
+ const stmt = getDb().prepare(`
73
+ INSERT OR REPLACE INTO workflow_history
74
+ (user_id, task_description, task_type, commit_message, timestamp, forced, force_reason, dropped, drop_reason)
75
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
76
+ `);
77
+ stmt.run(
78
+ userId,
79
+ entry.taskDescription,
80
+ entry.taskType,
81
+ entry.commitMessage,
82
+ entry.timestamp || new Date().toISOString(),
83
+ entry.forced ? 1 : 0,
84
+ entry.forceReason || null,
85
+ entry.dropped ? 1 : 0,
86
+ entry.dropReason || null
87
+ );
88
+ updateSummaryForUser(userId);
89
+ }
90
+
91
+ export function updateSummaryForUser(userId) {
92
+ const getHistory = getDb().prepare(`
93
+ SELECT task_type, timestamp, task_description
94
+ FROM workflow_history
95
+ WHERE user_id = ?
96
+ ORDER BY timestamp DESC
97
+ LIMIT 20
98
+ `);
99
+ const rows = getHistory.all(userId);
100
+
101
+ const taskTypes = {};
102
+ for (const row of rows) {
103
+ taskTypes[row.task_type] = (taskTypes[row.task_type] || 0) + 1;
104
+ }
105
+
106
+ const recentTasks = rows.slice(0, 5).map(r => ({
107
+ description: r.task_description,
108
+ type: r.task_type,
109
+ timestamp: r.timestamp,
110
+ }));
111
+
112
+ const lastActive = rows[0]?.timestamp || null;
113
+
114
+ const upsert = getDb().prepare(`
115
+ INSERT OR REPLACE INTO project_summary
116
+ (user_id, total_tasks, task_types, last_active, recent_tasks, updated_at)
117
+ VALUES (?, ?, ?, ?, ?, ?)
118
+ `);
119
+ upsert.run(
120
+ userId,
121
+ rows.length,
122
+ JSON.stringify(taskTypes),
123
+ lastActive,
124
+ JSON.stringify(recentTasks),
125
+ new Date().toISOString()
126
+ );
127
+ }
128
+
129
+ export function getSummaryForUser(userId) {
130
+ const stmt = getDb().prepare(`
131
+ SELECT * FROM project_summary WHERE user_id = ?
132
+ `);
133
+ const row = stmt.get(userId);
134
+ if (!row) return null;
135
+ return {
136
+ totalTasks: row.total_tasks,
137
+ taskTypes: JSON.parse(row.task_types),
138
+ lastActive: row.last_active,
139
+ recentTasks: JSON.parse(row.recent_tasks),
140
+ updatedAt: row.updated_at,
141
+ };
142
+ }
143
+
144
+ function buildHistoryFilters({ startDate, endDate }) {
145
+ const clauses = [];
146
+ const params = [];
147
+ if (startDate) {
148
+ clauses.push("DATE(timestamp) >= DATE(?)");
149
+ params.push(startDate);
150
+ }
151
+ if (endDate) {
152
+ clauses.push("DATE(timestamp) <= DATE(?)");
153
+ params.push(endDate);
154
+ }
155
+ return { clauses, params };
156
+ }
157
+
158
+ export function getHistoryForUser(userId, options = {}) {
159
+ const { page = 1, pageSize = 20, startDate, endDate } = options;
160
+ const sanitizedPage = Math.max(1, Number.isFinite(Number(page)) ? Math.floor(Number(page)) : 1);
161
+ const sanitizedPageSize = Math.min(100, Math.max(1, Number.isFinite(Number(pageSize)) ? Math.floor(Number(pageSize)) : 20));
162
+ const offset = (sanitizedPage - 1) * sanitizedPageSize;
163
+
164
+ const { clauses, params } = buildHistoryFilters({ startDate, endDate });
165
+ const where = ["user_id = ?", ...clauses].join(" AND ");
166
+ const dbInstance = getDb();
167
+
168
+ const countStmt = dbInstance.prepare(`SELECT COUNT(*) as total FROM workflow_history WHERE ${where}`);
169
+ const total = countStmt.get(userId, ...params).total;
170
+
171
+ const rowsStmt = dbInstance.prepare(`
172
+ SELECT *
173
+ FROM workflow_history
174
+ WHERE ${where}
175
+ ORDER BY timestamp DESC
176
+ LIMIT ?
177
+ OFFSET ?
178
+ `);
179
+ const entries = rowsStmt.all(userId, ...params, sanitizedPageSize, offset);
180
+
181
+ return {
182
+ entries,
183
+ total,
184
+ page: sanitizedPage,
185
+ pageSize: sanitizedPageSize,
186
+ };
187
+ }
188
+
189
+ export function getHistorySummary(userId, options = {}) {
190
+ const { startDate, endDate, frequency = "daily" } = options;
191
+ const format = FREQUENCY_FORMATS[frequency] || FREQUENCY_FORMATS.daily;
192
+ const { clauses, params } = buildHistoryFilters({ startDate, endDate });
193
+ const where = ["user_id = ?", ...clauses].join(" AND ");
194
+ const stmt = getDb().prepare(`
195
+ SELECT strftime('${format}', timestamp) AS period, COUNT(*) AS count
196
+ FROM workflow_history
197
+ WHERE ${where}
198
+ GROUP BY period
199
+ ORDER BY period DESC
200
+ `);
201
+ return stmt
202
+ .all(userId, ...params)
203
+ .map((row) => ({ period: row.period, count: row.count }));
204
+ }
package/exec.js CHANGED
@@ -1,4 +1,49 @@
1
1
  import { exec as execCallback } from "child_process";
2
+ import { existsSync } from "fs";
3
+ import path from "path";
4
+ import { fileURLToPath } from "url";
2
5
  import { promisify } from "util";
3
6
 
4
- export const exec = promisify(execCallback);
7
+ const execPromise = promisify(execCallback);
8
+
9
+ function findRepoRoot(startDir) {
10
+ if (!startDir) {
11
+ return null;
12
+ }
13
+
14
+ let current = path.resolve(startDir);
15
+ const { root } = path.parse(current);
16
+
17
+ while (true) {
18
+ if (existsSync(path.join(current, ".git"))) {
19
+ return current;
20
+ }
21
+
22
+ if (current === root) {
23
+ return null;
24
+ }
25
+
26
+ const parent = path.dirname(current);
27
+ if (parent === current) {
28
+ return null;
29
+ }
30
+
31
+ current = parent;
32
+ }
33
+ }
34
+
35
+ function getDefaultCwd() {
36
+ const moduleDir = path.dirname(fileURLToPath(import.meta.url));
37
+ const candidateDirs = [process.cwd(), process.env.INIT_CWD, moduleDir];
38
+ return candidateDirs.reduce((resolved, candidate) => {
39
+ if (resolved) {
40
+ return resolved;
41
+ }
42
+ return findRepoRoot(candidate) || null;
43
+ }, null) || process.cwd();
44
+ }
45
+
46
+ export function exec(command, options = {}) {
47
+ const execOptions = options.cwd ? options : { ...options, cwd: getDefaultCwd() };
48
+ return execPromise(command, execOptions);
49
+ }
package/index.js CHANGED
@@ -32,8 +32,7 @@ export { createCommitMessageParts, determineReleaseTypeFromCommit } from "./comm
32
32
  export { containsTestFilesInStatus, workingTreeSummary } from "./git-helpers.js";
33
33
  export { WorkflowState } from "./workflow-state.js";
34
34
 
35
- export const workflowState = new WorkflowState();
36
- await workflowState.load();
35
+ export let workflowState = null;
37
36
 
38
37
  const __filename = fileURLToPath(import.meta.url);
39
38
 
@@ -184,6 +183,17 @@ Use 'check_ready_to_commit' to verify workflow completion.
184
183
 
185
184
  // Start server
186
185
  export async function main() {
186
+ try {
187
+ // Initialize workflow state
188
+ workflowState = new WorkflowState();
189
+ await workflowState.load();
190
+ await workflowState.ensurePrimaryFile();
191
+ } catch (error) {
192
+ console.error("Warning: Failed to initialize workflow state:", error.message);
193
+ // Continue anyway - workflow state is optional for MCP to function
194
+ workflowState = new WorkflowState();
195
+ }
196
+
187
197
  const transport = new StdioServerTransport();
188
198
  await server.connect(transport);
189
199
  console.error("Dev Workflow MCP Server running on stdio");
package/install.js CHANGED
@@ -25,7 +25,11 @@ async function install() {
25
25
  return;
26
26
  }
27
27
 
28
- const stateFile = path.join(projectRoot, '.workflow-state.json');
28
+ const stateDir = path.join(projectRoot, '.state');
29
+ const stateFile = path.join(stateDir, 'workflow-state.json');
30
+
31
+ // Create .state directory if it doesn't exist
32
+ await fs.mkdir(stateDir, { recursive: true });
29
33
 
30
34
  // Check if state file already exists
31
35
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@programinglive/dev-workflow-mcp-server",
3
- "version": "1.1.6",
3
+ "version": "1.1.11",
4
4
  "description": "MCP server to enforce development workflow discipline",
5
5
  "type": "module",
6
6
  "main": "index.js",
@@ -8,16 +8,19 @@
8
8
  "dev-workflow-mcp": "index.js"
9
9
  },
10
10
  "scripts": {
11
+ "build": "vite build",
12
+ "dev": "node --watch index.js",
13
+ "start": "node index.js",
14
+ "local": "node index.js",
11
15
  "test": "node --test",
12
16
  "check": "node --check index.js",
13
- "start": "node index.js",
14
- "dev": "node --watch index.js",
15
17
  "prepare": "husky",
16
18
  "postinstall": "node install.js",
17
19
  "release": "standard-version",
18
20
  "release:major": "node release-wrapper.js major",
19
21
  "release:minor": "node release-wrapper.js minor",
20
- "release:patch": "node release-wrapper.js patch"
22
+ "release:patch": "node release-wrapper.js patch",
23
+ "web": "node web/server.js"
21
24
  },
22
25
  "keywords": [
23
26
  "mcp",
@@ -28,7 +31,13 @@
28
31
  "author": "",
29
32
  "license": "MIT",
30
33
  "dependencies": {
31
- "@modelcontextprotocol/sdk": "^1.0.0"
34
+ "@modelcontextprotocol/sdk": "^1.0.0",
35
+ "better-sqlite3": "^11.8.0",
36
+ "esbuild": "^0.25.0",
37
+ "get-port": "^7.1.0",
38
+ "husky": "^9.1.7",
39
+ "standard-version": "^9.5.0",
40
+ "vite": "^7.1.12"
32
41
  },
33
42
  "publishConfig": {
34
43
  "access": "public"
@@ -36,9 +45,7 @@
36
45
  "devDependencies": {
37
46
  "@commitlint/cli": "^20.1.0",
38
47
  "@commitlint/config-conventional": "^20.0.0",
39
- "@programinglive/commiter": "^1.0.0",
40
- "husky": "^9.1.7",
41
- "standard-version": "^9.5.0"
48
+ "@programinglive/commiter": "^1.1.0"
42
49
  },
43
50
  "standard-version": {
44
51
  "releaseCommitMessageFormat": "chore(release): {{currentTag}} πŸš€",
@@ -22,7 +22,7 @@ async function main() {
22
22
  process.exit(1);
23
23
  }
24
24
 
25
- const statePath = path.join(projectRoot, '.workflow-state.json');
25
+ const statePath = path.join(projectRoot, '.state', 'workflow-state.json');
26
26
  let state;
27
27
 
28
28
  try {
@@ -0,0 +1,37 @@
1
+ import { test } from 'node:test';
2
+ import assert from 'node:assert';
3
+ import { readFile, access } from 'fs/promises';
4
+ import { exec as execCallback } from 'child_process';
5
+ import { promisify } from 'util';
6
+ import path from 'path';
7
+
8
+ const exec = promisify(execCallback);
9
+
10
+ test('build generates dist/index.mjs file', async () => {
11
+ const { stderr } = await exec('npm run build');
12
+ // Allow the known vite warning about node:process
13
+ const knownWarning = '[plugin vite:resolve] Module "node:process" has been externalized for browser compatibility';
14
+ const actualStderr = stderr.replace(/\x1B\[[0-9;]*m/g, ''); // Remove ANSI codes
15
+ assert(
16
+ actualStderr === '' || actualStderr.includes(knownWarning),
17
+ `Build should not have unexpected errors: ${actualStderr}`
18
+ );
19
+
20
+ // Verify the file exists
21
+ const distPath = path.join(process.cwd(), 'dist', 'index.mjs');
22
+ await access(distPath);
23
+
24
+ // Verify it contains expected content
25
+ const content = await readFile(distPath, 'utf-8');
26
+ assert(content.includes('main'), 'Bundle should contain main function');
27
+ assert(content.length > 1000, 'Bundle should be substantial');
28
+ });
29
+
30
+ test('build script exists in package.json', async () => {
31
+ const packagePath = path.join(process.cwd(), 'package.json');
32
+ const content = await readFile(packagePath, 'utf-8');
33
+ const pkg = JSON.parse(content);
34
+
35
+ assert.strictEqual(typeof pkg.scripts.build, 'string');
36
+ assert(pkg.scripts.build.includes('vite build'));
37
+ });
@@ -0,0 +1,32 @@
1
+ import { test } from 'node:test';
2
+ import assert from 'node:assert';
3
+ import { exec } from '../exec.js';
4
+ import { tmpdir } from 'os';
5
+ import { mkdtemp, rm } from 'fs/promises';
6
+ import path from 'path';
7
+
8
+ test('exec runs commands successfully', async () => {
9
+ const { stdout } = await exec('echo "hello world"');
10
+ assert.strictEqual(stdout.trim(), 'hello world');
11
+ });
12
+
13
+ test('exec respects explicit cwd option', async () => {
14
+ const tempDir = await mkdtemp(path.join(tmpdir(), 'mcp-test-'));
15
+
16
+ try {
17
+ const { stdout } = await exec('pwd', { cwd: tempDir });
18
+ // Just verify it runs in the specified directory (path may vary by platform)
19
+ assert(stdout.trim().includes('mcp-test-'));
20
+ } finally {
21
+ await rm(tempDir, { recursive: true, force: true });
22
+ }
23
+ });
24
+
25
+ test('exec handles command errors', async () => {
26
+ try {
27
+ await exec('command-that-does-not-exist');
28
+ assert.fail('Should have thrown an error');
29
+ } catch (error) {
30
+ assert(error.code !== undefined || error.message.includes('command-not-found'));
31
+ }
32
+ });