mustflow 2.115.3 → 2.115.4

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.
@@ -1,4 +1,4 @@
1
- export const LOCAL_INDEX_SCHEMA_VERSION = '20';
1
+ export const LOCAL_INDEX_SCHEMA_VERSION = '21';
2
2
  export const LOCAL_INDEX_PARSER_VERSION = '1';
3
3
  export const DEFAULT_DATABASE_RELATIVE_PATH = '.mustflow/cache/mustflow.sqlite';
4
4
  export const LATEST_RUN_STATE_RELATIVE_PATH = '.mustflow/state/runs/latest.json';
@@ -1,6 +1,6 @@
1
- import { LOCAL_INDEX_SCHEMA_VERSION } from './constants.js';
1
+ import { DEFAULT_DATABASE_RELATIVE_PATH, LOCAL_INDEX_SCHEMA_VERSION } from './constants.js';
2
2
  import { hasTable, queryRows, readMetadataValue, toSearchString } from './database-read.js';
3
- import { normalizeIndexedFileSourceScope, readIndexedFileRecord } from './source-index.js';
3
+ import { collectSourceAnchorCandidatePaths, getSourceScopeHash, normalizeIndexedFileSourceScope, readIndexedFileRecord, readLocalIndexSourceConfig, } from './source-index.js';
4
4
  import { collectDocuments } from './workflow-documents.js';
5
5
  export function readStoredSchemaVersion(database) {
6
6
  return readMetadataValue(database, 'schema_version');
@@ -11,46 +11,76 @@ export function getStalePaths(projectRoot, database, options = {}) {
11
11
  if (schemaVersion !== LOCAL_INDEX_SCHEMA_VERSION) {
12
12
  return ['.mustflow/cache/mustflow.sqlite'];
13
13
  }
14
- if (hasTable(database, 'indexed_files')) {
15
- const stalePaths = new Set();
16
- const indexedRows = queryRows(database, 'SELECT path, source_scope, content_hash FROM indexed_files');
17
- const indexedPaths = new Set(indexedRows.map((row) => toSearchString(row.path)));
18
- for (const row of indexedRows) {
19
- const indexedPath = toSearchString(row.path);
20
- const sourceScope = normalizeIndexedFileSourceScope(toSearchString(row.source_scope));
21
- if (!includeState && sourceScope === 'state') {
22
- continue;
23
- }
24
- try {
25
- const current = readIndexedFileRecord(projectRoot, indexedPath, sourceScope);
26
- if (current.contentHash !== toSearchString(row.content_hash)) {
27
- stalePaths.add(indexedPath);
28
- }
29
- }
30
- catch {
14
+ if (!hasTable(database, 'indexed_files') ||
15
+ !hasTable(database, 'indexed_source_candidates') ||
16
+ !hasTable(database, 'source_anchors') ||
17
+ !hasTable(database, 'source_anchor_status')) {
18
+ return [DEFAULT_DATABASE_RELATIVE_PATH];
19
+ }
20
+ const stalePaths = new Set();
21
+ const indexedRows = queryRows(database, 'SELECT path, source_scope, content_hash FROM indexed_files');
22
+ const indexedPaths = new Set(indexedRows.map((row) => toSearchString(row.path)));
23
+ const indexedSourcePaths = new Set(queryRows(database, 'SELECT path FROM indexed_source_candidates').map((row) => toSearchString(row.path)));
24
+ for (const sourcePath of indexedSourcePaths) {
25
+ if (!indexedPaths.has(sourcePath)) {
26
+ stalePaths.add(sourcePath);
27
+ }
28
+ }
29
+ for (const row of indexedRows) {
30
+ const indexedPath = toSearchString(row.path);
31
+ const sourceScope = normalizeIndexedFileSourceScope(toSearchString(row.source_scope));
32
+ if (!includeState && sourceScope === 'state') {
33
+ continue;
34
+ }
35
+ try {
36
+ const current = readIndexedFileRecord(projectRoot, indexedPath, sourceScope);
37
+ if (current.contentHash !== toSearchString(row.content_hash)) {
31
38
  stalePaths.add(indexedPath);
32
39
  }
33
40
  }
34
- for (const document of collectDocuments(projectRoot)) {
35
- if (!indexedPaths.has(document.path)) {
36
- stalePaths.add(document.path);
37
- }
41
+ catch {
42
+ stalePaths.add(indexedPath);
38
43
  }
39
- return Array.from(stalePaths).sort((left, right) => left.localeCompare(right));
40
44
  }
41
- const indexedRows = queryRows(database, 'SELECT path, content_hash FROM documents');
42
- const indexedHashes = new Map(indexedRows.map((row) => [toSearchString(row.path), toSearchString(row.content_hash)]));
43
- const currentDocuments = collectDocuments(projectRoot);
44
- const currentHashes = new Map(currentDocuments.map((document) => [document.path, document.contentHash]));
45
- const stalePaths = new Set();
46
- for (const [indexedPath, indexedHash] of indexedHashes) {
47
- if (currentHashes.get(indexedPath) !== indexedHash) {
48
- stalePaths.add(indexedPath);
45
+ for (const document of collectDocuments(projectRoot)) {
46
+ if (!indexedPaths.has(document.path)) {
47
+ stalePaths.add(document.path);
49
48
  }
50
49
  }
51
- for (const currentPath of currentHashes.keys()) {
52
- if (!indexedHashes.has(currentPath)) {
53
- stalePaths.add(currentPath);
50
+ const sourceIndexEnabledValue = readMetadataValue(database, 'source_index_enabled');
51
+ if (sourceIndexEnabledValue !== 'true' && sourceIndexEnabledValue !== 'false') {
52
+ stalePaths.add(DEFAULT_DATABASE_RELATIVE_PATH);
53
+ }
54
+ else {
55
+ const sourceIndexEnabled = sourceIndexEnabledValue === 'true';
56
+ const hasIndexedSourceScope = indexedRows.some((row) => normalizeIndexedFileSourceScope(toSearchString(row.source_scope)) === 'source_anchor');
57
+ const hasSourceAnchorRows = hasTable(database, 'source_anchors') && queryRows(database, 'SELECT id FROM source_anchors LIMIT 1').length > 0;
58
+ if (!sourceIndexEnabled && (indexedSourcePaths.size > 0 || hasIndexedSourceScope || hasSourceAnchorRows)) {
59
+ stalePaths.add(DEFAULT_DATABASE_RELATIVE_PATH);
60
+ }
61
+ try {
62
+ const sourceConfig = readLocalIndexSourceConfig(projectRoot);
63
+ const storedSourceScopeHash = readMetadataValue(database, 'source_scope_hash');
64
+ const currentSourceScopeHash = getSourceScopeHash(sourceIndexEnabled, sourceConfig);
65
+ if (storedSourceScopeHash !== currentSourceScopeHash) {
66
+ stalePaths.add(DEFAULT_DATABASE_RELATIVE_PATH);
67
+ }
68
+ if (sourceIndexEnabled) {
69
+ const currentSourcePaths = new Set(collectSourceAnchorCandidatePaths(projectRoot, sourceConfig));
70
+ for (const sourcePath of currentSourcePaths) {
71
+ if (!indexedSourcePaths.has(sourcePath)) {
72
+ stalePaths.add(sourcePath);
73
+ }
74
+ }
75
+ for (const sourcePath of indexedSourcePaths) {
76
+ if (!currentSourcePaths.has(sourcePath)) {
77
+ stalePaths.add(sourcePath);
78
+ }
79
+ }
80
+ }
81
+ }
82
+ catch {
83
+ stalePaths.add(DEFAULT_DATABASE_RELATIVE_PATH);
54
84
  }
55
85
  }
56
86
  return Array.from(stalePaths).sort((left, right) => left.localeCompare(right));
@@ -158,6 +158,14 @@ function indexedFilesMatch(database, currentFiles) {
158
158
  }
159
159
  return true;
160
160
  }
161
+ function indexedSourceCandidatesMatch(database, currentPaths) {
162
+ if (!hasTable(database, 'indexed_source_candidates')) {
163
+ return false;
164
+ }
165
+ const storedPaths = new Set(queryRows(database, 'SELECT path FROM indexed_source_candidates').map((row) => toSearchString(row.path)));
166
+ const currentPathSet = new Set(currentPaths);
167
+ return storedPaths.size === currentPathSet.size && [...storedPaths].every((storedPath) => currentPathSet.has(storedPath));
168
+ }
161
169
  const INDEXED_FILE_MTIME_TOLERANCE_MS = 1;
162
170
  function indexedFileMtimeMsEqual(storedMtimeMs, currentMtimeMs) {
163
171
  return storedMtimeMs !== null && Math.abs(storedMtimeMs - currentMtimeMs) <= INDEXED_FILE_MTIME_TOLERANCE_MS;
@@ -183,8 +191,8 @@ function indexedFileMetadataMatch(database, currentFiles) {
183
191
  }
184
192
  return true;
185
193
  }
186
- async function readIncrementalPreflightReuse(SQL, databasePath, projectRoot, currentFiles, sourceScopeHash, dryRun, indexMode) {
187
- if (!currentFiles) {
194
+ async function readIncrementalPreflightReuse(SQL, databasePath, projectRoot, currentFiles, currentSourceCandidatePaths, sourceScopeHash, dryRun, indexMode) {
195
+ if (!currentFiles || !currentSourceCandidatePaths) {
188
196
  return { result: null, rebuildReason: null };
189
197
  }
190
198
  if (!existsSync(databasePath)) {
@@ -205,6 +213,9 @@ async function readIncrementalPreflightReuse(SQL, databasePath, projectRoot, cur
205
213
  if (!hasTable(database, 'indexed_files')) {
206
214
  return { result: null, rebuildReason: 'indexed_files_missing' };
207
215
  }
216
+ if (!indexedSourceCandidatesMatch(database, currentSourceCandidatePaths)) {
217
+ return { result: null, rebuildReason: 'source_scope_mismatch' };
218
+ }
208
219
  if (!indexedFileMetadataMatch(database, currentFiles)) {
209
220
  return { result: null, rebuildReason: 'file_fingerprint_mismatch' };
210
221
  }
@@ -225,7 +236,7 @@ async function readIncrementalPreflightReuse(SQL, databasePath, projectRoot, cur
225
236
  database?.close();
226
237
  }
227
238
  }
228
- async function readIncrementalReuseDecision(SQL, databasePath, currentFiles, sourceScopeHash) {
239
+ async function readIncrementalReuseDecision(SQL, databasePath, currentFiles, currentSourceCandidatePaths, sourceScopeHash) {
229
240
  if (!existsSync(databasePath)) {
230
241
  return { reusable: false, rebuildReason: 'missing_index', capabilities: null };
231
242
  }
@@ -244,6 +255,9 @@ async function readIncrementalReuseDecision(SQL, databasePath, currentFiles, sou
244
255
  if (!hasTable(database, 'indexed_files')) {
245
256
  return { reusable: false, rebuildReason: 'indexed_files_missing', capabilities: null };
246
257
  }
258
+ if (!indexedSourceCandidatesMatch(database, currentSourceCandidatePaths)) {
259
+ return { reusable: false, rebuildReason: 'source_scope_mismatch', capabilities: null };
260
+ }
247
261
  if (!indexedFilesMatch(database, currentFiles)) {
248
262
  return { reusable: false, rebuildReason: 'file_fingerprint_mismatch', capabilities: null };
249
263
  }
@@ -284,7 +298,7 @@ export async function createLocalIndex(projectRoot, options = {}) {
284
298
  capabilityDatabase.close();
285
299
  if (incremental) {
286
300
  const preflightFiles = collectFastPreflightIndexedFileMetadataRecords(projectRoot, includeSource, sourceConfig);
287
- const preflightReuse = await readIncrementalPreflightReuse(SQL, databasePath, projectRoot, preflightFiles, sourceScopeHash, dryRun, indexMode);
301
+ const preflightReuse = await readIncrementalPreflightReuse(SQL, databasePath, projectRoot, preflightFiles?.records ?? null, preflightFiles?.sourceCandidatePaths ?? null, sourceScopeHash, dryRun, indexMode);
288
302
  if (preflightReuse.result) {
289
303
  return preflightReuse.result;
290
304
  }
@@ -307,16 +321,16 @@ export async function createLocalIndex(projectRoot, options = {}) {
307
321
  const verificationEvidence = createVerificationEvidenceIndex(projectRoot);
308
322
  const indexedFiles = collectIndexedFileRecords(projectRoot, documents, sourceAnchors, sourceAnchorCandidatePaths);
309
323
  if (incremental) {
310
- const reuseDecision = await readIncrementalReuseDecision(SQL, databasePath, indexedFiles, sourceScopeHash);
324
+ const reuseDecision = await readIncrementalReuseDecision(SQL, databasePath, indexedFiles, sourceAnchorCandidatePaths, sourceScopeHash);
311
325
  reusedExisting = reuseDecision.reusable;
312
- rebuildReason = reuseDecision.rebuildReason ?? rebuildReason;
326
+ rebuildReason = reuseDecision.reusable ? null : (reuseDecision.rebuildReason ?? rebuildReason);
313
327
  capabilities = reuseDecision.capabilities ?? capabilities;
314
328
  }
315
329
  if (!dryRun && !reusedExisting) {
316
330
  const database = new SQL.Database();
317
331
  try {
318
332
  createSchema(database, capabilities);
319
- populateDatabase(database, capabilities, documents, skills, skillRoutes, commandIntents, sourceAnchors, indexedFiles, verificationEvidence, indexMode, sourceScopeHash, includeSource, new Date().toISOString());
333
+ populateDatabase(database, capabilities, documents, skills, skillRoutes, commandIntents, sourceAnchors, indexedFiles, sourceAnchorCandidatePaths, verificationEvidence, indexMode, sourceScopeHash, includeSource, new Date().toISOString());
320
334
  writeFileInsideWithoutSymlinks(projectRoot, databasePath, database.export());
321
335
  }
322
336
  finally {
@@ -702,10 +716,13 @@ export async function readLocalSourceAnchorCheckWarnings(projectRoot) {
702
716
  if (readMetadataValue(database, 'source_index_enabled') !== 'true') {
703
717
  return [];
704
718
  }
705
- if (!hasTable(database, 'indexed_files') || !hasTable(database, 'source_anchors') || !hasTable(database, 'source_anchor_status')) {
719
+ if (!hasTable(database, 'indexed_files') ||
720
+ !hasTable(database, 'indexed_source_candidates') ||
721
+ !hasTable(database, 'source_anchors') ||
722
+ !hasTable(database, 'source_anchor_status')) {
706
723
  return ['source anchor local index is missing source-anchor tables; refresh with mf index --source'];
707
724
  }
708
- const indexedSourcePaths = new Set(queryRows(database, 'SELECT path FROM indexed_files WHERE source_scope = "source_anchor"')
725
+ const indexedSourcePaths = new Set(queryRows(database, 'SELECT path FROM indexed_source_candidates')
709
726
  .map((row) => toSearchString(row.path))
710
727
  .filter(Boolean));
711
728
  const staleSourcePaths = getStalePaths(projectRoot, database, { includeState: false }).filter((stalePath) => indexedSourcePaths.has(stalePath));
@@ -172,7 +172,7 @@ function rollbackTransaction(database) {
172
172
  // Keep the original indexing failure as the actionable error.
173
173
  }
174
174
  }
175
- export function populateDatabase(database, capabilities, documents, skills, skillRoutes, commandIntents, sourceAnchors, indexedFiles, verificationEvidence, indexMode, sourceScopeHash, sourceIndexEnabled, indexedAt) {
175
+ export function populateDatabase(database, capabilities, documents, skills, skillRoutes, commandIntents, sourceAnchors, indexedFiles, indexedSourceCandidates, verificationEvidence, indexMode, sourceScopeHash, sourceIndexEnabled, indexedAt) {
176
176
  const sourceAnchorRiskSignals = createSourceAnchorRiskSignals(sourceAnchors);
177
177
  database.run('BEGIN TRANSACTION');
178
178
  try {
@@ -223,6 +223,9 @@ export function populateDatabase(database, capabilities, documents, skills, skil
223
223
  LOCAL_INDEX_PARSER_VERSION,
224
224
  ]);
225
225
  }
226
+ for (const sourceCandidatePath of indexedSourceCandidates) {
227
+ database.run('INSERT INTO indexed_source_candidates (path) VALUES (?)', [sourceCandidatePath]);
228
+ }
226
229
  for (const document of documents) {
227
230
  database.run('INSERT INTO documents (path, type, title, locale, revision, content_hash, content_snippet) VALUES (?, ?, ?, ?, ?, ?, ?)', [
228
231
  document.path,
@@ -1,5 +1,10 @@
1
1
  import { SEARCH_BACKEND_FTS5 } from './constants.js';
2
2
  export function createSchema(database, capabilities) {
3
+ database.run('PRAGMA foreign_keys = ON');
4
+ const [foreignKeyResult] = database.exec('PRAGMA foreign_keys');
5
+ if (foreignKeyResult?.values[0]?.[0] !== 1) {
6
+ throw new Error('SQLite foreign-key enforcement is unavailable for the local index.');
7
+ }
3
8
  database.run(`
4
9
  CREATE TABLE metadata (
5
10
  key TEXT PRIMARY KEY,
@@ -17,6 +22,11 @@ CREATE TABLE indexed_files (
17
22
  parser_version TEXT NOT NULL
18
23
  );
19
24
 
25
+ CREATE TABLE indexed_source_candidates (
26
+ path TEXT PRIMARY KEY,
27
+ FOREIGN KEY (path) REFERENCES indexed_files(path) ON UPDATE RESTRICT ON DELETE RESTRICT
28
+ );
29
+
20
30
  CREATE TABLE documents (
21
31
  path TEXT PRIMARY KEY,
22
32
  type TEXT NOT NULL,
@@ -1,11 +1,34 @@
1
- import { existsSync, readFileSync, statSync } from 'node:fs';
1
+ import { existsSync, statSync } from 'node:fs';
2
2
  import path from 'node:path';
3
3
  import { isRecord, readStringArray } from '../command-contract.js';
4
+ import { MUSTFLOW_TEXT_MAX_BYTES, mustflowProjectPath } from '../mustflow-read.js';
4
5
  import { readMustflowTomlFile } from '../toml.js';
6
+ import { ensureInsideWithoutSymlinks, readFileInsideWithoutSymlinks } from '../../../core/safe-filesystem.js';
5
7
  import { listSourceAnchorFiles } from '../../../core/source-anchors.js';
6
8
  import { INDEX_CONFIG_RELATIVE_PATH, LATEST_RUN_STATE_RELATIVE_PATH, MUSTFLOW_RELATIVE_PATH, SOURCE_INDEX_MAX_FILE_BYTES, } from './constants.js';
7
9
  import { sha256Bytes, sha256Text } from './hashing.js';
8
10
  import { getExistingIndexablePaths } from './workflow-documents.js';
11
+ function indexedProjectPath(projectRoot, relativePath) {
12
+ if (relativePath.length === 0 ||
13
+ relativePath.includes('\0') ||
14
+ relativePath.includes('\\') ||
15
+ path.posix.isAbsolute(relativePath) ||
16
+ path.win32.isAbsolute(relativePath)) {
17
+ throw new Error(`Indexed file path must be a canonical project-relative path: ${relativePath}`);
18
+ }
19
+ const segments = relativePath.split('/');
20
+ if (segments.some((segment) => segment.length === 0 || segment === '.' || segment === '..')) {
21
+ throw new Error(`Indexed file path must be a canonical project-relative path: ${relativePath}`);
22
+ }
23
+ const fullPath = mustflowProjectPath(projectRoot, relativePath);
24
+ ensureInsideWithoutSymlinks(projectRoot, fullPath);
25
+ return fullPath;
26
+ }
27
+ function readIndexedFileBytes(projectRoot, relativePath) {
28
+ return readFileInsideWithoutSymlinks(projectRoot, indexedProjectPath(projectRoot, relativePath), {
29
+ maxBytes: MUSTFLOW_TEXT_MAX_BYTES,
30
+ });
31
+ }
9
32
  function readIndexToml(projectRoot) {
10
33
  const indexConfigPath = path.join(projectRoot, ...INDEX_CONFIG_RELATIVE_PATH.split('/'));
11
34
  if (!existsSync(indexConfigPath)) {
@@ -69,13 +92,13 @@ export function readIndexedFileRecord(projectRoot, relativePath, sourceScope, co
69
92
  const metadata = readIndexedFileMetadataRecord(projectRoot, relativePath, sourceScope);
70
93
  return {
71
94
  ...metadata,
72
- contentHash: contentHash ?? sha256Bytes(readFileSync(path.join(projectRoot, ...relativePath.split('/')))),
95
+ contentHash: contentHash ?? sha256Bytes(readIndexedFileBytes(projectRoot, relativePath)),
73
96
  };
74
97
  }
75
98
  function hashIndexedFileMetadataRecord(projectRoot, metadata) {
76
99
  return {
77
100
  ...metadata,
78
- contentHash: sha256Bytes(readFileSync(path.join(projectRoot, ...metadata.path.split('/')))),
101
+ contentHash: sha256Bytes(readIndexedFileBytes(projectRoot, metadata.path)),
79
102
  };
80
103
  }
81
104
  function tryHashIndexedFileMetadataRecord(projectRoot, metadata) {
@@ -92,8 +115,11 @@ export function hashIndexedFileMetadataRecords(projectRoot, metadataRecords) {
92
115
  .filter((record) => Boolean(record));
93
116
  }
94
117
  export function readIndexedFileMetadataRecord(projectRoot, relativePath, sourceScope) {
95
- const fullPath = path.join(projectRoot, ...relativePath.split('/'));
118
+ const fullPath = indexedProjectPath(projectRoot, relativePath);
96
119
  const stats = statSync(fullPath);
120
+ if (!stats.isFile()) {
121
+ throw new Error(`Indexed path must be a regular file: ${relativePath}`);
122
+ }
97
123
  return {
98
124
  path: relativePath,
99
125
  sourceScope,
@@ -117,10 +143,7 @@ export function collectIndexedFileRecords(projectRoot, documents, sourceAnchors,
117
143
  const sourcePaths = new Set([...sourceAnchorCandidatePaths, ...sourceAnchors.map((anchor) => anchor.path)]);
118
144
  for (const anchorPath of [...sourcePaths].sort((left, right) => left.localeCompare(right))) {
119
145
  if (!records.has(anchorPath)) {
120
- const record = tryReadIndexedFileRecord(projectRoot, anchorPath, 'source_anchor');
121
- if (record) {
122
- records.set(anchorPath, record);
123
- }
146
+ records.set(anchorPath, readIndexedFileRecord(projectRoot, anchorPath, 'source_anchor'));
124
147
  }
125
148
  }
126
149
  if (existsSync(path.join(projectRoot, ...LATEST_RUN_STATE_RELATIVE_PATH.split('/')))) {
@@ -132,19 +155,25 @@ export function collectIndexedFileRecords(projectRoot, documents, sourceAnchors,
132
155
  return [...records.values()].sort((left, right) => left.path.localeCompare(right.path));
133
156
  }
134
157
  export function collectSourceAnchorCandidatePaths(projectRoot, sourceConfig) {
135
- return listSourceAnchorFiles(projectRoot, {
158
+ const paths = listSourceAnchorFiles(projectRoot, {
136
159
  ...sourceConfig,
137
160
  excludeGeneratedOrVendor: true,
138
161
  });
162
+ for (const relativePath of paths) {
163
+ indexedProjectPath(projectRoot, relativePath);
164
+ }
165
+ return paths;
139
166
  }
140
167
  export function collectFastPreflightIndexedFileMetadataRecords(projectRoot, includeSource, sourceConfig) {
141
168
  const records = new Map();
169
+ let sourceCandidatePaths = [];
142
170
  for (const relativePath of getExistingIndexablePaths(projectRoot)) {
143
171
  records.set(relativePath, readIndexedFileMetadataRecord(projectRoot, relativePath, 'workflow'));
144
172
  }
145
173
  if (includeSource) {
146
174
  try {
147
- for (const sourcePath of collectSourceAnchorCandidatePaths(projectRoot, sourceConfig)) {
175
+ sourceCandidatePaths = collectSourceAnchorCandidatePaths(projectRoot, sourceConfig);
176
+ for (const sourcePath of sourceCandidatePaths) {
148
177
  if (!records.has(sourcePath)) {
149
178
  records.set(sourcePath, readIndexedFileMetadataRecord(projectRoot, sourcePath, 'source_anchor'));
150
179
  }
@@ -157,5 +186,8 @@ export function collectFastPreflightIndexedFileMetadataRecords(projectRoot, incl
157
186
  if (existsSync(path.join(projectRoot, ...LATEST_RUN_STATE_RELATIVE_PATH.split('/')))) {
158
187
  records.set(LATEST_RUN_STATE_RELATIVE_PATH, readIndexedFileMetadataRecord(projectRoot, LATEST_RUN_STATE_RELATIVE_PATH, 'state'));
159
188
  }
160
- return [...records.values()].sort((left, right) => left.path.localeCompare(right.path));
189
+ return {
190
+ records: [...records.values()].sort((left, right) => left.path.localeCompare(right.path)),
191
+ sourceCandidatePaths,
192
+ };
161
193
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mustflow",
3
- "version": "2.115.3",
3
+ "version": "2.115.4",
4
4
  "description": "Agent workflow documents and CLI for mustflow repository roots.",
5
5
  "type": "module",
6
6
  "license": "MIT-0",
@@ -1,6 +1,6 @@
1
1
  id = "default"
2
2
  name = "default"
3
- version = "2.115.3"
3
+ version = "2.115.4"
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"