devmind 1.1.0 → 1.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +174 -157
- package/dist/cli/handlers.js +25 -5
- package/dist/cli/handlers.js.map +1 -1
- package/dist/cli/register-analysis.js +11 -0
- package/dist/cli/register-analysis.js.map +1 -1
- package/dist/cli/register-database.js +4 -0
- package/dist/cli/register-database.js.map +1 -1
- package/dist/codebase/generators/architecture-extractor.d.ts +41 -0
- package/dist/codebase/generators/architecture-extractor.js +388 -0
- package/dist/codebase/generators/architecture-extractor.js.map +1 -0
- package/dist/codebase/generators/architecture.d.ts +2 -4
- package/dist/codebase/generators/architecture.js +123 -30
- package/dist/codebase/generators/architecture.js.map +1 -1
- package/dist/codebase/generators/modules.js +8 -8
- package/dist/codebase/generators/overview.js +24 -24
- package/dist/codebase/index.js +4 -3
- package/dist/codebase/index.js.map +1 -1
- package/dist/codebase/parsers/typescript.js +126 -67
- package/dist/codebase/parsers/typescript.js.map +1 -1
- package/dist/codebase/scanners/filesystem.d.ts +2 -2
- package/dist/codebase/scanners/filesystem.js +32 -10
- package/dist/codebase/scanners/filesystem.js.map +1 -1
- package/dist/commands/analyze.js +11 -4
- package/dist/commands/analyze.js.map +1 -1
- package/dist/commands/audit-design.js +74 -0
- package/dist/commands/audit-design.js.map +1 -1
- package/dist/commands/audit-report.js.map +1 -1
- package/dist/commands/audit-source.js +3 -1
- package/dist/commands/audit-source.js.map +1 -1
- package/dist/commands/audit.js +4 -3
- package/dist/commands/audit.js.map +1 -1
- package/dist/commands/autosave.d.ts +14 -0
- package/dist/commands/autosave.js +121 -0
- package/dist/commands/autosave.js.map +1 -1
- package/dist/commands/claude-plugin.js.map +1 -1
- package/dist/commands/codex-plugin.js.map +1 -1
- package/dist/commands/design-system.js +8 -0
- package/dist/commands/design-system.js.map +1 -1
- package/dist/commands/extract.js +8 -4
- package/dist/commands/extract.js.map +1 -1
- package/dist/commands/retrieve.d.ts +3 -0
- package/dist/commands/retrieve.js +599 -7
- package/dist/commands/retrieve.js.map +1 -1
- package/dist/commands/status.js +4 -3
- package/dist/commands/status.js.map +1 -1
- package/dist/core/devmind-ignore.d.ts +2 -0
- package/dist/core/devmind-ignore.js +99 -0
- package/dist/core/devmind-ignore.js.map +1 -0
- package/dist/core/errors.js +32 -0
- package/dist/core/errors.js.map +1 -1
- package/dist/core/index.d.ts +2 -0
- package/dist/core/index.js +2 -0
- package/dist/core/index.js.map +1 -1
- package/dist/core/source-file-cache.js +4 -1
- package/dist/core/source-file-cache.js.map +1 -1
- package/dist/core/tool-output.d.ts +18 -0
- package/dist/core/tool-output.js +95 -0
- package/dist/core/tool-output.js.map +1 -0
- package/dist/database/cli/register-database.js +4 -0
- package/dist/database/cli/register-database.js.map +1 -1
- package/dist/database/commands/generate.d.ts +1 -0
- package/dist/database/commands/generate.js +23 -14
- package/dist/database/commands/generate.js.map +1 -1
- package/dist/database/commands/handoff.js +123 -123
- package/dist/database/commands/handoff.js.map +1 -1
- package/dist/database/commands/learn.js +5 -1
- package/dist/database/commands/learn.js.map +1 -1
- package/dist/database/commands/memory.js +113 -113
- package/dist/database/commands/watch.js +1 -1
- package/dist/database/commands/watch.js.map +1 -1
- package/dist/database/extractors/mysql.js +45 -45
- package/dist/database/extractors/postgres.js +54 -54
- package/dist/database/extractors/sqlite.js +8 -8
- package/dist/database/generators/templates.js +520 -520
- package/dist/generators/unified.d.ts +1 -1
- package/dist/generators/unified.js +305 -75
- package/dist/generators/unified.js.map +1 -1
- package/package.json +14 -6
|
@@ -26,6 +26,410 @@ function tokenize(input) {
|
|
|
26
26
|
.map((token) => token.trim())
|
|
27
27
|
.filter((token) => token.length >= 2);
|
|
28
28
|
}
|
|
29
|
+
function hasUiTokenSignal(tokens) {
|
|
30
|
+
const hasToken = tokens.includes('token') || tokens.includes('tokens');
|
|
31
|
+
if (!hasToken)
|
|
32
|
+
return false;
|
|
33
|
+
return (tokens.includes('design') ||
|
|
34
|
+
tokens.includes('theme') ||
|
|
35
|
+
tokens.includes('spacing') ||
|
|
36
|
+
tokens.includes('typography') ||
|
|
37
|
+
tokens.includes('color') ||
|
|
38
|
+
tokens.includes('colors') ||
|
|
39
|
+
tokens.includes('component') ||
|
|
40
|
+
tokens.includes('components'));
|
|
41
|
+
}
|
|
42
|
+
function hasAuthTokenSignal(tokens) {
|
|
43
|
+
const hasToken = tokens.includes('token') || tokens.includes('tokens');
|
|
44
|
+
return hasToken && !hasUiTokenSignal(tokens);
|
|
45
|
+
}
|
|
46
|
+
function detectRoutes(tokens) {
|
|
47
|
+
const routes = [];
|
|
48
|
+
const hasAny = (values) => values.some((value) => tokens.includes(value));
|
|
49
|
+
const authSignal = hasAny(['auth', 'authentication', 'authorize', 'authorization', 'login']) ||
|
|
50
|
+
hasAuthTokenSignal(tokens);
|
|
51
|
+
if (authSignal) {
|
|
52
|
+
routes.push('auth');
|
|
53
|
+
}
|
|
54
|
+
if (hasAny(['db', 'database', 'schema', 'sql', 'query', 'migration', 'postgres', 'mysql'])) {
|
|
55
|
+
routes.push('db');
|
|
56
|
+
}
|
|
57
|
+
const uiSignal = hasAny([
|
|
58
|
+
'ui',
|
|
59
|
+
'ux',
|
|
60
|
+
'frontend',
|
|
61
|
+
'component',
|
|
62
|
+
'layout',
|
|
63
|
+
'design',
|
|
64
|
+
'theme',
|
|
65
|
+
'hydration',
|
|
66
|
+
'ssr',
|
|
67
|
+
'csr',
|
|
68
|
+
'a11y',
|
|
69
|
+
'accessibility',
|
|
70
|
+
'form',
|
|
71
|
+
'client',
|
|
72
|
+
'server-component',
|
|
73
|
+
'animation',
|
|
74
|
+
'animations',
|
|
75
|
+
'motion',
|
|
76
|
+
'framer',
|
|
77
|
+
'gsap',
|
|
78
|
+
'lottie',
|
|
79
|
+
'keyframe',
|
|
80
|
+
'keyframes',
|
|
81
|
+
]) || hasUiTokenSignal(tokens);
|
|
82
|
+
if (uiSignal) {
|
|
83
|
+
routes.push('ui');
|
|
84
|
+
}
|
|
85
|
+
return routes;
|
|
86
|
+
}
|
|
87
|
+
function detectContractTargets(tokens) {
|
|
88
|
+
const targets = [];
|
|
89
|
+
const hasAny = (values) => values.some((value) => tokens.includes(value));
|
|
90
|
+
if (hasAny([
|
|
91
|
+
'econnrefused',
|
|
92
|
+
'eaddrinuse',
|
|
93
|
+
'port',
|
|
94
|
+
'listen',
|
|
95
|
+
'upstream',
|
|
96
|
+
'proxy',
|
|
97
|
+
'http',
|
|
98
|
+
'gateway',
|
|
99
|
+
'basepath',
|
|
100
|
+
'header',
|
|
101
|
+
])) {
|
|
102
|
+
targets.push('http');
|
|
103
|
+
}
|
|
104
|
+
if (hasAny([
|
|
105
|
+
'middleware',
|
|
106
|
+
'helper',
|
|
107
|
+
'helpers',
|
|
108
|
+
'signature',
|
|
109
|
+
'next',
|
|
110
|
+
'ctx',
|
|
111
|
+
'req',
|
|
112
|
+
'res',
|
|
113
|
+
'wrapper',
|
|
114
|
+
])) {
|
|
115
|
+
targets.push('middleware');
|
|
116
|
+
}
|
|
117
|
+
const authContractSignal = hasAny([
|
|
118
|
+
'auth',
|
|
119
|
+
'jwt',
|
|
120
|
+
'session',
|
|
121
|
+
'claim',
|
|
122
|
+
'claims',
|
|
123
|
+
'sub',
|
|
124
|
+
'aud',
|
|
125
|
+
'iss',
|
|
126
|
+
'scope',
|
|
127
|
+
'roles',
|
|
128
|
+
'permissions',
|
|
129
|
+
]) || hasAuthTokenSignal(tokens);
|
|
130
|
+
if (authContractSignal) {
|
|
131
|
+
targets.push('auth');
|
|
132
|
+
}
|
|
133
|
+
if (hasAny([
|
|
134
|
+
'ui',
|
|
135
|
+
'ux',
|
|
136
|
+
'frontend',
|
|
137
|
+
'component',
|
|
138
|
+
'layout',
|
|
139
|
+
'design',
|
|
140
|
+
'theme',
|
|
141
|
+
'hydration',
|
|
142
|
+
'ssr',
|
|
143
|
+
'csr',
|
|
144
|
+
'a11y',
|
|
145
|
+
'accessibility',
|
|
146
|
+
'form',
|
|
147
|
+
'validation',
|
|
148
|
+
'server-component',
|
|
149
|
+
'client-component',
|
|
150
|
+
])
|
|
151
|
+
|| hasUiTokenSignal(tokens)) {
|
|
152
|
+
targets.push('ui');
|
|
153
|
+
}
|
|
154
|
+
if (hasAny([
|
|
155
|
+
'animation',
|
|
156
|
+
'animations',
|
|
157
|
+
'motion',
|
|
158
|
+
'framer',
|
|
159
|
+
'framer-motion',
|
|
160
|
+
'gsap',
|
|
161
|
+
'lottie',
|
|
162
|
+
'keyframe',
|
|
163
|
+
'keyframes',
|
|
164
|
+
'spring',
|
|
165
|
+
'tween',
|
|
166
|
+
'timeline',
|
|
167
|
+
'reduced-motion',
|
|
168
|
+
'prefers-reduced-motion',
|
|
169
|
+
])) {
|
|
170
|
+
targets.push('motion');
|
|
171
|
+
}
|
|
172
|
+
if (hasAny(['go', 'golang', 'goroutine', 'gin', 'fiber', 'echo'])) {
|
|
173
|
+
targets.push('go');
|
|
174
|
+
}
|
|
175
|
+
if (hasAny(['python', 'fastapi', 'django', 'flask', 'uvicorn', 'pydantic'])) {
|
|
176
|
+
targets.push('python');
|
|
177
|
+
}
|
|
178
|
+
if (hasAny(['next', 'nextjs', 'next.js', 'app-router', 'route-handler', 'server-component'])) {
|
|
179
|
+
targets.push('next');
|
|
180
|
+
}
|
|
181
|
+
if (hasAny(['php', 'php-fpm', 'composer'])) {
|
|
182
|
+
targets.push('php');
|
|
183
|
+
}
|
|
184
|
+
if (hasAny(['laravel', 'eloquent', 'artisan', 'sanctum', 'passport'])) {
|
|
185
|
+
targets.push('laravel');
|
|
186
|
+
}
|
|
187
|
+
return [...new Set(targets)];
|
|
188
|
+
}
|
|
189
|
+
function parseRouteOption(raw) {
|
|
190
|
+
if (!raw)
|
|
191
|
+
return [];
|
|
192
|
+
const entries = raw
|
|
193
|
+
.split(',')
|
|
194
|
+
.map((value) => value.trim().toLowerCase())
|
|
195
|
+
.filter(Boolean);
|
|
196
|
+
const routes = [];
|
|
197
|
+
for (const entry of entries) {
|
|
198
|
+
if (entry === 'auth' || entry === 'db' || entry === 'ui') {
|
|
199
|
+
routes.push(entry);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
return [...new Set(routes)];
|
|
203
|
+
}
|
|
204
|
+
function detectEscalationLevel(tokens, override) {
|
|
205
|
+
if (override !== undefined) {
|
|
206
|
+
const parsed = Number(override);
|
|
207
|
+
if (parsed === 1 || parsed === 2 || parsed === 3)
|
|
208
|
+
return parsed;
|
|
209
|
+
}
|
|
210
|
+
const level3Hints = [
|
|
211
|
+
'refactor',
|
|
212
|
+
'cross-module',
|
|
213
|
+
'crossmodule',
|
|
214
|
+
'migration',
|
|
215
|
+
'migrate',
|
|
216
|
+
'incident',
|
|
217
|
+
'debug',
|
|
218
|
+
'outage',
|
|
219
|
+
'hotfix',
|
|
220
|
+
];
|
|
221
|
+
if (level3Hints.some((hint) => tokens.includes(hint)))
|
|
222
|
+
return 3;
|
|
223
|
+
const level2Hints = [
|
|
224
|
+
'modify',
|
|
225
|
+
'change',
|
|
226
|
+
'behavior',
|
|
227
|
+
'invariant',
|
|
228
|
+
'contract',
|
|
229
|
+
'breaking',
|
|
230
|
+
'semantics',
|
|
231
|
+
];
|
|
232
|
+
if (level2Hints.some((hint) => tokens.includes(hint)))
|
|
233
|
+
return 2;
|
|
234
|
+
return 1;
|
|
235
|
+
}
|
|
236
|
+
function routeCandidatesForLevel(level) {
|
|
237
|
+
const candidates = [{ level: 1, file: 'summary.md' }];
|
|
238
|
+
if (level >= 2)
|
|
239
|
+
candidates.push({ level: 2, file: 'details.md' });
|
|
240
|
+
if (level >= 3)
|
|
241
|
+
candidates.push({ level: 3, file: 'deep-dive.md' });
|
|
242
|
+
return candidates;
|
|
243
|
+
}
|
|
244
|
+
function shouldIncludeState(tokens, explicit) {
|
|
245
|
+
if (explicit === true)
|
|
246
|
+
return true;
|
|
247
|
+
const stateHints = [
|
|
248
|
+
'decision',
|
|
249
|
+
'decisions',
|
|
250
|
+
'hypothesis',
|
|
251
|
+
'hypotheses',
|
|
252
|
+
'assumption',
|
|
253
|
+
'assumptions',
|
|
254
|
+
'ruled-out',
|
|
255
|
+
'ruled',
|
|
256
|
+
'confirmed',
|
|
257
|
+
'state',
|
|
258
|
+
'context',
|
|
259
|
+
];
|
|
260
|
+
return stateHints.some((hint) => tokens.includes(hint));
|
|
261
|
+
}
|
|
262
|
+
function maxStateEntriesForLevel(level) {
|
|
263
|
+
if (level >= 3)
|
|
264
|
+
return 10;
|
|
265
|
+
if (level === 2)
|
|
266
|
+
return 6;
|
|
267
|
+
return 3;
|
|
268
|
+
}
|
|
269
|
+
function parseStateLogLines(content, kind, maxEntries) {
|
|
270
|
+
const lines = content
|
|
271
|
+
.split(/\r?\n/)
|
|
272
|
+
.map((line) => line.trim())
|
|
273
|
+
.filter(Boolean);
|
|
274
|
+
const parsed = [];
|
|
275
|
+
for (let i = lines.length - 1; i >= 0 && parsed.length < maxEntries; i -= 1) {
|
|
276
|
+
try {
|
|
277
|
+
const raw = JSON.parse(lines[i]);
|
|
278
|
+
const timestamp = typeof raw.timestamp === 'string' ? raw.timestamp : new Date(0).toISOString();
|
|
279
|
+
const textField = kind === 'decision'
|
|
280
|
+
? typeof raw.decision === 'string'
|
|
281
|
+
? raw.decision
|
|
282
|
+
: ''
|
|
283
|
+
: typeof raw.hypothesis === 'string'
|
|
284
|
+
? raw.hypothesis
|
|
285
|
+
: '';
|
|
286
|
+
if (!textField)
|
|
287
|
+
continue;
|
|
288
|
+
parsed.push({
|
|
289
|
+
kind,
|
|
290
|
+
timestamp,
|
|
291
|
+
source: typeof raw.source === 'string' ? raw.source : undefined,
|
|
292
|
+
note: typeof raw.note === 'string' ? raw.note : null,
|
|
293
|
+
text: textField,
|
|
294
|
+
status: kind === 'hypothesis' &&
|
|
295
|
+
(raw.status === 'open' || raw.status === 'ruled-out' || raw.status === 'confirmed')
|
|
296
|
+
? raw.status
|
|
297
|
+
: undefined,
|
|
298
|
+
});
|
|
299
|
+
}
|
|
300
|
+
catch {
|
|
301
|
+
// Skip malformed line.
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
return parsed.sort((a, b) => b.timestamp.localeCompare(a.timestamp));
|
|
305
|
+
}
|
|
306
|
+
async function loadStateEntries(outputDir, escalationLevel) {
|
|
307
|
+
const maxEntries = maxStateEntriesForLevel(escalationLevel);
|
|
308
|
+
const entries = [];
|
|
309
|
+
try {
|
|
310
|
+
const decisionContent = await readFileSafe(path.join(outputDir, 'context', 'DECISIONS.jsonl'));
|
|
311
|
+
entries.push(...parseStateLogLines(decisionContent, 'decision', maxEntries));
|
|
312
|
+
}
|
|
313
|
+
catch {
|
|
314
|
+
// Optional file
|
|
315
|
+
}
|
|
316
|
+
try {
|
|
317
|
+
const hypothesisContent = await readFileSafe(path.join(outputDir, 'context', 'HYPOTHESES.jsonl'));
|
|
318
|
+
entries.push(...parseStateLogLines(hypothesisContent, 'hypothesis', maxEntries));
|
|
319
|
+
}
|
|
320
|
+
catch {
|
|
321
|
+
// Optional file
|
|
322
|
+
}
|
|
323
|
+
return entries.sort((a, b) => b.timestamp.localeCompare(a.timestamp)).slice(0, maxEntries * 2);
|
|
324
|
+
}
|
|
325
|
+
async function loadContractContext(outputDir, contracts) {
|
|
326
|
+
const chunks = [];
|
|
327
|
+
for (const contract of contracts) {
|
|
328
|
+
const relSource = `context/contracts/${contract}.md`;
|
|
329
|
+
const fullPath = path.join(outputDir, relSource);
|
|
330
|
+
try {
|
|
331
|
+
const content = await readFileSafe(fullPath);
|
|
332
|
+
if (!content.trim())
|
|
333
|
+
continue;
|
|
334
|
+
chunks.push({
|
|
335
|
+
contract,
|
|
336
|
+
source: relSource,
|
|
337
|
+
title: `${contract.toUpperCase()} Contract`,
|
|
338
|
+
content: content.trim(),
|
|
339
|
+
});
|
|
340
|
+
}
|
|
341
|
+
catch {
|
|
342
|
+
// Optional file
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
return chunks;
|
|
346
|
+
}
|
|
347
|
+
function shouldIncludeDesignSystem(tokens, routes) {
|
|
348
|
+
if (routes.includes('ui'))
|
|
349
|
+
return true;
|
|
350
|
+
const uiHints = [
|
|
351
|
+
'ui',
|
|
352
|
+
'ux',
|
|
353
|
+
'design-system',
|
|
354
|
+
'component',
|
|
355
|
+
'a11y',
|
|
356
|
+
'accessibility',
|
|
357
|
+
'hydration',
|
|
358
|
+
'ssr',
|
|
359
|
+
'csr',
|
|
360
|
+
];
|
|
361
|
+
return uiHints.some((hint) => tokens.includes(hint)) || hasUiTokenSignal(tokens);
|
|
362
|
+
}
|
|
363
|
+
async function loadDesignSystemContext(outputDir) {
|
|
364
|
+
const source = 'design-system.json';
|
|
365
|
+
const fullPath = path.join(outputDir, source);
|
|
366
|
+
let raw = '';
|
|
367
|
+
try {
|
|
368
|
+
raw = await readFileSafe(fullPath);
|
|
369
|
+
}
|
|
370
|
+
catch {
|
|
371
|
+
return null;
|
|
372
|
+
}
|
|
373
|
+
if (!raw.trim())
|
|
374
|
+
return null;
|
|
375
|
+
try {
|
|
376
|
+
const parsed = JSON.parse(raw);
|
|
377
|
+
const lines = [
|
|
378
|
+
`Design system: ${parsed.name || 'unnamed'} (v${parsed.version || 'n/a'})`,
|
|
379
|
+
`Allowed component imports: ${(parsed.allowedComponentImports || []).join(', ') || '(none)'}`,
|
|
380
|
+
`Token sources: ${(parsed.tokenSources || []).join(', ') || '(none)'}`,
|
|
381
|
+
`Required wrappers: ${(parsed.requiredWrappers || []).join(', ') || '(none)'}`,
|
|
382
|
+
`Banned rules: ${(parsed.bannedRegexRules || [])
|
|
383
|
+
.map((rule) => `${rule.id || 'rule'}${rule.message ? ` (${rule.message})` : ''}`)
|
|
384
|
+
.join('; ') || '(none)'}`,
|
|
385
|
+
`Motion config: reducedMotionRequired=${parsed.motion?.reducedMotionRequired !== false}, maxDurationMs=${parsed.motion?.maxDurationMs || 900}, forbidInfiniteAnimations=${parsed.motion?.forbidInfiniteAnimations !== false}`,
|
|
386
|
+
];
|
|
387
|
+
return {
|
|
388
|
+
source,
|
|
389
|
+
title: 'Design System Context',
|
|
390
|
+
content: lines.join('\n'),
|
|
391
|
+
};
|
|
392
|
+
}
|
|
393
|
+
catch {
|
|
394
|
+
const snippet = raw.split(/\r?\n/).slice(0, 40).join('\n').trim();
|
|
395
|
+
return {
|
|
396
|
+
source,
|
|
397
|
+
title: 'Design System Context',
|
|
398
|
+
content: snippet || '(unreadable design-system content)',
|
|
399
|
+
};
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
function shouldLoadRefactorLedger(tokens, includeState) {
|
|
403
|
+
if (includeState)
|
|
404
|
+
return true;
|
|
405
|
+
return tokens.includes('refactor') || tokens.includes('rewrite') || tokens.includes('migration');
|
|
406
|
+
}
|
|
407
|
+
async function loadRoutedContext(outputDir, routes, escalationLevel) {
|
|
408
|
+
const chunks = [];
|
|
409
|
+
for (const route of routes) {
|
|
410
|
+
const candidates = routeCandidatesForLevel(escalationLevel);
|
|
411
|
+
for (const candidate of candidates) {
|
|
412
|
+
const relSource = `context/${route}/${candidate.file}`;
|
|
413
|
+
const fullPath = path.join(outputDir, relSource);
|
|
414
|
+
try {
|
|
415
|
+
const content = await readFileSafe(fullPath);
|
|
416
|
+
if (!content.trim())
|
|
417
|
+
continue;
|
|
418
|
+
chunks.push({
|
|
419
|
+
route,
|
|
420
|
+
level: candidate.level,
|
|
421
|
+
source: relSource,
|
|
422
|
+
title: `${route.toUpperCase()} Context (level-${candidate.level})`,
|
|
423
|
+
content: content.trim(),
|
|
424
|
+
});
|
|
425
|
+
}
|
|
426
|
+
catch {
|
|
427
|
+
// Optional routed context files; skip missing/unreadable paths.
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
return chunks;
|
|
432
|
+
}
|
|
29
433
|
function hashContent(content) {
|
|
30
434
|
return createHash('sha256').update(content, 'utf8').digest('hex').slice(0, 16);
|
|
31
435
|
}
|
|
@@ -38,6 +442,40 @@ function metadataScore(section, tokens) {
|
|
|
38
442
|
}
|
|
39
443
|
return score;
|
|
40
444
|
}
|
|
445
|
+
function criticalityScore(section, content, tokens) {
|
|
446
|
+
const criticalHints = [
|
|
447
|
+
'invariant',
|
|
448
|
+
'invariants',
|
|
449
|
+
'constraint',
|
|
450
|
+
'constraints',
|
|
451
|
+
'edge-case',
|
|
452
|
+
'edge-cases',
|
|
453
|
+
'edge case',
|
|
454
|
+
'migration',
|
|
455
|
+
'migrations',
|
|
456
|
+
'contract',
|
|
457
|
+
'contracts',
|
|
458
|
+
'decision',
|
|
459
|
+
'decisions',
|
|
460
|
+
'rollback',
|
|
461
|
+
];
|
|
462
|
+
const sectionSignal = `${section.title} ${section.tags.join(' ')} ${section.source} ${section.type}`.toLowerCase();
|
|
463
|
+
const contentSignal = content.toLowerCase();
|
|
464
|
+
let score = 0;
|
|
465
|
+
for (const hint of criticalHints) {
|
|
466
|
+
if (sectionSignal.includes(hint))
|
|
467
|
+
score += 4;
|
|
468
|
+
else if (contentSignal.includes(hint))
|
|
469
|
+
score += 2;
|
|
470
|
+
if (tokens.includes(hint.replace(/\s+/g, '-')) || tokens.includes(hint.replace(/\s+/g, ''))) {
|
|
471
|
+
if (sectionSignal.includes(hint) || contentSignal.includes(hint))
|
|
472
|
+
score += 2;
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
if (section.priority === 'high')
|
|
476
|
+
score += 1;
|
|
477
|
+
return score;
|
|
478
|
+
}
|
|
41
479
|
function contentScore(content, tokens) {
|
|
42
480
|
const lc = content.toLowerCase();
|
|
43
481
|
let score = 0;
|
|
@@ -53,7 +491,10 @@ function parseIndexSections(indexContent) {
|
|
|
53
491
|
}
|
|
54
492
|
function sliceByLines(content, startLine, endLine) {
|
|
55
493
|
const lines = content.split('\n');
|
|
56
|
-
return lines
|
|
494
|
+
return lines
|
|
495
|
+
.slice(startLine - 1, endLine)
|
|
496
|
+
.join('\n')
|
|
497
|
+
.trim();
|
|
57
498
|
}
|
|
58
499
|
function parseTags(tagsRaw) {
|
|
59
500
|
if (!tagsRaw)
|
|
@@ -77,6 +518,12 @@ export async function retrieve(options) {
|
|
|
77
518
|
const outputDir = options.output || '.devmind';
|
|
78
519
|
const query = options.query || '';
|
|
79
520
|
const tokens = tokenize(query);
|
|
521
|
+
const routeOverride = parseRouteOption(options.route);
|
|
522
|
+
const routedTargets = routeOverride.length > 0 ? routeOverride : detectRoutes(tokens);
|
|
523
|
+
const contractTargets = detectContractTargets(tokens);
|
|
524
|
+
const escalationLevel = detectEscalationLevel(tokens, options.level);
|
|
525
|
+
const includeState = shouldIncludeState(tokens, options.state);
|
|
526
|
+
const includeLedger = shouldLoadRefactorLedger(tokens, includeState);
|
|
80
527
|
const typeFilter = options.type?.toLowerCase();
|
|
81
528
|
const tagFilter = parseTags(options.tags);
|
|
82
529
|
const limit = clampInt(options.limit, 6);
|
|
@@ -85,11 +532,27 @@ export async function retrieve(options) {
|
|
|
85
532
|
const agentsPath = path.join(outputDir, 'AGENTS.md');
|
|
86
533
|
let indexSections = [];
|
|
87
534
|
let agentsContent = '';
|
|
535
|
+
let routedChunks = [];
|
|
536
|
+
let contractChunks = [];
|
|
537
|
+
let designSystemChunk = null;
|
|
538
|
+
let stateEntries = [];
|
|
539
|
+
let refactorLedger = '';
|
|
88
540
|
try {
|
|
89
541
|
indexSections = parseIndexSections(await profiler.section('retrieve.loadIndex', async () => readFileSafe(indexPath)));
|
|
90
542
|
agentsContent = await profiler.section('retrieve.loadAgents', async () => readFileSafe(agentsPath));
|
|
543
|
+
routedChunks = await profiler.section('retrieve.loadRoutedContext', async () => loadRoutedContext(outputDir, routedTargets, escalationLevel));
|
|
544
|
+
contractChunks = await profiler.section('retrieve.loadContractContext', async () => loadContractContext(outputDir, contractTargets));
|
|
545
|
+
designSystemChunk = shouldIncludeDesignSystem(tokens, routedTargets)
|
|
546
|
+
? await profiler.section('retrieve.loadDesignSystemContext', async () => loadDesignSystemContext(outputDir))
|
|
547
|
+
: null;
|
|
548
|
+
stateEntries = includeState
|
|
549
|
+
? await profiler.section('retrieve.loadStateEntries', async () => loadStateEntries(outputDir, escalationLevel))
|
|
550
|
+
: [];
|
|
551
|
+
refactorLedger = includeLedger
|
|
552
|
+
? await profiler.section('retrieve.loadRefactorLedger', async () => readFileSafe(path.join(outputDir, 'context', 'refactor-ledger.md')).catch(() => ''))
|
|
553
|
+
: '';
|
|
91
554
|
}
|
|
92
|
-
catch
|
|
555
|
+
catch {
|
|
93
556
|
const message = `Failed to load retrieval files in ${outputDir}. Run "devmind scan" or "devmind generate --all".`;
|
|
94
557
|
if (jsonMode) {
|
|
95
558
|
jsonFail(message);
|
|
@@ -110,7 +573,8 @@ export async function retrieve(options) {
|
|
|
110
573
|
let filtered = indexSections.filter((section) => {
|
|
111
574
|
if (typeFilter && section.type !== typeFilter)
|
|
112
575
|
return false;
|
|
113
|
-
if (tagFilter.length > 0 &&
|
|
576
|
+
if (tagFilter.length > 0 &&
|
|
577
|
+
!tagFilter.every((tag) => section.tags.map((t) => t.toLowerCase()).includes(tag))) {
|
|
114
578
|
return false;
|
|
115
579
|
}
|
|
116
580
|
return true;
|
|
@@ -121,6 +585,16 @@ export async function retrieve(options) {
|
|
|
121
585
|
success: true,
|
|
122
586
|
query,
|
|
123
587
|
outputDir,
|
|
588
|
+
routing: {
|
|
589
|
+
routes: routedTargets,
|
|
590
|
+
contracts: contractTargets,
|
|
591
|
+
escalationLevel,
|
|
592
|
+
},
|
|
593
|
+
contracts: contractChunks,
|
|
594
|
+
designSystem: designSystemChunk,
|
|
595
|
+
routed: routedChunks,
|
|
596
|
+
state: stateEntries,
|
|
597
|
+
ledger: refactorLedger,
|
|
124
598
|
selected: [],
|
|
125
599
|
message: 'No sections matched the requested filters.',
|
|
126
600
|
timestamp: new Date().toISOString(),
|
|
@@ -130,7 +604,6 @@ export async function retrieve(options) {
|
|
|
130
604
|
logger.warn('No sections matched the requested filters.');
|
|
131
605
|
return;
|
|
132
606
|
}
|
|
133
|
-
// Stage 1: metadata filter + ranking
|
|
134
607
|
filtered = filtered
|
|
135
608
|
.map((section) => ({ section, stage1Score: metadataScore(section, tokens) }))
|
|
136
609
|
.sort((a, b) => b.stage1Score - a.stage1Score ||
|
|
@@ -138,7 +611,6 @@ export async function retrieve(options) {
|
|
|
138
611
|
a.section.startLine - b.section.startLine)
|
|
139
612
|
.slice(0, Math.min(24, filtered.length))
|
|
140
613
|
.map((item) => item.section);
|
|
141
|
-
// Stage 2: content ranking
|
|
142
614
|
const ranked = filtered.map((section) => {
|
|
143
615
|
const content = sliceByLines(agentsContent, section.startLine, section.endLine);
|
|
144
616
|
const hash = hashContent(content);
|
|
@@ -147,22 +619,67 @@ export async function retrieve(options) {
|
|
|
147
619
|
logger.warn(`Section hash mismatch for ${section.id}; consider regenerating context.`);
|
|
148
620
|
}
|
|
149
621
|
}
|
|
150
|
-
const
|
|
622
|
+
const criticalScore = criticalityScore(section, content, tokens);
|
|
623
|
+
const score = metadataScore(section, tokens) * 2 + contentScore(content, tokens) + criticalScore;
|
|
151
624
|
return {
|
|
152
625
|
section,
|
|
153
626
|
stage1Score: metadataScore(section, tokens),
|
|
154
627
|
content,
|
|
155
628
|
score,
|
|
629
|
+
criticalityScore: criticalScore,
|
|
156
630
|
};
|
|
157
631
|
});
|
|
158
632
|
ranked.sort((a, b) => b.score - a.score ||
|
|
633
|
+
b.criticalityScore - a.criticalityScore ||
|
|
159
634
|
b.stage1Score - a.stage1Score ||
|
|
160
635
|
a.section.id.localeCompare(b.section.id) ||
|
|
161
636
|
a.section.startLine - b.section.startLine);
|
|
162
637
|
const selected = ranked.slice(0, limit);
|
|
163
|
-
const
|
|
638
|
+
const routedPicked = [];
|
|
164
639
|
let wordBudget = 0;
|
|
640
|
+
const contractPicked = [];
|
|
641
|
+
for (const chunk of contractChunks) {
|
|
642
|
+
const words = chunk.content.split(/\s+/).filter(Boolean).length;
|
|
643
|
+
if (wordBudget + words > maxWords && contractPicked.length > 0)
|
|
644
|
+
continue;
|
|
645
|
+
contractPicked.push(chunk);
|
|
646
|
+
wordBudget += words;
|
|
647
|
+
}
|
|
648
|
+
for (const chunk of routedChunks) {
|
|
649
|
+
const words = chunk.content.split(/\s+/).filter(Boolean).length;
|
|
650
|
+
if (wordBudget + words > maxWords && routedPicked.length > 0)
|
|
651
|
+
continue;
|
|
652
|
+
routedPicked.push(chunk);
|
|
653
|
+
wordBudget += words;
|
|
654
|
+
}
|
|
655
|
+
const includeDesignSystem = !!designSystemChunk &&
|
|
656
|
+
(wordBudget +
|
|
657
|
+
designSystemChunk.content
|
|
658
|
+
.split(/\s+/)
|
|
659
|
+
.filter(Boolean).length <=
|
|
660
|
+
maxWords ||
|
|
661
|
+
(wordBudget === 0 && !!designSystemChunk));
|
|
662
|
+
if (includeDesignSystem && designSystemChunk) {
|
|
663
|
+
wordBudget += designSystemChunk.content.split(/\s+/).filter(Boolean).length;
|
|
664
|
+
}
|
|
665
|
+
const statePicked = [];
|
|
666
|
+
for (const entry of stateEntries) {
|
|
667
|
+
const words = entry.text.split(/\s+/).filter(Boolean).length;
|
|
668
|
+
if (wordBudget + words > maxWords && statePicked.length > 0)
|
|
669
|
+
continue;
|
|
670
|
+
statePicked.push(entry);
|
|
671
|
+
wordBudget += words;
|
|
672
|
+
}
|
|
673
|
+
const ledgerWords = refactorLedger.split(/\s+/).filter(Boolean).length;
|
|
674
|
+
const includeLedgerInOutput = !!refactorLedger && (wordBudget + ledgerWords <= maxWords);
|
|
675
|
+
if (includeLedgerInOutput) {
|
|
676
|
+
wordBudget += ledgerWords;
|
|
677
|
+
}
|
|
678
|
+
const picked = [];
|
|
679
|
+
const sectionLimit = Math.max(0, limit - routedPicked.length - contractPicked.length);
|
|
165
680
|
for (const item of selected) {
|
|
681
|
+
if (picked.length >= sectionLimit)
|
|
682
|
+
break;
|
|
166
683
|
const words = item.content.split(/\s+/).filter(Boolean).length;
|
|
167
684
|
if (wordBudget + words > maxWords && picked.length > 0)
|
|
168
685
|
continue;
|
|
@@ -175,6 +692,16 @@ export async function retrieve(options) {
|
|
|
175
692
|
? {
|
|
176
693
|
query,
|
|
177
694
|
outputDir,
|
|
695
|
+
routing: {
|
|
696
|
+
routes: routedTargets,
|
|
697
|
+
contracts: contractTargets,
|
|
698
|
+
escalationLevel,
|
|
699
|
+
},
|
|
700
|
+
contracts: contractPicked,
|
|
701
|
+
designSystem: includeDesignSystem ? designSystemChunk : null,
|
|
702
|
+
routed: routedPicked,
|
|
703
|
+
state: statePicked,
|
|
704
|
+
ledger: includeLedgerInOutput ? refactorLedger : '',
|
|
178
705
|
selected: picked.map((item) => ({
|
|
179
706
|
id: item.section.id,
|
|
180
707
|
title: item.section.title,
|
|
@@ -184,6 +711,7 @@ export async function retrieve(options) {
|
|
|
184
711
|
startLine: item.section.startLine,
|
|
185
712
|
endLine: item.section.endLine,
|
|
186
713
|
score: item.score,
|
|
714
|
+
criticalityScore: item.criticalityScore,
|
|
187
715
|
content: item.content,
|
|
188
716
|
})),
|
|
189
717
|
profile,
|
|
@@ -191,6 +719,16 @@ export async function retrieve(options) {
|
|
|
191
719
|
: {
|
|
192
720
|
query,
|
|
193
721
|
outputDir,
|
|
722
|
+
routing: {
|
|
723
|
+
routes: routedTargets,
|
|
724
|
+
contracts: contractTargets,
|
|
725
|
+
escalationLevel,
|
|
726
|
+
},
|
|
727
|
+
contracts: contractPicked,
|
|
728
|
+
designSystem: includeDesignSystem ? designSystemChunk : null,
|
|
729
|
+
routed: routedPicked,
|
|
730
|
+
state: statePicked,
|
|
731
|
+
ledger: includeLedgerInOutput ? refactorLedger : '',
|
|
194
732
|
selected: picked.map((item) => ({
|
|
195
733
|
id: item.section.id,
|
|
196
734
|
title: item.section.title,
|
|
@@ -200,6 +738,7 @@ export async function retrieve(options) {
|
|
|
200
738
|
startLine: item.section.startLine,
|
|
201
739
|
endLine: item.section.endLine,
|
|
202
740
|
score: item.score,
|
|
741
|
+
criticalityScore: item.criticalityScore,
|
|
203
742
|
content: item.content,
|
|
204
743
|
})),
|
|
205
744
|
}, null, 2));
|
|
@@ -207,7 +746,60 @@ export async function retrieve(options) {
|
|
|
207
746
|
}
|
|
208
747
|
let output = '# Retrieved Context\n\n';
|
|
209
748
|
output += `Query: ${query || '(none)'}\n`;
|
|
749
|
+
if (routedTargets.length > 0) {
|
|
750
|
+
output += `Routing: ${routedTargets.map((target) => `\`${target}\``).join(', ')}\n`;
|
|
751
|
+
}
|
|
752
|
+
if (contractTargets.length > 0) {
|
|
753
|
+
output += `Contracts: ${contractTargets.map((target) => `\`${target}\``).join(', ')}\n`;
|
|
754
|
+
}
|
|
755
|
+
if (includeDesignSystem && designSystemChunk) {
|
|
756
|
+
output += 'Design system included: yes\n';
|
|
757
|
+
}
|
|
758
|
+
output += `Escalation level: ${escalationLevel}\n`;
|
|
759
|
+
output += `State logs included: ${statePicked.length}\n`;
|
|
210
760
|
output += `Selected sections: ${picked.length}\n\n`;
|
|
761
|
+
if (routedPicked.length > 0) {
|
|
762
|
+
output += '## Routed Context\n\n';
|
|
763
|
+
for (const chunk of routedPicked) {
|
|
764
|
+
output += `### ${chunk.title}\n\n`;
|
|
765
|
+
output += `- Route: \`${chunk.route}\`\n`;
|
|
766
|
+
output += `- Source: \`${chunk.source}\`\n\n`;
|
|
767
|
+
output += `${chunk.content}\n\n`;
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
if (contractPicked.length > 0) {
|
|
771
|
+
output += '## Contract Context\n\n';
|
|
772
|
+
for (const chunk of contractPicked) {
|
|
773
|
+
output += `### ${chunk.title}\n\n`;
|
|
774
|
+
output += `- Contract: \`${chunk.contract}\`\n`;
|
|
775
|
+
output += `- Source: \`${chunk.source}\`\n\n`;
|
|
776
|
+
output += `${chunk.content}\n\n`;
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
if (includeDesignSystem && designSystemChunk) {
|
|
780
|
+
output += '## Design System Context\n\n';
|
|
781
|
+
output += `- Source: \`${designSystemChunk.source}\`\n\n`;
|
|
782
|
+
output += `${designSystemChunk.content}\n\n`;
|
|
783
|
+
}
|
|
784
|
+
if (statePicked.length > 0) {
|
|
785
|
+
output += '## State Log\n\n';
|
|
786
|
+
for (const entry of statePicked) {
|
|
787
|
+
output += `- [${entry.kind}] ${entry.timestamp}`;
|
|
788
|
+
if (entry.status)
|
|
789
|
+
output += ` | status=\`${entry.status}\``;
|
|
790
|
+
if (entry.source)
|
|
791
|
+
output += ` | source=\`${entry.source}\``;
|
|
792
|
+
output += `\n`;
|
|
793
|
+
output += ` ${entry.text}\n`;
|
|
794
|
+
if (entry.note)
|
|
795
|
+
output += ` note: ${entry.note}\n`;
|
|
796
|
+
output += '\n';
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
if (includeLedgerInOutput) {
|
|
800
|
+
output += '## Refactor Ledger\n\n';
|
|
801
|
+
output += `${refactorLedger}\n\n`;
|
|
802
|
+
}
|
|
211
803
|
for (const item of picked) {
|
|
212
804
|
output += `## ${item.section.title}\n\n`;
|
|
213
805
|
output += `- ID: \`${item.section.id}\`\n`;
|