mustflow 2.75.1 → 2.84.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 (41) hide show
  1. package/README.md +31 -3
  2. package/dist/cli/commands/docs.js +86 -2
  3. package/dist/cli/commands/script-pack.js +5 -0
  4. package/dist/cli/i18n/en.js +101 -2
  5. package/dist/cli/i18n/es.js +101 -2
  6. package/dist/cli/i18n/fr.js +101 -2
  7. package/dist/cli/i18n/hi.js +101 -2
  8. package/dist/cli/i18n/ko.js +101 -2
  9. package/dist/cli/i18n/zh.js +101 -2
  10. package/dist/cli/lib/script-pack-registry.js +162 -7
  11. package/dist/cli/script-packs/code-export-diff.js +160 -0
  12. package/dist/cli/script-packs/code-outline.js +33 -5
  13. package/dist/cli/script-packs/code-route-outline.js +155 -0
  14. package/dist/cli/script-packs/docs-reference-drift.js +150 -0
  15. package/dist/cli/script-packs/repo-config-chain.js +163 -0
  16. package/dist/cli/script-packs/repo-related-files.js +161 -0
  17. package/dist/core/code-outline.js +527 -80
  18. package/dist/core/config-chain.js +595 -0
  19. package/dist/core/export-diff.js +359 -0
  20. package/dist/core/public-json-contracts.js +75 -0
  21. package/dist/core/reference-drift.js +388 -0
  22. package/dist/core/related-files.js +493 -0
  23. package/dist/core/route-outline.js +912 -0
  24. package/dist/core/script-pack-suggestions.js +111 -5
  25. package/dist/core/source-anchors.js +13 -1
  26. package/package.json +1 -1
  27. package/schemas/README.md +28 -5
  28. package/schemas/code-outline-report.schema.json +47 -1
  29. package/schemas/code-symbol-read-report.schema.json +64 -4
  30. package/schemas/config-chain-report.schema.json +187 -0
  31. package/schemas/export-diff-report.schema.json +220 -0
  32. package/schemas/reference-drift-report.schema.json +166 -0
  33. package/schemas/related-files-report.schema.json +145 -0
  34. package/schemas/route-outline-report.schema.json +200 -0
  35. package/templates/default/common/.mustflow/config/commands.toml +21 -0
  36. package/templates/default/i18n.toml +7 -1
  37. package/templates/default/locales/en/.mustflow/docs/agent-workflow.md +1 -1
  38. package/templates/default/locales/en/.mustflow/skills/INDEX.md +2 -1
  39. package/templates/default/locales/en/.mustflow/skills/cross-agent-session-reference/SKILL.md +131 -0
  40. package/templates/default/locales/en/.mustflow/skills/routes.toml +6 -0
  41. package/templates/default/manifest.toml +8 -1
@@ -2,6 +2,7 @@ import { createHash } from 'node:crypto';
2
2
  import { existsSync, lstatSync, readdirSync } from 'node:fs';
3
3
  import path from 'node:path';
4
4
  import { ensureInside, ensureInsideWithoutSymlinks, readFileInsideWithoutSymlinks } from './safe-filesystem.js';
5
+ import { listSourceAnchorFiles, parseSourceAnchorsInContent, sourceAnchorTextContainsSecretLike, splitSourceAnchorList, } from './source-anchors.js';
5
6
  export const CODE_PACK_ID = 'code';
6
7
  export const CODE_OUTLINE_SCRIPT_ID = 'outline';
7
8
  export const CODE_OUTLINE_SCRIPT_REF = `${CODE_PACK_ID}/${CODE_OUTLINE_SCRIPT_ID}`;
@@ -12,7 +13,12 @@ const DEFAULT_MAX_FILES = 200;
12
13
  const DEFAULT_CONTEXT_LINES = 0;
13
14
  const DEFAULT_MAX_SNIPPET_LINES = 250;
14
15
  const RETURN_PREVIEW_MAX_CHARS = 120;
15
- const CODE_FILE_EXTENSIONS = ['.ts', '.tsx', '.mts', '.cts', '.js', '.jsx', '.mjs', '.cjs'];
16
+ const TYPESCRIPT_JAVASCRIPT_EXTENSIONS = ['.ts', '.tsx', '.mts', '.cts', '.js', '.jsx', '.mjs', '.cjs'];
17
+ const ASTRO_EXTENSIONS = ['.astro'];
18
+ const SVELTE_EXTENSIONS = ['.svelte'];
19
+ const GO_EXTENSIONS = ['.go'];
20
+ const RUST_EXTENSIONS = ['.rs'];
21
+ const PYTHON_EXTENSIONS = ['.py'];
16
22
  const IGNORED_DIRECTORIES = [
17
23
  '.git',
18
24
  '.mustflow/cache',
@@ -35,6 +41,9 @@ const ERROR_SYMBOL_READ_CODES = new Set([
35
41
  'code_symbol_read_unreadable_path',
36
42
  'code_symbol_read_invalid_range',
37
43
  'code_symbol_read_no_symbol_at_line',
44
+ 'code_symbol_read_anchor_not_found',
45
+ 'code_symbol_read_anchor_ambiguous',
46
+ 'code_symbol_read_anchor_without_symbol',
38
47
  'code_symbol_read_snippet_too_large',
39
48
  ]);
40
49
  function toPosixPath(value) {
@@ -46,7 +55,7 @@ function normalizeRelativePath(value) {
46
55
  function sha256Tagged(buffer) {
47
56
  return `sha256:${createHash('sha256').update(buffer).digest('hex')}`;
48
57
  }
49
- function languageForPath(filePath) {
58
+ function typescriptJavascriptLanguageForPath(filePath) {
50
59
  switch (path.extname(filePath).toLowerCase()) {
51
60
  case '.ts':
52
61
  case '.mts':
@@ -66,6 +75,27 @@ function languageForPath(filePath) {
66
75
  return null;
67
76
  }
68
77
  }
78
+ function astroLanguageForPath(filePath) {
79
+ return path.extname(filePath).toLowerCase() === '.astro' ? 'astro' : null;
80
+ }
81
+ function svelteLanguageForPath(filePath) {
82
+ return path.extname(filePath).toLowerCase() === '.svelte' ? 'svelte' : null;
83
+ }
84
+ function goLanguageForPath(filePath) {
85
+ return path.extname(filePath).toLowerCase() === '.go' ? 'go' : null;
86
+ }
87
+ function rustLanguageForPath(filePath) {
88
+ return path.extname(filePath).toLowerCase() === '.rs' ? 'rust' : null;
89
+ }
90
+ function pythonLanguageForPath(filePath) {
91
+ return path.extname(filePath).toLowerCase() === '.py' ? 'python' : null;
92
+ }
93
+ function languageAdapterForPath(filePath) {
94
+ return CODE_OUTLINE_LANGUAGE_ADAPTERS.find((adapter) => adapter.languageForPath(filePath) !== null) ?? null;
95
+ }
96
+ export function languageForPath(filePath) {
97
+ return languageAdapterForPath(filePath)?.languageForPath(filePath) ?? null;
98
+ }
69
99
  function isIgnoredDirectory(relativePath) {
70
100
  const normalized = normalizeRelativePath(relativePath);
71
101
  return IGNORED_DIRECTORIES.some((directory) => normalized === directory || normalized.startsWith(`${directory}/`));
@@ -205,6 +235,7 @@ function declarationFromLine(line) {
205
235
  name: functionMatch.groups.name ?? '<anonymous>',
206
236
  exported: Boolean(functionMatch.groups.exported),
207
237
  async: Boolean(functionMatch.groups.async),
238
+ returnType: null,
208
239
  };
209
240
  }
210
241
  const shapeMatch = /^(?<exported>export\s+)?(?<kind>class|interface|type|enum)\s+(?<name>[$A-Z_a-z][$\w]*)/u.exec(trimmed);
@@ -214,6 +245,7 @@ function declarationFromLine(line) {
214
245
  name: shapeMatch.groups.name ?? '<anonymous>',
215
246
  exported: Boolean(shapeMatch.groups.exported),
216
247
  async: false,
248
+ returnType: null,
217
249
  };
218
250
  }
219
251
  const variableMatch = /^(?<exported>export\s+)?(?:const|let|var)\s+(?<name>[$A-Z_a-z][$\w]*)\s*[:=]/u.exec(trimmed);
@@ -223,6 +255,7 @@ function declarationFromLine(line) {
223
255
  name: variableMatch.groups.name ?? '<anonymous>',
224
256
  exported: Boolean(variableMatch.groups.exported),
225
257
  async: /async\s*(?:\(|[A-Z_a-z_$])/u.test(trimmed),
258
+ returnType: null,
226
259
  };
227
260
  }
228
261
  return null;
@@ -255,7 +288,7 @@ function buildSignature(lines, startLine, endLine) {
255
288
  if (trimmed.length > 0) {
256
289
  parts.push(trimmed);
257
290
  }
258
- if (/[{;]\s*$/u.test(trimmed) || /=>/u.test(trimmed)) {
291
+ if (/[{;]\s*$/u.test(trimmed) || /=>/u.test(trimmed) || /^(?:async\s+)?(?:def|class)\b.*:\s*$/u.test(trimmed)) {
259
292
  break;
260
293
  }
261
294
  }
@@ -269,6 +302,9 @@ function extractExplicitReturnType(signature, match) {
269
302
  if (match.kind !== 'function') {
270
303
  return null;
271
304
  }
305
+ if (match.returnType !== null) {
306
+ return match.returnType;
307
+ }
272
308
  const functionReturnMatch = /\bfunction(?:\s+[$A-Z_a-z][$\w]*)?\s*\([^)]*\)\s*:\s*(?<returnType>[^={;]+?)(?=\s*(?:\{|$))/u.exec(signature);
273
309
  if (functionReturnMatch?.groups?.returnType) {
274
310
  return normalizeReturnType(functionReturnMatch.groups.returnType);
@@ -303,7 +339,7 @@ function extractArrowExpressionReturnPreview(signature) {
303
339
  function isIdentifierCharacter(value) {
304
340
  return typeof value === 'string' && /[$\w]/u.test(value);
305
341
  }
306
- function findReturnKeywordIndex(line) {
342
+ function findReturnKeywordIndex(line, lineCommentStart) {
307
343
  let quote = null;
308
344
  let escaped = false;
309
345
  for (let index = 0; index < line.length; index += 1) {
@@ -323,7 +359,8 @@ function findReturnKeywordIndex(line) {
323
359
  }
324
360
  continue;
325
361
  }
326
- if (character === '/' && nextCharacter === '/') {
362
+ if ((lineCommentStart === '//' && character === '/' && nextCharacter === '/') ||
363
+ (lineCommentStart === '#' && character === '#')) {
327
364
  return -1;
328
365
  }
329
366
  if (character === '"' || character === "'" || character === '`') {
@@ -338,21 +375,21 @@ function findReturnKeywordIndex(line) {
338
375
  }
339
376
  return -1;
340
377
  }
341
- function extractReturnExpressionPreview(line) {
342
- const returnIndex = findReturnKeywordIndex(line);
378
+ function extractReturnExpressionPreview(line, lineCommentStart) {
379
+ const returnIndex = findReturnKeywordIndex(line, lineCommentStart);
343
380
  if (returnIndex < 0) {
344
381
  return null;
345
382
  }
346
- const expression = line
347
- .slice(returnIndex + 'return'.length)
348
- .replace(/\/\/.*$/u, '')
383
+ const rawExpression = line.slice(returnIndex + 'return'.length);
384
+ const commentIndex = rawExpression.indexOf(lineCommentStart);
385
+ const expression = (commentIndex >= 0 ? rawExpression.slice(0, commentIndex) : rawExpression)
349
386
  .trim()
350
387
  .replace(/;\s*$/u, '')
351
388
  .trim();
352
389
  return expression.length === 0 ? null : truncateReturnPreview(expression);
353
390
  }
354
- function hasReturnStatement(line) {
355
- return findReturnKeywordIndex(line) >= 0;
391
+ function hasReturnStatement(line, lineCommentStart) {
392
+ return findReturnKeywordIndex(line, lineCommentStart) >= 0;
356
393
  }
357
394
  function simpleBodyThrowsOnly(lines, startLine, endLine, returnType) {
358
395
  let sawThrow = false;
@@ -372,7 +409,26 @@ function simpleBodyThrowsOnly(lines, startLine, endLine, returnType) {
372
409
  }
373
410
  return sawThrow && (!sawOtherExecutableLine || returnType === 'never');
374
411
  }
375
- function extractReturnMetadata(lines, startLine, endLine, signature, match) {
412
+ function stripLineComment(line, lineCommentStart) {
413
+ const commentIndex = line.indexOf(lineCommentStart);
414
+ return commentIndex >= 0 ? line.slice(0, commentIndex) : line;
415
+ }
416
+ function extractTailExpressionReturnPreview(lines, startLine, endLine) {
417
+ let index = endLine - 2;
418
+ while (index >= startLine) {
419
+ const trimmed = stripLineComment(lines[index] ?? '', '//').trim();
420
+ if (trimmed.length === 0 || trimmed === '{' || trimmed === '}') {
421
+ index -= 1;
422
+ continue;
423
+ }
424
+ if (trimmed.endsWith(';') || /^(?:let|return|if|for|while|loop|match)\b/u.test(trimmed)) {
425
+ return null;
426
+ }
427
+ return truncateReturnPreview(trimmed.replace(/,\s*$/u, ''));
428
+ }
429
+ return null;
430
+ }
431
+ function extractReturnMetadata(lines, startLine, endLine, signature, match, lineCommentStart = '//', tailExpressionReturn = false) {
376
432
  const returnType = extractExplicitReturnType(signature, match);
377
433
  if (match.kind !== 'function') {
378
434
  return {
@@ -388,11 +444,11 @@ function extractReturnMetadata(lines, startLine, endLine, signature, match) {
388
444
  let voidReturnCount = 0;
389
445
  for (let index = startLine - 1; index < endLine; index += 1) {
390
446
  const line = lines[index] ?? '';
391
- if (!hasReturnStatement(line)) {
447
+ if (!hasReturnStatement(line, lineCommentStart)) {
392
448
  continue;
393
449
  }
394
450
  returnLines.push(index + 1);
395
- const preview = extractReturnExpressionPreview(line);
451
+ const preview = extractReturnExpressionPreview(line, lineCommentStart);
396
452
  if (preview === null) {
397
453
  voidReturnCount += 1;
398
454
  }
@@ -401,11 +457,12 @@ function extractReturnMetadata(lines, startLine, endLine, signature, match) {
401
457
  }
402
458
  }
403
459
  const arrowExpressionPreview = returnLines.length === 0 ? extractArrowExpressionReturnPreview(signature) : null;
460
+ const tailExpressionPreview = tailExpressionReturn && returnLines.length === 0 && returnType !== null ? extractTailExpressionReturnPreview(lines, startLine, endLine) : null;
404
461
  let returnBehavior;
405
462
  if (valueReturnPreviews.length > 0 && voidReturnCount > 0) {
406
463
  returnBehavior = 'mixed';
407
464
  }
408
- else if (valueReturnPreviews.length > 0 || arrowExpressionPreview !== null) {
465
+ else if (valueReturnPreviews.length > 0 || arrowExpressionPreview !== null || tailExpressionPreview !== null) {
409
466
  returnBehavior = 'value';
410
467
  }
411
468
  else if (voidReturnCount > 0) {
@@ -422,21 +479,141 @@ function extractReturnMetadata(lines, startLine, endLine, signature, match) {
422
479
  return_behavior: returnBehavior,
423
480
  return_count: returnLines.length,
424
481
  return_lines: returnLines,
425
- return_preview: valueReturnPreviews[0] ?? arrowExpressionPreview,
482
+ return_preview: valueReturnPreviews[0] ?? arrowExpressionPreview ?? tailExpressionPreview,
426
483
  };
427
484
  }
428
- function extractSymbols(relativePath, language, contentSha256, text) {
485
+ function startsWithUppercase(value) {
486
+ const [first] = value;
487
+ return typeof first === 'string' && first.toLocaleUpperCase() === first && first.toLocaleLowerCase() !== first;
488
+ }
489
+ function goDeclarationFromLine(line) {
490
+ const trimmed = line.trim();
491
+ const functionMatch = /^func\s+(?:\([^)]*\)\s*)?(?<name>[A-Z_a-z]\w*)\s*\([^)]*\)\s*(?<returnType>[^{]+?)?\s*(?:\{|$)/u.exec(trimmed);
492
+ if (functionMatch?.groups) {
493
+ const returnType = functionMatch.groups.returnType?.trim() ?? '';
494
+ return {
495
+ kind: 'function',
496
+ name: functionMatch.groups.name ?? '<anonymous>',
497
+ exported: startsWithUppercase(functionMatch.groups.name ?? ''),
498
+ async: false,
499
+ returnType: returnType.length > 0 ? normalizeReturnType(returnType) : null,
500
+ };
501
+ }
502
+ const typeMatch = /^type\s+(?<name>[A-Z_a-z]\w*)\s+(?<shape>struct|interface)?\b/u.exec(trimmed);
503
+ if (typeMatch?.groups) {
504
+ return {
505
+ kind: typeMatch.groups.shape === 'interface' ? 'interface' : 'type',
506
+ name: typeMatch.groups.name ?? '<anonymous>',
507
+ exported: startsWithUppercase(typeMatch.groups.name ?? ''),
508
+ async: false,
509
+ returnType: null,
510
+ };
511
+ }
512
+ return null;
513
+ }
514
+ function rustDeclarationFromLine(line) {
515
+ const trimmed = line.trim();
516
+ const visibility = String.raw `(?:pub(?:\([^)]*\))?\s+)?`;
517
+ const functionPattern = [
518
+ String.raw `^${visibility}`,
519
+ String.raw `(?:(?:async|const|unsafe)\s+)*`,
520
+ String.raw `(?:extern\s+"[^"]+"\s+)?`,
521
+ String.raw `fn\s+(?<name>[A-Z_a-z]\w*)\s*[^{;]*?`,
522
+ String.raw `(?:->\s*(?<returnType>[^{]+?))?\s*(?:where\b|\{|$)`,
523
+ ].join('');
524
+ const functionMatch = new RegExp(functionPattern, 'u').exec(trimmed);
525
+ if (functionMatch?.groups) {
526
+ const returnType = functionMatch.groups.returnType?.replace(/\bwhere\b.*$/u, '').trim() ?? '';
527
+ return {
528
+ kind: 'function',
529
+ name: functionMatch.groups.name ?? '<anonymous>',
530
+ exported: /^pub\b/u.test(trimmed),
531
+ async: /\basync\s+fn\b/u.test(trimmed) || /\basync\s+(?:const\s+|unsafe\s+)*fn\b/u.test(trimmed),
532
+ returnType: returnType.length > 0 ? normalizeReturnType(returnType) : null,
533
+ };
534
+ }
535
+ const shapeMatch = new RegExp(String.raw `^${visibility}(?<shape>struct|trait|type|enum)\s+(?<name>[A-Z_a-z]\w*)\b`, 'u').exec(trimmed);
536
+ if (shapeMatch?.groups) {
537
+ const shape = shapeMatch.groups.shape;
538
+ return {
539
+ kind: shape === 'trait' ? 'interface' : shape === 'enum' ? 'enum' : 'type',
540
+ name: shapeMatch.groups.name ?? '<anonymous>',
541
+ exported: /^pub\b/u.test(trimmed),
542
+ async: false,
543
+ returnType: null,
544
+ };
545
+ }
546
+ return null;
547
+ }
548
+ function pythonDeclarationFromLine(line) {
549
+ const trimmed = line.trim();
550
+ const functionMatch = /^(?<async>async\s+)?def\s+(?<name>[A-Z_a-z]\w*)\s*\([^)]*\)\s*(?:->\s*(?<returnType>[^:]+))?:/u.exec(trimmed);
551
+ if (functionMatch?.groups) {
552
+ const returnType = functionMatch.groups.returnType?.trim() ?? '';
553
+ return {
554
+ kind: 'function',
555
+ name: functionMatch.groups.name ?? '<anonymous>',
556
+ exported: !functionMatch.groups.name?.startsWith('_'),
557
+ async: Boolean(functionMatch.groups.async),
558
+ returnType: returnType.length > 0 ? normalizeReturnType(returnType) : null,
559
+ };
560
+ }
561
+ const classMatch = /^class\s+(?<name>[A-Z_a-z]\w*)\b/u.exec(trimmed);
562
+ if (classMatch?.groups) {
563
+ return {
564
+ kind: 'class',
565
+ name: classMatch.groups.name ?? '<anonymous>',
566
+ exported: !classMatch.groups.name?.startsWith('_'),
567
+ async: false,
568
+ returnType: null,
569
+ };
570
+ }
571
+ return null;
572
+ }
573
+ function leadingWhitespaceWidth(line) {
574
+ let width = 0;
575
+ for (const character of line) {
576
+ if (character === ' ') {
577
+ width += 1;
578
+ }
579
+ else if (character === '\t') {
580
+ width += 4;
581
+ }
582
+ else {
583
+ break;
584
+ }
585
+ }
586
+ return width;
587
+ }
588
+ function findPythonDeclarationEndLine(lines, startIndex) {
589
+ const startIndent = leadingWhitespaceWidth(lines[startIndex] ?? '');
590
+ let index = startIndex + 1;
591
+ while (index < lines.length) {
592
+ const line = lines[index] ?? '';
593
+ const trimmed = line.trim();
594
+ if (trimmed.length === 0 || trimmed.startsWith('#')) {
595
+ index += 1;
596
+ continue;
597
+ }
598
+ if (leadingWhitespaceWidth(line) <= startIndent) {
599
+ return index;
600
+ }
601
+ index += 1;
602
+ }
603
+ return lines.length;
604
+ }
605
+ function extractSymbolsFromDeclarations(relativePath, language, contentSha256, text, declarationForLine, endLineForMatch, lineCommentStart, tailExpressionReturn = false) {
429
606
  const lines = text.split(/\r\n|\r|\n/u);
430
607
  const symbols = [];
431
608
  for (const [index, line] of lines.entries()) {
432
- const match = declarationFromLine(line);
609
+ const match = declarationForLine(line);
433
610
  if (!match) {
434
611
  continue;
435
612
  }
436
613
  const startLine = index + 1;
437
- const endLine = findDeclarationEndLine(lines, index);
614
+ const endLine = endLineForMatch(lines, index);
438
615
  const signature = buildSignature(lines, startLine, endLine);
439
- const returnMetadata = extractReturnMetadata(lines, startLine, endLine, signature, match);
616
+ const returnMetadata = extractReturnMetadata(lines, startLine, endLine, signature, match, lineCommentStart, tailExpressionReturn);
440
617
  symbols.push({
441
618
  id: `${relativePath}:${startLine}:${match.kind}:${match.name}`,
442
619
  path: relativePath,
@@ -455,7 +632,172 @@ function extractSymbols(relativePath, language, contentSha256, text) {
455
632
  }
456
633
  return symbols;
457
634
  }
458
- function createOutlineInputHash(policy, files, findings) {
635
+ function extractTypescriptJavascriptSymbols(relativePath, language, contentSha256, text) {
636
+ return extractSymbolsFromDeclarations(relativePath, language, contentSha256, text, declarationFromLine, findDeclarationEndLine, '//');
637
+ }
638
+ function maskAstroFrontmatter(text) {
639
+ const lines = text.split(/\r\n|\r|\n/u);
640
+ if ((lines[0] ?? '').trim() !== '---') {
641
+ return lines.map(() => '').join('\n');
642
+ }
643
+ const masked = lines.map(() => '');
644
+ let index = 1;
645
+ while (index < lines.length) {
646
+ const line = lines[index] ?? '';
647
+ if (line.trim() === '---') {
648
+ return masked.join('\n');
649
+ }
650
+ masked[index] = line;
651
+ index += 1;
652
+ }
653
+ return lines.map(() => '').join('\n');
654
+ }
655
+ function maskSvelteScriptBlocks(text) {
656
+ const lines = text.split(/\r\n|\r|\n/u);
657
+ const masked = lines.map(() => '');
658
+ let insideScript = false;
659
+ for (const [index, line] of lines.entries()) {
660
+ let remaining = line;
661
+ if (!insideScript) {
662
+ const openMatch = /<script\b[^>]*>/iu.exec(remaining);
663
+ if (!openMatch || openMatch.index === undefined) {
664
+ continue;
665
+ }
666
+ remaining = remaining.slice(openMatch.index + openMatch[0].length);
667
+ insideScript = true;
668
+ }
669
+ const closeIndex = remaining.search(/<\/script\s*>/iu);
670
+ if (closeIndex >= 0) {
671
+ masked[index] = remaining.slice(0, closeIndex);
672
+ insideScript = false;
673
+ continue;
674
+ }
675
+ masked[index] = remaining;
676
+ }
677
+ return masked.join('\n');
678
+ }
679
+ function extractAstroSymbols(relativePath, language, contentSha256, text) {
680
+ return extractTypescriptJavascriptSymbols(relativePath, language, contentSha256, maskAstroFrontmatter(text));
681
+ }
682
+ function extractSvelteSymbols(relativePath, language, contentSha256, text) {
683
+ return extractTypescriptJavascriptSymbols(relativePath, language, contentSha256, maskSvelteScriptBlocks(text));
684
+ }
685
+ function extractGoSymbols(relativePath, language, contentSha256, text) {
686
+ return extractSymbolsFromDeclarations(relativePath, language, contentSha256, text, goDeclarationFromLine, findDeclarationEndLine, '//');
687
+ }
688
+ function extractRustSymbols(relativePath, language, contentSha256, text) {
689
+ return extractSymbolsFromDeclarations(relativePath, language, contentSha256, text, rustDeclarationFromLine, findDeclarationEndLine, '//', true);
690
+ }
691
+ function extractPythonSymbols(relativePath, language, contentSha256, text) {
692
+ return extractSymbolsFromDeclarations(relativePath, language, contentSha256, text, pythonDeclarationFromLine, findPythonDeclarationEndLine, '#');
693
+ }
694
+ const TYPESCRIPT_JAVASCRIPT_LANGUAGE_ADAPTER = {
695
+ id: 'typescript-javascript',
696
+ extensions: TYPESCRIPT_JAVASCRIPT_EXTENSIONS,
697
+ languageForPath: typescriptJavascriptLanguageForPath,
698
+ extractSymbols: extractTypescriptJavascriptSymbols,
699
+ };
700
+ const ASTRO_LANGUAGE_ADAPTER = {
701
+ id: 'astro',
702
+ extensions: ASTRO_EXTENSIONS,
703
+ languageForPath: astroLanguageForPath,
704
+ extractSymbols: extractAstroSymbols,
705
+ };
706
+ const SVELTE_LANGUAGE_ADAPTER = {
707
+ id: 'svelte',
708
+ extensions: SVELTE_EXTENSIONS,
709
+ languageForPath: svelteLanguageForPath,
710
+ extractSymbols: extractSvelteSymbols,
711
+ };
712
+ const GO_LANGUAGE_ADAPTER = {
713
+ id: 'go',
714
+ extensions: GO_EXTENSIONS,
715
+ languageForPath: goLanguageForPath,
716
+ extractSymbols: extractGoSymbols,
717
+ };
718
+ const RUST_LANGUAGE_ADAPTER = {
719
+ id: 'rust',
720
+ extensions: RUST_EXTENSIONS,
721
+ languageForPath: rustLanguageForPath,
722
+ extractSymbols: extractRustSymbols,
723
+ };
724
+ const PYTHON_LANGUAGE_ADAPTER = {
725
+ id: 'python',
726
+ extensions: PYTHON_EXTENSIONS,
727
+ languageForPath: pythonLanguageForPath,
728
+ extractSymbols: extractPythonSymbols,
729
+ };
730
+ const CODE_OUTLINE_LANGUAGE_ADAPTERS = [
731
+ TYPESCRIPT_JAVASCRIPT_LANGUAGE_ADAPTER,
732
+ ASTRO_LANGUAGE_ADAPTER,
733
+ SVELTE_LANGUAGE_ADAPTER,
734
+ GO_LANGUAGE_ADAPTER,
735
+ RUST_LANGUAGE_ADAPTER,
736
+ PYTHON_LANGUAGE_ADAPTER,
737
+ ];
738
+ const CODE_FILE_EXTENSIONS = CODE_OUTLINE_LANGUAGE_ADAPTERS.flatMap((adapter) => adapter.extensions);
739
+ export function extractSymbols(relativePath, language, contentSha256, text) {
740
+ const adapter = CODE_OUTLINE_LANGUAGE_ADAPTERS.find((candidate) => candidate.languageForPath(relativePath) === language);
741
+ return adapter?.extractSymbols(relativePath, language, contentSha256, text) ?? [];
742
+ }
743
+ function sourceAnchorEndLine(lineStart, rawText) {
744
+ return lineStart + rawText.split(/\r\n|\r|\n/u).length - 1;
745
+ }
746
+ function canBridgeAnchorToSymbol(lines, anchorEndLine, symbolStartLine) {
747
+ if (symbolStartLine <= anchorEndLine) {
748
+ return false;
749
+ }
750
+ for (let lineNumber = anchorEndLine + 1; lineNumber < symbolStartLine; lineNumber += 1) {
751
+ const trimmed = (lines[lineNumber - 1] ?? '').trim();
752
+ if (trimmed.length === 0 ||
753
+ trimmed.startsWith('//') ||
754
+ trimmed.startsWith('#') ||
755
+ trimmed.startsWith('*') ||
756
+ trimmed.startsWith('@')) {
757
+ continue;
758
+ }
759
+ return false;
760
+ }
761
+ return true;
762
+ }
763
+ function findAnchorTargetSymbol(relativePath, lines, anchorEndLine, symbols) {
764
+ const followingSymbols = symbols
765
+ .filter((symbol) => symbol.path === relativePath && symbol.start_line > anchorEndLine)
766
+ .sort((left, right) => left.start_line - right.start_line);
767
+ const target = followingSymbols[0] ?? null;
768
+ if (!target || !canBridgeAnchorToSymbol(lines, anchorEndLine, target.start_line)) {
769
+ return null;
770
+ }
771
+ return target;
772
+ }
773
+ function extractAnchors(relativePath, lines, text, symbols) {
774
+ const anchors = [];
775
+ for (const anchor of parseSourceAnchorsInContent(relativePath, text)) {
776
+ if (!anchor.idValid || sourceAnchorTextContainsSecretLike(anchor.rawText)) {
777
+ continue;
778
+ }
779
+ const lineEnd = sourceAnchorEndLine(anchor.lineStart, anchor.rawText);
780
+ const target = findAnchorTargetSymbol(relativePath, lines, lineEnd, symbols);
781
+ anchors.push({
782
+ id: anchor.rawId,
783
+ path: relativePath,
784
+ line_start: anchor.lineStart,
785
+ line_end: lineEnd,
786
+ purpose: anchor.fields.get('purpose') ?? null,
787
+ search: splitSourceAnchorList(anchor.fields.get('search')),
788
+ invariant: anchor.fields.get('invariant') ?? null,
789
+ risk: splitSourceAnchorList(anchor.fields.get('risk')),
790
+ navigation_only: true,
791
+ can_instruct_agent: false,
792
+ target_symbol_id: target?.id ?? null,
793
+ target_kind: target?.kind ?? null,
794
+ target_name: target?.name ?? null,
795
+ target_start_line: target?.start_line ?? null,
796
+ });
797
+ }
798
+ return anchors;
799
+ }
800
+ function createOutlineInputHash(policy, files, anchors, findings) {
459
801
  const inputState = {
460
802
  policy,
461
803
  files: files.map((file) => ({
@@ -464,6 +806,13 @@ function createOutlineInputHash(policy, files, findings) {
464
806
  size_bytes: file.size_bytes,
465
807
  symbol_count: file.symbol_count,
466
808
  })),
809
+ anchors: anchors.map((anchor) => ({
810
+ id: anchor.id,
811
+ path: anchor.path,
812
+ line_start: anchor.line_start,
813
+ line_end: anchor.line_end,
814
+ target_symbol_id: anchor.target_symbol_id,
815
+ })),
467
816
  input_errors: findings
468
817
  .filter((finding) => ERROR_OUTLINE_CODES.has(finding.code))
469
818
  .map((finding) => ({ code: finding.code, path: finding.path })),
@@ -486,6 +835,7 @@ export function inspectCodeOutline(projectRoot, options) {
486
835
  ignored_directories: [...IGNORED_DIRECTORIES],
487
836
  };
488
837
  const files = [];
838
+ const anchors = [];
489
839
  const symbols = [];
490
840
  const findings = [];
491
841
  const issues = [];
@@ -506,15 +856,18 @@ export function inspectCodeOutline(projectRoot, options) {
506
856
  }
507
857
  const contentSha256 = sha256Tagged(buffer);
508
858
  const text = buffer.toString('utf8');
859
+ const lines = text.split(/\r\n|\r|\n/u);
509
860
  const fileSymbols = extractSymbols(candidate.relativePath, candidate.language, contentSha256, text);
861
+ const fileAnchors = extractAnchors(candidate.relativePath, lines, text, fileSymbols);
510
862
  symbols.push(...fileSymbols);
863
+ anchors.push(...fileAnchors);
511
864
  files.push({
512
865
  kind: 'source_file',
513
866
  path: candidate.relativePath,
514
867
  language: candidate.language,
515
868
  sha256: contentSha256,
516
869
  size_bytes: buffer.byteLength,
517
- line_count: text.split(/\r\n|\r|\n/u).length,
870
+ line_count: lines.length,
518
871
  symbol_count: fileSymbols.length,
519
872
  });
520
873
  }
@@ -530,8 +883,9 @@ export function inspectCodeOutline(projectRoot, options) {
530
883
  ok: status === 'passed',
531
884
  mustflow_root: root,
532
885
  policy,
533
- input_hash: createOutlineInputHash(policy, files, findings),
886
+ input_hash: createOutlineInputHash(policy, files, anchors, findings),
534
887
  files,
888
+ anchors: anchors.sort((left, right) => left.path.localeCompare(right.path) || left.line_start - right.line_start),
535
889
  symbols: symbols.sort((left, right) => left.path.localeCompare(right.path) || left.start_line - right.start_line),
536
890
  findings,
537
891
  issues,
@@ -565,10 +919,12 @@ function createSymbolReadInputHash(policy, target, findings) {
565
919
  : {
566
920
  path: target.path,
567
921
  sha256: target.sha256,
922
+ requested_anchor_id: target.requested_anchor_id,
568
923
  requested_start_line: target.requested_start_line,
569
924
  requested_end_line: target.requested_end_line,
570
925
  context_start_line: target.context_start_line,
571
926
  context_end_line: target.context_end_line,
927
+ anchor_id: target.anchor?.id ?? null,
572
928
  },
573
929
  input_errors: findings
574
930
  .filter((finding) => ERROR_SYMBOL_READ_CODES.has(finding.code))
@@ -581,75 +937,66 @@ function createSymbolReadInputHash(policy, target, findings) {
581
937
  };
582
938
  return sha256Tagged(JSON.stringify(inputState));
583
939
  }
584
- export function readCodeSymbol(projectRoot, options) {
585
- const root = path.resolve(projectRoot);
586
- const policy = {
587
- start_line: options.startLine,
588
- end_line: options.endLine ?? null,
589
- context_lines: options.contextLines ?? DEFAULT_CONTEXT_LINES,
590
- max_file_bytes: options.maxFileBytes ?? DEFAULT_MAX_FILE_BYTES,
591
- max_snippet_lines: options.maxSnippetLines ?? DEFAULT_MAX_SNIPPET_LINES,
592
- };
593
- const findings = [];
594
- const issues = [];
595
- if (policy.start_line < 1 || (policy.end_line !== null && policy.end_line < policy.start_line) || policy.context_lines < 0) {
596
- findings.push(makeSymbolReadFinding('code_symbol_read_invalid_range', 'high', options.path, policy.start_line, policy.end_line, 'Line range must use 1-based positive lines, and end_line must be greater than or equal to start_line.'));
597
- return createEmptySymbolReadReport(root, policy, findings, issues);
598
- }
599
- let absolutePath;
600
- let relativePath;
601
- try {
602
- const normalized = normalizeTargetPath(root, options.path);
603
- absolutePath = normalized.absolutePath;
604
- relativePath = normalized.relativePath;
605
- ensureInsideWithoutSymlinks(root, absolutePath);
606
- }
607
- catch (error) {
608
- const message = error instanceof Error ? error.message : String(error);
609
- issues.push(message);
610
- findings.push(makeSymbolReadFinding('code_symbol_read_path_outside_root', 'high', options.path, policy.start_line, policy.end_line, message));
611
- return createEmptySymbolReadReport(root, policy, findings, issues);
612
- }
613
- const language = languageForPath(absolutePath);
614
- if (!language || !existsSync(absolutePath)) {
615
- const message = `${relativePath} is not a supported existing code file.`;
616
- issues.push(message);
617
- findings.push(makeSymbolReadFinding('code_symbol_read_unreadable_path', 'high', relativePath, policy.start_line, policy.end_line, message));
618
- return createEmptySymbolReadReport(root, policy, findings, issues);
619
- }
620
- let buffer;
621
- try {
622
- buffer = readFileInsideWithoutSymlinks(root, absolutePath, { maxBytes: policy.max_file_bytes });
623
- }
624
- catch (error) {
625
- const message = error instanceof Error ? error.message : String(error);
626
- issues.push(`${relativePath}: ${message}`);
627
- findings.push(makeSymbolReadFinding('code_symbol_read_unreadable_path', 'high', relativePath, policy.start_line, policy.end_line, message));
628
- return createEmptySymbolReadReport(root, policy, findings, issues);
940
+ function collectAnchorReadCandidates(root, anchorId, maxFileBytes) {
941
+ const candidates = [];
942
+ for (const relativePath of listSourceAnchorFiles(root, { allowedExtensions: CODE_FILE_EXTENSIONS, maxFileBytes })) {
943
+ const absolutePath = path.join(root, ...relativePath.split('/'));
944
+ const language = languageForPath(absolutePath);
945
+ if (!language || !existsSync(absolutePath)) {
946
+ continue;
947
+ }
948
+ let buffer;
949
+ try {
950
+ buffer = readFileInsideWithoutSymlinks(root, absolutePath, { maxBytes: maxFileBytes });
951
+ }
952
+ catch {
953
+ continue;
954
+ }
955
+ const contentSha256 = sha256Tagged(buffer);
956
+ const text = buffer.toString('utf8');
957
+ const lines = text.split(/\r\n|\r|\n/u);
958
+ const symbols = extractSymbols(relativePath, language, contentSha256, text);
959
+ const anchors = extractAnchors(relativePath, lines, text, symbols);
960
+ for (const anchor of anchors) {
961
+ if (anchor.id === anchorId) {
962
+ candidates.push({ relativePath, anchor });
963
+ }
964
+ }
629
965
  }
966
+ return candidates.sort((left, right) => left.relativePath.localeCompare(right.relativePath) || left.anchor.line_start - right.anchor.line_start);
967
+ }
968
+ function createSymbolReadReportFromFile(root, policy, relativePath, language, buffer, findings, issues, anchor) {
630
969
  const contentSha256 = sha256Tagged(buffer);
631
970
  const text = buffer.toString('utf8');
632
971
  const lines = text.split(/\r\n|\r|\n/u);
633
- if (policy.start_line > lines.length || (policy.end_line !== null && policy.end_line > lines.length)) {
634
- const message = `Line range ${policy.start_line}${policy.end_line === null ? '' : `-${policy.end_line}`} is outside ${relativePath}, which has ${lines.length} lines.`;
972
+ if ((policy.start_line !== null && policy.start_line > lines.length) || (policy.end_line !== null && policy.end_line > lines.length)) {
973
+ const requestedRange = policy.start_line === null
974
+ ? `anchor ${policy.anchor_id ?? '<unknown>'}`
975
+ : `${policy.start_line}${policy.end_line === null ? '' : `-${policy.end_line}`}`;
976
+ const message = `Line range ${requestedRange} is outside ${relativePath}, which has ${lines.length} lines.`;
635
977
  issues.push(message);
636
978
  findings.push(makeSymbolReadFinding('code_symbol_read_invalid_range', 'high', relativePath, policy.start_line, policy.end_line, message));
637
979
  return createEmptySymbolReadReport(root, policy, findings, issues);
638
980
  }
639
981
  const symbols = extractSymbols(relativePath, language, contentSha256, text);
640
- const matchedSymbol = policy.end_line === null
641
- ? (symbols.find((symbol) => symbol.start_line === policy.start_line) ??
642
- symbols.find((symbol) => symbol.start_line <= policy.start_line && symbol.end_line >= policy.start_line) ??
643
- null)
644
- : null;
645
- const resolvedStartLine = matchedSymbol?.start_line ?? policy.start_line;
982
+ const requestedStartLine = policy.start_line;
983
+ const matchedSymbol = anchor
984
+ ? (symbols.find((symbol) => symbol.id === anchor.target_symbol_id) ?? null)
985
+ : policy.end_line === null && requestedStartLine !== null
986
+ ? (symbols.find((symbol) => symbol.start_line === requestedStartLine) ??
987
+ symbols.find((symbol) => symbol.start_line <= requestedStartLine && symbol.end_line >= requestedStartLine) ??
988
+ null)
989
+ : null;
990
+ const resolvedStartLine = matchedSymbol?.start_line ?? requestedStartLine;
646
991
  const resolvedEndLine = matchedSymbol?.end_line ?? policy.end_line;
647
992
  if (resolvedEndLine === null) {
648
- const message = `No outline symbol starts at or contains line ${policy.start_line} in ${relativePath}. Pass --end-line to read an explicit range.`;
993
+ const message = anchor
994
+ ? `Source anchor ${anchor.id} in ${relativePath}:${anchor.line_start} does not target a readable outline symbol.`
995
+ : `No outline symbol starts at or contains line ${policy.start_line} in ${relativePath}. Pass --end-line to read an explicit range.`;
649
996
  issues.push(message);
650
- findings.push(makeSymbolReadFinding('code_symbol_read_no_symbol_at_line', 'high', relativePath, policy.start_line, null, message));
997
+ findings.push(makeSymbolReadFinding(anchor ? 'code_symbol_read_anchor_without_symbol' : 'code_symbol_read_no_symbol_at_line', 'high', relativePath, policy.start_line ?? anchor?.line_start ?? null, null, message));
651
998
  }
652
- const contextStartLine = resolvedEndLine === null ? null : Math.max(1, resolvedStartLine - policy.context_lines);
999
+ const contextStartLine = resolvedEndLine === null || resolvedStartLine === null ? null : Math.max(1, resolvedStartLine - policy.context_lines);
653
1000
  const contextEndLine = resolvedEndLine === null ? null : Math.min(lines.length, resolvedEndLine + policy.context_lines);
654
1001
  const target = {
655
1002
  path: relativePath,
@@ -657,12 +1004,14 @@ export function readCodeSymbol(projectRoot, options) {
657
1004
  sha256: contentSha256,
658
1005
  size_bytes: buffer.byteLength,
659
1006
  line_count: lines.length,
1007
+ requested_anchor_id: policy.anchor_id,
660
1008
  requested_start_line: policy.start_line,
661
1009
  requested_end_line: policy.end_line,
662
1010
  resolved_start_line: resolvedEndLine === null ? null : resolvedStartLine,
663
1011
  resolved_end_line: resolvedEndLine,
664
1012
  context_start_line: contextStartLine,
665
1013
  context_end_line: contextEndLine,
1014
+ anchor,
666
1015
  symbol: matchedSymbol,
667
1016
  };
668
1017
  let snippet = null;
@@ -703,3 +1052,101 @@ export function readCodeSymbol(projectRoot, options) {
703
1052
  issues,
704
1053
  };
705
1054
  }
1055
+ export function readCodeSymbol(projectRoot, options) {
1056
+ const root = path.resolve(projectRoot);
1057
+ const policy = {
1058
+ anchor_id: options.anchorId ?? null,
1059
+ start_line: options.startLine ?? null,
1060
+ end_line: options.endLine ?? null,
1061
+ context_lines: options.contextLines ?? DEFAULT_CONTEXT_LINES,
1062
+ max_file_bytes: options.maxFileBytes ?? DEFAULT_MAX_FILE_BYTES,
1063
+ max_snippet_lines: options.maxSnippetLines ?? DEFAULT_MAX_SNIPPET_LINES,
1064
+ };
1065
+ const findings = [];
1066
+ const issues = [];
1067
+ if (policy.context_lines < 0 ||
1068
+ (policy.start_line !== null && policy.start_line < 1) ||
1069
+ (policy.end_line !== null && (policy.start_line === null || policy.end_line < policy.start_line))) {
1070
+ findings.push(makeSymbolReadFinding('code_symbol_read_invalid_range', 'high', options.path ?? '.', policy.start_line, policy.end_line, 'Line range must use 1-based positive lines, and end_line must be greater than or equal to start_line.'));
1071
+ return createEmptySymbolReadReport(root, policy, findings, issues);
1072
+ }
1073
+ if (policy.anchor_id !== null) {
1074
+ const anchorCandidates = collectAnchorReadCandidates(root, policy.anchor_id, policy.max_file_bytes);
1075
+ if (anchorCandidates.length === 0) {
1076
+ const message = `No source anchor found with id ${policy.anchor_id}.`;
1077
+ issues.push(message);
1078
+ findings.push(makeSymbolReadFinding('code_symbol_read_anchor_not_found', 'high', '.', null, null, message));
1079
+ return createEmptySymbolReadReport(root, policy, findings, issues);
1080
+ }
1081
+ if (anchorCandidates.length > 1) {
1082
+ const locations = anchorCandidates.map((candidate) => `${candidate.relativePath}:${candidate.anchor.line_start}`).join(', ');
1083
+ const message = `Source anchor id ${policy.anchor_id} is ambiguous: ${locations}.`;
1084
+ issues.push(message);
1085
+ findings.push(makeSymbolReadFinding('code_symbol_read_anchor_ambiguous', 'high', '.', null, null, message));
1086
+ return createEmptySymbolReadReport(root, policy, findings, issues);
1087
+ }
1088
+ const candidate = anchorCandidates[0];
1089
+ const anchor = candidate?.anchor ?? null;
1090
+ const relativePath = candidate?.relativePath ?? '.';
1091
+ const absolutePath = path.join(root, ...relativePath.split('/'));
1092
+ const language = languageForPath(absolutePath);
1093
+ if (!anchor || !language || !existsSync(absolutePath)) {
1094
+ const message = `${relativePath} is not a supported existing code file.`;
1095
+ issues.push(message);
1096
+ findings.push(makeSymbolReadFinding('code_symbol_read_unreadable_path', 'high', relativePath, null, null, message));
1097
+ return createEmptySymbolReadReport(root, policy, findings, issues);
1098
+ }
1099
+ let buffer;
1100
+ try {
1101
+ buffer = readFileInsideWithoutSymlinks(root, absolutePath, { maxBytes: policy.max_file_bytes });
1102
+ }
1103
+ catch (error) {
1104
+ const message = error instanceof Error ? error.message : String(error);
1105
+ issues.push(`${relativePath}: ${message}`);
1106
+ findings.push(makeSymbolReadFinding('code_symbol_read_unreadable_path', 'high', relativePath, null, null, message));
1107
+ return createEmptySymbolReadReport(root, policy, findings, issues);
1108
+ }
1109
+ const anchorPolicy = {
1110
+ ...policy,
1111
+ start_line: null,
1112
+ end_line: null,
1113
+ };
1114
+ return createSymbolReadReportFromFile(root, anchorPolicy, relativePath, language, buffer, findings, issues, anchor);
1115
+ }
1116
+ if (!options.path || policy.start_line === null) {
1117
+ findings.push(makeSymbolReadFinding('code_symbol_read_invalid_range', 'high', options.path ?? '.', policy.start_line, policy.end_line, 'Path and --start-line are required unless --anchor is provided.'));
1118
+ return createEmptySymbolReadReport(root, policy, findings, issues);
1119
+ }
1120
+ let absolutePath;
1121
+ let relativePath;
1122
+ try {
1123
+ const normalized = normalizeTargetPath(root, options.path);
1124
+ absolutePath = normalized.absolutePath;
1125
+ relativePath = normalized.relativePath;
1126
+ ensureInsideWithoutSymlinks(root, absolutePath);
1127
+ }
1128
+ catch (error) {
1129
+ const message = error instanceof Error ? error.message : String(error);
1130
+ issues.push(message);
1131
+ findings.push(makeSymbolReadFinding('code_symbol_read_path_outside_root', 'high', options.path, policy.start_line, policy.end_line, message));
1132
+ return createEmptySymbolReadReport(root, policy, findings, issues);
1133
+ }
1134
+ const language = languageForPath(absolutePath);
1135
+ if (!language || !existsSync(absolutePath)) {
1136
+ const message = `${relativePath} is not a supported existing code file.`;
1137
+ issues.push(message);
1138
+ findings.push(makeSymbolReadFinding('code_symbol_read_unreadable_path', 'high', relativePath, policy.start_line, policy.end_line, message));
1139
+ return createEmptySymbolReadReport(root, policy, findings, issues);
1140
+ }
1141
+ let buffer;
1142
+ try {
1143
+ buffer = readFileInsideWithoutSymlinks(root, absolutePath, { maxBytes: policy.max_file_bytes });
1144
+ }
1145
+ catch (error) {
1146
+ const message = error instanceof Error ? error.message : String(error);
1147
+ issues.push(`${relativePath}: ${message}`);
1148
+ findings.push(makeSymbolReadFinding('code_symbol_read_unreadable_path', 'high', relativePath, policy.start_line, policy.end_line, message));
1149
+ return createEmptySymbolReadReport(root, policy, findings, issues);
1150
+ }
1151
+ return createSymbolReadReportFromFile(root, policy, relativePath, language, buffer, findings, issues, null);
1152
+ }