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.
- package/README.md +234 -527
- package/dist/cli/analyze.d.ts +13 -0
- package/dist/cli/analyze.js +143 -0
- package/dist/cli/analyze.js.map +1 -0
- package/dist/cli/index.js +76 -0
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/integrations/memory.d.ts +11 -0
- package/dist/cli/integrations/memory.js +156 -0
- package/dist/cli/integrations/memory.js.map +1 -0
- package/dist/cli/integrations/prompt.d.ts +5 -1
- package/dist/cli/integrations/prompt.js +29 -7
- package/dist/cli/integrations/prompt.js.map +1 -1
- package/dist/cli/integrations/registry.d.ts +35 -0
- package/dist/cli/integrations/registry.js +59 -0
- package/dist/cli/integrations/registry.js.map +1 -1
- package/dist/cli/rule.js +37 -46
- package/dist/cli/rule.js.map +1 -1
- package/dist/cli/sync.d.ts +7 -0
- package/dist/cli/sync.js +40 -7
- package/dist/cli/sync.js.map +1 -1
- package/dist/cli/workflow.d.ts +8 -0
- package/dist/cli/workflow.js +156 -0
- package/dist/cli/workflow.js.map +1 -0
- package/dist/db/analyze.d.ts +67 -0
- package/dist/db/analyze.js +163 -0
- package/dist/db/analyze.js.map +1 -0
- package/dist/db/database.d.ts +166 -3
- package/dist/db/database.js +700 -64
- package/dist/db/database.js.map +1 -1
- package/dist/db/schema.d.ts +28 -1
- package/dist/db/schema.js +34 -0
- package/dist/db/schema.js.map +1 -1
- package/dist/db/staging.d.ts +4 -0
- package/dist/db/staging.js +16 -2
- package/dist/db/staging.js.map +1 -1
- package/dist/db/workflow-import.d.ts +22 -0
- package/dist/db/workflow-import.js +116 -0
- package/dist/db/workflow-import.js.map +1 -0
- package/dist/mcp/server.d.ts +9 -0
- package/dist/mcp/server.js +417 -27
- package/dist/mcp/server.js.map +1 -1
- package/dist/utils/config.d.ts +2 -0
- package/dist/utils/config.js +11 -0
- package/dist/utils/config.js.map +1 -1
- package/dist/utils/git.d.ts +14 -0
- package/dist/utils/git.js +43 -0
- package/dist/utils/git.js.map +1 -0
- package/dist/utils/scanner.d.ts +7 -0
- package/dist/utils/scanner.js +9 -3
- package/dist/utils/scanner.js.map +1 -1
- 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":"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"}
|
package/dist/db/database.d.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
}
|