mustflow 2.75.2 → 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.
- package/README.md +31 -3
- package/dist/cli/commands/docs.js +86 -2
- package/dist/cli/commands/script-pack.js +5 -0
- package/dist/cli/i18n/en.js +101 -2
- package/dist/cli/i18n/es.js +101 -2
- package/dist/cli/i18n/fr.js +101 -2
- package/dist/cli/i18n/hi.js +101 -2
- package/dist/cli/i18n/ko.js +101 -2
- package/dist/cli/i18n/zh.js +101 -2
- package/dist/cli/lib/script-pack-registry.js +161 -6
- package/dist/cli/script-packs/code-export-diff.js +160 -0
- package/dist/cli/script-packs/code-outline.js +33 -5
- package/dist/cli/script-packs/code-route-outline.js +155 -0
- package/dist/cli/script-packs/docs-reference-drift.js +150 -0
- package/dist/cli/script-packs/repo-config-chain.js +163 -0
- package/dist/cli/script-packs/repo-related-files.js +161 -0
- package/dist/core/code-outline.js +460 -79
- package/dist/core/config-chain.js +595 -0
- package/dist/core/export-diff.js +359 -0
- package/dist/core/public-json-contracts.js +75 -0
- package/dist/core/reference-drift.js +388 -0
- package/dist/core/related-files.js +493 -0
- package/dist/core/route-outline.js +912 -0
- package/dist/core/script-pack-suggestions.js +111 -5
- package/dist/core/source-anchors.js +13 -1
- package/package.json +1 -1
- package/schemas/README.md +28 -6
- package/schemas/code-outline-report.schema.json +1 -1
- package/schemas/code-symbol-read-report.schema.json +64 -4
- package/schemas/config-chain-report.schema.json +187 -0
- package/schemas/export-diff-report.schema.json +220 -0
- package/schemas/reference-drift-report.schema.json +166 -0
- package/schemas/related-files-report.schema.json +145 -0
- package/schemas/route-outline-report.schema.json +200 -0
- package/templates/default/common/.mustflow/config/commands.toml +21 -0
- package/templates/default/i18n.toml +7 -1
- package/templates/default/locales/en/.mustflow/docs/agent-workflow.md +1 -1
- package/templates/default/locales/en/.mustflow/skills/INDEX.md +2 -1
- package/templates/default/locales/en/.mustflow/skills/cross-agent-session-reference/SKILL.md +131 -0
- package/templates/default/locales/en/.mustflow/skills/routes.toml +6 -0
- package/templates/default/manifest.toml +8 -1
|
@@ -2,7 +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 { parseSourceAnchorsInContent, sourceAnchorTextContainsSecretLike, splitSourceAnchorList, } from './source-anchors.js';
|
|
5
|
+
import { listSourceAnchorFiles, parseSourceAnchorsInContent, sourceAnchorTextContainsSecretLike, splitSourceAnchorList, } from './source-anchors.js';
|
|
6
6
|
export const CODE_PACK_ID = 'code';
|
|
7
7
|
export const CODE_OUTLINE_SCRIPT_ID = 'outline';
|
|
8
8
|
export const CODE_OUTLINE_SCRIPT_REF = `${CODE_PACK_ID}/${CODE_OUTLINE_SCRIPT_ID}`;
|
|
@@ -13,7 +13,12 @@ const DEFAULT_MAX_FILES = 200;
|
|
|
13
13
|
const DEFAULT_CONTEXT_LINES = 0;
|
|
14
14
|
const DEFAULT_MAX_SNIPPET_LINES = 250;
|
|
15
15
|
const RETURN_PREVIEW_MAX_CHARS = 120;
|
|
16
|
-
const
|
|
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'];
|
|
17
22
|
const IGNORED_DIRECTORIES = [
|
|
18
23
|
'.git',
|
|
19
24
|
'.mustflow/cache',
|
|
@@ -36,6 +41,9 @@ const ERROR_SYMBOL_READ_CODES = new Set([
|
|
|
36
41
|
'code_symbol_read_unreadable_path',
|
|
37
42
|
'code_symbol_read_invalid_range',
|
|
38
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',
|
|
39
47
|
'code_symbol_read_snippet_too_large',
|
|
40
48
|
]);
|
|
41
49
|
function toPosixPath(value) {
|
|
@@ -47,7 +55,7 @@ function normalizeRelativePath(value) {
|
|
|
47
55
|
function sha256Tagged(buffer) {
|
|
48
56
|
return `sha256:${createHash('sha256').update(buffer).digest('hex')}`;
|
|
49
57
|
}
|
|
50
|
-
function
|
|
58
|
+
function typescriptJavascriptLanguageForPath(filePath) {
|
|
51
59
|
switch (path.extname(filePath).toLowerCase()) {
|
|
52
60
|
case '.ts':
|
|
53
61
|
case '.mts':
|
|
@@ -67,6 +75,27 @@ function languageForPath(filePath) {
|
|
|
67
75
|
return null;
|
|
68
76
|
}
|
|
69
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
|
+
}
|
|
70
99
|
function isIgnoredDirectory(relativePath) {
|
|
71
100
|
const normalized = normalizeRelativePath(relativePath);
|
|
72
101
|
return IGNORED_DIRECTORIES.some((directory) => normalized === directory || normalized.startsWith(`${directory}/`));
|
|
@@ -206,6 +235,7 @@ function declarationFromLine(line) {
|
|
|
206
235
|
name: functionMatch.groups.name ?? '<anonymous>',
|
|
207
236
|
exported: Boolean(functionMatch.groups.exported),
|
|
208
237
|
async: Boolean(functionMatch.groups.async),
|
|
238
|
+
returnType: null,
|
|
209
239
|
};
|
|
210
240
|
}
|
|
211
241
|
const shapeMatch = /^(?<exported>export\s+)?(?<kind>class|interface|type|enum)\s+(?<name>[$A-Z_a-z][$\w]*)/u.exec(trimmed);
|
|
@@ -215,6 +245,7 @@ function declarationFromLine(line) {
|
|
|
215
245
|
name: shapeMatch.groups.name ?? '<anonymous>',
|
|
216
246
|
exported: Boolean(shapeMatch.groups.exported),
|
|
217
247
|
async: false,
|
|
248
|
+
returnType: null,
|
|
218
249
|
};
|
|
219
250
|
}
|
|
220
251
|
const variableMatch = /^(?<exported>export\s+)?(?:const|let|var)\s+(?<name>[$A-Z_a-z][$\w]*)\s*[:=]/u.exec(trimmed);
|
|
@@ -224,6 +255,7 @@ function declarationFromLine(line) {
|
|
|
224
255
|
name: variableMatch.groups.name ?? '<anonymous>',
|
|
225
256
|
exported: Boolean(variableMatch.groups.exported),
|
|
226
257
|
async: /async\s*(?:\(|[A-Z_a-z_$])/u.test(trimmed),
|
|
258
|
+
returnType: null,
|
|
227
259
|
};
|
|
228
260
|
}
|
|
229
261
|
return null;
|
|
@@ -256,7 +288,7 @@ function buildSignature(lines, startLine, endLine) {
|
|
|
256
288
|
if (trimmed.length > 0) {
|
|
257
289
|
parts.push(trimmed);
|
|
258
290
|
}
|
|
259
|
-
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)) {
|
|
260
292
|
break;
|
|
261
293
|
}
|
|
262
294
|
}
|
|
@@ -270,6 +302,9 @@ function extractExplicitReturnType(signature, match) {
|
|
|
270
302
|
if (match.kind !== 'function') {
|
|
271
303
|
return null;
|
|
272
304
|
}
|
|
305
|
+
if (match.returnType !== null) {
|
|
306
|
+
return match.returnType;
|
|
307
|
+
}
|
|
273
308
|
const functionReturnMatch = /\bfunction(?:\s+[$A-Z_a-z][$\w]*)?\s*\([^)]*\)\s*:\s*(?<returnType>[^={;]+?)(?=\s*(?:\{|$))/u.exec(signature);
|
|
274
309
|
if (functionReturnMatch?.groups?.returnType) {
|
|
275
310
|
return normalizeReturnType(functionReturnMatch.groups.returnType);
|
|
@@ -304,7 +339,7 @@ function extractArrowExpressionReturnPreview(signature) {
|
|
|
304
339
|
function isIdentifierCharacter(value) {
|
|
305
340
|
return typeof value === 'string' && /[$\w]/u.test(value);
|
|
306
341
|
}
|
|
307
|
-
function findReturnKeywordIndex(line) {
|
|
342
|
+
function findReturnKeywordIndex(line, lineCommentStart) {
|
|
308
343
|
let quote = null;
|
|
309
344
|
let escaped = false;
|
|
310
345
|
for (let index = 0; index < line.length; index += 1) {
|
|
@@ -324,7 +359,8 @@ function findReturnKeywordIndex(line) {
|
|
|
324
359
|
}
|
|
325
360
|
continue;
|
|
326
361
|
}
|
|
327
|
-
if (character === '/' && nextCharacter === '/')
|
|
362
|
+
if ((lineCommentStart === '//' && character === '/' && nextCharacter === '/') ||
|
|
363
|
+
(lineCommentStart === '#' && character === '#')) {
|
|
328
364
|
return -1;
|
|
329
365
|
}
|
|
330
366
|
if (character === '"' || character === "'" || character === '`') {
|
|
@@ -339,21 +375,21 @@ function findReturnKeywordIndex(line) {
|
|
|
339
375
|
}
|
|
340
376
|
return -1;
|
|
341
377
|
}
|
|
342
|
-
function extractReturnExpressionPreview(line) {
|
|
343
|
-
const returnIndex = findReturnKeywordIndex(line);
|
|
378
|
+
function extractReturnExpressionPreview(line, lineCommentStart) {
|
|
379
|
+
const returnIndex = findReturnKeywordIndex(line, lineCommentStart);
|
|
344
380
|
if (returnIndex < 0) {
|
|
345
381
|
return null;
|
|
346
382
|
}
|
|
347
|
-
const
|
|
348
|
-
|
|
349
|
-
|
|
383
|
+
const rawExpression = line.slice(returnIndex + 'return'.length);
|
|
384
|
+
const commentIndex = rawExpression.indexOf(lineCommentStart);
|
|
385
|
+
const expression = (commentIndex >= 0 ? rawExpression.slice(0, commentIndex) : rawExpression)
|
|
350
386
|
.trim()
|
|
351
387
|
.replace(/;\s*$/u, '')
|
|
352
388
|
.trim();
|
|
353
389
|
return expression.length === 0 ? null : truncateReturnPreview(expression);
|
|
354
390
|
}
|
|
355
|
-
function hasReturnStatement(line) {
|
|
356
|
-
return findReturnKeywordIndex(line) >= 0;
|
|
391
|
+
function hasReturnStatement(line, lineCommentStart) {
|
|
392
|
+
return findReturnKeywordIndex(line, lineCommentStart) >= 0;
|
|
357
393
|
}
|
|
358
394
|
function simpleBodyThrowsOnly(lines, startLine, endLine, returnType) {
|
|
359
395
|
let sawThrow = false;
|
|
@@ -373,7 +409,26 @@ function simpleBodyThrowsOnly(lines, startLine, endLine, returnType) {
|
|
|
373
409
|
}
|
|
374
410
|
return sawThrow && (!sawOtherExecutableLine || returnType === 'never');
|
|
375
411
|
}
|
|
376
|
-
function
|
|
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) {
|
|
377
432
|
const returnType = extractExplicitReturnType(signature, match);
|
|
378
433
|
if (match.kind !== 'function') {
|
|
379
434
|
return {
|
|
@@ -389,11 +444,11 @@ function extractReturnMetadata(lines, startLine, endLine, signature, match) {
|
|
|
389
444
|
let voidReturnCount = 0;
|
|
390
445
|
for (let index = startLine - 1; index < endLine; index += 1) {
|
|
391
446
|
const line = lines[index] ?? '';
|
|
392
|
-
if (!hasReturnStatement(line)) {
|
|
447
|
+
if (!hasReturnStatement(line, lineCommentStart)) {
|
|
393
448
|
continue;
|
|
394
449
|
}
|
|
395
450
|
returnLines.push(index + 1);
|
|
396
|
-
const preview = extractReturnExpressionPreview(line);
|
|
451
|
+
const preview = extractReturnExpressionPreview(line, lineCommentStart);
|
|
397
452
|
if (preview === null) {
|
|
398
453
|
voidReturnCount += 1;
|
|
399
454
|
}
|
|
@@ -402,11 +457,12 @@ function extractReturnMetadata(lines, startLine, endLine, signature, match) {
|
|
|
402
457
|
}
|
|
403
458
|
}
|
|
404
459
|
const arrowExpressionPreview = returnLines.length === 0 ? extractArrowExpressionReturnPreview(signature) : null;
|
|
460
|
+
const tailExpressionPreview = tailExpressionReturn && returnLines.length === 0 && returnType !== null ? extractTailExpressionReturnPreview(lines, startLine, endLine) : null;
|
|
405
461
|
let returnBehavior;
|
|
406
462
|
if (valueReturnPreviews.length > 0 && voidReturnCount > 0) {
|
|
407
463
|
returnBehavior = 'mixed';
|
|
408
464
|
}
|
|
409
|
-
else if (valueReturnPreviews.length > 0 || arrowExpressionPreview !== null) {
|
|
465
|
+
else if (valueReturnPreviews.length > 0 || arrowExpressionPreview !== null || tailExpressionPreview !== null) {
|
|
410
466
|
returnBehavior = 'value';
|
|
411
467
|
}
|
|
412
468
|
else if (voidReturnCount > 0) {
|
|
@@ -423,21 +479,141 @@ function extractReturnMetadata(lines, startLine, endLine, signature, match) {
|
|
|
423
479
|
return_behavior: returnBehavior,
|
|
424
480
|
return_count: returnLines.length,
|
|
425
481
|
return_lines: returnLines,
|
|
426
|
-
return_preview: valueReturnPreviews[0] ?? arrowExpressionPreview,
|
|
482
|
+
return_preview: valueReturnPreviews[0] ?? arrowExpressionPreview ?? tailExpressionPreview,
|
|
427
483
|
};
|
|
428
484
|
}
|
|
429
|
-
function
|
|
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) {
|
|
430
606
|
const lines = text.split(/\r\n|\r|\n/u);
|
|
431
607
|
const symbols = [];
|
|
432
608
|
for (const [index, line] of lines.entries()) {
|
|
433
|
-
const match =
|
|
609
|
+
const match = declarationForLine(line);
|
|
434
610
|
if (!match) {
|
|
435
611
|
continue;
|
|
436
612
|
}
|
|
437
613
|
const startLine = index + 1;
|
|
438
|
-
const endLine =
|
|
614
|
+
const endLine = endLineForMatch(lines, index);
|
|
439
615
|
const signature = buildSignature(lines, startLine, endLine);
|
|
440
|
-
const returnMetadata = extractReturnMetadata(lines, startLine, endLine, signature, match);
|
|
616
|
+
const returnMetadata = extractReturnMetadata(lines, startLine, endLine, signature, match, lineCommentStart, tailExpressionReturn);
|
|
441
617
|
symbols.push({
|
|
442
618
|
id: `${relativePath}:${startLine}:${match.kind}:${match.name}`,
|
|
443
619
|
path: relativePath,
|
|
@@ -456,6 +632,114 @@ function extractSymbols(relativePath, language, contentSha256, text) {
|
|
|
456
632
|
}
|
|
457
633
|
return symbols;
|
|
458
634
|
}
|
|
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
|
+
}
|
|
459
743
|
function sourceAnchorEndLine(lineStart, rawText) {
|
|
460
744
|
return lineStart + rawText.split(/\r\n|\r|\n/u).length - 1;
|
|
461
745
|
}
|
|
@@ -465,7 +749,11 @@ function canBridgeAnchorToSymbol(lines, anchorEndLine, symbolStartLine) {
|
|
|
465
749
|
}
|
|
466
750
|
for (let lineNumber = anchorEndLine + 1; lineNumber < symbolStartLine; lineNumber += 1) {
|
|
467
751
|
const trimmed = (lines[lineNumber - 1] ?? '').trim();
|
|
468
|
-
if (trimmed.length === 0 ||
|
|
752
|
+
if (trimmed.length === 0 ||
|
|
753
|
+
trimmed.startsWith('//') ||
|
|
754
|
+
trimmed.startsWith('#') ||
|
|
755
|
+
trimmed.startsWith('*') ||
|
|
756
|
+
trimmed.startsWith('@')) {
|
|
469
757
|
continue;
|
|
470
758
|
}
|
|
471
759
|
return false;
|
|
@@ -631,10 +919,12 @@ function createSymbolReadInputHash(policy, target, findings) {
|
|
|
631
919
|
: {
|
|
632
920
|
path: target.path,
|
|
633
921
|
sha256: target.sha256,
|
|
922
|
+
requested_anchor_id: target.requested_anchor_id,
|
|
634
923
|
requested_start_line: target.requested_start_line,
|
|
635
924
|
requested_end_line: target.requested_end_line,
|
|
636
925
|
context_start_line: target.context_start_line,
|
|
637
926
|
context_end_line: target.context_end_line,
|
|
927
|
+
anchor_id: target.anchor?.id ?? null,
|
|
638
928
|
},
|
|
639
929
|
input_errors: findings
|
|
640
930
|
.filter((finding) => ERROR_SYMBOL_READ_CODES.has(finding.code))
|
|
@@ -647,75 +937,66 @@ function createSymbolReadInputHash(policy, target, findings) {
|
|
|
647
937
|
};
|
|
648
938
|
return sha256Tagged(JSON.stringify(inputState));
|
|
649
939
|
}
|
|
650
|
-
|
|
651
|
-
const
|
|
652
|
-
const
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
const
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
issues.push(message);
|
|
676
|
-
findings.push(makeSymbolReadFinding('code_symbol_read_path_outside_root', 'high', options.path, policy.start_line, policy.end_line, message));
|
|
677
|
-
return createEmptySymbolReadReport(root, policy, findings, issues);
|
|
678
|
-
}
|
|
679
|
-
const language = languageForPath(absolutePath);
|
|
680
|
-
if (!language || !existsSync(absolutePath)) {
|
|
681
|
-
const message = `${relativePath} is not a supported existing code file.`;
|
|
682
|
-
issues.push(message);
|
|
683
|
-
findings.push(makeSymbolReadFinding('code_symbol_read_unreadable_path', 'high', relativePath, policy.start_line, policy.end_line, message));
|
|
684
|
-
return createEmptySymbolReadReport(root, policy, findings, issues);
|
|
685
|
-
}
|
|
686
|
-
let buffer;
|
|
687
|
-
try {
|
|
688
|
-
buffer = readFileInsideWithoutSymlinks(root, absolutePath, { maxBytes: policy.max_file_bytes });
|
|
689
|
-
}
|
|
690
|
-
catch (error) {
|
|
691
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
692
|
-
issues.push(`${relativePath}: ${message}`);
|
|
693
|
-
findings.push(makeSymbolReadFinding('code_symbol_read_unreadable_path', 'high', relativePath, policy.start_line, policy.end_line, message));
|
|
694
|
-
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
|
+
}
|
|
695
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) {
|
|
696
969
|
const contentSha256 = sha256Tagged(buffer);
|
|
697
970
|
const text = buffer.toString('utf8');
|
|
698
971
|
const lines = text.split(/\r\n|\r|\n/u);
|
|
699
|
-
if (policy.start_line > lines.length || (policy.end_line !== null && policy.end_line > lines.length)) {
|
|
700
|
-
const
|
|
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.`;
|
|
701
977
|
issues.push(message);
|
|
702
978
|
findings.push(makeSymbolReadFinding('code_symbol_read_invalid_range', 'high', relativePath, policy.start_line, policy.end_line, message));
|
|
703
979
|
return createEmptySymbolReadReport(root, policy, findings, issues);
|
|
704
980
|
}
|
|
705
981
|
const symbols = extractSymbols(relativePath, language, contentSha256, text);
|
|
706
|
-
const
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
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;
|
|
712
991
|
const resolvedEndLine = matchedSymbol?.end_line ?? policy.end_line;
|
|
713
992
|
if (resolvedEndLine === null) {
|
|
714
|
-
const message =
|
|
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.`;
|
|
715
996
|
issues.push(message);
|
|
716
|
-
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));
|
|
717
998
|
}
|
|
718
|
-
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);
|
|
719
1000
|
const contextEndLine = resolvedEndLine === null ? null : Math.min(lines.length, resolvedEndLine + policy.context_lines);
|
|
720
1001
|
const target = {
|
|
721
1002
|
path: relativePath,
|
|
@@ -723,12 +1004,14 @@ export function readCodeSymbol(projectRoot, options) {
|
|
|
723
1004
|
sha256: contentSha256,
|
|
724
1005
|
size_bytes: buffer.byteLength,
|
|
725
1006
|
line_count: lines.length,
|
|
1007
|
+
requested_anchor_id: policy.anchor_id,
|
|
726
1008
|
requested_start_line: policy.start_line,
|
|
727
1009
|
requested_end_line: policy.end_line,
|
|
728
1010
|
resolved_start_line: resolvedEndLine === null ? null : resolvedStartLine,
|
|
729
1011
|
resolved_end_line: resolvedEndLine,
|
|
730
1012
|
context_start_line: contextStartLine,
|
|
731
1013
|
context_end_line: contextEndLine,
|
|
1014
|
+
anchor,
|
|
732
1015
|
symbol: matchedSymbol,
|
|
733
1016
|
};
|
|
734
1017
|
let snippet = null;
|
|
@@ -769,3 +1052,101 @@ export function readCodeSymbol(projectRoot, options) {
|
|
|
769
1052
|
issues,
|
|
770
1053
|
};
|
|
771
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
|
+
}
|