@mrclrchtr/supi-code-intelligence 4.1.0 → 4.3.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 (47) hide show
  1. package/README.md +4 -2
  2. package/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/package.json +1 -1
  3. package/node_modules/@mrclrchtr/supi-code-runtime/package.json +2 -2
  4. package/node_modules/@mrclrchtr/supi-core/package.json +1 -1
  5. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/package.json +1 -1
  6. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-code-runtime/package.json +2 -2
  7. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/package.json +1 -1
  8. package/node_modules/@mrclrchtr/supi-lsp/package.json +3 -3
  9. package/node_modules/@mrclrchtr/supi-lsp/src/utils.ts +2 -2
  10. package/node_modules/@mrclrchtr/supi-tree-sitter/README.md +5 -5
  11. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/package.json +1 -1
  12. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-code-runtime/package.json +2 -2
  13. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/package.json +1 -1
  14. package/node_modules/@mrclrchtr/supi-tree-sitter/package.json +3 -3
  15. package/node_modules/@mrclrchtr/supi-tree-sitter/src/language.ts +2 -1
  16. package/node_modules/@mrclrchtr/supi-tree-sitter/src/operation-support.ts +19 -0
  17. package/node_modules/@mrclrchtr/supi-tree-sitter/src/syntax-node.ts +1 -0
  18. package/node_modules/@mrclrchtr/supi-tree-sitter/src/tool/outline-c-family.ts +230 -0
  19. package/node_modules/@mrclrchtr/supi-tree-sitter/src/tool/outline-html-sql.ts +186 -0
  20. package/node_modules/@mrclrchtr/supi-tree-sitter/src/tool/outline-jvm.ts +311 -0
  21. package/node_modules/@mrclrchtr/supi-tree-sitter/src/tool/outline-polyglot.ts +218 -0
  22. package/node_modules/@mrclrchtr/supi-tree-sitter/src/tool/outline-scripting.ts +307 -0
  23. package/node_modules/@mrclrchtr/supi-tree-sitter/src/tool/outline.ts +9 -5
  24. package/node_modules/@mrclrchtr/supi-tree-sitter/src/types.ts +6 -2
  25. package/package.json +5 -5
  26. package/src/analysis/search/pattern.ts +2 -2
  27. package/src/analysis/target/symbol.ts +5 -2
  28. package/src/extension.ts +5 -1
  29. package/src/session/orientation/collect.ts +134 -180
  30. package/src/session/orientation/context-sections.ts +14 -48
  31. package/src/session/orientation-types.ts +14 -4
  32. package/src/session/orientation-workflow.ts +20 -48
  33. package/src/substrate/lsp/lifecycle.ts +55 -3
  34. package/src/substrate/lsp/state.ts +6 -0
  35. package/src/tool/graph/execute.ts +1 -1
  36. package/src/tool/graph/markdown.ts +222 -0
  37. package/src/tool/orientation/markdown.ts +66 -11
  38. package/src/tool/register.ts +10 -0
  39. package/src/ui/footer.ts +33 -8
  40. package/src/ui/markdown/overview-data.ts +3 -15
  41. package/src/ui/markdown/overview.ts +4 -10
  42. package/src/ui/markdown/types.ts +0 -1
  43. package/src/tool/graph/calls-md.ts +0 -41
  44. package/src/tool/graph/implementations-md.ts +0 -57
  45. package/src/tool/graph/markdown-base.ts +0 -88
  46. package/src/tool/graph/provider-location-md.ts +0 -9
  47. package/src/tool/graph/references-md.ts +0 -41
@@ -23,22 +23,14 @@ import type {
23
23
  OrientationTarget,
24
24
  } from "../../ui/markdown/types.ts";
25
25
  import type {
26
- OrientationBlock,
26
+ OrientationItem,
27
27
  OrientationResultData,
28
28
  OrientationSectionData,
29
29
  } from "../orientation-types.ts";
30
30
  import { collectContextOrientationFacts } from "./context-facts.ts";
31
- import { formatSectionNote } from "./context-sections.ts";
32
-
33
- interface CollectedOrientationSection {
34
- readonly key: OrientationSection;
35
- readonly title: string;
36
- readonly lines: readonly string[];
37
- readonly metadata: OrientationSectionData;
38
- }
39
31
 
40
32
  interface TargetSectionCollection {
41
- readonly lines: string[];
33
+ readonly items: OrientationItem[];
42
34
  readonly hasStructuralEvidence: boolean;
43
35
  readonly hasSemanticEvidence: boolean;
44
36
  readonly status: "complete" | "partial" | "unavailable";
@@ -89,7 +81,7 @@ async function executeTargetOrientation(
89
81
  const requestedSections = DEFAULT_TARGET_SECTIONS;
90
82
  const limit = input.maxResults ?? 10;
91
83
  const focusTarget = input.target ? formatFocusTarget(input.target, deps.cwd) : null;
92
- const sections: CollectedOrientationSection[] = [];
84
+ const sections: OrientationSectionData[] = [];
93
85
 
94
86
  let hasStructural = false;
95
87
  let hasSemantic = false;
@@ -115,16 +107,16 @@ async function executeTargetOrientation(
115
107
  : "unavailable";
116
108
 
117
109
  return {
118
- blocks: buildTargetBlocks(focusTarget, sections),
119
- sections: sections.map((section) => section.metadata),
110
+ title: "Code Orientation",
111
+ notes: [],
112
+ sections,
120
113
  confidence,
121
114
  focusTarget,
122
115
  requestedSections,
123
116
  renderedSections: sections.map((section) => section.key),
124
117
  omittedCount: sections.reduce(
125
118
  (total, section) =>
126
- total +
127
- section.metadata.evidenceLists.reduce((sum, list) => sum + (list.omittedCount ?? 0), 0),
119
+ total + section.evidenceLists.reduce((sum, list) => sum + (list.omittedCount ?? 0), 0),
128
120
  0,
129
121
  ),
130
122
  nextQueries: buildNextQueries(input.target, deps.cwd),
@@ -139,7 +131,7 @@ async function buildRequestedSection(options: {
139
131
  limit: number;
140
132
  treeContext: Awaited<ReturnType<typeof maybeGatherTreeContext>>;
141
133
  }): Promise<{
142
- section: CollectedOrientationSection;
134
+ section: OrientationSectionData;
143
135
  hasStructuralEvidence: boolean;
144
136
  hasSemanticEvidence: boolean;
145
137
  }> {
@@ -172,22 +164,22 @@ async function buildEnrichedDefsSection(
172
164
  treeContext: Awaited<ReturnType<typeof maybeGatherTreeContext>>,
173
165
  limit: number,
174
166
  ): Promise<TargetSectionCollection> {
175
- const lines = buildDefinitionLines(target, deps.cwd, treeContext);
167
+ const items = buildDefinitionItems(target, deps.cwd, treeContext);
176
168
  const contextHasSemanticEvidence = Boolean(
177
169
  treeContext?.hover || (treeContext?.definition?.length ?? 0) > 0,
178
170
  );
179
- const hasStructuralEvidence = hasRenderableItems(lines);
171
+ const hasStructuralEvidence = hasRenderableItems(items);
180
172
 
181
173
  if (!target) {
182
174
  return unavailableTargetSection(
183
- lines,
175
+ items,
184
176
  hasStructuralEvidence,
185
177
  "Definitions require a precise target.",
186
178
  );
187
179
  }
188
180
  if (deps.lspRuntime.kind !== "ready") {
189
181
  return {
190
- lines,
182
+ items,
191
183
  hasStructuralEvidence,
192
184
  hasSemanticEvidence: contextHasSemanticEvidence,
193
185
  status: contextHasSemanticEvidence ? "partial" : "unavailable",
@@ -197,13 +189,13 @@ async function buildEnrichedDefsSection(
197
189
  }
198
190
 
199
191
  const definitions = await collectDefinitionTargets(target, deps, limit);
200
- if (definitions.lines.length > 0) {
201
- if (lines.length > 0) lines.push("");
202
- lines.push("**Definition:**", ...definitions.lines);
192
+ if (definitions.items.length > 0) {
193
+ if (items.length > 0) items.push({ kind: "blank" });
194
+ items.push({ kind: "paragraph", text: "**Definition:**" }, ...definitions.items);
203
195
  }
204
196
  const hasSemanticEvidence = contextHasSemanticEvidence || definitions.hasSemanticEvidence;
205
197
  return {
206
- lines,
198
+ items,
207
199
  hasStructuralEvidence,
208
200
  hasSemanticEvidence,
209
201
  status:
@@ -220,12 +212,12 @@ async function collectDefinitionTargets(
220
212
  ): Promise<
221
213
  Pick<
222
214
  TargetSectionCollection,
223
- "lines" | "hasSemanticEvidence" | "status" | "reason" | "evidenceLists"
215
+ "items" | "hasSemanticEvidence" | "status" | "reason" | "evidenceLists"
224
216
  >
225
217
  > {
226
218
  if (!deps.provider?.definition) {
227
219
  return {
228
- lines: [],
220
+ items: [],
229
221
  hasSemanticEvidence: false,
230
222
  status: "unavailable",
231
223
  reason: "Definition provider unavailable.",
@@ -239,7 +231,7 @@ async function collectDefinitionTargets(
239
231
  });
240
232
  if (result.kind === "unavailable") {
241
233
  return {
242
- lines: [],
234
+ items: [],
243
235
  hasSemanticEvidence: false,
244
236
  status: "unavailable",
245
237
  reason: result.reason,
@@ -265,7 +257,7 @@ async function collectDefinitionTargets(
265
257
  maxResults: limit,
266
258
  });
267
259
  return {
268
- lines: evidence.items,
260
+ items: evidence.items.map((location) => ({ kind: "paragraph", text: location })),
269
261
  hasSemanticEvidence: true,
270
262
  status: result.kind === "partial" ? "partial" : "complete",
271
263
  reason: result.kind === "partial" ? result.reason : null,
@@ -273,7 +265,7 @@ async function collectDefinitionTargets(
273
265
  };
274
266
  } catch (error) {
275
267
  return {
276
- lines: [],
268
+ items: [],
277
269
  hasSemanticEvidence: false,
278
270
  status: "unavailable",
279
271
  reason: `Definition provider failed: ${String(error)}`,
@@ -293,7 +285,10 @@ async function buildDiagnosticsSection(
293
285
  if (deps.lspRuntime.kind !== "ready") {
294
286
  return unavailableTargetSection(
295
287
  [
296
- "LSP not available — diagnostics require a live language server. Use `code_health` to check server status.",
288
+ {
289
+ kind: "paragraph",
290
+ text: "LSP not available — diagnostics require a live language server. Use `code_health` to check server status.",
291
+ },
297
292
  ],
298
293
  false,
299
294
  "Diagnostics require a live language server.",
@@ -305,7 +300,7 @@ async function buildDiagnosticsSection(
305
300
  const result = await deps.lspRuntime.runtime.fileDiagnostics(targetFile, 4);
306
301
  if (result.kind === "unavailable") {
307
302
  return unavailableTargetSection(
308
- [`Diagnostics unavailable for this target — ${result.reason}`],
303
+ [{ kind: "paragraph", text: `Diagnostics unavailable for this target — ${result.reason}` }],
309
304
  false,
310
305
  result.reason,
311
306
  );
@@ -327,10 +322,10 @@ async function buildDiagnosticsSection(
327
322
  maxResults: limit,
328
323
  });
329
324
  return {
330
- lines:
325
+ items:
331
326
  evidence.items.length === 0
332
- ? ["No diagnostics found near this target."]
333
- : evidence.items.map((diagnostic) => formatDiagnostic(diagnostic)),
327
+ ? [{ kind: "paragraph", text: "No diagnostics found near this target." }]
328
+ : evidence.items.map((diagnostic) => formatDiagnosticItem(diagnostic)),
334
329
  hasStructuralEvidence: false,
335
330
  hasSemanticEvidence: true,
336
331
  status: result.kind === "partial" ? "partial" : "complete",
@@ -339,24 +334,27 @@ async function buildDiagnosticsSection(
339
334
  };
340
335
  } catch (error) {
341
336
  return unavailableTargetSection(
342
- ["Diagnostics failed to load."],
337
+ [{ kind: "paragraph", text: "Diagnostics failed to load." }],
343
338
  false,
344
339
  `Diagnostics failed to load: ${String(error)}`,
345
340
  );
346
341
  }
347
342
  }
348
343
 
349
- function formatDiagnostic(diagnostic: Diagnostic): string {
344
+ function formatDiagnosticItem(diagnostic: Diagnostic): OrientationItem {
350
345
  const severity = (diagnostic.severity ?? 1) === 1 ? "ERROR" : "WARN";
351
- return `- **${severity}** (L${(diagnostic.range.start.line ?? 0) + 1}): ${diagnosticMessageString(diagnostic)}`;
346
+ return {
347
+ kind: "list-item",
348
+ text: `**${severity}** (L${(diagnostic.range.start.line ?? 0) + 1}): ${diagnosticMessageString(diagnostic)}`,
349
+ };
352
350
  }
353
351
 
354
352
  function completedDocsSection(
355
- lines: string[],
353
+ items: OrientationItem[],
356
354
  evidenceLists: readonly EvidenceListMetadata[],
357
355
  ): TargetSectionCollection {
358
356
  return {
359
- lines,
357
+ items,
360
358
  hasStructuralEvidence: false,
361
359
  hasSemanticEvidence: false,
362
360
  status: "complete",
@@ -366,12 +364,12 @@ function completedDocsSection(
366
364
  }
367
365
 
368
366
  function unavailableTargetSection(
369
- lines: string[],
367
+ items: OrientationItem[],
370
368
  hasStructuralEvidence: boolean,
371
369
  reason: string,
372
370
  ): TargetSectionCollection {
373
371
  return {
374
- lines,
372
+ items,
375
373
  hasStructuralEvidence,
376
374
  hasSemanticEvidence: false,
377
375
  status: "unavailable",
@@ -388,7 +386,7 @@ async function buildDocsSection(
388
386
  ): Promise<TargetSectionCollection> {
389
387
  if (!target) {
390
388
  return unavailableTargetSection(
391
- ["Docs unavailable without a precise target."],
389
+ [{ kind: "paragraph", text: "Docs unavailable without a precise target." }],
392
390
  false,
393
391
  "Docs require a precise target.",
394
392
  );
@@ -397,7 +395,7 @@ async function buildDocsSection(
397
395
  const targetFile = path.resolve(deps.cwd, target.file);
398
396
  if (!existsSync(targetFile)) {
399
397
  return unavailableTargetSection(
400
- ["Docs unavailable — target file not found."],
398
+ [{ kind: "paragraph", text: "Docs unavailable — target file not found." }],
401
399
  false,
402
400
  "Target file not found.",
403
401
  );
@@ -443,7 +441,10 @@ async function buildDocsSection(
443
441
  }
444
442
 
445
443
  if (jsdocStart === -1 || jsdocEnd === -1) {
446
- return completedDocsSection(["No JSDoc/TSDoc comment found for this symbol."], []);
444
+ return completedDocsSection(
445
+ [{ kind: "paragraph", text: "No JSDoc/TSDoc comment found for this symbol." }],
446
+ [],
447
+ );
447
448
  }
448
449
 
449
450
  const docLines = lines
@@ -457,7 +458,10 @@ async function buildDocsSection(
457
458
  .filter((line) => line.trim() !== "");
458
459
 
459
460
  if (docLines.length === 0) {
460
- return completedDocsSection(["No JSDoc/TSDoc comment found for this symbol."], []);
461
+ return completedDocsSection(
462
+ [{ kind: "paragraph", text: "No JSDoc/TSDoc comment found for this symbol." }],
463
+ [],
464
+ );
461
465
  }
462
466
 
463
467
  const evidence = createEvidenceList({
@@ -465,10 +469,13 @@ async function buildDocsSection(
465
469
  items: docLines,
466
470
  maxResults: limit,
467
471
  });
468
- return completedDocsSection(["```ts", ...evidence.items, "```"], [evidence.metadata]);
472
+ return completedDocsSection(
473
+ [{ kind: "code", language: "ts", lines: evidence.items }],
474
+ [evidence.metadata],
475
+ );
469
476
  } catch (error) {
470
477
  return unavailableTargetSection(
471
- ["Docs extraction failed."],
478
+ [{ kind: "paragraph", text: "Docs extraction failed." }],
472
479
  false,
473
480
  `Docs extraction failed: ${String(error)}`,
474
481
  );
@@ -484,66 +491,106 @@ async function maybeGatherTreeContext(
484
491
  return gatherTreeSitterContext(deps.provider, relPath, target.line, target.character);
485
492
  }
486
493
 
487
- function buildDefinitionLines(
494
+ function buildDefinitionItems(
488
495
  target: OrientationTarget | null | undefined,
489
496
  cwd: string,
490
497
  treeContext: Awaited<ReturnType<typeof maybeGatherTreeContext>>,
491
- ): string[] {
492
- if (!target) return ["No precise target context found."];
498
+ ): OrientationItem[] {
499
+ if (!target) return [{ kind: "paragraph", text: "No precise target context found." }];
493
500
 
494
- const lines = [`- Focus: \`${formatFocusTarget(target, cwd)}\``];
501
+ const items: OrientationItem[] = [
502
+ { kind: "list-item", text: `Focus: \`${formatFocusTarget(target, cwd)}\`` },
503
+ ];
495
504
  if (target.name) {
496
- lines.push(`- Symbol: \`${target.name}\`${target.kind ? ` (${target.kind})` : ""}`);
505
+ items.push({
506
+ kind: "list-item",
507
+ text: `Symbol: \`${target.name}\`${target.kind ? ` (${target.kind})` : ""}`,
508
+ });
497
509
  }
498
510
  if (treeContext?.nodeInfo?.type) {
499
- lines.push(`- Node: \`${treeContext.nodeInfo.type}\``);
511
+ items.push({ kind: "list-item", text: `Node: \`${treeContext.nodeInfo.type}\`` });
500
512
  }
501
513
  if (treeContext?.hover?.contents) {
502
- lines.push(...formatHoverLine(treeContext.hover.contents));
514
+ items.push(...hoverItems(treeContext.hover.contents));
503
515
  }
504
- return lines;
516
+ return items;
505
517
  }
506
518
 
507
- function formatHoverLine(contents: string): string[] {
508
- const trimmed = contents.trim();
509
- const maxHoverChars = 600;
510
- if (trimmed.startsWith("```")) {
511
- return formatFencedHover(trimmed, maxHoverChars);
512
- }
513
- if (trimmed.length <= maxHoverChars) return [`- Hover: ${trimmed}`];
519
+ const MAX_HOVER_CHARS = 600;
520
+ const HOVER_TRUNCATED_NOTE = "_(truncated, use `code_inspect` for full type)_";
514
521
 
515
- const hoverLines = trimmed.split("\n");
516
- if (hoverLines.length === 1) {
522
+ function hoverItems(contents: string): OrientationItem[] {
523
+ const trimmed = contents.trim();
524
+ if (!trimmed.startsWith("```")) {
525
+ if (trimmed.length <= MAX_HOVER_CHARS)
526
+ return [{ kind: "list-item", text: `Hover: ${trimmed}` }];
517
527
  return [
518
- `- Hover: ${trimmed.slice(0, maxHoverChars)}...`,
519
- " _(truncated, use `code_inspect` for full type)_",
528
+ { kind: "list-item", text: `Hover: ${clipHoverText(trimmed)}...` },
529
+ { kind: "paragraph", text: HOVER_TRUNCATED_NOTE },
520
530
  ];
521
531
  }
532
+ const { lines, truncated } = clipHoverLines(trimmed);
533
+ const items: OrientationItem[] = [
534
+ { kind: "list-item", text: "Hover:" },
535
+ ...hoverBodyItems(lines),
536
+ ];
537
+ if (truncated) items.push({ kind: "paragraph", text: HOVER_TRUNCATED_NOTE });
538
+ return items;
539
+ }
522
540
 
541
+ function clipHoverText(trimmed: string): string {
542
+ const lines = trimmed.split("\n");
543
+ if (lines.length === 1) return trimmed.slice(0, MAX_HOVER_CHARS);
523
544
  let acc = "";
524
- for (const line of hoverLines) {
525
- if (acc.length + line.length + 1 > maxHoverChars && acc.length > 0) break;
545
+ for (const line of lines) {
546
+ if (acc.length + line.length + 1 > MAX_HOVER_CHARS && acc.length > 0) break;
526
547
  acc += (acc ? "\n" : "") + line;
527
548
  }
528
- return [`- Hover: ${acc}`, " _(truncated, use `code_inspect` for full type)_"];
549
+ return acc;
529
550
  }
530
551
 
531
- /** Keep provider-supplied Markdown fences at block boundaries understood by sectionBlocks(). */
532
- function formatFencedHover(contents: string, maxChars: number): string[] {
552
+ function clipHoverLines(contents: string): { lines: string[]; truncated: boolean } {
533
553
  const sourceLines = contents.split("\n");
534
- if (contents.length <= maxChars) return ["- Hover:", ...sourceLines];
535
-
554
+ if (contents.length <= MAX_HOVER_CHARS) return { lines: sourceLines, truncated: false };
536
555
  const lines = [sourceLines[0]];
537
556
  let length = sourceLines[0].length;
538
- let closed = false;
539
557
  for (const line of sourceLines.slice(1)) {
540
- if (length + line.length + 1 > maxChars) break;
558
+ if (length + line.length + 1 > MAX_HOVER_CHARS) break;
541
559
  lines.push(line);
542
560
  length += line.length + 1;
543
- if (line.startsWith("```")) closed = true;
544
561
  }
545
- if (!closed) lines.push("```");
546
- return ["- Hover:", ...lines, "_(truncated, use `code_inspect` for full type)_"];
562
+ return { lines, truncated: true };
563
+ }
564
+
565
+ /** Parse provider hover Markdown (fenced code + prose) into rendered items. */
566
+ function hoverBodyItems(lines: readonly string[]): OrientationItem[] {
567
+ const items: OrientationItem[] = [];
568
+ let codeLanguage: string | null = null;
569
+ let codeLines: string[] = [];
570
+ const closeCode = (): void => {
571
+ if (codeLanguage === null) return;
572
+ items.push({
573
+ kind: "code",
574
+ language: codeLanguage.length > 0 ? codeLanguage : null,
575
+ lines: codeLines,
576
+ });
577
+ codeLanguage = null;
578
+ codeLines = [];
579
+ };
580
+ for (const line of lines) {
581
+ if (line.startsWith("```")) {
582
+ if (codeLanguage === null) codeLanguage = line.slice(3).trim();
583
+ else closeCode();
584
+ continue;
585
+ }
586
+ if (codeLanguage !== null) {
587
+ codeLines.push(line);
588
+ continue;
589
+ }
590
+ items.push(line.trim().length === 0 ? { kind: "blank" } : { kind: "paragraph", text: line });
591
+ }
592
+ closeCode();
593
+ return items;
547
594
  }
548
595
 
549
596
  function formatFocusTarget(target: OrientationTarget, cwd: string): string {
@@ -595,7 +642,7 @@ function targetSectionResult(
595
642
  collection: TargetSectionCollection,
596
643
  provenance: OrientationSectionData["provenance"],
597
644
  ): {
598
- section: CollectedOrientationSection;
645
+ section: OrientationSectionData;
599
646
  hasStructuralEvidence: boolean;
600
647
  hasSemanticEvidence: boolean;
601
648
  } {
@@ -608,111 +655,18 @@ function targetSectionResult(
608
655
  section: {
609
656
  key,
610
657
  title: SECTION_TITLES[key],
611
- lines: collection.lines,
612
- metadata: {
613
- key,
614
- title: SECTION_TITLES[key],
615
- status: collection.status,
616
- reason: collection.reason,
617
- confidence,
618
- provenance,
619
- evidenceLists: collection.evidenceLists,
620
- },
658
+ status: collection.status,
659
+ reason: collection.reason,
660
+ confidence,
661
+ provenance,
662
+ evidenceLists: collection.evidenceLists,
663
+ items: collection.items,
621
664
  },
622
665
  hasStructuralEvidence: collection.hasStructuralEvidence,
623
666
  hasSemanticEvidence: collection.hasSemanticEvidence,
624
667
  };
625
668
  }
626
669
 
627
- function buildTargetBlocks(
628
- focusTarget: string | null,
629
- sections: readonly CollectedOrientationSection[],
630
- ): OrientationBlock[] {
631
- const blocks: OrientationBlock[] = [
632
- { kind: "heading", level: 1, text: "Code Orientation" },
633
- { kind: "blank" },
634
- ];
635
- if (focusTarget) {
636
- blocks.push(
637
- { kind: "heading", level: 2, text: "Focus" },
638
- { kind: "list-item", text: `\`${focusTarget}\`` },
639
- { kind: "blank" },
640
- );
641
- }
642
- for (const section of sections) {
643
- blocks.push(
644
- { kind: "heading", level: 2, text: section.title },
645
- { kind: "paragraph", text: formatSectionNote(section.metadata) },
646
- );
647
- blocks.push(...sectionBlocks(section.lines.join("\n")), { kind: "blank" });
648
- }
649
- return blocks;
650
- }
651
-
652
- interface SectionBlockState {
653
- blocks: OrientationBlock[];
654
- codeLanguage: string | null;
655
- codeLines: string[];
656
- }
657
-
658
- /** Convert target-section fragments into typed blocks. Context Orientation never crosses a markdown seam. */
659
- function sectionBlocks(document: string): OrientationBlock[] {
660
- const state: SectionBlockState = { blocks: [], codeLanguage: null, codeLines: [] };
661
- for (const line of document.split("\n")) appendSectionLine(state, line);
662
- closeCodeBlock(state);
663
- return state.blocks;
664
- }
665
-
666
- function appendSectionLine(state: SectionBlockState, line: string): void {
667
- if (line.startsWith("```")) {
668
- toggleCodeBlock(state, line.slice(3).trim());
669
- return;
670
- }
671
- if (state.codeLanguage !== null) {
672
- state.codeLines.push(line);
673
- return;
674
- }
675
- appendProseBlock(state.blocks, line);
676
- }
677
-
678
- function toggleCodeBlock(state: SectionBlockState, language: string): void {
679
- if (state.codeLanguage === null) {
680
- state.codeLanguage = language;
681
- return;
682
- }
683
- closeCodeBlock(state);
684
- }
685
-
686
- function closeCodeBlock(state: SectionBlockState): void {
687
- if (state.codeLanguage === null) return;
688
- state.blocks.push({
689
- kind: "code",
690
- language: state.codeLanguage.length > 0 ? state.codeLanguage : null,
691
- lines: state.codeLines,
692
- });
693
- state.codeLanguage = null;
694
- state.codeLines = [];
695
- }
696
-
697
- function appendProseBlock(blocks: OrientationBlock[], line: string): void {
698
- const heading = /^(#{1,3})\s+(.*)$/.exec(line);
699
- if (heading) {
700
- blocks.push({
701
- kind: "heading",
702
- level: heading[1].length as 1 | 2 | 3,
703
- text: heading[2],
704
- });
705
- return;
706
- }
707
- if (line.startsWith("- ")) {
708
- blocks.push({ kind: "list-item", text: line.slice(2) });
709
- } else if (line.trim().length === 0) {
710
- blocks.push({ kind: "blank" });
711
- } else {
712
- blocks.push({ kind: "paragraph", text: line });
713
- }
714
- }
715
-
716
- function hasRenderableItems(lines: string[]): boolean {
717
- return lines.some((line) => line.trim().startsWith("- "));
670
+ function hasRenderableItems(items: readonly OrientationItem[]): boolean {
671
+ return items.some((item) => item.kind === "list-item");
718
672
  }
@@ -7,7 +7,6 @@ import type { EvidenceListMetadata, EvidencePartialReason } from "../../analysis
7
7
  import { createEvidenceList } from "../../analysis/evidence.ts";
8
8
  import type { CodeProvider } from "../../analysis/provider.ts";
9
9
  import type {
10
- OrientationBlock,
11
10
  OrientationProvenance,
12
11
  OrientationResultData,
13
12
  OrientationSectionData,
@@ -15,8 +14,9 @@ import type {
15
14
 
16
15
  /** Mutable collection state kept private to context-Oriented fact assembly. */
17
16
  export interface ContextOrientationBuilder {
18
- readonly blocks: OrientationBlock[];
17
+ readonly title: string;
19
18
  readonly sections: OrientationSectionData[];
19
+ readonly notes: string[];
20
20
  readonly maxResults: number;
21
21
  }
22
22
 
@@ -34,16 +34,17 @@ export interface ListSectionInput<T> {
34
34
  readonly partialReason?: EvidencePartialReason;
35
35
  }
36
36
 
37
- /** Start a context-Oriented document with one factual focus heading. */
37
+ /** Start a context-Oriented document with one factual focus title. */
38
38
  export function createBuilder(maxResults: number, title: string): ContextOrientationBuilder {
39
39
  return {
40
- blocks: [{ kind: "heading", level: 1, text: title }, { kind: "blank" }],
40
+ title,
41
41
  sections: [],
42
+ notes: [],
42
43
  maxResults,
43
44
  };
44
45
  }
45
46
 
46
- /** Add one bounded evidence list and matching Markdown/structured metadata. */
47
+ /** Add one bounded evidence list and matching section facts. */
47
48
  export function appendList<T>(
48
49
  builder: ContextOrientationBuilder,
49
50
  input: ListSectionInput<T>,
@@ -60,7 +61,7 @@ export function appendList<T>(
60
61
  partialReason: input.partialReason ?? "filesystem-error",
61
62
  }
62
63
  : list.metadata;
63
- const section: OrientationSectionData = {
64
+ builder.sections.push({
64
65
  key: input.key,
65
66
  title: input.title,
66
67
  status: input.status ?? "complete",
@@ -68,16 +69,8 @@ export function appendList<T>(
68
69
  confidence: input.confidence,
69
70
  provenance: input.provenance,
70
71
  evidenceLists: [{ ...metadata, key: input.key }],
71
- };
72
- builder.sections.push(section);
73
- builder.blocks.push(
74
- { kind: "heading", level: 2, text: input.title },
75
- { kind: "paragraph", text: formatSectionNote(section) },
76
- );
77
- for (const item of list.items) {
78
- builder.blocks.push({ kind: "list-item", text: input.render(item) });
79
- }
80
- builder.blocks.push({ kind: "blank" });
72
+ items: list.items.map((item) => ({ kind: "list-item", text: input.render(item) })),
73
+ });
81
74
  }
82
75
 
83
76
  /** Add an unavailable section without manufacturing an absence claim. */
@@ -90,7 +83,7 @@ export function appendUnavailable(
90
83
  provenance: readonly OrientationProvenance[];
91
84
  },
92
85
  ): void {
93
- const section: OrientationSectionData = {
86
+ builder.sections.push({
94
87
  key: input.key,
95
88
  title: input.title,
96
89
  status: "unavailable",
@@ -98,13 +91,8 @@ export function appendUnavailable(
98
91
  confidence: "unavailable",
99
92
  provenance: input.provenance,
100
93
  evidenceLists: [],
101
- };
102
- builder.sections.push(section);
103
- builder.blocks.push(
104
- { kind: "heading", level: 2, text: input.title },
105
- { kind: "paragraph", text: formatSectionNote(section) },
106
- { kind: "blank" },
107
- );
94
+ items: [],
95
+ });
108
96
  }
109
97
 
110
98
  /** Collect direct regular-file and directory names from one filesystem level. */
@@ -294,7 +282,8 @@ export function resultData(
294
282
  0,
295
283
  );
296
284
  return {
297
- blocks: builder.blocks,
285
+ title: builder.title,
286
+ notes: builder.notes,
298
287
  sections: builder.sections,
299
288
  confidence: highestConfidence(builder.sections),
300
289
  focusTarget: focus,
@@ -322,29 +311,6 @@ export function formatManifestValue(value: unknown): string {
322
311
  return `\`${serialized ?? String(value)}\``;
323
312
  }
324
313
 
325
- /** Render the same section status/provenance metadata stored in result details. */
326
- export function formatSectionNote(section: OrientationSectionData): string {
327
- const facts = section.evidenceLists.map(evidenceSummary).join("; ");
328
- const provenance = section.provenance.map(formatProvenance).join(", ");
329
- return `_(status: ${section.status}; provenance: ${provenance || "none"}${section.reason ? `; ${section.reason}` : ""}${facts ? `; ${facts}` : ""})_`;
330
- }
331
-
332
- function evidenceSummary(metadata: EvidenceListMetadata): string {
333
- if (metadata.totalCount === null) {
334
- const omitted = metadata.omittedCount ? `; ${metadata.omittedCount} collected omitted` : "";
335
- return `showing ${metadata.shownCount}${omitted}; more may exist — ${metadata.partialReason ?? "partial"}`;
336
- }
337
- if ((metadata.omittedCount ?? 0) > 0) {
338
- return `showing ${metadata.shownCount} of ${metadata.totalCount}; ${metadata.omittedCount} omitted`;
339
- }
340
- return `${metadata.totalCount} observed`;
341
- }
342
-
343
- function formatProvenance(provenance: OrientationProvenance): string {
344
- const detail = provenance.detail ? ` \`${provenance.detail}\`` : "";
345
- return `${provenance.source}${provenance.capability ? ` (${provenance.capability})` : ""}${detail}`;
346
- }
347
-
348
314
  function highestConfidence(sections: readonly OrientationSectionData[]): ConfidenceMode {
349
315
  if (sections.some((section) => section.confidence === "semantic")) return "semantic";
350
316
  if (sections.some((section) => section.confidence === "structural")) return "structural";