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.
Files changed (67) hide show
  1. package/dist/cli/commands/dashboard.js +51 -4
  2. package/dist/cli/commands/explain.js +3 -2
  3. package/dist/cli/commands/help.js +0 -1
  4. package/dist/cli/commands/run.js +51 -4
  5. package/dist/cli/commands/verify.js +2 -1
  6. package/dist/cli/i18n/en.js +5 -0
  7. package/dist/cli/i18n/es.js +5 -0
  8. package/dist/cli/i18n/fr.js +5 -0
  9. package/dist/cli/i18n/hi.js +5 -0
  10. package/dist/cli/i18n/ko.js +5 -0
  11. package/dist/cli/i18n/zh.js +5 -0
  12. package/dist/cli/lib/cli-output.js +1 -1
  13. package/dist/cli/lib/dashboard-html/client-script.js +9 -0
  14. package/dist/cli/lib/dashboard-html/styles.js +48 -1
  15. package/dist/cli/lib/doc-review-ledger.js +1 -1
  16. package/dist/cli/lib/git-changes.js +2 -0
  17. package/dist/cli/lib/local-index/index.js +324 -298
  18. package/dist/cli/lib/repo-map.js +19 -5
  19. package/dist/cli/lib/run-plan.js +20 -7
  20. package/dist/cli/lib/run-root-trust.js +33 -2
  21. package/dist/cli/lib/validation/index.js +6 -2
  22. package/dist/cli/lib/validation/test-selection.js +11 -1
  23. package/dist/core/active-run-locks.js +36 -8
  24. package/dist/core/atomic-state-write.js +5 -20
  25. package/dist/core/change-verification.js +18 -2
  26. package/dist/core/command-intent-eligibility.js +7 -0
  27. package/dist/core/contract-lint.js +3 -3
  28. package/dist/core/line-endings.js +2 -0
  29. package/dist/core/repeated-failure.js +1 -1
  30. package/dist/core/run-write-drift.js +42 -26
  31. package/dist/core/safe-filesystem.js +54 -5
  32. package/dist/core/skill-route-explanation.js +2 -1
  33. package/dist/core/source-anchors.js +7 -3
  34. package/dist/core/test-selection.js +13 -2
  35. package/dist/core/test-target-paths.js +17 -0
  36. package/dist/core/validation-ratchet.js +62 -17
  37. package/dist/core/verification-decision-graph.js +8 -1
  38. package/package.json +1 -1
  39. package/templates/default/i18n.toml +141 -3
  40. package/templates/default/locales/en/.mustflow/skills/INDEX.md +24 -1
  41. package/templates/default/locales/en/.mustflow/skills/api-contract-change/SKILL.md +212 -0
  42. package/templates/default/locales/en/.mustflow/skills/astro-code-change/SKILL.md +184 -0
  43. package/templates/default/locales/en/.mustflow/skills/auth-permission-change/SKILL.md +194 -0
  44. package/templates/default/locales/en/.mustflow/skills/config-env-change/SKILL.md +189 -0
  45. package/templates/default/locales/en/.mustflow/skills/css-code-change/SKILL.md +199 -0
  46. package/templates/default/locales/en/.mustflow/skills/dart-code-change/SKILL.md +179 -0
  47. package/templates/default/locales/en/.mustflow/skills/database-migration-change/SKILL.md +178 -0
  48. package/templates/default/locales/en/.mustflow/skills/dependency-upgrade-review/SKILL.md +151 -0
  49. package/templates/default/locales/en/.mustflow/skills/elysia-code-change/SKILL.md +115 -0
  50. package/templates/default/locales/en/.mustflow/skills/file-path-cross-platform-change/SKILL.md +147 -0
  51. package/templates/default/locales/en/.mustflow/skills/flutter-code-change/SKILL.md +116 -0
  52. package/templates/default/locales/en/.mustflow/skills/go-code-change/SKILL.md +156 -0
  53. package/templates/default/locales/en/.mustflow/skills/hono-code-change/SKILL.md +117 -0
  54. package/templates/default/locales/en/.mustflow/skills/html-code-change/SKILL.md +173 -0
  55. package/templates/default/locales/en/.mustflow/skills/javascript-code-change/SKILL.md +149 -0
  56. package/templates/default/locales/en/.mustflow/skills/python-code-change/SKILL.md +154 -0
  57. package/templates/default/locales/en/.mustflow/skills/release-publish-change/SKILL.md +172 -0
  58. package/templates/default/locales/en/.mustflow/skills/routes.toml +138 -0
  59. package/templates/default/locales/en/.mustflow/skills/rust-code-change/SKILL.md +154 -0
  60. package/templates/default/locales/en/.mustflow/skills/security-privacy-review/SKILL.md +22 -7
  61. package/templates/default/locales/en/.mustflow/skills/security-regression-tests/SKILL.md +31 -20
  62. package/templates/default/locales/en/.mustflow/skills/svelte-code-change/SKILL.md +186 -0
  63. package/templates/default/locales/en/.mustflow/skills/tailwind-code-change/SKILL.md +164 -0
  64. package/templates/default/locales/en/.mustflow/skills/tauri-code-change/SKILL.md +185 -0
  65. package/templates/default/locales/en/.mustflow/skills/typescript-code-change/SKILL.md +184 -0
  66. package/templates/default/locales/en/.mustflow/skills/unocss-code-change/SKILL.md +186 -0
  67. 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), { allowMissingLeaf: true });
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 readFileSync(fileDescriptor);
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
- renameSync(temporaryPath, absoluteChildPath);
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 end = content.indexOf('\n---', 3);
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 testTargets = readStringArray(value.select, 'test_targets') ?? [];
51
- if (!id || !risk || !reason || !paths || paths.length === 0 || !surfaces || surfaces.length === 0 || !intent || !fallbackIntent) {
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 gitDiffLines(projectRoot, relativePath) {
52
- const result = spawnSync('git', ['diff', '--no-ext-diff', '--unified=0', '--', relativePath], {
53
- cwd: projectRoot,
54
- encoding: 'utf8',
55
- windowsHide: true,
56
- });
57
- if (result.status !== 0 || typeof result.stdout !== 'string' || result.stdout.length === 0) {
58
- return { added: [], removed: [] };
59
- }
60
- const added = [];
61
- const removed = [];
62
- for (const line of result.stdout.split(/\r?\n/u)) {
63
- if (line.startsWith('+++') || line.startsWith('---')) {
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 { added, removed };
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' ? gitDiffLines(projectRoot, classification.path) : { added: [], removed: [] };
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
- return value.trim().replace(/[^A-Za-z0-9_.-]+/gu, '_') || 'none';
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mustflow",
3
- "version": "2.22.16",
3
+ "version": "2.22.46",
4
4
  "description": "Agent workflow documents and CLI for mustflow repository roots.",
5
5
  "type": "module",
6
6
  "license": "MIT-0",
@@ -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 = 73
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 = 17
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 = 9
472
+ revision = 11
335
473
  translations = {}
336
474
 
337
475
  [documents."skill.search-ad-content-authoring"]