mustflow 2.22.5 → 2.22.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -0
- package/dist/cli/commands/classify.js +2 -0
- package/dist/cli/commands/dashboard.js +9 -69
- package/dist/cli/commands/run/receipt.js +1 -0
- package/dist/cli/commands/run.js +14 -1
- package/dist/cli/commands/verify/evidence-input.js +269 -0
- package/dist/cli/commands/verify/input.js +212 -0
- package/dist/cli/commands/verify.js +23 -482
- package/dist/cli/i18n/en.js +3 -0
- package/dist/cli/i18n/es.js +3 -0
- package/dist/cli/i18n/fr.js +3 -0
- package/dist/cli/i18n/hi.js +3 -0
- package/dist/cli/i18n/ko.js +3 -0
- package/dist/cli/i18n/zh.js +3 -0
- package/dist/cli/lib/dashboard-export.js +2 -0
- package/dist/cli/lib/dashboard-mutations.js +79 -0
- package/dist/cli/lib/local-index/command-effect-index.js +25 -0
- package/dist/cli/lib/local-index/hashing.js +7 -0
- package/dist/cli/lib/local-index/index.js +127 -826
- package/dist/cli/lib/local-index/source-index.js +137 -0
- package/dist/cli/lib/local-index/verification-evidence.js +451 -0
- package/dist/cli/lib/local-index/workflow-documents.js +204 -0
- package/dist/cli/lib/run-root-trust.js +27 -0
- package/dist/core/change-classification-policy.js +47 -0
- package/dist/core/change-classification.js +10 -43
- package/dist/core/contract-lint.js +6 -2
- package/dist/core/correlation-id.js +16 -0
- package/dist/core/run-receipt.js +1 -0
- package/package.json +4 -1
- package/schemas/README.md +4 -0
- package/schemas/change-verification-report.schema.json +4 -0
- package/schemas/classify-report.schema.json +4 -0
- package/schemas/dashboard-export.schema.json +4 -0
- package/schemas/latest-run-pointer.schema.json +4 -0
- package/schemas/run-receipt.schema.json +4 -0
- package/schemas/verify-report.schema.json +4 -0
- package/schemas/verify-run-manifest.schema.json +4 -0
- package/templates/default/i18n.toml +3 -3
- package/templates/default/locales/en/.mustflow/skills/architecture-deepening-review/SKILL.md +25 -2
- package/templates/default/locales/en/.mustflow/skills/security-privacy-review/SKILL.md +9 -1
- package/templates/default/locales/en/.mustflow/skills/test-design-guard/SKILL.md +9 -1
- package/templates/default/manifest.toml +1 -1
|
@@ -1,69 +1,20 @@
|
|
|
1
|
-
import { existsSync, readFileSync
|
|
2
|
-
import { createHash } from 'node:crypto';
|
|
1
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
3
2
|
import path from 'node:path';
|
|
4
|
-
import { isRecord,
|
|
5
|
-
import {
|
|
6
|
-
import { readMustflowTomlFile } from '../toml.js';
|
|
7
|
-
import { MUSTFLOW_JSON_MAX_BYTES, readMustflowTextFile } from '../mustflow-read.js';
|
|
3
|
+
import { isRecord, readStringArray } from '../command-contract.js';
|
|
4
|
+
import { toPosixPath } from '../filesystem.js';
|
|
8
5
|
import { collectSourceAnchorIndexRecords, hasHighRiskSourceAnchorRiskTags, } from '../../../core/source-anchor-status.js';
|
|
9
|
-
import { listSourceAnchorFiles } from '../../../core/source-anchors.js';
|
|
10
|
-
import { normalizeCommandEffects } from '../../../core/command-effects.js';
|
|
11
6
|
import { listChangeClassificationRuleDescriptors } from '../../../core/change-classification.js';
|
|
12
7
|
import { writeFileInsideWithoutSymlinks } from '../../../core/safe-filesystem.js';
|
|
13
|
-
import { DEFAULT_DATABASE_RELATIVE_PATH, DEFAULT_PROMPT_CACHE_STABLE_READ, DEFAULT_PROMPT_CACHE_TASK_SOURCES, DEFAULT_PROMPT_CACHE_VOLATILE_SOURCES,
|
|
8
|
+
import { DEFAULT_DATABASE_RELATIVE_PATH, DEFAULT_PROMPT_CACHE_STABLE_READ, DEFAULT_PROMPT_CACHE_TASK_SOURCES, DEFAULT_PROMPT_CACHE_VOLATILE_SOURCES, LOCAL_INDEX_CONTENT_MODE, LOCAL_INDEX_EXCLUDED_RAW_DATA_KINDS, LOCAL_INDEX_PARSER_VERSION, LOCAL_INDEX_SCHEMA_VERSION, LOCAL_INDEX_STORE_FULL_CONTENT, MAX_SEARCH_MATCH_SNIPPET_CHARS, MAX_SNIPPET_BYTES_PER_DOCUMENT, SEARCH_BACKEND_FTS5, SEARCH_BACKEND_TABLE_SCAN, SEARCH_MATCH_CONTEXT_AFTER_CHARS, SEARCH_MATCH_CONTEXT_BEFORE_CHARS, SEARCH_MATCH_TRUNCATION_MARKER, SEARCH_NGRAM_MAX_GRAMS_PER_TARGET, SEARCH_NGRAM_MAX_LENGTH, SEARCH_NGRAM_MAX_TOKEN_CHARS, SEARCH_NGRAM_MIN_LENGTH, SOURCE_INDEX_MAX_FILE_BYTES, TEST_DISABLE_FTS5_ENV, } from './constants.js';
|
|
9
|
+
import { collectCommandIntents } from './command-effect-index.js';
|
|
10
|
+
import { sha256Text } from './hashing.js';
|
|
14
11
|
import { loadSqlJs } from './sql.js';
|
|
12
|
+
import { collectFastPreflightIndexedFileMetadataRecords, collectIndexedFileRecords, collectSourceAnchorCandidatePaths, getSourceScopeHash, hashIndexedFileMetadataRecords, normalizeIndexedFileSourceScope, readIndexedFileRecord, readLocalIndexSourceConfig, readMustflowToml, } from './source-index.js';
|
|
13
|
+
import { createVerificationEvidenceIndex } from './verification-evidence.js';
|
|
14
|
+
import { collectDocuments, collectDocumentsFromPaths, collectSkillRoutes, collectSkills, getExistingIndexablePaths, readText, skillRouteKey, splitVerificationIntents, } from './workflow-documents.js';
|
|
15
15
|
export function getLocalIndexDatabasePath(projectRoot) {
|
|
16
16
|
return path.join(projectRoot, ...DEFAULT_DATABASE_RELATIVE_PATH.split('/'));
|
|
17
17
|
}
|
|
18
|
-
function getExistingIndexablePaths(projectRoot) {
|
|
19
|
-
const paths = new Set();
|
|
20
|
-
const addIfExists = (relativePath) => {
|
|
21
|
-
if (existsSync(path.join(projectRoot, ...relativePath.split('/')))) {
|
|
22
|
-
paths.add(relativePath);
|
|
23
|
-
}
|
|
24
|
-
};
|
|
25
|
-
addIfExists('AGENTS.md');
|
|
26
|
-
for (const relativePath of listFilesRecursive(path.join(projectRoot, '.mustflow', 'docs'))) {
|
|
27
|
-
if (relativePath.endsWith('.md')) {
|
|
28
|
-
paths.add(toPosixPath(path.join('.mustflow', 'docs', relativePath)));
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
for (const relativePath of listFilesRecursive(path.join(projectRoot, '.mustflow', 'context'))) {
|
|
32
|
-
if (relativePath.endsWith('.md')) {
|
|
33
|
-
paths.add(toPosixPath(path.join('.mustflow', 'context', relativePath)));
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
for (const relativePath of listFilesRecursive(path.join(projectRoot, '.mustflow', 'skills'))) {
|
|
37
|
-
if (relativePath === 'INDEX.md' || relativePath.endsWith('/SKILL.md')) {
|
|
38
|
-
paths.add(toPosixPath(path.join('.mustflow', 'skills', relativePath)));
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
for (const relativePath of listFilesRecursive(path.join(projectRoot, '.mustflow', 'config'))) {
|
|
42
|
-
if (relativePath.endsWith('.toml')) {
|
|
43
|
-
paths.add(toPosixPath(path.join('.mustflow', 'config', relativePath)));
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
return Array.from(paths).sort((left, right) => left.localeCompare(right));
|
|
47
|
-
}
|
|
48
|
-
function readText(projectRoot, relativePath) {
|
|
49
|
-
return readMustflowTextFile(projectRoot, relativePath);
|
|
50
|
-
}
|
|
51
|
-
function readMustflowToml(projectRoot) {
|
|
52
|
-
const mustflowPath = path.join(projectRoot, ...MUSTFLOW_RELATIVE_PATH.split('/'));
|
|
53
|
-
if (!existsSync(mustflowPath)) {
|
|
54
|
-
return undefined;
|
|
55
|
-
}
|
|
56
|
-
const parsed = readMustflowTomlFile(projectRoot, MUSTFLOW_RELATIVE_PATH);
|
|
57
|
-
return isRecord(parsed) ? parsed : undefined;
|
|
58
|
-
}
|
|
59
|
-
function readIndexToml(projectRoot) {
|
|
60
|
-
const indexConfigPath = path.join(projectRoot, ...INDEX_CONFIG_RELATIVE_PATH.split('/'));
|
|
61
|
-
if (!existsSync(indexConfigPath)) {
|
|
62
|
-
return undefined;
|
|
63
|
-
}
|
|
64
|
-
const parsed = readMustflowTomlFile(projectRoot, INDEX_CONFIG_RELATIVE_PATH);
|
|
65
|
-
return isRecord(parsed) ? parsed : undefined;
|
|
66
|
-
}
|
|
67
18
|
function readNestedTable(table, key) {
|
|
68
19
|
if (!table || !isRecord(table[key])) {
|
|
69
20
|
return undefined;
|
|
@@ -73,297 +24,6 @@ function readNestedTable(table, key) {
|
|
|
73
24
|
function readOptionalStringArray(table, key) {
|
|
74
25
|
return table ? readStringArray(table, key) ?? null : null;
|
|
75
26
|
}
|
|
76
|
-
function readBoolean(table, key) {
|
|
77
|
-
const value = table?.[key];
|
|
78
|
-
return typeof value === 'boolean' ? value : undefined;
|
|
79
|
-
}
|
|
80
|
-
function readPositiveInteger(table, key) {
|
|
81
|
-
const value = table?.[key];
|
|
82
|
-
if (typeof value !== 'number' || !Number.isInteger(value) || value <= 0) {
|
|
83
|
-
return null;
|
|
84
|
-
}
|
|
85
|
-
return value;
|
|
86
|
-
}
|
|
87
|
-
function readLocalIndexSourceConfig(projectRoot) {
|
|
88
|
-
const sourceIndexTable = readNestedTable(readIndexToml(projectRoot), 'source_index');
|
|
89
|
-
const configuredMaxFileBytes = readPositiveInteger(sourceIndexTable, 'max_file_bytes');
|
|
90
|
-
return {
|
|
91
|
-
enabledByDefault: readBoolean(sourceIndexTable, 'enabled_by_default') === true,
|
|
92
|
-
include: readOptionalStringArray(sourceIndexTable, 'include') ?? [],
|
|
93
|
-
exclude: readOptionalStringArray(sourceIndexTable, 'exclude') ?? [],
|
|
94
|
-
maxFileBytes: Math.min(configuredMaxFileBytes ?? SOURCE_INDEX_MAX_FILE_BYTES, SOURCE_INDEX_MAX_FILE_BYTES),
|
|
95
|
-
allowedExtensions: readOptionalStringArray(sourceIndexTable, 'allowed_extensions') ?? [],
|
|
96
|
-
};
|
|
97
|
-
}
|
|
98
|
-
function sha256Text(content) {
|
|
99
|
-
return `sha256:${createHash('sha256').update(content).digest('hex')}`;
|
|
100
|
-
}
|
|
101
|
-
function sha256Bytes(content) {
|
|
102
|
-
return `sha256:${createHash('sha256').update(content).digest('hex')}`;
|
|
103
|
-
}
|
|
104
|
-
function getSourceScopeHash(includeSource, sourceConfig) {
|
|
105
|
-
return sha256Text(JSON.stringify({
|
|
106
|
-
includeSource,
|
|
107
|
-
sourceConfig,
|
|
108
|
-
}));
|
|
109
|
-
}
|
|
110
|
-
function getDocumentType(relativePath) {
|
|
111
|
-
if (relativePath === 'AGENTS.md') {
|
|
112
|
-
return 'agent_rules';
|
|
113
|
-
}
|
|
114
|
-
if (relativePath.startsWith('.mustflow/config/')) {
|
|
115
|
-
return 'config';
|
|
116
|
-
}
|
|
117
|
-
if (relativePath === '.mustflow/skills/INDEX.md') {
|
|
118
|
-
return 'skill_index';
|
|
119
|
-
}
|
|
120
|
-
if (relativePath === '.mustflow/context/INDEX.md') {
|
|
121
|
-
return 'context_index';
|
|
122
|
-
}
|
|
123
|
-
if (relativePath.startsWith('.mustflow/context/')) {
|
|
124
|
-
return 'context';
|
|
125
|
-
}
|
|
126
|
-
if (relativePath.endsWith('/SKILL.md')) {
|
|
127
|
-
return 'skill';
|
|
128
|
-
}
|
|
129
|
-
if (relativePath.startsWith('.mustflow/docs/')) {
|
|
130
|
-
return 'workflow_doc';
|
|
131
|
-
}
|
|
132
|
-
return 'document';
|
|
133
|
-
}
|
|
134
|
-
function parseFrontmatter(content) {
|
|
135
|
-
if (!content.startsWith('---')) {
|
|
136
|
-
return {};
|
|
137
|
-
}
|
|
138
|
-
const end = content.indexOf('\n---', 3);
|
|
139
|
-
if (end === -1) {
|
|
140
|
-
return {};
|
|
141
|
-
}
|
|
142
|
-
const result = {};
|
|
143
|
-
const rawFrontmatter = content.slice(3, end);
|
|
144
|
-
for (const line of rawFrontmatter.split(/\r?\n/)) {
|
|
145
|
-
const separatorIndex = line.indexOf(':');
|
|
146
|
-
if (separatorIndex === -1) {
|
|
147
|
-
continue;
|
|
148
|
-
}
|
|
149
|
-
const key = line.slice(0, separatorIndex).trim();
|
|
150
|
-
const value = line.slice(separatorIndex + 1).trim();
|
|
151
|
-
if (key.length > 0 && value.length > 0) {
|
|
152
|
-
result[key] = value;
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
return result;
|
|
156
|
-
}
|
|
157
|
-
function getTitle(relativePath, content) {
|
|
158
|
-
const heading = content.match(/^#\s+(.+)$/mu)?.[1]?.trim();
|
|
159
|
-
return heading && heading.length > 0 ? heading : path.posix.basename(relativePath);
|
|
160
|
-
}
|
|
161
|
-
function getSections(content) {
|
|
162
|
-
return [...content.matchAll(/^##\s+(.+)$/gmu)].map((match) => match[1]?.trim()).filter((value) => Boolean(value));
|
|
163
|
-
}
|
|
164
|
-
function truncateUtf8(value, maxBytes) {
|
|
165
|
-
const buffer = Buffer.from(value, 'utf8');
|
|
166
|
-
if (buffer.byteLength <= maxBytes) {
|
|
167
|
-
return value;
|
|
168
|
-
}
|
|
169
|
-
return buffer.subarray(0, maxBytes).toString('utf8').replace(/\uFFFD$/u, '');
|
|
170
|
-
}
|
|
171
|
-
function collectDocuments(projectRoot) {
|
|
172
|
-
return getExistingIndexablePaths(projectRoot).map((relativePath) => {
|
|
173
|
-
const content = readText(projectRoot, relativePath);
|
|
174
|
-
const frontmatter = parseFrontmatter(content);
|
|
175
|
-
const revision = Number.parseInt(frontmatter.revision ?? '', 10);
|
|
176
|
-
return {
|
|
177
|
-
path: relativePath,
|
|
178
|
-
type: getDocumentType(relativePath),
|
|
179
|
-
title: getTitle(relativePath, content),
|
|
180
|
-
locale: frontmatter.locale ?? null,
|
|
181
|
-
revision: Number.isInteger(revision) ? revision : null,
|
|
182
|
-
contentHash: sha256Text(content),
|
|
183
|
-
contentSnippet: truncateUtf8(content, MAX_SNIPPET_BYTES_PER_DOCUMENT),
|
|
184
|
-
sections: getSections(content),
|
|
185
|
-
};
|
|
186
|
-
});
|
|
187
|
-
}
|
|
188
|
-
function collectSkills(documents) {
|
|
189
|
-
return documents
|
|
190
|
-
.filter((document) => document.type === 'skill')
|
|
191
|
-
.map((document) => ({
|
|
192
|
-
name: document.path.split('/').at(-2) ?? document.title,
|
|
193
|
-
path: document.path,
|
|
194
|
-
title: document.title,
|
|
195
|
-
}))
|
|
196
|
-
.sort((left, right) => left.name.localeCompare(right.name));
|
|
197
|
-
}
|
|
198
|
-
function normalizeMarkdownCell(value) {
|
|
199
|
-
return value
|
|
200
|
-
.replace(/<br\s*\/?>/giu, ' ')
|
|
201
|
-
.replace(/`([^`]+)`/gu, '$1')
|
|
202
|
-
.replace(/\s+/gu, ' ')
|
|
203
|
-
.trim();
|
|
204
|
-
}
|
|
205
|
-
function parseMarkdownTableRow(line) {
|
|
206
|
-
return line
|
|
207
|
-
.trim()
|
|
208
|
-
.replace(/^\|/u, '')
|
|
209
|
-
.replace(/\|$/u, '')
|
|
210
|
-
.split('|')
|
|
211
|
-
.map((cell) => normalizeMarkdownCell(cell));
|
|
212
|
-
}
|
|
213
|
-
function isMarkdownSeparatorRow(cells) {
|
|
214
|
-
return cells.length > 0 && cells.every((cell) => /^:?-{3,}:?$/u.test(cell));
|
|
215
|
-
}
|
|
216
|
-
function skillNameFromPath(skillPath) {
|
|
217
|
-
return skillPath.split('/').at(-2) ?? path.posix.basename(skillPath, '.md');
|
|
218
|
-
}
|
|
219
|
-
function splitVerificationIntents(value) {
|
|
220
|
-
return value
|
|
221
|
-
.split(',')
|
|
222
|
-
.map((item) => item.trim())
|
|
223
|
-
.filter(Boolean)
|
|
224
|
-
.sort((left, right) => left.localeCompare(right));
|
|
225
|
-
}
|
|
226
|
-
function collectSkillRoutes(projectRoot) {
|
|
227
|
-
const indexPath = path.join(projectRoot, '.mustflow', 'skills', 'INDEX.md');
|
|
228
|
-
if (!existsSync(indexPath)) {
|
|
229
|
-
return [];
|
|
230
|
-
}
|
|
231
|
-
const content = readMustflowTextFile(projectRoot, '.mustflow/skills/INDEX.md');
|
|
232
|
-
const routes = [];
|
|
233
|
-
let inRouteTable = false;
|
|
234
|
-
for (const line of content.split(/\r?\n/u)) {
|
|
235
|
-
if (!line.trim().startsWith('|')) {
|
|
236
|
-
if (inRouteTable && line.trim() === '') {
|
|
237
|
-
inRouteTable = false;
|
|
238
|
-
}
|
|
239
|
-
continue;
|
|
240
|
-
}
|
|
241
|
-
const cells = parseMarkdownTableRow(line);
|
|
242
|
-
if (cells.includes('Skill Document') && cells.includes('Trigger')) {
|
|
243
|
-
inRouteTable = true;
|
|
244
|
-
continue;
|
|
245
|
-
}
|
|
246
|
-
if (!inRouteTable || isMarkdownSeparatorRow(cells) || cells.length < 7) {
|
|
247
|
-
continue;
|
|
248
|
-
}
|
|
249
|
-
const [trigger, skillPath, requiredInput, editScope, risk, verificationIntents, expectedOutput] = cells;
|
|
250
|
-
if (!skillPath?.startsWith('.mustflow/skills/') || !skillPath.endsWith('/SKILL.md')) {
|
|
251
|
-
continue;
|
|
252
|
-
}
|
|
253
|
-
routes.push({
|
|
254
|
-
skillName: skillNameFromPath(skillPath),
|
|
255
|
-
skillPath,
|
|
256
|
-
trigger: trigger ?? '',
|
|
257
|
-
requiredInput: requiredInput ?? '',
|
|
258
|
-
editScope: editScope ?? '',
|
|
259
|
-
risk: risk ?? '',
|
|
260
|
-
verificationIntents: splitVerificationIntents(verificationIntents ?? ''),
|
|
261
|
-
expectedOutput: expectedOutput ?? '',
|
|
262
|
-
});
|
|
263
|
-
}
|
|
264
|
-
return routes.sort((left, right) => {
|
|
265
|
-
const skillOrder = left.skillName.localeCompare(right.skillName);
|
|
266
|
-
return skillOrder === 0 ? left.trigger.localeCompare(right.trigger) : skillOrder;
|
|
267
|
-
});
|
|
268
|
-
}
|
|
269
|
-
function collectCommandIntents(projectRoot) {
|
|
270
|
-
if (!existsSync(path.join(projectRoot, '.mustflow', 'config', 'commands.toml'))) {
|
|
271
|
-
return [];
|
|
272
|
-
}
|
|
273
|
-
const contract = readCommandContract(projectRoot);
|
|
274
|
-
const intents = [];
|
|
275
|
-
for (const [name, intent] of Object.entries(contract.intents).sort(([left], [right]) => left.localeCompare(right))) {
|
|
276
|
-
if (!isRecord(intent)) {
|
|
277
|
-
continue;
|
|
278
|
-
}
|
|
279
|
-
intents.push({
|
|
280
|
-
name,
|
|
281
|
-
status: readString(intent, 'status') ?? 'unknown',
|
|
282
|
-
lifecycle: readString(intent, 'lifecycle') ?? null,
|
|
283
|
-
runPolicy: readString(intent, 'run_policy') ?? null,
|
|
284
|
-
description: readString(intent, 'description') ?? null,
|
|
285
|
-
effects: normalizeCommandEffects(projectRoot, contract, name),
|
|
286
|
-
});
|
|
287
|
-
}
|
|
288
|
-
return intents;
|
|
289
|
-
}
|
|
290
|
-
function normalizeIndexedFileSourceScope(value) {
|
|
291
|
-
const sourceScope = toSearchString(value);
|
|
292
|
-
if (sourceScope === 'source_anchor' || sourceScope === 'state') {
|
|
293
|
-
return sourceScope;
|
|
294
|
-
}
|
|
295
|
-
return 'workflow';
|
|
296
|
-
}
|
|
297
|
-
function readIndexedFileRecord(projectRoot, relativePath, sourceScope, contentHash = null) {
|
|
298
|
-
const metadata = readIndexedFileMetadataRecord(projectRoot, relativePath, sourceScope);
|
|
299
|
-
return {
|
|
300
|
-
...metadata,
|
|
301
|
-
contentHash: contentHash ?? sha256Bytes(readFileSync(path.join(projectRoot, ...relativePath.split('/')))),
|
|
302
|
-
};
|
|
303
|
-
}
|
|
304
|
-
function readIndexedFileMetadataRecord(projectRoot, relativePath, sourceScope) {
|
|
305
|
-
const fullPath = path.join(projectRoot, ...relativePath.split('/'));
|
|
306
|
-
const stats = statSync(fullPath);
|
|
307
|
-
return {
|
|
308
|
-
path: relativePath,
|
|
309
|
-
sourceScope,
|
|
310
|
-
sizeBytes: stats.size,
|
|
311
|
-
mtimeMs: Math.round(stats.mtimeMs),
|
|
312
|
-
};
|
|
313
|
-
}
|
|
314
|
-
function hashIndexedFileMetadataRecord(projectRoot, metadata) {
|
|
315
|
-
return {
|
|
316
|
-
...metadata,
|
|
317
|
-
contentHash: sha256Bytes(readFileSync(path.join(projectRoot, ...metadata.path.split('/')))),
|
|
318
|
-
};
|
|
319
|
-
}
|
|
320
|
-
function hashIndexedFileMetadataRecords(projectRoot, metadataRecords) {
|
|
321
|
-
return metadataRecords.map((metadata) => hashIndexedFileMetadataRecord(projectRoot, metadata));
|
|
322
|
-
}
|
|
323
|
-
function collectIndexedFileRecords(projectRoot, documents, sourceAnchors, sourceAnchorCandidatePaths = []) {
|
|
324
|
-
const records = new Map();
|
|
325
|
-
for (const document of documents) {
|
|
326
|
-
records.set(document.path, readIndexedFileRecord(projectRoot, document.path, 'workflow', document.contentHash));
|
|
327
|
-
}
|
|
328
|
-
const sourcePaths = new Set([...sourceAnchorCandidatePaths, ...sourceAnchors.map((anchor) => anchor.path)]);
|
|
329
|
-
for (const anchorPath of [...sourcePaths].sort((left, right) => left.localeCompare(right))) {
|
|
330
|
-
if (!records.has(anchorPath)) {
|
|
331
|
-
records.set(anchorPath, readIndexedFileRecord(projectRoot, anchorPath, 'source_anchor'));
|
|
332
|
-
}
|
|
333
|
-
}
|
|
334
|
-
if (existsSync(path.join(projectRoot, ...LATEST_RUN_STATE_RELATIVE_PATH.split('/')))) {
|
|
335
|
-
records.set(LATEST_RUN_STATE_RELATIVE_PATH, readIndexedFileRecord(projectRoot, LATEST_RUN_STATE_RELATIVE_PATH, 'state'));
|
|
336
|
-
}
|
|
337
|
-
return [...records.values()].sort((left, right) => left.path.localeCompare(right.path));
|
|
338
|
-
}
|
|
339
|
-
function collectSourceAnchorCandidatePaths(projectRoot, sourceConfig) {
|
|
340
|
-
return listSourceAnchorFiles(projectRoot, {
|
|
341
|
-
...sourceConfig,
|
|
342
|
-
excludeGeneratedOrVendor: true,
|
|
343
|
-
});
|
|
344
|
-
}
|
|
345
|
-
function collectFastPreflightIndexedFileMetadataRecords(projectRoot, includeSource, sourceConfig) {
|
|
346
|
-
const records = new Map();
|
|
347
|
-
for (const relativePath of getExistingIndexablePaths(projectRoot)) {
|
|
348
|
-
records.set(relativePath, readIndexedFileMetadataRecord(projectRoot, relativePath, 'workflow'));
|
|
349
|
-
}
|
|
350
|
-
if (includeSource) {
|
|
351
|
-
try {
|
|
352
|
-
for (const sourcePath of collectSourceAnchorCandidatePaths(projectRoot, sourceConfig)) {
|
|
353
|
-
if (!records.has(sourcePath)) {
|
|
354
|
-
records.set(sourcePath, readIndexedFileMetadataRecord(projectRoot, sourcePath, 'source_anchor'));
|
|
355
|
-
}
|
|
356
|
-
}
|
|
357
|
-
}
|
|
358
|
-
catch {
|
|
359
|
-
return null;
|
|
360
|
-
}
|
|
361
|
-
}
|
|
362
|
-
if (existsSync(path.join(projectRoot, ...LATEST_RUN_STATE_RELATIVE_PATH.split('/')))) {
|
|
363
|
-
records.set(LATEST_RUN_STATE_RELATIVE_PATH, readIndexedFileMetadataRecord(projectRoot, LATEST_RUN_STATE_RELATIVE_PATH, 'state'));
|
|
364
|
-
}
|
|
365
|
-
return [...records.values()].sort((left, right) => left.path.localeCompare(right.path));
|
|
366
|
-
}
|
|
367
27
|
function normalizeSearchText(value) {
|
|
368
28
|
return value.trim().replace(/\s+/g, ' ');
|
|
369
29
|
}
|
|
@@ -415,25 +75,7 @@ function queryRows(database, sql, params = []) {
|
|
|
415
75
|
return row;
|
|
416
76
|
});
|
|
417
77
|
}
|
|
418
|
-
const
|
|
419
|
-
'related_test_deleted',
|
|
420
|
-
'skip_or_only_marker_present',
|
|
421
|
-
'todo_or_pending_marker_added',
|
|
422
|
-
'assertion_count_decreased',
|
|
423
|
-
'assertion_matcher_weakened',
|
|
424
|
-
'negative_assertion_removed',
|
|
425
|
-
'exception_assertion_removed',
|
|
426
|
-
'snapshot_mass_updated',
|
|
427
|
-
'golden_output_replaced',
|
|
428
|
-
'verification_intent_disabled',
|
|
429
|
-
'verification_required_after_removed',
|
|
430
|
-
'success_exit_codes_widened',
|
|
431
|
-
'command_allows_no_tests',
|
|
432
|
-
'command_forces_snapshot_update',
|
|
433
|
-
'command_hides_failure',
|
|
434
|
-
'coverage_threshold_lowered',
|
|
435
|
-
'test_selection_narrowed',
|
|
436
|
-
]);
|
|
78
|
+
const DIRECT_SEARCH_MAX_WORKFLOW_FILES = 200;
|
|
437
79
|
function searchCapabilities(fts5Available) {
|
|
438
80
|
return {
|
|
439
81
|
backend: fts5Available ? SEARCH_BACKEND_FTS5 : SEARCH_BACKEND_TABLE_SCAN,
|
|
@@ -453,448 +95,9 @@ function detectLocalSearchCapabilities(database) {
|
|
|
453
95
|
return searchCapabilities(false);
|
|
454
96
|
}
|
|
455
97
|
}
|
|
456
|
-
function isJsonRecord(value) {
|
|
457
|
-
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
458
|
-
}
|
|
459
|
-
function readJsonRecord(projectRoot, relativePath) {
|
|
460
|
-
try {
|
|
461
|
-
const content = readMustflowTextFile(projectRoot, relativePath, { maxBytes: MUSTFLOW_JSON_MAX_BYTES });
|
|
462
|
-
const parsed = JSON.parse(content);
|
|
463
|
-
return isJsonRecord(parsed) ? { content, value: parsed } : null;
|
|
464
|
-
}
|
|
465
|
-
catch {
|
|
466
|
-
return null;
|
|
467
|
-
}
|
|
468
|
-
}
|
|
469
|
-
function stringField(record, key) {
|
|
470
|
-
const value = record?.[key];
|
|
471
|
-
return typeof value === 'string' ? value : null;
|
|
472
|
-
}
|
|
473
|
-
function booleanField(record, key) {
|
|
474
|
-
return record?.[key] === true;
|
|
475
|
-
}
|
|
476
|
-
function numberField(record, key) {
|
|
477
|
-
const value = record?.[key];
|
|
478
|
-
return typeof value === 'number' && Number.isFinite(value) ? value : 0;
|
|
479
|
-
}
|
|
480
|
-
function recordField(record, key) {
|
|
481
|
-
const value = record?.[key];
|
|
482
|
-
return isJsonRecord(value) ? value : null;
|
|
483
|
-
}
|
|
484
|
-
function recordArrayField(record, key) {
|
|
485
|
-
const value = record?.[key];
|
|
486
|
-
return Array.isArray(value) ? value.filter(isJsonRecord) : [];
|
|
487
|
-
}
|
|
488
|
-
function stringArrayField(record, key) {
|
|
489
|
-
const value = record?.[key];
|
|
490
|
-
return Array.isArray(value) ? value.filter((item) => typeof item === 'string') : [];
|
|
491
|
-
}
|
|
492
98
|
function joinedList(values) {
|
|
493
99
|
return [...values].sort((left, right) => left.localeCompare(right)).join(', ');
|
|
494
100
|
}
|
|
495
|
-
function hashJson(value) {
|
|
496
|
-
return sha256Text(JSON.stringify(value));
|
|
497
|
-
}
|
|
498
|
-
function stringListHash(values) {
|
|
499
|
-
const normalized = values.filter((value) => typeof value === 'string' && value.length > 0);
|
|
500
|
-
return normalized.length > 0 ? hashJson([...normalized].sort((left, right) => left.localeCompare(right))) : null;
|
|
501
|
-
}
|
|
502
|
-
function reproObservation(routeId, phase, evidence) {
|
|
503
|
-
const status = stringField(evidence, 'status');
|
|
504
|
-
const outcome = stringField(evidence, 'outcome') ?? status;
|
|
505
|
-
const receiptHash = stringField(evidence, 'receipt_sha256');
|
|
506
|
-
const diagnosticFingerprint = stringField(evidence, 'diagnostic_fingerprint') ??
|
|
507
|
-
stringField(evidence, 'diagnostic_hash') ??
|
|
508
|
-
hashJson({
|
|
509
|
-
phase,
|
|
510
|
-
status,
|
|
511
|
-
outcome,
|
|
512
|
-
summary: stringField(evidence, 'summary'),
|
|
513
|
-
reason: stringField(evidence, 'reason'),
|
|
514
|
-
});
|
|
515
|
-
return {
|
|
516
|
-
routeId,
|
|
517
|
-
phase,
|
|
518
|
-
outcome,
|
|
519
|
-
receiptHash,
|
|
520
|
-
diagnosticFingerprint,
|
|
521
|
-
};
|
|
522
|
-
}
|
|
523
|
-
function evidenceStatusForRunReceipt(latest) {
|
|
524
|
-
return stringField(latest, 'status') ?? (booleanField(latest, 'timed_out') ? 'timed_out' : 'unknown');
|
|
525
|
-
}
|
|
526
|
-
function failedIntentsFromReceipts(receipts) {
|
|
527
|
-
return receipts
|
|
528
|
-
.filter((receipt) => ['failed', 'timed_out', 'start_failed'].includes(receipt.status))
|
|
529
|
-
.map((receipt) => receipt.intent)
|
|
530
|
-
.filter((intent) => typeof intent === 'string' && intent.length > 0)
|
|
531
|
-
.sort((left, right) => left.localeCompare(right));
|
|
532
|
-
}
|
|
533
|
-
function createFailureFingerprint(input) {
|
|
534
|
-
if (input.status === 'passed' ||
|
|
535
|
-
input.status === 'verified' ||
|
|
536
|
-
(input.failedIntents.length === 0 && input.riskCodes.length === 0 && input.timedOut !== true && !input.errorKind)) {
|
|
537
|
-
return null;
|
|
538
|
-
}
|
|
539
|
-
return sha256Text(JSON.stringify({
|
|
540
|
-
command: input.command,
|
|
541
|
-
status: input.status,
|
|
542
|
-
verificationPlanId: input.verificationPlanId,
|
|
543
|
-
primaryReason: input.primaryReason,
|
|
544
|
-
failedIntents: [...input.failedIntents].sort((left, right) => left.localeCompare(right)),
|
|
545
|
-
riskCodes: [...input.riskCodes].sort((left, right) => left.localeCompare(right)),
|
|
546
|
-
runIntent: input.runIntent ?? null,
|
|
547
|
-
timedOut: input.timedOut === true,
|
|
548
|
-
exitCodeClass: input.exitCodeClass ?? null,
|
|
549
|
-
errorKind: input.errorKind ?? null,
|
|
550
|
-
}));
|
|
551
|
-
}
|
|
552
|
-
function createVerificationEvidenceIndex(projectRoot) {
|
|
553
|
-
const latestPath = path.join(projectRoot, ...LATEST_RUN_STATE_RELATIVE_PATH.split('/'));
|
|
554
|
-
if (!existsSync(latestPath)) {
|
|
555
|
-
return {
|
|
556
|
-
summaries: [],
|
|
557
|
-
verificationPlans: [],
|
|
558
|
-
acceptanceCriteria: [],
|
|
559
|
-
criterionCoverage: [],
|
|
560
|
-
receipts: [],
|
|
561
|
-
commandReceiptSummaries: [],
|
|
562
|
-
coverageStates: [],
|
|
563
|
-
riskSignals: [],
|
|
564
|
-
validationRatchetSignals: [],
|
|
565
|
-
completionVerdictSummaries: [],
|
|
566
|
-
failureFingerprints: [],
|
|
567
|
-
reproRoutes: [],
|
|
568
|
-
reproObservations: [],
|
|
569
|
-
failureFingerprintReadModels: [],
|
|
570
|
-
};
|
|
571
|
-
}
|
|
572
|
-
const latestRecord = readJsonRecord(projectRoot, LATEST_RUN_STATE_RELATIVE_PATH);
|
|
573
|
-
if (!latestRecord) {
|
|
574
|
-
return {
|
|
575
|
-
summaries: [],
|
|
576
|
-
verificationPlans: [],
|
|
577
|
-
acceptanceCriteria: [],
|
|
578
|
-
criterionCoverage: [],
|
|
579
|
-
receipts: [],
|
|
580
|
-
commandReceiptSummaries: [],
|
|
581
|
-
coverageStates: [],
|
|
582
|
-
riskSignals: [],
|
|
583
|
-
validationRatchetSignals: [],
|
|
584
|
-
completionVerdictSummaries: [],
|
|
585
|
-
failureFingerprints: [],
|
|
586
|
-
reproRoutes: [],
|
|
587
|
-
reproObservations: [],
|
|
588
|
-
failureFingerprintReadModels: [],
|
|
589
|
-
};
|
|
590
|
-
}
|
|
591
|
-
const latest = latestRecord.value;
|
|
592
|
-
const sourceHash = sha256Bytes(Buffer.from(latestRecord.content));
|
|
593
|
-
const command = stringField(latest, 'command') ?? 'unknown';
|
|
594
|
-
const kind = stringField(latest, 'kind') ?? (command === 'verify' ? 'verify_run_summary' : 'run_receipt');
|
|
595
|
-
const evidenceModel = recordField(latest, 'evidence_model');
|
|
596
|
-
const completionVerdict = recordField(latest, 'completion_verdict');
|
|
597
|
-
const completionEvidence = recordField(completionVerdict, 'evidence');
|
|
598
|
-
const verificationPlanId = stringField(latest, 'verification_plan_id') ?? stringField(evidenceModel, 'verification_plan_id');
|
|
599
|
-
const primaryReason = stringField(completionVerdict, 'primary_reason');
|
|
600
|
-
const status = stringField(latest, 'status') ?? stringField(completionVerdict, 'status') ?? 'unknown';
|
|
601
|
-
const completionStatus = stringField(completionVerdict, 'status');
|
|
602
|
-
const rawReceipts = recordArrayField(evidenceModel, 'receipts');
|
|
603
|
-
const rawCoverage = recordArrayField(evidenceModel, 'coverage_matrix');
|
|
604
|
-
const rawRequirements = recordArrayField(evidenceModel, 'requirements');
|
|
605
|
-
const rawRisks = recordArrayField(evidenceModel, 'remaining_risks');
|
|
606
|
-
const recordedFailureFingerprintRecord = recordField(latest, 'failure_fingerprint');
|
|
607
|
-
const repeatedFailureSummary = recordField(latest, 'repeated_failure_summary');
|
|
608
|
-
const reproEvidence = recordField(latest, 'repro_evidence') ?? recordField(evidenceModel, 'repro_evidence');
|
|
609
|
-
const reproductionRoute = recordField(reproEvidence, 'reproduction_route');
|
|
610
|
-
const recordedFailureFingerprint = stringField(recordedFailureFingerprintRecord, 'fingerprint');
|
|
611
|
-
const receipts = rawReceipts.length > 0
|
|
612
|
-
? rawReceipts.map((receipt, index) => ({
|
|
613
|
-
sourcePath: LATEST_RUN_STATE_RELATIVE_PATH,
|
|
614
|
-
ordinal: index + 1,
|
|
615
|
-
intent: stringField(receipt, 'intent'),
|
|
616
|
-
status: stringField(receipt, 'status') ?? 'unknown',
|
|
617
|
-
skipped: booleanField(receipt, 'skipped'),
|
|
618
|
-
verificationPlanId: stringField(receipt, 'verification_plan_id'),
|
|
619
|
-
receiptPath: stringField(receipt, 'receipt_path'),
|
|
620
|
-
receiptSha256: stringField(receipt, 'receipt_sha256'),
|
|
621
|
-
commandFingerprint: stringField(receipt, 'command_fingerprint'),
|
|
622
|
-
contractFingerprint: stringField(receipt, 'contract_fingerprint'),
|
|
623
|
-
currentStateHash: stringField(receipt, 'head_tree_hash') ??
|
|
624
|
-
stringField(receipt, 'changed_files_hash') ??
|
|
625
|
-
stringField(receipt, 'changed_file_hash'),
|
|
626
|
-
writeDriftStatus: stringField(receipt, 'write_drift_status'),
|
|
627
|
-
}))
|
|
628
|
-
: [
|
|
629
|
-
{
|
|
630
|
-
sourcePath: LATEST_RUN_STATE_RELATIVE_PATH,
|
|
631
|
-
ordinal: 1,
|
|
632
|
-
intent: stringField(latest, 'intent'),
|
|
633
|
-
status: evidenceStatusForRunReceipt(latest),
|
|
634
|
-
skipped: false,
|
|
635
|
-
verificationPlanId: null,
|
|
636
|
-
receiptPath: stringField(latest, 'receipt_path') ?? LATEST_RUN_STATE_RELATIVE_PATH,
|
|
637
|
-
receiptSha256: sourceHash,
|
|
638
|
-
commandFingerprint: stringField(recordField(latest, 'performance'), 'command_fingerprint'),
|
|
639
|
-
contractFingerprint: stringField(recordField(latest, 'performance'), 'contract_fingerprint'),
|
|
640
|
-
currentStateHash: stringField(latest, 'head_tree_hash') ?? stringField(latest, 'changed_files_hash'),
|
|
641
|
-
writeDriftStatus: stringField(recordField(latest, 'write_drift'), 'status'),
|
|
642
|
-
},
|
|
643
|
-
];
|
|
644
|
-
const coverageStates = rawCoverage.map((coverage) => {
|
|
645
|
-
const evidence = recordField(coverage, 'evidence');
|
|
646
|
-
return {
|
|
647
|
-
sourcePath: LATEST_RUN_STATE_RELATIVE_PATH,
|
|
648
|
-
criterionId: stringField(coverage, 'criterion_id') ?? 'unknown',
|
|
649
|
-
source: stringField(coverage, 'source') ?? 'unknown',
|
|
650
|
-
status: stringField(coverage, 'status') ?? 'unknown',
|
|
651
|
-
requirementReason: stringField(coverage, 'requirement_reason'),
|
|
652
|
-
intents: stringArrayField(evidence, 'intents'),
|
|
653
|
-
receiptCount: stringArrayField(evidence, 'receipt_paths').length,
|
|
654
|
-
gapCount: stringArrayField(evidence, 'gap_reasons').length,
|
|
655
|
-
sourceAnchorCount: stringArrayField(evidence, 'source_anchor_ids').length,
|
|
656
|
-
};
|
|
657
|
-
});
|
|
658
|
-
const riskSignals = rawRisks.map((risk, index) => ({
|
|
659
|
-
sourcePath: LATEST_RUN_STATE_RELATIVE_PATH,
|
|
660
|
-
ordinal: index + 1,
|
|
661
|
-
code: stringField(risk, 'code') ?? 'unknown',
|
|
662
|
-
severity: stringField(risk, 'severity') ?? 'unknown',
|
|
663
|
-
detailHash: sha256Text(stringField(risk, 'detail') ?? ''),
|
|
664
|
-
}));
|
|
665
|
-
const validationRatchetSignals = rawRisks
|
|
666
|
-
.map((risk, index) => {
|
|
667
|
-
const code = stringField(risk, 'code') ?? 'unknown';
|
|
668
|
-
if (!VALIDATION_RATCHET_RISK_CODES.has(code)) {
|
|
669
|
-
return null;
|
|
670
|
-
}
|
|
671
|
-
const severity = stringField(risk, 'severity') ?? 'unknown';
|
|
672
|
-
const pathValue = stringField(risk, 'path');
|
|
673
|
-
const detailHash = sha256Text(stringField(risk, 'detail') ?? '');
|
|
674
|
-
const pathHash = pathValue === null ? hashJson({ code, detailHash }) : sha256Text(pathValue);
|
|
675
|
-
const beforeHash = stringField(risk, 'before_hash') ?? stringField(risk, 'before_digest');
|
|
676
|
-
const afterHash = stringField(risk, 'after_hash') ?? stringField(risk, 'after_digest');
|
|
677
|
-
return {
|
|
678
|
-
signalId: hashJson({
|
|
679
|
-
sourcePath: LATEST_RUN_STATE_RELATIVE_PATH,
|
|
680
|
-
ordinal: index + 1,
|
|
681
|
-
planId: verificationPlanId,
|
|
682
|
-
code,
|
|
683
|
-
pathHash,
|
|
684
|
-
beforeHash,
|
|
685
|
-
afterHash,
|
|
686
|
-
}),
|
|
687
|
-
planId: verificationPlanId,
|
|
688
|
-
code,
|
|
689
|
-
severity,
|
|
690
|
-
pathHash,
|
|
691
|
-
beforeHash,
|
|
692
|
-
afterHash,
|
|
693
|
-
};
|
|
694
|
-
})
|
|
695
|
-
.filter((signal) => signal !== null);
|
|
696
|
-
const verificationPlans = verificationPlanId === null
|
|
697
|
-
? []
|
|
698
|
-
: [
|
|
699
|
-
{
|
|
700
|
-
planId: verificationPlanId,
|
|
701
|
-
sourcePath: LATEST_RUN_STATE_RELATIVE_PATH,
|
|
702
|
-
classificationHash: rawRequirements.length > 0 || rawCoverage.length > 0
|
|
703
|
-
? hashJson({
|
|
704
|
-
requirements: rawRequirements.map((requirement) => ({
|
|
705
|
-
id: stringField(requirement, 'requirement_id') ?? stringField(requirement, 'id'),
|
|
706
|
-
reason: stringField(requirement, 'reason'),
|
|
707
|
-
source: stringField(requirement, 'source'),
|
|
708
|
-
})),
|
|
709
|
-
coverage: rawCoverage.map((coverage) => ({
|
|
710
|
-
id: stringField(coverage, 'criterion_id'),
|
|
711
|
-
reason: stringField(coverage, 'requirement_reason'),
|
|
712
|
-
source: stringField(coverage, 'source'),
|
|
713
|
-
status: stringField(coverage, 'status'),
|
|
714
|
-
})),
|
|
715
|
-
})
|
|
716
|
-
: null,
|
|
717
|
-
commandContractHash: stringListHash(receipts.map((receipt) => receipt.contractFingerprint)),
|
|
718
|
-
selectedIntentsHash: stringListHash(receipts.map((receipt) => receipt.intent)),
|
|
719
|
-
createdAt: stringField(latest, 'started_at') ?? stringField(latest, 'created_at'),
|
|
720
|
-
sourceHash,
|
|
721
|
-
},
|
|
722
|
-
];
|
|
723
|
-
const acceptanceCriteria = verificationPlanId === null
|
|
724
|
-
? []
|
|
725
|
-
: rawCoverage.map((coverage) => {
|
|
726
|
-
const evidence = recordField(coverage, 'evidence');
|
|
727
|
-
const pathRefs = [
|
|
728
|
-
...stringArrayField(evidence, 'paths'),
|
|
729
|
-
...stringArrayField(evidence, 'changed_paths'),
|
|
730
|
-
...stringArrayField(evidence, 'source_anchor_ids'),
|
|
731
|
-
];
|
|
732
|
-
return {
|
|
733
|
-
criterionId: stringField(coverage, 'criterion_id') ?? 'unknown',
|
|
734
|
-
planId: verificationPlanId,
|
|
735
|
-
source: stringField(coverage, 'source') ?? 'unknown',
|
|
736
|
-
statementHash: stringField(coverage, 'statement') ? sha256Text(stringField(coverage, 'statement') ?? '') : null,
|
|
737
|
-
reason: stringField(coverage, 'requirement_reason'),
|
|
738
|
-
surface: stringField(coverage, 'surface'),
|
|
739
|
-
pathHash: pathRefs.length > 0 ? stringListHash(pathRefs) : null,
|
|
740
|
-
};
|
|
741
|
-
});
|
|
742
|
-
const criterionCoverage = verificationPlanId === null
|
|
743
|
-
? []
|
|
744
|
-
: coverageStates.map((coverage) => ({
|
|
745
|
-
criterionId: coverage.criterionId,
|
|
746
|
-
planId: verificationPlanId,
|
|
747
|
-
status: coverage.status,
|
|
748
|
-
receiptCount: coverage.receiptCount,
|
|
749
|
-
gapCount: coverage.gapCount,
|
|
750
|
-
riskCount: coverage.sourceAnchorCount,
|
|
751
|
-
}));
|
|
752
|
-
const commandReceiptSummaries = verificationPlanId === null
|
|
753
|
-
? []
|
|
754
|
-
: receipts
|
|
755
|
-
.filter((receipt) => receipt.verificationPlanId === verificationPlanId || receipt.verificationPlanId === null)
|
|
756
|
-
.map((receipt) => ({
|
|
757
|
-
receiptHash: receipt.receiptSha256 ??
|
|
758
|
-
hashJson({
|
|
759
|
-
sourcePath: receipt.sourcePath,
|
|
760
|
-
ordinal: receipt.ordinal,
|
|
761
|
-
intent: receipt.intent,
|
|
762
|
-
status: receipt.status,
|
|
763
|
-
verificationPlanId,
|
|
764
|
-
}),
|
|
765
|
-
planId: verificationPlanId,
|
|
766
|
-
intent: receipt.intent,
|
|
767
|
-
status: receipt.status,
|
|
768
|
-
commandFingerprint: receipt.commandFingerprint,
|
|
769
|
-
contractFingerprint: receipt.contractFingerprint,
|
|
770
|
-
currentStateHash: receipt.currentStateHash,
|
|
771
|
-
writeDriftStatus: receipt.writeDriftStatus,
|
|
772
|
-
}));
|
|
773
|
-
const completionVerdictSummaries = verificationPlanId === null || completionStatus === null
|
|
774
|
-
? []
|
|
775
|
-
: [
|
|
776
|
-
{
|
|
777
|
-
claimId: hashJson({
|
|
778
|
-
sourceHash,
|
|
779
|
-
verificationPlanId,
|
|
780
|
-
completionStatus,
|
|
781
|
-
primaryReason,
|
|
782
|
-
}),
|
|
783
|
-
planId: verificationPlanId,
|
|
784
|
-
status: completionStatus,
|
|
785
|
-
primaryReason,
|
|
786
|
-
riskCount: riskSignals.length,
|
|
787
|
-
contradictionCount: stringArrayField(completionVerdict, 'contradictions').length,
|
|
788
|
-
blockerCount: stringArrayField(completionVerdict, 'blockers').length,
|
|
789
|
-
},
|
|
790
|
-
];
|
|
791
|
-
const failedIntents = failedIntentsFromReceipts(receipts);
|
|
792
|
-
const failureFingerprint = recordedFailureFingerprint ??
|
|
793
|
-
createFailureFingerprint({
|
|
794
|
-
command,
|
|
795
|
-
status: completionStatus ?? status,
|
|
796
|
-
verificationPlanId,
|
|
797
|
-
primaryReason,
|
|
798
|
-
failedIntents,
|
|
799
|
-
riskCodes: riskSignals.map((risk) => risk.code),
|
|
800
|
-
runIntent: stringField(latest, 'intent'),
|
|
801
|
-
timedOut: booleanField(latest, 'timed_out'),
|
|
802
|
-
exitCodeClass: stringField(recordField(recordField(latest, 'performance'), 'result_summary'), 'exit_code_class'),
|
|
803
|
-
errorKind: stringField(recordField(recordField(latest, 'performance'), 'result_summary'), 'error_kind'),
|
|
804
|
-
});
|
|
805
|
-
const failureFingerprints = failureFingerprint === null
|
|
806
|
-
? []
|
|
807
|
-
: [
|
|
808
|
-
{
|
|
809
|
-
sourcePath: LATEST_RUN_STATE_RELATIVE_PATH,
|
|
810
|
-
fingerprint: failureFingerprint,
|
|
811
|
-
verificationPlanId,
|
|
812
|
-
status: completionStatus ?? status,
|
|
813
|
-
failedIntents,
|
|
814
|
-
primaryReason,
|
|
815
|
-
failedIntentsHash: stringField(recordedFailureFingerprintRecord, 'failed_intents_hash') ??
|
|
816
|
-
stringField(repeatedFailureSummary, 'failed_intents_hash'),
|
|
817
|
-
riskCodesHash: stringField(recordedFailureFingerprintRecord, 'risk_codes_hash') ??
|
|
818
|
-
stringField(repeatedFailureSummary, 'risk_codes_hash'),
|
|
819
|
-
affectedSurfacesHash: stringField(recordedFailureFingerprintRecord, 'affected_surfaces_hash') ??
|
|
820
|
-
stringField(repeatedFailureSummary, 'affected_surfaces_hash'),
|
|
821
|
-
firstSeenAt: stringField(repeatedFailureSummary, 'first_seen_at'),
|
|
822
|
-
lastSeenAt: stringField(repeatedFailureSummary, 'last_seen_at'),
|
|
823
|
-
seenCount: Math.max(1, numberField(repeatedFailureSummary, 'seen_count')),
|
|
824
|
-
requiresNewEvidence: booleanField(repeatedFailureSummary, 'requires_new_evidence'),
|
|
825
|
-
},
|
|
826
|
-
];
|
|
827
|
-
const routeId = stringField(reproductionRoute, 'route_id');
|
|
828
|
-
const reproRoutes = routeId === null || reproEvidence === null
|
|
829
|
-
? []
|
|
830
|
-
: [
|
|
831
|
-
{
|
|
832
|
-
routeId,
|
|
833
|
-
taskHash: hashJson({
|
|
834
|
-
reported_symptom: stringField(reproEvidence, 'reported_symptom'),
|
|
835
|
-
expected_behavior: stringField(reproEvidence, 'expected_behavior'),
|
|
836
|
-
observed_behavior: stringField(reproEvidence, 'observed_behavior'),
|
|
837
|
-
}),
|
|
838
|
-
routeDigest: stringField(reproductionRoute, 'route_digest'),
|
|
839
|
-
routeKind: stringField(reproductionRoute, 'route_kind'),
|
|
840
|
-
failureOracleHash: stringField(reproductionRoute, 'failure_oracle_hash'),
|
|
841
|
-
},
|
|
842
|
-
];
|
|
843
|
-
const reproObservations = routeId === null || reproEvidence === null
|
|
844
|
-
? []
|
|
845
|
-
: [
|
|
846
|
-
reproObservation(routeId, 'before_fix', recordField(reproEvidence, 'before_fix')),
|
|
847
|
-
reproObservation(routeId, 'after_fix', recordField(reproEvidence, 'after_fix')),
|
|
848
|
-
reproObservation(routeId, 'regression_guard', recordField(reproEvidence, 'regression_guard')),
|
|
849
|
-
];
|
|
850
|
-
const failureFingerprintReadModels = failureFingerprints.map((fingerprint) => ({
|
|
851
|
-
fingerprint: fingerprint.fingerprint,
|
|
852
|
-
planId: fingerprint.verificationPlanId,
|
|
853
|
-
failedIntentsHash: fingerprint.failedIntentsHash ?? stringListHash(fingerprint.failedIntents),
|
|
854
|
-
riskCodesHash: fingerprint.riskCodesHash,
|
|
855
|
-
seenCount: fingerprint.seenCount,
|
|
856
|
-
firstSeenAt: fingerprint.firstSeenAt,
|
|
857
|
-
lastSeenAt: fingerprint.lastSeenAt,
|
|
858
|
-
}));
|
|
859
|
-
return {
|
|
860
|
-
summaries: [
|
|
861
|
-
{
|
|
862
|
-
sourcePath: LATEST_RUN_STATE_RELATIVE_PATH,
|
|
863
|
-
sourceHash,
|
|
864
|
-
command,
|
|
865
|
-
kind,
|
|
866
|
-
status,
|
|
867
|
-
runDir: stringField(latest, 'run_dir'),
|
|
868
|
-
manifestPath: stringField(latest, 'manifest_path'),
|
|
869
|
-
verificationPlanId,
|
|
870
|
-
completionStatus,
|
|
871
|
-
primaryReason,
|
|
872
|
-
matchedIntents: numberField(completionEvidence, 'matched_intents'),
|
|
873
|
-
ranIntents: numberField(completionEvidence, 'ran_intents'),
|
|
874
|
-
passedIntents: numberField(completionEvidence, 'passed_intents'),
|
|
875
|
-
failedIntents: numberField(completionEvidence, 'failed_intents'),
|
|
876
|
-
skippedIntents: numberField(completionEvidence, 'skipped_intents'),
|
|
877
|
-
receiptCount: receipts.length,
|
|
878
|
-
coverageCount: coverageStates.length,
|
|
879
|
-
remainingRiskCount: riskSignals.length,
|
|
880
|
-
failureFingerprint,
|
|
881
|
-
},
|
|
882
|
-
],
|
|
883
|
-
verificationPlans,
|
|
884
|
-
acceptanceCriteria,
|
|
885
|
-
criterionCoverage,
|
|
886
|
-
receipts,
|
|
887
|
-
commandReceiptSummaries,
|
|
888
|
-
coverageStates,
|
|
889
|
-
riskSignals,
|
|
890
|
-
validationRatchetSignals,
|
|
891
|
-
completionVerdictSummaries,
|
|
892
|
-
failureFingerprints,
|
|
893
|
-
reproRoutes,
|
|
894
|
-
reproObservations,
|
|
895
|
-
failureFingerprintReadModels,
|
|
896
|
-
};
|
|
897
|
-
}
|
|
898
101
|
function readMetadataValue(database, key) {
|
|
899
102
|
return toSearchString(queryRows(database, 'SELECT value FROM metadata WHERE key = ?', [key])[0]?.value) || undefined;
|
|
900
103
|
}
|
|
@@ -1594,9 +797,6 @@ function populatePathSurfaceReadModel(database) {
|
|
|
1594
797
|
insertPathSurfaceReasons(database, rule.id, 'drift_check', rule.surface.driftChecks);
|
|
1595
798
|
}
|
|
1596
799
|
}
|
|
1597
|
-
function skillRouteKey(route) {
|
|
1598
|
-
return `${route.skillName}\u0000${route.trigger}`;
|
|
1599
|
-
}
|
|
1600
800
|
function populateSearchTables(database, capabilities, documents, skills, skillRoutes, commandIntents, sourceAnchors) {
|
|
1601
801
|
for (const document of documents) {
|
|
1602
802
|
const documentTerms = queryRows(database, 'SELECT term FROM document_terms WHERE document_path = ? ORDER BY term', [
|
|
@@ -2078,7 +1278,7 @@ function indexedFilesMatch(database, currentFiles) {
|
|
|
2078
1278
|
if (!current) {
|
|
2079
1279
|
return false;
|
|
2080
1280
|
}
|
|
2081
|
-
if (normalizeIndexedFileSourceScope(row.source_scope) !== current.sourceScope ||
|
|
1281
|
+
if (normalizeIndexedFileSourceScope(toSearchString(row.source_scope)) !== current.sourceScope ||
|
|
2082
1282
|
toSearchString(row.content_hash) !== current.contentHash ||
|
|
2083
1283
|
toSearchString(row.parser_version) !== LOCAL_INDEX_PARSER_VERSION) {
|
|
2084
1284
|
return false;
|
|
@@ -2098,7 +1298,7 @@ function indexedFileMetadataMatch(database, currentFiles) {
|
|
|
2098
1298
|
if (!current) {
|
|
2099
1299
|
return false;
|
|
2100
1300
|
}
|
|
2101
|
-
if (normalizeIndexedFileSourceScope(row.source_scope) !== current.sourceScope ||
|
|
1301
|
+
if (normalizeIndexedFileSourceScope(toSearchString(row.source_scope)) !== current.sourceScope ||
|
|
2102
1302
|
toNullableNumber(row.size_bytes) !== current.sizeBytes ||
|
|
2103
1303
|
toNullableNumber(row.mtime_ms) !== current.mtimeMs ||
|
|
2104
1304
|
toSearchString(row.parser_version) !== LOCAL_INDEX_PARSER_VERSION) {
|
|
@@ -2299,7 +1499,7 @@ function getStalePaths(projectRoot, database) {
|
|
|
2299
1499
|
const indexedPaths = new Set(indexedRows.map((row) => toSearchString(row.path)));
|
|
2300
1500
|
for (const row of indexedRows) {
|
|
2301
1501
|
const indexedPath = toSearchString(row.path);
|
|
2302
|
-
const sourceScope = normalizeIndexedFileSourceScope(row.source_scope);
|
|
1502
|
+
const sourceScope = normalizeIndexedFileSourceScope(toSearchString(row.source_scope));
|
|
2303
1503
|
try {
|
|
2304
1504
|
const current = readIndexedFileRecord(projectRoot, indexedPath, sourceScope);
|
|
2305
1505
|
if (current.contentHash !== toSearchString(row.content_hash)) {
|
|
@@ -2992,6 +2192,99 @@ function scoreIndexedOrTableScan(primaryFields, secondaryFields, query, indexedM
|
|
|
2992
2192
|
const tableScore = scoreMatch(primaryFields, secondaryFields, query);
|
|
2993
2193
|
return indexedMatches.active && matchSet.size > 0 && matchSet.has(key) ? Math.max(tableScore, 20) : tableScore;
|
|
2994
2194
|
}
|
|
2195
|
+
function sortLocalSearchResults(results, scope, limit) {
|
|
2196
|
+
return [...results]
|
|
2197
|
+
.sort((left, right) => {
|
|
2198
|
+
if (scope === 'all' && left.authority_rank !== right.authority_rank) {
|
|
2199
|
+
return left.authority_rank - right.authority_rank;
|
|
2200
|
+
}
|
|
2201
|
+
return right.score - left.score || (left.path ?? left.name ?? '').localeCompare(right.path ?? right.name ?? '');
|
|
2202
|
+
})
|
|
2203
|
+
.slice(0, limit);
|
|
2204
|
+
}
|
|
2205
|
+
function collectBoundedDirectSearchDocuments(projectRoot) {
|
|
2206
|
+
const documents = [];
|
|
2207
|
+
const relativePaths = getExistingIndexablePaths(projectRoot).slice(0, DIRECT_SEARCH_MAX_WORKFLOW_FILES);
|
|
2208
|
+
for (const relativePath of relativePaths) {
|
|
2209
|
+
try {
|
|
2210
|
+
documents.push(...collectDocumentsFromPaths(projectRoot, [relativePath]));
|
|
2211
|
+
}
|
|
2212
|
+
catch {
|
|
2213
|
+
continue;
|
|
2214
|
+
}
|
|
2215
|
+
}
|
|
2216
|
+
return documents;
|
|
2217
|
+
}
|
|
2218
|
+
function searchLocalWorkflowFilesDirectly(projectRoot, databasePath, normalizedQuery, limit, scope) {
|
|
2219
|
+
const cacheLayers = readCacheLayerSets(projectRoot);
|
|
2220
|
+
const results = [];
|
|
2221
|
+
if (scope === 'workflow' || scope === 'all') {
|
|
2222
|
+
const documents = collectBoundedDirectSearchDocuments(projectRoot);
|
|
2223
|
+
for (const document of documents) {
|
|
2224
|
+
let searchableContent = document.contentSnippet;
|
|
2225
|
+
try {
|
|
2226
|
+
searchableContent = readText(projectRoot, document.path);
|
|
2227
|
+
}
|
|
2228
|
+
catch {
|
|
2229
|
+
searchableContent = document.contentSnippet;
|
|
2230
|
+
}
|
|
2231
|
+
const primaryFields = [document.path, document.title];
|
|
2232
|
+
const secondaryFields = [document.type, searchableContent, ...document.sections];
|
|
2233
|
+
const fields = [...primaryFields, ...secondaryFields];
|
|
2234
|
+
if (!isMatched(fields, normalizedQuery)) {
|
|
2235
|
+
continue;
|
|
2236
|
+
}
|
|
2237
|
+
results.push(withCacheHint({
|
|
2238
|
+
kind: 'document',
|
|
2239
|
+
path: document.path,
|
|
2240
|
+
title: document.title,
|
|
2241
|
+
document_type: document.type,
|
|
2242
|
+
...workflowAuthorityForDocument(document.type),
|
|
2243
|
+
match: getMatchSnippet(fields, normalizedQuery),
|
|
2244
|
+
score: scoreMatch(primaryFields, secondaryFields, normalizedQuery),
|
|
2245
|
+
}, cacheLayers));
|
|
2246
|
+
}
|
|
2247
|
+
for (const skill of collectSkills(documents)) {
|
|
2248
|
+
const fields = [skill.name, skill.path, skill.title];
|
|
2249
|
+
if (!isMatched(fields, normalizedQuery)) {
|
|
2250
|
+
continue;
|
|
2251
|
+
}
|
|
2252
|
+
results.push(withCacheHint({
|
|
2253
|
+
kind: 'skill',
|
|
2254
|
+
name: skill.name,
|
|
2255
|
+
path: skill.path,
|
|
2256
|
+
title: skill.title,
|
|
2257
|
+
...skillAuthority(),
|
|
2258
|
+
match: getMatchSnippet(fields, normalizedQuery),
|
|
2259
|
+
score: scoreMatch(fields, [], normalizedQuery),
|
|
2260
|
+
}, cacheLayers));
|
|
2261
|
+
}
|
|
2262
|
+
}
|
|
2263
|
+
const sortedResults = sortLocalSearchResults(results, scope, limit);
|
|
2264
|
+
return {
|
|
2265
|
+
schema_version: LOCAL_INDEX_SCHEMA_VERSION,
|
|
2266
|
+
command: 'search',
|
|
2267
|
+
ok: true,
|
|
2268
|
+
mustflow_root: path.resolve(projectRoot),
|
|
2269
|
+
database_path: databasePath,
|
|
2270
|
+
query: normalizedQuery,
|
|
2271
|
+
limit,
|
|
2272
|
+
scope,
|
|
2273
|
+
index_fresh: false,
|
|
2274
|
+
stale_paths: [],
|
|
2275
|
+
search_backend: SEARCH_BACKEND_TABLE_SCAN,
|
|
2276
|
+
search_fts5_available: false,
|
|
2277
|
+
result_count: sortedResults.length,
|
|
2278
|
+
results: sortedResults,
|
|
2279
|
+
};
|
|
2280
|
+
}
|
|
2281
|
+
function isLocalIndexStaleError(error) {
|
|
2282
|
+
return error instanceof Error && error.message.startsWith('Local mustflow index is stale:');
|
|
2283
|
+
}
|
|
2284
|
+
function isLocalIndexRuntimeUnavailableError(error) {
|
|
2285
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
2286
|
+
return /file is not a database|database disk image is malformed|no such table|no such column|sqlite|sql\.js/iu.test(message);
|
|
2287
|
+
}
|
|
2995
2288
|
/**
|
|
2996
2289
|
* mf:anchor cli.search.local-index
|
|
2997
2290
|
* purpose: Search the local index while preserving workflow authority above source navigation hints.
|
|
@@ -3004,14 +2297,20 @@ export async function searchLocalIndex(projectRoot, query, options = {}) {
|
|
|
3004
2297
|
const limit = Math.max(1, Math.min(options.limit ?? 10, 50));
|
|
3005
2298
|
const scope = options.scope ?? 'workflow';
|
|
3006
2299
|
const databasePath = getLocalIndexDatabasePath(projectRoot);
|
|
3007
|
-
if (!existsSync(databasePath)) {
|
|
3008
|
-
throw new Error('Local mustflow index not found. Run `mf index` before searching.');
|
|
3009
|
-
}
|
|
3010
2300
|
if (normalizedQuery.length === 0) {
|
|
3011
2301
|
throw new Error('Search query must not be empty.');
|
|
3012
2302
|
}
|
|
3013
|
-
|
|
3014
|
-
|
|
2303
|
+
if (!existsSync(databasePath)) {
|
|
2304
|
+
return searchLocalWorkflowFilesDirectly(projectRoot, databasePath, normalizedQuery, limit, scope);
|
|
2305
|
+
}
|
|
2306
|
+
let database;
|
|
2307
|
+
try {
|
|
2308
|
+
const SQL = await loadSqlJs();
|
|
2309
|
+
database = new SQL.Database(readFileSync(databasePath));
|
|
2310
|
+
}
|
|
2311
|
+
catch {
|
|
2312
|
+
return searchLocalWorkflowFilesDirectly(projectRoot, databasePath, normalizedQuery, limit, scope);
|
|
2313
|
+
}
|
|
3015
2314
|
const cacheLayers = readCacheLayerSets(projectRoot);
|
|
3016
2315
|
let capabilities = searchCapabilities(false);
|
|
3017
2316
|
const results = [];
|
|
@@ -3161,17 +2460,19 @@ export async function searchLocalIndex(projectRoot, query, options = {}) {
|
|
|
3161
2460
|
}
|
|
3162
2461
|
}
|
|
3163
2462
|
}
|
|
2463
|
+
catch (error) {
|
|
2464
|
+
if (isLocalIndexStaleError(error)) {
|
|
2465
|
+
throw error;
|
|
2466
|
+
}
|
|
2467
|
+
if (isLocalIndexRuntimeUnavailableError(error)) {
|
|
2468
|
+
return searchLocalWorkflowFilesDirectly(projectRoot, databasePath, normalizedQuery, limit, scope);
|
|
2469
|
+
}
|
|
2470
|
+
throw error;
|
|
2471
|
+
}
|
|
3164
2472
|
finally {
|
|
3165
2473
|
database.close();
|
|
3166
2474
|
}
|
|
3167
|
-
const sortedResults = results
|
|
3168
|
-
.sort((left, right) => {
|
|
3169
|
-
if (scope === 'all' && left.authority_rank !== right.authority_rank) {
|
|
3170
|
-
return left.authority_rank - right.authority_rank;
|
|
3171
|
-
}
|
|
3172
|
-
return right.score - left.score || (left.path ?? left.name ?? '').localeCompare(right.path ?? right.name ?? '');
|
|
3173
|
-
})
|
|
3174
|
-
.slice(0, limit);
|
|
2475
|
+
const sortedResults = sortLocalSearchResults(results, scope, limit);
|
|
3175
2476
|
return {
|
|
3176
2477
|
schema_version: LOCAL_INDEX_SCHEMA_VERSION,
|
|
3177
2478
|
command: 'search',
|