mustflow 1.31.0 → 2.16.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 (66) hide show
  1. package/README.md +23 -9
  2. package/dist/cli/commands/classify.js +61 -6
  3. package/dist/cli/commands/contract-lint.js +13 -4
  4. package/dist/cli/commands/dashboard.js +77 -2
  5. package/dist/cli/commands/explain-verify.js +11 -1
  6. package/dist/cli/commands/index.js +14 -0
  7. package/dist/cli/commands/run.js +4 -1
  8. package/dist/cli/commands/verify.js +986 -43
  9. package/dist/cli/i18n/en.js +61 -10
  10. package/dist/cli/i18n/es.js +61 -10
  11. package/dist/cli/i18n/fr.js +61 -10
  12. package/dist/cli/i18n/hi.js +61 -10
  13. package/dist/cli/i18n/ko.js +61 -10
  14. package/dist/cli/i18n/zh.js +61 -10
  15. package/dist/cli/lib/dashboard-export.js +62 -12
  16. package/dist/cli/lib/dashboard-html/client-script.js +1936 -0
  17. package/dist/cli/lib/dashboard-html/locale-bootstrap.js +8 -0
  18. package/dist/cli/lib/dashboard-html/styles.js +572 -0
  19. package/dist/cli/lib/dashboard-html/template.js +134 -0
  20. package/dist/cli/lib/dashboard-html/types.js +1 -0
  21. package/dist/cli/lib/dashboard-html.js +1 -1907
  22. package/dist/cli/lib/dashboard-locale.js +37 -0
  23. package/dist/cli/lib/local-index/constants.js +48 -0
  24. package/dist/cli/lib/local-index/index.js +2951 -0
  25. package/dist/cli/lib/local-index/sql.js +15 -0
  26. package/dist/cli/lib/local-index/types.js +1 -0
  27. package/dist/cli/lib/local-index.js +1 -1911
  28. package/dist/cli/lib/run-plan.js +76 -1
  29. package/dist/cli/lib/templates.js +18 -1
  30. package/dist/cli/lib/validation/command-intents.js +11 -0
  31. package/dist/cli/lib/validation/constants.js +238 -0
  32. package/dist/cli/lib/validation/index.js +1384 -0
  33. package/dist/cli/lib/validation/primitives.js +198 -0
  34. package/dist/cli/lib/validation/test-selection.js +95 -0
  35. package/dist/cli/lib/validation/types.js +1 -0
  36. package/dist/cli/lib/validation.js +1 -1770
  37. package/dist/core/check-issues.js +6 -0
  38. package/dist/core/completion-verdict.js +341 -0
  39. package/dist/core/contract-lint.js +221 -6
  40. package/dist/core/external-evidence.js +9 -0
  41. package/dist/core/public-json-contracts.js +21 -0
  42. package/dist/core/repeated-failure.js +179 -0
  43. package/dist/core/repro-evidence.js +134 -0
  44. package/dist/core/scope-risk.js +64 -0
  45. package/dist/core/skill-route-alignment.js +20 -0
  46. package/dist/core/source-anchor-status.js +4 -1
  47. package/dist/core/test-selection.js +3 -0
  48. package/dist/core/validation-ratchet.js +196 -0
  49. package/dist/core/verification-evidence.js +249 -0
  50. package/examples/README.md +12 -4
  51. package/package.json +3 -3
  52. package/schemas/README.md +13 -3
  53. package/schemas/change-verification-report.schema.json +16 -2
  54. package/schemas/commands.schema.json +4 -0
  55. package/schemas/contract-lint-report.schema.json +29 -0
  56. package/schemas/dashboard-export.schema.json +310 -0
  57. package/schemas/explain-report.schema.json +173 -1
  58. package/schemas/latest-run-pointer.schema.json +601 -0
  59. package/schemas/run-receipt.schema.json +4 -0
  60. package/schemas/test-selection.schema.json +81 -0
  61. package/schemas/verify-report.schema.json +578 -1
  62. package/schemas/verify-run-manifest.schema.json +627 -0
  63. package/templates/default/i18n.toml +1 -1
  64. package/templates/default/locales/en/.mustflow/skills/INDEX.md +124 -29
  65. package/templates/default/locales/en/.mustflow/skills/routes.toml +289 -0
  66. package/templates/default/manifest.toml +29 -2
@@ -0,0 +1,2951 @@
1
+ import { existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from 'node:fs';
2
+ import { createHash } from 'node:crypto';
3
+ import path from 'node:path';
4
+ import { isRecord, readCommandContract, readString, readStringArray } from '../command-contract.js';
5
+ import { listFilesRecursive, toPosixPath } from '../filesystem.js';
6
+ import { readTomlFile } from '../toml.js';
7
+ import { collectSourceAnchorIndexRecords, hasHighRiskSourceAnchorRiskTags, } from '../../../core/source-anchor-status.js';
8
+ import { normalizeCommandEffects } from '../../../core/command-effects.js';
9
+ import { listChangeClassificationRuleDescriptors } from '../../../core/change-classification.js';
10
+ import { DEFAULT_DATABASE_RELATIVE_PATH, DEFAULT_PROMPT_CACHE_STABLE_READ, DEFAULT_PROMPT_CACHE_TASK_SOURCES, DEFAULT_PROMPT_CACHE_VOLATILE_SOURCES, INDEX_CONFIG_RELATIVE_PATH, LOCAL_INDEX_CONTENT_MODE, LOCAL_INDEX_EXCLUDED_RAW_DATA_KINDS, LOCAL_INDEX_PARSER_VERSION, LOCAL_INDEX_SCHEMA_VERSION, LOCAL_INDEX_STORE_FULL_CONTENT, LATEST_RUN_STATE_RELATIVE_PATH, MAX_SEARCH_MATCH_SNIPPET_CHARS, MAX_SNIPPET_BYTES_PER_DOCUMENT, MUSTFLOW_RELATIVE_PATH, SEARCH_BACKEND_FTS5, SEARCH_BACKEND_TABLE_SCAN, SEARCH_MATCH_CONTEXT_AFTER_CHARS, SEARCH_MATCH_CONTEXT_BEFORE_CHARS, SEARCH_MATCH_TRUNCATION_MARKER, SEARCH_NGRAM_MAX_LENGTH, SEARCH_NGRAM_MIN_LENGTH, TEST_DISABLE_FTS5_ENV, } from './constants.js';
11
+ import { loadSqlJs } from './sql.js';
12
+ export function getLocalIndexDatabasePath(projectRoot) {
13
+ return path.join(projectRoot, ...DEFAULT_DATABASE_RELATIVE_PATH.split('/'));
14
+ }
15
+ function getExistingIndexablePaths(projectRoot) {
16
+ const paths = new Set();
17
+ const addIfExists = (relativePath) => {
18
+ if (existsSync(path.join(projectRoot, ...relativePath.split('/')))) {
19
+ paths.add(relativePath);
20
+ }
21
+ };
22
+ addIfExists('AGENTS.md');
23
+ for (const relativePath of listFilesRecursive(path.join(projectRoot, '.mustflow', 'docs'))) {
24
+ if (relativePath.endsWith('.md')) {
25
+ paths.add(toPosixPath(path.join('.mustflow', 'docs', relativePath)));
26
+ }
27
+ }
28
+ for (const relativePath of listFilesRecursive(path.join(projectRoot, '.mustflow', 'context'))) {
29
+ if (relativePath.endsWith('.md')) {
30
+ paths.add(toPosixPath(path.join('.mustflow', 'context', relativePath)));
31
+ }
32
+ }
33
+ for (const relativePath of listFilesRecursive(path.join(projectRoot, '.mustflow', 'skills'))) {
34
+ if (relativePath === 'INDEX.md' || relativePath.endsWith('/SKILL.md')) {
35
+ paths.add(toPosixPath(path.join('.mustflow', 'skills', relativePath)));
36
+ }
37
+ }
38
+ for (const relativePath of listFilesRecursive(path.join(projectRoot, '.mustflow', 'config'))) {
39
+ if (relativePath.endsWith('.toml')) {
40
+ paths.add(toPosixPath(path.join('.mustflow', 'config', relativePath)));
41
+ }
42
+ }
43
+ return Array.from(paths).sort((left, right) => left.localeCompare(right));
44
+ }
45
+ function readText(projectRoot, relativePath) {
46
+ return readFileSync(path.join(projectRoot, ...relativePath.split('/')), 'utf8');
47
+ }
48
+ function readMustflowToml(projectRoot) {
49
+ const mustflowPath = path.join(projectRoot, ...MUSTFLOW_RELATIVE_PATH.split('/'));
50
+ if (!existsSync(mustflowPath)) {
51
+ return undefined;
52
+ }
53
+ const parsed = readTomlFile(mustflowPath);
54
+ return isRecord(parsed) ? parsed : undefined;
55
+ }
56
+ function readIndexToml(projectRoot) {
57
+ const indexConfigPath = path.join(projectRoot, ...INDEX_CONFIG_RELATIVE_PATH.split('/'));
58
+ if (!existsSync(indexConfigPath)) {
59
+ return undefined;
60
+ }
61
+ const parsed = readTomlFile(indexConfigPath);
62
+ return isRecord(parsed) ? parsed : undefined;
63
+ }
64
+ function readNestedTable(table, key) {
65
+ if (!table || !isRecord(table[key])) {
66
+ return undefined;
67
+ }
68
+ return table[key];
69
+ }
70
+ function readOptionalStringArray(table, key) {
71
+ return table ? readStringArray(table, key) ?? null : null;
72
+ }
73
+ function readBoolean(table, key) {
74
+ const value = table?.[key];
75
+ return typeof value === 'boolean' ? value : undefined;
76
+ }
77
+ function readPositiveInteger(table, key) {
78
+ const value = table?.[key];
79
+ if (typeof value !== 'number' || !Number.isInteger(value) || value <= 0) {
80
+ return null;
81
+ }
82
+ return value;
83
+ }
84
+ function readLocalIndexSourceConfig(projectRoot) {
85
+ const sourceIndexTable = readNestedTable(readIndexToml(projectRoot), 'source_index');
86
+ return {
87
+ enabledByDefault: readBoolean(sourceIndexTable, 'enabled_by_default') === true,
88
+ include: readOptionalStringArray(sourceIndexTable, 'include') ?? [],
89
+ exclude: readOptionalStringArray(sourceIndexTable, 'exclude') ?? [],
90
+ maxFileBytes: readPositiveInteger(sourceIndexTable, 'max_file_bytes'),
91
+ allowedExtensions: readOptionalStringArray(sourceIndexTable, 'allowed_extensions') ?? [],
92
+ };
93
+ }
94
+ function sha256Text(content) {
95
+ return `sha256:${createHash('sha256').update(content).digest('hex')}`;
96
+ }
97
+ function sha256Bytes(content) {
98
+ return `sha256:${createHash('sha256').update(content).digest('hex')}`;
99
+ }
100
+ function getSourceScopeHash(includeSource, sourceConfig) {
101
+ return sha256Text(JSON.stringify({
102
+ includeSource,
103
+ sourceConfig,
104
+ }));
105
+ }
106
+ function getDocumentType(relativePath) {
107
+ if (relativePath === 'AGENTS.md') {
108
+ return 'agent_rules';
109
+ }
110
+ if (relativePath.startsWith('.mustflow/config/')) {
111
+ return 'config';
112
+ }
113
+ if (relativePath === '.mustflow/skills/INDEX.md') {
114
+ return 'skill_index';
115
+ }
116
+ if (relativePath === '.mustflow/context/INDEX.md') {
117
+ return 'context_index';
118
+ }
119
+ if (relativePath.startsWith('.mustflow/context/')) {
120
+ return 'context';
121
+ }
122
+ if (relativePath.endsWith('/SKILL.md')) {
123
+ return 'skill';
124
+ }
125
+ if (relativePath.startsWith('.mustflow/docs/')) {
126
+ return 'workflow_doc';
127
+ }
128
+ return 'document';
129
+ }
130
+ function parseFrontmatter(content) {
131
+ if (!content.startsWith('---')) {
132
+ return {};
133
+ }
134
+ const end = content.indexOf('\n---', 3);
135
+ if (end === -1) {
136
+ return {};
137
+ }
138
+ const result = {};
139
+ const rawFrontmatter = content.slice(3, end);
140
+ for (const line of rawFrontmatter.split(/\r?\n/)) {
141
+ const separatorIndex = line.indexOf(':');
142
+ if (separatorIndex === -1) {
143
+ continue;
144
+ }
145
+ const key = line.slice(0, separatorIndex).trim();
146
+ const value = line.slice(separatorIndex + 1).trim();
147
+ if (key.length > 0 && value.length > 0) {
148
+ result[key] = value;
149
+ }
150
+ }
151
+ return result;
152
+ }
153
+ function getTitle(relativePath, content) {
154
+ const heading = content.match(/^#\s+(.+)$/mu)?.[1]?.trim();
155
+ return heading && heading.length > 0 ? heading : path.posix.basename(relativePath);
156
+ }
157
+ function getSections(content) {
158
+ return [...content.matchAll(/^##\s+(.+)$/gmu)].map((match) => match[1]?.trim()).filter((value) => Boolean(value));
159
+ }
160
+ function truncateUtf8(value, maxBytes) {
161
+ const buffer = Buffer.from(value, 'utf8');
162
+ if (buffer.byteLength <= maxBytes) {
163
+ return value;
164
+ }
165
+ return buffer.subarray(0, maxBytes).toString('utf8').replace(/\uFFFD$/u, '');
166
+ }
167
+ function collectDocuments(projectRoot) {
168
+ return getExistingIndexablePaths(projectRoot).map((relativePath) => {
169
+ const content = readText(projectRoot, relativePath);
170
+ const frontmatter = parseFrontmatter(content);
171
+ const revision = Number.parseInt(frontmatter.revision ?? '', 10);
172
+ return {
173
+ path: relativePath,
174
+ type: getDocumentType(relativePath),
175
+ title: getTitle(relativePath, content),
176
+ locale: frontmatter.locale ?? null,
177
+ revision: Number.isInteger(revision) ? revision : null,
178
+ contentHash: sha256Text(content),
179
+ contentSnippet: truncateUtf8(content, MAX_SNIPPET_BYTES_PER_DOCUMENT),
180
+ sections: getSections(content),
181
+ };
182
+ });
183
+ }
184
+ function collectSkills(documents) {
185
+ return documents
186
+ .filter((document) => document.type === 'skill')
187
+ .map((document) => ({
188
+ name: document.path.split('/').at(-2) ?? document.title,
189
+ path: document.path,
190
+ title: document.title,
191
+ }))
192
+ .sort((left, right) => left.name.localeCompare(right.name));
193
+ }
194
+ function normalizeMarkdownCell(value) {
195
+ return value
196
+ .replace(/<br\s*\/?>/giu, ' ')
197
+ .replace(/`([^`]+)`/gu, '$1')
198
+ .replace(/\s+/gu, ' ')
199
+ .trim();
200
+ }
201
+ function parseMarkdownTableRow(line) {
202
+ return line
203
+ .trim()
204
+ .replace(/^\|/u, '')
205
+ .replace(/\|$/u, '')
206
+ .split('|')
207
+ .map((cell) => normalizeMarkdownCell(cell));
208
+ }
209
+ function isMarkdownSeparatorRow(cells) {
210
+ return cells.length > 0 && cells.every((cell) => /^:?-{3,}:?$/u.test(cell));
211
+ }
212
+ function skillNameFromPath(skillPath) {
213
+ return skillPath.split('/').at(-2) ?? path.posix.basename(skillPath, '.md');
214
+ }
215
+ function splitVerificationIntents(value) {
216
+ return value
217
+ .split(',')
218
+ .map((item) => item.trim())
219
+ .filter(Boolean)
220
+ .sort((left, right) => left.localeCompare(right));
221
+ }
222
+ function collectSkillRoutes(projectRoot) {
223
+ const indexPath = path.join(projectRoot, '.mustflow', 'skills', 'INDEX.md');
224
+ if (!existsSync(indexPath)) {
225
+ return [];
226
+ }
227
+ const content = readFileSync(indexPath, 'utf8');
228
+ const routes = [];
229
+ let inRouteTable = false;
230
+ for (const line of content.split(/\r?\n/u)) {
231
+ if (!line.trim().startsWith('|')) {
232
+ if (inRouteTable && line.trim() === '') {
233
+ inRouteTable = false;
234
+ }
235
+ continue;
236
+ }
237
+ const cells = parseMarkdownTableRow(line);
238
+ if (cells.includes('Skill Document') && cells.includes('Trigger')) {
239
+ inRouteTable = true;
240
+ continue;
241
+ }
242
+ if (!inRouteTable || isMarkdownSeparatorRow(cells) || cells.length < 7) {
243
+ continue;
244
+ }
245
+ const [trigger, skillPath, requiredInput, editScope, risk, verificationIntents, expectedOutput] = cells;
246
+ if (!skillPath?.startsWith('.mustflow/skills/') || !skillPath.endsWith('/SKILL.md')) {
247
+ continue;
248
+ }
249
+ routes.push({
250
+ skillName: skillNameFromPath(skillPath),
251
+ skillPath,
252
+ trigger: trigger ?? '',
253
+ requiredInput: requiredInput ?? '',
254
+ editScope: editScope ?? '',
255
+ risk: risk ?? '',
256
+ verificationIntents: splitVerificationIntents(verificationIntents ?? ''),
257
+ expectedOutput: expectedOutput ?? '',
258
+ });
259
+ }
260
+ return routes.sort((left, right) => {
261
+ const skillOrder = left.skillName.localeCompare(right.skillName);
262
+ return skillOrder === 0 ? left.trigger.localeCompare(right.trigger) : skillOrder;
263
+ });
264
+ }
265
+ function collectCommandIntents(projectRoot) {
266
+ if (!existsSync(path.join(projectRoot, '.mustflow', 'config', 'commands.toml'))) {
267
+ return [];
268
+ }
269
+ const contract = readCommandContract(projectRoot);
270
+ const intents = [];
271
+ for (const [name, intent] of Object.entries(contract.intents).sort(([left], [right]) => left.localeCompare(right))) {
272
+ if (!isRecord(intent)) {
273
+ continue;
274
+ }
275
+ intents.push({
276
+ name,
277
+ status: readString(intent, 'status') ?? 'unknown',
278
+ lifecycle: readString(intent, 'lifecycle') ?? null,
279
+ runPolicy: readString(intent, 'run_policy') ?? null,
280
+ description: readString(intent, 'description') ?? null,
281
+ effects: normalizeCommandEffects(projectRoot, contract, name),
282
+ });
283
+ }
284
+ return intents;
285
+ }
286
+ function readIndexedFileRecord(projectRoot, relativePath, sourceScope, contentHash = null) {
287
+ const fullPath = path.join(projectRoot, ...relativePath.split('/'));
288
+ const stats = statSync(fullPath);
289
+ return {
290
+ path: relativePath,
291
+ sourceScope,
292
+ sizeBytes: stats.size,
293
+ mtimeMs: Math.round(stats.mtimeMs),
294
+ contentHash: contentHash ?? sha256Bytes(readFileSync(fullPath)),
295
+ };
296
+ }
297
+ function collectIndexedFileRecords(projectRoot, documents, sourceAnchors) {
298
+ const records = new Map();
299
+ for (const document of documents) {
300
+ records.set(document.path, readIndexedFileRecord(projectRoot, document.path, 'workflow', document.contentHash));
301
+ }
302
+ for (const anchorPath of [...new Set(sourceAnchors.map((anchor) => anchor.path))].sort((left, right) => left.localeCompare(right))) {
303
+ if (!records.has(anchorPath)) {
304
+ records.set(anchorPath, readIndexedFileRecord(projectRoot, anchorPath, 'source_anchor'));
305
+ }
306
+ }
307
+ return [...records.values()].sort((left, right) => left.path.localeCompare(right.path));
308
+ }
309
+ function normalizeSearchText(value) {
310
+ return value.trim().replace(/\s+/g, ' ');
311
+ }
312
+ function normalizeSearchTokenText(value) {
313
+ return normalizeSearchText(value).normalize('NFKC').toLowerCase();
314
+ }
315
+ function extractSearchTokens(value) {
316
+ return [...normalizeSearchTokenText(value).matchAll(/[\p{L}\p{N}]+/gu)]
317
+ .map((match) => match[0])
318
+ .filter((token) => Boolean(token));
319
+ }
320
+ function buildSearchNgrams(values) {
321
+ const grams = new Set();
322
+ for (const value of values) {
323
+ for (const token of extractSearchTokens(value)) {
324
+ const maxLength = Math.min(SEARCH_NGRAM_MAX_LENGTH, token.length);
325
+ for (let length = SEARCH_NGRAM_MIN_LENGTH; length <= maxLength; length += 1) {
326
+ for (let index = 0; index <= token.length - length; index += 1) {
327
+ grams.add(token.slice(index, index + length));
328
+ }
329
+ }
330
+ }
331
+ }
332
+ return [...grams].sort((left, right) => left.localeCompare(right));
333
+ }
334
+ function toSearchString(value) {
335
+ if (value === null || value === undefined) {
336
+ return '';
337
+ }
338
+ if (value instanceof Uint8Array) {
339
+ return '';
340
+ }
341
+ return String(value);
342
+ }
343
+ function queryRows(database, sql, params = []) {
344
+ const [result] = database.exec(sql, params);
345
+ if (!result) {
346
+ return [];
347
+ }
348
+ return result.values.map((values) => {
349
+ const row = {};
350
+ result.columns.forEach((column, index) => {
351
+ row[column] = values[index] ?? null;
352
+ });
353
+ return row;
354
+ });
355
+ }
356
+ const VALIDATION_RATCHET_RISK_CODES = new Set([
357
+ 'related_test_deleted',
358
+ 'skip_or_only_marker_present',
359
+ 'todo_or_pending_marker_added',
360
+ 'assertion_count_decreased',
361
+ 'assertion_matcher_weakened',
362
+ 'negative_assertion_removed',
363
+ 'exception_assertion_removed',
364
+ 'snapshot_mass_updated',
365
+ 'golden_output_replaced',
366
+ 'verification_intent_disabled',
367
+ 'verification_required_after_removed',
368
+ 'success_exit_codes_widened',
369
+ 'command_allows_no_tests',
370
+ 'command_forces_snapshot_update',
371
+ 'command_hides_failure',
372
+ 'coverage_threshold_lowered',
373
+ 'test_selection_narrowed',
374
+ ]);
375
+ function searchCapabilities(fts5Available) {
376
+ return {
377
+ backend: fts5Available ? SEARCH_BACKEND_FTS5 : SEARCH_BACKEND_TABLE_SCAN,
378
+ fts5Available,
379
+ };
380
+ }
381
+ function detectLocalSearchCapabilities(database) {
382
+ if (process.env[TEST_DISABLE_FTS5_ENV] === '1') {
383
+ return searchCapabilities(false);
384
+ }
385
+ try {
386
+ database.run('CREATE VIRTUAL TABLE temp.mustflow_fts5_probe USING fts5(value)');
387
+ database.run('DROP TABLE temp.mustflow_fts5_probe');
388
+ return searchCapabilities(true);
389
+ }
390
+ catch {
391
+ return searchCapabilities(false);
392
+ }
393
+ }
394
+ function isJsonRecord(value) {
395
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
396
+ }
397
+ function readJsonRecord(filePath) {
398
+ try {
399
+ const parsed = JSON.parse(readFileSync(filePath, 'utf8'));
400
+ return isJsonRecord(parsed) ? parsed : null;
401
+ }
402
+ catch {
403
+ return null;
404
+ }
405
+ }
406
+ function stringField(record, key) {
407
+ const value = record?.[key];
408
+ return typeof value === 'string' ? value : null;
409
+ }
410
+ function booleanField(record, key) {
411
+ return record?.[key] === true;
412
+ }
413
+ function numberField(record, key) {
414
+ const value = record?.[key];
415
+ return typeof value === 'number' && Number.isFinite(value) ? value : 0;
416
+ }
417
+ function recordField(record, key) {
418
+ const value = record?.[key];
419
+ return isJsonRecord(value) ? value : null;
420
+ }
421
+ function recordArrayField(record, key) {
422
+ const value = record?.[key];
423
+ return Array.isArray(value) ? value.filter(isJsonRecord) : [];
424
+ }
425
+ function stringArrayField(record, key) {
426
+ const value = record?.[key];
427
+ return Array.isArray(value) ? value.filter((item) => typeof item === 'string') : [];
428
+ }
429
+ function joinedList(values) {
430
+ return [...values].sort((left, right) => left.localeCompare(right)).join(', ');
431
+ }
432
+ function hashJson(value) {
433
+ return sha256Text(JSON.stringify(value));
434
+ }
435
+ function stringListHash(values) {
436
+ const normalized = values.filter((value) => typeof value === 'string' && value.length > 0);
437
+ return normalized.length > 0 ? hashJson([...normalized].sort((left, right) => left.localeCompare(right))) : null;
438
+ }
439
+ function reproObservation(routeId, phase, evidence) {
440
+ const status = stringField(evidence, 'status');
441
+ const outcome = stringField(evidence, 'outcome') ?? status;
442
+ const receiptHash = stringField(evidence, 'receipt_sha256');
443
+ const diagnosticFingerprint = stringField(evidence, 'diagnostic_fingerprint') ??
444
+ stringField(evidence, 'diagnostic_hash') ??
445
+ hashJson({
446
+ phase,
447
+ status,
448
+ outcome,
449
+ summary: stringField(evidence, 'summary'),
450
+ reason: stringField(evidence, 'reason'),
451
+ });
452
+ return {
453
+ routeId,
454
+ phase,
455
+ outcome,
456
+ receiptHash,
457
+ diagnosticFingerprint,
458
+ };
459
+ }
460
+ function evidenceStatusForRunReceipt(latest) {
461
+ return stringField(latest, 'status') ?? (booleanField(latest, 'timed_out') ? 'timed_out' : 'unknown');
462
+ }
463
+ function failedIntentsFromReceipts(receipts) {
464
+ return receipts
465
+ .filter((receipt) => ['failed', 'timed_out', 'start_failed'].includes(receipt.status))
466
+ .map((receipt) => receipt.intent)
467
+ .filter((intent) => typeof intent === 'string' && intent.length > 0)
468
+ .sort((left, right) => left.localeCompare(right));
469
+ }
470
+ function createFailureFingerprint(input) {
471
+ if (input.status === 'passed' ||
472
+ input.status === 'verified' ||
473
+ (input.failedIntents.length === 0 && input.riskCodes.length === 0 && input.timedOut !== true && !input.errorKind)) {
474
+ return null;
475
+ }
476
+ return sha256Text(JSON.stringify({
477
+ command: input.command,
478
+ status: input.status,
479
+ verificationPlanId: input.verificationPlanId,
480
+ primaryReason: input.primaryReason,
481
+ failedIntents: [...input.failedIntents].sort((left, right) => left.localeCompare(right)),
482
+ riskCodes: [...input.riskCodes].sort((left, right) => left.localeCompare(right)),
483
+ runIntent: input.runIntent ?? null,
484
+ timedOut: input.timedOut === true,
485
+ exitCodeClass: input.exitCodeClass ?? null,
486
+ errorKind: input.errorKind ?? null,
487
+ }));
488
+ }
489
+ function createVerificationEvidenceIndex(projectRoot) {
490
+ const latestPath = path.join(projectRoot, ...LATEST_RUN_STATE_RELATIVE_PATH.split('/'));
491
+ if (!existsSync(latestPath)) {
492
+ return {
493
+ summaries: [],
494
+ verificationPlans: [],
495
+ acceptanceCriteria: [],
496
+ criterionCoverage: [],
497
+ receipts: [],
498
+ commandReceiptSummaries: [],
499
+ coverageStates: [],
500
+ riskSignals: [],
501
+ validationRatchetSignals: [],
502
+ completionVerdictSummaries: [],
503
+ failureFingerprints: [],
504
+ reproRoutes: [],
505
+ reproObservations: [],
506
+ failureFingerprintReadModels: [],
507
+ };
508
+ }
509
+ const latest = readJsonRecord(latestPath);
510
+ if (!latest) {
511
+ return {
512
+ summaries: [],
513
+ verificationPlans: [],
514
+ acceptanceCriteria: [],
515
+ criterionCoverage: [],
516
+ receipts: [],
517
+ commandReceiptSummaries: [],
518
+ coverageStates: [],
519
+ riskSignals: [],
520
+ validationRatchetSignals: [],
521
+ completionVerdictSummaries: [],
522
+ failureFingerprints: [],
523
+ reproRoutes: [],
524
+ reproObservations: [],
525
+ failureFingerprintReadModels: [],
526
+ };
527
+ }
528
+ const sourceHash = sha256Bytes(readFileSync(latestPath));
529
+ const command = stringField(latest, 'command') ?? 'unknown';
530
+ const kind = stringField(latest, 'kind') ?? (command === 'verify' ? 'verify_run_summary' : 'run_receipt');
531
+ const evidenceModel = recordField(latest, 'evidence_model');
532
+ const completionVerdict = recordField(latest, 'completion_verdict');
533
+ const completionEvidence = recordField(completionVerdict, 'evidence');
534
+ const verificationPlanId = stringField(latest, 'verification_plan_id') ?? stringField(evidenceModel, 'verification_plan_id');
535
+ const primaryReason = stringField(completionVerdict, 'primary_reason');
536
+ const status = stringField(latest, 'status') ?? stringField(completionVerdict, 'status') ?? 'unknown';
537
+ const completionStatus = stringField(completionVerdict, 'status');
538
+ const rawReceipts = recordArrayField(evidenceModel, 'receipts');
539
+ const rawCoverage = recordArrayField(evidenceModel, 'coverage_matrix');
540
+ const rawRequirements = recordArrayField(evidenceModel, 'requirements');
541
+ const rawRisks = recordArrayField(evidenceModel, 'remaining_risks');
542
+ const recordedFailureFingerprintRecord = recordField(latest, 'failure_fingerprint');
543
+ const repeatedFailureSummary = recordField(latest, 'repeated_failure_summary');
544
+ const reproEvidence = recordField(latest, 'repro_evidence') ?? recordField(evidenceModel, 'repro_evidence');
545
+ const reproductionRoute = recordField(reproEvidence, 'reproduction_route');
546
+ const recordedFailureFingerprint = stringField(recordedFailureFingerprintRecord, 'fingerprint');
547
+ const receipts = rawReceipts.length > 0
548
+ ? rawReceipts.map((receipt, index) => ({
549
+ sourcePath: LATEST_RUN_STATE_RELATIVE_PATH,
550
+ ordinal: index + 1,
551
+ intent: stringField(receipt, 'intent'),
552
+ status: stringField(receipt, 'status') ?? 'unknown',
553
+ skipped: booleanField(receipt, 'skipped'),
554
+ verificationPlanId: stringField(receipt, 'verification_plan_id'),
555
+ receiptPath: stringField(receipt, 'receipt_path'),
556
+ receiptSha256: stringField(receipt, 'receipt_sha256'),
557
+ commandFingerprint: stringField(receipt, 'command_fingerprint'),
558
+ contractFingerprint: stringField(receipt, 'contract_fingerprint'),
559
+ currentStateHash: stringField(receipt, 'head_tree_hash') ??
560
+ stringField(receipt, 'changed_files_hash') ??
561
+ stringField(receipt, 'changed_file_hash'),
562
+ writeDriftStatus: stringField(receipt, 'write_drift_status'),
563
+ }))
564
+ : [
565
+ {
566
+ sourcePath: LATEST_RUN_STATE_RELATIVE_PATH,
567
+ ordinal: 1,
568
+ intent: stringField(latest, 'intent'),
569
+ status: evidenceStatusForRunReceipt(latest),
570
+ skipped: false,
571
+ verificationPlanId: null,
572
+ receiptPath: stringField(latest, 'receipt_path') ?? LATEST_RUN_STATE_RELATIVE_PATH,
573
+ receiptSha256: sourceHash,
574
+ commandFingerprint: stringField(recordField(latest, 'performance'), 'command_fingerprint'),
575
+ contractFingerprint: stringField(recordField(latest, 'performance'), 'contract_fingerprint'),
576
+ currentStateHash: stringField(latest, 'head_tree_hash') ?? stringField(latest, 'changed_files_hash'),
577
+ writeDriftStatus: stringField(recordField(latest, 'write_drift'), 'status'),
578
+ },
579
+ ];
580
+ const coverageStates = rawCoverage.map((coverage) => {
581
+ const evidence = recordField(coverage, 'evidence');
582
+ return {
583
+ sourcePath: LATEST_RUN_STATE_RELATIVE_PATH,
584
+ criterionId: stringField(coverage, 'criterion_id') ?? 'unknown',
585
+ source: stringField(coverage, 'source') ?? 'unknown',
586
+ status: stringField(coverage, 'status') ?? 'unknown',
587
+ requirementReason: stringField(coverage, 'requirement_reason'),
588
+ intents: stringArrayField(evidence, 'intents'),
589
+ receiptCount: stringArrayField(evidence, 'receipt_paths').length,
590
+ gapCount: stringArrayField(evidence, 'gap_reasons').length,
591
+ sourceAnchorCount: stringArrayField(evidence, 'source_anchor_ids').length,
592
+ };
593
+ });
594
+ const riskSignals = rawRisks.map((risk, index) => ({
595
+ sourcePath: LATEST_RUN_STATE_RELATIVE_PATH,
596
+ ordinal: index + 1,
597
+ code: stringField(risk, 'code') ?? 'unknown',
598
+ severity: stringField(risk, 'severity') ?? 'unknown',
599
+ detailHash: sha256Text(stringField(risk, 'detail') ?? ''),
600
+ }));
601
+ const validationRatchetSignals = rawRisks
602
+ .map((risk, index) => {
603
+ const code = stringField(risk, 'code') ?? 'unknown';
604
+ if (!VALIDATION_RATCHET_RISK_CODES.has(code)) {
605
+ return null;
606
+ }
607
+ const severity = stringField(risk, 'severity') ?? 'unknown';
608
+ const pathValue = stringField(risk, 'path');
609
+ const detailHash = sha256Text(stringField(risk, 'detail') ?? '');
610
+ const pathHash = pathValue === null ? hashJson({ code, detailHash }) : sha256Text(pathValue);
611
+ const beforeHash = stringField(risk, 'before_hash') ?? stringField(risk, 'before_digest');
612
+ const afterHash = stringField(risk, 'after_hash') ?? stringField(risk, 'after_digest');
613
+ return {
614
+ signalId: hashJson({
615
+ sourcePath: LATEST_RUN_STATE_RELATIVE_PATH,
616
+ ordinal: index + 1,
617
+ planId: verificationPlanId,
618
+ code,
619
+ pathHash,
620
+ beforeHash,
621
+ afterHash,
622
+ }),
623
+ planId: verificationPlanId,
624
+ code,
625
+ severity,
626
+ pathHash,
627
+ beforeHash,
628
+ afterHash,
629
+ };
630
+ })
631
+ .filter((signal) => signal !== null);
632
+ const verificationPlans = verificationPlanId === null
633
+ ? []
634
+ : [
635
+ {
636
+ planId: verificationPlanId,
637
+ sourcePath: LATEST_RUN_STATE_RELATIVE_PATH,
638
+ classificationHash: rawRequirements.length > 0 || rawCoverage.length > 0
639
+ ? hashJson({
640
+ requirements: rawRequirements.map((requirement) => ({
641
+ id: stringField(requirement, 'requirement_id') ?? stringField(requirement, 'id'),
642
+ reason: stringField(requirement, 'reason'),
643
+ source: stringField(requirement, 'source'),
644
+ })),
645
+ coverage: rawCoverage.map((coverage) => ({
646
+ id: stringField(coverage, 'criterion_id'),
647
+ reason: stringField(coverage, 'requirement_reason'),
648
+ source: stringField(coverage, 'source'),
649
+ status: stringField(coverage, 'status'),
650
+ })),
651
+ })
652
+ : null,
653
+ commandContractHash: stringListHash(receipts.map((receipt) => receipt.contractFingerprint)),
654
+ selectedIntentsHash: stringListHash(receipts.map((receipt) => receipt.intent)),
655
+ createdAt: stringField(latest, 'started_at') ?? stringField(latest, 'created_at'),
656
+ sourceHash,
657
+ },
658
+ ];
659
+ const acceptanceCriteria = verificationPlanId === null
660
+ ? []
661
+ : rawCoverage.map((coverage) => {
662
+ const evidence = recordField(coverage, 'evidence');
663
+ const pathRefs = [
664
+ ...stringArrayField(evidence, 'paths'),
665
+ ...stringArrayField(evidence, 'changed_paths'),
666
+ ...stringArrayField(evidence, 'source_anchor_ids'),
667
+ ];
668
+ return {
669
+ criterionId: stringField(coverage, 'criterion_id') ?? 'unknown',
670
+ planId: verificationPlanId,
671
+ source: stringField(coverage, 'source') ?? 'unknown',
672
+ statementHash: stringField(coverage, 'statement') ? sha256Text(stringField(coverage, 'statement') ?? '') : null,
673
+ reason: stringField(coverage, 'requirement_reason'),
674
+ surface: stringField(coverage, 'surface'),
675
+ pathHash: pathRefs.length > 0 ? stringListHash(pathRefs) : null,
676
+ };
677
+ });
678
+ const criterionCoverage = verificationPlanId === null
679
+ ? []
680
+ : coverageStates.map((coverage) => ({
681
+ criterionId: coverage.criterionId,
682
+ planId: verificationPlanId,
683
+ status: coverage.status,
684
+ receiptCount: coverage.receiptCount,
685
+ gapCount: coverage.gapCount,
686
+ riskCount: coverage.sourceAnchorCount,
687
+ }));
688
+ const commandReceiptSummaries = verificationPlanId === null
689
+ ? []
690
+ : receipts
691
+ .filter((receipt) => receipt.verificationPlanId === verificationPlanId || receipt.verificationPlanId === null)
692
+ .map((receipt) => ({
693
+ receiptHash: receipt.receiptSha256 ??
694
+ hashJson({
695
+ sourcePath: receipt.sourcePath,
696
+ ordinal: receipt.ordinal,
697
+ intent: receipt.intent,
698
+ status: receipt.status,
699
+ verificationPlanId,
700
+ }),
701
+ planId: verificationPlanId,
702
+ intent: receipt.intent,
703
+ status: receipt.status,
704
+ commandFingerprint: receipt.commandFingerprint,
705
+ contractFingerprint: receipt.contractFingerprint,
706
+ currentStateHash: receipt.currentStateHash,
707
+ writeDriftStatus: receipt.writeDriftStatus,
708
+ }));
709
+ const completionVerdictSummaries = verificationPlanId === null || completionStatus === null
710
+ ? []
711
+ : [
712
+ {
713
+ claimId: hashJson({
714
+ sourceHash,
715
+ verificationPlanId,
716
+ completionStatus,
717
+ primaryReason,
718
+ }),
719
+ planId: verificationPlanId,
720
+ status: completionStatus,
721
+ primaryReason,
722
+ riskCount: riskSignals.length,
723
+ contradictionCount: stringArrayField(completionVerdict, 'contradictions').length,
724
+ blockerCount: stringArrayField(completionVerdict, 'blockers').length,
725
+ },
726
+ ];
727
+ const failedIntents = failedIntentsFromReceipts(receipts);
728
+ const failureFingerprint = recordedFailureFingerprint ??
729
+ createFailureFingerprint({
730
+ command,
731
+ status: completionStatus ?? status,
732
+ verificationPlanId,
733
+ primaryReason,
734
+ failedIntents,
735
+ riskCodes: riskSignals.map((risk) => risk.code),
736
+ runIntent: stringField(latest, 'intent'),
737
+ timedOut: booleanField(latest, 'timed_out'),
738
+ exitCodeClass: stringField(recordField(recordField(latest, 'performance'), 'result_summary'), 'exit_code_class'),
739
+ errorKind: stringField(recordField(recordField(latest, 'performance'), 'result_summary'), 'error_kind'),
740
+ });
741
+ const failureFingerprints = failureFingerprint === null
742
+ ? []
743
+ : [
744
+ {
745
+ sourcePath: LATEST_RUN_STATE_RELATIVE_PATH,
746
+ fingerprint: failureFingerprint,
747
+ verificationPlanId,
748
+ status: completionStatus ?? status,
749
+ failedIntents,
750
+ primaryReason,
751
+ failedIntentsHash: stringField(recordedFailureFingerprintRecord, 'failed_intents_hash') ??
752
+ stringField(repeatedFailureSummary, 'failed_intents_hash'),
753
+ riskCodesHash: stringField(recordedFailureFingerprintRecord, 'risk_codes_hash') ??
754
+ stringField(repeatedFailureSummary, 'risk_codes_hash'),
755
+ affectedSurfacesHash: stringField(recordedFailureFingerprintRecord, 'affected_surfaces_hash') ??
756
+ stringField(repeatedFailureSummary, 'affected_surfaces_hash'),
757
+ firstSeenAt: stringField(repeatedFailureSummary, 'first_seen_at'),
758
+ lastSeenAt: stringField(repeatedFailureSummary, 'last_seen_at'),
759
+ seenCount: Math.max(1, numberField(repeatedFailureSummary, 'seen_count')),
760
+ requiresNewEvidence: booleanField(repeatedFailureSummary, 'requires_new_evidence'),
761
+ },
762
+ ];
763
+ const routeId = stringField(reproductionRoute, 'route_id');
764
+ const reproRoutes = routeId === null || reproEvidence === null
765
+ ? []
766
+ : [
767
+ {
768
+ routeId,
769
+ taskHash: hashJson({
770
+ reported_symptom: stringField(reproEvidence, 'reported_symptom'),
771
+ expected_behavior: stringField(reproEvidence, 'expected_behavior'),
772
+ observed_behavior: stringField(reproEvidence, 'observed_behavior'),
773
+ }),
774
+ routeDigest: stringField(reproductionRoute, 'route_digest'),
775
+ routeKind: stringField(reproductionRoute, 'route_kind'),
776
+ failureOracleHash: stringField(reproductionRoute, 'failure_oracle_hash'),
777
+ },
778
+ ];
779
+ const reproObservations = routeId === null || reproEvidence === null
780
+ ? []
781
+ : [
782
+ reproObservation(routeId, 'before_fix', recordField(reproEvidence, 'before_fix')),
783
+ reproObservation(routeId, 'after_fix', recordField(reproEvidence, 'after_fix')),
784
+ reproObservation(routeId, 'regression_guard', recordField(reproEvidence, 'regression_guard')),
785
+ ];
786
+ const failureFingerprintReadModels = failureFingerprints.map((fingerprint) => ({
787
+ fingerprint: fingerprint.fingerprint,
788
+ planId: fingerprint.verificationPlanId,
789
+ failedIntentsHash: fingerprint.failedIntentsHash ?? stringListHash(fingerprint.failedIntents),
790
+ riskCodesHash: fingerprint.riskCodesHash,
791
+ seenCount: fingerprint.seenCount,
792
+ firstSeenAt: fingerprint.firstSeenAt,
793
+ lastSeenAt: fingerprint.lastSeenAt,
794
+ }));
795
+ return {
796
+ summaries: [
797
+ {
798
+ sourcePath: LATEST_RUN_STATE_RELATIVE_PATH,
799
+ sourceHash,
800
+ command,
801
+ kind,
802
+ status,
803
+ runDir: stringField(latest, 'run_dir'),
804
+ manifestPath: stringField(latest, 'manifest_path'),
805
+ verificationPlanId,
806
+ completionStatus,
807
+ primaryReason,
808
+ matchedIntents: numberField(completionEvidence, 'matched_intents'),
809
+ ranIntents: numberField(completionEvidence, 'ran_intents'),
810
+ passedIntents: numberField(completionEvidence, 'passed_intents'),
811
+ failedIntents: numberField(completionEvidence, 'failed_intents'),
812
+ skippedIntents: numberField(completionEvidence, 'skipped_intents'),
813
+ receiptCount: receipts.length,
814
+ coverageCount: coverageStates.length,
815
+ remainingRiskCount: riskSignals.length,
816
+ failureFingerprint,
817
+ },
818
+ ],
819
+ verificationPlans,
820
+ acceptanceCriteria,
821
+ criterionCoverage,
822
+ receipts,
823
+ commandReceiptSummaries,
824
+ coverageStates,
825
+ riskSignals,
826
+ validationRatchetSignals,
827
+ completionVerdictSummaries,
828
+ failureFingerprints,
829
+ reproRoutes,
830
+ reproObservations,
831
+ failureFingerprintReadModels,
832
+ };
833
+ }
834
+ function readMetadataValue(database, key) {
835
+ return toSearchString(queryRows(database, 'SELECT value FROM metadata WHERE key = ?', [key])[0]?.value) || undefined;
836
+ }
837
+ function hasTable(database, tableName) {
838
+ return queryRows(database, 'SELECT name FROM sqlite_master WHERE type = "table" AND name = ?', [tableName]).length > 0;
839
+ }
840
+ function readStoredSearchCapabilities(database) {
841
+ const fts5Available = readMetadataValue(database, 'search_fts5_available') === 'true';
842
+ const backend = readMetadataValue(database, 'search_backend');
843
+ if (backend === SEARCH_BACKEND_FTS5 && hasTable(database, 'search_documents_fts')) {
844
+ return { backend: SEARCH_BACKEND_FTS5, fts5Available };
845
+ }
846
+ return { backend: SEARCH_BACKEND_TABLE_SCAN, fts5Available };
847
+ }
848
+ function toNullableNumber(value) {
849
+ if (typeof value !== 'number') {
850
+ return null;
851
+ }
852
+ return Number.isFinite(value) ? value : null;
853
+ }
854
+ function splitIndexedList(value) {
855
+ return toSearchString(value)
856
+ .split(',')
857
+ .map((item) => item.trim())
858
+ .filter(Boolean)
859
+ .sort((left, right) => left.localeCompare(right));
860
+ }
861
+ function createCommandEffectGraphStatus(databasePath, status, stalePaths = []) {
862
+ return {
863
+ source: 'local_index',
864
+ authority: 'explanation_only',
865
+ commandAuthority: '.mustflow/config/commands.toml',
866
+ grantsCommandAuthority: false,
867
+ status,
868
+ databasePath,
869
+ indexFresh: status === 'fresh',
870
+ stalePaths,
871
+ writeLocks: [],
872
+ lockConflicts: [],
873
+ refreshHint: status === 'fresh' ? null : 'Run `mf index` to refresh command-effect graph explanations.',
874
+ };
875
+ }
876
+ async function readPreviousSourceAnchorSnapshots(databasePath) {
877
+ if (!existsSync(databasePath)) {
878
+ return [];
879
+ }
880
+ const SQL = await loadSqlJs();
881
+ const database = new SQL.Database(readFileSync(databasePath));
882
+ try {
883
+ const rows = queryRows(database, `
884
+ SELECT
885
+ source_anchors.id,
886
+ source_anchors.path,
887
+ source_anchors.line_start,
888
+ source_anchors.purpose,
889
+ source_anchors.search_terms,
890
+ source_anchors.invariant,
891
+ source_anchors.risk,
892
+ source_anchor_fingerprints.anchor_metadata_hash,
893
+ source_anchor_fingerprints.anchor_text_hash,
894
+ source_anchor_fingerprints.context_hash,
895
+ source_anchor_fingerprints.search_terms_hash,
896
+ source_anchor_fingerprints.invariant_hash,
897
+ source_anchor_fingerprints.risk_hash,
898
+ source_anchor_fingerprints.symbol_kind,
899
+ source_anchor_fingerprints.symbol_name,
900
+ source_anchor_fingerprints.symbol_exported,
901
+ source_anchor_fingerprints.signature_hash,
902
+ source_anchor_fingerprints.body_hash,
903
+ source_anchor_fingerprints.symbol_start_line,
904
+ source_anchor_fingerprints.symbol_end_line
905
+ FROM source_anchors
906
+ JOIN source_anchor_fingerprints ON source_anchor_fingerprints.anchor_id = source_anchors.id
907
+ `);
908
+ return rows.map((row) => {
909
+ const symbol = {
910
+ kind: toSearchString(row.symbol_kind),
911
+ name: toSearchString(row.symbol_name) || null,
912
+ exported: Number(row.symbol_exported) === 1,
913
+ signatureHash: toSearchString(row.signature_hash) || null,
914
+ bodyHash: toSearchString(row.body_hash) || null,
915
+ startLine: toNullableNumber(row.symbol_start_line),
916
+ endLine: toNullableNumber(row.symbol_end_line),
917
+ };
918
+ return {
919
+ id: toSearchString(row.id),
920
+ path: toSearchString(row.path),
921
+ lineStart: Number(row.line_start),
922
+ purpose: toSearchString(row.purpose) || null,
923
+ search: toSearchString(row.search_terms)
924
+ .split(/[,;]/u)
925
+ .map((value) => value.trim())
926
+ .filter((value) => value.length > 0),
927
+ invariant: toSearchString(row.invariant) || null,
928
+ risk: toSearchString(row.risk)
929
+ .split(/[,;]/u)
930
+ .map((value) => value.trim())
931
+ .filter((value) => value.length > 0),
932
+ navigationOnly: true,
933
+ canInstructAgent: false,
934
+ fingerprint: {
935
+ anchorMetadataHash: toSearchString(row.anchor_metadata_hash),
936
+ anchorTextHash: toSearchString(row.anchor_text_hash),
937
+ contextHash: toSearchString(row.context_hash),
938
+ searchTermsHash: toSearchString(row.search_terms_hash) || null,
939
+ invariantHash: toSearchString(row.invariant_hash) || null,
940
+ riskHash: toSearchString(row.risk_hash),
941
+ symbol,
942
+ },
943
+ };
944
+ });
945
+ }
946
+ catch {
947
+ return [];
948
+ }
949
+ finally {
950
+ database.close();
951
+ }
952
+ }
953
+ function readCacheLayerSets(projectRoot) {
954
+ const mustflow = readMustflowToml(projectRoot);
955
+ const promptCache = readNestedTable(mustflow, 'prompt_cache');
956
+ const layers = readNestedTable(promptCache, 'layers');
957
+ const stable = readNestedTable(layers, 'stable');
958
+ const task = readNestedTable(layers, 'task');
959
+ const volatile = readNestedTable(layers, 'volatile');
960
+ const normalize = (values) => new Set(values.map((value) => toPosixPath(value)));
961
+ return {
962
+ stable: normalize(readOptionalStringArray(stable, 'read') ?? [...DEFAULT_PROMPT_CACHE_STABLE_READ]),
963
+ task: normalize(readOptionalStringArray(task, 'sources') ?? [...DEFAULT_PROMPT_CACHE_TASK_SOURCES]),
964
+ volatile: normalize(readOptionalStringArray(volatile, 'sources') ?? [...DEFAULT_PROMPT_CACHE_VOLATILE_SOURCES]),
965
+ };
966
+ }
967
+ function inferCacheLayer(relativePath, kind, cacheLayers) {
968
+ const normalized = relativePath ? toPosixPath(relativePath) : null;
969
+ if (normalized && cacheLayers.volatile.has(normalized)) {
970
+ return 'volatile';
971
+ }
972
+ if (normalized && cacheLayers.stable.has(normalized)) {
973
+ return 'stable';
974
+ }
975
+ if (kind === 'command_intent' && cacheLayers.stable.has('.mustflow/config/commands.toml')) {
976
+ return 'stable';
977
+ }
978
+ if (normalized &&
979
+ (cacheLayers.task.has(normalized) || normalized.startsWith('.mustflow/context/') || normalized.endsWith('/SKILL.md'))) {
980
+ return 'task';
981
+ }
982
+ if (normalized?.startsWith('.mustflow/state/') || normalized?.startsWith('.mustflow/cache/')) {
983
+ return 'volatile';
984
+ }
985
+ return 'task';
986
+ }
987
+ function withCacheHint(item, cacheLayers) {
988
+ const layer = inferCacheLayer(item.path ?? null, item.kind, cacheLayers);
989
+ return {
990
+ ...item,
991
+ cache_layer: layer,
992
+ volatile: layer === 'volatile',
993
+ };
994
+ }
995
+ function workflowAuthorityForDocument(documentType) {
996
+ if (documentType === 'agent_rules' || documentType === 'config' || documentType === 'workflow_doc') {
997
+ return {
998
+ authority_rank: 2,
999
+ authority_label: 'workflow_authority',
1000
+ source_scope: 'workflow',
1001
+ navigation_only: false,
1002
+ can_instruct_agent: true,
1003
+ };
1004
+ }
1005
+ return {
1006
+ authority_rank: 4,
1007
+ authority_label: 'workflow_context',
1008
+ source_scope: 'workflow',
1009
+ navigation_only: false,
1010
+ can_instruct_agent: false,
1011
+ };
1012
+ }
1013
+ function skillAuthority() {
1014
+ return {
1015
+ authority_rank: 3,
1016
+ authority_label: 'skill_procedure',
1017
+ source_scope: 'workflow',
1018
+ navigation_only: false,
1019
+ can_instruct_agent: true,
1020
+ };
1021
+ }
1022
+ function commandIntentAuthority() {
1023
+ return {
1024
+ authority_rank: 1,
1025
+ authority_label: 'command_contract',
1026
+ source_scope: 'workflow',
1027
+ navigation_only: false,
1028
+ can_instruct_agent: true,
1029
+ };
1030
+ }
1031
+ function sourceAnchorAuthority() {
1032
+ return {
1033
+ authority_rank: 5,
1034
+ authority_label: 'source_navigation_hint',
1035
+ source_scope: 'source',
1036
+ navigation_only: true,
1037
+ can_instruct_agent: false,
1038
+ };
1039
+ }
1040
+ function getMatchSnippet(fields, query) {
1041
+ const normalized = normalizeSearchText(fields.join(' '));
1042
+ const lower = normalized.toLowerCase();
1043
+ let start = lower.indexOf(query.toLowerCase());
1044
+ let matchLength = query.length;
1045
+ if (start === -1) {
1046
+ const [firstGram] = buildSearchNgrams([query]).filter((gram) => lower.includes(gram));
1047
+ if (!firstGram) {
1048
+ return truncateSearchMatchSnippet(normalized);
1049
+ }
1050
+ start = lower.indexOf(firstGram);
1051
+ matchLength = firstGram.length;
1052
+ }
1053
+ const from = Math.max(0, start - SEARCH_MATCH_CONTEXT_BEFORE_CHARS);
1054
+ const to = Math.min(normalized.length, start + matchLength + SEARCH_MATCH_CONTEXT_AFTER_CHARS);
1055
+ const prefix = from > 0 ? SEARCH_MATCH_TRUNCATION_MARKER : '';
1056
+ const suffix = to < normalized.length ? SEARCH_MATCH_TRUNCATION_MARKER : '';
1057
+ return truncateSearchMatchSnippet(`${prefix}${normalized.slice(from, to)}${suffix}`);
1058
+ }
1059
+ function truncateSearchMatchSnippet(value) {
1060
+ if (value.length <= MAX_SEARCH_MATCH_SNIPPET_CHARS) {
1061
+ return value;
1062
+ }
1063
+ return `${value.slice(0, MAX_SEARCH_MATCH_SNIPPET_CHARS - SEARCH_MATCH_TRUNCATION_MARKER.length)}${SEARCH_MATCH_TRUNCATION_MARKER}`;
1064
+ }
1065
+ function scoreMatch(primaryFields, secondaryFields, query) {
1066
+ const lowerQuery = query.toLowerCase();
1067
+ if (primaryFields.some((field) => field.toLowerCase() === lowerQuery)) {
1068
+ return 100;
1069
+ }
1070
+ if (primaryFields.some((field) => field.toLowerCase().includes(lowerQuery))) {
1071
+ return 80;
1072
+ }
1073
+ if (secondaryFields.some((field) => field.toLowerCase().includes(lowerQuery))) {
1074
+ return 40;
1075
+ }
1076
+ return 0;
1077
+ }
1078
+ function isMatched(fields, query) {
1079
+ const lowerQuery = query.toLowerCase();
1080
+ return fields.some((field) => field.toLowerCase().includes(lowerQuery));
1081
+ }
1082
+ function createSchema(database, capabilities) {
1083
+ database.run(`
1084
+ CREATE TABLE metadata (
1085
+ key TEXT PRIMARY KEY,
1086
+ value TEXT NOT NULL
1087
+ );
1088
+
1089
+ CREATE TABLE indexed_files (
1090
+ path TEXT PRIMARY KEY,
1091
+ source_scope TEXT NOT NULL,
1092
+ size_bytes INTEGER NOT NULL,
1093
+ mtime_ms INTEGER NOT NULL,
1094
+ content_hash TEXT NOT NULL,
1095
+ indexed_at TEXT NOT NULL,
1096
+ index_mode TEXT NOT NULL,
1097
+ parser_version TEXT NOT NULL
1098
+ );
1099
+
1100
+ CREATE TABLE documents (
1101
+ path TEXT PRIMARY KEY,
1102
+ type TEXT NOT NULL,
1103
+ title TEXT NOT NULL,
1104
+ locale TEXT,
1105
+ revision INTEGER,
1106
+ content_hash TEXT NOT NULL,
1107
+ content_snippet TEXT NOT NULL
1108
+ );
1109
+
1110
+ CREATE TABLE sections (
1111
+ document_path TEXT NOT NULL,
1112
+ ordinal INTEGER NOT NULL,
1113
+ heading TEXT NOT NULL,
1114
+ PRIMARY KEY (document_path, ordinal)
1115
+ );
1116
+
1117
+ CREATE TABLE document_terms (
1118
+ document_path TEXT NOT NULL,
1119
+ term TEXT NOT NULL,
1120
+ source TEXT NOT NULL,
1121
+ PRIMARY KEY (document_path, term, source)
1122
+ );
1123
+
1124
+ CREATE TABLE search_ngrams (
1125
+ target_kind TEXT NOT NULL,
1126
+ target_key TEXT NOT NULL,
1127
+ gram TEXT NOT NULL,
1128
+ source TEXT NOT NULL,
1129
+ PRIMARY KEY (target_kind, target_key, gram, source)
1130
+ );
1131
+
1132
+ CREATE INDEX search_ngrams_lookup ON search_ngrams(target_kind, gram, target_key);
1133
+
1134
+ CREATE TABLE skills (
1135
+ name TEXT PRIMARY KEY,
1136
+ path TEXT NOT NULL,
1137
+ title TEXT NOT NULL
1138
+ );
1139
+
1140
+ CREATE TABLE skill_routes (
1141
+ skill_name TEXT NOT NULL,
1142
+ skill_path TEXT NOT NULL,
1143
+ trigger TEXT NOT NULL,
1144
+ required_input TEXT NOT NULL,
1145
+ edit_scope TEXT NOT NULL,
1146
+ risk TEXT NOT NULL,
1147
+ verification_intents TEXT NOT NULL,
1148
+ expected_output TEXT NOT NULL,
1149
+ PRIMARY KEY (skill_name, trigger)
1150
+ );
1151
+
1152
+ CREATE TABLE command_intents (
1153
+ name TEXT PRIMARY KEY,
1154
+ status TEXT NOT NULL,
1155
+ lifecycle TEXT,
1156
+ run_policy TEXT,
1157
+ description TEXT
1158
+ );
1159
+
1160
+ CREATE TABLE command_effects (
1161
+ intent TEXT NOT NULL,
1162
+ source TEXT NOT NULL,
1163
+ access TEXT NOT NULL,
1164
+ mode TEXT NOT NULL,
1165
+ path TEXT,
1166
+ lock TEXT NOT NULL,
1167
+ concurrency TEXT NOT NULL
1168
+ );
1169
+
1170
+ CREATE VIEW command_write_locks AS
1171
+ SELECT
1172
+ intent,
1173
+ lock,
1174
+ group_concat(DISTINCT path) AS paths,
1175
+ group_concat(DISTINCT mode) AS modes,
1176
+ group_concat(DISTINCT source) AS sources,
1177
+ group_concat(DISTINCT concurrency) AS concurrencies,
1178
+ count(*) AS effect_count
1179
+ FROM command_effects
1180
+ WHERE access = 'write'
1181
+ GROUP BY intent, lock;
1182
+
1183
+ CREATE VIEW command_lock_conflicts AS
1184
+ SELECT
1185
+ a.intent AS left_intent,
1186
+ b.intent AS right_intent,
1187
+ a.lock AS lock,
1188
+ group_concat(DISTINCT a.path) AS left_paths,
1189
+ group_concat(DISTINCT b.path) AS right_paths,
1190
+ group_concat(DISTINCT a.mode) AS left_modes,
1191
+ group_concat(DISTINCT b.mode) AS right_modes,
1192
+ group_concat(DISTINCT a.concurrency) AS left_concurrencies,
1193
+ group_concat(DISTINCT b.concurrency) AS right_concurrencies
1194
+ FROM command_effects a
1195
+ JOIN command_effects b
1196
+ ON a.lock = b.lock
1197
+ AND a.intent < b.intent
1198
+ WHERE
1199
+ a.access = 'write'
1200
+ OR b.access = 'write'
1201
+ OR a.concurrency = 'exclusive'
1202
+ OR b.concurrency = 'exclusive'
1203
+ OR a.mode = 'delete_recreate'
1204
+ OR b.mode = 'delete_recreate'
1205
+ GROUP BY a.intent, b.intent, a.lock;
1206
+
1207
+ CREATE TABLE path_surfaces (
1208
+ rule_id TEXT PRIMARY KEY,
1209
+ pattern_kind TEXT NOT NULL,
1210
+ pattern TEXT NOT NULL,
1211
+ pattern_flags TEXT NOT NULL,
1212
+ surface_kind TEXT NOT NULL,
1213
+ category TEXT NOT NULL,
1214
+ is_public_surface INTEGER NOT NULL,
1215
+ update_policy TEXT NOT NULL
1216
+ );
1217
+
1218
+ CREATE TABLE path_surface_reasons (
1219
+ rule_id TEXT NOT NULL,
1220
+ reason_kind TEXT NOT NULL,
1221
+ reason TEXT NOT NULL,
1222
+ ordinal INTEGER NOT NULL,
1223
+ PRIMARY KEY (rule_id, reason_kind, reason)
1224
+ );
1225
+
1226
+ CREATE TABLE source_anchors (
1227
+ id TEXT PRIMARY KEY,
1228
+ path TEXT NOT NULL,
1229
+ line_start INTEGER NOT NULL,
1230
+ purpose TEXT,
1231
+ search_terms TEXT NOT NULL,
1232
+ invariant TEXT,
1233
+ risk TEXT NOT NULL,
1234
+ navigation_only INTEGER NOT NULL,
1235
+ can_instruct_agent INTEGER NOT NULL
1236
+ );
1237
+
1238
+ CREATE TABLE source_anchor_fingerprints (
1239
+ anchor_id TEXT PRIMARY KEY,
1240
+ path TEXT NOT NULL,
1241
+ line_start INTEGER NOT NULL,
1242
+ anchor_metadata_hash TEXT NOT NULL,
1243
+ anchor_text_hash TEXT NOT NULL,
1244
+ context_hash TEXT NOT NULL,
1245
+ search_terms_hash TEXT,
1246
+ invariant_hash TEXT,
1247
+ risk_hash TEXT NOT NULL,
1248
+ symbol_kind TEXT NOT NULL,
1249
+ symbol_name TEXT,
1250
+ symbol_exported INTEGER NOT NULL,
1251
+ signature_hash TEXT,
1252
+ body_hash TEXT,
1253
+ symbol_start_line INTEGER,
1254
+ symbol_end_line INTEGER
1255
+ );
1256
+
1257
+ CREATE TABLE source_anchor_status (
1258
+ anchor_id TEXT PRIMARY KEY,
1259
+ status TEXT NOT NULL,
1260
+ confidence REAL NOT NULL,
1261
+ identity_signal TEXT NOT NULL,
1262
+ location_signal TEXT NOT NULL,
1263
+ symbol_signal TEXT NOT NULL,
1264
+ body_signal TEXT NOT NULL,
1265
+ metadata_signal TEXT NOT NULL,
1266
+ semantic_signal TEXT NOT NULL,
1267
+ risk_signal TEXT NOT NULL,
1268
+ navigation_only INTEGER NOT NULL,
1269
+ can_instruct_agent INTEGER NOT NULL
1270
+ );
1271
+
1272
+ CREATE TABLE verification_evidence_summaries (
1273
+ source_path TEXT PRIMARY KEY,
1274
+ source_hash TEXT NOT NULL,
1275
+ command TEXT NOT NULL,
1276
+ kind TEXT NOT NULL,
1277
+ status TEXT NOT NULL,
1278
+ run_dir TEXT,
1279
+ manifest_path TEXT,
1280
+ verification_plan_id TEXT,
1281
+ completion_status TEXT,
1282
+ primary_reason TEXT,
1283
+ matched_intents INTEGER NOT NULL,
1284
+ ran_intents INTEGER NOT NULL,
1285
+ passed_intents INTEGER NOT NULL,
1286
+ failed_intents INTEGER NOT NULL,
1287
+ skipped_intents INTEGER NOT NULL,
1288
+ receipt_count INTEGER NOT NULL,
1289
+ coverage_count INTEGER NOT NULL,
1290
+ remaining_risk_count INTEGER NOT NULL,
1291
+ failure_fingerprint TEXT
1292
+ );
1293
+
1294
+ CREATE TABLE verification_plans (
1295
+ plan_id TEXT PRIMARY KEY,
1296
+ source_path TEXT NOT NULL,
1297
+ classification_hash TEXT,
1298
+ command_contract_hash TEXT,
1299
+ selected_intents_hash TEXT,
1300
+ created_at TEXT,
1301
+ source_hash TEXT NOT NULL
1302
+ );
1303
+
1304
+ CREATE TABLE acceptance_criteria (
1305
+ criterion_id TEXT NOT NULL,
1306
+ plan_id TEXT NOT NULL,
1307
+ source TEXT NOT NULL,
1308
+ statement_hash TEXT,
1309
+ reason TEXT,
1310
+ surface TEXT,
1311
+ path_hash TEXT,
1312
+ PRIMARY KEY (plan_id, criterion_id)
1313
+ );
1314
+
1315
+ CREATE TABLE criterion_coverage (
1316
+ criterion_id TEXT NOT NULL,
1317
+ plan_id TEXT NOT NULL,
1318
+ status TEXT NOT NULL,
1319
+ receipt_count INTEGER NOT NULL,
1320
+ gap_count INTEGER NOT NULL,
1321
+ risk_count INTEGER NOT NULL,
1322
+ PRIMARY KEY (plan_id, criterion_id)
1323
+ );
1324
+
1325
+ CREATE TABLE verification_receipt_summaries (
1326
+ source_path TEXT NOT NULL,
1327
+ ordinal INTEGER NOT NULL,
1328
+ intent TEXT,
1329
+ status TEXT NOT NULL,
1330
+ skipped INTEGER NOT NULL,
1331
+ verification_plan_id TEXT,
1332
+ receipt_path TEXT,
1333
+ receipt_sha256 TEXT,
1334
+ PRIMARY KEY (source_path, ordinal)
1335
+ );
1336
+
1337
+ CREATE TABLE command_receipt_summaries (
1338
+ receipt_hash TEXT NOT NULL,
1339
+ plan_id TEXT NOT NULL,
1340
+ intent TEXT,
1341
+ status TEXT NOT NULL,
1342
+ command_fingerprint TEXT,
1343
+ contract_fingerprint TEXT,
1344
+ current_state_hash TEXT,
1345
+ write_drift_status TEXT,
1346
+ PRIMARY KEY (plan_id, receipt_hash)
1347
+ );
1348
+
1349
+ CREATE TABLE verification_coverage_states (
1350
+ source_path TEXT NOT NULL,
1351
+ criterion_id TEXT NOT NULL,
1352
+ source TEXT NOT NULL,
1353
+ status TEXT NOT NULL,
1354
+ requirement_reason TEXT,
1355
+ intents TEXT NOT NULL,
1356
+ receipt_count INTEGER NOT NULL,
1357
+ gap_count INTEGER NOT NULL,
1358
+ source_anchor_count INTEGER NOT NULL,
1359
+ PRIMARY KEY (source_path, criterion_id)
1360
+ );
1361
+
1362
+ CREATE TABLE verification_risk_signals (
1363
+ source_path TEXT NOT NULL,
1364
+ ordinal INTEGER NOT NULL,
1365
+ code TEXT NOT NULL,
1366
+ severity TEXT NOT NULL,
1367
+ detail_hash TEXT NOT NULL,
1368
+ PRIMARY KEY (source_path, ordinal)
1369
+ );
1370
+
1371
+ CREATE TABLE validation_ratchet_signals (
1372
+ signal_id TEXT PRIMARY KEY,
1373
+ plan_id TEXT,
1374
+ code TEXT NOT NULL,
1375
+ severity TEXT NOT NULL,
1376
+ path_hash TEXT NOT NULL,
1377
+ before_hash TEXT,
1378
+ after_hash TEXT
1379
+ );
1380
+
1381
+ CREATE TABLE completion_verdict_summaries (
1382
+ claim_id TEXT PRIMARY KEY,
1383
+ plan_id TEXT NOT NULL,
1384
+ status TEXT NOT NULL,
1385
+ primary_reason TEXT,
1386
+ risk_count INTEGER NOT NULL,
1387
+ contradiction_count INTEGER NOT NULL,
1388
+ blocker_count INTEGER NOT NULL
1389
+ );
1390
+
1391
+ CREATE TABLE repro_routes (
1392
+ route_id TEXT PRIMARY KEY,
1393
+ task_hash TEXT NOT NULL,
1394
+ route_digest TEXT,
1395
+ route_kind TEXT,
1396
+ failure_oracle_hash TEXT
1397
+ );
1398
+
1399
+ CREATE TABLE repro_observations (
1400
+ route_id TEXT NOT NULL,
1401
+ phase TEXT NOT NULL,
1402
+ outcome TEXT,
1403
+ receipt_hash TEXT,
1404
+ diagnostic_fingerprint TEXT NOT NULL,
1405
+ PRIMARY KEY (route_id, phase)
1406
+ );
1407
+
1408
+ CREATE TABLE failure_fingerprints (
1409
+ fingerprint TEXT PRIMARY KEY,
1410
+ plan_id TEXT,
1411
+ failed_intents_hash TEXT,
1412
+ risk_codes_hash TEXT,
1413
+ seen_count INTEGER NOT NULL,
1414
+ first_seen_at TEXT,
1415
+ last_seen_at TEXT
1416
+ );
1417
+
1418
+ CREATE TABLE verification_failure_fingerprints (
1419
+ source_path TEXT NOT NULL,
1420
+ fingerprint TEXT NOT NULL,
1421
+ verification_plan_id TEXT,
1422
+ status TEXT NOT NULL,
1423
+ failed_intents TEXT NOT NULL,
1424
+ primary_reason TEXT,
1425
+ failed_intents_hash TEXT,
1426
+ risk_codes_hash TEXT,
1427
+ affected_surfaces_hash TEXT,
1428
+ first_seen_at TEXT,
1429
+ last_seen_at TEXT,
1430
+ seen_count INTEGER NOT NULL,
1431
+ requires_new_evidence INTEGER NOT NULL,
1432
+ PRIMARY KEY (source_path, fingerprint)
1433
+ );
1434
+
1435
+ CREATE TABLE source_anchor_risk_signals (
1436
+ anchor_id TEXT PRIMARY KEY,
1437
+ path_hash TEXT NOT NULL,
1438
+ status TEXT NOT NULL,
1439
+ risk_signal TEXT NOT NULL,
1440
+ confidence REAL NOT NULL,
1441
+ navigation_only INTEGER NOT NULL,
1442
+ can_instruct_agent INTEGER NOT NULL
1443
+ );
1444
+ `);
1445
+ if (capabilities.backend === SEARCH_BACKEND_FTS5) {
1446
+ database.run(`
1447
+ CREATE VIRTUAL TABLE search_documents_fts USING fts5(
1448
+ path UNINDEXED,
1449
+ type UNINDEXED,
1450
+ title,
1451
+ sections,
1452
+ terms,
1453
+ snippet
1454
+ );
1455
+
1456
+ CREATE VIRTUAL TABLE search_skills_fts USING fts5(
1457
+ name UNINDEXED,
1458
+ path UNINDEXED,
1459
+ title
1460
+ );
1461
+
1462
+ CREATE VIRTUAL TABLE search_skill_routes_fts USING fts5(
1463
+ route_key UNINDEXED,
1464
+ skill_name UNINDEXED,
1465
+ skill_path UNINDEXED,
1466
+ trigger,
1467
+ required_input,
1468
+ edit_scope,
1469
+ risk,
1470
+ verification_intents,
1471
+ expected_output
1472
+ );
1473
+
1474
+ CREATE VIRTUAL TABLE search_command_intents_fts USING fts5(
1475
+ name UNINDEXED,
1476
+ status UNINDEXED,
1477
+ lifecycle UNINDEXED,
1478
+ run_policy UNINDEXED,
1479
+ description,
1480
+ effects
1481
+ );
1482
+
1483
+ CREATE VIRTUAL TABLE search_source_anchors_fts USING fts5(
1484
+ id UNINDEXED,
1485
+ path UNINDEXED,
1486
+ purpose,
1487
+ search_terms,
1488
+ invariant,
1489
+ risk
1490
+ );
1491
+ `);
1492
+ }
1493
+ }
1494
+ function insertDocumentTerm(database, documentPath, term, source) {
1495
+ const normalized = normalizeSearchText(term ?? '');
1496
+ if (normalized.length === 0) {
1497
+ return;
1498
+ }
1499
+ database.run('INSERT OR IGNORE INTO document_terms (document_path, term, source) VALUES (?, ?, ?)', [
1500
+ documentPath,
1501
+ normalized,
1502
+ source,
1503
+ ]);
1504
+ }
1505
+ function insertSearchNgrams(database, targetKind, targetKey, values, source) {
1506
+ for (const gram of buildSearchNgrams(values)) {
1507
+ database.run('INSERT OR IGNORE INTO search_ngrams (target_kind, target_key, gram, source) VALUES (?, ?, ?, ?)', [targetKind, targetKey, gram, source]);
1508
+ }
1509
+ }
1510
+ function insertPathSurfaceReasons(database, ruleId, reasonKind, values) {
1511
+ values.forEach((value, index) => {
1512
+ database.run('INSERT INTO path_surface_reasons (rule_id, reason_kind, reason, ordinal) VALUES (?, ?, ?, ?)', [ruleId, reasonKind, value, index + 1]);
1513
+ });
1514
+ }
1515
+ function populatePathSurfaceReadModel(database) {
1516
+ for (const rule of listChangeClassificationRuleDescriptors()) {
1517
+ database.run('INSERT INTO path_surfaces (rule_id, pattern_kind, pattern, pattern_flags, surface_kind, category, is_public_surface, update_policy) VALUES (?, ?, ?, ?, ?, ?, ?, ?)', [
1518
+ rule.id,
1519
+ rule.patternKind,
1520
+ rule.pattern,
1521
+ rule.patternFlags,
1522
+ rule.surface.kind,
1523
+ rule.surface.category,
1524
+ rule.surface.isPublicSurface ? 1 : 0,
1525
+ rule.surface.updatePolicy,
1526
+ ]);
1527
+ insertPathSurfaceReasons(database, rule.id, 'change_kind', rule.changeKinds);
1528
+ insertPathSurfaceReasons(database, rule.id, 'validation_reason', rule.surface.validationReasons);
1529
+ insertPathSurfaceReasons(database, rule.id, 'affected_contract', rule.surface.affectedContracts);
1530
+ insertPathSurfaceReasons(database, rule.id, 'drift_check', rule.surface.driftChecks);
1531
+ }
1532
+ }
1533
+ function skillRouteKey(route) {
1534
+ return `${route.skillName}\u0000${route.trigger}`;
1535
+ }
1536
+ function populateSearchTables(database, capabilities, documents, skills, skillRoutes, commandIntents, sourceAnchors) {
1537
+ for (const document of documents) {
1538
+ const documentTerms = queryRows(database, 'SELECT term FROM document_terms WHERE document_path = ? ORDER BY term', [
1539
+ document.path,
1540
+ ]).map((row) => toSearchString(row.term));
1541
+ insertSearchNgrams(database, 'document', document.path, [
1542
+ document.path,
1543
+ document.type,
1544
+ document.title,
1545
+ document.sections.join(' '),
1546
+ documentTerms.join(' '),
1547
+ document.contentSnippet,
1548
+ ], 'workflow_document');
1549
+ if (capabilities.backend === SEARCH_BACKEND_FTS5) {
1550
+ database.run('INSERT INTO search_documents_fts (path, type, title, sections, terms, snippet) VALUES (?, ?, ?, ?, ?, ?)', [
1551
+ document.path,
1552
+ document.type,
1553
+ document.title,
1554
+ document.sections.join(' '),
1555
+ documentTerms.join(' '),
1556
+ document.contentSnippet,
1557
+ ]);
1558
+ }
1559
+ }
1560
+ for (const skill of skills) {
1561
+ insertSearchNgrams(database, 'skill', skill.name, [skill.name, skill.path, skill.title], 'skill');
1562
+ if (capabilities.backend === SEARCH_BACKEND_FTS5) {
1563
+ database.run('INSERT INTO search_skills_fts (name, path, title) VALUES (?, ?, ?)', [
1564
+ skill.name,
1565
+ skill.path,
1566
+ skill.title,
1567
+ ]);
1568
+ }
1569
+ }
1570
+ for (const route of skillRoutes) {
1571
+ const verificationIntents = route.verificationIntents.join(' ');
1572
+ insertSearchNgrams(database, 'skill_route', skillRouteKey(route), [
1573
+ skillRouteKey(route),
1574
+ route.skillName,
1575
+ route.skillPath,
1576
+ route.trigger,
1577
+ route.requiredInput,
1578
+ route.editScope,
1579
+ route.risk,
1580
+ verificationIntents,
1581
+ route.expectedOutput,
1582
+ ], 'skill_route');
1583
+ if (capabilities.backend === SEARCH_BACKEND_FTS5) {
1584
+ database.run('INSERT INTO search_skill_routes_fts (route_key, skill_name, skill_path, trigger, required_input, edit_scope, risk, verification_intents, expected_output) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)', [
1585
+ skillRouteKey(route),
1586
+ route.skillName,
1587
+ route.skillPath,
1588
+ route.trigger,
1589
+ route.requiredInput,
1590
+ route.editScope,
1591
+ route.risk,
1592
+ verificationIntents,
1593
+ route.expectedOutput,
1594
+ ]);
1595
+ }
1596
+ }
1597
+ for (const intent of commandIntents) {
1598
+ const effects = intent.effects
1599
+ .flatMap((effect) => [effect.lock, effect.path ?? '', effect.mode, effect.access, effect.concurrency])
1600
+ .join(' ');
1601
+ insertSearchNgrams(database, 'command_intent', intent.name, [intent.name, intent.status, intent.lifecycle ?? '', intent.runPolicy ?? '', intent.description ?? '', effects], 'command_intent');
1602
+ if (capabilities.backend === SEARCH_BACKEND_FTS5) {
1603
+ database.run('INSERT INTO search_command_intents_fts (name, status, lifecycle, run_policy, description, effects) VALUES (?, ?, ?, ?, ?, ?)', [intent.name, intent.status, intent.lifecycle, intent.runPolicy, intent.description, effects]);
1604
+ }
1605
+ }
1606
+ for (const anchor of sourceAnchors) {
1607
+ insertSearchNgrams(database, 'source_anchor', anchor.id, [
1608
+ anchor.id,
1609
+ anchor.path,
1610
+ anchor.purpose ?? '',
1611
+ anchor.search.join(' '),
1612
+ anchor.invariant ?? '',
1613
+ anchor.risk.join(' '),
1614
+ ], 'source_anchor');
1615
+ if (capabilities.backend === SEARCH_BACKEND_FTS5) {
1616
+ database.run('INSERT INTO search_source_anchors_fts (id, path, purpose, search_terms, invariant, risk) VALUES (?, ?, ?, ?, ?, ?)', [
1617
+ anchor.id,
1618
+ anchor.path,
1619
+ anchor.purpose,
1620
+ anchor.search.join(' '),
1621
+ anchor.invariant,
1622
+ anchor.risk.join(' '),
1623
+ ]);
1624
+ }
1625
+ }
1626
+ }
1627
+ function createSourceAnchorRiskSignals(sourceAnchors) {
1628
+ return sourceAnchors
1629
+ .filter((anchor) => ['changed', 'review', 'stale'].includes(anchor.status))
1630
+ .map((anchor) => ({
1631
+ anchorId: anchor.id,
1632
+ pathHash: sha256Text(anchor.path),
1633
+ status: anchor.status,
1634
+ riskSignal: anchor.signals.risk,
1635
+ confidence: anchor.confidence,
1636
+ navigationOnly: anchor.navigationOnly,
1637
+ canInstructAgent: anchor.canInstructAgent,
1638
+ }));
1639
+ }
1640
+ function populateDatabase(database, capabilities, documents, skills, skillRoutes, commandIntents, sourceAnchors, indexedFiles, verificationEvidence, indexMode, sourceScopeHash, sourceIndexEnabled, indexedAt) {
1641
+ const sourceAnchorRiskSignals = createSourceAnchorRiskSignals(sourceAnchors);
1642
+ database.run('INSERT INTO metadata (key, value) VALUES (?, ?)', ['schema_version', LOCAL_INDEX_SCHEMA_VERSION]);
1643
+ database.run('INSERT INTO metadata (key, value) VALUES (?, ?)', ['parser_version', LOCAL_INDEX_PARSER_VERSION]);
1644
+ database.run('INSERT INTO metadata (key, value) VALUES (?, ?)', ['content_mode', LOCAL_INDEX_CONTENT_MODE]);
1645
+ database.run('INSERT INTO metadata (key, value) VALUES (?, ?)', [
1646
+ 'store_full_content',
1647
+ String(LOCAL_INDEX_STORE_FULL_CONTENT),
1648
+ ]);
1649
+ database.run('INSERT INTO metadata (key, value) VALUES (?, ?)', [
1650
+ 'max_snippet_bytes_per_document',
1651
+ String(MAX_SNIPPET_BYTES_PER_DOCUMENT),
1652
+ ]);
1653
+ database.run('INSERT INTO metadata (key, value) VALUES (?, ?)', [
1654
+ 'excluded_raw_data_kinds',
1655
+ LOCAL_INDEX_EXCLUDED_RAW_DATA_KINDS.join(','),
1656
+ ]);
1657
+ database.run('INSERT INTO metadata (key, value) VALUES (?, ?)', ['search_backend', capabilities.backend]);
1658
+ database.run('INSERT INTO metadata (key, value) VALUES (?, ?)', [
1659
+ 'search_fts5_available',
1660
+ String(capabilities.fts5Available),
1661
+ ]);
1662
+ database.run('INSERT INTO metadata (key, value) VALUES (?, ?)', ['source_scope_hash', sourceScopeHash]);
1663
+ database.run('INSERT INTO metadata (key, value) VALUES (?, ?)', ['source_index_enabled', String(sourceIndexEnabled)]);
1664
+ database.run('INSERT INTO metadata (key, value) VALUES (?, ?)', ['index_mode', indexMode]);
1665
+ for (const indexedFile of indexedFiles) {
1666
+ database.run('INSERT INTO indexed_files (path, source_scope, size_bytes, mtime_ms, content_hash, indexed_at, index_mode, parser_version) VALUES (?, ?, ?, ?, ?, ?, ?, ?)', [
1667
+ indexedFile.path,
1668
+ indexedFile.sourceScope,
1669
+ indexedFile.sizeBytes,
1670
+ indexedFile.mtimeMs,
1671
+ indexedFile.contentHash,
1672
+ indexedAt,
1673
+ indexMode,
1674
+ LOCAL_INDEX_PARSER_VERSION,
1675
+ ]);
1676
+ }
1677
+ for (const document of documents) {
1678
+ database.run('INSERT INTO documents (path, type, title, locale, revision, content_hash, content_snippet) VALUES (?, ?, ?, ?, ?, ?, ?)', [
1679
+ document.path,
1680
+ document.type,
1681
+ document.title,
1682
+ document.locale,
1683
+ document.revision,
1684
+ document.contentHash,
1685
+ document.contentSnippet,
1686
+ ]);
1687
+ document.sections.forEach((heading, index) => {
1688
+ database.run('INSERT INTO sections (document_path, ordinal, heading) VALUES (?, ?, ?)', [
1689
+ document.path,
1690
+ index + 1,
1691
+ heading,
1692
+ ]);
1693
+ });
1694
+ }
1695
+ for (const skill of skills) {
1696
+ database.run('INSERT INTO skills (name, path, title) VALUES (?, ?, ?)', [skill.name, skill.path, skill.title]);
1697
+ }
1698
+ for (const route of skillRoutes) {
1699
+ const verificationIntents = route.verificationIntents.join(', ');
1700
+ database.run('INSERT INTO skill_routes (skill_name, skill_path, trigger, required_input, edit_scope, risk, verification_intents, expected_output) VALUES (?, ?, ?, ?, ?, ?, ?, ?)', [
1701
+ route.skillName,
1702
+ route.skillPath,
1703
+ route.trigger,
1704
+ route.requiredInput,
1705
+ route.editScope,
1706
+ route.risk,
1707
+ verificationIntents,
1708
+ route.expectedOutput,
1709
+ ]);
1710
+ insertDocumentTerm(database, '.mustflow/skills/INDEX.md', route.skillName, 'skill_route_name');
1711
+ insertDocumentTerm(database, '.mustflow/skills/INDEX.md', route.trigger, 'skill_route_trigger');
1712
+ insertDocumentTerm(database, '.mustflow/skills/INDEX.md', route.risk, 'skill_route_risk');
1713
+ insertDocumentTerm(database, '.mustflow/skills/INDEX.md', route.requiredInput, 'skill_route_required_input');
1714
+ insertDocumentTerm(database, '.mustflow/skills/INDEX.md', route.editScope, 'skill_route_edit_scope');
1715
+ insertDocumentTerm(database, '.mustflow/skills/INDEX.md', verificationIntents, 'skill_route_verification_intents');
1716
+ }
1717
+ for (const intent of commandIntents) {
1718
+ database.run('INSERT INTO command_intents (name, status, lifecycle, run_policy, description) VALUES (?, ?, ?, ?, ?)', [intent.name, intent.status, intent.lifecycle, intent.runPolicy, intent.description]);
1719
+ insertDocumentTerm(database, '.mustflow/config/commands.toml', intent.name, 'command_intent');
1720
+ insertDocumentTerm(database, '.mustflow/config/commands.toml', intent.status, 'command_status');
1721
+ insertDocumentTerm(database, '.mustflow/config/commands.toml', intent.lifecycle, 'command_lifecycle');
1722
+ insertDocumentTerm(database, '.mustflow/config/commands.toml', intent.runPolicy, 'command_run_policy');
1723
+ insertDocumentTerm(database, '.mustflow/config/commands.toml', intent.description, 'command_description');
1724
+ for (const effect of intent.effects) {
1725
+ database.run('INSERT INTO command_effects (intent, source, access, mode, path, lock, concurrency) VALUES (?, ?, ?, ?, ?, ?, ?)', [effect.intent, effect.source, effect.access, effect.mode, effect.path, effect.lock, effect.concurrency]);
1726
+ if (effect.path !== null) {
1727
+ insertDocumentTerm(database, '.mustflow/config/commands.toml', effect.path, 'command_effect_path');
1728
+ }
1729
+ insertDocumentTerm(database, '.mustflow/config/commands.toml', effect.lock, 'command_effect_lock');
1730
+ insertDocumentTerm(database, '.mustflow/config/commands.toml', effect.mode, 'command_effect_mode');
1731
+ }
1732
+ }
1733
+ for (const anchor of sourceAnchors) {
1734
+ database.run('INSERT INTO source_anchors (id, path, line_start, purpose, search_terms, invariant, risk, navigation_only, can_instruct_agent) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)', [
1735
+ anchor.id,
1736
+ anchor.path,
1737
+ anchor.lineStart,
1738
+ anchor.purpose,
1739
+ anchor.search.join(', '),
1740
+ anchor.invariant,
1741
+ anchor.risk.join(', '),
1742
+ anchor.navigationOnly ? 1 : 0,
1743
+ anchor.canInstructAgent ? 1 : 0,
1744
+ ]);
1745
+ database.run('INSERT INTO source_anchor_fingerprints (anchor_id, path, line_start, anchor_metadata_hash, anchor_text_hash, context_hash, search_terms_hash, invariant_hash, risk_hash, symbol_kind, symbol_name, symbol_exported, signature_hash, body_hash, symbol_start_line, symbol_end_line) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)', [
1746
+ anchor.id,
1747
+ anchor.path,
1748
+ anchor.lineStart,
1749
+ anchor.fingerprint.anchorMetadataHash,
1750
+ anchor.fingerprint.anchorTextHash,
1751
+ anchor.fingerprint.contextHash,
1752
+ anchor.fingerprint.searchTermsHash,
1753
+ anchor.fingerprint.invariantHash,
1754
+ anchor.fingerprint.riskHash,
1755
+ anchor.fingerprint.symbol.kind,
1756
+ anchor.fingerprint.symbol.name,
1757
+ anchor.fingerprint.symbol.exported ? 1 : 0,
1758
+ anchor.fingerprint.symbol.signatureHash,
1759
+ anchor.fingerprint.symbol.bodyHash,
1760
+ anchor.fingerprint.symbol.startLine,
1761
+ anchor.fingerprint.symbol.endLine,
1762
+ ]);
1763
+ database.run('INSERT INTO source_anchor_status (anchor_id, status, confidence, identity_signal, location_signal, symbol_signal, body_signal, metadata_signal, semantic_signal, risk_signal, navigation_only, can_instruct_agent) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)', [
1764
+ anchor.id,
1765
+ anchor.status,
1766
+ anchor.confidence,
1767
+ anchor.signals.identity,
1768
+ anchor.signals.location,
1769
+ anchor.signals.symbol,
1770
+ anchor.signals.body,
1771
+ anchor.signals.metadata,
1772
+ anchor.signals.semantic,
1773
+ anchor.signals.risk,
1774
+ anchor.navigationOnly ? 1 : 0,
1775
+ anchor.canInstructAgent ? 1 : 0,
1776
+ ]);
1777
+ }
1778
+ for (const summary of verificationEvidence.summaries) {
1779
+ database.run('INSERT INTO verification_evidence_summaries (source_path, source_hash, command, kind, status, run_dir, manifest_path, verification_plan_id, completion_status, primary_reason, matched_intents, ran_intents, passed_intents, failed_intents, skipped_intents, receipt_count, coverage_count, remaining_risk_count, failure_fingerprint) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)', [
1780
+ summary.sourcePath,
1781
+ summary.sourceHash,
1782
+ summary.command,
1783
+ summary.kind,
1784
+ summary.status,
1785
+ summary.runDir,
1786
+ summary.manifestPath,
1787
+ summary.verificationPlanId,
1788
+ summary.completionStatus,
1789
+ summary.primaryReason,
1790
+ summary.matchedIntents,
1791
+ summary.ranIntents,
1792
+ summary.passedIntents,
1793
+ summary.failedIntents,
1794
+ summary.skippedIntents,
1795
+ summary.receiptCount,
1796
+ summary.coverageCount,
1797
+ summary.remainingRiskCount,
1798
+ summary.failureFingerprint,
1799
+ ]);
1800
+ }
1801
+ for (const plan of verificationEvidence.verificationPlans) {
1802
+ database.run('INSERT INTO verification_plans (plan_id, source_path, classification_hash, command_contract_hash, selected_intents_hash, created_at, source_hash) VALUES (?, ?, ?, ?, ?, ?, ?)', [
1803
+ plan.planId,
1804
+ plan.sourcePath,
1805
+ plan.classificationHash,
1806
+ plan.commandContractHash,
1807
+ plan.selectedIntentsHash,
1808
+ plan.createdAt,
1809
+ plan.sourceHash,
1810
+ ]);
1811
+ }
1812
+ for (const criterion of verificationEvidence.acceptanceCriteria) {
1813
+ database.run('INSERT INTO acceptance_criteria (criterion_id, plan_id, source, statement_hash, reason, surface, path_hash) VALUES (?, ?, ?, ?, ?, ?, ?)', [
1814
+ criterion.criterionId,
1815
+ criterion.planId,
1816
+ criterion.source,
1817
+ criterion.statementHash,
1818
+ criterion.reason,
1819
+ criterion.surface,
1820
+ criterion.pathHash,
1821
+ ]);
1822
+ }
1823
+ for (const coverage of verificationEvidence.criterionCoverage) {
1824
+ database.run('INSERT INTO criterion_coverage (criterion_id, plan_id, status, receipt_count, gap_count, risk_count) VALUES (?, ?, ?, ?, ?, ?)', [coverage.criterionId, coverage.planId, coverage.status, coverage.receiptCount, coverage.gapCount, coverage.riskCount]);
1825
+ }
1826
+ for (const receipt of verificationEvidence.receipts) {
1827
+ database.run('INSERT INTO verification_receipt_summaries (source_path, ordinal, intent, status, skipped, verification_plan_id, receipt_path, receipt_sha256) VALUES (?, ?, ?, ?, ?, ?, ?, ?)', [
1828
+ receipt.sourcePath,
1829
+ receipt.ordinal,
1830
+ receipt.intent,
1831
+ receipt.status,
1832
+ receipt.skipped ? 1 : 0,
1833
+ receipt.verificationPlanId,
1834
+ receipt.receiptPath,
1835
+ receipt.receiptSha256,
1836
+ ]);
1837
+ }
1838
+ for (const receipt of verificationEvidence.commandReceiptSummaries) {
1839
+ database.run('INSERT INTO command_receipt_summaries (receipt_hash, plan_id, intent, status, command_fingerprint, contract_fingerprint, current_state_hash, write_drift_status) VALUES (?, ?, ?, ?, ?, ?, ?, ?)', [
1840
+ receipt.receiptHash,
1841
+ receipt.planId,
1842
+ receipt.intent,
1843
+ receipt.status,
1844
+ receipt.commandFingerprint,
1845
+ receipt.contractFingerprint,
1846
+ receipt.currentStateHash,
1847
+ receipt.writeDriftStatus,
1848
+ ]);
1849
+ }
1850
+ for (const coverage of verificationEvidence.coverageStates) {
1851
+ database.run('INSERT INTO verification_coverage_states (source_path, criterion_id, source, status, requirement_reason, intents, receipt_count, gap_count, source_anchor_count) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)', [
1852
+ coverage.sourcePath,
1853
+ coverage.criterionId,
1854
+ coverage.source,
1855
+ coverage.status,
1856
+ coverage.requirementReason,
1857
+ joinedList(coverage.intents),
1858
+ coverage.receiptCount,
1859
+ coverage.gapCount,
1860
+ coverage.sourceAnchorCount,
1861
+ ]);
1862
+ }
1863
+ for (const risk of verificationEvidence.riskSignals) {
1864
+ database.run('INSERT INTO verification_risk_signals (source_path, ordinal, code, severity, detail_hash) VALUES (?, ?, ?, ?, ?)', [risk.sourcePath, risk.ordinal, risk.code, risk.severity, risk.detailHash]);
1865
+ }
1866
+ for (const signal of verificationEvidence.validationRatchetSignals) {
1867
+ database.run('INSERT INTO validation_ratchet_signals (signal_id, plan_id, code, severity, path_hash, before_hash, after_hash) VALUES (?, ?, ?, ?, ?, ?, ?)', [signal.signalId, signal.planId, signal.code, signal.severity, signal.pathHash, signal.beforeHash, signal.afterHash]);
1868
+ }
1869
+ for (const verdict of verificationEvidence.completionVerdictSummaries) {
1870
+ database.run('INSERT INTO completion_verdict_summaries (claim_id, plan_id, status, primary_reason, risk_count, contradiction_count, blocker_count) VALUES (?, ?, ?, ?, ?, ?, ?)', [
1871
+ verdict.claimId,
1872
+ verdict.planId,
1873
+ verdict.status,
1874
+ verdict.primaryReason,
1875
+ verdict.riskCount,
1876
+ verdict.contradictionCount,
1877
+ verdict.blockerCount,
1878
+ ]);
1879
+ }
1880
+ for (const route of verificationEvidence.reproRoutes) {
1881
+ database.run('INSERT INTO repro_routes (route_id, task_hash, route_digest, route_kind, failure_oracle_hash) VALUES (?, ?, ?, ?, ?)', [route.routeId, route.taskHash, route.routeDigest, route.routeKind, route.failureOracleHash]);
1882
+ }
1883
+ for (const observation of verificationEvidence.reproObservations) {
1884
+ database.run('INSERT INTO repro_observations (route_id, phase, outcome, receipt_hash, diagnostic_fingerprint) VALUES (?, ?, ?, ?, ?)', [
1885
+ observation.routeId,
1886
+ observation.phase,
1887
+ observation.outcome,
1888
+ observation.receiptHash,
1889
+ observation.diagnosticFingerprint,
1890
+ ]);
1891
+ }
1892
+ for (const fingerprint of verificationEvidence.failureFingerprintReadModels) {
1893
+ database.run('INSERT INTO failure_fingerprints (fingerprint, plan_id, failed_intents_hash, risk_codes_hash, seen_count, first_seen_at, last_seen_at) VALUES (?, ?, ?, ?, ?, ?, ?)', [
1894
+ fingerprint.fingerprint,
1895
+ fingerprint.planId,
1896
+ fingerprint.failedIntentsHash,
1897
+ fingerprint.riskCodesHash,
1898
+ fingerprint.seenCount,
1899
+ fingerprint.firstSeenAt,
1900
+ fingerprint.lastSeenAt,
1901
+ ]);
1902
+ }
1903
+ for (const fingerprint of verificationEvidence.failureFingerprints) {
1904
+ database.run('INSERT INTO verification_failure_fingerprints (source_path, fingerprint, verification_plan_id, status, failed_intents, primary_reason, failed_intents_hash, risk_codes_hash, affected_surfaces_hash, first_seen_at, last_seen_at, seen_count, requires_new_evidence) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)', [
1905
+ fingerprint.sourcePath,
1906
+ fingerprint.fingerprint,
1907
+ fingerprint.verificationPlanId,
1908
+ fingerprint.status,
1909
+ joinedList(fingerprint.failedIntents),
1910
+ fingerprint.primaryReason,
1911
+ fingerprint.failedIntentsHash,
1912
+ fingerprint.riskCodesHash,
1913
+ fingerprint.affectedSurfacesHash,
1914
+ fingerprint.firstSeenAt,
1915
+ fingerprint.lastSeenAt,
1916
+ fingerprint.seenCount,
1917
+ fingerprint.requiresNewEvidence ? 1 : 0,
1918
+ ]);
1919
+ }
1920
+ for (const signal of sourceAnchorRiskSignals) {
1921
+ database.run('INSERT INTO source_anchor_risk_signals (anchor_id, path_hash, status, risk_signal, confidence, navigation_only, can_instruct_agent) VALUES (?, ?, ?, ?, ?, ?, ?)', [
1922
+ signal.anchorId,
1923
+ signal.pathHash,
1924
+ signal.status,
1925
+ signal.riskSignal,
1926
+ signal.confidence,
1927
+ signal.navigationOnly ? 1 : 0,
1928
+ signal.canInstructAgent ? 1 : 0,
1929
+ ]);
1930
+ }
1931
+ populatePathSurfaceReadModel(database);
1932
+ populateSearchTables(database, capabilities, documents, skills, skillRoutes, commandIntents, sourceAnchors);
1933
+ }
1934
+ function indexedFilesMatch(database, currentFiles) {
1935
+ const rows = queryRows(database, 'SELECT path, source_scope, content_hash, parser_version FROM indexed_files ORDER BY path');
1936
+ if (rows.length !== currentFiles.length) {
1937
+ return false;
1938
+ }
1939
+ const currentByPath = new Map(currentFiles.map((file) => [file.path, file]));
1940
+ for (const row of rows) {
1941
+ const storedPath = toSearchString(row.path);
1942
+ const current = currentByPath.get(storedPath);
1943
+ if (!current) {
1944
+ return false;
1945
+ }
1946
+ if (toSearchString(row.source_scope) !== current.sourceScope ||
1947
+ toSearchString(row.content_hash) !== current.contentHash ||
1948
+ toSearchString(row.parser_version) !== LOCAL_INDEX_PARSER_VERSION) {
1949
+ return false;
1950
+ }
1951
+ }
1952
+ return true;
1953
+ }
1954
+ async function readIncrementalReuseDecision(SQL, databasePath, currentFiles, sourceScopeHash) {
1955
+ if (!existsSync(databasePath)) {
1956
+ return { reusable: false, rebuildReason: 'missing_index', capabilities: null };
1957
+ }
1958
+ let database;
1959
+ try {
1960
+ database = new SQL.Database(readFileSync(databasePath));
1961
+ if (readStoredSchemaVersion(database) !== LOCAL_INDEX_SCHEMA_VERSION) {
1962
+ return { reusable: false, rebuildReason: 'schema_version_mismatch', capabilities: null };
1963
+ }
1964
+ if (readMetadataValue(database, 'parser_version') !== LOCAL_INDEX_PARSER_VERSION) {
1965
+ return { reusable: false, rebuildReason: 'parser_version_mismatch', capabilities: null };
1966
+ }
1967
+ if (readMetadataValue(database, 'source_scope_hash') !== sourceScopeHash) {
1968
+ return { reusable: false, rebuildReason: 'source_scope_mismatch', capabilities: null };
1969
+ }
1970
+ if (!hasTable(database, 'indexed_files')) {
1971
+ return { reusable: false, rebuildReason: 'indexed_files_missing', capabilities: null };
1972
+ }
1973
+ if (!indexedFilesMatch(database, currentFiles)) {
1974
+ return { reusable: false, rebuildReason: 'file_fingerprint_mismatch', capabilities: null };
1975
+ }
1976
+ return {
1977
+ reusable: true,
1978
+ rebuildReason: null,
1979
+ capabilities: readStoredSearchCapabilities(database),
1980
+ };
1981
+ }
1982
+ catch {
1983
+ return { reusable: false, rebuildReason: 'unreadable_index', capabilities: null };
1984
+ }
1985
+ finally {
1986
+ database?.close();
1987
+ }
1988
+ }
1989
+ /**
1990
+ * mf:anchor cli.index.create
1991
+ * purpose: Build the local SQLite index for workflow documents and optional source anchors.
1992
+ * search: mf index, local index, sqlite, source anchors, workflow documents
1993
+ * invariant: Source anchors are indexed only when requested by CLI flag or local index configuration.
1994
+ * risk: cache, config
1995
+ */
1996
+ export async function createLocalIndex(projectRoot, options = {}) {
1997
+ const databasePath = getLocalIndexDatabasePath(projectRoot);
1998
+ const dryRun = options.dryRun === true;
1999
+ const incremental = options.incremental === true;
2000
+ const indexMode = incremental ? 'incremental' : 'full';
2001
+ const documents = collectDocuments(projectRoot);
2002
+ const skills = collectSkills(documents);
2003
+ const skillRoutes = collectSkillRoutes(projectRoot);
2004
+ const commandIntents = collectCommandIntents(projectRoot);
2005
+ const sourceConfig = readLocalIndexSourceConfig(projectRoot);
2006
+ const includeSource = options.includeSource === true || sourceConfig.enabledByDefault;
2007
+ const sourceScopeHash = getSourceScopeHash(includeSource, sourceConfig);
2008
+ const previousSourceAnchors = includeSource
2009
+ ? await readPreviousSourceAnchorSnapshots(databasePath).catch(() => [])
2010
+ : [];
2011
+ const sourceAnchors = includeSource
2012
+ ? collectSourceAnchorIndexRecords(projectRoot, previousSourceAnchors, {
2013
+ ...sourceConfig,
2014
+ excludeGeneratedOrVendor: true,
2015
+ })
2016
+ : [];
2017
+ const verificationEvidence = createVerificationEvidenceIndex(projectRoot);
2018
+ const indexedFiles = collectIndexedFileRecords(projectRoot, documents, sourceAnchors);
2019
+ let capabilities = searchCapabilities(false);
2020
+ let reusedExisting = false;
2021
+ let rebuildReason = null;
2022
+ const SQL = await loadSqlJs();
2023
+ const capabilityDatabase = new SQL.Database();
2024
+ capabilities = detectLocalSearchCapabilities(capabilityDatabase);
2025
+ capabilityDatabase.close();
2026
+ if (incremental) {
2027
+ const reuseDecision = await readIncrementalReuseDecision(SQL, databasePath, indexedFiles, sourceScopeHash);
2028
+ reusedExisting = reuseDecision.reusable;
2029
+ rebuildReason = reuseDecision.rebuildReason;
2030
+ capabilities = reuseDecision.capabilities ?? capabilities;
2031
+ }
2032
+ if (!dryRun && !reusedExisting) {
2033
+ const database = new SQL.Database();
2034
+ createSchema(database, capabilities);
2035
+ populateDatabase(database, capabilities, documents, skills, skillRoutes, commandIntents, sourceAnchors, indexedFiles, verificationEvidence, indexMode, sourceScopeHash, includeSource, new Date().toISOString());
2036
+ mkdirSync(path.dirname(databasePath), { recursive: true });
2037
+ writeFileSync(databasePath, database.export());
2038
+ database.close();
2039
+ }
2040
+ return {
2041
+ schema_version: LOCAL_INDEX_SCHEMA_VERSION,
2042
+ command: 'index',
2043
+ ok: true,
2044
+ mustflow_root: path.resolve(projectRoot),
2045
+ database_path: databasePath,
2046
+ dry_run: dryRun,
2047
+ wrote_files: !dryRun && !reusedExisting,
2048
+ index_mode: indexMode,
2049
+ reused_existing: reusedExisting,
2050
+ rebuild_reason: rebuildReason,
2051
+ document_count: documents.length,
2052
+ skill_count: skills.length,
2053
+ skill_route_count: skillRoutes.length,
2054
+ command_intent_count: commandIntents.length,
2055
+ command_effect_count: commandIntents.reduce((count, intent) => count + intent.effects.length, 0),
2056
+ verification_evidence_summary_count: verificationEvidence.summaries.length,
2057
+ verification_plan_count: verificationEvidence.verificationPlans.length,
2058
+ acceptance_criteria_count: verificationEvidence.acceptanceCriteria.length,
2059
+ criterion_coverage_count: verificationEvidence.criterionCoverage.length,
2060
+ verification_receipt_summary_count: verificationEvidence.receipts.length,
2061
+ command_receipt_summary_count: verificationEvidence.commandReceiptSummaries.length,
2062
+ verification_coverage_state_count: verificationEvidence.coverageStates.length,
2063
+ verification_risk_signal_count: verificationEvidence.riskSignals.length,
2064
+ validation_ratchet_signal_count: verificationEvidence.validationRatchetSignals.length,
2065
+ completion_verdict_summary_count: verificationEvidence.completionVerdictSummaries.length,
2066
+ repro_route_count: verificationEvidence.reproRoutes.length,
2067
+ repro_observation_count: verificationEvidence.reproObservations.length,
2068
+ failure_fingerprint_count: verificationEvidence.failureFingerprints.length,
2069
+ source_index_enabled: includeSource,
2070
+ source_anchor_count: sourceAnchors.length,
2071
+ source_anchor_risk_signal_count: createSourceAnchorRiskSignals(sourceAnchors).length,
2072
+ search_backend: capabilities.backend,
2073
+ search_fts5_available: capabilities.fts5Available,
2074
+ content_mode: LOCAL_INDEX_CONTENT_MODE,
2075
+ store_full_content: LOCAL_INDEX_STORE_FULL_CONTENT,
2076
+ max_snippet_bytes_per_document: MAX_SNIPPET_BYTES_PER_DOCUMENT,
2077
+ excluded_raw_data_kinds: [...LOCAL_INDEX_EXCLUDED_RAW_DATA_KINDS],
2078
+ indexed_file_count: indexedFiles.length,
2079
+ indexed_paths: documents.map((document) => document.path),
2080
+ };
2081
+ }
2082
+ function readStoredSchemaVersion(database) {
2083
+ return readMetadataValue(database, 'schema_version');
2084
+ }
2085
+ function getStalePaths(projectRoot, database) {
2086
+ const schemaVersion = readStoredSchemaVersion(database);
2087
+ if (schemaVersion !== LOCAL_INDEX_SCHEMA_VERSION) {
2088
+ return ['.mustflow/cache/mustflow.sqlite'];
2089
+ }
2090
+ if (hasTable(database, 'indexed_files')) {
2091
+ const stalePaths = new Set();
2092
+ const indexedRows = queryRows(database, 'SELECT path, source_scope, content_hash FROM indexed_files');
2093
+ const indexedPaths = new Set(indexedRows.map((row) => toSearchString(row.path)));
2094
+ for (const row of indexedRows) {
2095
+ const indexedPath = toSearchString(row.path);
2096
+ const sourceScope = toSearchString(row.source_scope) === 'source_anchor' ? 'source_anchor' : 'workflow';
2097
+ try {
2098
+ const current = readIndexedFileRecord(projectRoot, indexedPath, sourceScope);
2099
+ if (current.contentHash !== toSearchString(row.content_hash)) {
2100
+ stalePaths.add(indexedPath);
2101
+ }
2102
+ }
2103
+ catch {
2104
+ stalePaths.add(indexedPath);
2105
+ }
2106
+ }
2107
+ for (const document of collectDocuments(projectRoot)) {
2108
+ if (!indexedPaths.has(document.path)) {
2109
+ stalePaths.add(document.path);
2110
+ }
2111
+ }
2112
+ return Array.from(stalePaths).sort((left, right) => left.localeCompare(right));
2113
+ }
2114
+ const indexedRows = queryRows(database, 'SELECT path, content_hash FROM documents');
2115
+ const indexedHashes = new Map(indexedRows.map((row) => [toSearchString(row.path), toSearchString(row.content_hash)]));
2116
+ const currentDocuments = collectDocuments(projectRoot);
2117
+ const currentHashes = new Map(currentDocuments.map((document) => [document.path, document.contentHash]));
2118
+ const stalePaths = new Set();
2119
+ for (const [indexedPath, indexedHash] of indexedHashes) {
2120
+ if (currentHashes.get(indexedPath) !== indexedHash) {
2121
+ stalePaths.add(indexedPath);
2122
+ }
2123
+ }
2124
+ for (const currentPath of currentHashes.keys()) {
2125
+ if (!indexedHashes.has(currentPath)) {
2126
+ stalePaths.add(currentPath);
2127
+ }
2128
+ }
2129
+ return Array.from(stalePaths).sort((left, right) => left.localeCompare(right));
2130
+ }
2131
+ function mapCommandLockConflict(row, intent) {
2132
+ const targetIsLeft = toSearchString(row.left_intent) === intent;
2133
+ const targetPrefix = targetIsLeft ? 'left' : 'right';
2134
+ const otherPrefix = targetIsLeft ? 'right' : 'left';
2135
+ return {
2136
+ intent: toSearchString(row[`${otherPrefix}_intent`]),
2137
+ lock: toSearchString(row.lock),
2138
+ paths: splitIndexedList(row[`${targetPrefix}_paths`]),
2139
+ modes: splitIndexedList(row[`${targetPrefix}_modes`]),
2140
+ concurrencies: splitIndexedList(row[`${targetPrefix}_concurrencies`]),
2141
+ conflictingPaths: splitIndexedList(row[`${otherPrefix}_paths`]),
2142
+ conflictingModes: splitIndexedList(row[`${otherPrefix}_modes`]),
2143
+ conflictingConcurrencies: splitIndexedList(row[`${otherPrefix}_concurrencies`]),
2144
+ };
2145
+ }
2146
+ /**
2147
+ * mf:anchor cli.index.command-effect-graph
2148
+ * purpose: Read command-effect lock and conflict explanations from the local SQLite index.
2149
+ * search: mf explain command, command locks, local index, sqlite graph
2150
+ * invariant: Indexed command-effect rows explain current commands.toml only when the index is fresh and never grant command authority.
2151
+ * risk: cache, config
2152
+ */
2153
+ function queryLocalCommandEffectGraph(databasePath, database, intent) {
2154
+ const writeLocks = queryRows(database, `
2155
+ SELECT lock, paths, modes, sources, concurrencies, effect_count
2156
+ FROM command_write_locks
2157
+ WHERE intent = ?
2158
+ ORDER BY lock
2159
+ `, [intent]).map((row) => ({
2160
+ lock: toSearchString(row.lock),
2161
+ paths: splitIndexedList(row.paths),
2162
+ modes: splitIndexedList(row.modes),
2163
+ sources: splitIndexedList(row.sources),
2164
+ concurrencies: splitIndexedList(row.concurrencies),
2165
+ effectCount: typeof row.effect_count === 'number' && Number.isFinite(row.effect_count) ? row.effect_count : 0,
2166
+ }));
2167
+ const lockConflicts = queryRows(database, `
2168
+ SELECT
2169
+ left_intent,
2170
+ right_intent,
2171
+ lock,
2172
+ left_paths,
2173
+ right_paths,
2174
+ left_modes,
2175
+ right_modes,
2176
+ left_concurrencies,
2177
+ right_concurrencies
2178
+ FROM command_lock_conflicts
2179
+ WHERE left_intent = ? OR right_intent = ?
2180
+ ORDER BY lock, left_intent, right_intent
2181
+ `, [intent, intent]).map((row) => mapCommandLockConflict(row, intent));
2182
+ return {
2183
+ source: 'local_index',
2184
+ authority: 'explanation_only',
2185
+ commandAuthority: '.mustflow/config/commands.toml',
2186
+ grantsCommandAuthority: false,
2187
+ status: 'fresh',
2188
+ databasePath,
2189
+ indexFresh: true,
2190
+ stalePaths: [],
2191
+ writeLocks,
2192
+ lockConflicts,
2193
+ refreshHint: null,
2194
+ };
2195
+ }
2196
+ export async function readLocalCommandEffectGraph(projectRoot, intent) {
2197
+ const graphs = await readLocalCommandEffectGraphs(projectRoot, [intent]);
2198
+ return graphs.get(intent) ?? createCommandEffectGraphStatus(getLocalIndexDatabasePath(projectRoot), 'unreadable');
2199
+ }
2200
+ export async function readLocalCommandEffectGraphs(projectRoot, intents) {
2201
+ const databasePath = getLocalIndexDatabasePath(projectRoot);
2202
+ const intentNames = [...new Set(intents)];
2203
+ const statusMap = (status, stalePaths = []) => new Map(intentNames.map((intent) => [intent, createCommandEffectGraphStatus(databasePath, status, stalePaths)]));
2204
+ if (!existsSync(databasePath)) {
2205
+ return statusMap('missing');
2206
+ }
2207
+ const SQL = await loadSqlJs();
2208
+ const database = new SQL.Database(readFileSync(databasePath));
2209
+ try {
2210
+ const stalePaths = getStalePaths(projectRoot, database);
2211
+ if (stalePaths.length > 0) {
2212
+ return statusMap('stale', stalePaths);
2213
+ }
2214
+ return new Map(intentNames.map((intent) => [intent, queryLocalCommandEffectGraph(databasePath, database, intent)]));
2215
+ }
2216
+ catch {
2217
+ return statusMap('unreadable');
2218
+ }
2219
+ finally {
2220
+ database.close();
2221
+ }
2222
+ }
2223
+ function createPathSurfaceReadModelStatus(databasePath, status, inputPath, stalePaths = []) {
2224
+ return {
2225
+ source: 'local_index',
2226
+ status,
2227
+ databasePath,
2228
+ indexFresh: status === 'fresh',
2229
+ stalePaths,
2230
+ inputPath,
2231
+ match: null,
2232
+ refreshHint: status === 'fresh' ? null : 'Run `mf index` to refresh path-surface explanations.',
2233
+ };
2234
+ }
2235
+ function createLocalIndexPromptContextStatus(databasePath, status, stalePaths = [], capabilities = null) {
2236
+ return {
2237
+ source: 'local_index',
2238
+ status,
2239
+ databasePath,
2240
+ indexFresh: status === 'fresh',
2241
+ stalePaths,
2242
+ searchBackend: capabilities?.backend ?? null,
2243
+ searchFts5Available: capabilities?.fts5Available ?? null,
2244
+ refreshHint: status === 'fresh' ? null : 'Run `mf index` to refresh prompt-cache task context local-index metadata.',
2245
+ };
2246
+ }
2247
+ export async function readLocalIndexPromptContext(projectRoot) {
2248
+ const databasePath = getLocalIndexDatabasePath(projectRoot);
2249
+ if (!existsSync(databasePath)) {
2250
+ return createLocalIndexPromptContextStatus(databasePath, 'missing');
2251
+ }
2252
+ let database;
2253
+ try {
2254
+ const SQL = await loadSqlJs();
2255
+ database = new SQL.Database(readFileSync(databasePath));
2256
+ const capabilities = readStoredSearchCapabilities(database);
2257
+ const stalePaths = getStalePaths(projectRoot, database);
2258
+ if (stalePaths.length > 0) {
2259
+ return createLocalIndexPromptContextStatus(databasePath, 'stale', stalePaths, capabilities);
2260
+ }
2261
+ return createLocalIndexPromptContextStatus(databasePath, 'fresh', [], capabilities);
2262
+ }
2263
+ catch {
2264
+ return createLocalIndexPromptContextStatus(databasePath, 'unreadable');
2265
+ }
2266
+ finally {
2267
+ database?.close();
2268
+ }
2269
+ }
2270
+ function createVerificationReadModelQueryStatus(databasePath, status, planId, stalePaths = []) {
2271
+ return {
2272
+ source: 'local_index',
2273
+ authority: 'evidence_only',
2274
+ commandAuthority: '.mustflow/config/commands.toml',
2275
+ grantsCommandAuthority: false,
2276
+ status,
2277
+ databasePath,
2278
+ indexFresh: status === 'fresh',
2279
+ stalePaths,
2280
+ planId,
2281
+ uncoveredCriteria: [],
2282
+ severeRisks: [],
2283
+ nonPassingReceipts: [],
2284
+ repeatedFailureFingerprints: [],
2285
+ validationWeakeningSignals: [],
2286
+ refreshHint: status === 'fresh' ? null : 'Run `mf index` to refresh verification read-model evidence.',
2287
+ };
2288
+ }
2289
+ function readLatestVerificationPlanId(database) {
2290
+ const row = queryRows(database, `
2291
+ SELECT plan_id
2292
+ FROM verification_plans
2293
+ ORDER BY COALESCE(created_at, '') DESC, source_path DESC, plan_id DESC
2294
+ LIMIT 1
2295
+ `)[0];
2296
+ return toSearchString(row?.plan_id) || null;
2297
+ }
2298
+ function readUncoveredCriteria(database, planId) {
2299
+ return queryRows(database, `
2300
+ SELECT
2301
+ acceptance_criteria.criterion_id,
2302
+ acceptance_criteria.source,
2303
+ acceptance_criteria.reason,
2304
+ acceptance_criteria.surface,
2305
+ acceptance_criteria.path_hash,
2306
+ criterion_coverage.status AS coverage_status,
2307
+ criterion_coverage.receipt_count,
2308
+ criterion_coverage.gap_count,
2309
+ criterion_coverage.risk_count
2310
+ FROM acceptance_criteria
2311
+ LEFT JOIN criterion_coverage
2312
+ ON criterion_coverage.plan_id = acceptance_criteria.plan_id
2313
+ AND criterion_coverage.criterion_id = acceptance_criteria.criterion_id
2314
+ WHERE acceptance_criteria.plan_id = ?
2315
+ AND (criterion_coverage.status IS NULL OR criterion_coverage.status != 'covered')
2316
+ ORDER BY acceptance_criteria.criterion_id
2317
+ `, [planId]).map((row) => ({
2318
+ criterionId: toSearchString(row.criterion_id),
2319
+ source: toSearchString(row.source),
2320
+ reason: toSearchString(row.reason) || null,
2321
+ surface: toSearchString(row.surface) || null,
2322
+ pathHash: toSearchString(row.path_hash) || null,
2323
+ coverageStatus: toSearchString(row.coverage_status) || null,
2324
+ receiptCount: toNullableNumber(row.receipt_count) ?? 0,
2325
+ gapCount: toNullableNumber(row.gap_count) ?? 0,
2326
+ riskCount: toNullableNumber(row.risk_count) ?? 0,
2327
+ }));
2328
+ }
2329
+ function readSevereVerificationRisks(database, planId) {
2330
+ return queryRows(database, `
2331
+ SELECT
2332
+ verification_risk_signals.source_path,
2333
+ verification_risk_signals.ordinal,
2334
+ verification_risk_signals.code,
2335
+ verification_risk_signals.severity,
2336
+ verification_risk_signals.detail_hash
2337
+ FROM verification_risk_signals
2338
+ JOIN verification_evidence_summaries
2339
+ ON verification_evidence_summaries.source_path = verification_risk_signals.source_path
2340
+ WHERE verification_evidence_summaries.verification_plan_id = ?
2341
+ AND verification_risk_signals.severity IN ('high', 'critical')
2342
+ ORDER BY verification_risk_signals.source_path, verification_risk_signals.ordinal
2343
+ `, [planId]).map((row) => ({
2344
+ sourcePath: toSearchString(row.source_path),
2345
+ ordinal: toNullableNumber(row.ordinal) ?? 0,
2346
+ code: toSearchString(row.code),
2347
+ severity: toSearchString(row.severity),
2348
+ detailHash: toSearchString(row.detail_hash),
2349
+ }));
2350
+ }
2351
+ function readNonPassingReceipts(database, planId) {
2352
+ return queryRows(database, `
2353
+ SELECT
2354
+ receipt_hash,
2355
+ plan_id,
2356
+ intent,
2357
+ status,
2358
+ command_fingerprint,
2359
+ contract_fingerprint,
2360
+ current_state_hash,
2361
+ write_drift_status
2362
+ FROM command_receipt_summaries
2363
+ WHERE plan_id = ?
2364
+ AND (
2365
+ status != 'passed'
2366
+ OR write_drift_status IS NULL
2367
+ OR write_drift_status NOT IN ('clean', 'none')
2368
+ )
2369
+ ORDER BY intent, receipt_hash
2370
+ `, [planId]).map((row) => ({
2371
+ receiptHash: toSearchString(row.receipt_hash),
2372
+ planId: toSearchString(row.plan_id),
2373
+ intent: toSearchString(row.intent) || null,
2374
+ status: toSearchString(row.status),
2375
+ commandFingerprint: toSearchString(row.command_fingerprint) || null,
2376
+ contractFingerprint: toSearchString(row.contract_fingerprint) || null,
2377
+ currentStateHash: toSearchString(row.current_state_hash) || null,
2378
+ writeDriftStatus: toSearchString(row.write_drift_status) || null,
2379
+ }));
2380
+ }
2381
+ function readRepeatedFailureFingerprints(database, planId) {
2382
+ return queryRows(database, `
2383
+ SELECT
2384
+ source_path,
2385
+ fingerprint,
2386
+ verification_plan_id,
2387
+ status,
2388
+ failed_intents,
2389
+ primary_reason,
2390
+ failed_intents_hash,
2391
+ risk_codes_hash,
2392
+ affected_surfaces_hash,
2393
+ seen_count,
2394
+ requires_new_evidence
2395
+ FROM verification_failure_fingerprints
2396
+ WHERE verification_plan_id = ?
2397
+ AND requires_new_evidence = 1
2398
+ ORDER BY fingerprint
2399
+ `, [planId]).map((row) => ({
2400
+ sourcePath: toSearchString(row.source_path),
2401
+ fingerprint: toSearchString(row.fingerprint),
2402
+ verificationPlanId: toSearchString(row.verification_plan_id) || null,
2403
+ status: toSearchString(row.status),
2404
+ failedIntents: splitIndexedList(row.failed_intents),
2405
+ primaryReason: toSearchString(row.primary_reason) || null,
2406
+ failedIntentsHash: toSearchString(row.failed_intents_hash) || null,
2407
+ riskCodesHash: toSearchString(row.risk_codes_hash) || null,
2408
+ affectedSurfacesHash: toSearchString(row.affected_surfaces_hash) || null,
2409
+ seenCount: toNullableNumber(row.seen_count) ?? 0,
2410
+ requiresNewEvidence: Number(row.requires_new_evidence) === 1,
2411
+ }));
2412
+ }
2413
+ function readValidationWeakeningSignals(database, planId) {
2414
+ return queryRows(database, `
2415
+ SELECT signal_id, plan_id, code, severity, path_hash, before_hash, after_hash
2416
+ FROM validation_ratchet_signals
2417
+ WHERE plan_id = ?
2418
+ ORDER BY severity DESC, code, signal_id
2419
+ `, [planId]).map((row) => ({
2420
+ signalId: toSearchString(row.signal_id),
2421
+ planId: toSearchString(row.plan_id) || null,
2422
+ code: toSearchString(row.code),
2423
+ severity: toSearchString(row.severity),
2424
+ pathHash: toSearchString(row.path_hash),
2425
+ beforeHash: toSearchString(row.before_hash) || null,
2426
+ afterHash: toSearchString(row.after_hash) || null,
2427
+ }));
2428
+ }
2429
+ function queryLocalVerificationReadModel(databasePath, database, planId) {
2430
+ const selectedPlanId = planId ?? readLatestVerificationPlanId(database);
2431
+ const base = createVerificationReadModelQueryStatus(databasePath, 'fresh', selectedPlanId);
2432
+ if (!selectedPlanId) {
2433
+ return base;
2434
+ }
2435
+ return {
2436
+ ...base,
2437
+ uncoveredCriteria: readUncoveredCriteria(database, selectedPlanId),
2438
+ severeRisks: readSevereVerificationRisks(database, selectedPlanId),
2439
+ nonPassingReceipts: readNonPassingReceipts(database, selectedPlanId),
2440
+ repeatedFailureFingerprints: readRepeatedFailureFingerprints(database, selectedPlanId),
2441
+ validationWeakeningSignals: readValidationWeakeningSignals(database, selectedPlanId),
2442
+ };
2443
+ }
2444
+ export async function readLocalVerificationReadModelQueries(projectRoot, planId) {
2445
+ return readLocalVerificationReadModelQueriesForPlan(projectRoot, planId);
2446
+ }
2447
+ export async function readLocalVerificationReadModelQueriesForPlan(projectRoot, planId) {
2448
+ const databasePath = getLocalIndexDatabasePath(projectRoot);
2449
+ if (!existsSync(databasePath)) {
2450
+ return createVerificationReadModelQueryStatus(databasePath, 'missing', planId);
2451
+ }
2452
+ const SQL = await loadSqlJs();
2453
+ const database = new SQL.Database(readFileSync(databasePath));
2454
+ try {
2455
+ const requiredTables = [
2456
+ 'acceptance_criteria',
2457
+ 'criterion_coverage',
2458
+ 'verification_evidence_summaries',
2459
+ 'verification_plans',
2460
+ 'verification_risk_signals',
2461
+ 'command_receipt_summaries',
2462
+ 'verification_failure_fingerprints',
2463
+ 'validation_ratchet_signals',
2464
+ ];
2465
+ if (requiredTables.some((tableName) => !hasTable(database, tableName))) {
2466
+ return createVerificationReadModelQueryStatus(databasePath, 'unreadable', planId);
2467
+ }
2468
+ const stalePaths = getStalePaths(projectRoot, database);
2469
+ if (stalePaths.length > 0) {
2470
+ return createVerificationReadModelQueryStatus(databasePath, 'stale', planId, stalePaths);
2471
+ }
2472
+ return queryLocalVerificationReadModel(databasePath, database, planId);
2473
+ }
2474
+ catch {
2475
+ return createVerificationReadModelQueryStatus(databasePath, 'unreadable', planId);
2476
+ }
2477
+ finally {
2478
+ database.close();
2479
+ }
2480
+ }
2481
+ export async function readLatestLocalVerificationReadModelQueries(projectRoot) {
2482
+ return readLocalVerificationReadModelQueriesForPlan(projectRoot, null);
2483
+ }
2484
+ function pathSurfaceReadModelWithMatch(base, match) {
2485
+ return {
2486
+ ...base,
2487
+ match,
2488
+ };
2489
+ }
2490
+ function readPathSurfaceReasonMap(database) {
2491
+ const byRule = new Map();
2492
+ for (const row of queryRows(database, 'SELECT rule_id, reason_kind, reason FROM path_surface_reasons ORDER BY rule_id, reason_kind, ordinal')) {
2493
+ const ruleId = toSearchString(row.rule_id);
2494
+ const reasonKind = toSearchString(row.reason_kind);
2495
+ const reason = toSearchString(row.reason);
2496
+ let reasonsByKind = byRule.get(ruleId);
2497
+ if (!reasonsByKind) {
2498
+ reasonsByKind = new Map();
2499
+ byRule.set(ruleId, reasonsByKind);
2500
+ }
2501
+ const reasons = reasonsByKind.get(reasonKind) ?? [];
2502
+ reasons.push(reason);
2503
+ reasonsByKind.set(reasonKind, reasons);
2504
+ }
2505
+ return byRule;
2506
+ }
2507
+ function readPathSurfaceRuleMatches(database) {
2508
+ const reasons = readPathSurfaceReasonMap(database);
2509
+ return queryRows(database, 'SELECT rule_id, pattern_kind, pattern, pattern_flags, surface_kind, category, is_public_surface, update_policy FROM path_surfaces ORDER BY rowid').map((row) => {
2510
+ const ruleId = toSearchString(row.rule_id);
2511
+ const reasonsByKind = reasons.get(ruleId);
2512
+ const reasonList = (kind) => reasonsByKind?.get(kind) ?? [];
2513
+ return {
2514
+ ruleId,
2515
+ patternKind: toSearchString(row.pattern_kind),
2516
+ pattern: toSearchString(row.pattern),
2517
+ patternFlags: toSearchString(row.pattern_flags),
2518
+ changeKinds: reasonList('change_kind'),
2519
+ surface: {
2520
+ kind: toSearchString(row.surface_kind),
2521
+ category: toSearchString(row.category),
2522
+ isPublicSurface: Number(row.is_public_surface) === 1,
2523
+ validationReasons: reasonList('validation_reason'),
2524
+ affectedContracts: reasonList('affected_contract'),
2525
+ updatePolicy: toSearchString(row.update_policy),
2526
+ driftChecks: reasonList('drift_check'),
2527
+ },
2528
+ };
2529
+ });
2530
+ }
2531
+ function matchPathSurfaceRule(relativePath, rules) {
2532
+ if (!relativePath) {
2533
+ return null;
2534
+ }
2535
+ for (const rule of rules) {
2536
+ try {
2537
+ if (new RegExp(rule.pattern, rule.patternFlags).test(relativePath)) {
2538
+ return rule;
2539
+ }
2540
+ }
2541
+ catch {
2542
+ continue;
2543
+ }
2544
+ }
2545
+ return null;
2546
+ }
2547
+ export async function readLocalPathSurfaces(projectRoot, relativePaths) {
2548
+ const databasePath = getLocalIndexDatabasePath(projectRoot);
2549
+ const normalizedPaths = [...new Set(relativePaths.map((relativePath) => toPosixPath(relativePath)).filter(Boolean))];
2550
+ const statusMap = (status, stalePaths = []) => new Map(normalizedPaths.map((relativePath) => [
2551
+ relativePath,
2552
+ createPathSurfaceReadModelStatus(databasePath, status, relativePath, stalePaths),
2553
+ ]));
2554
+ if (!existsSync(databasePath)) {
2555
+ return statusMap('missing');
2556
+ }
2557
+ const SQL = await loadSqlJs();
2558
+ const database = new SQL.Database(readFileSync(databasePath));
2559
+ try {
2560
+ const stalePaths = getStalePaths(projectRoot, database);
2561
+ if (stalePaths.length > 0) {
2562
+ return statusMap('stale', stalePaths);
2563
+ }
2564
+ const rules = readPathSurfaceRuleMatches(database);
2565
+ return new Map(normalizedPaths.map((relativePath) => {
2566
+ const base = createPathSurfaceReadModelStatus(databasePath, 'fresh', relativePath);
2567
+ return [relativePath, pathSurfaceReadModelWithMatch(base, matchPathSurfaceRule(relativePath, rules))];
2568
+ }));
2569
+ }
2570
+ catch {
2571
+ return statusMap('unreadable');
2572
+ }
2573
+ finally {
2574
+ database.close();
2575
+ }
2576
+ }
2577
+ export async function readLocalPathSurface(projectRoot, relativePath) {
2578
+ const databasePath = getLocalIndexDatabasePath(projectRoot);
2579
+ const inputPath = relativePath ? toPosixPath(relativePath) : null;
2580
+ if (!inputPath) {
2581
+ if (!existsSync(databasePath)) {
2582
+ return createPathSurfaceReadModelStatus(databasePath, 'missing', null);
2583
+ }
2584
+ const SQL = await loadSqlJs();
2585
+ const database = new SQL.Database(readFileSync(databasePath));
2586
+ try {
2587
+ const stalePaths = getStalePaths(projectRoot, database);
2588
+ return createPathSurfaceReadModelStatus(databasePath, stalePaths.length > 0 ? 'stale' : 'fresh', null, stalePaths);
2589
+ }
2590
+ catch {
2591
+ return createPathSurfaceReadModelStatus(databasePath, 'unreadable', null);
2592
+ }
2593
+ finally {
2594
+ database.close();
2595
+ }
2596
+ }
2597
+ const surfaces = await readLocalPathSurfaces(projectRoot, [inputPath]);
2598
+ return surfaces.get(inputPath) ?? createPathSurfaceReadModelStatus(databasePath, 'unreadable', inputPath);
2599
+ }
2600
+ export async function readLocalSourceAnchorVerdictRisks(projectRoot, relativePaths) {
2601
+ const databasePath = getLocalIndexDatabasePath(projectRoot);
2602
+ const normalizedPaths = new Set(relativePaths.map((relativePath) => toPosixPath(relativePath)).filter(Boolean));
2603
+ if (!existsSync(databasePath) || normalizedPaths.size === 0) {
2604
+ return [];
2605
+ }
2606
+ const SQL = await loadSqlJs();
2607
+ const database = new SQL.Database(readFileSync(databasePath));
2608
+ try {
2609
+ const stalePaths = getStalePaths(projectRoot, database);
2610
+ if (stalePaths.length > 0 || !hasTable(database, 'source_anchors') || !hasTable(database, 'source_anchor_status')) {
2611
+ return [];
2612
+ }
2613
+ const rows = queryRows(database, `
2614
+ SELECT
2615
+ source_anchors.id,
2616
+ source_anchors.path,
2617
+ source_anchors.line_start,
2618
+ source_anchors.invariant,
2619
+ source_anchors.risk,
2620
+ source_anchor_status.status
2621
+ FROM source_anchors
2622
+ JOIN source_anchor_status ON source_anchor_status.anchor_id = source_anchors.id
2623
+ WHERE source_anchor_status.status IN ('changed', 'review', 'stale')
2624
+ ORDER BY source_anchors.id
2625
+ `);
2626
+ return rows
2627
+ .map((row) => {
2628
+ const relativePath = toSearchString(row.path);
2629
+ const riskTags = splitIndexedList(row.risk);
2630
+ const status = toSearchString(row.status);
2631
+ if (!normalizedPaths.has(relativePath) ||
2632
+ !hasHighRiskSourceAnchorRiskTags(riskTags) ||
2633
+ (status !== 'changed' && status !== 'review' && status !== 'stale')) {
2634
+ return null;
2635
+ }
2636
+ return {
2637
+ source: 'local_index',
2638
+ authority: 'evidence_only',
2639
+ anchorId: toSearchString(row.id),
2640
+ path: relativePath,
2641
+ lineStart: Number(row.line_start),
2642
+ status,
2643
+ riskTags,
2644
+ invariant: toSearchString(row.invariant) || null,
2645
+ };
2646
+ })
2647
+ .filter((risk) => risk !== null);
2648
+ }
2649
+ catch {
2650
+ return [];
2651
+ }
2652
+ finally {
2653
+ database.close();
2654
+ }
2655
+ }
2656
+ function getSectionHeadings(database, documentPath) {
2657
+ return queryRows(database, 'SELECT heading FROM sections WHERE document_path = ? ORDER BY ordinal', [documentPath]).map((row) => toSearchString(row.heading));
2658
+ }
2659
+ function getDocumentTerms(database, documentPath) {
2660
+ return queryRows(database, 'SELECT term FROM document_terms WHERE document_path = ? ORDER BY term', [documentPath]).map((row) => toSearchString(row.term));
2661
+ }
2662
+ function getCommandEffects(database, intent) {
2663
+ return queryRows(database, 'SELECT intent, source, access, mode, path, lock, concurrency FROM command_effects WHERE intent = ? ORDER BY lock, path, mode', [intent]).map((row) => ({
2664
+ intent: toSearchString(row.intent),
2665
+ source: toSearchString(row.source),
2666
+ access: toSearchString(row.access),
2667
+ mode: toSearchString(row.mode),
2668
+ path: row.path === null || row.path === undefined ? null : toSearchString(row.path),
2669
+ lock: toSearchString(row.lock),
2670
+ concurrency: toSearchString(row.concurrency),
2671
+ }));
2672
+ }
2673
+ const EMPTY_INDEXED_SEARCH_MATCHES = {
2674
+ active: false,
2675
+ documents: new Set(),
2676
+ skills: new Set(),
2677
+ skillRoutes: new Set(),
2678
+ commandIntents: new Set(),
2679
+ sourceAnchors: new Set(),
2680
+ };
2681
+ function buildFtsQuery(query) {
2682
+ const tokens = extractSearchTokens(query);
2683
+ if (tokens.length === 0) {
2684
+ return null;
2685
+ }
2686
+ return [...new Set(tokens)].map((token) => `"${token.replaceAll('"', '""')}"`).join(' AND ');
2687
+ }
2688
+ function queryFtsSet(database, sql, ftsQuery, column) {
2689
+ return new Set(queryRows(database, sql, [ftsQuery]).map((row) => toSearchString(row[column])));
2690
+ }
2691
+ function mergeSearchSets(left, right) {
2692
+ return new Set([...left, ...right]);
2693
+ }
2694
+ function mergeIndexedSearchMatches(left, right) {
2695
+ return {
2696
+ active: left.active || right.active,
2697
+ documents: mergeSearchSets(left.documents, right.documents),
2698
+ skills: mergeSearchSets(left.skills, right.skills),
2699
+ skillRoutes: mergeSearchSets(left.skillRoutes, right.skillRoutes),
2700
+ commandIntents: mergeSearchSets(left.commandIntents, right.commandIntents),
2701
+ sourceAnchors: mergeSearchSets(left.sourceAnchors, right.sourceAnchors),
2702
+ };
2703
+ }
2704
+ function queryNgramSet(database, targetKind, grams) {
2705
+ const placeholders = grams.map(() => '?').join(', ');
2706
+ if (!placeholders) {
2707
+ return new Set();
2708
+ }
2709
+ return new Set(queryRows(database, `SELECT target_key
2710
+ FROM search_ngrams
2711
+ WHERE target_kind = ? AND gram IN (${placeholders})
2712
+ GROUP BY target_key
2713
+ HAVING COUNT(DISTINCT gram) = ?`, [targetKind, ...grams, grams.length]).map((row) => toSearchString(row.target_key)));
2714
+ }
2715
+ function getNgramSearchMatches(database, query) {
2716
+ if (!hasTable(database, 'search_ngrams')) {
2717
+ return EMPTY_INDEXED_SEARCH_MATCHES;
2718
+ }
2719
+ const grams = buildSearchNgrams([query]);
2720
+ if (grams.length === 0) {
2721
+ return EMPTY_INDEXED_SEARCH_MATCHES;
2722
+ }
2723
+ return {
2724
+ active: true,
2725
+ documents: queryNgramSet(database, 'document', grams),
2726
+ skills: queryNgramSet(database, 'skill', grams),
2727
+ skillRoutes: queryNgramSet(database, 'skill_route', grams),
2728
+ commandIntents: queryNgramSet(database, 'command_intent', grams),
2729
+ sourceAnchors: queryNgramSet(database, 'source_anchor', grams),
2730
+ };
2731
+ }
2732
+ function getIndexedSearchMatches(database, query) {
2733
+ const capabilities = readStoredSearchCapabilities(database);
2734
+ const ftsQuery = capabilities.backend === SEARCH_BACKEND_FTS5 ? buildFtsQuery(query) : null;
2735
+ const ngramMatches = getNgramSearchMatches(database, query);
2736
+ if (!ftsQuery) {
2737
+ return ngramMatches;
2738
+ }
2739
+ try {
2740
+ const ftsMatches = {
2741
+ active: true,
2742
+ documents: queryFtsSet(database, 'SELECT path FROM search_documents_fts WHERE search_documents_fts MATCH ?', ftsQuery, 'path'),
2743
+ skills: queryFtsSet(database, 'SELECT name FROM search_skills_fts WHERE search_skills_fts MATCH ?', ftsQuery, 'name'),
2744
+ skillRoutes: queryFtsSet(database, 'SELECT route_key FROM search_skill_routes_fts WHERE search_skill_routes_fts MATCH ?', ftsQuery, 'route_key'),
2745
+ commandIntents: queryFtsSet(database, 'SELECT name FROM search_command_intents_fts WHERE search_command_intents_fts MATCH ?', ftsQuery, 'name'),
2746
+ sourceAnchors: queryFtsSet(database, 'SELECT id FROM search_source_anchors_fts WHERE search_source_anchors_fts MATCH ?', ftsQuery, 'id'),
2747
+ };
2748
+ return mergeIndexedSearchMatches(ftsMatches, ngramMatches);
2749
+ }
2750
+ catch {
2751
+ return ngramMatches;
2752
+ }
2753
+ }
2754
+ function matchesIndexedOrTableScan(fields, query, indexedMatches, matchSet, key) {
2755
+ return (indexedMatches.active && matchSet.has(key)) || isMatched(fields, query);
2756
+ }
2757
+ function scoreIndexedOrTableScan(primaryFields, secondaryFields, query, indexedMatches, matchSet, key) {
2758
+ const tableScore = scoreMatch(primaryFields, secondaryFields, query);
2759
+ return indexedMatches.active && matchSet.has(key) ? Math.max(tableScore, 20) : tableScore;
2760
+ }
2761
+ /**
2762
+ * mf:anchor cli.search.local-index
2763
+ * purpose: Search the local index while preserving workflow authority above source navigation hints.
2764
+ * search: mf search, scope workflow source all, authority rank, navigation only
2765
+ * invariant: Source anchor results remain navigation-only and cannot outrank command or workflow authority.
2766
+ * risk: cache, config
2767
+ */
2768
+ export async function searchLocalIndex(projectRoot, query, options = {}) {
2769
+ const normalizedQuery = normalizeSearchText(query);
2770
+ const limit = Math.max(1, Math.min(options.limit ?? 10, 50));
2771
+ const scope = options.scope ?? 'workflow';
2772
+ const databasePath = getLocalIndexDatabasePath(projectRoot);
2773
+ if (!existsSync(databasePath)) {
2774
+ throw new Error('Local mustflow index not found. Run `mf index` before searching.');
2775
+ }
2776
+ if (normalizedQuery.length === 0) {
2777
+ throw new Error('Search query must not be empty.');
2778
+ }
2779
+ const SQL = await loadSqlJs();
2780
+ const database = new SQL.Database(readFileSync(databasePath));
2781
+ const cacheLayers = readCacheLayerSets(projectRoot);
2782
+ let capabilities = searchCapabilities(false);
2783
+ const results = [];
2784
+ try {
2785
+ const stalePaths = getStalePaths(projectRoot, database);
2786
+ capabilities = readStoredSearchCapabilities(database);
2787
+ const indexedMatches = getIndexedSearchMatches(database, normalizedQuery);
2788
+ if (stalePaths.length > 0) {
2789
+ throw new Error(`Local mustflow index is stale: ${stalePaths.join(', ')}. Run \`mf index\` before searching. Refresh command: mf index`);
2790
+ }
2791
+ if (scope === 'workflow' || scope === 'all') {
2792
+ for (const row of queryRows(database, 'SELECT path, type, title, content_snippet FROM documents')) {
2793
+ const pathValue = toSearchString(row.path);
2794
+ const typeValue = toSearchString(row.type);
2795
+ const title = toSearchString(row.title);
2796
+ const contentSnippet = toSearchString(row.content_snippet);
2797
+ const sectionHeadings = getSectionHeadings(database, pathValue);
2798
+ const documentTerms = getDocumentTerms(database, pathValue);
2799
+ const primaryFields = [pathValue, title];
2800
+ const secondaryFields = [typeValue, contentSnippet, ...sectionHeadings, ...documentTerms];
2801
+ const fields = [...primaryFields, ...secondaryFields];
2802
+ if (!matchesIndexedOrTableScan(fields, normalizedQuery, indexedMatches, indexedMatches.documents, pathValue)) {
2803
+ continue;
2804
+ }
2805
+ results.push(withCacheHint({
2806
+ kind: 'document',
2807
+ path: pathValue,
2808
+ title,
2809
+ document_type: typeValue,
2810
+ ...workflowAuthorityForDocument(typeValue),
2811
+ match: getMatchSnippet(fields, normalizedQuery),
2812
+ score: scoreIndexedOrTableScan(primaryFields, secondaryFields, normalizedQuery, indexedMatches, indexedMatches.documents, pathValue),
2813
+ }, cacheLayers));
2814
+ }
2815
+ for (const row of queryRows(database, 'SELECT name, path, title FROM skills')) {
2816
+ const name = toSearchString(row.name);
2817
+ const pathValue = toSearchString(row.path);
2818
+ const title = toSearchString(row.title);
2819
+ const fields = [name, pathValue, title];
2820
+ if (!matchesIndexedOrTableScan(fields, normalizedQuery, indexedMatches, indexedMatches.skills, name)) {
2821
+ continue;
2822
+ }
2823
+ results.push(withCacheHint({
2824
+ kind: 'skill',
2825
+ name,
2826
+ path: pathValue,
2827
+ title,
2828
+ ...skillAuthority(),
2829
+ match: getMatchSnippet(fields, normalizedQuery),
2830
+ score: scoreIndexedOrTableScan([name, pathValue, title], [], normalizedQuery, indexedMatches, indexedMatches.skills, name),
2831
+ }, cacheLayers));
2832
+ }
2833
+ for (const row of queryRows(database, 'SELECT skill_name, skill_path, trigger, required_input, edit_scope, risk, verification_intents, expected_output FROM skill_routes')) {
2834
+ const name = toSearchString(row.skill_name);
2835
+ const pathValue = toSearchString(row.skill_path);
2836
+ const trigger = toSearchString(row.trigger);
2837
+ const requiredInput = toSearchString(row.required_input);
2838
+ const editScope = toSearchString(row.edit_scope);
2839
+ const risk = toSearchString(row.risk);
2840
+ const verificationIntents = splitVerificationIntents(toSearchString(row.verification_intents));
2841
+ const expectedOutput = toSearchString(row.expected_output);
2842
+ const primaryFields = [name, trigger];
2843
+ const secondaryFields = [pathValue, requiredInput, editScope, risk, expectedOutput];
2844
+ const fields = [...primaryFields, ...secondaryFields];
2845
+ const routeKey = skillRouteKey({ skillName: name, trigger });
2846
+ if (!matchesIndexedOrTableScan(fields, normalizedQuery, indexedMatches, indexedMatches.skillRoutes, routeKey)) {
2847
+ continue;
2848
+ }
2849
+ results.push(withCacheHint({
2850
+ kind: 'skill_route',
2851
+ name,
2852
+ path: pathValue,
2853
+ title: name,
2854
+ route_trigger: trigger,
2855
+ route_risk: risk,
2856
+ verification_intents: verificationIntents,
2857
+ ...skillAuthority(),
2858
+ match: getMatchSnippet(fields, normalizedQuery),
2859
+ score: scoreIndexedOrTableScan(primaryFields, secondaryFields, normalizedQuery, indexedMatches, indexedMatches.skillRoutes, routeKey),
2860
+ }, cacheLayers));
2861
+ }
2862
+ for (const row of queryRows(database, 'SELECT name, status, lifecycle, run_policy, description FROM command_intents')) {
2863
+ const name = toSearchString(row.name);
2864
+ const status = toSearchString(row.status);
2865
+ const lifecycle = toSearchString(row.lifecycle);
2866
+ const runPolicy = toSearchString(row.run_policy);
2867
+ const description = toSearchString(row.description);
2868
+ const effects = getCommandEffects(database, name);
2869
+ const effectLocks = [...new Set(effects.map((effect) => effect.lock))].sort((left, right) => left.localeCompare(right));
2870
+ const effectPaths = [
2871
+ ...new Set(effects.map((effect) => effect.path).filter((effectPath) => effectPath !== null)),
2872
+ ].sort((left, right) => left.localeCompare(right));
2873
+ const effectModes = [...new Set(effects.map((effect) => effect.mode))].sort((left, right) => left.localeCompare(right));
2874
+ const primaryFields = [name];
2875
+ const secondaryFields = [status, lifecycle, runPolicy, description, ...effectLocks, ...effectPaths, ...effectModes];
2876
+ const fields = [...primaryFields, ...secondaryFields];
2877
+ if (!matchesIndexedOrTableScan(fields, normalizedQuery, indexedMatches, indexedMatches.commandIntents, name)) {
2878
+ continue;
2879
+ }
2880
+ results.push(withCacheHint({
2881
+ kind: 'command_intent',
2882
+ name,
2883
+ title: description || name,
2884
+ effect_locks: effectLocks,
2885
+ effect_paths: effectPaths,
2886
+ effect_modes: effectModes,
2887
+ ...commandIntentAuthority(),
2888
+ match: getMatchSnippet(fields, normalizedQuery),
2889
+ score: scoreIndexedOrTableScan(primaryFields, secondaryFields, normalizedQuery, indexedMatches, indexedMatches.commandIntents, name),
2890
+ }, cacheLayers));
2891
+ }
2892
+ }
2893
+ if (scope === 'source' || scope === 'all') {
2894
+ for (const row of queryRows(database, 'SELECT source_anchors.id, path, line_start, purpose, search_terms, invariant, risk, source_anchors.navigation_only, source_anchors.can_instruct_agent, status, confidence FROM source_anchors LEFT JOIN source_anchor_status ON source_anchor_status.anchor_id = source_anchors.id')) {
2895
+ const id = toSearchString(row.id);
2896
+ const pathValue = toSearchString(row.path);
2897
+ const purpose = toSearchString(row.purpose);
2898
+ const searchTerms = toSearchString(row.search_terms);
2899
+ const invariant = toSearchString(row.invariant);
2900
+ const risk = toSearchString(row.risk);
2901
+ const primaryFields = [id, pathValue];
2902
+ const secondaryFields = [purpose, searchTerms, invariant, risk];
2903
+ const fields = [...primaryFields, ...secondaryFields];
2904
+ if (!matchesIndexedOrTableScan(fields, normalizedQuery, indexedMatches, indexedMatches.sourceAnchors, id)) {
2905
+ continue;
2906
+ }
2907
+ results.push(withCacheHint({
2908
+ kind: 'source_anchor',
2909
+ anchor_id: id,
2910
+ name: id,
2911
+ path: pathValue,
2912
+ line_start: Number(row.line_start),
2913
+ title: purpose || id,
2914
+ risk,
2915
+ ...sourceAnchorAuthority(),
2916
+ stale_status: toSearchString(row.status),
2917
+ stale_confidence: Number(row.confidence),
2918
+ match: getMatchSnippet(fields, normalizedQuery),
2919
+ score: scoreIndexedOrTableScan(primaryFields, secondaryFields, normalizedQuery, indexedMatches, indexedMatches.sourceAnchors, id),
2920
+ }, cacheLayers));
2921
+ }
2922
+ }
2923
+ }
2924
+ finally {
2925
+ database.close();
2926
+ }
2927
+ const sortedResults = results
2928
+ .sort((left, right) => {
2929
+ if (scope === 'all' && left.authority_rank !== right.authority_rank) {
2930
+ return left.authority_rank - right.authority_rank;
2931
+ }
2932
+ return right.score - left.score || (left.path ?? left.name ?? '').localeCompare(right.path ?? right.name ?? '');
2933
+ })
2934
+ .slice(0, limit);
2935
+ return {
2936
+ schema_version: LOCAL_INDEX_SCHEMA_VERSION,
2937
+ command: 'search',
2938
+ ok: true,
2939
+ mustflow_root: path.resolve(projectRoot),
2940
+ database_path: databasePath,
2941
+ query: normalizedQuery,
2942
+ limit,
2943
+ scope,
2944
+ index_fresh: true,
2945
+ stale_paths: [],
2946
+ search_backend: capabilities.backend,
2947
+ search_fts5_available: capabilities.fts5Available,
2948
+ result_count: sortedResults.length,
2949
+ results: sortedResults,
2950
+ };
2951
+ }