devsmind-mcp 2.2.0 → 2.3.0

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.
Files changed (51) hide show
  1. package/README.md +234 -527
  2. package/dist/cli/analyze.d.ts +13 -0
  3. package/dist/cli/analyze.js +143 -0
  4. package/dist/cli/analyze.js.map +1 -0
  5. package/dist/cli/index.js +76 -0
  6. package/dist/cli/index.js.map +1 -1
  7. package/dist/cli/integrations/memory.d.ts +11 -0
  8. package/dist/cli/integrations/memory.js +156 -0
  9. package/dist/cli/integrations/memory.js.map +1 -0
  10. package/dist/cli/integrations/prompt.d.ts +5 -1
  11. package/dist/cli/integrations/prompt.js +29 -7
  12. package/dist/cli/integrations/prompt.js.map +1 -1
  13. package/dist/cli/integrations/registry.d.ts +35 -0
  14. package/dist/cli/integrations/registry.js +59 -0
  15. package/dist/cli/integrations/registry.js.map +1 -1
  16. package/dist/cli/rule.js +37 -46
  17. package/dist/cli/rule.js.map +1 -1
  18. package/dist/cli/sync.d.ts +7 -0
  19. package/dist/cli/sync.js +40 -7
  20. package/dist/cli/sync.js.map +1 -1
  21. package/dist/cli/workflow.d.ts +8 -0
  22. package/dist/cli/workflow.js +156 -0
  23. package/dist/cli/workflow.js.map +1 -0
  24. package/dist/db/analyze.d.ts +67 -0
  25. package/dist/db/analyze.js +163 -0
  26. package/dist/db/analyze.js.map +1 -0
  27. package/dist/db/database.d.ts +166 -3
  28. package/dist/db/database.js +700 -64
  29. package/dist/db/database.js.map +1 -1
  30. package/dist/db/schema.d.ts +28 -1
  31. package/dist/db/schema.js +34 -0
  32. package/dist/db/schema.js.map +1 -1
  33. package/dist/db/staging.d.ts +4 -0
  34. package/dist/db/staging.js +16 -2
  35. package/dist/db/staging.js.map +1 -1
  36. package/dist/db/workflow-import.d.ts +22 -0
  37. package/dist/db/workflow-import.js +116 -0
  38. package/dist/db/workflow-import.js.map +1 -0
  39. package/dist/mcp/server.d.ts +9 -0
  40. package/dist/mcp/server.js +417 -27
  41. package/dist/mcp/server.js.map +1 -1
  42. package/dist/utils/config.d.ts +2 -0
  43. package/dist/utils/config.js +11 -0
  44. package/dist/utils/config.js.map +1 -1
  45. package/dist/utils/git.d.ts +14 -0
  46. package/dist/utils/git.js +43 -0
  47. package/dist/utils/git.js.map +1 -0
  48. package/dist/utils/scanner.d.ts +7 -0
  49. package/dist/utils/scanner.js +9 -3
  50. package/dist/utils/scanner.js.map +1 -1
  51. package/package.json +1 -1
@@ -0,0 +1,156 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ var __importDefault = (this && this.__importDefault) || function (mod) {
36
+ return (mod && mod.__esModule) ? mod : { "default": mod };
37
+ };
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.handleWorkflow = handleWorkflow;
40
+ exports.handleWorkflowImport = handleWorkflowImport;
41
+ const fs = __importStar(require("fs"));
42
+ const path = __importStar(require("path"));
43
+ const prompts_1 = __importDefault(require("prompts"));
44
+ const config_1 = require("../utils/config");
45
+ const database_1 = require("../db/database");
46
+ const workflow_import_1 = require("../db/workflow-import");
47
+ function openDb(pathOpt) {
48
+ const devmindDir = (0, config_1.resolveDevmindDir)(pathOpt);
49
+ if (!devmindDir) {
50
+ console.error(`❌ No .devmind directory found.\n` +
51
+ ` Run from inside a DevsMind brain folder, or pass --path <devmind_path>.`);
52
+ process.exit(1);
53
+ }
54
+ return { db: new database_1.DevMindDatabase(path.join(devmindDir, 'brain.db')), devmindDir };
55
+ }
56
+ /** `devsmind workflow` — interactive list/view/pause/resume. Day-to-day creation and step-recording happens through the MCP tools the agent calls, not this command. */
57
+ async function handleWorkflow(opts) {
58
+ const { db } = openDb(opts.path);
59
+ try {
60
+ await runWorkflowLoop(db);
61
+ }
62
+ finally {
63
+ db.close();
64
+ }
65
+ }
66
+ async function runWorkflowLoop(db) {
67
+ while (true) {
68
+ const workflows = db.listWorkflows();
69
+ if (workflows.length === 0) {
70
+ console.log('📝 No workflows yet. Create one via the workflow_create MCP tool, or import existing docs with `devsmind workflow import <path>`.');
71
+ return;
72
+ }
73
+ const statusEmoji = { active: '🟢', paused: '⏸️', completed: '✅' };
74
+ const choices = workflows.map(w => ({
75
+ title: `${statusEmoji[w.status] || ''} [${w.status}] ${w.name}`,
76
+ value: w.id
77
+ }));
78
+ choices.push({ title: '🚪 Exit', value: 'exit' });
79
+ const response = await (0, prompts_1.default)({
80
+ type: 'select',
81
+ name: 'id',
82
+ message: `Workflows (${workflows.length}):`,
83
+ choices
84
+ });
85
+ if (!response.id || response.id === 'exit') {
86
+ console.log('🚪 Goodbye!');
87
+ break;
88
+ }
89
+ await showWorkflowMenu(db, response.id);
90
+ }
91
+ }
92
+ async function showWorkflowMenu(db, id) {
93
+ while (true) {
94
+ let workflow;
95
+ let steps, artifacts;
96
+ try {
97
+ ({ workflow, steps, artifacts } = db.getWorkflowContext(id));
98
+ }
99
+ catch {
100
+ return;
101
+ }
102
+ console.log(`\n==================================================`);
103
+ console.log(`📋 ${workflow.name} [${workflow.status}]`);
104
+ console.log(`==================================================`);
105
+ console.log(workflow.description);
106
+ console.log(`\nSteps (${steps.length}):`);
107
+ for (const s of steps) {
108
+ console.log(` ${s.step_index}. ${s.summary}${s.pending_tasks ? ` (pending: ${s.pending_tasks})` : ''}`);
109
+ }
110
+ console.log(`\nArtifacts (${artifacts.length}):`);
111
+ for (const a of artifacts) {
112
+ console.log(` - [${a.type}] ${a.source_name} → ${a.file_path.replace(/\\/g, '/')}`);
113
+ }
114
+ const choices = [];
115
+ if (workflow.status !== 'active')
116
+ choices.push({ title: '▶️ Resume (make active)', value: 'resume' });
117
+ if (workflow.status === 'active')
118
+ choices.push({ title: '⏸️ Pause', value: 'pause' });
119
+ if (workflow.status !== 'completed')
120
+ choices.push({ title: '✅ Mark completed', value: 'complete' });
121
+ choices.push({ title: '⬅️ Back to list', value: 'back' });
122
+ const response = await (0, prompts_1.default)({ type: 'select', name: 'action', message: 'Action:', choices });
123
+ if (!response.action || response.action === 'back')
124
+ return;
125
+ if (response.action === 'resume')
126
+ db.resumeWorkflow(id);
127
+ if (response.action === 'pause')
128
+ db.pauseWorkflow();
129
+ if (response.action === 'complete')
130
+ db.completeWorkflow(id);
131
+ }
132
+ }
133
+ /** `devsmind workflow import <path>` — imports a folder of .md flow docs, or a single file, as paused workflows. */
134
+ async function handleWorkflowImport(pathArg, opts) {
135
+ const { db } = openDb(opts.path);
136
+ try {
137
+ const resolved = path.resolve(pathArg);
138
+ const isDir = fs.existsSync(resolved) && fs.statSync(resolved).isDirectory();
139
+ const result = (0, workflow_import_1.importWorkflowDocs)(db, isDir ? resolved : undefined, isDir ? undefined : resolved);
140
+ console.log(`\n📥 Import complete.`);
141
+ if (result.created.length)
142
+ console.log(` Created (${result.created.length}): ${result.created.join(', ')}`);
143
+ if (result.updated.length)
144
+ console.log(` Updated (${result.updated.length}): ${result.updated.join(', ')}`);
145
+ if (result.skipped.length)
146
+ console.log(` Skipped (${result.skipped.length}): ${result.skipped.join(', ')}`);
147
+ if (!result.created.length && !result.updated.length && !result.skipped.length) {
148
+ console.log(' Nothing to import.');
149
+ }
150
+ console.log();
151
+ }
152
+ finally {
153
+ db.close();
154
+ }
155
+ }
156
+ //# sourceMappingURL=workflow.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"workflow.js","sourceRoot":"","sources":["../../src/cli/workflow.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,67 @@
1
+ import { DevMindDatabase } from './database';
2
+ export interface AnalysisOptions {
3
+ fix?: boolean;
4
+ godEntityThreshold?: number;
5
+ }
6
+ export interface AnalysisReport {
7
+ fixed: boolean;
8
+ summary: Record<string, number>;
9
+ god_entities: {
10
+ id: string;
11
+ name: string;
12
+ file_path: string;
13
+ degree: number;
14
+ }[];
15
+ circular_dependencies: string[][];
16
+ orphaned_nodes: {
17
+ id: string;
18
+ name: string;
19
+ file_path: string;
20
+ }[];
21
+ dangling_edges: {
22
+ source_node_id: string;
23
+ target_node_id: string;
24
+ }[];
25
+ duplicate_ids: {
26
+ lower_id: string;
27
+ ids: string[];
28
+ }[];
29
+ missing_developer_attribution: {
30
+ id: string;
31
+ node_id: string;
32
+ updated_at: string;
33
+ }[];
34
+ empty_code_snapshots: {
35
+ id: string;
36
+ node_id: string;
37
+ updated_at: string;
38
+ }[];
39
+ spurious_nodes: {
40
+ id: string;
41
+ name: string;
42
+ file_path: string;
43
+ }[];
44
+ missing_files: {
45
+ id: string;
46
+ name: string;
47
+ file_path: string;
48
+ }[];
49
+ renamed_files: {
50
+ repo: string;
51
+ from: string;
52
+ to: string;
53
+ migrated: boolean;
54
+ }[];
55
+ untracked_files: {
56
+ repo: string;
57
+ file: string;
58
+ }[];
59
+ }
60
+ /**
61
+ * Runs every local, zero-AI health check against the graph and — when `fix` is true —
62
+ * applies only the safe/reversible fixes (soft-deprecate dead nodes, delete dangling
63
+ * edges, migrate renames). Everything else is report-only: god entities, cycles,
64
+ * duplicate ids, missing developer attribution, empty snapshots, and untracked files
65
+ * all need human/AI judgement a mechanical fixer shouldn't make on its own.
66
+ */
67
+ export declare function runAnalysis(db: DevMindDatabase, workspaceRoot: string, opts?: AnalysisOptions): AnalysisReport;
@@ -0,0 +1,163 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.runAnalysis = runAnalysis;
37
+ const fs = __importStar(require("fs"));
38
+ const path = __importStar(require("path"));
39
+ const config_1 = require("../utils/config");
40
+ const git_1 = require("../utils/git");
41
+ const scanner_1 = require("../utils/scanner");
42
+ const LAST_ANALYSIS_KEY = 'last_analysis_at';
43
+ const DEFAULT_LOOKBACK_DAYS = 30;
44
+ /**
45
+ * Runs every local, zero-AI health check against the graph and — when `fix` is true —
46
+ * applies only the safe/reversible fixes (soft-deprecate dead nodes, delete dangling
47
+ * edges, migrate renames). Everything else is report-only: god entities, cycles,
48
+ * duplicate ids, missing developer attribution, empty snapshots, and untracked files
49
+ * all need human/AI judgement a mechanical fixer shouldn't make on its own.
50
+ */
51
+ function runAnalysis(db, workspaceRoot, opts = {}) {
52
+ const fix = opts.fix === true;
53
+ const godEntityThreshold = opts.godEntityThreshold ?? 15;
54
+ const god_entities = db.getGodEntities(godEntityThreshold);
55
+ const circular_dependencies = db.getCircularDependencies();
56
+ const orphaned_nodes = db.getOrphanedNodes().map(n => ({ id: n.id, name: n.name, file_path: n.file_path }));
57
+ const dangling_edges = db.getDanglingEdges().map(c => ({ source_node_id: c.source_node_id, target_node_id: c.target_node_id }));
58
+ const duplicate_ids = db.getDuplicateNodeIds().map(d => ({ lower_id: d.lowerId, ids: d.ids }));
59
+ const missing_developer_attribution = db.getHistoryMissingDeveloper();
60
+ const empty_code_snapshots = db.getEmptyCodeSnapshots();
61
+ const { spurious: spurious_nodes, missingFile: missing_files } = db.findSpuriousAndMissingFileNodes(workspaceRoot);
62
+ const lastAnalysisAt = db.getSystemMeta(LAST_ANALYSIS_KEY);
63
+ const lookbackIso = lastAnalysisAt || new Date(Date.now() - DEFAULT_LOOKBACK_DAYS * 24 * 60 * 60 * 1000).toISOString();
64
+ const renamed_files = [];
65
+ const untracked_files = [];
66
+ const context = db.getContext();
67
+ if (context) {
68
+ for (const repo of context.config.repos) {
69
+ const repoPath = (0, config_1.resolveRepoPath)(context, repo.name);
70
+ if (!repoPath || !fs.existsSync(repoPath))
71
+ continue;
72
+ for (const r of (0, git_1.getRenamedFilesSince)(repoPath, lookbackIso)) {
73
+ renamed_files.push({ repo: repo.name, from: r.from, to: r.to, migrated: false });
74
+ }
75
+ const changed = (0, git_1.getChangedFilesSince)(repoPath, lookbackIso);
76
+ const knownFiles = new Set(db.listNodes({ include_deprecated: true }).flatMap(n => (n.file_path || '').split(',').map(p => p.trim()).filter(Boolean)));
77
+ for (const relFile of changed) {
78
+ const ext = path.extname(relFile).toLowerCase();
79
+ if (!scanner_1.INDEXABLE_EXTENSIONS.has(ext))
80
+ continue;
81
+ const absFile = path.resolve(repoPath, relFile);
82
+ if (!fs.existsSync(absFile))
83
+ continue; // deleted since — not a blind spot
84
+ const hasNode = Array.from(knownFiles).some(f => path.resolve(f) === absFile);
85
+ if (!hasNode)
86
+ untracked_files.push({ repo: repo.name, file: relFile });
87
+ }
88
+ }
89
+ }
90
+ if (fix) {
91
+ for (const n of [...orphaned_nodes, ...spurious_nodes, ...missing_files]) {
92
+ db.deprecateNode(n.id);
93
+ }
94
+ for (const e of dangling_edges) {
95
+ db.deleteDanglingEdge(e.source_node_id, e.target_node_id);
96
+ }
97
+ if (context) {
98
+ for (const r of renamed_files) {
99
+ const migrated = migrateRename(db, context, r.repo, r.from, r.to);
100
+ r.migrated = migrated;
101
+ }
102
+ }
103
+ db.setSystemMeta(LAST_ANALYSIS_KEY, new Date().toISOString());
104
+ }
105
+ const summary = {
106
+ god_entities: god_entities.length,
107
+ circular_dependencies: circular_dependencies.length,
108
+ orphaned_nodes: orphaned_nodes.length,
109
+ dangling_edges: dangling_edges.length,
110
+ duplicate_ids: duplicate_ids.length,
111
+ missing_developer_attribution: missing_developer_attribution.length,
112
+ empty_code_snapshots: empty_code_snapshots.length,
113
+ spurious_nodes: spurious_nodes.length,
114
+ missing_files: missing_files.length,
115
+ renamed_files: renamed_files.length,
116
+ untracked_files: untracked_files.length,
117
+ };
118
+ return {
119
+ fixed: fix,
120
+ summary,
121
+ god_entities,
122
+ circular_dependencies,
123
+ orphaned_nodes,
124
+ dangling_edges,
125
+ duplicate_ids,
126
+ missing_developer_attribution,
127
+ empty_code_snapshots,
128
+ spurious_nodes,
129
+ missing_files,
130
+ renamed_files,
131
+ untracked_files,
132
+ };
133
+ }
134
+ /** Migrates every node whose file_path resolves to `from` onto `to`, cascading the id/connections/history via `renameNode`. Returns true if anything was migrated. */
135
+ function migrateRename(db, context, repoName, from, to) {
136
+ if (!context)
137
+ return false;
138
+ const repoPath = (0, config_1.resolveRepoPath)(context, repoName);
139
+ if (!repoPath)
140
+ return false;
141
+ const oldAbs = path.resolve(repoPath, from);
142
+ const newAbs = path.resolve(repoPath, to);
143
+ // Deliberately excludes deprecated nodes — renameNode's insert doesn't preserve the
144
+ // deprecated flag, so migrating a dead node would silently resurrect it as active.
145
+ // A deprecated node's file_path accuracy doesn't matter once it's already excluded
146
+ // from use.
147
+ const affected = db.listNodes().filter(n => (n.file_path || '').split(',').map(p => p.trim()).some(p => path.resolve(p) === oldAbs));
148
+ let migrated = false;
149
+ for (const node of affected) {
150
+ const newId = node.id.replace(from, to);
151
+ if (newId === node.id)
152
+ continue;
153
+ try {
154
+ db.renameNode(node.id, newId, undefined, newAbs);
155
+ migrated = true;
156
+ }
157
+ catch {
158
+ // Leave this node untouched — analyze reports it again next run rather than failing the whole batch.
159
+ }
160
+ }
161
+ return migrated;
162
+ }
163
+ //# sourceMappingURL=analyze.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"analyze.js","sourceRoot":"","sources":["../../src/db/analyze.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAsCA,kCA2FC;AAjID,uCAAyB;AACzB,2CAA6B;AAE7B,4CAAkD;AAClD,sCAA0E;AAC1E,8CAAwD;AAuBxD,MAAM,iBAAiB,GAAG,kBAAkB,CAAC;AAC7C,MAAM,qBAAqB,GAAG,EAAE,CAAC;AAEjC;;;;;;GAMG;AACH,SAAgB,WAAW,CAAC,EAAmB,EAAE,aAAqB,EAAE,OAAwB,EAAE;IAChG,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,KAAK,IAAI,CAAC;IAC9B,MAAM,kBAAkB,GAAG,IAAI,CAAC,kBAAkB,IAAI,EAAE,CAAC;IAEzD,MAAM,YAAY,GAAG,EAAE,CAAC,cAAc,CAAC,kBAAkB,CAAC,CAAC;IAC3D,MAAM,qBAAqB,GAAG,EAAE,CAAC,uBAAuB,EAAE,CAAC;IAC3D,MAAM,cAAc,GAAG,EAAE,CAAC,gBAAgB,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,SAAS,EAAE,CAAC,CAAC,SAAS,EAAE,CAAC,CAAC,CAAC;IAC5G,MAAM,cAAc,GAAG,EAAE,CAAC,gBAAgB,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,cAAc,EAAE,CAAC,CAAC,cAAc,EAAE,cAAc,EAAE,CAAC,CAAC,cAAc,EAAE,CAAC,CAAC,CAAC;IAChI,MAAM,aAAa,GAAG,EAAE,CAAC,mBAAmB,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;IAC/F,MAAM,6BAA6B,GAAG,EAAE,CAAC,0BAA0B,EAAE,CAAC;IACtE,MAAM,oBAAoB,GAAG,EAAE,CAAC,qBAAqB,EAAE,CAAC;IACxD,MAAM,EAAE,QAAQ,EAAE,cAAc,EAAE,WAAW,EAAE,aAAa,EAAE,GAAG,EAAE,CAAC,+BAA+B,CAAC,aAAa,CAAC,CAAC;IAEnH,MAAM,cAAc,GAAG,EAAE,CAAC,aAAa,CAAC,iBAAiB,CAAC,CAAC;IAC3D,MAAM,WAAW,GAAG,cAAc,IAAI,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,qBAAqB,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC,WAAW,EAAE,CAAC;IAEvH,MAAM,aAAa,GAAoC,EAAE,CAAC;IAC1D,MAAM,eAAe,GAAsC,EAAE,CAAC;IAE9D,MAAM,OAAO,GAAG,EAAE,CAAC,UAAU,EAAE,CAAC;IAChC,IAAI,OAAO,EAAE,CAAC;QACZ,KAAK,MAAM,IAAI,IAAI,OAAO,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC;YACxC,MAAM,QAAQ,GAAG,IAAA,wBAAe,EAAC,OAAO,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC;YACrD,IAAI,CAAC,QAAQ,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC;gBAAE,SAAS;YAEpD,KAAK,MAAM,CAAC,IAAI,IAAA,0BAAoB,EAAC,QAAQ,EAAE,WAAW,CAAC,EAAE,CAAC;gBAC5D,aAAa,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC,CAAC;YACnF,CAAC;YAED,MAAM,OAAO,GAAG,IAAA,0BAAoB,EAAC,QAAQ,EAAE,WAAW,CAAC,CAAC;YAC5D,MAAM,UAAU,GAAG,IAAI,GAAG,CACxB,EAAE,CAAC,SAAS,CAAC,EAAE,kBAAkB,EAAE,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CACrD,CAAC,CAAC,CAAC,SAAS,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAClE,CACF,CAAC;YACF,KAAK,MAAM,OAAO,IAAI,OAAO,EAAE,CAAC;gBAC9B,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,WAAW,EAAE,CAAC;gBAChD,IAAI,CAAC,8BAAoB,CAAC,GAAG,CAAC,GAAG,CAAC;oBAAE,SAAS;gBAC7C,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;gBAChD,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,OAAO,CAAC;oBAAE,SAAS,CAAC,mCAAmC;gBAC1E,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,OAAO,CAAC,CAAC;gBAC9E,IAAI,CAAC,OAAO;oBAAE,eAAe,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,CAAC;YACzE,CAAC;QACH,CAAC;IACH,CAAC;IAED,IAAI,GAAG,EAAE,CAAC;QACR,KAAK,MAAM,CAAC,IAAI,CAAC,GAAG,cAAc,EAAE,GAAG,cAAc,EAAE,GAAG,aAAa,CAAC,EAAE,CAAC;YACzE,EAAE,CAAC,aAAa,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;QACzB,CAAC;QACD,KAAK,MAAM,CAAC,IAAI,cAAc,EAAE,CAAC;YAC/B,EAAE,CAAC,kBAAkB,CAAC,CAAC,CAAC,cAAc,EAAE,CAAC,CAAC,cAAc,CAAC,CAAC;QAC5D,CAAC;QACD,IAAI,OAAO,EAAE,CAAC;YACZ,KAAK,MAAM,CAAC,IAAI,aAAa,EAAE,CAAC;gBAC9B,MAAM,QAAQ,GAAG,aAAa,CAAC,EAAE,EAAE,OAAO,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC;gBAClE,CAAC,CAAC,QAAQ,GAAG,QAAQ,CAAC;YACxB,CAAC;QACH,CAAC;QACD,EAAE,CAAC,aAAa,CAAC,iBAAiB,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC,CAAC;IAChE,CAAC;IAED,MAAM,OAAO,GAA2B;QACtC,YAAY,EAAE,YAAY,CAAC,MAAM;QACjC,qBAAqB,EAAE,qBAAqB,CAAC,MAAM;QACnD,cAAc,EAAE,cAAc,CAAC,MAAM;QACrC,cAAc,EAAE,cAAc,CAAC,MAAM;QACrC,aAAa,EAAE,aAAa,CAAC,MAAM;QACnC,6BAA6B,EAAE,6BAA6B,CAAC,MAAM;QACnE,oBAAoB,EAAE,oBAAoB,CAAC,MAAM;QACjD,cAAc,EAAE,cAAc,CAAC,MAAM;QACrC,aAAa,EAAE,aAAa,CAAC,MAAM;QACnC,aAAa,EAAE,aAAa,CAAC,MAAM;QACnC,eAAe,EAAE,eAAe,CAAC,MAAM;KACxC,CAAC;IAEF,OAAO;QACL,KAAK,EAAE,GAAG;QACV,OAAO;QACP,YAAY;QACZ,qBAAqB;QACrB,cAAc;QACd,cAAc;QACd,aAAa;QACb,6BAA6B;QAC7B,oBAAoB;QACpB,cAAc;QACd,aAAa;QACb,aAAa;QACb,eAAe;KAChB,CAAC;AACJ,CAAC;AAED,sKAAsK;AACtK,SAAS,aAAa,CAAC,EAAmB,EAAE,OAAkD,EAAE,QAAgB,EAAE,IAAY,EAAE,EAAU;IACxI,IAAI,CAAC,OAAO;QAAE,OAAO,KAAK,CAAC;IAC3B,MAAM,QAAQ,GAAG,IAAA,wBAAe,EAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;IACpD,IAAI,CAAC,QAAQ;QAAE,OAAO,KAAK,CAAC;IAE5B,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;IAC5C,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;IAC1C,oFAAoF;IACpF,mFAAmF;IACnF,mFAAmF;IACnF,YAAY;IACZ,MAAM,QAAQ,GAAG,EAAE,CAAC,SAAS,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CACzC,CAAC,CAAC,CAAC,SAAS,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,MAAM,CAAC,CACxF,CAAC;IAEF,IAAI,QAAQ,GAAG,KAAK,CAAC;IACrB,KAAK,MAAM,IAAI,IAAI,QAAQ,EAAE,CAAC;QAC5B,MAAM,KAAK,GAAG,IAAI,CAAC,EAAE,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;QACxC,IAAI,KAAK,KAAK,IAAI,CAAC,EAAE;YAAE,SAAS;QAChC,IAAI,CAAC;YACH,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,CAAC,CAAC;YACjD,QAAQ,GAAG,IAAI,CAAC;QAClB,CAAC;QAAC,MAAM,CAAC;YACP,qGAAqG;QACvG,CAAC;IACH,CAAC;IACD,OAAO,QAAQ,CAAC;AAClB,CAAC"}
@@ -1,4 +1,5 @@
1
- import { DbNode, DbHistory, DbConnection } from './schema';
1
+ import { DbNode, DbHistory, DbConnection, DbWorkflow, DbWorkflowStep, DbWorkflowArtifact } from './schema';
2
+ import { ProjectContext } from '../utils/config';
2
3
  export interface ReasoningObject {
3
4
  what_changed: string;
4
5
  why: string;
@@ -48,6 +49,7 @@ export declare class DevMindDatabase {
48
49
  private context;
49
50
  constructor(dbPath: string);
50
51
  private initSchema;
52
+ getContext(): ProjectContext | null;
51
53
  getSystemMeta(key: string): string | null;
52
54
  setSystemMeta(key: string, value: string): void;
53
55
  getNodesByFilePath(filePath: string): DbNode[];
@@ -88,7 +90,8 @@ export declare class DevMindDatabase {
88
90
  getNode(id: string): DbNode | null;
89
91
  deleteNode(id: string): void;
90
92
  deprecateNode(id: string): void;
91
- renameNode(oldId: string, newId: string, newName?: string): void;
93
+ /** `newFilePath`: pass when the rename is a file move (analyze's rename migration), leave undefined for a pure symbol-id rename where the file itself is unchanged. */
94
+ renameNode(oldId: string, newId: string, newName?: string, newFilePath?: string): void;
92
95
  /**
93
96
  * Rewrites a history/[id].json file's identifying fields (node_id, node_metadata) in
94
97
  * place, leaving code_snapshot/reasoning/timestamps untouched. Used after a rename so
@@ -153,7 +156,21 @@ export declare class DevMindDatabase {
153
156
  reasoning: string | ReasoningObject;
154
157
  session_id?: string;
155
158
  }): DbHistory;
156
- searchNodes(query: string): DbNode[];
159
+ /**
160
+ * Search for nodes by name/id/reasoning first (cheap, SQL-only). If that finds
161
+ * nothing, transparently fall back to a code-content search (same engine as
162
+ * {@link searchCode}) so a query like "alipay" still succeeds even when no
163
+ * node's name/id/reasoning mentions it but the code itself does. Every result
164
+ * is tagged `matched_via` so the caller knows which path found it.
165
+ */
166
+ searchNodes(query: string, opts?: {
167
+ is_regex?: boolean;
168
+ case_insensitive?: boolean;
169
+ }): Array<(DbNode & {
170
+ matched_via: 'identifier';
171
+ }) | (ReturnType<DevMindDatabase['searchCode']>[number] & {
172
+ matched_via: 'code';
173
+ })>;
157
174
  getRecentChanges(hours?: number, analyzeImpact?: boolean): {
158
175
  node_id: string;
159
176
  node_name: string;
@@ -210,6 +227,135 @@ export declare class DevMindDatabase {
210
227
  }): DbNode[];
211
228
  getAllConnections(): DbConnection[];
212
229
  getAllHistory(): DbHistory[];
230
+ /** Nodes whose total (in + out) connection degree meets/exceeds `threshold` — architectural bottleneck candidates. */
231
+ getGodEntities(threshold?: number): {
232
+ id: string;
233
+ name: string;
234
+ file_path: string;
235
+ degree: number;
236
+ }[];
237
+ /** DFS cycle detection over the connection graph, capped at `maxCycles` reported paths. */
238
+ getCircularDependencies(maxCycles?: number): string[][];
239
+ /** node_connections rows whose source or target no longer exists in `nodes` (broken by a non-transactional delete, or a sync race). */
240
+ getDanglingEdges(): DbConnection[];
241
+ /** Deletes a single dangling `node_connections` row. The edge itself is invalid data — no history/graph JSON to rewrite. */
242
+ deleteDanglingEdge(sourceId: string, targetId: string): void;
243
+ /** Node ids that differ only by case — a real collision risk on Windows's case-insensitive filesystem. */
244
+ getDuplicateNodeIds(): {
245
+ lowerId: string;
246
+ ids: string[];
247
+ }[];
248
+ /** History rows whose flattened `reasoning` text has no non-empty `Developer:` line — can't be attributed to anyone. */
249
+ getHistoryMissingDeveloper(): {
250
+ id: string;
251
+ node_id: string;
252
+ updated_at: string;
253
+ }[];
254
+ /**
255
+ * History rows with a blank code snapshot — usually a silent AST extraction failure.
256
+ * The `history.code_snapshot` DB column is always written as `''` (the real content
257
+ * lives only in the per-row JSON on disk, see `populateHistoryFromDisk`), so this
258
+ * must read through the populated rows rather than querying the column directly.
259
+ */
260
+ getEmptyCodeSnapshots(): {
261
+ id: string;
262
+ node_id: string;
263
+ updated_at: string;
264
+ }[];
265
+ private workflowsDir;
266
+ /** Serializes the workflow + its steps + artifact index to disk so teammates can sync it via git. */
267
+ private writeWorkflowToDisk;
268
+ createWorkflow(name: string, description: string): DbWorkflow;
269
+ getWorkflow(id: string): DbWorkflow | null;
270
+ getActiveWorkflow(): DbWorkflow | null;
271
+ listWorkflows(status?: 'active' | 'paused' | 'completed'): DbWorkflow[];
272
+ /** Pauses the currently active workflow (if any) and clears the active pointer. */
273
+ pauseWorkflow(): DbWorkflow | null;
274
+ /** Resumes `id`, auto-pausing whatever was previously active (only one workflow is active at a time). */
275
+ resumeWorkflow(id: string): DbWorkflow;
276
+ completeWorkflow(id: string): DbWorkflow;
277
+ addWorkflowStep(workflowId: string, opts: {
278
+ summary: string;
279
+ pendingTasks?: string;
280
+ historyIds?: string[];
281
+ sessionId?: string;
282
+ }): DbWorkflowStep;
283
+ /** Writes `content` to `.devmind/workflows/<workflowId>/<artifactId>_<sourceName>` and records the DB row. */
284
+ addWorkflowArtifact(workflowId: string, opts: {
285
+ stepId?: string;
286
+ type: string;
287
+ sourceName: string;
288
+ content: string;
289
+ }): DbWorkflowArtifact;
290
+ getWorkflowContext(id: string, opts?: {
291
+ includeArtifactContent?: boolean;
292
+ }): {
293
+ workflow: DbWorkflow;
294
+ steps: DbWorkflowStep[];
295
+ artifacts: (DbWorkflowArtifact & {
296
+ content?: string;
297
+ })[];
298
+ };
299
+ /**
300
+ * Returns steps for a workflow with optional pagination.
301
+ * Use `last_n` to get only the most recent N steps (tail), or `limit`/`offset` for
302
+ * arbitrary pagination. Without any option, all steps are returned.
303
+ */
304
+ getWorkflowSteps(workflowId: string, opts?: {
305
+ limit?: number;
306
+ offset?: number;
307
+ last_n?: number;
308
+ }): DbWorkflowStep[];
309
+ /**
310
+ * Reads a single workflow artifact's file content from disk.
311
+ * Accepts either an artifact_id or a source_name (first match used).
312
+ */
313
+ readWorkflowArtifact(workflowId: string, artifactId: string): {
314
+ artifact: DbWorkflowArtifact;
315
+ content: string;
316
+ };
317
+ /**
318
+ * Full-text keyword search across all workflows' step summaries, pending_tasks,
319
+ * and artifact source names. Optionally also searches artifact file content.
320
+ * Returns a list of matches grouped by workflow.
321
+ */
322
+ searchWorkflows(query: string, opts?: {
323
+ include_artifact_content?: boolean;
324
+ status?: 'active' | 'paused' | 'completed';
325
+ }): Array<{
326
+ workflow: DbWorkflow;
327
+ matched_steps: DbWorkflowStep[];
328
+ matched_artifacts: (DbWorkflowArtifact & {
329
+ content_snippet?: string;
330
+ })[];
331
+ }>;
332
+ /**
333
+ * Imports an existing flow/architecture doc as a paused workflow (not active — importing
334
+ * a doc isn't the same as declaring active work). Idempotent on `name`: re-importing the
335
+ * same doc overwrites its existing `imported_doc` artifact file in place instead of
336
+ * creating a duplicate workflow every time the source docs are re-imported.
337
+ */
338
+ importWorkflowDoc(name: string, description: string, content: string, sourceFileName: string): {
339
+ workflow: DbWorkflow;
340
+ created: boolean;
341
+ };
342
+ private static readonly SPURIOUS_NODE_NAMES;
343
+ /**
344
+ * Read-only detection shared by `pruneSpuriousNodes` (which acts on it) and `devsmind
345
+ * analyze`'s dry-run report (which just lists it). Never mutates the DB.
346
+ */
347
+ findSpuriousAndMissingFileNodes(workspaceRoot: string): {
348
+ spurious: {
349
+ id: string;
350
+ name: string;
351
+ file_path: string;
352
+ }[];
353
+ missingFile: {
354
+ id: string;
355
+ name: string;
356
+ file_path: string;
357
+ }[];
358
+ };
213
359
  pruneSpuriousNodes(workspaceRoot: string): {
214
360
  prunedCount: number;
215
361
  prunedNodes: string[];
@@ -217,9 +363,26 @@ export declare class DevMindDatabase {
217
363
  private populateHistoryFromDisk;
218
364
  private writeHistoryToDisk;
219
365
  toRepoRelativePath(absolutePath: string): string;
366
+ /**
367
+ * Rejects a resolved path that escapes its expected root (e.g. via a stored
368
+ * `{repo}/../../..` path traveling outside the repo) by clamping it back to
369
+ * the root itself. node_id/file_path values flow in from AI-supplied tool
370
+ * calls, so a resolve must never be trusted to stay inside root on its own.
371
+ */
372
+ private clampToRoot;
373
+ /**
374
+ * True if `absPath` sits inside a configured repo root or the workspace root itself.
375
+ * Used to reject `stage_change`/`update_history` file paths that would otherwise let a
376
+ * tool call read/write any file on disk (absolute path, or a `../` escape) instead of
377
+ * just repo source — nothing upstream of this validates that the AI-supplied path is
378
+ * actually inside the project.
379
+ */
380
+ isPathAllowed(absPath: string): boolean;
220
381
  toAbsolutePath(repoRelativePath: string): string;
221
382
  syncFromDisk(): void;
222
383
  /** Escape LIKE metacharacters so a path is matched literally (use with ESCAPE '\\'). */
223
384
  private likeEscape;
224
385
  writeGraphToDisk(filePath: string): void;
386
+ /** Force-syncs all database nodes and workflows to disk JSON files. */
387
+ syncToDisk(): void;
225
388
  }