mustflow 2.85.4 → 2.99.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/dist/cli/commands/script-pack.js +10 -0
- package/dist/cli/i18n/en.js +183 -0
- package/dist/cli/i18n/es.js +183 -0
- package/dist/cli/i18n/fr.js +183 -0
- package/dist/cli/i18n/hi.js +183 -0
- package/dist/cli/i18n/ko.js +183 -0
- package/dist/cli/i18n/zh.js +183 -0
- package/dist/cli/lib/script-pack-registry.js +284 -1
- package/dist/cli/script-packs/code-change-impact.js +6 -0
- package/dist/cli/script-packs/code-import-cycle.js +193 -0
- package/dist/cli/script-packs/docs-link-integrity.js +145 -0
- package/dist/cli/script-packs/repo-approval-gate.js +100 -0
- package/dist/cli/script-packs/repo-git-ignore-audit.js +119 -0
- package/dist/cli/script-packs/repo-manifest-lock-drift.js +122 -0
- package/dist/cli/script-packs/repo-merge-conflict-scan.js +123 -0
- package/dist/cli/script-packs/repo-skill-route-audit.js +86 -0
- package/dist/cli/script-packs/repo-version-source.js +92 -0
- package/dist/cli/script-packs/test-performance-report.js +247 -0
- package/dist/cli/script-packs/test-regression-selector.js +167 -0
- package/dist/core/change-impact.js +23 -51
- package/dist/core/change-surface-classification.js +198 -0
- package/dist/core/docs-link-integrity.js +443 -0
- package/dist/core/import-cycle.js +152 -0
- package/dist/core/public-json-contracts.js +116 -0
- package/dist/core/repo-approval-gate.js +116 -0
- package/dist/core/repo-git-ignore-audit.js +302 -0
- package/dist/core/repo-manifest-lock-drift.js +321 -0
- package/dist/core/repo-merge-conflict-scan.js +335 -0
- package/dist/core/repo-version-source.js +82 -0
- package/dist/core/script-pack-suggestions.js +77 -1
- package/dist/core/skill-route-audit.js +354 -0
- package/dist/core/test-performance-report.js +697 -0
- package/dist/core/test-regression-selector.js +335 -0
- package/package.json +1 -1
- package/schemas/README.md +40 -2
- package/schemas/change-impact-report.schema.json +35 -1
- package/schemas/import-cycle-report.schema.json +157 -0
- package/schemas/link-integrity-report.schema.json +176 -0
- package/schemas/repo-approval-gate-report.schema.json +115 -0
- package/schemas/repo-git-ignore-audit-report.schema.json +201 -0
- package/schemas/repo-manifest-lock-drift-report.schema.json +202 -0
- package/schemas/repo-merge-conflict-scan-report.schema.json +169 -0
- package/schemas/repo-version-source-report.schema.json +127 -0
- package/schemas/skill-route-audit-report.schema.json +144 -0
- package/schemas/test-performance-report.schema.json +319 -0
- package/schemas/test-regression-selector-report.schema.json +187 -0
- package/templates/default/i18n.toml +66 -18
- package/templates/default/locales/en/.mustflow/skills/INDEX.md +45 -8
- package/templates/default/locales/en/.mustflow/skills/api-access-control-review/SKILL.md +48 -27
- package/templates/default/locales/en/.mustflow/skills/api-failure-triage/SKILL.md +270 -0
- package/templates/default/locales/en/.mustflow/skills/auth-flow-triage/SKILL.md +192 -0
- package/templates/default/locales/en/.mustflow/skills/auth-permission-change/SKILL.md +59 -13
- package/templates/default/locales/en/.mustflow/skills/backend-log-evidence-review/SKILL.md +14 -5
- package/templates/default/locales/en/.mustflow/skills/cache-integrity-review/SKILL.md +30 -15
- package/templates/default/locales/en/.mustflow/skills/change-blast-radius-review/SKILL.md +45 -32
- package/templates/default/locales/en/.mustflow/skills/ci-pipeline-triage/SKILL.md +200 -0
- package/templates/default/locales/en/.mustflow/skills/clarifying-question-gate/SKILL.md +87 -13
- package/templates/default/locales/en/.mustflow/skills/docker-runtime-triage/SKILL.md +191 -0
- package/templates/default/locales/en/.mustflow/skills/go-code-change/SKILL.md +18 -13
- package/templates/default/locales/en/.mustflow/skills/line-ending-hygiene/SKILL.md +18 -10
- package/templates/default/locales/en/.mustflow/skills/llm-hallucination-control-review/SKILL.md +4 -1
- package/templates/default/locales/en/.mustflow/skills/motion-system-contract-review/SKILL.md +155 -0
- package/templates/default/locales/en/.mustflow/skills/next-action-menu/SKILL.md +177 -0
- package/templates/default/locales/en/.mustflow/skills/observability-debuggability-review/SKILL.md +15 -7
- package/templates/default/locales/en/.mustflow/skills/payment-integrity-review/SKILL.md +59 -35
- package/templates/default/locales/en/.mustflow/skills/powershell-code-change/SKILL.md +16 -6
- package/templates/default/locales/en/.mustflow/skills/prompt-contract-quality-review/SKILL.md +4 -1
- package/templates/default/locales/en/.mustflow/skills/python-code-change/SKILL.md +19 -10
- package/templates/default/locales/en/.mustflow/skills/rag-pipeline-triage/SKILL.md +206 -0
- package/templates/default/locales/en/.mustflow/skills/routes.toml +54 -0
- package/templates/default/locales/en/.mustflow/skills/rust-code-change/SKILL.md +10 -4
- package/templates/default/locales/en/.mustflow/skills/search-index-integrity-review/SKILL.md +181 -0
- package/templates/default/locales/en/.mustflow/skills/service-boundary-architecture/SKILL.md +37 -23
- package/templates/default/locales/en/.mustflow/skills/test-suite-performance-review/SKILL.md +9 -0
- package/templates/default/locales/en/.mustflow/skills/typescript-code-change/SKILL.md +14 -9
- package/templates/default/locales/en/.mustflow/skills/vector-search-integrity-review/SKILL.md +209 -0
- package/templates/default/locales/en/.mustflow/skills/version-freshness-check/SKILL.md +16 -14
- package/templates/default/manifest.toml +64 -1
|
@@ -0,0 +1,335 @@
|
|
|
1
|
+
import { spawnSync } from 'node:child_process';
|
|
2
|
+
import { createHash } from 'node:crypto';
|
|
3
|
+
import { existsSync } from 'node:fs';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { classifyChangeSurface, normalizeChangePath, selectorFallbackForChangedPath, statusFromGitNameStatus, } from './change-surface-classification.js';
|
|
6
|
+
import { inspectDependencyGraph } from './dependency-graph.js';
|
|
7
|
+
export const TEST_REGRESSION_SELECTOR_PACK_ID = 'test';
|
|
8
|
+
export const TEST_REGRESSION_SELECTOR_SCRIPT_ID = 'regression-selector';
|
|
9
|
+
export const TEST_REGRESSION_SELECTOR_SCRIPT_REF = `${TEST_REGRESSION_SELECTOR_PACK_ID}/${TEST_REGRESSION_SELECTOR_SCRIPT_ID}`;
|
|
10
|
+
const DEFAULT_BASE_REF = 'HEAD';
|
|
11
|
+
const DEFAULT_MAX_FILES = 200;
|
|
12
|
+
const DEFAULT_MAX_TESTS = 100;
|
|
13
|
+
function normalizeRelativePath(value) {
|
|
14
|
+
return normalizeChangePath(value);
|
|
15
|
+
}
|
|
16
|
+
function sha256Tagged(value) {
|
|
17
|
+
return `sha256:${createHash('sha256').update(value).digest('hex')}`;
|
|
18
|
+
}
|
|
19
|
+
function runGit(root, args) {
|
|
20
|
+
const result = spawnSync('git', [...args], {
|
|
21
|
+
cwd: root,
|
|
22
|
+
encoding: 'utf8',
|
|
23
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
24
|
+
windowsHide: true,
|
|
25
|
+
maxBuffer: 16 * 1024 * 1024,
|
|
26
|
+
});
|
|
27
|
+
return {
|
|
28
|
+
ok: result.status === 0,
|
|
29
|
+
stdout: result.stdout ?? '',
|
|
30
|
+
stderr: result.stderr ?? '',
|
|
31
|
+
status: result.status,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
function isInsideGitWorktree(root) {
|
|
35
|
+
const result = runGit(root, ['rev-parse', '--is-inside-work-tree']);
|
|
36
|
+
return result.ok && result.stdout.trim() === 'true';
|
|
37
|
+
}
|
|
38
|
+
function makeFinding(code, severity, pathValue, message) {
|
|
39
|
+
return { code, severity, path: pathValue, message };
|
|
40
|
+
}
|
|
41
|
+
function statusFromCode(code) {
|
|
42
|
+
return statusFromGitNameStatus(code);
|
|
43
|
+
}
|
|
44
|
+
function surfaceForPath(relativePath) {
|
|
45
|
+
return classifyChangeSurface(relativePath, { includeDocsSite: true });
|
|
46
|
+
}
|
|
47
|
+
function parseNameStatus(stdout) {
|
|
48
|
+
const files = [];
|
|
49
|
+
for (const line of stdout.split(/\r?\n/u)) {
|
|
50
|
+
if (!line.trim()) {
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
const [statusCode = 'M', firstPath = '', secondPath = ''] = line.split('\t');
|
|
54
|
+
const currentPath = normalizeRelativePath(secondPath || firstPath);
|
|
55
|
+
const previousPath = secondPath ? normalizeRelativePath(firstPath) : null;
|
|
56
|
+
files.push({
|
|
57
|
+
path: currentPath,
|
|
58
|
+
previous_path: previousPath,
|
|
59
|
+
status: statusFromCode(statusCode),
|
|
60
|
+
surface: surfaceForPath(currentPath),
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
return files;
|
|
64
|
+
}
|
|
65
|
+
function addUntrackedFiles(root, paths, files) {
|
|
66
|
+
const untracked = runGit(root, ['ls-files', '--others', '--exclude-standard', '--', ...paths]);
|
|
67
|
+
if (!untracked.ok) {
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
const known = new Set(files.map((file) => file.path));
|
|
71
|
+
for (const line of untracked.stdout.split(/\r?\n/u)) {
|
|
72
|
+
const relativePath = normalizeRelativePath(line.trim());
|
|
73
|
+
if (!relativePath || relativePath === '.' || known.has(relativePath)) {
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
files.push({
|
|
77
|
+
path: relativePath,
|
|
78
|
+
previous_path: null,
|
|
79
|
+
status: 'untracked',
|
|
80
|
+
surface: surfaceForPath(relativePath),
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
function collectChangedFiles(root, policy, findings, issues) {
|
|
85
|
+
if (!isInsideGitWorktree(root)) {
|
|
86
|
+
const message = 'Git worktree is unavailable; regression-selector cannot inspect changed files.';
|
|
87
|
+
findings.push(makeFinding('test_regression_selector_git_unavailable', 'low', '.', message));
|
|
88
|
+
issues.push(message);
|
|
89
|
+
return [];
|
|
90
|
+
}
|
|
91
|
+
const diffArgs = policy.head_ref
|
|
92
|
+
? ['diff', '--name-status', '--diff-filter=ACMRTD', policy.base_ref, policy.head_ref, '--', ...policy.path_filters]
|
|
93
|
+
: ['diff', '--name-status', '--diff-filter=ACMRTD', policy.base_ref, '--', ...policy.path_filters];
|
|
94
|
+
const diff = runGit(root, diffArgs);
|
|
95
|
+
if (!diff.ok) {
|
|
96
|
+
const detail = diff.stderr.trim() || diff.stdout.trim() || `git diff exited with ${diff.status}`;
|
|
97
|
+
findings.push(makeFinding('test_regression_selector_invalid_ref', 'high', '.', detail));
|
|
98
|
+
issues.push(detail);
|
|
99
|
+
return [];
|
|
100
|
+
}
|
|
101
|
+
const files = parseNameStatus(diff.stdout);
|
|
102
|
+
if (!policy.head_ref) {
|
|
103
|
+
addUntrackedFiles(root, policy.path_filters, files);
|
|
104
|
+
}
|
|
105
|
+
files.sort((left, right) => left.path.localeCompare(right.path));
|
|
106
|
+
if (files.length > policy.max_files) {
|
|
107
|
+
const message = `Regression selector matched ${files.length} files; max_files is ${policy.max_files}.`;
|
|
108
|
+
findings.push(makeFinding('test_regression_selector_max_files_exceeded', 'high', '.', message));
|
|
109
|
+
issues.push(message);
|
|
110
|
+
}
|
|
111
|
+
return files.slice(0, policy.max_files);
|
|
112
|
+
}
|
|
113
|
+
function siblingBase(relativePath) {
|
|
114
|
+
const normalized = normalizeRelativePath(relativePath);
|
|
115
|
+
const directory = path.posix.dirname(normalized);
|
|
116
|
+
const parsed = path.posix.parse(normalized);
|
|
117
|
+
return directory === '.' ? parsed.name : `${directory}/${parsed.name}`;
|
|
118
|
+
}
|
|
119
|
+
function testCandidatesFor(relativePath) {
|
|
120
|
+
const base = siblingBase(relativePath);
|
|
121
|
+
const name = path.posix.basename(base);
|
|
122
|
+
return [
|
|
123
|
+
`${base}.test.ts`,
|
|
124
|
+
`${base}.test.js`,
|
|
125
|
+
`${base}.spec.ts`,
|
|
126
|
+
`${base}.spec.js`,
|
|
127
|
+
`tests/${name}.test.js`,
|
|
128
|
+
`tests/${name}.test.ts`,
|
|
129
|
+
`tests/cli/${name}.test.js`,
|
|
130
|
+
];
|
|
131
|
+
}
|
|
132
|
+
function addTestCandidate(root, candidates, pathValue, reason, sourcePath, confidence) {
|
|
133
|
+
const normalized = normalizeRelativePath(pathValue);
|
|
134
|
+
const existing = candidates.get(normalized);
|
|
135
|
+
if (existing) {
|
|
136
|
+
const reasons = [...new Set([...existing.reasons, reason])];
|
|
137
|
+
const sourcePaths = [...new Set([...existing.source_paths, sourcePath])];
|
|
138
|
+
const primaryReason = confidence > existing.confidence ? reason : existing.reason;
|
|
139
|
+
const primarySourcePath = confidence > existing.confidence ? sourcePath : existing.source_path;
|
|
140
|
+
candidates.set(normalized, {
|
|
141
|
+
...existing,
|
|
142
|
+
reason: primaryReason,
|
|
143
|
+
reasons,
|
|
144
|
+
source_path: primarySourcePath,
|
|
145
|
+
source_paths: sourcePaths,
|
|
146
|
+
confidence: Math.max(existing.confidence, confidence),
|
|
147
|
+
});
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
candidates.set(normalized, {
|
|
151
|
+
path: normalized,
|
|
152
|
+
exists: existsSync(path.join(root, ...normalized.split('/'))),
|
|
153
|
+
reason,
|
|
154
|
+
reasons: [reason],
|
|
155
|
+
source_path: sourcePath,
|
|
156
|
+
source_paths: [sourcePath],
|
|
157
|
+
confidence,
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
function addChangedFileCandidates(root, changedFiles, candidates) {
|
|
161
|
+
for (const changedFile of changedFiles) {
|
|
162
|
+
if (changedFile.surface === 'test' && changedFile.status !== 'deleted') {
|
|
163
|
+
addTestCandidate(root, candidates, changedFile.path, 'changed_test', changedFile.path, 1);
|
|
164
|
+
}
|
|
165
|
+
if (changedFile.surface === 'source' && changedFile.status !== 'deleted') {
|
|
166
|
+
for (const candidatePath of testCandidatesFor(changedFile.path)) {
|
|
167
|
+
addTestCandidate(root, candidates, candidatePath, 'sibling_test', changedFile.path, 0.7);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
function addImporterCandidates(root, changedFiles, candidates, issues) {
|
|
173
|
+
const sourcePaths = changedFiles
|
|
174
|
+
.filter((file) => file.surface === 'source' && file.status !== 'deleted')
|
|
175
|
+
.map((file) => file.path);
|
|
176
|
+
if (sourcePaths.length === 0) {
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
const dependencyReport = inspectDependencyGraph(root, {
|
|
180
|
+
paths: sourcePaths,
|
|
181
|
+
maxDepth: 1,
|
|
182
|
+
maxFiles: Math.max(sourcePaths.length, 50),
|
|
183
|
+
maxFileBytes: 256 * 1024,
|
|
184
|
+
maxNodes: 200,
|
|
185
|
+
maxEdges: 300,
|
|
186
|
+
});
|
|
187
|
+
if (!dependencyReport.ok) {
|
|
188
|
+
for (const issue of dependencyReport.issues) {
|
|
189
|
+
issues.push(`dependency-graph: ${issue}`);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
const changedPathSet = new Set(sourcePaths);
|
|
193
|
+
for (const edge of dependencyReport.edges) {
|
|
194
|
+
if (!changedPathSet.has(edge.target_path) || changedPathSet.has(edge.source_path)) {
|
|
195
|
+
continue;
|
|
196
|
+
}
|
|
197
|
+
for (const candidatePath of testCandidatesFor(edge.source_path)) {
|
|
198
|
+
addTestCandidate(root, candidates, candidatePath, 'importer_sibling_test', edge.target_path, 0.6);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
function fallbackForChangedFile(changedFile) {
|
|
203
|
+
return selectorFallbackForChangedPath(changedFile);
|
|
204
|
+
}
|
|
205
|
+
function chooseRecommendedIntent(selectedTests, fallbacks, changedFiles) {
|
|
206
|
+
if (fallbacks.some((fallback) => fallback.recommended_intent === 'test')) {
|
|
207
|
+
return 'test';
|
|
208
|
+
}
|
|
209
|
+
if (fallbacks.some((fallback) => fallback.recommended_intent === 'test_release')) {
|
|
210
|
+
return 'test_release';
|
|
211
|
+
}
|
|
212
|
+
if (fallbacks.length > 0) {
|
|
213
|
+
return 'test_related';
|
|
214
|
+
}
|
|
215
|
+
if (selectedTests.length > 0 || changedFiles.some((file) => file.surface === 'source' || file.surface === 'test')) {
|
|
216
|
+
return 'test_related_cached';
|
|
217
|
+
}
|
|
218
|
+
return 'changes_status';
|
|
219
|
+
}
|
|
220
|
+
function confidenceFor(selectedTests, fallbacks, changedFiles) {
|
|
221
|
+
if (fallbacks.length > 0) {
|
|
222
|
+
return 'low';
|
|
223
|
+
}
|
|
224
|
+
if (selectedTests.length === 0 || changedFiles.length === 0) {
|
|
225
|
+
return 'low';
|
|
226
|
+
}
|
|
227
|
+
if (changedFiles.every((file) => file.surface === 'test')) {
|
|
228
|
+
return 'high';
|
|
229
|
+
}
|
|
230
|
+
return 'medium';
|
|
231
|
+
}
|
|
232
|
+
function selectionStatus(selectedTests, fallbacks, changedFiles) {
|
|
233
|
+
if (fallbacks.length > 0) {
|
|
234
|
+
return 'fallback';
|
|
235
|
+
}
|
|
236
|
+
if (changedFiles.length === 0 || selectedTests.length === 0) {
|
|
237
|
+
return 'empty';
|
|
238
|
+
}
|
|
239
|
+
return 'selected';
|
|
240
|
+
}
|
|
241
|
+
function createRunHint(selectedTests, recommendedIntent) {
|
|
242
|
+
if (recommendedIntent === 'test_related_cached' && selectedTests.length > 0) {
|
|
243
|
+
return `MUSTFLOW_TEST_CHANGED_FILES=${selectedTests.map((test) => test.path).join(',')} mf run test_related_cached`;
|
|
244
|
+
}
|
|
245
|
+
if (recommendedIntent === 'test_related' || recommendedIntent === 'test_release' || recommendedIntent === 'test') {
|
|
246
|
+
return `mf run ${recommendedIntent}`;
|
|
247
|
+
}
|
|
248
|
+
return null;
|
|
249
|
+
}
|
|
250
|
+
export function createTestRegressionSelectorReport(projectRoot, options = {}) {
|
|
251
|
+
const root = path.resolve(projectRoot);
|
|
252
|
+
const policy = {
|
|
253
|
+
base_ref: options.baseRef ?? DEFAULT_BASE_REF,
|
|
254
|
+
head_ref: options.headRef ?? null,
|
|
255
|
+
compare_worktree: options.headRef === undefined || options.headRef === null,
|
|
256
|
+
max_files: options.maxFiles ?? DEFAULT_MAX_FILES,
|
|
257
|
+
max_tests: options.maxTests ?? DEFAULT_MAX_TESTS,
|
|
258
|
+
path_filters: (options.paths ?? []).map(normalizeRelativePath),
|
|
259
|
+
};
|
|
260
|
+
const findings = [];
|
|
261
|
+
const issues = [];
|
|
262
|
+
const changedFiles = collectChangedFiles(root, policy, findings, issues);
|
|
263
|
+
const candidates = new Map();
|
|
264
|
+
addChangedFileCandidates(root, changedFiles, candidates);
|
|
265
|
+
addImporterCandidates(root, changedFiles, candidates, issues);
|
|
266
|
+
const fallbackMap = new Map();
|
|
267
|
+
for (const changedFile of changedFiles) {
|
|
268
|
+
const fallback = fallbackForChangedFile(changedFile);
|
|
269
|
+
if (fallback) {
|
|
270
|
+
fallbackMap.set(`${fallback.reason}:${fallback.path}`, fallback);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
if (findings.some((finding) => finding.code === 'test_regression_selector_max_files_exceeded')) {
|
|
274
|
+
fallbackMap.set('fallback_truncated:.', {
|
|
275
|
+
reason: 'fallback_truncated',
|
|
276
|
+
path: '.',
|
|
277
|
+
message: 'Changed-file input was truncated by max_files.',
|
|
278
|
+
recommended_intent: 'test',
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
if (changedFiles.length === 0) {
|
|
282
|
+
fallbackMap.set('no_changed_files:.', {
|
|
283
|
+
reason: 'no_changed_files',
|
|
284
|
+
path: '.',
|
|
285
|
+
message: 'No changed files were detected for regression selection.',
|
|
286
|
+
recommended_intent: 'changes_status',
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
const sortedCandidates = [...candidates.values()].sort((left, right) => Number(right.exists) - Number(left.exists) || right.confidence - left.confidence || left.path.localeCompare(right.path));
|
|
290
|
+
const selectedTests = sortedCandidates.filter((candidate) => candidate.exists).slice(0, policy.max_tests);
|
|
291
|
+
const missingCandidates = sortedCandidates.filter((candidate) => !candidate.exists).slice(0, policy.max_tests);
|
|
292
|
+
const truncated = sortedCandidates.filter((candidate) => candidate.exists).length > selectedTests.length;
|
|
293
|
+
if (truncated) {
|
|
294
|
+
const message = `Regression selector found more than ${policy.max_tests} existing test candidates.`;
|
|
295
|
+
findings.push(makeFinding('test_regression_selector_max_tests_exceeded', 'medium', '.', message));
|
|
296
|
+
issues.push(message);
|
|
297
|
+
}
|
|
298
|
+
const fallbacks = [...fallbackMap.values()].sort((left, right) => left.recommended_intent.localeCompare(right.recommended_intent) || left.path.localeCompare(right.path));
|
|
299
|
+
const recommendedIntent = chooseRecommendedIntent(selectedTests, fallbacks, changedFiles);
|
|
300
|
+
const selectorStatus = selectionStatus(selectedTests, fallbacks, changedFiles);
|
|
301
|
+
const status = findings.some((finding) => finding.severity === 'high' || finding.severity === 'critical')
|
|
302
|
+
? 'failed'
|
|
303
|
+
: 'passed';
|
|
304
|
+
const summary = {
|
|
305
|
+
changed_file_count: changedFiles.length,
|
|
306
|
+
selected_test_count: selectedTests.length,
|
|
307
|
+
missing_candidate_count: missingCandidates.length,
|
|
308
|
+
fallback_count: fallbacks.length,
|
|
309
|
+
selection_status: selectorStatus,
|
|
310
|
+
confidence: confidenceFor(selectedTests, fallbacks, changedFiles),
|
|
311
|
+
recommended_intent: recommendedIntent,
|
|
312
|
+
};
|
|
313
|
+
return {
|
|
314
|
+
schema_version: '1',
|
|
315
|
+
command: 'script-pack',
|
|
316
|
+
pack_id: TEST_REGRESSION_SELECTOR_PACK_ID,
|
|
317
|
+
script_id: TEST_REGRESSION_SELECTOR_SCRIPT_ID,
|
|
318
|
+
script_ref: TEST_REGRESSION_SELECTOR_SCRIPT_REF,
|
|
319
|
+
action: 'select',
|
|
320
|
+
status,
|
|
321
|
+
ok: status === 'passed',
|
|
322
|
+
mustflow_root: root,
|
|
323
|
+
policy,
|
|
324
|
+
input_hash: sha256Tagged(JSON.stringify({ policy, changedFiles, selectedTests, fallbacks })),
|
|
325
|
+
summary,
|
|
326
|
+
changed_files: changedFiles,
|
|
327
|
+
selected_tests: selectedTests,
|
|
328
|
+
missing_candidates: missingCandidates,
|
|
329
|
+
fallbacks,
|
|
330
|
+
run_hint: createRunHint(selectedTests, recommendedIntent),
|
|
331
|
+
truncated,
|
|
332
|
+
findings,
|
|
333
|
+
issues,
|
|
334
|
+
};
|
|
335
|
+
}
|
package/package.json
CHANGED
package/schemas/README.md
CHANGED
|
@@ -73,10 +73,15 @@ Current schemas:
|
|
|
73
73
|
`mf script-pack run code/dependency-graph scan <path...> --json`, containing bounded relative
|
|
74
74
|
TypeScript and JavaScript import graph nodes, edges, cycle hints, target flags, depth, importer
|
|
75
75
|
counts, policy limits, and stable input-limit finding codes
|
|
76
|
+
- `import-cycle-report.schema.json`: output of
|
|
77
|
+
`mf script-pack run code/import-cycle check <path...> --json`, containing bounded relative
|
|
78
|
+
TypeScript and JavaScript import cycles with exact cycle paths, import line evidence, policy
|
|
79
|
+
limits, and stable cycle or input-limit finding codes
|
|
76
80
|
- `change-impact-report.schema.json`: output of
|
|
77
81
|
`mf script-pack run code/change-impact analyze [path...] --json`, containing git-diff changed
|
|
78
|
-
files, surface classifications, bounded impact candidates,
|
|
79
|
-
verification intent
|
|
82
|
+
files, surface classifications, bounded impact candidates, script-pack hints with related
|
|
83
|
+
verification intent and selected-test fallback evidence, verification intent hints, policy limits,
|
|
84
|
+
and stable git or input-limit finding codes
|
|
80
85
|
- `code-symbol-read-report.schema.json`: output of
|
|
81
86
|
`mf script-pack run code/symbol-read read <path> --start-line <line> --json`, containing a
|
|
82
87
|
focused source snippet selected by source anchor, outline symbol line, or explicit line range with
|
|
@@ -95,6 +100,18 @@ Current schemas:
|
|
|
95
100
|
`mf script-pack run docs/reference-drift check [path...] --json`, containing checked
|
|
96
101
|
documentation references to `mf` commands, script-pack refs, schema files, repository paths,
|
|
97
102
|
and stable stale-reference finding codes
|
|
103
|
+
- `link-integrity-report.schema.json`: output of
|
|
104
|
+
`mf script-pack run docs/link-integrity check [path...] --json`, containing checked Markdown
|
|
105
|
+
and MDX inline links, local file and anchor resolution status, skipped external URL metadata,
|
|
106
|
+
and stable missing-link finding codes
|
|
107
|
+
- `test-performance-report.schema.json`: output of
|
|
108
|
+
`mf script-pack run test/performance-report summarize --json`, containing retained run
|
|
109
|
+
performance evidence, slow intent summaries, timeout-pressure findings, selected-test fallback
|
|
110
|
+
signals, and phase bottleneck summaries from `.mustflow/state`
|
|
111
|
+
- `test-regression-selector-report.schema.json`: output of
|
|
112
|
+
`mf script-pack run test/regression-selector select --json`, containing changed files, likely
|
|
113
|
+
regression-test candidates with selection evidence, conservative fallback reasons, confidence,
|
|
114
|
+
and recommended verification intent hints
|
|
98
115
|
- `config-chain-report.schema.json`: output of
|
|
99
116
|
`mf script-pack run repo/config-chain inspect <path...> --json`, containing nearby package,
|
|
100
117
|
TypeScript, ESLint, Prettier, Vite, Vitest, Tailwind, Jest, Playwright, and mustflow config files
|
|
@@ -114,6 +131,27 @@ Current schemas:
|
|
|
114
131
|
- `generated-boundary-report.schema.json`: output of
|
|
115
132
|
`mf script-pack run repo/generated-boundary check <path...> --json`, containing candidate path
|
|
116
133
|
classifications for generated, ignored, protected, vendor, and cache boundaries before or after edits
|
|
134
|
+
- `repo-merge-conflict-scan-report.schema.json`: output of
|
|
135
|
+
`mf script-pack run repo/merge-conflict-scan check [path...] --json`, containing checked files,
|
|
136
|
+
redaction-safe Git merge conflict marker locations, marker summaries, and stable conflict-marker
|
|
137
|
+
finding codes
|
|
138
|
+
- `repo-git-ignore-audit-report.schema.json`: output of
|
|
139
|
+
`mf script-pack run repo/git-ignore-audit audit [path...] --json`, containing Git ignore source
|
|
140
|
+
metadata, explicit path visibility status, ignore-rule evidence, tracked-path caveats, and stable
|
|
141
|
+
ignored-path finding codes without reading ignored file content
|
|
142
|
+
- `repo-manifest-lock-drift-report.schema.json`: output of
|
|
143
|
+
`mf script-pack run repo/manifest-lock-drift check [path...] --json`, containing manifest-lock
|
|
144
|
+
metadata, locked file existence, hash comparison status, and stable drift finding codes without
|
|
145
|
+
rewriting `.mustflow/config/manifest.lock.toml`
|
|
146
|
+
- `skill-route-audit-report.schema.json`: output of
|
|
147
|
+
`mf script-pack run repo/skill-route-audit audit --json`, containing source skill, route metadata,
|
|
148
|
+
skill index, default template skill copy, manifest profile, and i18n metadata drift findings
|
|
149
|
+
- `repo-version-source-report.schema.json`: output of
|
|
150
|
+
`mf script-pack run repo/version-source inspect --json`, containing detected version sources,
|
|
151
|
+
release-versioning preference status, source authority counts, and missing-source findings
|
|
152
|
+
- `repo-approval-gate-report.schema.json`: output of
|
|
153
|
+
`mf script-pack run repo/approval-gate check --action <type> --json`, containing approval policy
|
|
154
|
+
decisions, required-action findings, and unreadable policy issues
|
|
117
155
|
- `related-files-report.schema.json`: output of
|
|
118
156
|
`mf script-pack run repo/related-files map <path...> --json`, containing conservative related-file
|
|
119
157
|
candidates from direct imports, importers, same-basename siblings, parent configuration files, and
|
|
@@ -49,6 +49,32 @@
|
|
|
49
49
|
"surface": {
|
|
50
50
|
"enum": ["source", "test", "docs", "schema", "config", "package", "template", "workflow", "i18n", "unknown"]
|
|
51
51
|
},
|
|
52
|
+
"selectorReason": {
|
|
53
|
+
"enum": [
|
|
54
|
+
"changed_test",
|
|
55
|
+
"sibling_test",
|
|
56
|
+
"importer_sibling_test",
|
|
57
|
+
"fallback_lockfile",
|
|
58
|
+
"fallback_package_metadata",
|
|
59
|
+
"fallback_template",
|
|
60
|
+
"fallback_compiler_or_runner_config",
|
|
61
|
+
"fallback_shared_test_fixture",
|
|
62
|
+
"fallback_migration_or_database",
|
|
63
|
+
"fallback_generated_contract",
|
|
64
|
+
"fallback_ci_workflow",
|
|
65
|
+
"fallback_mustflow_workflow",
|
|
66
|
+
"fallback_deleted",
|
|
67
|
+
"fallback_renamed",
|
|
68
|
+
"fallback_type_changed",
|
|
69
|
+
"fallback_package_or_template",
|
|
70
|
+
"fallback_schema",
|
|
71
|
+
"fallback_workflow_or_config",
|
|
72
|
+
"fallback_deleted_or_renamed",
|
|
73
|
+
"fallback_unknown",
|
|
74
|
+
"fallback_truncated",
|
|
75
|
+
"no_changed_files"
|
|
76
|
+
]
|
|
77
|
+
},
|
|
52
78
|
"policy": {
|
|
53
79
|
"type": "object",
|
|
54
80
|
"additionalProperties": false,
|
|
@@ -115,7 +141,15 @@
|
|
|
115
141
|
"script_ref": { "type": "string" },
|
|
116
142
|
"command": { "type": "string" },
|
|
117
143
|
"reason": { "type": "string" },
|
|
118
|
-
"confidence": { "type": "number", "minimum": 0, "maximum": 1 }
|
|
144
|
+
"confidence": { "type": "number", "minimum": 0, "maximum": 1 },
|
|
145
|
+
"related_intents": {
|
|
146
|
+
"type": "array",
|
|
147
|
+
"items": { "type": "string" }
|
|
148
|
+
},
|
|
149
|
+
"expected_fallback_reasons": {
|
|
150
|
+
"type": "array",
|
|
151
|
+
"items": { "$ref": "#/$defs/selectorReason" }
|
|
152
|
+
}
|
|
119
153
|
}
|
|
120
154
|
},
|
|
121
155
|
"verificationHint": {
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://mustflow.github.io/schemas/import-cycle-report.schema.json",
|
|
4
|
+
"title": "mustflow import-cycle report",
|
|
5
|
+
"type": "object",
|
|
6
|
+
"additionalProperties": false,
|
|
7
|
+
"required": [
|
|
8
|
+
"schema_version",
|
|
9
|
+
"command",
|
|
10
|
+
"pack_id",
|
|
11
|
+
"script_id",
|
|
12
|
+
"script_ref",
|
|
13
|
+
"action",
|
|
14
|
+
"status",
|
|
15
|
+
"ok",
|
|
16
|
+
"mustflow_root",
|
|
17
|
+
"policy",
|
|
18
|
+
"input_hash",
|
|
19
|
+
"targets",
|
|
20
|
+
"graph",
|
|
21
|
+
"cycles",
|
|
22
|
+
"truncated",
|
|
23
|
+
"findings",
|
|
24
|
+
"issues"
|
|
25
|
+
],
|
|
26
|
+
"properties": {
|
|
27
|
+
"schema_version": { "const": "1" },
|
|
28
|
+
"command": { "const": "script-pack" },
|
|
29
|
+
"pack_id": { "const": "code" },
|
|
30
|
+
"script_id": { "const": "import-cycle" },
|
|
31
|
+
"script_ref": { "const": "code/import-cycle" },
|
|
32
|
+
"action": { "const": "check" },
|
|
33
|
+
"status": { "enum": ["passed", "failed", "error"] },
|
|
34
|
+
"ok": { "type": "boolean" },
|
|
35
|
+
"mustflow_root": { "type": "string" },
|
|
36
|
+
"policy": { "$ref": "#/$defs/policy" },
|
|
37
|
+
"input_hash": { "$ref": "#/$defs/sha256" },
|
|
38
|
+
"targets": { "type": "array", "items": { "$ref": "#/$defs/target" } },
|
|
39
|
+
"graph": { "$ref": "#/$defs/graph" },
|
|
40
|
+
"cycles": { "type": "array", "items": { "$ref": "#/$defs/cycle" } },
|
|
41
|
+
"truncated": { "type": "boolean" },
|
|
42
|
+
"findings": { "type": "array", "items": { "$ref": "#/$defs/finding" } },
|
|
43
|
+
"issues": { "type": "array", "items": { "type": "string" } }
|
|
44
|
+
},
|
|
45
|
+
"$defs": {
|
|
46
|
+
"sha256": { "type": "string", "pattern": "^sha256:[a-f0-9]{64}$" },
|
|
47
|
+
"stringArray": { "type": "array", "items": { "type": "string" } },
|
|
48
|
+
"language": {
|
|
49
|
+
"enum": [
|
|
50
|
+
"typescript",
|
|
51
|
+
"tsx",
|
|
52
|
+
"javascript",
|
|
53
|
+
"jsx",
|
|
54
|
+
"javascript-module",
|
|
55
|
+
"javascript-commonjs",
|
|
56
|
+
"json",
|
|
57
|
+
"other"
|
|
58
|
+
]
|
|
59
|
+
},
|
|
60
|
+
"targetKind": { "enum": ["file", "directory", "missing", "other", "unknown"] },
|
|
61
|
+
"edgeKind": { "enum": ["static_import", "static_export", "dynamic_import", "require"] },
|
|
62
|
+
"policy": {
|
|
63
|
+
"type": "object",
|
|
64
|
+
"additionalProperties": false,
|
|
65
|
+
"required": [
|
|
66
|
+
"max_file_bytes",
|
|
67
|
+
"max_files",
|
|
68
|
+
"max_depth",
|
|
69
|
+
"max_nodes",
|
|
70
|
+
"max_edges",
|
|
71
|
+
"extensions",
|
|
72
|
+
"ignored_directories",
|
|
73
|
+
"max_cycles"
|
|
74
|
+
],
|
|
75
|
+
"properties": {
|
|
76
|
+
"max_file_bytes": { "type": "integer", "minimum": 1 },
|
|
77
|
+
"max_files": { "type": "integer", "minimum": 1 },
|
|
78
|
+
"max_depth": { "type": "integer", "minimum": 1 },
|
|
79
|
+
"max_nodes": { "type": "integer", "minimum": 1 },
|
|
80
|
+
"max_edges": { "type": "integer", "minimum": 1 },
|
|
81
|
+
"extensions": { "$ref": "#/$defs/stringArray" },
|
|
82
|
+
"ignored_directories": { "$ref": "#/$defs/stringArray" },
|
|
83
|
+
"max_cycles": { "type": "integer", "minimum": 1 }
|
|
84
|
+
}
|
|
85
|
+
},
|
|
86
|
+
"target": {
|
|
87
|
+
"type": "object",
|
|
88
|
+
"additionalProperties": false,
|
|
89
|
+
"required": ["input", "path", "exists", "kind", "language"],
|
|
90
|
+
"properties": {
|
|
91
|
+
"input": { "type": "string" },
|
|
92
|
+
"path": { "type": "string" },
|
|
93
|
+
"exists": { "type": ["boolean", "null"] },
|
|
94
|
+
"kind": { "$ref": "#/$defs/targetKind" },
|
|
95
|
+
"language": { "$ref": "#/$defs/language" }
|
|
96
|
+
}
|
|
97
|
+
},
|
|
98
|
+
"graph": {
|
|
99
|
+
"type": "object",
|
|
100
|
+
"additionalProperties": false,
|
|
101
|
+
"required": ["script_ref", "status", "node_count", "edge_count", "cycle_hint_count", "truncated"],
|
|
102
|
+
"properties": {
|
|
103
|
+
"script_ref": { "const": "code/dependency-graph" },
|
|
104
|
+
"status": { "enum": ["passed", "failed", "error"] },
|
|
105
|
+
"node_count": { "type": "integer", "minimum": 0 },
|
|
106
|
+
"edge_count": { "type": "integer", "minimum": 0 },
|
|
107
|
+
"cycle_hint_count": { "type": "integer", "minimum": 0 },
|
|
108
|
+
"truncated": { "type": "boolean" }
|
|
109
|
+
}
|
|
110
|
+
},
|
|
111
|
+
"edge": {
|
|
112
|
+
"type": "object",
|
|
113
|
+
"additionalProperties": false,
|
|
114
|
+
"required": ["source_path", "target_path", "specifier", "line", "kind"],
|
|
115
|
+
"properties": {
|
|
116
|
+
"source_path": { "type": "string" },
|
|
117
|
+
"target_path": { "type": "string" },
|
|
118
|
+
"specifier": { "type": "string" },
|
|
119
|
+
"line": { "type": "integer", "minimum": 1 },
|
|
120
|
+
"kind": { "$ref": "#/$defs/edgeKind" }
|
|
121
|
+
}
|
|
122
|
+
},
|
|
123
|
+
"cycle": {
|
|
124
|
+
"type": "object",
|
|
125
|
+
"additionalProperties": false,
|
|
126
|
+
"required": ["cycle_id", "path_count", "paths", "edges"],
|
|
127
|
+
"properties": {
|
|
128
|
+
"cycle_id": { "type": "string", "pattern": "^cycle:[a-f0-9]{12}$" },
|
|
129
|
+
"path_count": { "type": "integer", "minimum": 0 },
|
|
130
|
+
"paths": { "$ref": "#/$defs/stringArray" },
|
|
131
|
+
"edges": { "type": "array", "items": { "$ref": "#/$defs/edge" } }
|
|
132
|
+
}
|
|
133
|
+
},
|
|
134
|
+
"finding": {
|
|
135
|
+
"type": "object",
|
|
136
|
+
"additionalProperties": false,
|
|
137
|
+
"required": ["code", "severity", "message", "path"],
|
|
138
|
+
"properties": {
|
|
139
|
+
"code": {
|
|
140
|
+
"enum": [
|
|
141
|
+
"dependency_graph_path_outside_root",
|
|
142
|
+
"dependency_graph_unreadable_path",
|
|
143
|
+
"dependency_graph_max_files_exceeded",
|
|
144
|
+
"dependency_graph_max_nodes_exceeded",
|
|
145
|
+
"dependency_graph_max_edges_exceeded",
|
|
146
|
+
"import_cycle_detected",
|
|
147
|
+
"import_cycle_max_cycles_exceeded"
|
|
148
|
+
]
|
|
149
|
+
},
|
|
150
|
+
"severity": { "enum": ["low", "medium", "high", "critical"] },
|
|
151
|
+
"message": { "type": "string" },
|
|
152
|
+
"path": { "type": "string" },
|
|
153
|
+
"cycle_id": { "type": "string", "pattern": "^cycle:[a-f0-9]{12}$" }
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|