@programinglive/dev-workflow-mcp-server 1.6.3 → 1.7.9
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/.gitattributes +10 -0
- package/.github/workflows/github-release.yml +17 -0
- package/CHANGELOG.md +110 -0
- package/README.md +69 -2
- package/db/adapters/base.js +46 -0
- package/db/adapters/mysql.js +292 -0
- package/db/adapters/postgres.js +291 -0
- package/db/adapters/sqlite.js +343 -0
- package/db/index.js +52 -183
- package/docs/product/PRD.md +15 -5
- package/docs/release-notes/RELEASE_NOTES.md +106 -0
- package/package.json +6 -3
- package/scripts/release.cjs +11 -4
- package/tools/handlers.js +12 -11
- package/vite.config.js +8 -0
- package/workflow-state.js +32 -5
- package/.github/workflows/npm-publish.yml +0 -50
package/.gitattributes
ADDED
|
@@ -12,6 +12,7 @@ on:
|
|
|
12
12
|
|
|
13
13
|
permissions:
|
|
14
14
|
contents: write
|
|
15
|
+
id-token: write
|
|
15
16
|
|
|
16
17
|
jobs:
|
|
17
18
|
release:
|
|
@@ -28,6 +29,7 @@ jobs:
|
|
|
28
29
|
uses: actions/setup-node@v4
|
|
29
30
|
with:
|
|
30
31
|
node-version: "20"
|
|
32
|
+
registry-url: "https://registry.npmjs.org"
|
|
31
33
|
|
|
32
34
|
- name: Determine version
|
|
33
35
|
id: version
|
|
@@ -47,6 +49,21 @@ jobs:
|
|
|
47
49
|
echo "tag=$TAG" >> "$GITHUB_OUTPUT"
|
|
48
50
|
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
|
|
49
51
|
|
|
52
|
+
- name: Update npm
|
|
53
|
+
run: npm install -g npm@latest
|
|
54
|
+
|
|
55
|
+
- name: Install dependencies
|
|
56
|
+
run: npm ci
|
|
57
|
+
|
|
58
|
+
- name: Install web dependencies
|
|
59
|
+
run: npm ci --prefix web
|
|
60
|
+
|
|
61
|
+
- name: Build package
|
|
62
|
+
run: npm run build
|
|
63
|
+
|
|
64
|
+
- name: Publish to npm
|
|
65
|
+
run: npm publish --provenance
|
|
66
|
+
|
|
50
67
|
- name: Generate release notes
|
|
51
68
|
run: |
|
|
52
69
|
echo "Extracting release notes for version ${{ steps.version.outputs.version }}"
|
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,116 @@
|
|
|
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.9](https://github.com/programinglive/dev-workflow-mcp-server/compare/v1.7.8...v1.7.9) (2025-12-16)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
### 🐛 Bug Fixes
|
|
9
|
+
|
|
10
|
+
* **ci:** apply official npm trusted publishing config ([9256583](https://github.com/programinglive/dev-workflow-mcp-server/commit/925658336c093c4e2fcfca30dfe469262d67c02d))
|
|
11
|
+
|
|
12
|
+
### [1.7.8](https://github.com/programinglive/dev-workflow-mcp-server/compare/v1.7.7...v1.7.8) (2025-12-16)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
### 🐛 Bug Fixes
|
|
16
|
+
|
|
17
|
+
* **ci:** remove all token config for pure OIDC publishing ([d4759cc](https://github.com/programinglive/dev-workflow-mcp-server/commit/d4759ccf492396c22fa5aa0fdfbd624c571f7f42))
|
|
18
|
+
|
|
19
|
+
### [1.7.7](https://github.com/programinglive/dev-workflow-mcp-server/compare/v1.7.6...v1.7.7) (2025-12-16)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
### 🐛 Bug Fixes
|
|
23
|
+
|
|
24
|
+
* **ci:** remove duplicates and apply robust trusted publishing config ([eedb661](https://github.com/programinglive/dev-workflow-mcp-server/commit/eedb661fce4461de68b636e8e7b60b4f0cf465b3))
|
|
25
|
+
|
|
26
|
+
### [1.7.6](https://github.com/programinglive/dev-workflow-mcp-server/compare/v1.7.5...v1.7.6) (2025-12-16)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
### 🐛 Bug Fixes
|
|
30
|
+
|
|
31
|
+
* **ci:** restore registry config with token fallback for Trusted Publishing ([542615c](https://github.com/programinglive/dev-workflow-mcp-server/commit/542615cb95fe1aaac5f1a5be18694a7577b8eb86))
|
|
32
|
+
|
|
33
|
+
### [1.7.5](https://github.com/programinglive/dev-workflow-mcp-server/compare/v1.7.4...v1.7.5) (2025-12-16)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
### 🐛 Bug Fixes
|
|
37
|
+
|
|
38
|
+
* **ci:** switch to tokenless trusted publishing ([fdfc4a9](https://github.com/programinglive/dev-workflow-mcp-server/commit/fdfc4a9a86485ee60f0f650e16aa9195671d9ab3))
|
|
39
|
+
|
|
40
|
+
### [1.7.4](https://github.com/programinglive/dev-workflow-mcp-server/compare/v1.7.3...v1.7.4) (2025-12-16)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
### 🐛 Bug Fixes
|
|
44
|
+
|
|
45
|
+
* **ci:** add id-token permission for npm provenance ([8930338](https://github.com/programinglive/dev-workflow-mcp-server/commit/8930338aaf90616f1c741c4634ac8e96d3c43fb7))
|
|
46
|
+
|
|
47
|
+
### [1.7.3](https://github.com/programinglive/dev-workflow-mcp-server/compare/v1.7.2...v1.7.3) (2025-12-16)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
### 🐛 Bug Fixes
|
|
51
|
+
|
|
52
|
+
* **ci:** add web dependencies installation step ([7d5dc62](https://github.com/programinglive/dev-workflow-mcp-server/commit/7d5dc62a213d3105c2329a1fb810cc6260fe4815))
|
|
53
|
+
|
|
54
|
+
### [1.7.2](https://github.com/programinglive/dev-workflow-mcp-server/compare/v1.7.1...v1.7.2) (2025-12-16)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
### 🐛 Bug Fixes
|
|
58
|
+
|
|
59
|
+
* **ci:** consolidate release and publish workflows ([98e5b31](https://github.com/programinglive/dev-workflow-mcp-server/commit/98e5b31fb64e39b1c846ce594215fb38ab13bf19))
|
|
60
|
+
|
|
61
|
+
### [1.7.1](https://github.com/programinglive/dev-workflow-mcp-server/compare/v1.7.0...v1.7.1) (2025-12-16)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
### 🐛 Bug Fixes
|
|
65
|
+
|
|
66
|
+
* build errors and warnings ([dc24831](https://github.com/programinglive/dev-workflow-mcp-server/commit/dc2483188a58d932590503ec2058c51e3de78d0b))
|
|
67
|
+
* suppress remaining build warnings ([f7d9b2d](https://github.com/programinglive/dev-workflow-mcp-server/commit/f7d9b2d9fb695e6699d65456747c21a301b62fee))
|
|
68
|
+
|
|
69
|
+
## [1.7.0](https://github.com/programinglive/dev-workflow-mcp-server/compare/v1.6.3...v1.7.0) (2025-12-16)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
### 🐛 Bug Fixes
|
|
73
|
+
|
|
74
|
+
* handle null CustPL in birthday-customers.blade.php (ERP-19) ([71b8f7e](https://github.com/programinglive/dev-workflow-mcp-server/commit/71b8f7ebbf83266008e28446630be97ae24e1d64))
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
### ♻️ Refactors
|
|
78
|
+
|
|
79
|
+
* integrate database adapters into workflow state ([370cbb4](https://github.com/programinglive/dev-workflow-mcp-server/commit/370cbb416b49c758239177296978f7deba5e49e2))
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
### 📝 Documentation
|
|
83
|
+
|
|
84
|
+
* update PRD and add configuration guide ([1dfccf5](https://github.com/programinglive/dev-workflow-mcp-server/commit/1dfccf5e37eee2994acaf9067320cf13a64fd515))
|
|
85
|
+
* update release notes for 1.6.3 ([afb9fb9](https://github.com/programinglive/dev-workflow-mcp-server/commit/afb9fb9b4c14be0a9f8ecfada3bb72ca75b7f116))
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
### 🧹 Chores
|
|
89
|
+
|
|
90
|
+
* add mysql2 and pg dependencies ([94423d7](https://github.com/programinglive/dev-workflow-mcp-server/commit/94423d72aab3cb1c2ee47d9a4995e05cceda1f11))
|
|
91
|
+
* **release:** null 🚀 ([93a17e2](https://github.com/programinglive/dev-workflow-mcp-server/commit/93a17e26f1995fda34d2ae40f61f39b697dc03bf))
|
|
92
|
+
* **release:** null 🚀 ([033097e](https://github.com/programinglive/dev-workflow-mcp-server/commit/033097eecd04b2dd3f9299d497ebe71d32f470b9))
|
|
93
|
+
* **release:** null 🚀 ([91c800a](https://github.com/programinglive/dev-workflow-mcp-server/commit/91c800a584f4b967fe60b8058684d5224543f26b))
|
|
94
|
+
* **release:** null 🚀 ([f0958c4](https://github.com/programinglive/dev-workflow-mcp-server/commit/f0958c40f187c0bd4ff77cb2f628fb2338dafa6b))
|
|
95
|
+
* **release:** null 🚀 ([ac3cc6e](https://github.com/programinglive/dev-workflow-mcp-server/commit/ac3cc6e8a5dea2d119c072fbb8029947e234643c))
|
|
96
|
+
* **release:** null 🚀 ([f3f9a00](https://github.com/programinglive/dev-workflow-mcp-server/commit/f3f9a00059c435d2e18940f8a4b41e166a779ff3))
|
|
97
|
+
* **release:** null 🚀 ([bcf7d95](https://github.com/programinglive/dev-workflow-mcp-server/commit/bcf7d9560af34b0a257bfe6566a0cd1572ebb5d1))
|
|
98
|
+
* **release:** null 🚀 ([74df6d6](https://github.com/programinglive/dev-workflow-mcp-server/commit/74df6d6d2972ab2c4702da7e773ab651a41daaa3))
|
|
99
|
+
* **release:** null 🚀 ([0d9dc4d](https://github.com/programinglive/dev-workflow-mcp-server/commit/0d9dc4d3ba751bbe69aecf6221eb79ea2f54ccd9))
|
|
100
|
+
* **release:** null 🚀 ([9667012](https://github.com/programinglive/dev-workflow-mcp-server/commit/966701266afec03b6f621670d2217b98c1a85eb1))
|
|
101
|
+
* **release:** null 🚀 ([db25d0c](https://github.com/programinglive/dev-workflow-mcp-server/commit/db25d0c9117e00d20584a5a175a727f16219bfe9))
|
|
102
|
+
* **release:** null 🚀 ([50d5353](https://github.com/programinglive/dev-workflow-mcp-server/commit/50d535373b080de2b765b0ac9c32ce6f7d4a55f7))
|
|
103
|
+
* **release:** null 🚀 ([ee0a540](https://github.com/programinglive/dev-workflow-mcp-server/commit/ee0a54097cc481c2aeb74ca99a41b34914ab540f))
|
|
104
|
+
* **release:** null 🚀 ([92558af](https://github.com/programinglive/dev-workflow-mcp-server/commit/92558af18f41169760dfbe069f32c9e4969b656d))
|
|
105
|
+
* **release:** null 🚀 ([cd851ac](https://github.com/programinglive/dev-workflow-mcp-server/commit/cd851ac6d3ddd91286b63a83d05a1a5a5a17aee6))
|
|
106
|
+
* **release:** null 🚀 ([49ec562](https://github.com/programinglive/dev-workflow-mcp-server/commit/49ec5629b8308dc9ed19672b07966c3711748817))
|
|
107
|
+
* **release:** null 🚀 ([bcc7a3c](https://github.com/programinglive/dev-workflow-mcp-server/commit/bcc7a3c1a554dbea2dce16585ef926bed925e062))
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
### ✨ Features
|
|
111
|
+
|
|
112
|
+
* implement database adapters for sqlite, mysql, postgres ([8e7416a](https://github.com/programinglive/dev-workflow-mcp-server/commit/8e7416a467286eaa26051e8e0b4af5f350295087))
|
|
113
|
+
* persist active workflow state to database ([d29546d](https://github.com/programinglive/dev-workflow-mcp-server/commit/d29546dee52940170a9a855146ca870fa078cf66))
|
|
114
|
+
|
|
5
115
|
### [1.6.3](https://github.com/programinglive/dev-workflow-mcp-server/compare/v1.6.2...v1.6.3) (2025-12-06)
|
|
6
116
|
|
|
7
117
|
|
package/README.md
CHANGED
|
@@ -181,9 +181,76 @@ Antigravity users should configure the MCP server in their `mcp_config.json`.
|
|
|
181
181
|
|
|
182
182
|
See [Antigravity Getting Started](./docs/antigravity/GETTING-STARTED.md) for detailed instructions and troubleshooting.
|
|
183
183
|
|
|
184
|
-
##
|
|
184
|
+
## ⚙️ Configuration
|
|
185
|
+
|
|
186
|
+
### Database Backend
|
|
187
|
+
|
|
188
|
+
By default, the server uses a local **SQLite** database stored in `.state/dev-workflow.db`. You can switch to **MySQL** or **PostgreSQL** for centralized storage using environment variables.
|
|
189
|
+
|
|
190
|
+
| Variable | Description | Default |
|
|
191
|
+
|----------|-------------|---------|
|
|
192
|
+
| `DEV_WORKFLOW_DB_TYPE` | Database driver (`sqlite`, `mysql`, `postgres`) | `sqlite` |
|
|
193
|
+
| `DEV_WORKFLOW_DB_URL` | Connection string for MySQL/Postgres | `null` |
|
|
194
|
+
| `DEV_WORKFLOW_DB_PATH` | Override path for the SQLite database file | `<project>/.state/dev-workflow.db` |
|
|
195
|
+
|
|
196
|
+
#### Examples
|
|
197
|
+
|
|
198
|
+
**MySQL:**
|
|
199
|
+
```bash
|
|
200
|
+
export DEV_WORKFLOW_DB_TYPE=mysql
|
|
201
|
+
export DEV_WORKFLOW_DB_URL="mysql://user:password@localhost:3306/dev_workflow"
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
**PostgreSQL:**
|
|
205
|
+
```bash
|
|
206
|
+
export DEV_WORKFLOW_DB_TYPE=postgres
|
|
207
|
+
export DEV_WORKFLOW_DB_URL="postgresql://user:password@localhost:5432/dev_workflow"
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
### User & State Management
|
|
211
|
+
|
|
212
|
+
| Variable | Description |
|
|
213
|
+
|----------|-------------|
|
|
214
|
+
| `DEV_WORKFLOW_USER_ID` | Override the auto-generated user ID (e.g., set to your name/email) |
|
|
215
|
+
| `DEV_WORKFLOW_STATE_FILE` | Override the location of the `workflow-state.json` file |
|
|
216
|
+
|
|
217
|
+
### Using Multiple Clients (Antigravity & Windsurf)
|
|
218
|
+
|
|
219
|
+
If you use multiple AI coding tools simultaneously (e.g., Antigravity and Windsurf) on the same project, they will share the same workflow state by default.
|
|
220
|
+
|
|
221
|
+
To maintain **separate, distinct sessions** for each tool, configure a unique `DEV_WORKFLOW_USER_ID` for each.
|
|
222
|
+
|
|
223
|
+
**Antigravity Config (`mcp_config.json`):**
|
|
224
|
+
```json
|
|
225
|
+
{
|
|
226
|
+
"mcpServers": {
|
|
227
|
+
"dev-workflow": {
|
|
228
|
+
"command": "node",
|
|
229
|
+
"args": ["path/to/server/index.js"],
|
|
230
|
+
"env": {
|
|
231
|
+
"DEV_WORKFLOW_USER_ID": "antigravity_user"
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
**Windsurf Config:**
|
|
239
|
+
Add the environment variable in your Windsurf MCP settings:
|
|
240
|
+
```json
|
|
241
|
+
{
|
|
242
|
+
"mcpServers": {
|
|
243
|
+
"dev-workflow": {
|
|
244
|
+
"command": "node",
|
|
245
|
+
"args": ["path/to/server/index.js"],
|
|
246
|
+
"env": {
|
|
247
|
+
"DEV_WORKFLOW_USER_ID": "windsurf_user"
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
```
|
|
185
253
|
|
|
186
|
-
This project includes a Vite-based build system for creating optimized distributions.
|
|
187
254
|
|
|
188
255
|
### Scripts
|
|
189
256
|
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
export class DbAdapter {
|
|
2
|
+
constructor(config = {}) {
|
|
3
|
+
this.config = config;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
async connect() {
|
|
7
|
+
throw new Error("connect() must be implemented by subclass");
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
async migrate() {
|
|
11
|
+
throw new Error("migrate() must be implemented by subclass");
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* @param {string} userId - User identifier
|
|
16
|
+
* @param {string} projectPath - Path/ID of the project
|
|
17
|
+
* @param {Object} entry - Workflow history entry
|
|
18
|
+
*/
|
|
19
|
+
async insertHistoryEntry(userId, projectPath, entry) {
|
|
20
|
+
throw new Error("insertHistoryEntry() must be implemented by subclass");
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
async updateSummaryForUser(userId, projectPath) {
|
|
24
|
+
throw new Error("updateSummaryForUser() must be implemented by subclass");
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
async getSummaryForUser(userId, projectPath) {
|
|
28
|
+
throw new Error("getSummaryForUser() must be implemented by subclass");
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async getHistoryForUser(userId, projectPath, options = {}) {
|
|
32
|
+
throw new Error("getHistoryForUser() must be implemented by subclass");
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async getHistorySummary(userId, projectPath, options = {}) {
|
|
36
|
+
throw new Error("getHistorySummary() must be implemented by subclass");
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async saveState(userId, projectPath, state) {
|
|
40
|
+
throw new Error("saveState() must be implemented by subclass");
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
async getState(userId, projectPath) {
|
|
44
|
+
throw new Error("getState() must be implemented by subclass");
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
import mysql from "mysql2/promise";
|
|
2
|
+
import { DbAdapter } from "./base.js";
|
|
3
|
+
|
|
4
|
+
const FREQUENCY_FORMATS = {
|
|
5
|
+
daily: "%Y-%m-%d",
|
|
6
|
+
monthly: "%Y-%m",
|
|
7
|
+
yearly: "%Y",
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export class MysqlAdapter extends DbAdapter {
|
|
11
|
+
constructor(config = {}) {
|
|
12
|
+
super(config);
|
|
13
|
+
this.pool = null;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
async connect() {
|
|
17
|
+
if (!this.pool) {
|
|
18
|
+
this.pool = mysql.createPool({
|
|
19
|
+
uri: this.config.connectionUrl,
|
|
20
|
+
waitForConnections: true,
|
|
21
|
+
connectionLimit: 10,
|
|
22
|
+
queueLimit: 0,
|
|
23
|
+
// Support named parameters or handle manually? mysql2 supports '?' by default.
|
|
24
|
+
});
|
|
25
|
+
await this.migrate();
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async migrate() {
|
|
30
|
+
const conn = await this.pool.getConnection();
|
|
31
|
+
try {
|
|
32
|
+
await conn.query(`
|
|
33
|
+
CREATE TABLE IF NOT EXISTS workflow_history (
|
|
34
|
+
id INT AUTO_INCREMENT PRIMARY KEY,
|
|
35
|
+
user_id VARCHAR(255) NOT NULL,
|
|
36
|
+
project_path VARCHAR(255) DEFAULT '',
|
|
37
|
+
task_description TEXT NOT NULL,
|
|
38
|
+
task_type VARCHAR(50) NOT NULL,
|
|
39
|
+
commit_message TEXT,
|
|
40
|
+
timestamp DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
41
|
+
forced TINYINT(1) DEFAULT 0,
|
|
42
|
+
force_reason TEXT,
|
|
43
|
+
dropped TINYINT(1) DEFAULT 0,
|
|
44
|
+
drop_reason TEXT,
|
|
45
|
+
UNIQUE KEY uniq_workflow_entry (user_id, project_path, timestamp, task_description(100))
|
|
46
|
+
)
|
|
47
|
+
`);
|
|
48
|
+
|
|
49
|
+
await conn.query(`
|
|
50
|
+
CREATE TABLE IF NOT EXISTS project_summary (
|
|
51
|
+
user_id VARCHAR(255),
|
|
52
|
+
project_path VARCHAR(255) DEFAULT '',
|
|
53
|
+
total_tasks INT DEFAULT 0,
|
|
54
|
+
task_types JSON,
|
|
55
|
+
last_active DATETIME,
|
|
56
|
+
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
57
|
+
recent_tasks JSON,
|
|
58
|
+
PRIMARY KEY (user_id, project_path)
|
|
59
|
+
)
|
|
60
|
+
`);
|
|
61
|
+
|
|
62
|
+
await conn.query(`
|
|
63
|
+
CREATE TABLE IF NOT EXISTS workflow_state (
|
|
64
|
+
user_id VARCHAR(255),
|
|
65
|
+
project_path VARCHAR(255) DEFAULT '',
|
|
66
|
+
state_json JSON,
|
|
67
|
+
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
68
|
+
PRIMARY KEY (user_id, project_path)
|
|
69
|
+
)
|
|
70
|
+
`);
|
|
71
|
+
|
|
72
|
+
// Indexes are effectively covered by PRIMARY/UNIQUE keys, but we can add secondary if needed.
|
|
73
|
+
} finally {
|
|
74
|
+
conn.release();
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
async insertHistoryEntry(userId, projectPath, entry) {
|
|
79
|
+
await this.connect();
|
|
80
|
+
const normalizedProjectPath = projectPath || '';
|
|
81
|
+
|
|
82
|
+
// MySQL INSERT ON DUPLICATE KEY UPDATE
|
|
83
|
+
const query = `
|
|
84
|
+
INSERT INTO workflow_history
|
|
85
|
+
(user_id, project_path, task_description, task_type, commit_message, timestamp, forced, force_reason, dropped, drop_reason)
|
|
86
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
87
|
+
ON DUPLICATE KEY UPDATE
|
|
88
|
+
commit_message=VALUES(commit_message),
|
|
89
|
+
forced=VALUES(forced),
|
|
90
|
+
force_reason=VALUES(force_reason),
|
|
91
|
+
dropped=VALUES(dropped),
|
|
92
|
+
drop_reason=VALUES(drop_reason)
|
|
93
|
+
`;
|
|
94
|
+
|
|
95
|
+
await this.pool.execute(query, [
|
|
96
|
+
userId,
|
|
97
|
+
normalizedProjectPath,
|
|
98
|
+
entry.taskDescription,
|
|
99
|
+
entry.taskType,
|
|
100
|
+
entry.commitMessage,
|
|
101
|
+
entry.timestamp || new Date().toISOString(),
|
|
102
|
+
entry.forced ? 1 : 0,
|
|
103
|
+
entry.forceReason || null,
|
|
104
|
+
entry.dropped ? 1 : 0,
|
|
105
|
+
entry.dropReason || null
|
|
106
|
+
]);
|
|
107
|
+
|
|
108
|
+
await this.updateSummaryForUser(userId, normalizedProjectPath);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
async updateSummaryForUser(userId, projectPath) {
|
|
112
|
+
await this.connect();
|
|
113
|
+
const normalizedProjectPath = projectPath || '';
|
|
114
|
+
|
|
115
|
+
const [rows] = await this.pool.execute(`
|
|
116
|
+
SELECT task_type, timestamp, task_description
|
|
117
|
+
FROM workflow_history
|
|
118
|
+
WHERE user_id = ? AND project_path = ?
|
|
119
|
+
ORDER BY timestamp DESC
|
|
120
|
+
LIMIT 20
|
|
121
|
+
`, [userId, normalizedProjectPath]);
|
|
122
|
+
|
|
123
|
+
const taskTypes = {};
|
|
124
|
+
for (const row of rows) {
|
|
125
|
+
taskTypes[row.task_type] = (taskTypes[row.task_type] || 0) + 1;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const recentTasks = rows.slice(0, 5).map(r => ({
|
|
129
|
+
description: r.task_description,
|
|
130
|
+
type: r.task_type,
|
|
131
|
+
timestamp: r.timestamp,
|
|
132
|
+
}));
|
|
133
|
+
|
|
134
|
+
const lastActive = rows[0]?.timestamp || null;
|
|
135
|
+
|
|
136
|
+
const upsert = `
|
|
137
|
+
INSERT INTO project_summary
|
|
138
|
+
(user_id, project_path, total_tasks, task_types, last_active, recent_tasks, updated_at)
|
|
139
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
140
|
+
ON DUPLICATE KEY UPDATE
|
|
141
|
+
total_tasks=VALUES(total_tasks),
|
|
142
|
+
task_types=VALUES(task_types),
|
|
143
|
+
last_active=VALUES(last_active),
|
|
144
|
+
recent_tasks=VALUES(recent_tasks),
|
|
145
|
+
updated_at=VALUES(updated_at)
|
|
146
|
+
`;
|
|
147
|
+
|
|
148
|
+
await this.pool.execute(upsert, [
|
|
149
|
+
userId,
|
|
150
|
+
normalizedProjectPath,
|
|
151
|
+
rows.length,
|
|
152
|
+
JSON.stringify(taskTypes),
|
|
153
|
+
lastActive,
|
|
154
|
+
JSON.stringify(recentTasks),
|
|
155
|
+
new Date().toISOString()
|
|
156
|
+
]);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
async getSummaryForUser(userId, projectPath) {
|
|
160
|
+
await this.connect();
|
|
161
|
+
const normalizedProjectPath = projectPath || '';
|
|
162
|
+
|
|
163
|
+
const [rows] = await this.pool.execute(`
|
|
164
|
+
SELECT * FROM project_summary WHERE user_id = ? AND project_path = ?
|
|
165
|
+
`, [userId, normalizedProjectPath]);
|
|
166
|
+
|
|
167
|
+
const row = rows[0];
|
|
168
|
+
if (!row) return null;
|
|
169
|
+
|
|
170
|
+
return {
|
|
171
|
+
totalTasks: row.total_tasks,
|
|
172
|
+
taskTypes: row.task_types, // MySQL driver typically parses JSON columns automatically
|
|
173
|
+
lastActive: row.last_active,
|
|
174
|
+
recentTasks: row.recent_tasks,
|
|
175
|
+
updatedAt: row.updated_at,
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
async getHistoryForUser(userId, projectPath, options = {}) {
|
|
180
|
+
await this.connect();
|
|
181
|
+
const normalizedProjectPath = projectPath || '';
|
|
182
|
+
|
|
183
|
+
const { page = 1, pageSize = 20, startDate, endDate } = options;
|
|
184
|
+
const sanitizedPage = Math.max(1, Number.isFinite(Number(page)) ? Math.floor(Number(page)) : 1);
|
|
185
|
+
const sanitizedPageSize = Math.min(100, Math.max(1, Number.isFinite(Number(pageSize)) ? Math.floor(Number(pageSize)) : 20));
|
|
186
|
+
const offset = (sanitizedPage - 1) * sanitizedPageSize;
|
|
187
|
+
|
|
188
|
+
const clauses = [];
|
|
189
|
+
const params = [userId, normalizedProjectPath];
|
|
190
|
+
|
|
191
|
+
if (startDate) {
|
|
192
|
+
clauses.push("DATE(timestamp) >= DATE(?)");
|
|
193
|
+
params.push(startDate);
|
|
194
|
+
}
|
|
195
|
+
if (endDate) {
|
|
196
|
+
clauses.push("DATE(timestamp) <= DATE(?)");
|
|
197
|
+
params.push(endDate);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
const where = ["user_id = ?", "project_path = ?", ...clauses].join(" AND ");
|
|
201
|
+
|
|
202
|
+
const [countRows] = await this.pool.execute(
|
|
203
|
+
`SELECT COUNT(*) as total FROM workflow_history WHERE ${where}`,
|
|
204
|
+
params
|
|
205
|
+
);
|
|
206
|
+
const total = countRows[0].total;
|
|
207
|
+
|
|
208
|
+
const [entries] = await this.pool.execute(`
|
|
209
|
+
SELECT *
|
|
210
|
+
FROM workflow_history
|
|
211
|
+
WHERE ${where}
|
|
212
|
+
ORDER BY timestamp DESC
|
|
213
|
+
LIMIT ?
|
|
214
|
+
OFFSET ?
|
|
215
|
+
`, [...params, sanitizedPageSize.toString(), offset.toString()]); // prepared statement params often stringified in strict mode, but execute handles numbers usually.
|
|
216
|
+
|
|
217
|
+
return {
|
|
218
|
+
entries,
|
|
219
|
+
total,
|
|
220
|
+
page: sanitizedPage,
|
|
221
|
+
pageSize: sanitizedPageSize,
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
async getHistorySummary(userId, projectPath, options = {}) {
|
|
226
|
+
await this.connect();
|
|
227
|
+
const normalizedProjectPath = projectPath || '';
|
|
228
|
+
|
|
229
|
+
const { startDate, endDate, frequency = "daily" } = options;
|
|
230
|
+
const format = FREQUENCY_FORMATS[frequency] || FREQUENCY_FORMATS.daily;
|
|
231
|
+
|
|
232
|
+
const clauses = [];
|
|
233
|
+
const params = [userId, normalizedProjectPath];
|
|
234
|
+
if (startDate) {
|
|
235
|
+
clauses.push("DATE(timestamp) >= DATE(?)");
|
|
236
|
+
params.push(startDate);
|
|
237
|
+
}
|
|
238
|
+
if (endDate) {
|
|
239
|
+
clauses.push("DATE(timestamp) <= DATE(?)");
|
|
240
|
+
params.push(endDate);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
const where = ["user_id = ?", "project_path = ?", ...clauses].join(" AND ");
|
|
244
|
+
|
|
245
|
+
const [rows] = await this.pool.execute(`
|
|
246
|
+
SELECT DATE_FORMAT(timestamp, '${format}') AS period, COUNT(*) AS count
|
|
247
|
+
FROM workflow_history
|
|
248
|
+
WHERE ${where}
|
|
249
|
+
GROUP BY period
|
|
250
|
+
ORDER BY period DESC
|
|
251
|
+
`, params);
|
|
252
|
+
|
|
253
|
+
return rows;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
async saveState(userId, projectPath, state) {
|
|
257
|
+
await this.connect();
|
|
258
|
+
const normalizedProjectPath = projectPath || '';
|
|
259
|
+
|
|
260
|
+
const query = `
|
|
261
|
+
INSERT INTO workflow_state
|
|
262
|
+
(user_id, project_path, state_json, updated_at)
|
|
263
|
+
VALUES (?, ?, ?, ?)
|
|
264
|
+
ON DUPLICATE KEY UPDATE
|
|
265
|
+
state_json=VALUES(state_json),
|
|
266
|
+
updated_at=VALUES(updated_at)
|
|
267
|
+
`;
|
|
268
|
+
|
|
269
|
+
await this.pool.execute(query, [
|
|
270
|
+
userId,
|
|
271
|
+
normalizedProjectPath,
|
|
272
|
+
JSON.stringify(state),
|
|
273
|
+
new Date().toISOString()
|
|
274
|
+
]);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
async getState(userId, projectPath) {
|
|
278
|
+
await this.connect();
|
|
279
|
+
const normalizedProjectPath = projectPath || '';
|
|
280
|
+
|
|
281
|
+
const [rows] = await this.pool.execute(`
|
|
282
|
+
SELECT state_json FROM workflow_state WHERE user_id = ? AND project_path = ?
|
|
283
|
+
`, [userId, normalizedProjectPath]);
|
|
284
|
+
|
|
285
|
+
const row = rows[0];
|
|
286
|
+
if (!row) return null;
|
|
287
|
+
|
|
288
|
+
// mysql2 usually parses JSON columns, but if not:
|
|
289
|
+
return typeof row.state_json === 'string' ? JSON.parse(row.state_json) : row.state_json;
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|