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.
Files changed (78) hide show
  1. package/README.md +174 -157
  2. package/dist/cli/handlers.js +25 -5
  3. package/dist/cli/handlers.js.map +1 -1
  4. package/dist/cli/register-analysis.js +11 -0
  5. package/dist/cli/register-analysis.js.map +1 -1
  6. package/dist/cli/register-database.js +4 -0
  7. package/dist/cli/register-database.js.map +1 -1
  8. package/dist/codebase/generators/architecture-extractor.d.ts +41 -0
  9. package/dist/codebase/generators/architecture-extractor.js +388 -0
  10. package/dist/codebase/generators/architecture-extractor.js.map +1 -0
  11. package/dist/codebase/generators/architecture.d.ts +2 -4
  12. package/dist/codebase/generators/architecture.js +123 -30
  13. package/dist/codebase/generators/architecture.js.map +1 -1
  14. package/dist/codebase/generators/modules.js +8 -8
  15. package/dist/codebase/generators/overview.js +24 -24
  16. package/dist/codebase/index.js +4 -3
  17. package/dist/codebase/index.js.map +1 -1
  18. package/dist/codebase/parsers/typescript.js +126 -67
  19. package/dist/codebase/parsers/typescript.js.map +1 -1
  20. package/dist/codebase/scanners/filesystem.d.ts +2 -2
  21. package/dist/codebase/scanners/filesystem.js +32 -10
  22. package/dist/codebase/scanners/filesystem.js.map +1 -1
  23. package/dist/commands/analyze.js +11 -4
  24. package/dist/commands/analyze.js.map +1 -1
  25. package/dist/commands/audit-design.js +74 -0
  26. package/dist/commands/audit-design.js.map +1 -1
  27. package/dist/commands/audit-report.js.map +1 -1
  28. package/dist/commands/audit-source.js +3 -1
  29. package/dist/commands/audit-source.js.map +1 -1
  30. package/dist/commands/audit.js +4 -3
  31. package/dist/commands/audit.js.map +1 -1
  32. package/dist/commands/autosave.d.ts +14 -0
  33. package/dist/commands/autosave.js +121 -0
  34. package/dist/commands/autosave.js.map +1 -1
  35. package/dist/commands/claude-plugin.js.map +1 -1
  36. package/dist/commands/codex-plugin.js.map +1 -1
  37. package/dist/commands/design-system.js +8 -0
  38. package/dist/commands/design-system.js.map +1 -1
  39. package/dist/commands/extract.js +8 -4
  40. package/dist/commands/extract.js.map +1 -1
  41. package/dist/commands/retrieve.d.ts +3 -0
  42. package/dist/commands/retrieve.js +599 -7
  43. package/dist/commands/retrieve.js.map +1 -1
  44. package/dist/commands/status.js +4 -3
  45. package/dist/commands/status.js.map +1 -1
  46. package/dist/core/devmind-ignore.d.ts +2 -0
  47. package/dist/core/devmind-ignore.js +99 -0
  48. package/dist/core/devmind-ignore.js.map +1 -0
  49. package/dist/core/errors.js +32 -0
  50. package/dist/core/errors.js.map +1 -1
  51. package/dist/core/index.d.ts +2 -0
  52. package/dist/core/index.js +2 -0
  53. package/dist/core/index.js.map +1 -1
  54. package/dist/core/source-file-cache.js +4 -1
  55. package/dist/core/source-file-cache.js.map +1 -1
  56. package/dist/core/tool-output.d.ts +18 -0
  57. package/dist/core/tool-output.js +95 -0
  58. package/dist/core/tool-output.js.map +1 -0
  59. package/dist/database/cli/register-database.js +4 -0
  60. package/dist/database/cli/register-database.js.map +1 -1
  61. package/dist/database/commands/generate.d.ts +1 -0
  62. package/dist/database/commands/generate.js +23 -14
  63. package/dist/database/commands/generate.js.map +1 -1
  64. package/dist/database/commands/handoff.js +123 -123
  65. package/dist/database/commands/handoff.js.map +1 -1
  66. package/dist/database/commands/learn.js +5 -1
  67. package/dist/database/commands/learn.js.map +1 -1
  68. package/dist/database/commands/memory.js +113 -113
  69. package/dist/database/commands/watch.js +1 -1
  70. package/dist/database/commands/watch.js.map +1 -1
  71. package/dist/database/extractors/mysql.js +45 -45
  72. package/dist/database/extractors/postgres.js +54 -54
  73. package/dist/database/extractors/sqlite.js +8 -8
  74. package/dist/database/generators/templates.js +520 -520
  75. package/dist/generators/unified.d.ts +1 -1
  76. package/dist/generators/unified.js +305 -75
  77. package/dist/generators/unified.js.map +1 -1
  78. 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.slice(startLine - 1, endLine).join('\n').trim();
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 (error) {
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 && !tagFilter.every((tag) => section.tags.map((t) => t.toLowerCase()).includes(tag))) {
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 score = metadataScore(section, tokens) * 2 + contentScore(content, tokens);
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 picked = [];
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`;