devsmind-mcp 2.4.0 → 4.0.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 (135) hide show
  1. package/README.md +317 -234
  2. package/dist/cli/activity.d.ts +9 -0
  3. package/dist/cli/activity.js +69 -0
  4. package/dist/cli/activity.js.map +1 -0
  5. package/dist/cli/describe.d.ts +31 -0
  6. package/dist/cli/describe.js +201 -0
  7. package/dist/cli/describe.js.map +1 -0
  8. package/dist/cli/diff.d.ts +14 -0
  9. package/dist/cli/diff.js +161 -0
  10. package/dist/cli/diff.js.map +1 -0
  11. package/dist/cli/embed.d.ts +6 -0
  12. package/dist/cli/embed.js +109 -0
  13. package/dist/cli/embed.js.map +1 -0
  14. package/dist/cli/extract-agent.d.ts +66 -0
  15. package/dist/cli/extract-agent.js +192 -0
  16. package/dist/cli/extract-agent.js.map +1 -0
  17. package/dist/cli/feedback.d.ts +11 -0
  18. package/dist/cli/feedback.js +90 -0
  19. package/dist/cli/feedback.js.map +1 -0
  20. package/dist/cli/index.js +139 -9
  21. package/dist/cli/index.js.map +1 -1
  22. package/dist/cli/init.d.ts +30 -0
  23. package/dist/cli/init.js +89 -18
  24. package/dist/cli/init.js.map +1 -1
  25. package/dist/cli/integrations/memory-topics.d.ts +55 -0
  26. package/dist/cli/integrations/memory-topics.js +318 -0
  27. package/dist/cli/integrations/memory-topics.js.map +1 -0
  28. package/dist/cli/integrations/memory.d.ts +17 -6
  29. package/dist/cli/integrations/memory.js +52 -139
  30. package/dist/cli/integrations/memory.js.map +1 -1
  31. package/dist/cli/integrations/prompt.d.ts +8 -0
  32. package/dist/cli/integrations/prompt.js +24 -3
  33. package/dist/cli/integrations/prompt.js.map +1 -1
  34. package/dist/cli/integrations/registry.d.ts +26 -9
  35. package/dist/cli/integrations/registry.js +61 -30
  36. package/dist/cli/integrations/registry.js.map +1 -1
  37. package/dist/cli/llm-client.d.ts +116 -0
  38. package/dist/cli/llm-client.js +322 -0
  39. package/dist/cli/llm-client.js.map +1 -0
  40. package/dist/cli/rule.d.ts +22 -2
  41. package/dist/cli/rule.js +110 -34
  42. package/dist/cli/rule.js.map +1 -1
  43. package/dist/cli/runner.d.ts +21 -0
  44. package/dist/cli/runner.js +94 -276
  45. package/dist/cli/runner.js.map +1 -1
  46. package/dist/cli/sync-progress.d.ts +9 -0
  47. package/dist/cli/sync-progress.js +41 -0
  48. package/dist/cli/sync-progress.js.map +1 -0
  49. package/dist/cli/sync.js +5 -2
  50. package/dist/cli/sync.js.map +1 -1
  51. package/dist/cli/view.js +3 -2
  52. package/dist/cli/view.js.map +1 -1
  53. package/dist/cli/workflow.js +26 -20
  54. package/dist/cli/workflow.js.map +1 -1
  55. package/dist/db/activity-graph.d.ts +55 -0
  56. package/dist/db/activity-graph.js +314 -0
  57. package/dist/db/activity-graph.js.map +1 -0
  58. package/dist/db/activity.d.ts +221 -0
  59. package/dist/db/activity.js +348 -0
  60. package/dist/db/activity.js.map +1 -0
  61. package/dist/db/analyze.js +18 -6
  62. package/dist/db/analyze.js.map +1 -1
  63. package/dist/db/database.d.ts +654 -102
  64. package/dist/db/database.js +1911 -558
  65. package/dist/db/database.js.map +1 -1
  66. package/dist/db/edges.d.ts +24 -0
  67. package/dist/db/edges.js +84 -0
  68. package/dist/db/edges.js.map +1 -1
  69. package/dist/db/embedder.d.ts +39 -0
  70. package/dist/db/embedder.js +0 -0
  71. package/dist/db/embedder.js.map +1 -0
  72. package/dist/db/feedback.d.ts +128 -0
  73. package/dist/db/feedback.js +182 -0
  74. package/dist/db/feedback.js.map +1 -0
  75. package/dist/db/file-diff.d.ts +32 -0
  76. package/dist/db/file-diff.js +110 -0
  77. package/dist/db/file-diff.js.map +1 -0
  78. package/dist/db/grep.d.ts +78 -0
  79. package/dist/db/grep.js +475 -0
  80. package/dist/db/grep.js.map +1 -0
  81. package/dist/db/index-build.d.ts +75 -0
  82. package/dist/db/index-build.js +177 -0
  83. package/dist/db/index-build.js.map +1 -0
  84. package/dist/db/message-revert.d.ts +63 -0
  85. package/dist/db/message-revert.js +258 -0
  86. package/dist/db/message-revert.js.map +1 -0
  87. package/dist/db/revert.d.ts +31 -0
  88. package/dist/db/revert.js +108 -0
  89. package/dist/db/revert.js.map +1 -0
  90. package/dist/db/schema.d.ts +49 -4
  91. package/dist/db/schema.js +125 -73
  92. package/dist/db/schema.js.map +1 -1
  93. package/dist/db/search-index.d.ts +65 -0
  94. package/dist/db/search-index.js +74 -0
  95. package/dist/db/search-index.js.map +1 -0
  96. package/dist/db/staging.d.ts +91 -5
  97. package/dist/db/staging.js +148 -22
  98. package/dist/db/staging.js.map +1 -1
  99. package/dist/mcp/server.d.ts +29 -7
  100. package/dist/mcp/server.js +2637 -878
  101. package/dist/mcp/server.js.map +1 -1
  102. package/dist/mcp/vendor/3d-force-graph.min.js +5 -0
  103. package/dist/mcp/vendor/force-graph.min.js +5 -0
  104. package/dist/mcp/vendor/model/model_int8.onnx +0 -0
  105. package/dist/mcp/vendor/model/vocab.txt +30522 -0
  106. package/dist/mcp/vendor/three.min.js +7 -0
  107. package/dist/mcp/view.css +419 -0
  108. package/dist/mcp/view.html +161 -0
  109. package/dist/mcp/view.js +245 -0
  110. package/dist/mcp/view_chat.js +382 -0
  111. package/dist/mcp/view_graph.js +576 -0
  112. package/dist/mcp/visualizer.d.ts +25 -2
  113. package/dist/mcp/visualizer.js +31 -4
  114. package/dist/mcp/visualizer.js.map +1 -1
  115. package/dist/utils/ast.d.ts +89 -0
  116. package/dist/utils/ast.js +378 -10
  117. package/dist/utils/ast.js.map +1 -1
  118. package/dist/utils/diff.d.ts +44 -0
  119. package/dist/utils/diff.js +78 -0
  120. package/dist/utils/diff.js.map +1 -0
  121. package/dist/utils/edit.d.ts +6 -0
  122. package/dist/utils/edit.js +47 -14
  123. package/dist/utils/edit.js.map +1 -1
  124. package/dist/utils/scanner.d.ts +6 -4
  125. package/dist/utils/scanner.js +18 -8
  126. package/dist/utils/scanner.js.map +1 -1
  127. package/dist/utils/tokenize.d.ts +45 -0
  128. package/dist/utils/tokenize.js +129 -0
  129. package/dist/utils/tokenize.js.map +1 -0
  130. package/dist/utils/version.d.ts +14 -0
  131. package/dist/utils/version.js +61 -0
  132. package/dist/utils/version.js.map +1 -0
  133. package/package.json +18 -5
  134. package/dist/mcp/visualizer_2d.html +0 -635
  135. package/dist/mcp/visualizer_3d.html +0 -613
@@ -0,0 +1,66 @@
1
+ import { ExtractionCandidate } from '../utils/ast';
2
+ import { LlmCredentials } from './llm-client';
3
+ /**
4
+ * Agentic node-extraction curation — the judgment half of Phase D. `enumerateFileCandidates`
5
+ * (deterministic, no LLM) already found every candidate and split them by whether their EXISTENCE
6
+ * is unambiguous (`isExported`). This module handles only what's left: for candidates whose
7
+ * significance genuinely requires judgment (an unexported helper, an anonymous default, a tiny
8
+ * inline callback), a multi-turn tool-calling agent decides keep/drop/merge/rename. Existence is
9
+ * never delegated to the model — only significance is, which is the whole reason this is more
10
+ * reliable than the old "dump the whole file to an LLM and hope it finds everything" extraction.
11
+ */
12
+ export type CurationDecisionKind = 'keep' | 'drop' | 'merge' | 'rename';
13
+ export interface CurationDecision {
14
+ qualified: string;
15
+ decision: CurationDecisionKind;
16
+ /** Present when `decision === 'merge'` — the candidate this one conceptually belongs to. */
17
+ mergeInto?: string;
18
+ /** Present when `decision === 'rename'` — the better name for this candidate. */
19
+ renameTo?: string;
20
+ reason?: string;
21
+ }
22
+ export interface CurationResult {
23
+ decisions: CurationDecision[];
24
+ /** True when the turn budget ran out before the model called `submit_decisions` — every
25
+ * candidate defaulted to "keep" (see {@link curateAmbiguousCandidates}'s doc for why). */
26
+ timedOut: boolean;
27
+ }
28
+ /**
29
+ * Runs the agentic curation loop over one file's AMBIGUOUS candidates only (the `isExported:false`
30
+ * subset of `enumerateFileCandidates` — the caller is responsible for that split, see
31
+ * {@link extractFileWithCuration}). Multi-turn: the model may call `get_file_imports` any number
32
+ * of times before it must call `submit_decisions` to finish. `opts.onTurn` receives one line per
33
+ * model turn — nothing about this loop happens silently.
34
+ *
35
+ * If the turn budget is exhausted without a `submit_decisions` call, every candidate defaults to
36
+ * "keep" — silently dropping a real candidate is a worse failure than over-including one, and
37
+ * everything reaching this function already passed the deterministic auto-accept gate, so "keep,
38
+ * unreviewed" is the safe fallback, not a crash or a lost node.
39
+ */
40
+ export declare function curateAmbiguousCandidates(creds: LlmCredentials, filePath: string, candidates: ExtractionCandidate[], opts?: {
41
+ maxTurns?: number;
42
+ rpm?: number;
43
+ onTurn?: (line: string) => void;
44
+ }): Promise<CurationResult>;
45
+ /** The shape `runBackgroundIndexing`/`runBackgroundReindexing` already consume from the old
46
+ * `extractNodesFromCode` — matched exactly so wiring this in is a drop-in call-site swap. */
47
+ export interface CuratedExtractionResult {
48
+ nodes: {
49
+ node_id: string;
50
+ name: string;
51
+ type: string;
52
+ signature?: string;
53
+ code_snapshot: string;
54
+ }[];
55
+ }
56
+ /**
57
+ * The full Phase D per-file extraction: deterministic enumeration + auto-accept for exported
58
+ * candidates (zero LLM turns), agentic curation only for the ambiguous remainder. This is the
59
+ * cost control the whole design rests on — on a typical file most candidates ARE exported, so most
60
+ * files touch the LLM only for a handful of borderline cases, or not at all.
61
+ */
62
+ export declare function extractFileWithCuration(creds: LlmCredentials, filePath: string, opts?: {
63
+ maxTurns?: number;
64
+ rpm?: number;
65
+ onLog?: (line: string) => void;
66
+ }): Promise<CuratedExtractionResult>;
@@ -0,0 +1,192 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.curateAmbiguousCandidates = curateAmbiguousCandidates;
4
+ exports.extractFileWithCuration = extractFileWithCuration;
5
+ const ast_1 = require("../utils/ast");
6
+ const llm_client_1 = require("./llm-client");
7
+ const runner_1 = require("./runner");
8
+ const SUBMIT_TOOL_NAME = 'submit_decisions';
9
+ const GET_IMPORTS_TOOL_NAME = 'get_file_imports';
10
+ function buildTools() {
11
+ return [
12
+ {
13
+ name: GET_IMPORTS_TOOL_NAME,
14
+ description: "Returns this file's own import statements (module specifier + imported name). Useful to judge whether an ambiguous candidate is a real, independently meaningful entity or just a trivial local detail — e.g. whether it mirrors something worth tracking alongside a specific imported dependency, versus pure local plumbing.",
15
+ parameters: { type: 'object', properties: {}, additionalProperties: false }
16
+ },
17
+ {
18
+ name: SUBMIT_TOOL_NAME,
19
+ description: 'REQUIRED — call this exactly once, when you have a decision for every candidate, to finish the task. This is the ONLY way to finish; do not stop without calling it.',
20
+ parameters: {
21
+ type: 'object',
22
+ properties: {
23
+ decisions: {
24
+ type: 'array',
25
+ items: {
26
+ type: 'object',
27
+ properties: {
28
+ qualified: { type: 'string', description: 'The candidate\'s "qualified" name, copied exactly as given.' },
29
+ decision: { type: 'string', enum: ['keep', 'drop', 'merge', 'rename'] },
30
+ merge_into: { type: 'string', description: 'Required when decision is "merge" — the qualified name of the candidate this one belongs to.' },
31
+ rename_to: { type: 'string', description: 'Required when decision is "rename" — the better name for this candidate.' },
32
+ reason: { type: 'string', description: 'One short sentence: why.' }
33
+ },
34
+ required: ['qualified', 'decision']
35
+ }
36
+ }
37
+ },
38
+ required: ['decisions']
39
+ }
40
+ }
41
+ ];
42
+ }
43
+ function buildSystemPrompt() {
44
+ return [
45
+ 'You are curating a list of code-graph node CANDIDATES for one source file.',
46
+ "These candidates were found deterministically by parsing the file's AST — every one of them genuinely exists in the code, at the exact line numbers given. Nothing about their EXISTENCE is in question here.",
47
+ '',
48
+ 'Your ONLY job is judgment. For EACH candidate, decide:',
49
+ '- "keep" — a real, independently meaningful entity worth its own node.',
50
+ '- "drop" — trivial noise (e.g. a tiny inline callback, a throwaway local) not worth tracking on its own.',
51
+ '- "merge" — this is really part of another candidate\'s concept, not a distinct entity (give merge_into).',
52
+ "- \"rename\" — the extractor's name is wrong, unclear, or anonymous and you have a genuinely better one (give rename_to).",
53
+ '',
54
+ `Call ${GET_IMPORTS_TOOL_NAME} if the file's imports would help you judge significance — as many times as you like, though it never changes between calls.`,
55
+ `When you have a decision for EVERY candidate, call ${SUBMIT_TOOL_NAME} exactly once with the full list. Keep any "reason" to one short sentence. Do not respond with plain text instead of a tool call.`
56
+ ].join('\n');
57
+ }
58
+ function buildInitialUserMessage(filePath, candidates) {
59
+ const blocks = candidates.map(c => `- qualified: "${c.qualified}"\n type: ${c.type}\n lines: ${c.startLine}-${c.endLine}\n code:\n${c.codeSnapshot.split('\n').map(l => ' ' + l).join('\n')}`);
60
+ return `File: ${filePath}\n\n${candidates.length} candidate(s) to curate:\n\n${blocks.join('\n\n')}`;
61
+ }
62
+ function parseDecisions(args, candidates) {
63
+ const raw = Array.isArray(args.decisions) ? args.decisions : [];
64
+ const validQualified = new Set(candidates.map(c => c.qualified));
65
+ const decisions = [];
66
+ const seen = new Set();
67
+ for (const entry of raw) {
68
+ const d = entry;
69
+ if (!d || typeof d.qualified !== 'string' || !validQualified.has(d.qualified))
70
+ continue;
71
+ if (seen.has(d.qualified))
72
+ continue; // one decision per candidate — a duplicate is ignored
73
+ seen.add(d.qualified);
74
+ const decisionKind = d.decision === 'drop' || d.decision === 'merge' || d.decision === 'rename' ? d.decision : 'keep';
75
+ decisions.push({
76
+ qualified: d.qualified,
77
+ decision: decisionKind,
78
+ mergeInto: typeof d.merge_into === 'string' ? d.merge_into : undefined,
79
+ renameTo: typeof d.rename_to === 'string' ? d.rename_to : undefined,
80
+ reason: typeof d.reason === 'string' ? d.reason : undefined
81
+ });
82
+ }
83
+ // Any candidate the model never mentioned still needs a decision — same conservative default
84
+ // as a turn-budget timeout (see curateAmbiguousCandidates): keep, not silently drop.
85
+ for (const c of candidates) {
86
+ if (!seen.has(c.qualified)) {
87
+ decisions.push({ qualified: c.qualified, decision: 'keep', reason: 'not mentioned by the model — conservative default' });
88
+ }
89
+ }
90
+ return decisions;
91
+ }
92
+ /**
93
+ * Runs the agentic curation loop over one file's AMBIGUOUS candidates only (the `isExported:false`
94
+ * subset of `enumerateFileCandidates` — the caller is responsible for that split, see
95
+ * {@link extractFileWithCuration}). Multi-turn: the model may call `get_file_imports` any number
96
+ * of times before it must call `submit_decisions` to finish. `opts.onTurn` receives one line per
97
+ * model turn — nothing about this loop happens silently.
98
+ *
99
+ * If the turn budget is exhausted without a `submit_decisions` call, every candidate defaults to
100
+ * "keep" — silently dropping a real candidate is a worse failure than over-including one, and
101
+ * everything reaching this function already passed the deterministic auto-accept gate, so "keep,
102
+ * unreviewed" is the safe fallback, not a crash or a lost node.
103
+ */
104
+ async function curateAmbiguousCandidates(creds, filePath, candidates, opts = {}) {
105
+ if (candidates.length === 0)
106
+ return { decisions: [], timedOut: false };
107
+ const maxTurns = opts.maxTurns ?? 4;
108
+ const tools = buildTools();
109
+ const systemPrompt = buildSystemPrompt();
110
+ const messages = [{ role: 'user', content: buildInitialUserMessage(filePath, candidates) }];
111
+ const log = opts.onTurn ?? (() => { });
112
+ for (let turn = 1; turn <= maxTurns; turn++) {
113
+ await (0, runner_1.throttleRpm)(opts.rpm);
114
+ const result = await (0, llm_client_1.sendConversationTurnWithRetry)(creds, systemPrompt, messages, tools, {
115
+ onRetry: (m) => log(` ⚠ ${m}`)
116
+ });
117
+ const submitCall = result.toolCalls.find(tc => tc.name === SUBMIT_TOOL_NAME);
118
+ if (submitCall) {
119
+ const decisions = parseDecisions(submitCall.args, candidates);
120
+ log(` ✓ turn ${turn}: ${SUBMIT_TOOL_NAME} — ${decisions.length} decision(s)`);
121
+ return { decisions, timedOut: false };
122
+ }
123
+ const importsCall = result.toolCalls.find(tc => tc.name === GET_IMPORTS_TOOL_NAME);
124
+ if (importsCall) {
125
+ log(` → turn ${turn}: ${GET_IMPORTS_TOOL_NAME}`);
126
+ messages.push({ role: 'assistant', content: result.text, toolCalls: result.toolCalls });
127
+ messages.push({
128
+ role: 'tool',
129
+ toolCallId: importsCall.id,
130
+ toolName: GET_IMPORTS_TOOL_NAME,
131
+ content: JSON.stringify((0, ast_1.listFileImports)(filePath))
132
+ });
133
+ continue;
134
+ }
135
+ // No recognized tool call this turn — nudge once rather than loop silently on a model that
136
+ // isn't cooperating; if it still won't call a tool, the turn budget above ends this cleanly.
137
+ log(` ? turn ${turn}: no tool call — nudging`);
138
+ messages.push({ role: 'assistant', content: result.text, toolCalls: [] });
139
+ messages.push({
140
+ role: 'user',
141
+ content: `You must call ${SUBMIT_TOOL_NAME} to finish (or ${GET_IMPORTS_TOOL_NAME} first if you need it). Do not respond with plain text.`
142
+ });
143
+ }
144
+ log(` ⚠ turn budget (${maxTurns}) exhausted — defaulting all ${candidates.length} candidate(s) to "keep"`);
145
+ return {
146
+ decisions: candidates.map(c => ({ qualified: c.qualified, decision: 'keep', reason: 'curation turn budget exhausted — conservative default' })),
147
+ timedOut: true
148
+ };
149
+ }
150
+ /**
151
+ * The full Phase D per-file extraction: deterministic enumeration + auto-accept for exported
152
+ * candidates (zero LLM turns), agentic curation only for the ambiguous remainder. This is the
153
+ * cost control the whole design rests on — on a typical file most candidates ARE exported, so most
154
+ * files touch the LLM only for a handful of borderline cases, or not at all.
155
+ */
156
+ async function extractFileWithCuration(creds, filePath, opts = {}) {
157
+ const log = opts.onLog ?? (() => { });
158
+ const candidates = (0, ast_1.enumerateFileCandidates)(filePath);
159
+ const exported = candidates.filter(c => c.isExported);
160
+ const ambiguous = candidates.filter(c => !c.isExported);
161
+ log(` ${exported.length} exported (auto-accepted, 0 LLM turns), ${ambiguous.length} ambiguous`);
162
+ const nodes = exported.map(c => ({
163
+ node_id: c.qualified,
164
+ name: c.name,
165
+ type: c.type,
166
+ signature: c.signature ?? undefined,
167
+ code_snapshot: c.codeSnapshot
168
+ }));
169
+ if (ambiguous.length === 0)
170
+ return { nodes };
171
+ const result = await curateAmbiguousCandidates(creds, filePath, ambiguous, {
172
+ maxTurns: opts.maxTurns,
173
+ rpm: opts.rpm,
174
+ onTurn: log
175
+ });
176
+ const byQualified = new Map(ambiguous.map(c => [c.qualified, c]));
177
+ for (const d of result.decisions) {
178
+ const c = byQualified.get(d.qualified);
179
+ if (!c)
180
+ continue;
181
+ // "merge" folds this candidate's SIGNIFICANCE into its target conceptually — it does not
182
+ // become a separate node. The merge target (if it's also a real candidate) is handled by its
183
+ // own decision entry; this candidate's code simply isn't extracted as its own node.
184
+ if (d.decision === 'drop' || d.decision === 'merge')
185
+ continue;
186
+ const name = d.decision === 'rename' && d.renameTo ? d.renameTo : c.name;
187
+ const nodeId = d.decision === 'rename' && d.renameTo ? d.renameTo : c.qualified;
188
+ nodes.push({ node_id: nodeId, name, type: c.type, signature: c.signature ?? undefined, code_snapshot: c.codeSnapshot });
189
+ }
190
+ return { nodes };
191
+ }
192
+ //# sourceMappingURL=extract-agent.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"extract-agent.js","sourceRoot":"","sources":["../../src/cli/extract-agent.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ /**
2
+ * `devsmind feedback` — the human-facing read of the local, gitignored feedback log that
3
+ * `commit_changes`' `feedback` param writes to (see db/feedback.ts). Read-only, same store
4
+ * `read_graph_feedback` (an agent's own batch graph-fix session) and the AI-only product log
5
+ * draw from — this is the first surface a person has ever had to actually see any of it.
6
+ */
7
+ export declare function handleFeedback(opts: {
8
+ path?: string;
9
+ since?: string;
10
+ all?: boolean;
11
+ }): Promise<void>;
@@ -0,0 +1,90 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.handleFeedback = handleFeedback;
4
+ const config_1 = require("../utils/config");
5
+ const feedback_1 = require("../db/feedback");
6
+ const DIM = '\x1b[2m';
7
+ const BOLD = '\x1b[1m';
8
+ const GREEN = '\x1b[32m';
9
+ const YELLOW = '\x1b[33m';
10
+ const RESET = '\x1b[0m';
11
+ function sinceCutoff(sinceDays) {
12
+ const n = sinceDays ? parseInt(sinceDays, 10) : undefined;
13
+ return n !== undefined && !Number.isNaN(n) ? Date.now() - n * 24 * 60 * 60 * 1000 : null;
14
+ }
15
+ function printGraphCluster(c) {
16
+ const confidenceTag = c.confidence === 'confirmed' ? `${GREEN}confirmed${RESET}` : `${YELLOW}suspected${RESET}`;
17
+ console.log(` ${BOLD}${c.count}x${RESET} ${c.category} ${DIM}${c.node_id}${RESET} (${confidenceTag})`);
18
+ // One representative line of text per cluster keeps this readable even when the same report
19
+ // repeated 30 times — the individual entries (with evidence) are what read_graph_feedback
20
+ // exposes to the AI's own batch graph-fix session, not what a human skim needs here.
21
+ console.log(` ${c.entries[0].text}`);
22
+ if (c.entries[0].evidence) {
23
+ const e = c.entries[0].evidence;
24
+ console.log(` ${DIM}evidence: ${e.file}${e.line ? ':' + e.line : ''}${RESET}`);
25
+ }
26
+ }
27
+ function printProductEntries(entries, category) {
28
+ const filtered = entries.filter(e => e.category === category);
29
+ if (!filtered.length)
30
+ return;
31
+ const label = { tools_used: 'What helped', dropped_and_why: 'What got dropped', devsmind_better: 'How to improve' }[category];
32
+ console.log(` ${BOLD}${label}${RESET}`);
33
+ for (const e of filtered) {
34
+ console.log(` ${DIM}${new Date(e.ts).toLocaleDateString()}${RESET} ${e.text}`);
35
+ }
36
+ }
37
+ /**
38
+ * `devsmind feedback` — the human-facing read of the local, gitignored feedback log that
39
+ * `commit_changes`' `feedback` param writes to (see db/feedback.ts). Read-only, same store
40
+ * `read_graph_feedback` (an agent's own batch graph-fix session) and the AI-only product log
41
+ * draw from — this is the first surface a person has ever had to actually see any of it.
42
+ */
43
+ async function handleFeedback(opts) {
44
+ const devmindDir = (0, config_1.resolveDevmindDir)(opts.path);
45
+ if (!devmindDir) {
46
+ console.error(`❌ No .devmind directory found.\n` +
47
+ ` Run from inside a DevsMind brain folder, or pass --path <devmind_path>.`);
48
+ process.exit(1);
49
+ }
50
+ const cutoff = sinceCutoff(opts.since);
51
+ const inWindow = (ts) => cutoff === null || new Date(ts).getTime() >= cutoff;
52
+ const graphEntries = (0, feedback_1.readGraphFeedback)(devmindDir, { includeProcessed: !!opts.all }).filter(e => inWindow(e.ts));
53
+ const productEntries = (0, feedback_1.readProductFeedback)(devmindDir).filter(e => inWindow(e.ts));
54
+ const ruleCandidates = (0, feedback_1.readIndexerRuleCandidates)(devmindDir).filter(e => inWindow(e.ts));
55
+ if (!graphEntries.length && !productEntries.length && !ruleCandidates.length) {
56
+ console.log(`\n📭 No feedback recorded${cutoff !== null ? ` in the last ${opts.since} day(s)` : ''}.\n` +
57
+ ` commit_changes asks for feedback on every commit — "none" is a valid answer and writes\n` +
58
+ ` nothing here, so an empty log usually just means every recent commit answered "none".\n`);
59
+ return;
60
+ }
61
+ console.log('');
62
+ if (graphEntries.length) {
63
+ const clusters = (0, feedback_1.clusterGraphFeedback)(graphEntries);
64
+ console.log(`${BOLD}Graph feedback${RESET} ${DIM}${graphEntries.length} report(s) in ${clusters.length} cluster(s)${opts.all ? '' : ' — unprocessed only, pass --all to include processed'}${RESET}`);
65
+ for (const c of clusters)
66
+ printGraphCluster(c);
67
+ console.log('');
68
+ }
69
+ if (productEntries.length) {
70
+ console.log(`${BOLD}Product feedback${RESET} ${DIM}${productEntries.length} entries — never read by an agent, for you to act on${RESET}`);
71
+ printProductEntries(productEntries, 'devsmind_better');
72
+ printProductEntries(productEntries, 'dropped_and_why');
73
+ printProductEntries(productEntries, 'tools_used');
74
+ console.log('');
75
+ }
76
+ if (ruleCandidates.length) {
77
+ console.log(`${BOLD}Indexer rule candidates${RESET} ${DIM}recurring patterns worth a permanent detector${RESET}`);
78
+ for (const r of ruleCandidates) {
79
+ console.log(` ${BOLD}${r.evidence_count}x${RESET} ${r.pattern}`);
80
+ for (const ex of r.examples.slice(0, 3))
81
+ console.log(` ${DIM}${ex}${RESET}`);
82
+ }
83
+ console.log('');
84
+ }
85
+ if (graphEntries.length) {
86
+ console.log(`${DIM}Graph feedback is meant to be resolved by an AI agent's own batch graph-fix session`);
87
+ console.log(`(ask it to "run a graph-fix session" — it calls read_graph_feedback / mark_graph_feedback_processed).${RESET}\n`);
88
+ }
89
+ }
90
+ //# sourceMappingURL=feedback.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"feedback.js","sourceRoot":"","sources":["../../src/cli/feedback.ts"],"names":[],"mappings":"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"}
package/dist/cli/index.js CHANGED
@@ -8,16 +8,22 @@ const view_1 = require("./view");
8
8
  const prune_1 = require("./prune");
9
9
  const sync_1 = require("./sync");
10
10
  const analyze_1 = require("./analyze");
11
+ const diff_1 = require("./diff");
12
+ const activity_1 = require("./activity");
13
+ const feedback_1 = require("./feedback");
11
14
  const workflow_1 = require("./workflow");
12
15
  const mcp_1 = require("./integrations/mcp");
13
16
  const memory_1 = require("./integrations/memory");
14
17
  const runner_1 = require("./runner");
18
+ const describe_1 = require("./describe");
19
+ const embed_1 = require("./embed");
15
20
  const server_1 = require("../mcp/server");
21
+ const version_1 = require("../utils/version");
16
22
  const program = new commander_1.Command();
17
23
  program
18
24
  .name('devsmind')
19
25
  .description('DevsMind — Team AI Brain CLI')
20
- .version('1.0.0', '-v, --version');
26
+ .version(version_1.DEVSMIND_VERSION, '-v, --version');
21
27
  program
22
28
  .command('init')
23
29
  .description('Initialize a new DevsMind brain or update repository paths')
@@ -37,7 +43,7 @@ program
37
43
  ` IDEs connect via: http://localhost:${server_1.DEVSMIND_PORT}/mcp`)
38
44
  .option('--stdio', 'Use stdio transport instead of HTTP (for direct IDE process injection)')
39
45
  .option('-p, --port <number>', `HTTP port to listen on (default: ${server_1.DEVSMIND_PORT})`, String(server_1.DEVSMIND_PORT))
40
- .option('--path <devmind_path>', 'Explicit path to the .devmind directory (auto-detected from cwd by default) used only for --sync/--analyze below')
46
+ .option('--path <devmind_path>', 'Explicit path to the .devmind directory the server binds to (auto-detected from cwd by default). The server serves this one project, so callers never pass a path.')
41
47
  .option('--sync', 'Run devsmind sync before starting the server')
42
48
  .option('--analyze', 'Run devsmind analyze before starting the server')
43
49
  .option('--fix', 'With --analyze, also apply safe automatic fixes')
@@ -55,8 +61,16 @@ program
55
61
  }
56
62
  }
57
63
  if (opts.stdio) {
58
- // Stdio mode: IDE manages the process directly
59
- (0, server_1.runStdioMcpServer)();
64
+ // Stdio mode: IDE manages the process directly. Errors here (e.g. a bad --path) must not
65
+ // be an unhandled throw — same clean-exit treatment as the HTTP branch below, just via
66
+ // stderr since stdout is the JSON-RPC pipe.
67
+ try {
68
+ (0, server_1.runStdioMcpServer)(opts.path);
69
+ }
70
+ catch (err) {
71
+ console.error(`❌ MCP Server failed to start: ${err.message}`);
72
+ process.exit(1);
73
+ }
60
74
  }
61
75
  else {
62
76
  // HTTP mode: IDE connects over the network
@@ -66,7 +80,7 @@ program
66
80
  process.exit(1);
67
81
  }
68
82
  try {
69
- await (0, server_1.runHttpMcpServer)(port);
83
+ await (0, server_1.runHttpMcpServer)(port, opts.path);
70
84
  }
71
85
  catch (err) {
72
86
  const msg = err.message;
@@ -85,6 +99,7 @@ program
85
99
  .description('Get the AI workspace rule and place it in your tool (guided), or print it')
86
100
  .option('-p, --path <devmind_path>', 'Explicit path to the .devmind directory (auto-detected from cwd by default)')
87
101
  .option('--print', 'Just print the rule to stdout (no interactive placement)')
102
+ .option('--manual', 'Manual workflow style: AI searches/reads freely but only stages or commits when explicitly asked (default is automatic — stages and commits without being asked). Only needed with --print/non-interactive; the interactive flow asks.')
88
103
  .action(async (opts) => {
89
104
  try {
90
105
  await (0, rule_1.handleRule)(opts);
@@ -109,8 +124,9 @@ program
109
124
  });
110
125
  program
111
126
  .command('memory')
112
- .description('Seed a tool\'s own persistent agent-memory/skills store (guided, per-tool)')
113
- .option('-p, --path <devmind_path>', 'Explicit path to the .devmind directory (auto-detected from cwd by default)')
127
+ .description('Print one paste-able prompt asking your AI to remember the DevsMind workflow — writes nothing to disk')
128
+ .option('--print', 'Skip the interactive tool picker and print immediately (non-interactive use)')
129
+ .option('--tool <id>', 'Which tool\'s framing line to print (claude-code, cursor, antigravity, ...). The prompt itself is the same either way. Only used with --print/non-interactive; the interactive flow asks.')
114
130
  .action(async (opts) => {
115
131
  try {
116
132
  await (0, memory_1.handleMemory)(opts);
@@ -151,6 +167,112 @@ program
151
167
  process.exit(1);
152
168
  }
153
169
  });
170
+ program
171
+ .command('describe')
172
+ .description('Backfill natural-language descriptions for existing nodes that predate the description requirement — what search_nodes needs to find code by natural language. Always safe to re-run (work queue is just "nodes with no description yet"); new nodes going forward get described via commit_changes\' gate + add_description instead, not this command.')
173
+ .option('-p, --path <devmind_path>', 'Explicit path to the .devmind directory (auto-detected from cwd by default)')
174
+ .option('--provider <provider>', 'LLM provider: "gemini", "vertex", or "ollama"', 'gemini')
175
+ .option('--model <name>', 'Model identifier (default: "gemini-2.0-flash", "gemini-1.5-flash", or "qwen2.5-coder")')
176
+ .option('--key <api_key>', 'API Key or Service Account file path (overrides GEMINI_API_KEY / GOOGLE_APPLICATION_CREDENTIALS)')
177
+ .option('--url <url>', 'Ollama server endpoint (default: "http://localhost:11434")')
178
+ .option('--rpm <number>', 'Max LLM requests per minute, proactively paced (default: unthrottled)')
179
+ .option('--batch-size <number>', 'Nodes described per LLM call (default: 25)')
180
+ .option('--dry-run', 'List pending nodes without calling the LLM or writing anything')
181
+ .action(async (opts) => {
182
+ try {
183
+ await (0, describe_1.handleDescribe)({
184
+ path: opts.path,
185
+ provider: opts.provider,
186
+ model: opts.model,
187
+ key: opts.key,
188
+ url: opts.url,
189
+ rpm: opts.rpm ? parseInt(opts.rpm, 10) : undefined,
190
+ batchSize: opts.batchSize ? parseInt(opts.batchSize, 10) : undefined,
191
+ dryRun: !!opts.dryRun
192
+ });
193
+ }
194
+ catch (err) {
195
+ console.error(`❌ Describe failed: ${err.message}`);
196
+ process.exit(1);
197
+ }
198
+ });
199
+ program
200
+ .command('embed')
201
+ .description('Compute semantic (vector) search embeddings for every described node that lacks a current one. Fully local/offline — no LLM credentials needed, inference runs on-device. Always safe to re-run (work queue is "needs a vector"); new/edited descriptions going forward get auto-embedded via commit_changes/add_description/describe instead, not this command — it\'s only for clearing an existing backlog or a model upgrade (--force).')
202
+ .option('-p, --path <devmind_path>', 'Explicit path to the .devmind directory (auto-detected from cwd by default)')
203
+ .option('--batch-size <number>', 'Nodes embedded per inference call (default: 32)')
204
+ .option('--dry-run', 'List pending nodes without running inference or writing anything')
205
+ .option('--force', 'Re-embed every described node regardless of current vector state (use after a model upgrade)')
206
+ .action(async (opts) => {
207
+ try {
208
+ await (0, embed_1.handleEmbed)({
209
+ path: opts.path,
210
+ batchSize: opts.batchSize ? parseInt(opts.batchSize, 10) : undefined,
211
+ dryRun: !!opts.dryRun,
212
+ force: !!opts.force
213
+ });
214
+ }
215
+ catch (err) {
216
+ console.error(`❌ Embed failed: ${err.message}`);
217
+ process.exit(1);
218
+ }
219
+ });
220
+ program
221
+ .command('diff <node_id>')
222
+ .description('Show what changed in an entity, red/green, with the reasoning recorded for it')
223
+ .option('-p, --path <devmind_path>', 'Explicit path to the .devmind directory (auto-detected from cwd by default)')
224
+ .action(async (nodeId, opts) => {
225
+ try {
226
+ await (0, diff_1.handleDiff)(nodeId, opts);
227
+ }
228
+ catch (err) {
229
+ console.error(`❌ Diff failed: ${err.message}`);
230
+ process.exit(1);
231
+ }
232
+ });
233
+ program
234
+ .command('revert <node_id>')
235
+ .description('Undo an entity\'s most recent recorded edit and erase it from history')
236
+ .option('-p, --path <devmind_path>', 'Explicit path to the .devmind directory (auto-detected from cwd by default)')
237
+ .option('-y, --yes', 'Skip the confirmation prompt')
238
+ .action(async (nodeId, opts) => {
239
+ try {
240
+ await (0, diff_1.handleRevert)(nodeId, opts);
241
+ }
242
+ catch (err) {
243
+ console.error(`❌ Revert failed: ${err.message}`);
244
+ process.exit(1);
245
+ }
246
+ });
247
+ program
248
+ .command('activity')
249
+ .description('Show your local activity timeline — sessions and messages by day (local only, never pushed)')
250
+ .option('-p, --path <devmind_path>', 'Explicit path to the .devmind directory (auto-detected from cwd by default)')
251
+ .option('--since <days>', 'Only show messages from the last N days')
252
+ .action(async (opts) => {
253
+ try {
254
+ await (0, activity_1.handleActivity)(opts);
255
+ }
256
+ catch (err) {
257
+ console.error(`❌ Activity failed: ${err.message}`);
258
+ process.exit(1);
259
+ }
260
+ });
261
+ program
262
+ .command('feedback')
263
+ .description('Show feedback recorded from commit_changes — graph problems, product feedback, and indexer rule candidates (local only, never pushed)')
264
+ .option('-p, --path <devmind_path>', 'Explicit path to the .devmind directory (auto-detected from cwd by default)')
265
+ .option('--since <days>', 'Only show entries from the last N days')
266
+ .option('--all', 'Include graph-feedback entries already marked processed (default: unprocessed only)')
267
+ .action(async (opts) => {
268
+ try {
269
+ await (0, feedback_1.handleFeedback)(opts);
270
+ }
271
+ catch (err) {
272
+ console.error(`❌ Feedback failed: ${err.message}`);
273
+ process.exit(1);
274
+ }
275
+ });
154
276
  program
155
277
  .command('workflow')
156
278
  .description('List, view, pause, and resume persistent feature workflows (interactive)')
@@ -200,6 +322,8 @@ program
200
322
  .option('--from-scratch', 'Wipe ALL nodes, connections, history, and graph/history folders, then reindex from zero. Asks for confirmation unless --yes is passed.')
201
323
  .option('--nodes-only', 'Only run Phase 1 (node/code extraction). No connections are built or touched.')
202
324
  .option('--edges-only', 'Only run Phase 2 (connection resolution). Wipes existing connections and rebuilds them fresh across all current nodes. Requires nodes to already exist.')
325
+ .option('--describe', 'Only meaningful with --nodes-only: also run Phase 3 (description backfill) right after that structure-only extraction, using the SAME credentials — an optional extension when you want a --nodes-only run to be searchable immediately instead of running `devsmind describe` separately later. On a FULL run (neither --nodes-only nor --edges-only), Phase 3 always runs regardless of this flag — descriptions are mandatory there, since Phase 1/2 never write one and an undescribed "finished" index is not actually searchable by search_nodes\' description/vector layers. Not allowed with --edges-only (which never extracts nodes or resolves credentials).')
326
+ .option('--describe-batch-size <number>', 'Nodes described per LLM call during Phase 3 (default: 25)')
203
327
  .option('--repos <names>', 'Comma-separated repo names to restrict this run to (standalone mode only). Composes with --nodes-only / --edges-only, or full. Not allowed with --from-scratch.')
204
328
  .option('--rpm <number>', 'Max LLM requests per minute, paced proactively to avoid 429s (default: unthrottled — fires as fast as possible)')
205
329
  .option('--yes', 'Skip the confirmation prompt for --from-scratch')
@@ -215,6 +339,10 @@ program
215
339
  console.error('❌ Error: --from-scratch and --edges-only cannot be used together — --from-scratch wipes nodes, so there would be nothing to build edges from. Use --from-scratch alone, or --from-scratch --nodes-only, then --edges-only separately.');
216
340
  process.exit(1);
217
341
  }
342
+ if (opts.describe && opts.edgesOnly) {
343
+ console.error('❌ Error: --describe and --edges-only cannot be used together — --edges-only never extracts nodes or resolves LLM credentials, so there is nothing new to describe. Run `devsmind describe` separately if you need to backfill descriptions after an --edges-only run.');
344
+ process.exit(1);
345
+ }
218
346
  try {
219
347
  await (0, runner_1.runBackgroundIndexing)({
220
348
  devmindPath,
@@ -228,6 +356,8 @@ program
228
356
  fromScratch: !!opts.fromScratch,
229
357
  nodesOnly: !!opts.nodesOnly,
230
358
  edgesOnly: !!opts.edgesOnly,
359
+ describe: !!opts.describe,
360
+ describeBatchSize: opts.describeBatchSize ? parseInt(opts.describeBatchSize, 10) : undefined,
231
361
  repos: opts.repos ? opts.repos.split(',').map(s => s.trim()).filter(Boolean) : undefined,
232
362
  rpm: opts.rpm ? parseInt(opts.rpm, 10) : undefined,
233
363
  yes: !!opts.yes
@@ -243,8 +373,8 @@ program
243
373
  console.log(` Brain : ${resolved}`);
244
374
  console.log(`\n📋 To index your codebase, tell your AI assistant:\n`);
245
375
  console.log(` "Call devsmind.index_start with devmind_path = ${resolved}"`);
246
- console.log(` "Then read every file it returns and call stage_change for each entity, then commit_changes."`);
247
- console.log(` "Checkpoint every 10 files. Call index_complete when done."`);
376
+ console.log(` "It already parses the code itself describe each node it hands you with add_description, then call index_continue."`);
377
+ console.log(` "Repeat index_continue until every file is extracted and described, then call index_complete."`);
248
378
  console.log(` "NEVER use or write external scripts (like Python) to index files."\n`);
249
379
  console.log(` Or run it locally in the background using:\n`);
250
380
  console.log(` devsmind index --run --provider gemini --key YOUR_GEMINI_KEY`);