mustflow 2.22.16 → 2.22.46
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/dashboard.js +51 -4
- package/dist/cli/commands/explain.js +3 -2
- package/dist/cli/commands/help.js +0 -1
- package/dist/cli/commands/run.js +51 -4
- package/dist/cli/commands/verify.js +2 -1
- 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/cli-output.js +1 -1
- package/dist/cli/lib/dashboard-html/client-script.js +9 -0
- package/dist/cli/lib/dashboard-html/styles.js +48 -1
- package/dist/cli/lib/doc-review-ledger.js +1 -1
- package/dist/cli/lib/git-changes.js +2 -0
- package/dist/cli/lib/local-index/index.js +324 -298
- package/dist/cli/lib/repo-map.js +19 -5
- package/dist/cli/lib/run-plan.js +20 -7
- package/dist/cli/lib/run-root-trust.js +33 -2
- package/dist/cli/lib/validation/index.js +6 -2
- package/dist/cli/lib/validation/test-selection.js +11 -1
- package/dist/core/active-run-locks.js +36 -8
- package/dist/core/atomic-state-write.js +5 -20
- package/dist/core/change-verification.js +18 -2
- package/dist/core/command-intent-eligibility.js +7 -0
- package/dist/core/contract-lint.js +3 -3
- package/dist/core/line-endings.js +2 -0
- package/dist/core/repeated-failure.js +1 -1
- package/dist/core/run-write-drift.js +42 -26
- package/dist/core/safe-filesystem.js +54 -5
- package/dist/core/skill-route-explanation.js +2 -1
- package/dist/core/source-anchors.js +7 -3
- package/dist/core/test-selection.js +13 -2
- package/dist/core/test-target-paths.js +17 -0
- package/dist/core/validation-ratchet.js +62 -17
- package/dist/core/verification-decision-graph.js +8 -1
- package/package.json +1 -1
- package/templates/default/i18n.toml +141 -3
- package/templates/default/locales/en/.mustflow/skills/INDEX.md +24 -1
- package/templates/default/locales/en/.mustflow/skills/api-contract-change/SKILL.md +212 -0
- package/templates/default/locales/en/.mustflow/skills/astro-code-change/SKILL.md +184 -0
- package/templates/default/locales/en/.mustflow/skills/auth-permission-change/SKILL.md +194 -0
- package/templates/default/locales/en/.mustflow/skills/config-env-change/SKILL.md +189 -0
- package/templates/default/locales/en/.mustflow/skills/css-code-change/SKILL.md +199 -0
- package/templates/default/locales/en/.mustflow/skills/dart-code-change/SKILL.md +179 -0
- package/templates/default/locales/en/.mustflow/skills/database-migration-change/SKILL.md +178 -0
- package/templates/default/locales/en/.mustflow/skills/dependency-upgrade-review/SKILL.md +151 -0
- package/templates/default/locales/en/.mustflow/skills/elysia-code-change/SKILL.md +115 -0
- package/templates/default/locales/en/.mustflow/skills/file-path-cross-platform-change/SKILL.md +147 -0
- package/templates/default/locales/en/.mustflow/skills/flutter-code-change/SKILL.md +116 -0
- package/templates/default/locales/en/.mustflow/skills/go-code-change/SKILL.md +156 -0
- package/templates/default/locales/en/.mustflow/skills/hono-code-change/SKILL.md +117 -0
- package/templates/default/locales/en/.mustflow/skills/html-code-change/SKILL.md +173 -0
- package/templates/default/locales/en/.mustflow/skills/javascript-code-change/SKILL.md +149 -0
- package/templates/default/locales/en/.mustflow/skills/python-code-change/SKILL.md +154 -0
- package/templates/default/locales/en/.mustflow/skills/release-publish-change/SKILL.md +172 -0
- package/templates/default/locales/en/.mustflow/skills/routes.toml +138 -0
- package/templates/default/locales/en/.mustflow/skills/rust-code-change/SKILL.md +154 -0
- package/templates/default/locales/en/.mustflow/skills/security-privacy-review/SKILL.md +22 -7
- package/templates/default/locales/en/.mustflow/skills/security-regression-tests/SKILL.md +31 -20
- package/templates/default/locales/en/.mustflow/skills/svelte-code-change/SKILL.md +186 -0
- package/templates/default/locales/en/.mustflow/skills/tailwind-code-change/SKILL.md +164 -0
- package/templates/default/locales/en/.mustflow/skills/tauri-code-change/SKILL.md +185 -0
- package/templates/default/locales/en/.mustflow/skills/typescript-code-change/SKILL.md +184 -0
- package/templates/default/locales/en/.mustflow/skills/unocss-code-change/SKILL.md +186 -0
- package/templates/default/manifest.toml +158 -1
|
@@ -1,7 +1,11 @@
|
|
|
1
|
-
import { closeSync, constants, fstatSync, lstatSync, mkdirSync, openSync, readFileSync, renameSync, unlinkSync, writeFileSync, } from 'node:fs';
|
|
1
|
+
import { closeSync, constants, fstatSync, lstatSync, mkdirSync, openSync, readFileSync, readSync, renameSync, unlinkSync, writeFileSync, } from 'node:fs';
|
|
2
2
|
import { randomBytes } from 'node:crypto';
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
const NOFOLLOW_FLAG = typeof constants.O_NOFOLLOW === 'number' ? constants.O_NOFOLLOW : 0;
|
|
5
|
+
const WINDOWS_RENAME_RETRY_DELAYS_MS = [10, 25, 50, 100, 200];
|
|
6
|
+
const WINDOWS_RENAME_RETRY_CODES = new Set(['EBUSY', 'ENOTEMPTY', 'EPERM']);
|
|
7
|
+
const WRITE_SLEEP_BUFFER = new Int32Array(new SharedArrayBuffer(4));
|
|
8
|
+
const READ_CHUNK_BYTES = 64 * 1024;
|
|
5
9
|
function isMissingPathError(error) {
|
|
6
10
|
return error instanceof Error && 'code' in error && error.code === 'ENOENT';
|
|
7
11
|
}
|
|
@@ -9,6 +13,30 @@ function tempFilePath(targetPath) {
|
|
|
9
13
|
const suffix = `${process.pid}-${Date.now()}-${randomBytes(6).toString('hex')}`;
|
|
10
14
|
return path.join(path.dirname(targetPath), `.${path.basename(targetPath)}.${suffix}.tmp`);
|
|
11
15
|
}
|
|
16
|
+
function sleep(milliseconds) {
|
|
17
|
+
Atomics.wait(WRITE_SLEEP_BUFFER, 0, 0, milliseconds);
|
|
18
|
+
}
|
|
19
|
+
function isRetryableWindowsRenameError(error) {
|
|
20
|
+
if (process.platform !== 'win32' || !error || typeof error !== 'object' || !('code' in error)) {
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
return WINDOWS_RENAME_RETRY_CODES.has(String(error.code));
|
|
24
|
+
}
|
|
25
|
+
function renameWithWindowsRetry(sourcePath, targetPath) {
|
|
26
|
+
for (let attempt = 0;; attempt += 1) {
|
|
27
|
+
try {
|
|
28
|
+
renameSync(sourcePath, targetPath);
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
catch (error) {
|
|
32
|
+
const delay = WINDOWS_RENAME_RETRY_DELAYS_MS[attempt];
|
|
33
|
+
if (delay === undefined || !isRetryableWindowsRenameError(error)) {
|
|
34
|
+
throw error;
|
|
35
|
+
}
|
|
36
|
+
sleep(delay);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
12
40
|
export function ensureInside(parentPath, childPath) {
|
|
13
41
|
const parent = path.resolve(parentPath);
|
|
14
42
|
const child = path.resolve(childPath);
|
|
@@ -42,13 +70,29 @@ export function ensureInsideWithoutSymlinks(parentPath, childPath, options = {})
|
|
|
42
70
|
}
|
|
43
71
|
}
|
|
44
72
|
catch (error) {
|
|
45
|
-
if (isMissingPathError(error) && options.allowMissingLeaf) {
|
|
73
|
+
if (isMissingPathError(error) && (options.allowMissingDescendant || (isLeaf && options.allowMissingLeaf))) {
|
|
46
74
|
return;
|
|
47
75
|
}
|
|
48
76
|
throw error;
|
|
49
77
|
}
|
|
50
78
|
}
|
|
51
79
|
}
|
|
80
|
+
function readBoundedFileDescriptor(fileDescriptor, childPath, maxBytes) {
|
|
81
|
+
const chunks = [];
|
|
82
|
+
let totalBytes = 0;
|
|
83
|
+
while (true) {
|
|
84
|
+
const chunk = Buffer.allocUnsafe(Math.min(READ_CHUNK_BYTES, maxBytes + 1 - totalBytes));
|
|
85
|
+
const bytesRead = readSync(fileDescriptor, chunk, 0, chunk.byteLength, null);
|
|
86
|
+
if (bytesRead === 0) {
|
|
87
|
+
return Buffer.concat(chunks, totalBytes);
|
|
88
|
+
}
|
|
89
|
+
totalBytes += bytesRead;
|
|
90
|
+
if (totalBytes > maxBytes) {
|
|
91
|
+
throw new Error(`File exceeds maximum size ${maxBytes} bytes: ${childPath}`);
|
|
92
|
+
}
|
|
93
|
+
chunks.push(bytesRead === chunk.byteLength ? chunk : chunk.subarray(0, bytesRead));
|
|
94
|
+
}
|
|
95
|
+
}
|
|
52
96
|
function ensureDirectoryInsideWithoutSymlinks(parentPath, directoryPath) {
|
|
53
97
|
ensureInside(parentPath, directoryPath);
|
|
54
98
|
const parent = path.resolve(parentPath);
|
|
@@ -86,7 +130,7 @@ function ensureDirectoryInsideWithoutSymlinks(parentPath, directoryPath) {
|
|
|
86
130
|
export function ensureFileTargetInsideWithoutSymlinks(parentPath, childPath, options = {}) {
|
|
87
131
|
const absoluteChildPath = path.resolve(childPath);
|
|
88
132
|
ensureInside(parentPath, absoluteChildPath);
|
|
89
|
-
ensureInsideWithoutSymlinks(parentPath, path.dirname(absoluteChildPath), {
|
|
133
|
+
ensureInsideWithoutSymlinks(parentPath, path.dirname(absoluteChildPath), { allowMissingDescendant: true });
|
|
90
134
|
try {
|
|
91
135
|
const stats = lstatSync(absoluteChildPath);
|
|
92
136
|
if (stats.isSymbolicLink()) {
|
|
@@ -108,6 +152,9 @@ export function readFileInsideWithoutSymlinks(parentPath, childPath, options = {
|
|
|
108
152
|
ensureInsideWithoutSymlinks(parentPath, absoluteChildPath);
|
|
109
153
|
const fileDescriptor = openSync(absoluteChildPath, constants.O_RDONLY | NOFOLLOW_FLAG);
|
|
110
154
|
try {
|
|
155
|
+
if (NOFOLLOW_FLAG === 0 && lstatSync(absoluteChildPath).isSymbolicLink()) {
|
|
156
|
+
throw new Error(`Path must not contain symlinks: ${childPath}`);
|
|
157
|
+
}
|
|
111
158
|
const stats = fstatSync(fileDescriptor);
|
|
112
159
|
if (!stats.isFile()) {
|
|
113
160
|
throw new Error(`Path must be a regular file: ${childPath}`);
|
|
@@ -115,7 +162,9 @@ export function readFileInsideWithoutSymlinks(parentPath, childPath, options = {
|
|
|
115
162
|
if (options.maxBytes !== undefined && stats.size > options.maxBytes) {
|
|
116
163
|
throw new Error(`File exceeds maximum size ${options.maxBytes} bytes: ${childPath}`);
|
|
117
164
|
}
|
|
118
|
-
return
|
|
165
|
+
return options.maxBytes === undefined
|
|
166
|
+
? readFileSync(fileDescriptor)
|
|
167
|
+
: readBoundedFileDescriptor(fileDescriptor, childPath, options.maxBytes);
|
|
119
168
|
}
|
|
120
169
|
finally {
|
|
121
170
|
closeSync(fileDescriptor);
|
|
@@ -137,7 +186,7 @@ export function writeFileInsideWithoutSymlinks(parentPath, childPath, content) {
|
|
|
137
186
|
closeSync(fileDescriptor);
|
|
138
187
|
fileDescriptor = null;
|
|
139
188
|
ensureFileTargetInsideWithoutSymlinks(parentPath, absoluteChildPath, { allowMissingLeaf: true });
|
|
140
|
-
|
|
189
|
+
renameWithWindowsRetry(temporaryPath, absoluteChildPath);
|
|
141
190
|
}
|
|
142
191
|
catch (error) {
|
|
143
192
|
if (fileDescriptor !== null) {
|
|
@@ -16,7 +16,8 @@ function readFrontmatterLines(content) {
|
|
|
16
16
|
if (!content.startsWith('---')) {
|
|
17
17
|
return [];
|
|
18
18
|
}
|
|
19
|
-
const
|
|
19
|
+
const endMatch = /\n---(?:\r?\n|$)/u.exec(content.slice(3));
|
|
20
|
+
const end = endMatch ? 3 + endMatch.index : -1;
|
|
20
21
|
if (end < 0) {
|
|
21
22
|
return [];
|
|
22
23
|
}
|
|
@@ -21,6 +21,7 @@ export const SOURCE_ANCHOR_GENERATED_PATH_PARTS = new Set([
|
|
|
21
21
|
'vendor',
|
|
22
22
|
]);
|
|
23
23
|
export const SOURCE_ANCHOR_ALLOWED_FIELDS = new Set(['purpose', 'search', 'invariant', 'risk']);
|
|
24
|
+
const MAX_SOURCE_ANCHOR_DIRECTORY_DEPTH = 200;
|
|
24
25
|
export const SOURCE_ANCHOR_ALLOWED_RISKS = new Set([
|
|
25
26
|
'authn',
|
|
26
27
|
'authz',
|
|
@@ -79,10 +80,13 @@ function fileIsWithinSizeLimit(filePath, maxFileBytes) {
|
|
|
79
80
|
return false;
|
|
80
81
|
}
|
|
81
82
|
}
|
|
82
|
-
function listFilesRecursive(root, options, current = root) {
|
|
83
|
+
function listFilesRecursive(root, options, current = root, depth = 0) {
|
|
83
84
|
if (!existsSync(current)) {
|
|
84
85
|
return [];
|
|
85
86
|
}
|
|
87
|
+
if (depth > MAX_SOURCE_ANCHOR_DIRECTORY_DEPTH) {
|
|
88
|
+
return [];
|
|
89
|
+
}
|
|
86
90
|
const currentRealPath = realpathSync(current);
|
|
87
91
|
if (!pathIsInsideRoot(options.rootRealPath, currentRealPath) || options.visitedRealDirectories.has(currentRealPath)) {
|
|
88
92
|
return [];
|
|
@@ -96,7 +100,7 @@ function listFilesRecursive(root, options, current = root) {
|
|
|
96
100
|
continue;
|
|
97
101
|
}
|
|
98
102
|
if (entry.isDirectory()) {
|
|
99
|
-
files.push(...listFilesRecursive(root, options, entryPath));
|
|
103
|
+
files.push(...listFilesRecursive(root, options, entryPath, depth + 1));
|
|
100
104
|
continue;
|
|
101
105
|
}
|
|
102
106
|
if (entry.isSymbolicLink()) {
|
|
@@ -121,7 +125,7 @@ function listFilesRecursive(root, options, current = root) {
|
|
|
121
125
|
continue;
|
|
122
126
|
}
|
|
123
127
|
if (stat.isDirectory()) {
|
|
124
|
-
files.push(...listFilesRecursive(root, options, entryPath));
|
|
128
|
+
files.push(...listFilesRecursive(root, options, entryPath, depth + 1));
|
|
125
129
|
continue;
|
|
126
130
|
}
|
|
127
131
|
if (stat.isFile()) {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { existsSync } from 'node:fs';
|
|
2
2
|
import { isRecord, readMustflowOwnedTomlFile, readStringArray, resolveMustflowConfigPath, } from './config-loading.js';
|
|
3
3
|
import { classifyVerificationCandidate, } from './verification-plan.js';
|
|
4
|
+
import { normalizeSafeTestTargetPath } from './test-target-paths.js';
|
|
4
5
|
export const TEST_SELECTION_CONFIG_RELATIVE_PATH = '.mustflow/config/test-selection.toml';
|
|
5
6
|
const STALE_OR_MISSING_RULES_NOTE = 'Project-declared test selection rules did not cover the current changed files; review .mustflow/config/test-selection.toml for stale or missing rules.';
|
|
6
7
|
function uniqueSorted(values) {
|
|
@@ -47,8 +48,18 @@ function readRule(value) {
|
|
|
47
48
|
const surfaces = readStringArray(value.match, 'surfaces');
|
|
48
49
|
const intent = readStringField(value.select, 'intent');
|
|
49
50
|
const fallbackIntent = readStringField(value.select, 'fallback_intent');
|
|
50
|
-
const
|
|
51
|
-
|
|
51
|
+
const rawTestTargets = readStringArray(value.select, 'test_targets') ?? [];
|
|
52
|
+
const testTargets = rawTestTargets.map((target) => normalizeSafeTestTargetPath(target));
|
|
53
|
+
if (!id ||
|
|
54
|
+
!risk ||
|
|
55
|
+
!reason ||
|
|
56
|
+
!paths ||
|
|
57
|
+
paths.length === 0 ||
|
|
58
|
+
!surfaces ||
|
|
59
|
+
surfaces.length === 0 ||
|
|
60
|
+
!intent ||
|
|
61
|
+
!fallbackIntent ||
|
|
62
|
+
!testTargets.every((target) => target !== null)) {
|
|
52
63
|
return null;
|
|
53
64
|
}
|
|
54
65
|
return {
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
export const TEST_TARGET_PATH_ERROR = "entries must be non-empty relative paths that do not start with '-'";
|
|
3
|
+
export function normalizeSafeTestTargetPath(value) {
|
|
4
|
+
if (typeof value !== 'string') {
|
|
5
|
+
return null;
|
|
6
|
+
}
|
|
7
|
+
const raw = value.trim();
|
|
8
|
+
const normalized = raw.replace(/\\/g, '/');
|
|
9
|
+
if (normalized.length === 0 ||
|
|
10
|
+
normalized.startsWith('-') ||
|
|
11
|
+
path.posix.isAbsolute(normalized) ||
|
|
12
|
+
path.win32.isAbsolute(raw)) {
|
|
13
|
+
return null;
|
|
14
|
+
}
|
|
15
|
+
const segments = normalized.split('/');
|
|
16
|
+
return segments.every((segment) => segment.length > 0 && segment !== '.' && segment !== '..') ? normalized : null;
|
|
17
|
+
}
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { spawnSync } from 'node:child_process';
|
|
2
2
|
import { existsSync, readFileSync } from 'node:fs';
|
|
3
3
|
import path from 'node:path';
|
|
4
|
+
import { createCommandEnv } from './command-env.js';
|
|
5
|
+
const GIT_DIFF_TIMEOUT_MS = 10_000;
|
|
6
|
+
const GIT_DIFF_MAX_BUFFER_BYTES = 16 * 1024 * 1024;
|
|
4
7
|
const TEST_CHANGE_KINDS = new Set(['test', 'test_fixture']);
|
|
5
8
|
const SKIP_OR_ONLY_MARKER = /\b(?:describe|it|test)\s*\.\s*(?:skip|only)\s*\(/u;
|
|
6
9
|
const TODO_OR_PENDING_MARKER = /\b(?:describe|it|test)\s*\.\s*(?:todo|pending)\s*\(/u;
|
|
@@ -48,29 +51,66 @@ function fileTextIfReadable(projectRoot, relativePath) {
|
|
|
48
51
|
return null;
|
|
49
52
|
}
|
|
50
53
|
}
|
|
51
|
-
function
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
const
|
|
62
|
-
|
|
63
|
-
|
|
54
|
+
function normalizeGitDiffPath(value) {
|
|
55
|
+
return value
|
|
56
|
+
.replace(/^"(.*)"$/u, '$1')
|
|
57
|
+
.replace(/^(?:a|b)\//u, '')
|
|
58
|
+
.replaceAll('\\', '/');
|
|
59
|
+
}
|
|
60
|
+
function parseGitDiffLines(stdout) {
|
|
61
|
+
const byPath = new Map();
|
|
62
|
+
let oldPath = null;
|
|
63
|
+
let currentPath = null;
|
|
64
|
+
for (const line of stdout.split(/\r?\n/u)) {
|
|
65
|
+
if (line.startsWith('--- ')) {
|
|
66
|
+
const rawPath = line.slice(4).trim();
|
|
67
|
+
oldPath = rawPath === '/dev/null' ? null : normalizeGitDiffPath(rawPath);
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
if (line.startsWith('+++ ')) {
|
|
71
|
+
const rawPath = line.slice(4).trim();
|
|
72
|
+
currentPath = rawPath === '/dev/null' ? oldPath : normalizeGitDiffPath(rawPath);
|
|
73
|
+
if (currentPath && !byPath.has(currentPath)) {
|
|
74
|
+
byPath.set(currentPath, { added: [], removed: [] });
|
|
75
|
+
}
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
if (!currentPath || line.startsWith('@@')) {
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
const diff = byPath.get(currentPath);
|
|
82
|
+
if (!diff) {
|
|
64
83
|
continue;
|
|
65
84
|
}
|
|
66
85
|
if (line.startsWith('+')) {
|
|
67
|
-
added.push(line.slice(1));
|
|
86
|
+
diff.added.push(line.slice(1));
|
|
68
87
|
}
|
|
69
88
|
else if (line.startsWith('-')) {
|
|
70
|
-
removed.push(line.slice(1));
|
|
89
|
+
diff.removed.push(line.slice(1));
|
|
71
90
|
}
|
|
72
91
|
}
|
|
73
|
-
return
|
|
92
|
+
return new Map([...byPath.entries()].map(([filePath, diff]) => [
|
|
93
|
+
filePath,
|
|
94
|
+
{ added: diff.added, removed: diff.removed },
|
|
95
|
+
]));
|
|
96
|
+
}
|
|
97
|
+
function gitDiffLinesByPath(projectRoot, relativePaths) {
|
|
98
|
+
const uniquePaths = [...new Set(relativePaths)].filter((relativePath) => resolveInsideRoot(projectRoot, relativePath) !== null);
|
|
99
|
+
if (uniquePaths.length === 0) {
|
|
100
|
+
return new Map();
|
|
101
|
+
}
|
|
102
|
+
const result = spawnSync('git', ['diff', '--no-ext-diff', '--unified=0', '--', ...uniquePaths], {
|
|
103
|
+
cwd: projectRoot,
|
|
104
|
+
encoding: 'utf8',
|
|
105
|
+
env: createCommandEnv(projectRoot, { policy: 'minimal', allowlist: [] }),
|
|
106
|
+
maxBuffer: GIT_DIFF_MAX_BUFFER_BYTES,
|
|
107
|
+
timeout: GIT_DIFF_TIMEOUT_MS,
|
|
108
|
+
windowsHide: true,
|
|
109
|
+
});
|
|
110
|
+
if (result.status !== 0 || typeof result.stdout !== 'string' || result.stdout.length === 0) {
|
|
111
|
+
return new Map();
|
|
112
|
+
}
|
|
113
|
+
return parseGitDiffLines(result.stdout);
|
|
74
114
|
}
|
|
75
115
|
function countMatching(lines, pattern) {
|
|
76
116
|
return lines.filter((line) => pattern.test(line)).length;
|
|
@@ -103,6 +143,9 @@ export function countValidationRatchetVerdictEffects(risks) {
|
|
|
103
143
|
export function createValidationRatchetRisks(report, projectRoot) {
|
|
104
144
|
const risks = [];
|
|
105
145
|
const seenRisks = new Set();
|
|
146
|
+
const changedDiffs = report.source === 'changed'
|
|
147
|
+
? gitDiffLinesByPath(projectRoot, report.classifications.map((classification) => classification.path))
|
|
148
|
+
: new Map();
|
|
106
149
|
function addRisk(code, severity, pathText, detail) {
|
|
107
150
|
const key = `${pathText}\0${code}`;
|
|
108
151
|
if (seenRisks.has(key)) {
|
|
@@ -113,7 +156,9 @@ export function createValidationRatchetRisks(report, projectRoot) {
|
|
|
113
156
|
}
|
|
114
157
|
for (const classification of report.classifications) {
|
|
115
158
|
const resolvedPath = resolveInsideRoot(projectRoot, classification.path);
|
|
116
|
-
const diff = report.source === 'changed'
|
|
159
|
+
const diff = report.source === 'changed'
|
|
160
|
+
? changedDiffs.get(classification.path) ?? { added: [], removed: [] }
|
|
161
|
+
: { added: [], removed: [] };
|
|
117
162
|
const addedText = diff.added.join('\n');
|
|
118
163
|
if (isTestClassification(classification)) {
|
|
119
164
|
if (report.source === 'changed' && (resolvedPath === null || !existsSync(resolvedPath))) {
|
|
@@ -1,7 +1,14 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
1
2
|
import { isRecord, readString, readStringArray, } from './config-loading.js';
|
|
2
3
|
export const VERIFICATION_DECISION_GRAPH_SCHEMA_VERSION = '1';
|
|
3
4
|
function stableIdPart(value) {
|
|
4
|
-
|
|
5
|
+
const trimmed = value.trim();
|
|
6
|
+
if (trimmed.length === 0) {
|
|
7
|
+
return 'none';
|
|
8
|
+
}
|
|
9
|
+
const readable = trimmed.replace(/[^A-Za-z0-9_.-]+/gu, '_') || 'value';
|
|
10
|
+
const digest = createHash('sha256').update(trimmed).digest('hex').slice(0, 10);
|
|
11
|
+
return `${readable}_${digest}`;
|
|
5
12
|
}
|
|
6
13
|
function readBoolean(table, key) {
|
|
7
14
|
const value = table[key];
|
package/package.json
CHANGED
|
@@ -56,7 +56,7 @@ translations = {}
|
|
|
56
56
|
[documents."skills.index"]
|
|
57
57
|
source = "locales/en/.mustflow/skills/INDEX.md"
|
|
58
58
|
source_locale = "en"
|
|
59
|
-
revision =
|
|
59
|
+
revision = 81
|
|
60
60
|
translations = {}
|
|
61
61
|
|
|
62
62
|
[documents."skill.adapter-boundary"]
|
|
@@ -77,6 +77,12 @@ source_locale = "en"
|
|
|
77
77
|
revision = 2
|
|
78
78
|
translations = {}
|
|
79
79
|
|
|
80
|
+
[documents."skill.api-contract-change"]
|
|
81
|
+
source = "locales/en/.mustflow/skills/api-contract-change/SKILL.md"
|
|
82
|
+
source_locale = "en"
|
|
83
|
+
revision = 1
|
|
84
|
+
translations = {}
|
|
85
|
+
|
|
80
86
|
[documents."skill.behavior-preserving-refactor"]
|
|
81
87
|
source = "locales/en/.mustflow/skills/behavior-preserving-refactor/SKILL.md"
|
|
82
88
|
source_locale = "en"
|
|
@@ -107,6 +113,12 @@ source_locale = "en"
|
|
|
107
113
|
revision = 16
|
|
108
114
|
translations = {}
|
|
109
115
|
|
|
116
|
+
[documents."skill.database-migration-change"]
|
|
117
|
+
source = "locales/en/.mustflow/skills/database-migration-change/SKILL.md"
|
|
118
|
+
source_locale = "en"
|
|
119
|
+
revision = 1
|
|
120
|
+
translations = {}
|
|
121
|
+
|
|
110
122
|
[documents."skill.dependency-injection"]
|
|
111
123
|
source = "locales/en/.mustflow/skills/dependency-injection/SKILL.md"
|
|
112
124
|
source_locale = "en"
|
|
@@ -119,12 +131,24 @@ source_locale = "en"
|
|
|
119
131
|
revision = 6
|
|
120
132
|
translations = {}
|
|
121
133
|
|
|
134
|
+
[documents."skill.dependency-upgrade-review"]
|
|
135
|
+
source = "locales/en/.mustflow/skills/dependency-upgrade-review/SKILL.md"
|
|
136
|
+
source_locale = "en"
|
|
137
|
+
revision = 1
|
|
138
|
+
translations = {}
|
|
139
|
+
|
|
122
140
|
[documents."skill.line-ending-hygiene"]
|
|
123
141
|
source = "locales/en/.mustflow/skills/line-ending-hygiene/SKILL.md"
|
|
124
142
|
source_locale = "en"
|
|
125
143
|
revision = 1
|
|
126
144
|
translations = {}
|
|
127
145
|
|
|
146
|
+
[documents."skill.file-path-cross-platform-change"]
|
|
147
|
+
source = "locales/en/.mustflow/skills/file-path-cross-platform-change/SKILL.md"
|
|
148
|
+
source_locale = "en"
|
|
149
|
+
revision = 1
|
|
150
|
+
translations = {}
|
|
151
|
+
|
|
128
152
|
[documents."skill.diff-risk-review"]
|
|
129
153
|
source = "locales/en/.mustflow/skills/diff-risk-review/SKILL.md"
|
|
130
154
|
source_locale = "en"
|
|
@@ -137,6 +161,114 @@ source_locale = "en"
|
|
|
137
161
|
revision = 2
|
|
138
162
|
translations = {}
|
|
139
163
|
|
|
164
|
+
[documents."skill.astro-code-change"]
|
|
165
|
+
source = "locales/en/.mustflow/skills/astro-code-change/SKILL.md"
|
|
166
|
+
source_locale = "en"
|
|
167
|
+
revision = 2
|
|
168
|
+
translations = {}
|
|
169
|
+
|
|
170
|
+
[documents."skill.auth-permission-change"]
|
|
171
|
+
source = "locales/en/.mustflow/skills/auth-permission-change/SKILL.md"
|
|
172
|
+
source_locale = "en"
|
|
173
|
+
revision = 1
|
|
174
|
+
translations = {}
|
|
175
|
+
|
|
176
|
+
[documents."skill.config-env-change"]
|
|
177
|
+
source = "locales/en/.mustflow/skills/config-env-change/SKILL.md"
|
|
178
|
+
source_locale = "en"
|
|
179
|
+
revision = 1
|
|
180
|
+
translations = {}
|
|
181
|
+
|
|
182
|
+
[documents."skill.css-code-change"]
|
|
183
|
+
source = "locales/en/.mustflow/skills/css-code-change/SKILL.md"
|
|
184
|
+
source_locale = "en"
|
|
185
|
+
revision = 2
|
|
186
|
+
translations = {}
|
|
187
|
+
|
|
188
|
+
[documents."skill.dart-code-change"]
|
|
189
|
+
source = "locales/en/.mustflow/skills/dart-code-change/SKILL.md"
|
|
190
|
+
source_locale = "en"
|
|
191
|
+
revision = 2
|
|
192
|
+
translations = {}
|
|
193
|
+
|
|
194
|
+
[documents."skill.elysia-code-change"]
|
|
195
|
+
source = "locales/en/.mustflow/skills/elysia-code-change/SKILL.md"
|
|
196
|
+
source_locale = "en"
|
|
197
|
+
revision = 1
|
|
198
|
+
translations = {}
|
|
199
|
+
|
|
200
|
+
[documents."skill.flutter-code-change"]
|
|
201
|
+
source = "locales/en/.mustflow/skills/flutter-code-change/SKILL.md"
|
|
202
|
+
source_locale = "en"
|
|
203
|
+
revision = 1
|
|
204
|
+
translations = {}
|
|
205
|
+
|
|
206
|
+
[documents."skill.go-code-change"]
|
|
207
|
+
source = "locales/en/.mustflow/skills/go-code-change/SKILL.md"
|
|
208
|
+
source_locale = "en"
|
|
209
|
+
revision = 2
|
|
210
|
+
translations = {}
|
|
211
|
+
|
|
212
|
+
[documents."skill.hono-code-change"]
|
|
213
|
+
source = "locales/en/.mustflow/skills/hono-code-change/SKILL.md"
|
|
214
|
+
source_locale = "en"
|
|
215
|
+
revision = 1
|
|
216
|
+
translations = {}
|
|
217
|
+
|
|
218
|
+
[documents."skill.html-code-change"]
|
|
219
|
+
source = "locales/en/.mustflow/skills/html-code-change/SKILL.md"
|
|
220
|
+
source_locale = "en"
|
|
221
|
+
revision = 2
|
|
222
|
+
translations = {}
|
|
223
|
+
|
|
224
|
+
[documents."skill.javascript-code-change"]
|
|
225
|
+
source = "locales/en/.mustflow/skills/javascript-code-change/SKILL.md"
|
|
226
|
+
source_locale = "en"
|
|
227
|
+
revision = 2
|
|
228
|
+
translations = {}
|
|
229
|
+
|
|
230
|
+
[documents."skill.python-code-change"]
|
|
231
|
+
source = "locales/en/.mustflow/skills/python-code-change/SKILL.md"
|
|
232
|
+
source_locale = "en"
|
|
233
|
+
revision = 2
|
|
234
|
+
translations = {}
|
|
235
|
+
|
|
236
|
+
[documents."skill.rust-code-change"]
|
|
237
|
+
source = "locales/en/.mustflow/skills/rust-code-change/SKILL.md"
|
|
238
|
+
source_locale = "en"
|
|
239
|
+
revision = 2
|
|
240
|
+
translations = {}
|
|
241
|
+
|
|
242
|
+
[documents."skill.svelte-code-change"]
|
|
243
|
+
source = "locales/en/.mustflow/skills/svelte-code-change/SKILL.md"
|
|
244
|
+
source_locale = "en"
|
|
245
|
+
revision = 2
|
|
246
|
+
translations = {}
|
|
247
|
+
|
|
248
|
+
[documents."skill.tailwind-code-change"]
|
|
249
|
+
source = "locales/en/.mustflow/skills/tailwind-code-change/SKILL.md"
|
|
250
|
+
source_locale = "en"
|
|
251
|
+
revision = 2
|
|
252
|
+
translations = {}
|
|
253
|
+
|
|
254
|
+
[documents."skill.tauri-code-change"]
|
|
255
|
+
source = "locales/en/.mustflow/skills/tauri-code-change/SKILL.md"
|
|
256
|
+
source_locale = "en"
|
|
257
|
+
revision = 2
|
|
258
|
+
translations = {}
|
|
259
|
+
|
|
260
|
+
[documents."skill.typescript-code-change"]
|
|
261
|
+
source = "locales/en/.mustflow/skills/typescript-code-change/SKILL.md"
|
|
262
|
+
source_locale = "en"
|
|
263
|
+
revision = 2
|
|
264
|
+
translations = {}
|
|
265
|
+
|
|
266
|
+
[documents."skill.unocss-code-change"]
|
|
267
|
+
source = "locales/en/.mustflow/skills/unocss-code-change/SKILL.md"
|
|
268
|
+
source_locale = "en"
|
|
269
|
+
revision = 2
|
|
270
|
+
translations = {}
|
|
271
|
+
|
|
140
272
|
[documents."skill.cli-output-contract-review"]
|
|
141
273
|
source = "locales/en/.mustflow/skills/cli-output-contract-review/SKILL.md"
|
|
142
274
|
source_locale = "en"
|
|
@@ -322,16 +454,22 @@ source_locale = "en"
|
|
|
322
454
|
revision = 2
|
|
323
455
|
translations = {}
|
|
324
456
|
|
|
457
|
+
[documents."skill.release-publish-change"]
|
|
458
|
+
source = "locales/en/.mustflow/skills/release-publish-change/SKILL.md"
|
|
459
|
+
source_locale = "en"
|
|
460
|
+
revision = 1
|
|
461
|
+
translations = {}
|
|
462
|
+
|
|
325
463
|
[documents."skill.security-privacy-review"]
|
|
326
464
|
source = "locales/en/.mustflow/skills/security-privacy-review/SKILL.md"
|
|
327
465
|
source_locale = "en"
|
|
328
|
-
revision =
|
|
466
|
+
revision = 19
|
|
329
467
|
translations = {}
|
|
330
468
|
|
|
331
469
|
[documents."skill.security-regression-tests"]
|
|
332
470
|
source = "locales/en/.mustflow/skills/security-regression-tests/SKILL.md"
|
|
333
471
|
source_locale = "en"
|
|
334
|
-
revision =
|
|
472
|
+
revision = 11
|
|
335
473
|
translations = {}
|
|
336
474
|
|
|
337
475
|
[documents."skill.search-ad-content-authoring"]
|