@programinglive/dev-workflow-mcp-server 1.7.14 โ 1.7.17
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 +46 -0
- package/README.md +72 -3
- package/db/adapters/postgres.js +85 -28
- package/db/adapters/sqlite.js +7 -1
- package/docs/product/PRD.md +4 -2
- package/docs/release-notes/RELEASE_NOTES.md +23 -0
- package/index.js +12 -2
- package/package.json +4 -2
- package/scripts/release.cjs +28 -1
- package/tests/build.test.js +1 -1
- package/tests/handlers.test.js +39 -0
- package/tests/startup-resilience.test.js +73 -0
- package/tools/definitions.js +15 -0
- package/tools/handler-modules/shared.js +187 -0
- package/tools/handlers.js +80 -19
- package/web/__tests__/api/workflow-history.test.js +14 -12
- package/web/app/docs/page.tsx +1 -1
- package/web/app/login/page.tsx +1 -1
- package/web/components/Hero.tsx +1 -1
- package/web/netlify.toml +5 -0
- package/web/next.config.ts +1 -0
- package/web/package-lock.json +5868 -2499
- package/web/package.json +4 -2
- package/workflow-state.js +13 -2
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,52 @@
|
|
|
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.7.17](https://github.com/programinglive/dev-workflow-mcp-server/compare/v1.7.16...v1.7.17) (2025-12-26)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
### ๐ Documentation
|
|
9
|
+
|
|
10
|
+
* update release notes for v1.7.16 ([0afd96b](https://github.com/programinglive/dev-workflow-mcp-server/commit/0afd96b754717953eb162f65426129457fc8626e))
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
### โจ Features
|
|
14
|
+
|
|
15
|
+
* allow skipping release step ([004e4fa](https://github.com/programinglive/dev-workflow-mcp-server/commit/004e4fae071e064b2c2efb9d69ecd97e794d3cb8))
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
### ๐งน Chores
|
|
19
|
+
|
|
20
|
+
* add @programinglive/commiter setup ([1d2a19d](https://github.com/programinglive/dev-workflow-mcp-server/commit/1d2a19d0453b50c6f0db35b15924b51a27c4891a))
|
|
21
|
+
|
|
22
|
+
### [1.7.16](https://github.com/programinglive/dev-workflow-mcp-server/compare/v1.7.15...v1.7.16) (2025-12-24)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
### ๐ Bug Fixes
|
|
26
|
+
|
|
27
|
+
* align database schema with dashboard and improve user ID handling ([69e7f38](https://github.com/programinglive/dev-workflow-mcp-server/commit/69e7f381955d15bb727a6e309082a344729d1c1e))
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
### ๐งน Chores
|
|
31
|
+
|
|
32
|
+
* cleanup temporary debugging scripts ([a5e2b15](https://github.com/programinglive/dev-workflow-mcp-server/commit/a5e2b15b894632efadce46d83caf4d008e50fdd3))
|
|
33
|
+
* sync version to 1.7.15 across web and landing pages ([c520e4a](https://github.com/programinglive/dev-workflow-mcp-server/commit/c520e4a5d795c880fb74f31142b128a9c55beb6f))
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
### ๐ Documentation
|
|
37
|
+
|
|
38
|
+
* update macOS MCP config instructions ([1e368da](https://github.com/programinglive/dev-workflow-mcp-server/commit/1e368da37bede45e0282b1312002f2fedeb6ad60))
|
|
39
|
+
|
|
40
|
+
### [1.7.15](https://github.com/programinglive/dev-workflow-mcp-server/compare/v1.7.14...v1.7.15) (2025-12-19)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
### ๐ Bug Fixes
|
|
44
|
+
|
|
45
|
+
* optimize startup performance and add database connection resilience ([bff2714](https://github.com/programinglive/dev-workflow-mcp-server/commit/bff27146fe9537788741e4a084191bce93588569))
|
|
46
|
+
* refactor Navbar and History to use absolute API client URLs v1.7.18 ([dc33eae](https://github.com/programinglive/dev-workflow-mcp-server/commit/dc33eae612067c5246ca69d08960090c0a0ad6bd))
|
|
47
|
+
* sync all version labels to 1.7.14 and improve netlify routing ([78712eb](https://github.com/programinglive/dev-workflow-mcp-server/commit/78712eb398458605f77b2ba78c9ff61a0be1d335))
|
|
48
|
+
* sync all version strings to 1.7.14 ([e2a1795](https://github.com/programinglive/dev-workflow-mcp-server/commit/e2a1795a7b794c8874b1e610de3370cd6ed5fa7a))
|
|
49
|
+
* sync version to 1.7.14 and fix static routing via trailingSlash ([1c152bd](https://github.com/programinglive/dev-workflow-mcp-server/commit/1c152bd23bbfd30a73e0a89107f4ea18564d20d0))
|
|
50
|
+
|
|
5
51
|
### [1.7.14](https://github.com/programinglive/dev-workflow-mcp-server/compare/v1.7.13...v1.7.14) (2025-12-19)
|
|
6
52
|
|
|
7
53
|
|
package/README.md
CHANGED
|
@@ -8,6 +8,11 @@
|
|
|
8
8
|
|
|
9
9
|
An MCP (Model Context Protocol) server that helps enforce development discipline and workflow best practices. This server acts as your coding conscience, reminding you to follow proper development workflows.
|
|
10
10
|
|
|
11
|
+
### ๐ก๏ธ Resilience Features
|
|
12
|
+
- **Startup Resilience**: 5-second connection timeouts for external databases.
|
|
13
|
+
- **Background Loading**: Non-critical assets (compatibility mirrors) initialize in the background to ensure sub-second startup.
|
|
14
|
+
- **Diagnostic Mode**: Automatic startup timing logs displayed in stderr.
|
|
15
|
+
|
|
11
16
|
## ๐ฏ Purpose
|
|
12
17
|
|
|
13
18
|
This MCP server guides you through a disciplined development workflow:
|
|
@@ -100,7 +105,12 @@ Point your MCP client to the server entry point. Replace `<PROJECT_ROOT>` with t
|
|
|
100
105
|
"mcpServers": {
|
|
101
106
|
"dev-workflow": {
|
|
102
107
|
"command": "node",
|
|
103
|
-
"
|
|
108
|
+
"cwd": "<PROJECT_ROOT>",
|
|
109
|
+
"args": ["index.js"],
|
|
110
|
+
"env": {
|
|
111
|
+
"DEV_WORKFLOW_DB_TYPE": "postgres",
|
|
112
|
+
"DEV_WORKFLOW_DB_URL": "postgres://USER:PASS@HOST:5432/devworkflow"
|
|
113
|
+
}
|
|
104
114
|
}
|
|
105
115
|
}
|
|
106
116
|
}
|
|
@@ -177,6 +187,28 @@ Point your MCP client to the server entry point. Replace `<PROJECT_ROOT>` with t
|
|
|
177
187
|
|
|
178
188
|
> **Note:** On Windows paths in JSON require escaped backslashes (e.g., `"C:\\path\\to\\project"`).
|
|
179
189
|
|
|
190
|
+
#### Full Windsurf MCP Config Example (macOS)
|
|
191
|
+
|
|
192
|
+
If you keep this repository checked out at `/Users/alex/code/dev-workflow-mcp-server` and want to point Windsurf at a hosted PostgreSQL instance, drop the following into `~/Library/Application Support/Windsurf/mcp_config.json`:
|
|
193
|
+
|
|
194
|
+
```json
|
|
195
|
+
{
|
|
196
|
+
"mcpServers": {
|
|
197
|
+
"dev-workflow": {
|
|
198
|
+
"command": "node",
|
|
199
|
+
"cwd": "/Users/alex/code/dev-workflow-mcp-server",
|
|
200
|
+
"args": ["index.js"],
|
|
201
|
+
"env": {
|
|
202
|
+
"DEV_WORKFLOW_DB_TYPE": "postgres",
|
|
203
|
+
"DEV_WORKFLOW_DB_URL": "postgres://devworkflow:devworkflow_secure_password@34.50.121.142:5432/devworkflow"
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
This mirrors the Windows configuration shared in previous releases, but avoids `npx` lookup issues on macOS by launching the local `index.js` directly.
|
|
211
|
+
|
|
180
212
|
### 4. Restart Windsurf/Claude Desktop
|
|
181
213
|
|
|
182
214
|
After adding the configuration, restart the application to load the MCP server.
|
|
@@ -201,6 +233,29 @@ Antigravity users should configure the MCP server in their `mcp_config.json`.
|
|
|
201
233
|
|
|
202
234
|
See [Antigravity Getting Started](./docs/antigravity/GETTING-STARTED.md) for detailed instructions and troubleshooting.
|
|
203
235
|
|
|
236
|
+
### โก Performance Optimization (Recommended)
|
|
237
|
+
|
|
238
|
+
To ensure the fastest possible startup (critical for IDE-integrated clients like Claude Desktop), we recommend pointing directly to `index.js` using `node` rather than `npx`. This avoids the overhead of checking for package updates.
|
|
239
|
+
|
|
240
|
+
**Optimized Configuration:**
|
|
241
|
+
|
|
242
|
+
```json
|
|
243
|
+
{
|
|
244
|
+
"mcpServers": {
|
|
245
|
+
"dev-workflow": {
|
|
246
|
+
"command": "node",
|
|
247
|
+
"args": ["<PROJECT_ROOT>\\index.js"],
|
|
248
|
+
"env": {
|
|
249
|
+
"DEV_WORKFLOW_USER_ID": "your_user_id"
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
The server automatically times its own startup and logs it to stderr:
|
|
257
|
+
`Dev Workflow MCP Server running on stdio (startup took 15ms)`
|
|
258
|
+
|
|
204
259
|
## โ๏ธ Configuration
|
|
205
260
|
|
|
206
261
|
### Database Setup
|
|
@@ -241,6 +296,19 @@ export DEV_WORKFLOW_DB_TYPE=postgres
|
|
|
241
296
|
export DEV_WORKFLOW_DB_URL="postgresql://user:password@localhost:5432/dev_workflow"
|
|
242
297
|
```
|
|
243
298
|
|
|
299
|
+
#### ๐ ๏ธ Database Schema Normalization (Postgres/MySQL)
|
|
300
|
+
To ensure compatibility with existing reporting dashboards, the `PostgresAdapter` and `MysqlAdapter` automatically normalize column names:
|
|
301
|
+
- `task_description` โ DB column: `description`
|
|
302
|
+
- `timestamp` โ DB column: `completed_at`
|
|
303
|
+
|
|
304
|
+
The adapters use aliases in queries so the MCP tools still receive the expected `task_description` and `timestamp` fields.
|
|
305
|
+
|
|
306
|
+
#### ๐ค User ID Handling (Postgres/MySQL)
|
|
307
|
+
These databases use an `INTEGER` column for `user_id`.
|
|
308
|
+
- **Numeric strings** (e.g., `"1"`) are parsed directly into integers.
|
|
309
|
+
- **Non-numeric strings** (e.g., `"programinglive"`) are automatically hashed into a consistent, positive integer to ensure compatibility with the schema while maintaining unique user isolation.
|
|
310
|
+
|
|
311
|
+
|
|
244
312
|
### User & State Management
|
|
245
313
|
|
|
246
314
|
| Variable | Description |
|
|
@@ -444,7 +512,7 @@ This keeps workflow state local to each developer's machine.
|
|
|
444
512
|
- `create_documentation` - Mark documentation as created
|
|
445
513
|
- `check_ready_to_commit` - Verify all steps are complete
|
|
446
514
|
- `commit_and_push` - Commit and push changes
|
|
447
|
-
- `perform_release` - Record release details
|
|
515
|
+
- `perform_release` - Record release details (or use `skip_release` when the project has no release automation)
|
|
448
516
|
- `complete_task` - Mark task as complete and reset
|
|
449
517
|
- `force_complete_task` - Force completion with reason
|
|
450
518
|
- `drop_task` - Abandon current task
|
|
@@ -485,6 +553,7 @@ The tool will:
|
|
|
485
553
|
5. `check_ready_to_commit`
|
|
486
554
|
6. `commit_and_push` with `commitMessage` and optional `branch`
|
|
487
555
|
7. `perform_release` with `releaseCommand`, plus optional `releaseNotes`, `releaseType`, and `preset`
|
|
556
|
+
- Alternatively call `skip_release` with a justification when the repository has no Node-based release step (e.g., Python-only or docs-only tasks)
|
|
488
557
|
8. `complete_task` reusing `commitMessage`
|
|
489
558
|
|
|
490
559
|
All arguments except the optional flags are required and must be non-empty strings.
|
|
@@ -503,7 +572,7 @@ The package ships with a release guard (`release-wrapper.js`) that backs the `np
|
|
|
503
572
|
|
|
504
573
|
If any requirements are missing, the guard exits with guidance to return to the MCP tools. This prevents accidentally bumping versions or tagging releases outside the managed workflow. To release correctly:
|
|
505
574
|
|
|
506
|
-
1. Use `perform_release {"command":"patch"}` (or `minor`/`major`) via the MCP client.
|
|
575
|
+
1. Use `perform_release {"command":"patch"}` (or `minor`/`major`) via the MCP client, or `skip_release {"reason":"<explanation>"}` if no release applies.
|
|
507
576
|
2. The guard runs automatically, verifies the workflow state, and records the release before letting you finish with `complete_task`.
|
|
508
577
|
|
|
509
578
|
### Automated npm Publishing
|
package/db/adapters/postgres.js
CHANGED
|
@@ -15,11 +15,25 @@ export class PostgresAdapter extends DbAdapter {
|
|
|
15
15
|
this.pool = null;
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
+
getNumericUserId(userId) {
|
|
19
|
+
let numericUserId = parseInt(userId, 10);
|
|
20
|
+
if (isNaN(numericUserId)) {
|
|
21
|
+
let hash = 0;
|
|
22
|
+
for (let i = 0; i < userId.length; i++) {
|
|
23
|
+
hash = (hash << 5) - hash + userId.charCodeAt(i);
|
|
24
|
+
hash |= 0;
|
|
25
|
+
}
|
|
26
|
+
numericUserId = Math.abs(hash);
|
|
27
|
+
}
|
|
28
|
+
return numericUserId;
|
|
29
|
+
}
|
|
30
|
+
|
|
18
31
|
async connect() {
|
|
19
32
|
if (!this.pool) {
|
|
20
33
|
this.pool = new Pool({
|
|
21
34
|
connectionString: this.config.connectionUrl,
|
|
22
|
-
//
|
|
35
|
+
connectionTimeoutMillis: 5000, // 5 second timeout for new connections
|
|
36
|
+
idleTimeoutMillis: 10000, // close idle connections after 10 seconds
|
|
23
37
|
});
|
|
24
38
|
await this.migrate();
|
|
25
39
|
}
|
|
@@ -31,23 +45,55 @@ export class PostgresAdapter extends DbAdapter {
|
|
|
31
45
|
await client.query(`
|
|
32
46
|
CREATE TABLE IF NOT EXISTS workflow_history (
|
|
33
47
|
id SERIAL PRIMARY KEY,
|
|
34
|
-
user_id
|
|
48
|
+
user_id INTEGER NOT NULL,
|
|
35
49
|
project_path TEXT DEFAULT '',
|
|
36
|
-
|
|
50
|
+
description TEXT NOT NULL,
|
|
37
51
|
task_type TEXT NOT NULL,
|
|
38
52
|
commit_message TEXT,
|
|
39
|
-
|
|
53
|
+
completed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
40
54
|
forced BOOLEAN DEFAULT FALSE,
|
|
41
55
|
force_reason TEXT,
|
|
42
56
|
dropped BOOLEAN DEFAULT FALSE,
|
|
43
57
|
drop_reason TEXT,
|
|
44
|
-
UNIQUE (user_id, project_path,
|
|
58
|
+
UNIQUE (user_id, project_path, completed_at, description)
|
|
45
59
|
)
|
|
46
60
|
`);
|
|
47
61
|
|
|
62
|
+
// Migration: Add columns to existing table if they don't exist
|
|
63
|
+
const columns = await client.query(`
|
|
64
|
+
SELECT column_name FROM information_schema.columns WHERE table_name = 'workflow_history'
|
|
65
|
+
`);
|
|
66
|
+
const existingColumns = columns.rows.map(r => r.column_name);
|
|
67
|
+
|
|
68
|
+
if (!existingColumns.includes('project_path')) {
|
|
69
|
+
await client.query('ALTER TABLE workflow_history ADD COLUMN project_path TEXT DEFAULT \'\'');
|
|
70
|
+
}
|
|
71
|
+
if (!existingColumns.includes('forced')) {
|
|
72
|
+
await client.query('ALTER TABLE workflow_history ADD COLUMN forced BOOLEAN DEFAULT FALSE');
|
|
73
|
+
}
|
|
74
|
+
if (!existingColumns.includes('force_reason')) {
|
|
75
|
+
await client.query('ALTER TABLE workflow_history ADD COLUMN force_reason TEXT');
|
|
76
|
+
}
|
|
77
|
+
if (!existingColumns.includes('dropped')) {
|
|
78
|
+
await client.query('ALTER TABLE workflow_history ADD COLUMN dropped BOOLEAN DEFAULT FALSE');
|
|
79
|
+
}
|
|
80
|
+
if (!existingColumns.includes('drop_reason')) {
|
|
81
|
+
await client.query('ALTER TABLE workflow_history ADD COLUMN drop_reason TEXT');
|
|
82
|
+
}
|
|
83
|
+
// Ensure completed_at vs timestamp handled if table exists
|
|
84
|
+
if (!existingColumns.includes('completed_at') && existingColumns.includes('timestamp')) {
|
|
85
|
+
await client.query('ALTER TABLE workflow_history RENAME COLUMN timestamp TO completed_at');
|
|
86
|
+
} else if (existingColumns.includes('completed_at') && !existingColumns.includes('timestamp')) {
|
|
87
|
+
// Keep completed_at
|
|
88
|
+
}
|
|
89
|
+
// Logic for description vs task_description
|
|
90
|
+
if (!existingColumns.includes('description') && existingColumns.includes('task_description')) {
|
|
91
|
+
await client.query('ALTER TABLE workflow_history RENAME COLUMN task_description TO description');
|
|
92
|
+
}
|
|
93
|
+
|
|
48
94
|
await client.query(`
|
|
49
95
|
CREATE TABLE IF NOT EXISTS project_summary (
|
|
50
|
-
user_id
|
|
96
|
+
user_id INTEGER,
|
|
51
97
|
project_path TEXT DEFAULT '',
|
|
52
98
|
total_tasks INTEGER DEFAULT 0,
|
|
53
99
|
task_types JSONB,
|
|
@@ -60,7 +106,7 @@ export class PostgresAdapter extends DbAdapter {
|
|
|
60
106
|
|
|
61
107
|
await client.query(`
|
|
62
108
|
CREATE TABLE IF NOT EXISTS workflow_state (
|
|
63
|
-
user_id
|
|
109
|
+
user_id INTEGER,
|
|
64
110
|
project_path TEXT DEFAULT '',
|
|
65
111
|
state_json JSONB,
|
|
66
112
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
@@ -75,12 +121,13 @@ export class PostgresAdapter extends DbAdapter {
|
|
|
75
121
|
async insertHistoryEntry(userId, projectPath, entry) {
|
|
76
122
|
await this.connect();
|
|
77
123
|
const normalizedProjectPath = projectPath || '';
|
|
124
|
+
const numericUserId = this.getNumericUserId(userId);
|
|
78
125
|
|
|
79
126
|
const query = `
|
|
80
127
|
INSERT INTO workflow_history
|
|
81
|
-
(user_id, project_path,
|
|
128
|
+
(user_id, project_path, description, task_type, commit_message, completed_at, forced, force_reason, dropped, drop_reason)
|
|
82
129
|
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
|
|
83
|
-
ON CONFLICT (user_id, project_path,
|
|
130
|
+
ON CONFLICT (user_id, project_path, completed_at, description) DO UPDATE SET
|
|
84
131
|
commit_message=EXCLUDED.commit_message,
|
|
85
132
|
forced=EXCLUDED.forced,
|
|
86
133
|
force_reason=EXCLUDED.force_reason,
|
|
@@ -89,12 +136,12 @@ export class PostgresAdapter extends DbAdapter {
|
|
|
89
136
|
`;
|
|
90
137
|
|
|
91
138
|
await this.pool.query(query, [
|
|
92
|
-
|
|
139
|
+
numericUserId,
|
|
93
140
|
normalizedProjectPath,
|
|
94
|
-
entry.taskDescription,
|
|
141
|
+
entry.taskDescription || entry.description,
|
|
95
142
|
entry.taskType,
|
|
96
143
|
entry.commitMessage,
|
|
97
|
-
entry.timestamp || new Date().toISOString(),
|
|
144
|
+
entry.timestamp || entry.completed_at || new Date().toISOString(),
|
|
98
145
|
entry.forced || false,
|
|
99
146
|
entry.forceReason || null,
|
|
100
147
|
entry.dropped || false,
|
|
@@ -107,14 +154,16 @@ export class PostgresAdapter extends DbAdapter {
|
|
|
107
154
|
async updateSummaryForUser(userId, projectPath) {
|
|
108
155
|
await this.connect();
|
|
109
156
|
const normalizedProjectPath = projectPath || '';
|
|
157
|
+
const numericUserId = this.getNumericUserId(userId);
|
|
110
158
|
|
|
111
159
|
const res = await this.pool.query(`
|
|
112
|
-
SELECT task_type, timestamp, task_description
|
|
160
|
+
SELECT task_type, completed_at as timestamp, description as task_description
|
|
113
161
|
FROM workflow_history
|
|
114
162
|
WHERE user_id = $1 AND project_path = $2
|
|
115
|
-
ORDER BY
|
|
163
|
+
ORDER BY completed_at DESC
|
|
116
164
|
LIMIT 20
|
|
117
|
-
`, [
|
|
165
|
+
`, [numericUserId, normalizedProjectPath]);
|
|
166
|
+
// ... (lines continue similarly)
|
|
118
167
|
|
|
119
168
|
const rows = res.rows;
|
|
120
169
|
|
|
@@ -144,7 +193,7 @@ export class PostgresAdapter extends DbAdapter {
|
|
|
144
193
|
`;
|
|
145
194
|
|
|
146
195
|
await this.pool.query(upsert, [
|
|
147
|
-
|
|
196
|
+
numericUserId,
|
|
148
197
|
normalizedProjectPath,
|
|
149
198
|
rows.length,
|
|
150
199
|
JSON.stringify(taskTypes),
|
|
@@ -157,10 +206,11 @@ export class PostgresAdapter extends DbAdapter {
|
|
|
157
206
|
async getSummaryForUser(userId, projectPath) {
|
|
158
207
|
await this.connect();
|
|
159
208
|
const normalizedProjectPath = projectPath || '';
|
|
209
|
+
const numericUserId = this.getNumericUserId(userId);
|
|
160
210
|
|
|
161
211
|
const res = await this.pool.query(`
|
|
162
212
|
SELECT * FROM project_summary WHERE user_id = $1 AND project_path = $2
|
|
163
|
-
`, [
|
|
213
|
+
`, [numericUserId, normalizedProjectPath]);
|
|
164
214
|
|
|
165
215
|
const row = res.rows[0];
|
|
166
216
|
if (!row) return null;
|
|
@@ -183,16 +233,18 @@ export class PostgresAdapter extends DbAdapter {
|
|
|
183
233
|
const sanitizedPageSize = Math.min(100, Math.max(1, Number.isFinite(Number(pageSize)) ? Math.floor(Number(pageSize)) : 20));
|
|
184
234
|
const offset = (sanitizedPage - 1) * sanitizedPageSize;
|
|
185
235
|
|
|
236
|
+
const numericUserId = this.getNumericUserId(userId);
|
|
237
|
+
|
|
186
238
|
const clauses = [];
|
|
187
|
-
const params = [
|
|
239
|
+
const params = [numericUserId, normalizedProjectPath];
|
|
188
240
|
let paramIdx = 3;
|
|
189
241
|
|
|
190
242
|
if (startDate) {
|
|
191
|
-
clauses.push(`DATE(
|
|
243
|
+
clauses.push(`DATE(completed_at) >= DATE($${paramIdx++})`);
|
|
192
244
|
params.push(startDate);
|
|
193
245
|
}
|
|
194
246
|
if (endDate) {
|
|
195
|
-
clauses.push(`DATE(
|
|
247
|
+
clauses.push(`DATE(completed_at) <= DATE($${paramIdx++})`);
|
|
196
248
|
params.push(endDate);
|
|
197
249
|
}
|
|
198
250
|
|
|
@@ -205,10 +257,10 @@ export class PostgresAdapter extends DbAdapter {
|
|
|
205
257
|
const total = parseInt(countRes.rows[0].total, 10);
|
|
206
258
|
|
|
207
259
|
const entriesRes = await this.pool.query(`
|
|
208
|
-
SELECT
|
|
260
|
+
SELECT *, description as task_description, completed_at as timestamp
|
|
209
261
|
FROM workflow_history
|
|
210
262
|
WHERE ${where}
|
|
211
|
-
ORDER BY
|
|
263
|
+
ORDER BY completed_at DESC
|
|
212
264
|
LIMIT $${paramIdx++}
|
|
213
265
|
OFFSET $${paramIdx++}
|
|
214
266
|
`, [...params, sanitizedPageSize, offset]);
|
|
@@ -228,23 +280,25 @@ export class PostgresAdapter extends DbAdapter {
|
|
|
228
280
|
const { startDate, endDate, frequency = "daily" } = options;
|
|
229
281
|
const format = FREQUENCY_FORMATS[frequency] || FREQUENCY_FORMATS.daily;
|
|
230
282
|
|
|
283
|
+
const numericUserId = this.getNumericUserId(userId);
|
|
284
|
+
|
|
231
285
|
const clauses = [];
|
|
232
|
-
const params = [
|
|
286
|
+
const params = [numericUserId, normalizedProjectPath];
|
|
233
287
|
let paramIdx = 3;
|
|
234
288
|
|
|
235
289
|
if (startDate) {
|
|
236
|
-
clauses.push(`DATE(
|
|
290
|
+
clauses.push(`DATE(completed_at) >= DATE($${paramIdx++})`);
|
|
237
291
|
params.push(startDate);
|
|
238
292
|
}
|
|
239
293
|
if (endDate) {
|
|
240
|
-
clauses.push(`DATE(
|
|
294
|
+
clauses.push(`DATE(completed_at) <= DATE($${paramIdx++})`);
|
|
241
295
|
params.push(endDate);
|
|
242
296
|
}
|
|
243
297
|
|
|
244
298
|
const where = [`user_id = $1`, `project_path = $2`, ...clauses].join(" AND ");
|
|
245
299
|
|
|
246
300
|
const res = await this.pool.query(`
|
|
247
|
-
SELECT to_char(
|
|
301
|
+
SELECT to_char(completed_at, '${format}') AS period, COUNT(*) AS count
|
|
248
302
|
FROM workflow_history
|
|
249
303
|
WHERE ${where}
|
|
250
304
|
GROUP BY period
|
|
@@ -267,8 +321,10 @@ export class PostgresAdapter extends DbAdapter {
|
|
|
267
321
|
updated_at=EXCLUDED.updated_at
|
|
268
322
|
`;
|
|
269
323
|
|
|
324
|
+
const numericUserId = this.getNumericUserId(userId);
|
|
325
|
+
|
|
270
326
|
await this.pool.query(query, [
|
|
271
|
-
|
|
327
|
+
numericUserId,
|
|
272
328
|
normalizedProjectPath,
|
|
273
329
|
JSON.stringify(state),
|
|
274
330
|
new Date().toISOString()
|
|
@@ -278,10 +334,11 @@ export class PostgresAdapter extends DbAdapter {
|
|
|
278
334
|
async getState(userId, projectPath) {
|
|
279
335
|
await this.connect();
|
|
280
336
|
const normalizedProjectPath = projectPath || '';
|
|
337
|
+
const numericUserId = this.getNumericUserId(userId);
|
|
281
338
|
|
|
282
339
|
const res = await this.pool.query(`
|
|
283
340
|
SELECT state_json FROM workflow_state WHERE user_id = $1 AND project_path = $2
|
|
284
|
-
`, [
|
|
341
|
+
`, [numericUserId, normalizedProjectPath]);
|
|
285
342
|
|
|
286
343
|
const row = res.rows[0];
|
|
287
344
|
if (!row) return null;
|
package/db/adapters/sqlite.js
CHANGED
|
@@ -25,9 +25,15 @@ export class SqliteAdapter extends DbAdapter {
|
|
|
25
25
|
// Otherwise, attempt to find project root relative to CWD
|
|
26
26
|
const initCwd = process.env.INIT_CWD || process.cwd();
|
|
27
27
|
let projectRoot = initCwd;
|
|
28
|
-
|
|
28
|
+
let limit = 20; // Prevent infinite loop in weird environments
|
|
29
|
+
while (projectRoot !== path.dirname(projectRoot) && limit > 0) {
|
|
29
30
|
if (existsSync(path.join(projectRoot, "package.json"))) break;
|
|
30
31
|
projectRoot = path.dirname(projectRoot);
|
|
32
|
+
limit--;
|
|
33
|
+
}
|
|
34
|
+
if (limit === 0) {
|
|
35
|
+
console.error("Warning: Could not find project root effectively, defaulting to INIT_CWD");
|
|
36
|
+
projectRoot = initCwd;
|
|
31
37
|
}
|
|
32
38
|
return path.join(projectRoot, ".state", DB_FILENAME);
|
|
33
39
|
}
|
package/docs/product/PRD.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
- **Product**: Development Workflow MCP Server
|
|
5
5
|
- **Prepared by**: Cascade (AI assistant)
|
|
6
6
|
- **Date**: 2025-12-16
|
|
7
|
-
- **Status**: Live v1.7.
|
|
7
|
+
- **Status**: Live v1.7.15
|
|
8
8
|
|
|
9
9
|
## 2. Problem Statement
|
|
10
10
|
Developers need structured guidance to maintain disciplined workflows across projects. Existing tooling lacks enforcement of sequential best practices (testing, documentation, release hygiene) and offers limited visibility into historical task execution.
|
|
@@ -67,7 +67,9 @@ Developers need structured guidance to maintain disciplined workflows across pro
|
|
|
67
67
|
3. **v1.5.0**: Netlify deployment support for static web dashboard (complete).
|
|
68
68
|
4. **v1.6.0+**: Dark/Light mode, UI Improvements (complete).
|
|
69
69
|
5. **Multi-Database Support**: Refactor storage layer to support MySQL and PostgreSQL (complete v1.7.1).
|
|
70
|
-
|
|
70
|
+
7. **v1.7.14 Performance & Resilience**: Implemented connection timeouts and optimized startup sequence for stdio clients (complete).
|
|
71
|
+
8. **v1.7.15 Schema Alignment**: Implemented Postgres/MySQL column normalization and automated integer User ID hashing for cross-dashboard compatibility (complete).
|
|
72
|
+
9. **Future**: Expanded dashboard analytics, customizable workflows, richer integrations.
|
|
71
73
|
|
|
72
74
|
## 11. Risks & Mitigations
|
|
73
75
|
- **Risk**: Workflow feels overly prescriptive. **Mitigation**: Provide escape hatches (`force_complete_task`, `drop_task`) with audit trails.
|
|
@@ -4,6 +4,8 @@ Authoritative history of user-visible changes for the Development Workflow MCP S
|
|
|
4
4
|
|
|
5
5
|
| Version | Date | Notes |
|
|
6
6
|
|---------|------|-------|
|
|
7
|
+
| 1.7.16 | 2025-12-24 | align database schema with dashboard and improve user ID handling (69e7f38) |
|
|
8
|
+
| 1.7.15 | 2025-12-19 | optimize startup performance and add database connection resilience (bff2714) |
|
|
7
9
|
| 1.7.14 | 2025-12-19 | add API dependencies and update login to use API client (94bb42f) |
|
|
8
10
|
| 1.7.13 | 2025-12-18 | remote db connection, history api 500 errors, and windows startup (ce559a6) |
|
|
9
11
|
| 1.7.12 | 2025-12-17 | post-release cleanup v1.7.11 (c2ee733) |
|
|
@@ -41,6 +43,27 @@ Authoritative history of user-visible changes for the Development Workflow MCP S
|
|
|
41
43
|
|
|
42
44
|
|
|
43
45
|
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
## 1.7.16 โ ๐ Bug Fixes
|
|
49
|
+
|
|
50
|
+
Released on **2025-12-24**.
|
|
51
|
+
|
|
52
|
+
- align database schema with dashboard and improve user ID handling (69e7f38)
|
|
53
|
+
- cleanup temporary debugging scripts (a5e2b15)
|
|
54
|
+
- sync version to 1.7.15 across web and landing pages (c520e4a)
|
|
55
|
+
- update macOS MCP config instructions (1e368da)
|
|
56
|
+
|
|
57
|
+
## 1.7.15 โ ๐ Bug Fixes
|
|
58
|
+
|
|
59
|
+
Released on **2025-12-19**.
|
|
60
|
+
|
|
61
|
+
- optimize startup performance and add database connection resilience (bff2714)
|
|
62
|
+
- refactor Navbar and History to use absolute API client URLs v1.7.18 (dc33eae)
|
|
63
|
+
- sync all version labels to 1.7.14 and improve netlify routing (78712eb)
|
|
64
|
+
- sync all version strings to 1.7.14 (e2a1795)
|
|
65
|
+
- sync version to 1.7.14 and fix static routing via trailingSlash (1c152bd)
|
|
66
|
+
|
|
44
67
|
## 1.7.14 โ โจ Features
|
|
45
68
|
|
|
46
69
|
Released on **2025-12-19**.
|
package/index.js
CHANGED
|
@@ -191,20 +191,30 @@ Use 'check_ready_to_commit' to verify workflow completion.
|
|
|
191
191
|
|
|
192
192
|
// Start server
|
|
193
193
|
export async function main() {
|
|
194
|
+
const startTime = Date.now();
|
|
195
|
+
console.error(`[${new Date().toISOString()}] MCP Server starting...`);
|
|
196
|
+
|
|
194
197
|
try {
|
|
195
198
|
// Initialize workflow state
|
|
196
199
|
workflowState = new WorkflowState();
|
|
200
|
+
console.error(`[${new Date().toISOString()}] Loading workflow state...`);
|
|
197
201
|
await workflowState.load();
|
|
202
|
+
console.error(`[${new Date().toISOString()}] Workflow state loaded.`);
|
|
203
|
+
|
|
204
|
+
console.error(`[${new Date().toISOString()}] Ensuring primary file...`);
|
|
198
205
|
await workflowState.ensurePrimaryFile();
|
|
206
|
+
console.error(`[${new Date().toISOString()}] Workflow state initialization complete.`);
|
|
199
207
|
} catch (error) {
|
|
200
|
-
console.error(
|
|
208
|
+
console.error(`[${new Date().toISOString()}] Warning: Failed to initialize workflow state:`, error.message);
|
|
201
209
|
// Continue anyway - workflow state is optional for MCP to function
|
|
202
210
|
workflowState = new WorkflowState();
|
|
203
211
|
}
|
|
204
212
|
|
|
205
213
|
const transport = new StdioServerTransport();
|
|
214
|
+
console.error(`[${new Date().toISOString()}] Connecting transport...`);
|
|
206
215
|
await server.connect(transport);
|
|
207
|
-
|
|
216
|
+
const duration = Date.now() - startTime;
|
|
217
|
+
console.error(`[${new Date().toISOString()}] Dev Workflow MCP Server running on stdio (startup took ${duration}ms)`);
|
|
208
218
|
}
|
|
209
219
|
|
|
210
220
|
// --- Lightweight CLI: dev-workflow-mcp call <toolName> [--args '<json>'] ---
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@programinglive/dev-workflow-mcp-server",
|
|
3
|
-
"version": "1.7.
|
|
3
|
+
"version": "1.7.17",
|
|
4
4
|
"description": "MCP server to enforce development workflow discipline",
|
|
5
5
|
"homepage": "https://devworkflow.programinglive.com/",
|
|
6
6
|
"mcpName": "io.github.programinglive/dev-workflow-mcp-server",
|
|
@@ -62,11 +62,13 @@
|
|
|
62
62
|
"devDependencies": {
|
|
63
63
|
"@commitlint/cli": "^20.2.0",
|
|
64
64
|
"@commitlint/config-conventional": "^20.2.0",
|
|
65
|
-
"@programinglive/commiter": "^1.2.
|
|
65
|
+
"@programinglive/commiter": "^1.2.17",
|
|
66
66
|
"autoprefixer": "^10.4.22",
|
|
67
|
+
"cross-env": "^10.1.0",
|
|
67
68
|
"husky": "^9.1.7",
|
|
68
69
|
"postcss": "^8.5.6",
|
|
69
70
|
"standard-version": "^9.5.0",
|
|
71
|
+
"supertest": "^7.1.4",
|
|
70
72
|
"tailwindcss": "^3.4.18"
|
|
71
73
|
},
|
|
72
74
|
"standard-version": {
|
package/scripts/release.cjs
CHANGED
|
@@ -11,6 +11,9 @@ const FS_FOK_PRELOAD_FLAG = buildPreloadFlag(fsFokPreloadPath);
|
|
|
11
11
|
|
|
12
12
|
require('./preload/fs-f-ok.cjs');
|
|
13
13
|
|
|
14
|
+
const DEFAULT_PRD_RELATIVE_PATH = path.join('docs', 'PRD.md');
|
|
15
|
+
const DEFAULT_PRD_DISPLAY_PATH = 'docs/PRD.md';
|
|
16
|
+
|
|
14
17
|
const VALID_RELEASE_TYPES = new Set([
|
|
15
18
|
'major',
|
|
16
19
|
'minor',
|
|
@@ -161,6 +164,8 @@ function runRelease({
|
|
|
161
164
|
throw new Error('Working tree has uncommitted changes. Commit or stash before running release.');
|
|
162
165
|
}
|
|
163
166
|
|
|
167
|
+
ensurePrdPresence({ cwd, dependencies });
|
|
168
|
+
|
|
164
169
|
const testResult = runProjectTests({ spawn, env });
|
|
165
170
|
if (testResult && typeof testResult.status === 'number' && testResult.status !== 0) {
|
|
166
171
|
return testResult;
|
|
@@ -219,9 +224,31 @@ module.exports = {
|
|
|
219
224
|
detectPackageManager,
|
|
220
225
|
runProjectTests,
|
|
221
226
|
runRelease,
|
|
222
|
-
isWorkingTreeClean
|
|
227
|
+
isWorkingTreeClean,
|
|
228
|
+
ensurePrdPresence
|
|
223
229
|
};
|
|
224
230
|
|
|
231
|
+
function ensurePrdPresence({ cwd = process.cwd(), dependencies = {} } = {}) {
|
|
232
|
+
const {
|
|
233
|
+
fsExistsSync = fs.existsSync,
|
|
234
|
+
logger = console,
|
|
235
|
+
prdRelativePath = DEFAULT_PRD_RELATIVE_PATH,
|
|
236
|
+
prdDisplayPath = DEFAULT_PRD_DISPLAY_PATH
|
|
237
|
+
} = dependencies;
|
|
238
|
+
|
|
239
|
+
const resolvedPath = path.isAbsolute(prdRelativePath)
|
|
240
|
+
? prdRelativePath
|
|
241
|
+
: path.join(cwd, prdRelativePath);
|
|
242
|
+
|
|
243
|
+
if (fsExistsSync(resolvedPath)) {
|
|
244
|
+
return true;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
const warn = typeof logger?.warn === 'function' ? logger.warn.bind(logger) : console.warn.bind(console);
|
|
248
|
+
warn(`โ ๏ธ Product Requirements Document missing (${prdDisplayPath}). Add ${DEFAULT_PRD_DISPLAY_PATH} so teams understand release expectations.`);
|
|
249
|
+
return false;
|
|
250
|
+
}
|
|
251
|
+
|
|
225
252
|
function buildPreloadFlag(filePath) {
|
|
226
253
|
const resolved = path.resolve(filePath);
|
|
227
254
|
const escaped = resolved.includes(' ')
|
package/tests/build.test.js
CHANGED
|
@@ -8,7 +8,7 @@ import path from 'path';
|
|
|
8
8
|
const exec = promisify(execCallback);
|
|
9
9
|
|
|
10
10
|
test('build generates dist/index.mjs file', async () => {
|
|
11
|
-
const { stderr } = await exec('npm run build:server');
|
|
11
|
+
const { stderr } = await exec('npx cross-env NODE_ENV=development npm run build:server');
|
|
12
12
|
// Allow the known vite warning about node:process
|
|
13
13
|
const knownWarning = '[plugin vite:resolve] Module "node:process" has been externalized for browser compatibility';
|
|
14
14
|
const tailwindOutput = 'โ tailwindcss';
|