mustflow 2.75.1 → 2.84.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +31 -3
- package/dist/cli/commands/docs.js +86 -2
- package/dist/cli/commands/script-pack.js +5 -0
- package/dist/cli/i18n/en.js +101 -2
- package/dist/cli/i18n/es.js +101 -2
- package/dist/cli/i18n/fr.js +101 -2
- package/dist/cli/i18n/hi.js +101 -2
- package/dist/cli/i18n/ko.js +101 -2
- package/dist/cli/i18n/zh.js +101 -2
- package/dist/cli/lib/script-pack-registry.js +162 -7
- package/dist/cli/script-packs/code-export-diff.js +160 -0
- package/dist/cli/script-packs/code-outline.js +33 -5
- package/dist/cli/script-packs/code-route-outline.js +155 -0
- package/dist/cli/script-packs/docs-reference-drift.js +150 -0
- package/dist/cli/script-packs/repo-config-chain.js +163 -0
- package/dist/cli/script-packs/repo-related-files.js +161 -0
- package/dist/core/code-outline.js +527 -80
- package/dist/core/config-chain.js +595 -0
- package/dist/core/export-diff.js +359 -0
- package/dist/core/public-json-contracts.js +75 -0
- package/dist/core/reference-drift.js +388 -0
- package/dist/core/related-files.js +493 -0
- package/dist/core/route-outline.js +912 -0
- package/dist/core/script-pack-suggestions.js +111 -5
- package/dist/core/source-anchors.js +13 -1
- package/package.json +1 -1
- package/schemas/README.md +28 -5
- package/schemas/code-outline-report.schema.json +47 -1
- package/schemas/code-symbol-read-report.schema.json +64 -4
- package/schemas/config-chain-report.schema.json +187 -0
- package/schemas/export-diff-report.schema.json +220 -0
- package/schemas/reference-drift-report.schema.json +166 -0
- package/schemas/related-files-report.schema.json +145 -0
- package/schemas/route-outline-report.schema.json +200 -0
- package/templates/default/common/.mustflow/config/commands.toml +21 -0
- package/templates/default/i18n.toml +7 -1
- package/templates/default/locales/en/.mustflow/docs/agent-workflow.md +1 -1
- package/templates/default/locales/en/.mustflow/skills/INDEX.md +2 -1
- package/templates/default/locales/en/.mustflow/skills/cross-agent-session-reference/SKILL.md +131 -0
- package/templates/default/locales/en/.mustflow/skills/routes.toml +6 -0
- package/templates/default/manifest.toml +8 -1
|
@@ -0,0 +1,388 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { existsSync, lstatSync, readdirSync } from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { ensureInside, ensureInsideWithoutSymlinks, readFileInsideWithoutSymlinks } from './safe-filesystem.js';
|
|
5
|
+
export const REFERENCE_DRIFT_PACK_ID = 'docs';
|
|
6
|
+
export const REFERENCE_DRIFT_SCRIPT_ID = 'reference-drift';
|
|
7
|
+
export const REFERENCE_DRIFT_SCRIPT_REF = `${REFERENCE_DRIFT_PACK_ID}/${REFERENCE_DRIFT_SCRIPT_ID}`;
|
|
8
|
+
const DEFAULT_MAX_FILES = 200;
|
|
9
|
+
const DEFAULT_MAX_FILE_BYTES = 512 * 1024;
|
|
10
|
+
const MAX_ISSUES = 50;
|
|
11
|
+
const DEFAULT_PATHS = ['README.md', 'schemas/README.md', 'docs-site/src/content/docs'];
|
|
12
|
+
const PATH_FILTERS = ['*.md', '*.mdx'];
|
|
13
|
+
const CHECKED_REFERENCE_KINDS = [
|
|
14
|
+
'mf_command',
|
|
15
|
+
'script_pack_ref',
|
|
16
|
+
'schema_file',
|
|
17
|
+
'repo_path',
|
|
18
|
+
];
|
|
19
|
+
const IGNORED_DIRECTORIES = new Set(['.git', 'node_modules', 'dist', 'build', 'coverage', '.astro']);
|
|
20
|
+
const ERROR_CODES = new Set([
|
|
21
|
+
'reference_drift_path_outside_root',
|
|
22
|
+
'reference_drift_unreadable_path',
|
|
23
|
+
'reference_drift_file_too_large',
|
|
24
|
+
'reference_drift_max_files_exceeded',
|
|
25
|
+
]);
|
|
26
|
+
function normalizeRelativePath(value) {
|
|
27
|
+
return value.replace(/\\/gu, '/').replace(/^\.\/+/u, '') || '.';
|
|
28
|
+
}
|
|
29
|
+
function sha256Tagged(buffer) {
|
|
30
|
+
return `sha256:${createHash('sha256').update(buffer).digest('hex')}`;
|
|
31
|
+
}
|
|
32
|
+
function makeFinding(code, severity, pathValue, message, line) {
|
|
33
|
+
return line === undefined ? { code, severity, path: pathValue, message } : { code, severity, path: pathValue, line, message };
|
|
34
|
+
}
|
|
35
|
+
function pushIssue(issues, issue) {
|
|
36
|
+
if (issues.length < MAX_ISSUES) {
|
|
37
|
+
issues.push(issue);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
function isDocumentPath(relativePath) {
|
|
41
|
+
return /\.(?:md|mdx)$/u.test(relativePath);
|
|
42
|
+
}
|
|
43
|
+
function normalizeInputPath(projectRoot, inputPath) {
|
|
44
|
+
const absolutePath = path.resolve(process.cwd(), inputPath);
|
|
45
|
+
ensureInside(projectRoot, absolutePath);
|
|
46
|
+
return {
|
|
47
|
+
absolutePath,
|
|
48
|
+
relativePath: normalizeRelativePath(path.relative(projectRoot, absolutePath)),
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
function addCandidate(candidates, findings, issues, policy, candidate) {
|
|
52
|
+
if (candidates.has(candidate.relativePath)) {
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
if (candidates.size >= policy.max_files) {
|
|
56
|
+
if (!findings.some((finding) => finding.code === 'reference_drift_max_files_exceeded')) {
|
|
57
|
+
const message = `Reference-drift matched more than ${policy.max_files} document files; remaining files were skipped.`;
|
|
58
|
+
pushIssue(issues, message);
|
|
59
|
+
findings.push(makeFinding('reference_drift_max_files_exceeded', 'medium', candidate.relativePath, message));
|
|
60
|
+
}
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
candidates.set(candidate.relativePath, candidate);
|
|
64
|
+
}
|
|
65
|
+
function collectDocumentsFromDirectory(projectRoot, absoluteDirectory, candidates, findings, issues, policy) {
|
|
66
|
+
const relativeDirectory = normalizeRelativePath(path.relative(projectRoot, absoluteDirectory));
|
|
67
|
+
if (IGNORED_DIRECTORIES.has(path.basename(relativeDirectory)) || [...IGNORED_DIRECTORIES].some((entry) => relativeDirectory.startsWith(`${entry}/`))) {
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
let entries;
|
|
71
|
+
try {
|
|
72
|
+
ensureInsideWithoutSymlinks(projectRoot, absoluteDirectory);
|
|
73
|
+
entries = readdirSync(absoluteDirectory, { withFileTypes: true });
|
|
74
|
+
}
|
|
75
|
+
catch (error) {
|
|
76
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
77
|
+
pushIssue(issues, `${relativeDirectory}: ${message}`);
|
|
78
|
+
findings.push(makeFinding('reference_drift_unreadable_path', 'high', relativeDirectory, message));
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
for (const entry of entries) {
|
|
82
|
+
const absoluteEntry = path.join(absoluteDirectory, entry.name);
|
|
83
|
+
const relativeEntry = normalizeRelativePath(path.relative(projectRoot, absoluteEntry));
|
|
84
|
+
if (entry.isDirectory()) {
|
|
85
|
+
collectDocumentsFromDirectory(projectRoot, absoluteEntry, candidates, findings, issues, policy);
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
if (entry.isFile() && isDocumentPath(relativeEntry)) {
|
|
89
|
+
addCandidate(candidates, findings, issues, policy, { absolutePath: absoluteEntry, relativePath: relativeEntry });
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
function collectDocumentCandidates(projectRoot, inputPaths, policy, findings, issues) {
|
|
94
|
+
const candidates = new Map();
|
|
95
|
+
const pathsToCheck = inputPaths.length > 0 ? inputPaths : policy.default_paths;
|
|
96
|
+
for (const inputPath of pathsToCheck) {
|
|
97
|
+
let absolutePath;
|
|
98
|
+
let relativePath;
|
|
99
|
+
try {
|
|
100
|
+
const normalized = normalizeInputPath(projectRoot, inputPath);
|
|
101
|
+
absolutePath = normalized.absolutePath;
|
|
102
|
+
relativePath = normalized.relativePath;
|
|
103
|
+
ensureInsideWithoutSymlinks(projectRoot, absolutePath, {
|
|
104
|
+
allowMissingDescendant: true,
|
|
105
|
+
allowMissingLeaf: true,
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
catch (error) {
|
|
109
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
110
|
+
pushIssue(issues, message);
|
|
111
|
+
findings.push(makeFinding('reference_drift_path_outside_root', 'high', inputPath, message));
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
if (!existsSync(absolutePath)) {
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
let stats;
|
|
118
|
+
try {
|
|
119
|
+
stats = lstatSync(absolutePath);
|
|
120
|
+
}
|
|
121
|
+
catch (error) {
|
|
122
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
123
|
+
pushIssue(issues, `${relativePath}: ${message}`);
|
|
124
|
+
findings.push(makeFinding('reference_drift_unreadable_path', 'high', relativePath, message));
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
if (stats.isDirectory()) {
|
|
128
|
+
collectDocumentsFromDirectory(projectRoot, absolutePath, candidates, findings, issues, policy);
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
if (stats.isFile() && isDocumentPath(relativePath)) {
|
|
132
|
+
addCandidate(candidates, findings, issues, policy, { absolutePath, relativePath });
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
return [...candidates.values()].sort((left, right) => left.relativePath.localeCompare(right.relativePath));
|
|
136
|
+
}
|
|
137
|
+
function trimCommandValue(value) {
|
|
138
|
+
return value
|
|
139
|
+
.replace(/[|)>.,;:]+$/u, '')
|
|
140
|
+
.replace(/\s+$/u, '')
|
|
141
|
+
.slice(0, 240);
|
|
142
|
+
}
|
|
143
|
+
function extractBacktickValues(line) {
|
|
144
|
+
return [...line.matchAll(/`([^`]+)`/gu)].map((match) => match[1] ?? '').filter((value) => value.length > 0);
|
|
145
|
+
}
|
|
146
|
+
function normalizeSchemaTarget(value) {
|
|
147
|
+
return path.basename(value.replace(/[.,;:)]+$/u, ''));
|
|
148
|
+
}
|
|
149
|
+
function looksLikeRepoPath(value) {
|
|
150
|
+
const trimmed = value.trim().replace(/[.,;:)]+$/u, '');
|
|
151
|
+
if (trimmed.length === 0 ||
|
|
152
|
+
trimmed.startsWith('mf ') ||
|
|
153
|
+
trimmed.startsWith('http://') ||
|
|
154
|
+
trimmed.startsWith('https://') ||
|
|
155
|
+
trimmed.includes('<') ||
|
|
156
|
+
trimmed.includes('>') ||
|
|
157
|
+
trimmed.includes('*') ||
|
|
158
|
+
trimmed.includes('...') ||
|
|
159
|
+
trimmed.includes(' --') ||
|
|
160
|
+
trimmed.includes('\n')) {
|
|
161
|
+
return false;
|
|
162
|
+
}
|
|
163
|
+
return (trimmed === 'AGENTS.md' ||
|
|
164
|
+
trimmed === 'README.md' ||
|
|
165
|
+
trimmed === 'CHANGELOG.md' ||
|
|
166
|
+
trimmed === 'REPO_MAP.md' ||
|
|
167
|
+
trimmed === 'LICENSE' ||
|
|
168
|
+
trimmed === 'package.json' ||
|
|
169
|
+
trimmed.startsWith('.mustflow/') ||
|
|
170
|
+
trimmed.startsWith('docs-site/') ||
|
|
171
|
+
trimmed.startsWith('schemas/') ||
|
|
172
|
+
trimmed.startsWith('scripts/') ||
|
|
173
|
+
trimmed.startsWith('src/') ||
|
|
174
|
+
trimmed.startsWith('templates/') ||
|
|
175
|
+
trimmed.startsWith('tests/'));
|
|
176
|
+
}
|
|
177
|
+
function normalizedPathTarget(value) {
|
|
178
|
+
return normalizeRelativePath(value.trim().replace(/[.,;:)]+$/u, ''));
|
|
179
|
+
}
|
|
180
|
+
function extractRawReferences(relativePath, text) {
|
|
181
|
+
const references = [];
|
|
182
|
+
const lines = text.split(/\r?\n/u);
|
|
183
|
+
for (const [index, line] of lines.entries()) {
|
|
184
|
+
const lineNumber = index + 1;
|
|
185
|
+
for (const match of line.matchAll(/\bmf\s+([a-z][a-z0-9-]*)\b[^\n`|)]*/giu)) {
|
|
186
|
+
const command = match[1] ?? '';
|
|
187
|
+
const value = trimCommandValue(match[0] ?? '');
|
|
188
|
+
references.push({
|
|
189
|
+
kind: 'mf_command',
|
|
190
|
+
path: relativePath,
|
|
191
|
+
line: lineNumber,
|
|
192
|
+
value,
|
|
193
|
+
target: command,
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
for (const match of line.matchAll(/\bmf\s+script-pack\s+run\s+([a-z0-9-]+\/[a-z0-9-]+)\b/giu)) {
|
|
197
|
+
const target = match[1] ?? '';
|
|
198
|
+
references.push({
|
|
199
|
+
kind: 'script_pack_ref',
|
|
200
|
+
path: relativePath,
|
|
201
|
+
line: lineNumber,
|
|
202
|
+
value: match[0] ?? target,
|
|
203
|
+
target,
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
for (const match of line.matchAll(/\b(?:[A-Za-z0-9._/-]+\/)?([A-Za-z0-9-]+\.schema\.json)\b/gu)) {
|
|
207
|
+
const value = match[0] ?? '';
|
|
208
|
+
references.push({
|
|
209
|
+
kind: 'schema_file',
|
|
210
|
+
path: relativePath,
|
|
211
|
+
line: lineNumber,
|
|
212
|
+
value,
|
|
213
|
+
target: normalizeSchemaTarget(value),
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
for (const value of extractBacktickValues(line)) {
|
|
217
|
+
if (!looksLikeRepoPath(value)) {
|
|
218
|
+
continue;
|
|
219
|
+
}
|
|
220
|
+
const target = normalizedPathTarget(value);
|
|
221
|
+
references.push({
|
|
222
|
+
kind: 'repo_path',
|
|
223
|
+
path: relativePath,
|
|
224
|
+
line: lineNumber,
|
|
225
|
+
value,
|
|
226
|
+
target,
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
return references;
|
|
231
|
+
}
|
|
232
|
+
function validateReference(projectRoot, commandNames, scriptRefs, schemaFiles, reference) {
|
|
233
|
+
switch (reference.kind) {
|
|
234
|
+
case 'mf_command':
|
|
235
|
+
return commandNames.has(reference.target)
|
|
236
|
+
? { ...reference, status: 'ok', message: `mf command exists: ${reference.target}` }
|
|
237
|
+
: { ...reference, status: 'unknown', message: `Unknown mf command: ${reference.target}` };
|
|
238
|
+
case 'script_pack_ref':
|
|
239
|
+
return scriptRefs.has(reference.target)
|
|
240
|
+
? { ...reference, status: 'ok', message: `script-pack ref exists: ${reference.target}` }
|
|
241
|
+
: { ...reference, status: 'unknown', message: `Unknown script-pack ref: ${reference.target}` };
|
|
242
|
+
case 'schema_file': {
|
|
243
|
+
const schemaPath = path.join(projectRoot, 'schemas', reference.target);
|
|
244
|
+
const exists = schemaFiles.has(reference.target) && existsSync(schemaPath);
|
|
245
|
+
return exists
|
|
246
|
+
? { ...reference, status: 'ok', message: `schema file exists: ${reference.target}` }
|
|
247
|
+
: { ...reference, status: 'unknown', message: `Unknown schema file: ${reference.target}` };
|
|
248
|
+
}
|
|
249
|
+
case 'repo_path': {
|
|
250
|
+
const absolutePath = path.join(projectRoot, ...reference.target.split('/'));
|
|
251
|
+
try {
|
|
252
|
+
ensureInside(projectRoot, absolutePath);
|
|
253
|
+
}
|
|
254
|
+
catch {
|
|
255
|
+
return { ...reference, status: 'missing', message: `Referenced path escapes the mustflow root: ${reference.target}` };
|
|
256
|
+
}
|
|
257
|
+
return existsSync(absolutePath)
|
|
258
|
+
? { ...reference, status: 'ok', message: `repo path exists: ${reference.target}` }
|
|
259
|
+
: { ...reference, status: 'missing', message: `Referenced repo path is missing: ${reference.target}` };
|
|
260
|
+
}
|
|
261
|
+
default:
|
|
262
|
+
return { ...reference, status: 'skipped', message: 'Reference kind is not checked.' };
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
function findingForReference(reference) {
|
|
266
|
+
if (reference.status === 'ok' || reference.status === 'skipped') {
|
|
267
|
+
return null;
|
|
268
|
+
}
|
|
269
|
+
if (reference.kind === 'mf_command') {
|
|
270
|
+
return makeFinding('reference_drift_unknown_command', 'medium', reference.path, reference.message, reference.line);
|
|
271
|
+
}
|
|
272
|
+
if (reference.kind === 'script_pack_ref') {
|
|
273
|
+
return makeFinding('reference_drift_unknown_script_pack', 'medium', reference.path, reference.message, reference.line);
|
|
274
|
+
}
|
|
275
|
+
if (reference.kind === 'schema_file') {
|
|
276
|
+
return makeFinding('reference_drift_unknown_schema', 'medium', reference.path, reference.message, reference.line);
|
|
277
|
+
}
|
|
278
|
+
return makeFinding('reference_drift_missing_path', 'medium', reference.path, reference.message, reference.line);
|
|
279
|
+
}
|
|
280
|
+
function referenceDriftStatus(findings) {
|
|
281
|
+
if (findings.some((finding) => ERROR_CODES.has(finding.code))) {
|
|
282
|
+
return 'error';
|
|
283
|
+
}
|
|
284
|
+
if (findings.some((finding) => ['medium', 'high', 'critical'].includes(finding.severity))) {
|
|
285
|
+
return 'failed';
|
|
286
|
+
}
|
|
287
|
+
return 'passed';
|
|
288
|
+
}
|
|
289
|
+
function summarizeReferences(files, references) {
|
|
290
|
+
const count = (status) => references.filter((reference) => reference.status === status).length;
|
|
291
|
+
return {
|
|
292
|
+
files_checked: files.length,
|
|
293
|
+
references_checked: references.length,
|
|
294
|
+
ok: count('ok'),
|
|
295
|
+
missing: count('missing'),
|
|
296
|
+
unknown: count('unknown'),
|
|
297
|
+
skipped: count('skipped'),
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
function createInputHash(policy, files, references, findings) {
|
|
301
|
+
return sha256Tagged(JSON.stringify({
|
|
302
|
+
policy,
|
|
303
|
+
files: files.map((file) => ({ path: file.path, sha256: file.sha256, reference_count: file.reference_count })),
|
|
304
|
+
references: references.map((reference) => ({
|
|
305
|
+
kind: reference.kind,
|
|
306
|
+
path: reference.path,
|
|
307
|
+
line: reference.line,
|
|
308
|
+
target: reference.target,
|
|
309
|
+
status: reference.status,
|
|
310
|
+
})),
|
|
311
|
+
findings: findings.map((finding) => ({ code: finding.code, path: finding.path, line: finding.line })),
|
|
312
|
+
}));
|
|
313
|
+
}
|
|
314
|
+
export function checkReferenceDrift(projectRoot, options) {
|
|
315
|
+
const root = path.resolve(projectRoot);
|
|
316
|
+
const policy = {
|
|
317
|
+
max_files: options.maxFiles ?? DEFAULT_MAX_FILES,
|
|
318
|
+
max_file_bytes: options.maxFileBytes ?? DEFAULT_MAX_FILE_BYTES,
|
|
319
|
+
default_paths: [...DEFAULT_PATHS],
|
|
320
|
+
path_filters: [...PATH_FILTERS],
|
|
321
|
+
checked_reference_kinds: [...CHECKED_REFERENCE_KINDS],
|
|
322
|
+
};
|
|
323
|
+
const commandNames = new Set(options.commandNames);
|
|
324
|
+
const scriptRefs = new Set(options.scriptRefs);
|
|
325
|
+
const schemaFiles = new Set(options.schemaFiles);
|
|
326
|
+
const findings = [];
|
|
327
|
+
const issues = [];
|
|
328
|
+
const fileEntries = [];
|
|
329
|
+
const references = [];
|
|
330
|
+
const candidates = collectDocumentCandidates(root, options.paths, policy, findings, issues);
|
|
331
|
+
for (const candidate of candidates) {
|
|
332
|
+
let buffer = null;
|
|
333
|
+
try {
|
|
334
|
+
buffer = readFileInsideWithoutSymlinks(root, candidate.absolutePath, { maxBytes: policy.max_file_bytes });
|
|
335
|
+
}
|
|
336
|
+
catch (error) {
|
|
337
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
338
|
+
const code = message.includes('exceeds maximum size')
|
|
339
|
+
? 'reference_drift_file_too_large'
|
|
340
|
+
: 'reference_drift_unreadable_path';
|
|
341
|
+
pushIssue(issues, `${candidate.relativePath}: ${message}`);
|
|
342
|
+
findings.push(makeFinding(code, 'high', candidate.relativePath, message));
|
|
343
|
+
}
|
|
344
|
+
const text = buffer?.toString('utf8') ?? '';
|
|
345
|
+
const rawReferences = buffer ? extractRawReferences(candidate.relativePath, text) : [];
|
|
346
|
+
const validatedReferences = rawReferences.map((reference) => validateReference(root, commandNames, scriptRefs, schemaFiles, reference));
|
|
347
|
+
references.push(...validatedReferences);
|
|
348
|
+
for (const reference of validatedReferences) {
|
|
349
|
+
const finding = findingForReference(reference);
|
|
350
|
+
if (finding) {
|
|
351
|
+
findings.push(finding);
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
fileEntries.push({
|
|
355
|
+
kind: 'document',
|
|
356
|
+
path: candidate.relativePath,
|
|
357
|
+
sha256: buffer ? sha256Tagged(buffer) : null,
|
|
358
|
+
size_bytes: buffer?.byteLength ?? null,
|
|
359
|
+
line_count: buffer ? text.split(/\r?\n/u).length : null,
|
|
360
|
+
reference_count: rawReferences.length,
|
|
361
|
+
});
|
|
362
|
+
}
|
|
363
|
+
const sortedFiles = fileEntries.sort((left, right) => left.path.localeCompare(right.path));
|
|
364
|
+
const sortedReferences = references.sort((left, right) => left.path.localeCompare(right.path) ||
|
|
365
|
+
left.line - right.line ||
|
|
366
|
+
left.kind.localeCompare(right.kind) ||
|
|
367
|
+
left.target.localeCompare(right.target));
|
|
368
|
+
const status = referenceDriftStatus(findings);
|
|
369
|
+
const summary = summarizeReferences(sortedFiles, sortedReferences);
|
|
370
|
+
return {
|
|
371
|
+
schema_version: '1',
|
|
372
|
+
command: 'script-pack',
|
|
373
|
+
pack_id: REFERENCE_DRIFT_PACK_ID,
|
|
374
|
+
script_id: REFERENCE_DRIFT_SCRIPT_ID,
|
|
375
|
+
script_ref: REFERENCE_DRIFT_SCRIPT_REF,
|
|
376
|
+
action: 'check',
|
|
377
|
+
status,
|
|
378
|
+
ok: status === 'passed',
|
|
379
|
+
mustflow_root: root,
|
|
380
|
+
policy,
|
|
381
|
+
input_hash: createInputHash(policy, sortedFiles, sortedReferences, findings),
|
|
382
|
+
files: sortedFiles,
|
|
383
|
+
references: sortedReferences,
|
|
384
|
+
summary,
|
|
385
|
+
findings,
|
|
386
|
+
issues,
|
|
387
|
+
};
|
|
388
|
+
}
|