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