sweet-search 2.5.1 → 2.5.3

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 (159) hide show
  1. package/core/cli.js +45 -0
  2. package/core/embedding/embedding-cache.js +90 -4
  3. package/core/embedding/embedding-service.js +27 -5
  4. package/core/graph/graph-expansion.js +215 -36
  5. package/core/graph/graph-extractor.js +196 -11
  6. package/core/graph/graph-search.js +395 -92
  7. package/core/graph/hcgs-generator.js +2 -1
  8. package/core/graph/index.js +2 -0
  9. package/core/graph/repo-map.js +28 -6
  10. package/core/graph/structural-answer-cues.js +168 -0
  11. package/core/graph/structural-callsite-hints.js +40 -0
  12. package/core/graph/structural-context-format.js +40 -0
  13. package/core/graph/structural-context.js +450 -0
  14. package/core/graph/structural-forward-push.js +156 -0
  15. package/core/graph/structural-header-context.js +19 -0
  16. package/core/graph/structural-importance.js +148 -0
  17. package/core/graph/structural-pagerank.js +197 -0
  18. package/core/graph/summary-manager.js +13 -9
  19. package/core/incremental-indexing/application/dirty-scan.mjs +236 -0
  20. package/core/incremental-indexing/application/file-watcher.mjs +197 -0
  21. package/core/incremental-indexing/application/maintenance-handlers.mjs +519 -0
  22. package/core/incremental-indexing/application/maintenance-worker.mjs +380 -0
  23. package/core/incremental-indexing/application/operator-cli.mjs +554 -0
  24. package/core/incremental-indexing/application/production-li-delta.mjs +192 -0
  25. package/core/incremental-indexing/application/production-reconciler-helpers.mjs +107 -0
  26. package/core/incremental-indexing/application/production-reconciler.mjs +583 -0
  27. package/core/incremental-indexing/application/reconciler.mjs +477 -0
  28. package/core/incremental-indexing/application/tombstone-injector.mjs +148 -0
  29. package/core/incremental-indexing/domain/chunk-identity.mjs +260 -0
  30. package/core/incremental-indexing/domain/encoder-deps.mjs +193 -0
  31. package/core/incremental-indexing/domain/encoder-input.mjs +225 -0
  32. package/core/incremental-indexing/domain/interval-autotune.mjs +255 -0
  33. package/core/incremental-indexing/domain/reconcile-counters.mjs +149 -0
  34. package/core/incremental-indexing/domain/watermark-scheduler.mjs +239 -0
  35. package/core/incremental-indexing/infrastructure/artifact-temp-sweep.mjs +163 -0
  36. package/core/incremental-indexing/infrastructure/baseline-readiness.mjs +121 -0
  37. package/core/incremental-indexing/infrastructure/dirty-set.mjs +233 -0
  38. package/core/incremental-indexing/infrastructure/graph-gc.mjs +314 -0
  39. package/core/incremental-indexing/infrastructure/hashing.mjs +298 -0
  40. package/core/incremental-indexing/infrastructure/hcgs-invalidation.mjs +182 -0
  41. package/core/incremental-indexing/infrastructure/li-segment-merge.mjs +278 -0
  42. package/core/incremental-indexing/infrastructure/li-segment-state.mjs +173 -0
  43. package/core/incremental-indexing/infrastructure/lockfile.mjs +119 -0
  44. package/core/incremental-indexing/infrastructure/maintenance-state-reader.mjs +283 -0
  45. package/core/incremental-indexing/infrastructure/manifest.mjs +194 -0
  46. package/core/incremental-indexing/infrastructure/path-filter.mjs +190 -0
  47. package/core/incremental-indexing/infrastructure/reader-heartbeat.mjs +201 -0
  48. package/core/incremental-indexing/infrastructure/schema-migrations.mjs +257 -0
  49. package/core/incremental-indexing/infrastructure/sparse-gram-delta.mjs +335 -0
  50. package/core/incremental-indexing/infrastructure/sqlite-fts5.mjs +176 -0
  51. package/core/incremental-indexing/infrastructure/staleness-display.mjs +105 -0
  52. package/core/incremental-indexing/infrastructure/tombstone-bitmap.mjs +234 -0
  53. package/core/incremental-indexing/infrastructure/vector-delta-writer.mjs +359 -0
  54. package/core/incremental-indexing/infrastructure/vector-gc.mjs +133 -0
  55. package/core/incremental-indexing/infrastructure/worktree-stamp.mjs +155 -0
  56. package/core/incremental-indexing/infrastructure/wsl2-detect.mjs +115 -0
  57. package/core/indexing/admission-policy.js +139 -0
  58. package/core/indexing/artifact-builder.js +29 -12
  59. package/core/indexing/ast-chunker.js +107 -30
  60. package/core/indexing/dedup/exemplar-selector.js +19 -1
  61. package/core/indexing/gitignore-filter.js +223 -0
  62. package/core/indexing/incremental-tracker.js +99 -30
  63. package/core/indexing/index-codebase-v21.js +37 -7
  64. package/core/indexing/index-maintainer.mjs +698 -6
  65. package/core/indexing/indexer-ann.js +99 -15
  66. package/core/indexing/indexer-build.js +158 -45
  67. package/core/indexing/indexer-empty-baseline.js +80 -0
  68. package/core/indexing/indexer-manifest.js +66 -0
  69. package/core/indexing/indexer-phases.js +56 -23
  70. package/core/indexing/indexer-sparse-gram.js +54 -13
  71. package/core/indexing/indexer-utils.js +26 -208
  72. package/core/indexing/indexing-file-policy.js +32 -7
  73. package/core/indexing/maintainer-launcher.mjs +137 -0
  74. package/core/indexing/merkle-tracker.js +251 -244
  75. package/core/indexing/model-pool.js +46 -5
  76. package/core/infrastructure/code-graph-repository.js +758 -6
  77. package/core/infrastructure/code-graph-visibility.js +157 -0
  78. package/core/infrastructure/codebase-repository.js +100 -13
  79. package/core/infrastructure/config/search.js +1 -1
  80. package/core/infrastructure/db-utils.js +118 -0
  81. package/core/infrastructure/dedup-hashing.js +10 -13
  82. package/core/infrastructure/hardware-capability.js +17 -7
  83. package/core/infrastructure/index.js +10 -2
  84. package/core/infrastructure/init-config.js +138 -0
  85. package/core/infrastructure/language-patterns/maps.js +4 -1
  86. package/core/infrastructure/language-patterns/registry-core.js +56 -17
  87. package/core/infrastructure/language-patterns/registry-object-oriented.js +12 -5
  88. package/core/infrastructure/language-patterns.js +69 -0
  89. package/core/infrastructure/model-registry.js +20 -0
  90. package/core/infrastructure/native-inference.js +7 -12
  91. package/core/infrastructure/native-resolver.js +52 -37
  92. package/core/infrastructure/native-sparse-gram.js +261 -20
  93. package/core/infrastructure/native-tokenizer.js +6 -15
  94. package/core/infrastructure/simd-distance.js +10 -16
  95. package/core/infrastructure/sparse-gram-delta-reader.js +76 -0
  96. package/core/infrastructure/structural-alias-resolver.js +122 -0
  97. package/core/infrastructure/structural-candidate-ranker.js +34 -0
  98. package/core/infrastructure/structural-context-repository.js +472 -0
  99. package/core/infrastructure/structural-context-utils.js +51 -0
  100. package/core/infrastructure/structural-graph-signals.js +121 -0
  101. package/core/infrastructure/structural-qualified-resolution.js +15 -0
  102. package/core/infrastructure/structural-source-definitions.js +100 -0
  103. package/core/infrastructure/tombstone-bitmap-reader.js +139 -0
  104. package/core/infrastructure/tree-sitter-provider.js +811 -37
  105. package/core/prompt-optimization/data/p7-final/sweet-search-system-prompt.md +50 -0
  106. package/core/query/query-router.js +55 -5
  107. package/core/ranking/file-kind-ranking.js +2192 -15
  108. package/core/ranking/late-interaction-index.js +87 -12
  109. package/core/search/cli-decoration.js +290 -0
  110. package/core/search/context-expander.js +988 -78
  111. package/core/search/index.js +1 -0
  112. package/core/search/output-policy.js +275 -0
  113. package/core/search/search-anchor.js +499 -0
  114. package/core/search/search-boost.js +93 -1
  115. package/core/search/search-cli.js +61 -204
  116. package/core/search/search-hybrid.js +250 -10
  117. package/core/search/search-pattern-chunks.js +57 -8
  118. package/core/search/search-pattern-planner.js +68 -9
  119. package/core/search/search-pattern-prefilter.js +30 -10
  120. package/core/search/search-pattern-ripgrep.js +40 -4
  121. package/core/search/search-pattern-sparse-overlay.js +256 -0
  122. package/core/search/search-pattern.js +117 -29
  123. package/core/search/search-postprocess.js +479 -5
  124. package/core/search/search-read-semantic.js +277 -23
  125. package/core/search/search-read.js +82 -64
  126. package/core/search/search-reader-pin.js +71 -0
  127. package/core/search/search-rrf.js +279 -0
  128. package/core/search/search-semantic.js +110 -5
  129. package/core/search/search-server.js +273 -54
  130. package/core/search/search-trace.js +107 -0
  131. package/core/search/server-identity.js +93 -0
  132. package/core/search/session-daemon-prewarm.mjs +33 -10
  133. package/core/search/sweet-search.js +414 -9
  134. package/core/skills/sweet-index/SKILL.md +8 -6
  135. package/core/start-server.js +13 -2
  136. package/core/vector-store/binary-hnsw-index.js +194 -30
  137. package/core/vector-store/float-vector-store.js +96 -6
  138. package/core/vector-store/hnsw-index.js +220 -49
  139. package/eval/agent-read-workflows/bin/_ss-helpers.mjs +471 -0
  140. package/eval/agent-read-workflows/bin/ss-find +15 -0
  141. package/eval/agent-read-workflows/bin/ss-grep +12 -0
  142. package/eval/agent-read-workflows/bin/ss-read +14 -0
  143. package/eval/agent-read-workflows/bin/ss-search +18 -0
  144. package/eval/agent-read-workflows/bin/ss-semantic +12 -0
  145. package/eval/agent-read-workflows/bin/ss-trace +11 -0
  146. package/mcp/read-tool.js +109 -0
  147. package/mcp/server.js +55 -15
  148. package/mcp/tool-handlers.js +14 -124
  149. package/mcp/trace-tool.js +81 -0
  150. package/package.json +25 -10
  151. package/scripts/hooks/intercept-read.mjs +55 -0
  152. package/scripts/hooks/remind-tools.mjs +40 -0
  153. package/scripts/init.js +698 -54
  154. package/scripts/inject-agent-instructions.js +431 -0
  155. package/scripts/install-prompt-reminders.js +188 -0
  156. package/scripts/install-tool-enforcement.js +220 -0
  157. package/scripts/smoke-test.js +12 -9
  158. package/scripts/uninstall.js +427 -23
  159. package/scripts/write-claude-rules.js +110 -0
@@ -399,12 +399,13 @@ async function generateSummariesForEntities(entityIds, options = {}) {
399
399
  } = options;
400
400
 
401
401
  const Database = (await import('better-sqlite3')).default;
402
- const { applyReadPragmas } = await import('../infrastructure/db-utils.js');
402
+ const { applyReadPragmas, assertInClauseSize } = await import('../infrastructure/db-utils.js');
403
403
  const db = new Database(dbPath, { readonly: true });
404
404
  applyReadPragmas(db);
405
405
 
406
406
  try {
407
407
  // Fetch entities by IDs
408
+ assertInClauseSize(entityIds.length, 'hcgs-generator.fetchEntitiesByIds');
408
409
  const placeholders = entityIds.map(() => '?').join(',');
409
410
  const entities = db.prepare(`
410
411
  SELECT id, file_path, type, name, signature, doc_comment, parent_id, hierarchy_level
@@ -11,6 +11,8 @@ export { default as communityDetector } from './community-detector.js';
11
11
  export * from './leiden-algorithm.js';
12
12
  export * from './repo-map.js';
13
13
  export { default as repoMap } from './repo-map.js';
14
+ export * from './structural-context.js';
15
+ export { default as StructuralContextBuilder } from './structural-context.js';
14
16
  export * from './hcgs-generator.js';
15
17
  export * from './summary-manager.js';
16
18
  export { default as summaryManager } from './summary-manager.js';
@@ -17,6 +17,16 @@ import Database from 'better-sqlite3';
17
17
  import path from 'path';
18
18
  import { DB_PATHS } from '../infrastructure/config/index.js';
19
19
  import { applyReadPragmas } from '../infrastructure/db-utils.js';
20
+ import {
21
+ createCodeGraphVisibility,
22
+ entityVisibilityParams,
23
+ entityVisibilitySql,
24
+ readAdjacentManifest,
25
+ readAdjacentManifestEpoch,
26
+ relationshipVisibilityParams,
27
+ relationshipVisibilitySql,
28
+ resolveManifestCodeGraphPath,
29
+ } from '../infrastructure/code-graph-visibility.js';
20
30
 
21
31
  // ---------------------------------------------------------------------------
22
32
  // Constants
@@ -119,24 +129,32 @@ export function pageRank(outEdges, allNodes, opts = {}) {
119
129
  /**
120
130
  * Load graph data from the code-graph.db SQLite database.
121
131
  * @param {string} [dbPath] - Path to code-graph.db
132
+ * @param {{ manifestEpoch?: number }} [opts]
122
133
  * @returns {{ entities: Array, relationships: Array }}
123
134
  */
124
- export function loadGraph(dbPath) {
125
- const resolvedPath = dbPath || DB_PATHS.codeGraph;
135
+ export function loadGraph(dbPath, opts = {}) {
136
+ const basePath = dbPath || DB_PATHS.codeGraph;
137
+ const manifest = opts.manifest || readAdjacentManifest(basePath);
138
+ const resolvedPath = resolveManifestCodeGraphPath(basePath, manifest);
126
139
  const db = new Database(resolvedPath, { readonly: true, timeout: 5000 });
127
140
  applyReadPragmas(db);
128
141
 
129
142
  try {
143
+ const manifestEpoch = Number.isInteger(opts.manifestEpoch)
144
+ ? opts.manifestEpoch
145
+ : (Number.isInteger(manifest?.epoch) ? manifest.epoch : readAdjacentManifestEpoch(resolvedPath));
146
+ const visibility = createCodeGraphVisibility(db, manifestEpoch);
130
147
  const entities = db.prepare(`
131
148
  SELECT id, file_path, type, name, signature, start_line, end_line
132
149
  FROM entities
133
- WHERE stale_since IS NULL
134
- `).all();
150
+ WHERE ${entityVisibilitySql(visibility)}
151
+ `).all(...entityVisibilityParams(visibility));
135
152
 
136
153
  const relationships = db.prepare(`
137
154
  SELECT source_id, target_id, target_name, type, weight
138
155
  FROM relationships
139
- `).all();
156
+ WHERE ${relationshipVisibilitySql(visibility, '')}
157
+ `).all(...relationshipVisibilityParams(visibility));
140
158
 
141
159
  return { entities, relationships };
142
160
  } finally {
@@ -335,6 +353,7 @@ function buildMapText(entries) {
335
353
  * @param {string} [opts.dbPath] - Override code-graph.db path
336
354
  * @param {string[]} [opts.focusFiles] - Boost scores for entities in these files
337
355
  * @param {string[]} [opts.focusEntities] - Boost scores for these entity names
356
+ * @param {number} [opts.manifestEpoch] - Optional pinned manifest epoch for incremental readers
338
357
  * @returns {{ text: string, entityCount: number, fileCount: number, totalEntities: number, pageRankTimeMs: number }}
339
358
  */
340
359
  export function generateRepoMap(opts = {}) {
@@ -342,7 +361,10 @@ export function generateRepoMap(opts = {}) {
342
361
  const start = Date.now();
343
362
 
344
363
  // 1. Load graph from SQLite
345
- const graph = loadGraph(opts.dbPath);
364
+ const graph = loadGraph(opts.dbPath, {
365
+ manifest: opts.manifest,
366
+ manifestEpoch: opts.manifestEpoch,
367
+ });
346
368
 
347
369
  if (graph.entities.length === 0) {
348
370
  return {
@@ -0,0 +1,168 @@
1
+ import { stripNonCode } from './structural-callsite-hints.js';
2
+
3
+ const STOP = new Set([
4
+ 'const', 'let', 'var', 'return', 'function', 'export', 'import', 'from', 'null', 'true', 'false',
5
+ 'this', 'self', 'if', 'else', 'for', 'while', 'switch', 'case', 'break', 'continue', 'typeof',
6
+ 'string', 'number', 'boolean', 'undefined', 'object', 'class', 'struct', 'enum', 'trait', 'impl',
7
+ 'pub', 'fn', 'func', 'mut', 'use', 'type', 'async', 'await', 'new', 'def', 'len', 'none', 'the', 'that', 'this', 'with',
8
+ 'when', 'where', 'which', 'from', 'into', 'will', 'can', 'may', 'not', 'and', 'or',
9
+ ]);
10
+
11
+ function terms(text) {
12
+ const out = new Map();
13
+ for (const m of String(text || '').matchAll(/[A-Za-z_$][A-Za-z0-9_$]{2,}/g)) {
14
+ const raw = m[0];
15
+ const key = raw.toLowerCase();
16
+ if (STOP.has(key)) continue;
17
+ const prev = out.get(key) || { raw, count: 0, score: 0 };
18
+ prev.count++;
19
+ prev.score += /[A-Z_]/.test(raw.slice(1)) || /^[A-Z]/.test(raw) ? 2 : 1;
20
+ out.set(key, prev);
21
+ }
22
+ return out;
23
+ }
24
+
25
+ function rankedTerms(target, hint) {
26
+ const found = terms(`${target.signature || ''}\n${target.summary || ''}\n${stripNonCode(target.code || '')}`);
27
+ const hintText = String(hint || '').toLowerCase();
28
+ for (const [key, value] of found) {
29
+ if (hintText.includes(key)) value.score += 4;
30
+ value.score += Math.min(4, value.count);
31
+ }
32
+ return [...found.values()]
33
+ .sort((a, b) => (b.score - a.score) || a.raw.localeCompare(b.raw))
34
+ .slice(0, 24)
35
+ .map(x => x.raw);
36
+ }
37
+
38
+ function shortItem(x) {
39
+ const loc = x.filePath ? `${x.filePath}:${x.startLine || '?'}` : 'external';
40
+ const edge = x.relationship ? `${x.relationship} ` : '';
41
+ return `${edge}${x.name} (${loc})`;
42
+ }
43
+
44
+ function shortPath(p) {
45
+ return p.path.map(x => `${x.name}${x.filePath ? `@${x.filePath}:${x.startLine || '?'}` : '@external'}`).join(' -> ');
46
+ }
47
+
48
+ function isLowSignalPath(filePath = '') {
49
+ return /(^|\/)(__tests__|tests?|spec|fixtures|examples?|docs?)(\/|$)|[-_.](test|spec)\.[cm]?[jt]sx?$|_test\.go$/.test(filePath);
50
+ }
51
+
52
+ function preferProduction(items, isLowSignal) {
53
+ const primary = items.filter(item => !isLowSignal(item));
54
+ return primary.length ? primary : items;
55
+ }
56
+
57
+ function addSymbol(out, name) {
58
+ if (name && !out.includes(name)) out.push(name);
59
+ }
60
+
61
+ function keySymbols(target, hint, callers, callees, impactPaths) {
62
+ const q = String(hint || '').toLowerCase();
63
+ const out = [];
64
+ addSymbol(out, target.name);
65
+ const wantsCallers = /\b(caller|callers|upstream|references)\b/.test(q);
66
+ const wantsCallees = /\b(callee|callees|downstream|helper|helpers|conversion|next)\b/.test(q);
67
+ const wantsImpact = /\b(impact|changing|change|affect|break|handoff|surface)\b/.test(q);
68
+ if (wantsCallers || (!wantsCallees && !wantsImpact)) callers.slice(0, 3).forEach(item => addSymbol(out, item.name));
69
+ if (wantsCallees || (!wantsCallers && !wantsImpact)) callees.slice(0, 4).forEach(item => addSymbol(out, item.name));
70
+ if (wantsImpact) {
71
+ for (const path of impactPaths.slice(0, 4)) {
72
+ for (const item of path.path || []) {
73
+ if (item.type !== 'external') addSymbol(out, item.name);
74
+ }
75
+ }
76
+ }
77
+ return out.slice(0, 10);
78
+ }
79
+
80
+ const BRANCH_ORDER = [
81
+ 'tuple', 'dict', 'list', 'str', 'string', 'bytes', 'bytearray', 'buffer', 'stream', 'json',
82
+ 'response', 'status', 'headers', 'params', 'body', 'querystring', 'atomicbool', 'parallel',
83
+ ];
84
+ const BRANCH_WORDS = new Set(BRANCH_ORDER);
85
+
86
+ function branchTerms(targetTerms) {
87
+ const byLower = new Map();
88
+ for (const term of targetTerms) {
89
+ const lower = term.toLowerCase();
90
+ if (BRANCH_WORDS.has(lower)) {
91
+ byLower.set(lower, term);
92
+ continue;
93
+ }
94
+ for (const branch of BRANCH_ORDER) {
95
+ if (lower === `${branch}s` || lower.startsWith(`${branch}schema`)) {
96
+ byLower.set(branch, branch);
97
+ }
98
+ }
99
+ }
100
+ return BRANCH_ORDER.map(term => byLower.get(term)).filter(Boolean).slice(0, 12);
101
+ }
102
+
103
+ function branchSnippets(target, branches) {
104
+ if (!branches.length || !target.code) return [];
105
+ const wanted = branches.map(x => x.toLowerCase());
106
+ const lines = String(target.code).split('\n');
107
+ const out = [];
108
+ const covered = new Set();
109
+ let tripleQuote = null;
110
+ for (let i = 0; i < lines.length && out.length < 12; i++) {
111
+ const line = lines[i].trim();
112
+ const quote = line.includes('"""') ? '"""' : line.includes("'''") ? "'''" : null;
113
+ if (tripleQuote) {
114
+ if (quote === tripleQuote) tripleQuote = null;
115
+ continue;
116
+ }
117
+ if (quote) {
118
+ if ((line.match(new RegExp(quote, 'g')) || []).length % 2 === 1) tripleQuote = quote;
119
+ continue;
120
+ }
121
+ if (!line || /^(#|\/\/|\/\*|\*|`|:)/.test(line)) continue;
122
+ if (!/[()[\]{}=:]|\b(if|elif|else|case|switch|const|let|var|return)\b/.test(line)) continue;
123
+ const lower = line.toLowerCase();
124
+ const hits = wanted.filter(term => lower.includes(term) || lower.includes(`${term}schema`));
125
+ if (!hits.length || hits.every(term => covered.has(term))) continue;
126
+ const lineNo = (target.startLine || 1) + i;
127
+ out.push(`L${lineNo}: ${line.replace(/\s+/g, ' ')}`);
128
+ hits.forEach(term => covered.add(term));
129
+ if (covered.size >= wanted.length) break;
130
+ }
131
+ return out;
132
+ }
133
+
134
+ function relatedDefinitions(target, targetTerms, resolveTerm) {
135
+ if (!resolveTerm) return [];
136
+ const out = [];
137
+ const seen = new Set([target.id]);
138
+ for (const term of targetTerms.slice(0, 12)) {
139
+ const found = resolveTerm(term);
140
+ if (!found || seen.has(found.id) || found.filePath !== target.filePath) continue;
141
+ if (found.name === target.name && found.startLine === target.startLine) continue;
142
+ seen.add(found.id);
143
+ const snippet = found.summary && found.summary !== 'Same-file definition found from source text'
144
+ ? ` - ${found.summary}`
145
+ : '';
146
+ out.push(`${found.name} [${found.type}] ${found.filePath}:${found.startLine || '?'}${snippet}`);
147
+ if (out.length >= 6) break;
148
+ }
149
+ return out;
150
+ }
151
+
152
+ export function buildAnswerCues({ target, hint, callers, callees, impactPaths, resolveTerm }) {
153
+ const targetTerms = rankedTerms(target, hint);
154
+ const branches = branchTerms(targetTerms);
155
+ const cueCallers = preferProduction(callers, item => isLowSignalPath(item.filePath));
156
+ const cueCallees = preferProduction(callees, item => isLowSignalPath(item.filePath));
157
+ const cuePaths = preferProduction(impactPaths, path => (path.path || []).some(item => isLowSignalPath(item.filePath)));
158
+ return {
159
+ targetTerms,
160
+ keySymbols: keySymbols(target, hint, cueCallers, cueCallees, cuePaths),
161
+ branchTerms: branches,
162
+ branchSnippets: branchSnippets(target, branches),
163
+ relatedDefinitions: relatedDefinitions(target, targetTerms, resolveTerm),
164
+ topCallers: cueCallers.slice(0, 5).map(shortItem),
165
+ topCallees: cueCallees.slice(0, 5).map(shortItem),
166
+ criticalPaths: cuePaths.slice(0, 5).map(shortPath),
167
+ };
168
+ }
@@ -0,0 +1,40 @@
1
+ const SKIP = new Set([
2
+ 'if', 'for', 'while', 'switch', 'catch', 'function', 'func', 'return', 'defer', 'go', 'select',
3
+ 'len', 'cap', 'make', 'new', 'append', 'copy', 'delete', 'panic', 'recover',
4
+ 'setTimeout', 'clearTimeout', 'require', 'import', 'new', 'await', 'async',
5
+ ]);
6
+
7
+ const MEMBER_SKIP = new Set([
8
+ 'bind', 'call', 'apply', 'map', 'filter', 'reduce', 'forEach', 'then', 'catch',
9
+ 'toString', 'String', 'Error', 'Println', 'Printf', 'Errorf', 'Fatalf',
10
+ ]);
11
+
12
+ function addHint(out, known, name, member = false) {
13
+ if (!name || known.has(name) || out.includes(name)) return;
14
+ if (SKIP.has(name) || (member && MEMBER_SKIP.has(name))) return;
15
+ out.push(name);
16
+ }
17
+
18
+ export function stripNonCode(text) {
19
+ return String(text || '')
20
+ .replace(/("""|''')[\s\S]*?\1/g, '')
21
+ .replace(/(["'`])(?:\\.|(?!\1)[\s\S])*?\1/g, '')
22
+ .replace(/\/\*[\s\S]*?\*\//g, '')
23
+ .replace(/^\s*(#|\/\/).*$/gm, '');
24
+ }
25
+
26
+ export function callsiteHints(code, known = new Set()) {
27
+ const out = [];
28
+ const text = stripNonCode(code);
29
+ const free = /(?<![.\w$])([A-Za-z_$][\w$]*)\s*(?:\.(?:bind|call|apply))?\s*\(/g;
30
+ for (const m of text.matchAll(free)) {
31
+ addHint(out, known, m[1], false);
32
+ if (out.length >= 12) return out;
33
+ }
34
+ const member = /(?:\.|::)\s*([A-Za-z_$][\w$]*)\s*\(/g;
35
+ for (const m of text.matchAll(member)) {
36
+ addHint(out, known, m[1], true);
37
+ if (out.length >= 12) return out;
38
+ }
39
+ return out;
40
+ }
@@ -0,0 +1,40 @@
1
+ export function formatStructuralContext(result) {
2
+ if (!result.target) return `No indexed symbol found for "${result.symbol}".`;
3
+ const t = result.target;
4
+ const lines = [];
5
+ lines.push(`# trace ${t.name} [${t.type}] ${t.filePath}:${t.startLine}-${t.endLine}`);
6
+ lines.push(`fan-in=${t.fanIn} fan-out=${t.fanOut} budget=${result.tokensUsed}/${result.tokenBudget} (${result.budgetTier}:${result.budgetReason}) latency=${result.stats.latencyMs}ms`);
7
+ if (result.disambiguation.length) {
8
+ lines.push(`ambiguous: using first match; alternatives: ${result.disambiguation.slice(0, 5).map(a => `${a.name} ${a.file}:${a.startLine}`).join(', ')}`);
9
+ }
10
+ if (result.answerCues?.targetTerms?.length) {
11
+ if (result.answerCues.keySymbols?.length) lines.push(`answer checklist: key symbols=${result.answerCues.keySymbols.join(', ')}`);
12
+ if (result.answerCues.branchTerms?.length) lines.push(`answer checklist: branch terms to preserve=${result.answerCues.branchTerms.join(', ')}`);
13
+ if (result.answerCues.branchSnippets?.length) lines.push(`answer checklist: branch code=${result.answerCues.branchSnippets.join(' | ')}`);
14
+ if (result.answerCues.relatedDefinitions?.length) lines.push(`answer checklist: related definitions=${result.answerCues.relatedDefinitions.join(' | ')}`);
15
+ lines.push(`answer cues: target terms=${result.answerCues.targetTerms.join(', ')}`);
16
+ if (result.answerCues.topCallers.length) lines.push(`answer cues: top callers=${result.answerCues.topCallers.join(' | ')}`);
17
+ if (result.answerCues.topCallees.length) lines.push(`answer cues: top callees=${result.answerCues.topCallees.join(' | ')}`);
18
+ if (result.answerCues.criticalPaths.length) lines.push(`answer cues: critical paths=${result.answerCues.criticalPaths.join(' | ')}`);
19
+ }
20
+ if (t.headerContext) lines.push('\n## target imports\n```', t.headerContext, '```');
21
+ if (t.code) lines.push('\n## target\n```', t.code, '```');
22
+ if (t.callsiteHints?.length) lines.push(`target callsite hints: ${t.callsiteHints.join(', ')}`);
23
+ for (const [title, section] of [['callers', result.sections.callers], ['callees', result.sections.callees]]) {
24
+ lines.push(`\n## ${title} (${section.total})`);
25
+ for (const item of section.items) {
26
+ lines.push(`\n### ${item.name} [${item.type}] importance=${item.importance}`);
27
+ lines.push(item.summary);
28
+ if (item.code) lines.push('```', item.code, '```');
29
+ }
30
+ if (!section.items.length) lines.push('(none)');
31
+ }
32
+ lines.push(`\n## impact paths (${result.sections.impact.total}, depth <= ${result.maxDepth})`);
33
+ if (!result.sections.impact.paths.length) lines.push('(none)');
34
+ result.sections.impact.paths.forEach((p, i) => {
35
+ lines.push(`${i + 1}. ${p.direction} importance=${p.importance} ${p.path}`);
36
+ });
37
+ return lines.join('\n');
38
+ }
39
+
40
+ export default formatStructuralContext;