@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.
@@ -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.0",
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"
@@ -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
- async function initDatabase(options = {}) {
90
- const projectRoot = process.cwd();
91
- console.log('Initializing DevFlow database...');
92
-
93
- // Ensure directory exists
94
- try {
95
- await fs.promises.mkdir(DB_DIR, { recursive: true });
96
- console.log(`✓ Created directory: ${DB_DIR}`);
97
- } catch (error) {
98
- console.log(`✓ Directory already exists: ${DB_DIR}`);
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
- // Create database connection
102
- const client = createClient({
103
- url: `file:${DB_PATH}`,
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
- const db = drizzle(client);
146
-
147
- // Run migrations
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: './drizzle' });
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
- const selected = await chooseSchemaTemplate(projectRoot, options);
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