@sureshdsk/devflow-mcp 3.1.0 → 3.1.2
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/README.md +7 -1
- package/dist/server/start.js +159 -44
- package/dist/server/start.js.map +1 -1
- package/drizzle/meta/_journal.json +14 -0
- package/package.json +3 -1
- package/scripts/init-db.js +86 -63
- package/src/db/index.ts +50 -1
|
@@ -15,6 +15,20 @@
|
|
|
15
15
|
"when": 1771834907179,
|
|
16
16
|
"tag": "0001_mixed_genesis",
|
|
17
17
|
"breakpoints": true
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
"idx": 2,
|
|
21
|
+
"version": "6",
|
|
22
|
+
"when": 1771835000000,
|
|
23
|
+
"tag": "0002_task_body_field",
|
|
24
|
+
"breakpoints": true
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
"idx": 3,
|
|
28
|
+
"version": "6",
|
|
29
|
+
"when": 1771835100000,
|
|
30
|
+
"tag": "0003_specs_table",
|
|
31
|
+
"breakpoints": true
|
|
18
32
|
}
|
|
19
33
|
]
|
|
20
34
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sureshdsk/devflow-mcp",
|
|
3
|
-
"version": "3.1.
|
|
3
|
+
"version": "3.1.2",
|
|
4
4
|
"description": "Context-First Kanban for AI Agents - MCP server with web UI for managing AI agent workflows",
|
|
5
5
|
"author": "DevFlow Team",
|
|
6
6
|
"license": "MIT",
|
|
@@ -20,6 +20,8 @@
|
|
|
20
20
|
"project-management"
|
|
21
21
|
],
|
|
22
22
|
"packageManager": "pnpm@10.14.0",
|
|
23
|
+
"type": "commonjs",
|
|
24
|
+
"main": "./bin/devflow.js",
|
|
23
25
|
"bin": {
|
|
24
26
|
"devflow": "./bin/devflow.js",
|
|
25
27
|
"devflow-mcp": "./bin/devflow.js"
|
package/scripts/init-db.js
CHANGED
|
@@ -17,6 +17,8 @@ const {
|
|
|
17
17
|
|
|
18
18
|
const DB_DIR = path.join(os.homedir(), '.devflow');
|
|
19
19
|
const DB_PATH = path.join(DB_DIR, 'devflow.db');
|
|
20
|
+
// Always resolve migrations relative to the package, not process.cwd()
|
|
21
|
+
const MIGRATIONS_FOLDER = path.join(__dirname, '..', 'drizzle');
|
|
20
22
|
|
|
21
23
|
function parseInitArgs(args) {
|
|
22
24
|
const options = {};
|
|
@@ -86,68 +88,35 @@ async function chooseSchemaTemplate(projectRoot, options) {
|
|
|
86
88
|
};
|
|
87
89
|
}
|
|
88
90
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
91
|
+
/**
|
|
92
|
+
* Validate that the migrations folder exists and has the required structure.
|
|
93
|
+
*/
|
|
94
|
+
function validateMigrationsFolder(folder) {
|
|
95
|
+
const journalPath = path.join(folder, 'meta', '_journal.json');
|
|
96
|
+
if (!fs.existsSync(folder)) {
|
|
97
|
+
throw new Error(
|
|
98
|
+
`Migrations folder not found at: ${folder}\n` +
|
|
99
|
+
'This usually means the package was not installed correctly.\n' +
|
|
100
|
+
'Try reinstalling: npm install -g @sureshdsk/devflow-mcp',
|
|
101
|
+
);
|
|
99
102
|
}
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
await client.execute('PRAGMA journal_mode = WAL');
|
|
107
|
-
await client.execute('PRAGMA foreign_keys = ON');
|
|
108
|
-
|
|
109
|
-
// If DB has old schema (features table), drop and recreate
|
|
110
|
-
const tables = await client.execute(
|
|
111
|
-
`SELECT name FROM sqlite_master WHERE type='table' AND name='features'`,
|
|
112
|
-
);
|
|
113
|
-
if (tables.rows.length > 0) {
|
|
114
|
-
console.log('⚠ Detected old schema (features table). Recreating database...');
|
|
115
|
-
client.close();
|
|
116
|
-
await fs.promises.unlink(DB_PATH);
|
|
117
|
-
const freshClient = createClient({ url: `file:${DB_PATH}` });
|
|
118
|
-
await freshClient.execute('PRAGMA journal_mode = WAL');
|
|
119
|
-
await freshClient.execute('PRAGMA foreign_keys = ON');
|
|
120
|
-
const freshDb = drizzle(freshClient);
|
|
121
|
-
console.log('Running migrations...');
|
|
122
|
-
await migrate(freshDb, { migrationsFolder: './drizzle' });
|
|
123
|
-
console.log('✓ Migrations complete');
|
|
124
|
-
const existingProjects = await freshClient.execute('SELECT id FROM projects LIMIT 1');
|
|
125
|
-
if (existingProjects.rows.length === 0) {
|
|
126
|
-
const id = randomUUID();
|
|
127
|
-
const now = Math.floor(Date.now() / 1000);
|
|
128
|
-
await freshClient.execute({
|
|
129
|
-
sql: `INSERT INTO projects (id, name, description, status, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?)`,
|
|
130
|
-
args: [id, 'Default Project', 'Default project for tasks', 'active', now, now],
|
|
131
|
-
});
|
|
132
|
-
console.log('✓ Created default project');
|
|
133
|
-
}
|
|
134
|
-
const selected = await chooseSchemaTemplate(projectRoot, options);
|
|
135
|
-
console.log(`\n✓ Default schema template: ${selected.schemaId} (${selected.reason})`);
|
|
136
|
-
console.log(`✓ Saved project config: ${selected.configPath}`);
|
|
137
|
-
console.log(`\n✅ Database initialized at: ${DB_PATH}`);
|
|
138
|
-
console.log('\nYou can now:');
|
|
139
|
-
console.log(' 1. Start the web UI: devflow dev');
|
|
140
|
-
console.log(' 2. Start the MCP server: devflow mcp');
|
|
141
|
-
freshClient.close();
|
|
142
|
-
return;
|
|
103
|
+
if (!fs.existsSync(journalPath)) {
|
|
104
|
+
throw new Error(
|
|
105
|
+
`Migration journal not found at: ${journalPath}\n` +
|
|
106
|
+
'The drizzle/meta/_journal.json file is missing from the package.\n' +
|
|
107
|
+
'Try reinstalling: npm install -g @sureshdsk/devflow-mcp',
|
|
108
|
+
);
|
|
143
109
|
}
|
|
110
|
+
}
|
|
144
111
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
112
|
+
/**
|
|
113
|
+
* Run migrations and create the default project if needed.
|
|
114
|
+
* Shared logic used by both fresh-db and existing-db paths.
|
|
115
|
+
*/
|
|
116
|
+
async function runMigrationsAndSeed(client, db) {
|
|
148
117
|
console.log('Running migrations...');
|
|
149
118
|
try {
|
|
150
|
-
await migrate(db, { migrationsFolder:
|
|
119
|
+
await migrate(db, { migrationsFolder: MIGRATIONS_FOLDER });
|
|
151
120
|
} catch (err) {
|
|
152
121
|
if (err.message?.includes('already exists')) {
|
|
153
122
|
console.log('✓ Schema already up to date');
|
|
@@ -163,28 +132,82 @@ async function initDatabase(options = {}) {
|
|
|
163
132
|
const id = randomUUID();
|
|
164
133
|
const now = Math.floor(Date.now() / 1000);
|
|
165
134
|
await client.execute({
|
|
166
|
-
sql: `INSERT INTO projects (id, name, description, status, created_at, updated_at)
|
|
167
|
-
VALUES (?, ?, ?, ?, ?, ?)`,
|
|
135
|
+
sql: `INSERT INTO projects (id, name, description, status, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?)`,
|
|
168
136
|
args: [id, 'Default Project', 'Default project for tasks', 'active', now, now],
|
|
169
137
|
});
|
|
170
138
|
console.log('✓ Created default project');
|
|
171
139
|
}
|
|
140
|
+
}
|
|
172
141
|
|
|
173
|
-
|
|
142
|
+
async function initDatabase(options = {}) {
|
|
143
|
+
const projectRoot = process.cwd();
|
|
144
|
+
console.log('Initializing DevFlow database...');
|
|
145
|
+
|
|
146
|
+
// Validate migrations folder before doing anything
|
|
147
|
+
validateMigrationsFolder(MIGRATIONS_FOLDER);
|
|
148
|
+
|
|
149
|
+
// Ensure directory exists
|
|
150
|
+
await fs.promises.mkdir(DB_DIR, { recursive: true });
|
|
151
|
+
console.log(`✓ Database directory: ${DB_DIR}`);
|
|
152
|
+
|
|
153
|
+
// Create database connection
|
|
154
|
+
const client = createClient({
|
|
155
|
+
url: `file:${DB_PATH}`,
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
try {
|
|
159
|
+
await client.execute('PRAGMA journal_mode = WAL');
|
|
160
|
+
await client.execute('PRAGMA foreign_keys = ON');
|
|
161
|
+
|
|
162
|
+
// If DB has old schema (features table), drop and recreate
|
|
163
|
+
const tables = await client.execute(
|
|
164
|
+
`SELECT name FROM sqlite_master WHERE type='table' AND name='features'`,
|
|
165
|
+
);
|
|
166
|
+
if (tables.rows.length > 0) {
|
|
167
|
+
console.log('⚠ Detected old schema (features table). Recreating database...');
|
|
168
|
+
client.close();
|
|
169
|
+
await fs.promises.unlink(DB_PATH);
|
|
170
|
+
const freshClient = createClient({ url: `file:${DB_PATH}` });
|
|
171
|
+
try {
|
|
172
|
+
await freshClient.execute('PRAGMA journal_mode = WAL');
|
|
173
|
+
await freshClient.execute('PRAGMA foreign_keys = ON');
|
|
174
|
+
const freshDb = drizzle(freshClient);
|
|
175
|
+
await runMigrationsAndSeed(freshClient, freshDb);
|
|
176
|
+
const selected = await chooseSchemaTemplate(projectRoot, options);
|
|
177
|
+
printSuccess(selected);
|
|
178
|
+
} finally {
|
|
179
|
+
freshClient.close();
|
|
180
|
+
}
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const db = drizzle(client);
|
|
185
|
+
await runMigrationsAndSeed(client, db);
|
|
186
|
+
|
|
187
|
+
const selected = await chooseSchemaTemplate(projectRoot, options);
|
|
188
|
+
printSuccess(selected);
|
|
189
|
+
} finally {
|
|
190
|
+
client.close();
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function printSuccess(selected) {
|
|
174
195
|
console.log(`\n✓ Default schema template: ${selected.schemaId} (${selected.reason})`);
|
|
175
196
|
console.log(`✓ Saved project config: ${selected.configPath}`);
|
|
176
197
|
console.log(`\n✅ Database initialized at: ${DB_PATH}`);
|
|
177
198
|
console.log('\nYou can now:');
|
|
178
199
|
console.log(' 1. Start the web UI: devflow dev');
|
|
179
200
|
console.log(' 2. Start the MCP server: devflow mcp');
|
|
180
|
-
|
|
181
|
-
client.close();
|
|
182
201
|
}
|
|
183
202
|
|
|
184
203
|
if (require.main === module) {
|
|
185
204
|
const options = parseInitArgs(process.argv.slice(2));
|
|
186
205
|
initDatabase(options).catch((error) => {
|
|
187
|
-
console.error('Failed to initialize database:', error);
|
|
206
|
+
console.error('Failed to initialize database:', error.message || error);
|
|
207
|
+
console.error('\nTroubleshooting:');
|
|
208
|
+
console.error(' 1. Try reinstalling: npm install -g @sureshdsk/devflow-mcp');
|
|
209
|
+
console.error(' 2. Ensure ~/.devflow directory is writable');
|
|
210
|
+
console.error(' 3. Report issues at: https://github.com/anthropics/devflow-mcp/issues');
|
|
188
211
|
process.exit(1);
|
|
189
212
|
});
|
|
190
213
|
}
|
package/src/db/index.ts
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { createClient } from '@libsql/client';
|
|
2
2
|
import { drizzle } from 'drizzle-orm/libsql';
|
|
3
|
+
import { migrate } from 'drizzle-orm/libsql/migrator';
|
|
3
4
|
import * as schema from './schema';
|
|
4
|
-
import { mkdirSync } from 'fs';
|
|
5
|
+
import { mkdirSync, existsSync } from 'fs';
|
|
5
6
|
import { join } from 'path';
|
|
6
7
|
import { homedir } from 'os';
|
|
8
|
+
import { randomUUID } from 'crypto';
|
|
7
9
|
|
|
8
10
|
const DB_DIR = join(homedir(), '.devflow');
|
|
9
11
|
const DB_PATH = join(DB_DIR, 'devflow.db');
|
|
@@ -17,7 +19,28 @@ function ensureDbDir() {
|
|
|
17
19
|
}
|
|
18
20
|
}
|
|
19
21
|
|
|
22
|
+
/**
|
|
23
|
+
* Find the migrations folder. Works whether running from source (dev)
|
|
24
|
+
* or from the built dist/server output (production).
|
|
25
|
+
*/
|
|
26
|
+
function findMigrationsFolder(): string {
|
|
27
|
+
// Try common locations relative to this file
|
|
28
|
+
const candidates = [
|
|
29
|
+
join(__dirname, '..', '..', 'drizzle'), // from src/db/ or dist/server/
|
|
30
|
+
join(__dirname, '..', 'drizzle'), // from src/ or dist/
|
|
31
|
+
join(__dirname, 'drizzle'), // same dir
|
|
32
|
+
];
|
|
33
|
+
for (const candidate of candidates) {
|
|
34
|
+
if (existsSync(join(candidate, 'meta', '_journal.json'))) {
|
|
35
|
+
return candidate;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
// Fallback — return first candidate and let drizzle give a clear error
|
|
39
|
+
return candidates[0];
|
|
40
|
+
}
|
|
41
|
+
|
|
20
42
|
let dbInstance: ReturnType<typeof drizzle> | null = null;
|
|
43
|
+
let migrationRan = false;
|
|
21
44
|
|
|
22
45
|
export async function getDb() {
|
|
23
46
|
if (dbInstance) return dbInstance;
|
|
@@ -33,6 +56,32 @@ export async function getDb() {
|
|
|
33
56
|
|
|
34
57
|
dbInstance = drizzle(client, { schema });
|
|
35
58
|
|
|
59
|
+
// Auto-run migrations on first connection to ensure schema is up to date.
|
|
60
|
+
// This is a safety net for cases where postinstall didn't run.
|
|
61
|
+
if (!migrationRan) {
|
|
62
|
+
migrationRan = true;
|
|
63
|
+
try {
|
|
64
|
+
const migrationsFolder = findMigrationsFolder();
|
|
65
|
+
if (existsSync(join(migrationsFolder, 'meta', '_journal.json'))) {
|
|
66
|
+
await migrate(dbInstance, { migrationsFolder });
|
|
67
|
+
|
|
68
|
+
// Seed default project if needed
|
|
69
|
+
const rows = await client.execute('SELECT id FROM projects LIMIT 1');
|
|
70
|
+
if (rows.rows.length === 0) {
|
|
71
|
+
const id = randomUUID();
|
|
72
|
+
const now = Math.floor(Date.now() / 1000);
|
|
73
|
+
await client.execute({
|
|
74
|
+
sql: `INSERT INTO projects (id, name, description, status, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?)`,
|
|
75
|
+
args: [id, 'Default Project', 'Default project for tasks', 'active', now, now],
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
} catch {
|
|
80
|
+
// Non-fatal: if migrations fail here, the explicit `devflow init` path
|
|
81
|
+
// will give the user a proper error message.
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
36
85
|
return dbInstance;
|
|
37
86
|
}
|
|
38
87
|
|