devsmind-mcp 2.2.2 → 2.4.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 -606
- 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 +62 -0
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/init.js +9 -0
- package/dist/cli/init.js.map +1 -1
- package/dist/cli/integrations/memory.js +9 -3
- package/dist/cli/integrations/memory.js.map +1 -1
- package/dist/cli/integrations/prompt.d.ts +2 -0
- package/dist/cli/integrations/prompt.js +21 -7
- package/dist/cli/integrations/prompt.js.map +1 -1
- package/dist/cli/prune.js +4 -3
- package/dist/cli/prune.js.map +1 -1
- package/dist/cli/rule.js +43 -83
- 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 +167 -0
- package/dist/db/analyze.js.map +1 -0
- package/dist/db/database.d.ts +195 -2
- package/dist/db/database.js +870 -74
- 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 +1 -1
- package/dist/mcp/server.js +641 -48
- package/dist/mcp/server.js.map +1 -1
- package/dist/utils/ast.d.ts +98 -0
- package/dist/utils/ast.js +262 -10
- package/dist/utils/ast.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/edit.d.ts +41 -0
- package/dist/utils/edit.js +163 -0
- package/dist/utils/edit.js.map +1 -0
- package/dist/utils/git.d.ts +14 -0
- package/dist/utils/git.js +43 -0
- package/dist/utils/git.js.map +1 -0
- package/package.json +1 -1
|
@@ -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,167 @@
|
|
|
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
|
+
// Canonicalized on the way in: a raw path.resolve() comparison misses on Windows whenever
|
|
77
|
+
// the two sides disagree on drive-letter case (a stored node path vs. one freshly resolved
|
|
78
|
+
// from cwd), which silently reported every file in the repo as untracked — same bug class
|
|
79
|
+
// already fixed for getNodesByFilePath, not previously applied here.
|
|
80
|
+
const knownFiles = new Set(db.listNodes({ include_deprecated: true }).flatMap(n => (n.file_path || '').split(',').map(p => (0, config_1.canonicalizePath)(p.trim())).filter(Boolean)));
|
|
81
|
+
for (const relFile of changed) {
|
|
82
|
+
const ext = path.extname(relFile).toLowerCase();
|
|
83
|
+
if (!scanner_1.INDEXABLE_EXTENSIONS.has(ext))
|
|
84
|
+
continue;
|
|
85
|
+
const absFile = (0, config_1.canonicalizePath)(path.resolve(repoPath, relFile));
|
|
86
|
+
if (!fs.existsSync(absFile))
|
|
87
|
+
continue; // deleted since — not a blind spot
|
|
88
|
+
const hasNode = knownFiles.has(absFile);
|
|
89
|
+
if (!hasNode)
|
|
90
|
+
untracked_files.push({ repo: repo.name, file: relFile });
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
if (fix) {
|
|
95
|
+
for (const n of [...orphaned_nodes, ...spurious_nodes, ...missing_files]) {
|
|
96
|
+
db.deprecateNode(n.id);
|
|
97
|
+
}
|
|
98
|
+
for (const e of dangling_edges) {
|
|
99
|
+
db.deleteDanglingEdge(e.source_node_id, e.target_node_id);
|
|
100
|
+
}
|
|
101
|
+
if (context) {
|
|
102
|
+
for (const r of renamed_files) {
|
|
103
|
+
const migrated = migrateRename(db, context, r.repo, r.from, r.to);
|
|
104
|
+
r.migrated = migrated;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
db.setSystemMeta(LAST_ANALYSIS_KEY, new Date().toISOString());
|
|
108
|
+
}
|
|
109
|
+
const summary = {
|
|
110
|
+
god_entities: god_entities.length,
|
|
111
|
+
circular_dependencies: circular_dependencies.length,
|
|
112
|
+
orphaned_nodes: orphaned_nodes.length,
|
|
113
|
+
dangling_edges: dangling_edges.length,
|
|
114
|
+
duplicate_ids: duplicate_ids.length,
|
|
115
|
+
missing_developer_attribution: missing_developer_attribution.length,
|
|
116
|
+
empty_code_snapshots: empty_code_snapshots.length,
|
|
117
|
+
spurious_nodes: spurious_nodes.length,
|
|
118
|
+
missing_files: missing_files.length,
|
|
119
|
+
renamed_files: renamed_files.length,
|
|
120
|
+
untracked_files: untracked_files.length,
|
|
121
|
+
};
|
|
122
|
+
return {
|
|
123
|
+
fixed: fix,
|
|
124
|
+
summary,
|
|
125
|
+
god_entities,
|
|
126
|
+
circular_dependencies,
|
|
127
|
+
orphaned_nodes,
|
|
128
|
+
dangling_edges,
|
|
129
|
+
duplicate_ids,
|
|
130
|
+
missing_developer_attribution,
|
|
131
|
+
empty_code_snapshots,
|
|
132
|
+
spurious_nodes,
|
|
133
|
+
missing_files,
|
|
134
|
+
renamed_files,
|
|
135
|
+
untracked_files,
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
/** Migrates every node whose file_path resolves to `from` onto `to`, cascading the id/connections/history via `renameNode`. Returns true if anything was migrated. */
|
|
139
|
+
function migrateRename(db, context, repoName, from, to) {
|
|
140
|
+
if (!context)
|
|
141
|
+
return false;
|
|
142
|
+
const repoPath = (0, config_1.resolveRepoPath)(context, repoName);
|
|
143
|
+
if (!repoPath)
|
|
144
|
+
return false;
|
|
145
|
+
const oldAbs = path.resolve(repoPath, from);
|
|
146
|
+
const newAbs = path.resolve(repoPath, to);
|
|
147
|
+
// Deliberately excludes deprecated nodes — renameNode's insert doesn't preserve the
|
|
148
|
+
// deprecated flag, so migrating a dead node would silently resurrect it as active.
|
|
149
|
+
// A deprecated node's file_path accuracy doesn't matter once it's already excluded
|
|
150
|
+
// from use.
|
|
151
|
+
const affected = db.listNodes().filter(n => (n.file_path || '').split(',').map(p => p.trim()).some(p => path.resolve(p) === oldAbs));
|
|
152
|
+
let migrated = false;
|
|
153
|
+
for (const node of affected) {
|
|
154
|
+
const newId = node.id.replace(from, to);
|
|
155
|
+
if (newId === node.id)
|
|
156
|
+
continue;
|
|
157
|
+
try {
|
|
158
|
+
db.renameNode(node.id, newId, undefined, newAbs);
|
|
159
|
+
migrated = true;
|
|
160
|
+
}
|
|
161
|
+
catch {
|
|
162
|
+
// Leave this node untouched — analyze reports it again next run rather than failing the whole batch.
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
return migrated;
|
|
166
|
+
}
|
|
167
|
+
//# 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;
|
|
@@ -42,14 +43,31 @@ export interface GraphResult {
|
|
|
42
43
|
nodes_without_code?: number;
|
|
43
44
|
}
|
|
44
45
|
export declare function formatReasoning(r: string | ReasoningObject): string;
|
|
46
|
+
/**
|
|
47
|
+
* Inverse of `formatReasoning`. A single history row accumulates every later update appended
|
|
48
|
+
* under a `── Update @ … ──` separator, so one stored blob can hold several changes — this
|
|
49
|
+
* splits them back apart and returns them NEWEST FIRST.
|
|
50
|
+
*
|
|
51
|
+
* Reasoning written before the structured format (or by a caller passing a bare string) has no
|
|
52
|
+
* labels to read; rather than drop it, the whole chunk is surfaced as `what_changed`.
|
|
53
|
+
*/
|
|
54
|
+
export declare function parseReasoningBlocks(raw: string): ReasoningObject[];
|
|
45
55
|
export declare class DevMindDatabase {
|
|
46
56
|
private db;
|
|
47
57
|
private dbPath;
|
|
48
58
|
private context;
|
|
49
59
|
constructor(dbPath: string);
|
|
50
60
|
private initSchema;
|
|
61
|
+
getContext(): ProjectContext | null;
|
|
51
62
|
getSystemMeta(key: string): string | null;
|
|
52
63
|
setSystemMeta(key: string, value: string): void;
|
|
64
|
+
/**
|
|
65
|
+
* Nodes declared in one file. Both sides are folded to a canonical form before comparing:
|
|
66
|
+
* a stored `c:\x\y.ts` and a caller's `C:/x/y.ts` are the same file on Windows, and a raw
|
|
67
|
+
* `=` match silently returns nothing — which reads as "this file has no nodes" rather than
|
|
68
|
+
* as an error. There is no index on file_path, so this was already a full scan; normalizing
|
|
69
|
+
* in SQL costs nothing extra.
|
|
70
|
+
*/
|
|
53
71
|
getNodesByFilePath(filePath: string): DbNode[];
|
|
54
72
|
close(): void;
|
|
55
73
|
/** Snapshot of active-node / connection / history row counts (used by `devsmind sync`). */
|
|
@@ -88,7 +106,8 @@ export declare class DevMindDatabase {
|
|
|
88
106
|
getNode(id: string): DbNode | null;
|
|
89
107
|
deleteNode(id: string): void;
|
|
90
108
|
deprecateNode(id: string): void;
|
|
91
|
-
|
|
109
|
+
/** `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. */
|
|
110
|
+
renameNode(oldId: string, newId: string, newName?: string, newFilePath?: string): void;
|
|
92
111
|
/**
|
|
93
112
|
* Rewrites a history/[id].json file's identifying fields (node_id, node_metadata) in
|
|
94
113
|
* place, leaving code_snapshot/reasoning/timestamps untouched. Used after a rename so
|
|
@@ -167,7 +186,27 @@ export declare class DevMindDatabase {
|
|
|
167
186
|
matched_via: 'identifier';
|
|
168
187
|
}) | (ReturnType<DevMindDatabase['searchCode']>[number] & {
|
|
169
188
|
matched_via: 'code';
|
|
189
|
+
}) | (DbNode & {
|
|
190
|
+
matched_via: 'fuzzy';
|
|
191
|
+
matched_terms: string[];
|
|
192
|
+
score: number;
|
|
170
193
|
})>;
|
|
194
|
+
/**
|
|
195
|
+
* Splits a query string into lowercase word tokens for the fuzzy fallback
|
|
196
|
+
* stage of {@link searchNodes}. This is request-scoped tokenization only —
|
|
197
|
+
* nothing is persisted or indexed; the result is discarded after the call.
|
|
198
|
+
*/
|
|
199
|
+
private tokenizeQuery;
|
|
200
|
+
/**
|
|
201
|
+
* Word-split relevance-ranked fallback for {@link searchNodes}. Runs only
|
|
202
|
+
* when the exact identifier and code stages both return nothing. Scores
|
|
203
|
+
* every non-deprecated node by how many distinct query tokens appear as a
|
|
204
|
+
* substring of its file_path/name/id (highest signal), latest reasoning,
|
|
205
|
+
* or code content (lowest signal, one point per matching line). No new
|
|
206
|
+
* data is written or synced — this is a plain in-memory scan reusing the
|
|
207
|
+
* same node/history sources searchCode already reads.
|
|
208
|
+
*/
|
|
209
|
+
private fuzzySearchNodes;
|
|
171
210
|
getRecentChanges(hours?: number, analyzeImpact?: boolean): {
|
|
172
211
|
node_id: string;
|
|
173
212
|
node_name: string;
|
|
@@ -224,6 +263,135 @@ export declare class DevMindDatabase {
|
|
|
224
263
|
}): DbNode[];
|
|
225
264
|
getAllConnections(): DbConnection[];
|
|
226
265
|
getAllHistory(): DbHistory[];
|
|
266
|
+
/** Nodes whose total (in + out) connection degree meets/exceeds `threshold` — architectural bottleneck candidates. */
|
|
267
|
+
getGodEntities(threshold?: number): {
|
|
268
|
+
id: string;
|
|
269
|
+
name: string;
|
|
270
|
+
file_path: string;
|
|
271
|
+
degree: number;
|
|
272
|
+
}[];
|
|
273
|
+
/** DFS cycle detection over the connection graph, capped at `maxCycles` reported paths. */
|
|
274
|
+
getCircularDependencies(maxCycles?: number): string[][];
|
|
275
|
+
/** node_connections rows whose source or target no longer exists in `nodes` (broken by a non-transactional delete, or a sync race). */
|
|
276
|
+
getDanglingEdges(): DbConnection[];
|
|
277
|
+
/** Deletes a single dangling `node_connections` row. The edge itself is invalid data — no history/graph JSON to rewrite. */
|
|
278
|
+
deleteDanglingEdge(sourceId: string, targetId: string): void;
|
|
279
|
+
/** Node ids that differ only by case — a real collision risk on Windows's case-insensitive filesystem. */
|
|
280
|
+
getDuplicateNodeIds(): {
|
|
281
|
+
lowerId: string;
|
|
282
|
+
ids: string[];
|
|
283
|
+
}[];
|
|
284
|
+
/** History rows whose flattened `reasoning` text has no non-empty `Developer:` line — can't be attributed to anyone. */
|
|
285
|
+
getHistoryMissingDeveloper(): {
|
|
286
|
+
id: string;
|
|
287
|
+
node_id: string;
|
|
288
|
+
updated_at: string;
|
|
289
|
+
}[];
|
|
290
|
+
/**
|
|
291
|
+
* History rows with a blank code snapshot — usually a silent AST extraction failure.
|
|
292
|
+
* The `history.code_snapshot` DB column is always written as `''` (the real content
|
|
293
|
+
* lives only in the per-row JSON on disk, see `populateHistoryFromDisk`), so this
|
|
294
|
+
* must read through the populated rows rather than querying the column directly.
|
|
295
|
+
*/
|
|
296
|
+
getEmptyCodeSnapshots(): {
|
|
297
|
+
id: string;
|
|
298
|
+
node_id: string;
|
|
299
|
+
updated_at: string;
|
|
300
|
+
}[];
|
|
301
|
+
private workflowsDir;
|
|
302
|
+
/** Serializes the workflow + its steps + artifact index to disk so teammates can sync it via git. */
|
|
303
|
+
private writeWorkflowToDisk;
|
|
304
|
+
createWorkflow(name: string, description: string): DbWorkflow;
|
|
305
|
+
getWorkflow(id: string): DbWorkflow | null;
|
|
306
|
+
getActiveWorkflow(): DbWorkflow | null;
|
|
307
|
+
listWorkflows(status?: 'active' | 'paused' | 'completed'): DbWorkflow[];
|
|
308
|
+
/** Pauses the currently active workflow (if any) and clears the active pointer. */
|
|
309
|
+
pauseWorkflow(): DbWorkflow | null;
|
|
310
|
+
/** Resumes `id`, auto-pausing whatever was previously active (only one workflow is active at a time). */
|
|
311
|
+
resumeWorkflow(id: string): DbWorkflow;
|
|
312
|
+
completeWorkflow(id: string): DbWorkflow;
|
|
313
|
+
addWorkflowStep(workflowId: string, opts: {
|
|
314
|
+
summary: string;
|
|
315
|
+
pendingTasks?: string;
|
|
316
|
+
historyIds?: string[];
|
|
317
|
+
sessionId?: string;
|
|
318
|
+
}): DbWorkflowStep;
|
|
319
|
+
/** Writes `content` to `.devmind/workflows/<workflowId>/<artifactId>_<sourceName>` and records the DB row. */
|
|
320
|
+
addWorkflowArtifact(workflowId: string, opts: {
|
|
321
|
+
stepId?: string;
|
|
322
|
+
type: string;
|
|
323
|
+
sourceName: string;
|
|
324
|
+
content: string;
|
|
325
|
+
}): DbWorkflowArtifact;
|
|
326
|
+
getWorkflowContext(id: string, opts?: {
|
|
327
|
+
includeArtifactContent?: boolean;
|
|
328
|
+
}): {
|
|
329
|
+
workflow: DbWorkflow;
|
|
330
|
+
steps: DbWorkflowStep[];
|
|
331
|
+
artifacts: (DbWorkflowArtifact & {
|
|
332
|
+
content?: string;
|
|
333
|
+
})[];
|
|
334
|
+
};
|
|
335
|
+
/**
|
|
336
|
+
* Returns steps for a workflow with optional pagination.
|
|
337
|
+
* Use `last_n` to get only the most recent N steps (tail), or `limit`/`offset` for
|
|
338
|
+
* arbitrary pagination. Without any option, all steps are returned.
|
|
339
|
+
*/
|
|
340
|
+
getWorkflowSteps(workflowId: string, opts?: {
|
|
341
|
+
limit?: number;
|
|
342
|
+
offset?: number;
|
|
343
|
+
last_n?: number;
|
|
344
|
+
}): DbWorkflowStep[];
|
|
345
|
+
/**
|
|
346
|
+
* Reads a single workflow artifact's file content from disk.
|
|
347
|
+
* Accepts either an artifact_id or a source_name (first match used).
|
|
348
|
+
*/
|
|
349
|
+
readWorkflowArtifact(workflowId: string, artifactId: string): {
|
|
350
|
+
artifact: DbWorkflowArtifact;
|
|
351
|
+
content: string;
|
|
352
|
+
};
|
|
353
|
+
/**
|
|
354
|
+
* Full-text keyword search across all workflows' step summaries, pending_tasks,
|
|
355
|
+
* and artifact source names. Optionally also searches artifact file content.
|
|
356
|
+
* Returns a list of matches grouped by workflow.
|
|
357
|
+
*/
|
|
358
|
+
searchWorkflows(query: string, opts?: {
|
|
359
|
+
include_artifact_content?: boolean;
|
|
360
|
+
status?: 'active' | 'paused' | 'completed';
|
|
361
|
+
}): Array<{
|
|
362
|
+
workflow: DbWorkflow;
|
|
363
|
+
matched_steps: DbWorkflowStep[];
|
|
364
|
+
matched_artifacts: (DbWorkflowArtifact & {
|
|
365
|
+
content_snippet?: string;
|
|
366
|
+
})[];
|
|
367
|
+
}>;
|
|
368
|
+
/**
|
|
369
|
+
* Imports an existing flow/architecture doc as a paused workflow (not active — importing
|
|
370
|
+
* a doc isn't the same as declaring active work). Idempotent on `name`: re-importing the
|
|
371
|
+
* same doc overwrites its existing `imported_doc` artifact file in place instead of
|
|
372
|
+
* creating a duplicate workflow every time the source docs are re-imported.
|
|
373
|
+
*/
|
|
374
|
+
importWorkflowDoc(name: string, description: string, content: string, sourceFileName: string): {
|
|
375
|
+
workflow: DbWorkflow;
|
|
376
|
+
created: boolean;
|
|
377
|
+
};
|
|
378
|
+
private static readonly SPURIOUS_NODE_NAMES;
|
|
379
|
+
/**
|
|
380
|
+
* Read-only detection shared by `pruneSpuriousNodes` (which acts on it) and `devsmind
|
|
381
|
+
* analyze`'s dry-run report (which just lists it). Never mutates the DB.
|
|
382
|
+
*/
|
|
383
|
+
findSpuriousAndMissingFileNodes(workspaceRoot: string): {
|
|
384
|
+
spurious: {
|
|
385
|
+
id: string;
|
|
386
|
+
name: string;
|
|
387
|
+
file_path: string;
|
|
388
|
+
}[];
|
|
389
|
+
missingFile: {
|
|
390
|
+
id: string;
|
|
391
|
+
name: string;
|
|
392
|
+
file_path: string;
|
|
393
|
+
}[];
|
|
394
|
+
};
|
|
227
395
|
pruneSpuriousNodes(workspaceRoot: string): {
|
|
228
396
|
prunedCount: number;
|
|
229
397
|
prunedNodes: string[];
|
|
@@ -231,9 +399,34 @@ export declare class DevMindDatabase {
|
|
|
231
399
|
private populateHistoryFromDisk;
|
|
232
400
|
private writeHistoryToDisk;
|
|
233
401
|
toRepoRelativePath(absolutePath: string): string;
|
|
402
|
+
/**
|
|
403
|
+
* Rejects a resolved path that escapes its expected root (e.g. via a stored
|
|
404
|
+
* `{repo}/../../..` path traveling outside the repo) by clamping it back to
|
|
405
|
+
* the root itself. node_id/file_path values flow in from AI-supplied tool
|
|
406
|
+
* calls, so a resolve must never be trusted to stay inside root on its own.
|
|
407
|
+
*/
|
|
408
|
+
private clampToRoot;
|
|
409
|
+
/**
|
|
410
|
+
* True if `absPath` sits inside a configured repo root or the workspace root itself.
|
|
411
|
+
* Used to reject `stage_change`/`update_history` file paths that would otherwise let a
|
|
412
|
+
* tool call read/write any file on disk (absolute path, or a `../` escape) instead of
|
|
413
|
+
* just repo source — nothing upstream of this validates that the AI-supplied path is
|
|
414
|
+
* actually inside the project.
|
|
415
|
+
*/
|
|
416
|
+
/**
|
|
417
|
+
* Gate for every AI-facing write (edit_node, stage_change, the legacy update_history):
|
|
418
|
+
* true only for paths inside a configured repo. `.devmind` itself — this project's OWN
|
|
419
|
+
* config, brain.db, and cached graph JSON — is never writable through these tools, even
|
|
420
|
+
* though it sits next to (and, before this check, was indistinguishable from) real source:
|
|
421
|
+
* without this, a write tool built to "never refuse a file type" would just as happily
|
|
422
|
+
* rewrite devsmind's own config.json as it would application source.
|
|
423
|
+
*/
|
|
424
|
+
isPathAllowed(absPath: string): boolean;
|
|
234
425
|
toAbsolutePath(repoRelativePath: string): string;
|
|
235
426
|
syncFromDisk(): void;
|
|
236
427
|
/** Escape LIKE metacharacters so a path is matched literally (use with ESCAPE '\\'). */
|
|
237
428
|
private likeEscape;
|
|
238
429
|
writeGraphToDisk(filePath: string): void;
|
|
430
|
+
/** Force-syncs all database nodes and workflows to disk JSON files. */
|
|
431
|
+
syncToDisk(): void;
|
|
239
432
|
}
|