mustflow 2.75.1 → 2.75.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -14,15 +14,15 @@ export const SCRIPT_PACKS = [
14
14
  summaryKey: 'scriptPack.script.codeOutline.summary',
15
15
  actions: ['scan'],
16
16
  useWhen: [
17
- 'Scan TypeScript or JavaScript files for symbol headers before reading large source files chunk by chunk.',
18
- 'Build a bounded source outline with file paths, line ranges, signatures, export flags, and content hashes for codebase orientation.',
17
+ 'Scan TypeScript or JavaScript files for symbol headers and source anchors before reading large source files chunk by chunk.',
18
+ 'Build a bounded source outline with file paths, line ranges, signatures, export flags, source-anchor metadata, and content hashes for codebase orientation.',
19
19
  ],
20
20
  phases: ['before_change', 'during_change', 'review'],
21
21
  readOnly: true,
22
22
  mutates: false,
23
23
  network: false,
24
24
  inputs: ['path', 'max_files', 'max_file_bytes'],
25
- outputs: ['human_summary', 'json_report', 'symbol_outline'],
25
+ outputs: ['human_summary', 'json_report', 'symbol_outline', 'source_anchors'],
26
26
  relatedSkills: [
27
27
  'codebase-orientation',
28
28
  'javascript-code-change',
@@ -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 { 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}`;
@@ -455,7 +456,60 @@ function extractSymbols(relativePath, language, contentSha256, text) {
455
456
  }
456
457
  return symbols;
457
458
  }
458
- function createOutlineInputHash(policy, files, findings) {
459
+ function sourceAnchorEndLine(lineStart, rawText) {
460
+ return lineStart + rawText.split(/\r\n|\r|\n/u).length - 1;
461
+ }
462
+ function canBridgeAnchorToSymbol(lines, anchorEndLine, symbolStartLine) {
463
+ if (symbolStartLine <= anchorEndLine) {
464
+ return false;
465
+ }
466
+ for (let lineNumber = anchorEndLine + 1; lineNumber < symbolStartLine; lineNumber += 1) {
467
+ const trimmed = (lines[lineNumber - 1] ?? '').trim();
468
+ if (trimmed.length === 0 || trimmed.startsWith('//') || trimmed.startsWith('*') || trimmed.startsWith('@')) {
469
+ continue;
470
+ }
471
+ return false;
472
+ }
473
+ return true;
474
+ }
475
+ function findAnchorTargetSymbol(relativePath, lines, anchorEndLine, symbols) {
476
+ const followingSymbols = symbols
477
+ .filter((symbol) => symbol.path === relativePath && symbol.start_line > anchorEndLine)
478
+ .sort((left, right) => left.start_line - right.start_line);
479
+ const target = followingSymbols[0] ?? null;
480
+ if (!target || !canBridgeAnchorToSymbol(lines, anchorEndLine, target.start_line)) {
481
+ return null;
482
+ }
483
+ return target;
484
+ }
485
+ function extractAnchors(relativePath, lines, text, symbols) {
486
+ const anchors = [];
487
+ for (const anchor of parseSourceAnchorsInContent(relativePath, text)) {
488
+ if (!anchor.idValid || sourceAnchorTextContainsSecretLike(anchor.rawText)) {
489
+ continue;
490
+ }
491
+ const lineEnd = sourceAnchorEndLine(anchor.lineStart, anchor.rawText);
492
+ const target = findAnchorTargetSymbol(relativePath, lines, lineEnd, symbols);
493
+ anchors.push({
494
+ id: anchor.rawId,
495
+ path: relativePath,
496
+ line_start: anchor.lineStart,
497
+ line_end: lineEnd,
498
+ purpose: anchor.fields.get('purpose') ?? null,
499
+ search: splitSourceAnchorList(anchor.fields.get('search')),
500
+ invariant: anchor.fields.get('invariant') ?? null,
501
+ risk: splitSourceAnchorList(anchor.fields.get('risk')),
502
+ navigation_only: true,
503
+ can_instruct_agent: false,
504
+ target_symbol_id: target?.id ?? null,
505
+ target_kind: target?.kind ?? null,
506
+ target_name: target?.name ?? null,
507
+ target_start_line: target?.start_line ?? null,
508
+ });
509
+ }
510
+ return anchors;
511
+ }
512
+ function createOutlineInputHash(policy, files, anchors, findings) {
459
513
  const inputState = {
460
514
  policy,
461
515
  files: files.map((file) => ({
@@ -464,6 +518,13 @@ function createOutlineInputHash(policy, files, findings) {
464
518
  size_bytes: file.size_bytes,
465
519
  symbol_count: file.symbol_count,
466
520
  })),
521
+ anchors: anchors.map((anchor) => ({
522
+ id: anchor.id,
523
+ path: anchor.path,
524
+ line_start: anchor.line_start,
525
+ line_end: anchor.line_end,
526
+ target_symbol_id: anchor.target_symbol_id,
527
+ })),
467
528
  input_errors: findings
468
529
  .filter((finding) => ERROR_OUTLINE_CODES.has(finding.code))
469
530
  .map((finding) => ({ code: finding.code, path: finding.path })),
@@ -486,6 +547,7 @@ export function inspectCodeOutline(projectRoot, options) {
486
547
  ignored_directories: [...IGNORED_DIRECTORIES],
487
548
  };
488
549
  const files = [];
550
+ const anchors = [];
489
551
  const symbols = [];
490
552
  const findings = [];
491
553
  const issues = [];
@@ -506,15 +568,18 @@ export function inspectCodeOutline(projectRoot, options) {
506
568
  }
507
569
  const contentSha256 = sha256Tagged(buffer);
508
570
  const text = buffer.toString('utf8');
571
+ const lines = text.split(/\r\n|\r|\n/u);
509
572
  const fileSymbols = extractSymbols(candidate.relativePath, candidate.language, contentSha256, text);
573
+ const fileAnchors = extractAnchors(candidate.relativePath, lines, text, fileSymbols);
510
574
  symbols.push(...fileSymbols);
575
+ anchors.push(...fileAnchors);
511
576
  files.push({
512
577
  kind: 'source_file',
513
578
  path: candidate.relativePath,
514
579
  language: candidate.language,
515
580
  sha256: contentSha256,
516
581
  size_bytes: buffer.byteLength,
517
- line_count: text.split(/\r\n|\r|\n/u).length,
582
+ line_count: lines.length,
518
583
  symbol_count: fileSymbols.length,
519
584
  });
520
585
  }
@@ -530,8 +595,9 @@ export function inspectCodeOutline(projectRoot, options) {
530
595
  ok: status === 'passed',
531
596
  mustflow_root: root,
532
597
  policy,
533
- input_hash: createOutlineInputHash(policy, files, findings),
598
+ input_hash: createOutlineInputHash(policy, files, anchors, findings),
534
599
  files,
600
+ anchors: anchors.sort((left, right) => left.path.localeCompare(right.path) || left.line_start - right.line_start),
535
601
  symbols: symbols.sort((left, right) => left.path.localeCompare(right.path) || left.start_line - right.start_line),
536
602
  findings,
537
603
  issues,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mustflow",
3
- "version": "2.75.1",
3
+ "version": "2.75.2",
4
4
  "description": "Agent workflow documents and CLI for mustflow repository roots.",
5
5
  "type": "module",
6
6
  "license": "MIT-0",
package/schemas/README.md CHANGED
@@ -67,7 +67,8 @@ Current schemas:
67
67
  - `code-outline-report.schema.json`: output of
68
68
  `mf script-pack run code/outline scan <path...> --json`, containing a bounded TypeScript and
69
69
  JavaScript source outline with file hashes, symbol names, declaration kinds, line ranges,
70
- signatures, export flags, static return metadata, and stable input-limit finding codes
70
+ signatures, export flags, source-anchor navigation metadata, static return metadata, and stable
71
+ input-limit finding codes
71
72
  - `code-symbol-read-report.schema.json`: output of
72
73
  `mf script-pack run code/symbol-read read <path> --start-line <line> --json`, containing a
73
74
  focused source snippet selected by outline symbol line or explicit line range with file hash,
@@ -17,6 +17,7 @@
17
17
  "policy",
18
18
  "input_hash",
19
19
  "files",
20
+ "anchors",
20
21
  "symbols",
21
22
  "findings",
22
23
  "issues"
@@ -37,6 +38,10 @@
37
38
  "type": "array",
38
39
  "items": { "$ref": "#/$defs/file" }
39
40
  },
41
+ "anchors": {
42
+ "type": "array",
43
+ "items": { "$ref": "#/$defs/anchor" }
44
+ },
40
45
  "symbols": {
41
46
  "type": "array",
42
47
  "items": { "$ref": "#/$defs/symbol" }
@@ -90,6 +95,47 @@
90
95
  "symbol_count": { "type": "integer", "minimum": 0 }
91
96
  }
92
97
  },
98
+ "anchor": {
99
+ "type": "object",
100
+ "additionalProperties": false,
101
+ "required": [
102
+ "id",
103
+ "path",
104
+ "line_start",
105
+ "line_end",
106
+ "purpose",
107
+ "search",
108
+ "invariant",
109
+ "risk",
110
+ "navigation_only",
111
+ "can_instruct_agent",
112
+ "target_symbol_id",
113
+ "target_kind",
114
+ "target_name",
115
+ "target_start_line"
116
+ ],
117
+ "properties": {
118
+ "id": { "type": "string" },
119
+ "path": { "type": "string" },
120
+ "line_start": { "type": "integer", "minimum": 1 },
121
+ "line_end": { "type": "integer", "minimum": 1 },
122
+ "purpose": { "type": ["string", "null"] },
123
+ "search": { "$ref": "#/$defs/stringArray" },
124
+ "invariant": { "type": ["string", "null"] },
125
+ "risk": { "$ref": "#/$defs/stringArray" },
126
+ "navigation_only": { "const": true },
127
+ "can_instruct_agent": { "const": false },
128
+ "target_symbol_id": { "type": ["string", "null"] },
129
+ "target_kind": {
130
+ "anyOf": [
131
+ { "$ref": "#/$defs/symbolKind" },
132
+ { "type": "null" }
133
+ ]
134
+ },
135
+ "target_name": { "type": ["string", "null"] },
136
+ "target_start_line": { "type": ["integer", "null"], "minimum": 1 }
137
+ }
138
+ },
93
139
  "symbol": {
94
140
  "type": "object",
95
141
  "additionalProperties": false,
@@ -1,6 +1,6 @@
1
1
  id = "default"
2
2
  name = "default"
3
- version = "2.75.1"
3
+ version = "2.75.2"
4
4
  description = "Minimal workflow for LLM agents to read, edit, and verify their work in a repository."
5
5
  common_root = "common"
6
6
  locales_root = "locales"