mustflow 2.31.0 → 2.37.1

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 (45) hide show
  1. package/dist/cli/commands/api/actions.js +55 -0
  2. package/dist/cli/commands/api/report-runner.js +62 -0
  3. package/dist/cli/commands/api/serve.js +149 -0
  4. package/dist/cli/commands/api/workspace-recommendations.js +13 -0
  5. package/dist/cli/commands/api.js +15 -275
  6. package/dist/cli/lib/local-index/search-read-model.js +44 -7
  7. package/dist/cli/lib/validation/frontmatter.js +75 -0
  8. package/dist/cli/lib/validation/index.js +4 -86
  9. package/dist/cli/lib/validation/safe-read.js +13 -0
  10. package/dist/core/active-run-locks.js +110 -10
  11. package/dist/core/run-performance-history.js +14 -1
  12. package/dist/core/validation-ratchet.js +1 -1
  13. package/package.json +1 -1
  14. package/templates/default/i18n.toml +63 -21
  15. package/templates/default/locales/en/.mustflow/docs/agent-workflow.md +15 -7
  16. package/templates/default/locales/en/.mustflow/skills/INDEX.md +21 -8
  17. package/templates/default/locales/en/.mustflow/skills/adapter-boundary/SKILL.md +9 -2
  18. package/templates/default/locales/en/.mustflow/skills/ai-generated-code-hardening/SKILL.md +249 -0
  19. package/templates/default/locales/en/.mustflow/skills/api-contract-change/SKILL.md +16 -11
  20. package/templates/default/locales/en/.mustflow/skills/auth-permission-change/SKILL.md +11 -4
  21. package/templates/default/locales/en/.mustflow/skills/backend-reliability-change/SKILL.md +289 -0
  22. package/templates/default/locales/en/.mustflow/skills/css-code-change/SKILL.md +24 -14
  23. package/templates/default/locales/en/.mustflow/skills/dependency-upgrade-review/SKILL.md +18 -7
  24. package/templates/default/locales/en/.mustflow/skills/frontend-render-stability/SKILL.md +144 -0
  25. package/templates/default/locales/en/.mustflow/skills/go-code-change/SKILL.md +70 -18
  26. package/templates/default/locales/en/.mustflow/skills/heuristic-candidate-selection/SKILL.md +165 -0
  27. package/templates/default/locales/en/.mustflow/skills/html-code-change/SKILL.md +20 -13
  28. package/templates/default/locales/en/.mustflow/skills/http-delivery-streaming/SKILL.md +205 -0
  29. package/templates/default/locales/en/.mustflow/skills/performance-budget-check/SKILL.md +9 -7
  30. package/templates/default/locales/en/.mustflow/skills/proactive-risk-surfacing/SKILL.md +198 -0
  31. package/templates/default/locales/en/.mustflow/skills/python-code-change/SKILL.md +27 -11
  32. package/templates/default/locales/en/.mustflow/skills/routes.toml +43 -1
  33. package/templates/default/locales/en/.mustflow/skills/rust-code-change/SKILL.md +41 -17
  34. package/templates/default/locales/en/.mustflow/skills/security-privacy-review/SKILL.md +4 -1
  35. package/templates/default/locales/en/.mustflow/skills/security-regression-tests/SKILL.md +5 -2
  36. package/templates/default/locales/en/.mustflow/skills/service-boundary-architecture/SKILL.md +167 -0
  37. package/templates/default/locales/en/.mustflow/skills/tailwind-code-change/SKILL.md +37 -23
  38. package/templates/default/locales/en/.mustflow/skills/tauri-code-change/SKILL.md +27 -10
  39. package/templates/default/locales/en/.mustflow/skills/typescript-code-change/SKILL.md +22 -4
  40. package/templates/default/locales/en/.mustflow/skills/unocss-code-change/SKILL.md +34 -15
  41. package/templates/default/locales/en/.mustflow/skills/version-freshness-check/SKILL.md +29 -5
  42. package/templates/default/locales/en/AGENTS.md +3 -2
  43. package/templates/default/locales/ko/.mustflow/docs/agent-workflow.md +13 -8
  44. package/templates/default/locales/ko/AGENTS.md +2 -2
  45. package/templates/default/manifest.toml +44 -1
@@ -0,0 +1,75 @@
1
+ import { SKILL_SECTION_MARKER_PATTERN } from './constants.js';
2
+ export function readSkillSectionIds(content) {
3
+ return new Set([...content.matchAll(SKILL_SECTION_MARKER_PATTERN)].map((match) => match[1]));
4
+ }
5
+ function findFrontmatterEnd(content) {
6
+ const match = /\n---(?:\r?\n|$)/u.exec(content.slice(3));
7
+ return match ? 3 + match.index : -1;
8
+ }
9
+ export function parseSimpleFrontmatter(content) {
10
+ if (!content.startsWith('---')) {
11
+ return {};
12
+ }
13
+ const end = findFrontmatterEnd(content);
14
+ if (end === -1) {
15
+ return {};
16
+ }
17
+ const frontmatter = {};
18
+ for (const line of content.slice(3, end).split(/\r?\n/)) {
19
+ const separatorIndex = line.indexOf(':');
20
+ if (separatorIndex === -1) {
21
+ continue;
22
+ }
23
+ const key = line.slice(0, separatorIndex).trim();
24
+ const value = line.slice(separatorIndex + 1).trim().replace(/^["']|["']$/g, '');
25
+ if (key.length > 0 && value.length > 0) {
26
+ frontmatter[key] = value;
27
+ }
28
+ }
29
+ return frontmatter;
30
+ }
31
+ function readFrontmatterLines(content) {
32
+ if (!content.startsWith('---')) {
33
+ return [];
34
+ }
35
+ const end = findFrontmatterEnd(content);
36
+ if (end === -1) {
37
+ return [];
38
+ }
39
+ return content
40
+ .slice(3, end)
41
+ .split(/\n/u)
42
+ .map((line) => line.replace(/\r$/u, ''));
43
+ }
44
+ function stripScalarMarkers(value) {
45
+ return value.trim().replace(/^["'`]|["'`]$/g, '').trim();
46
+ }
47
+ export function readFrontmatterList(content, key) {
48
+ const lines = readFrontmatterLines(content);
49
+ const values = [];
50
+ let keyIndent;
51
+ for (const line of lines) {
52
+ const keyMatch = line.match(new RegExp(`^(\\s*)${key}:\\s*$`, 'u'));
53
+ if (keyIndent === undefined) {
54
+ if (keyMatch) {
55
+ keyIndent = keyMatch[1].length;
56
+ }
57
+ continue;
58
+ }
59
+ if (line.trim().length === 0) {
60
+ continue;
61
+ }
62
+ const lineIndent = line.match(/^\s*/u)?.[0].length ?? 0;
63
+ const itemMatch = line.match(/^\s*-\s+(.+)$/u);
64
+ if (lineIndent <= keyIndent && !itemMatch) {
65
+ break;
66
+ }
67
+ if (itemMatch) {
68
+ const value = stripScalarMarkers(itemMatch[1]);
69
+ if (value.length > 0) {
70
+ values.push(value);
71
+ }
72
+ }
73
+ }
74
+ return values;
75
+ }
@@ -13,13 +13,15 @@ import { readGitChangedFiles } from '../git-changes.js';
13
13
  import { inspectManifestLock } from '../manifest-lock.js';
14
14
  import { generateRepoMap } from '../repo-map.js';
15
15
  import { parseTomlText, readMustflowTomlFile } from '../toml.js';
16
- import { MUSTFLOW_JSON_MAX_BYTES, readMustflowTextFileResult, } from '../mustflow-read.js';
16
+ import { MUSTFLOW_JSON_MAX_BYTES } from '../mustflow-read.js';
17
17
  import { getContractModelDefinitions, validateCandidateContractModelConfig, } from '../../../core/contract-models.js';
18
18
  import { VERSIONING_CONFIG_PATH, detectVersionSourcePaths, readDeclaredVersionSources, releaseVersioningIsEnabled, } from '../../../core/version-sources.js';
19
19
  import { normalizeTechnologyPreferencesTable, TECHNOLOGY_CONFIG_RELATIVE_PATH, } from '../../../core/technology-preferences.js';
20
- import { ALLOWED_APPROVAL_ACTIONS, ALLOWED_APPROVAL_GATES, ALLOWED_BUDGET_LIMIT_ACTIONS, ALLOWED_CAPABILITY_STATES, ALLOWED_COMMIT_MESSAGE_STYLES, ALLOWED_COMPACTION_CATEGORIES, ALLOWED_COMPACTION_LONG_LIMIT_ACTIONS, ALLOWED_COMPACTION_RAW_LIMIT_ACTIONS, ALLOWED_COMPACTION_STATE_STORES, ALLOWED_COMPACTION_STRATEGIES, ALLOWED_CONTEXT_AUTHORITIES, ALLOWED_CONTEXT_DOCUMENT_AUTHORITIES, ALLOWED_CONTEXT_READ_POLICIES, ALLOWED_HANDOFF_MODES, ALLOWED_HARNESS_FRESH_CONTEXT_MODES, ALLOWED_HARNESS_MODES, ALLOWED_HARNESS_PHASES, ALLOWED_ISOLATION_PREFERENCES, ALLOWED_MAP_MODES, ALLOWED_MAP_PRIVACY_LEVELS, ALLOWED_PROJECT_PROFILES, ALLOWED_REPO_MAP_DEGRADED_VALUES, ALLOWED_REPO_MAP_GIT_LS_FILES_STATUSES, ALLOWED_PROMPT_CACHE_STABLE_PREFIX_POLICIES, ALLOWED_PROMPT_CACHE_STRATEGIES, ALLOWED_PROMPT_CACHE_TASK_READ_POLICIES, ALLOWED_REFRESH_CHECKPOINTS, ALLOWED_REFRESH_METHODS, ALLOWED_REFRESH_MODES, ALLOWED_REFRESH_STATE_STORES, ALLOWED_SKILL_RESOURCE_TYPES, ALLOWED_SKILL_ROUTE_CATEGORIES, ALLOWED_SKILL_ROUTE_PROFILES, ALLOWED_SKILL_ROUTE_TYPES, ALLOWED_STALE_TEST_ACTIONS, ALLOWED_TEST_AUTHORING_POLICIES, ALLOWED_TEST_DELETION_REASONS, ALLOWED_TESTING_POLICIES, ALLOWED_TRANSLATION_POLICIES, ALLOWED_VERIFICATION_SELECTION_STRATEGIES, ALLOWED_VERSION_SOURCE_AUTHORITIES, ALLOWED_VERSION_SOURCE_KINDS, CAPABILITY_BOOLEAN_FIELDS, CAPABILITY_STATE_FIELDS, CONTEXT_AUTHORITY_DRIFT_PATTERNS, DESIGN_TOKEN_DEFINITION_PATTERNS, FORBIDDEN_RELEASE_VERSIONING_CONTRACT_FIELDS, FORBIDDEN_TEST_DELETION_REASONS, FORBIDDEN_VERIFICATION_SELECTION_AUTHORITY_FIELDS, LOCAL_ABSOLUTE_PATH_PATTERNS, LOCAL_TASK_STATE_ROOTS, RAW_COMMAND_FENCE_PATTERN, RELEASE_VERSIONING_BOOLEAN_FIELDS, REPO_MAP_DOC_ID, REPO_MAP_GENERATOR, REPO_MAP_LIFECYCLE, REPO_MAP_PRIVACY_MODE, REPO_MAP_RELATIVE_ROOT, REPO_MAP_REMOTE_OR_BRANCH_PATTERNS, REPO_MAP_SOURCE_FINGERPRINT_PATTERN, REPO_MAP_SOURCE_POLICY, REQUIRED_AGENT_LOOP_PHASES, REQUIRED_SKILL_SCRIPT_RUN_POLICY, REQUIRED_SKILL_SECTION_IDS, ROUTER_INDEX_FILES, ROUTER_INDEX_PROCEDURE_SECTION_PATTERN, SECRET_LIKE_CONTEXT_PATTERNS, SKILL_COMMAND_PERMISSION_CLAIM_PATTERNS, SKILL_INDEX_PATH, SKILL_PACK_ID_PATTERN, SKILL_RESOURCE_MANIFEST, SKILL_RESOURCE_ROOTS, SKILL_RESOURCE_TYPE_BY_ROOT, SKILL_ROUTE_CATEGORY_LABELS, SKILL_ROUTES_METADATA_PATH, SKILL_SECTION_MARKER_PATTERN, SUPPORTED_SKILL_SCHEMA_VERSION, TEST_AUTHORING_BOOLEAN_FIELDS, VERIFICATION_SELECTION_BOOLEAN_FIELDS, VOLATILE_REPO_MAP_PATTERNS } from './constants.js';
20
+ import { ALLOWED_APPROVAL_ACTIONS, ALLOWED_APPROVAL_GATES, ALLOWED_BUDGET_LIMIT_ACTIONS, ALLOWED_CAPABILITY_STATES, ALLOWED_COMMIT_MESSAGE_STYLES, ALLOWED_COMPACTION_CATEGORIES, ALLOWED_COMPACTION_LONG_LIMIT_ACTIONS, ALLOWED_COMPACTION_RAW_LIMIT_ACTIONS, ALLOWED_COMPACTION_STATE_STORES, ALLOWED_COMPACTION_STRATEGIES, ALLOWED_CONTEXT_AUTHORITIES, ALLOWED_CONTEXT_DOCUMENT_AUTHORITIES, ALLOWED_CONTEXT_READ_POLICIES, ALLOWED_HANDOFF_MODES, ALLOWED_HARNESS_FRESH_CONTEXT_MODES, ALLOWED_HARNESS_MODES, ALLOWED_HARNESS_PHASES, ALLOWED_ISOLATION_PREFERENCES, ALLOWED_MAP_MODES, ALLOWED_MAP_PRIVACY_LEVELS, ALLOWED_PROJECT_PROFILES, ALLOWED_REPO_MAP_DEGRADED_VALUES, ALLOWED_REPO_MAP_GIT_LS_FILES_STATUSES, ALLOWED_PROMPT_CACHE_STABLE_PREFIX_POLICIES, ALLOWED_PROMPT_CACHE_STRATEGIES, ALLOWED_PROMPT_CACHE_TASK_READ_POLICIES, ALLOWED_REFRESH_CHECKPOINTS, ALLOWED_REFRESH_METHODS, ALLOWED_REFRESH_MODES, ALLOWED_REFRESH_STATE_STORES, ALLOWED_SKILL_RESOURCE_TYPES, ALLOWED_SKILL_ROUTE_CATEGORIES, ALLOWED_SKILL_ROUTE_PROFILES, ALLOWED_SKILL_ROUTE_TYPES, ALLOWED_STALE_TEST_ACTIONS, ALLOWED_TEST_AUTHORING_POLICIES, ALLOWED_TEST_DELETION_REASONS, ALLOWED_TESTING_POLICIES, ALLOWED_TRANSLATION_POLICIES, ALLOWED_VERIFICATION_SELECTION_STRATEGIES, ALLOWED_VERSION_SOURCE_AUTHORITIES, ALLOWED_VERSION_SOURCE_KINDS, CAPABILITY_BOOLEAN_FIELDS, CAPABILITY_STATE_FIELDS, CONTEXT_AUTHORITY_DRIFT_PATTERNS, DESIGN_TOKEN_DEFINITION_PATTERNS, FORBIDDEN_RELEASE_VERSIONING_CONTRACT_FIELDS, FORBIDDEN_TEST_DELETION_REASONS, FORBIDDEN_VERIFICATION_SELECTION_AUTHORITY_FIELDS, LOCAL_ABSOLUTE_PATH_PATTERNS, LOCAL_TASK_STATE_ROOTS, RAW_COMMAND_FENCE_PATTERN, RELEASE_VERSIONING_BOOLEAN_FIELDS, REPO_MAP_DOC_ID, REPO_MAP_GENERATOR, REPO_MAP_LIFECYCLE, REPO_MAP_PRIVACY_MODE, REPO_MAP_RELATIVE_ROOT, REPO_MAP_REMOTE_OR_BRANCH_PATTERNS, REPO_MAP_SOURCE_FINGERPRINT_PATTERN, REPO_MAP_SOURCE_POLICY, REQUIRED_AGENT_LOOP_PHASES, REQUIRED_SKILL_SCRIPT_RUN_POLICY, REQUIRED_SKILL_SECTION_IDS, ROUTER_INDEX_FILES, ROUTER_INDEX_PROCEDURE_SECTION_PATTERN, SECRET_LIKE_CONTEXT_PATTERNS, SKILL_COMMAND_PERMISSION_CLAIM_PATTERNS, SKILL_INDEX_PATH, SKILL_PACK_ID_PATTERN, SKILL_RESOURCE_MANIFEST, SKILL_RESOURCE_ROOTS, SKILL_RESOURCE_TYPE_BY_ROOT, SKILL_ROUTE_CATEGORY_LABELS, SKILL_ROUTES_METADATA_PATH, SUPPORTED_SKILL_SCHEMA_VERSION, TEST_AUTHORING_BOOLEAN_FIELDS, VERIFICATION_SELECTION_BOOLEAN_FIELDS, VOLATILE_REPO_MAP_PATTERNS } from './constants.js';
21
21
  import { hasOwn, isPositiveInteger, isSafeRelativePath, pushStrictIssue, pushStrictWarning, validateAllowedStringField, validateBooleanField, validateExactStringArrayField, validateNestedTable, validatePathArrayField, validatePathField, validatePositiveIntegerField, validateRequiredFiles, validateRequiredPathField, validateRequiredStringField, validateStringArrayField, validateStringArrayMembers, validateStringField, validateTable, validateToml, validateWorkspaceRoots } from './primitives.js';
22
22
  import { isConfiguredCommandIntent, isDeclaredCommandIntent } from './command-intents.js';
23
+ import { parseSimpleFrontmatter, readFrontmatterList, readSkillSectionIds } from './frontmatter.js';
24
+ import { readStrictMustflowText } from './safe-read.js';
23
25
  import { validateStrictTestSelectionConfig } from './test-selection.js';
24
26
  import { getDefaultTemplate, getTemplateFiles } from '../templates.js';
25
27
  export { describeCheckIssues, getCheckIssueId, } from '../../../core/check-issues.js';
@@ -481,90 +483,6 @@ function validateSkills(projectRoot, issues) {
481
483
  }
482
484
  }
483
485
  }
484
- function readSkillSectionIds(content) {
485
- return new Set([...content.matchAll(SKILL_SECTION_MARKER_PATTERN)].map((match) => match[1]));
486
- }
487
- function findFrontmatterEnd(content) {
488
- const match = /\n---(?:\r?\n|$)/u.exec(content.slice(3));
489
- return match ? 3 + match.index : -1;
490
- }
491
- function parseSimpleFrontmatter(content) {
492
- if (!content.startsWith('---')) {
493
- return {};
494
- }
495
- const end = findFrontmatterEnd(content);
496
- if (end === -1) {
497
- return {};
498
- }
499
- const frontmatter = {};
500
- for (const line of content.slice(3, end).split(/\r?\n/)) {
501
- const separatorIndex = line.indexOf(':');
502
- if (separatorIndex === -1) {
503
- continue;
504
- }
505
- const key = line.slice(0, separatorIndex).trim();
506
- const value = line.slice(separatorIndex + 1).trim().replace(/^["']|["']$/g, '');
507
- if (key.length > 0 && value.length > 0) {
508
- frontmatter[key] = value;
509
- }
510
- }
511
- return frontmatter;
512
- }
513
- function readStrictMustflowText(projectRoot, relativePath, issues, options = {}) {
514
- const result = readMustflowTextFileResult(projectRoot, relativePath, options);
515
- if (result.ok) {
516
- return result.content;
517
- }
518
- if (result.exists && result.error) {
519
- pushStrictIssue(issues, `${toPosixPath(relativePath)} could not be read safely: ${result.error}`);
520
- }
521
- return undefined;
522
- }
523
- function readFrontmatterLines(content) {
524
- if (!content.startsWith('---')) {
525
- return [];
526
- }
527
- const end = findFrontmatterEnd(content);
528
- if (end === -1) {
529
- return [];
530
- }
531
- return content
532
- .slice(3, end)
533
- .split(/\n/u)
534
- .map((line) => line.replace(/\r$/u, ''));
535
- }
536
- function stripScalarMarkers(value) {
537
- return value.trim().replace(/^["'`]|["'`]$/g, '').trim();
538
- }
539
- function readFrontmatterList(content, key) {
540
- const lines = readFrontmatterLines(content);
541
- const values = [];
542
- let keyIndent;
543
- for (const line of lines) {
544
- const keyMatch = line.match(new RegExp(`^(\\s*)${key}:\\s*$`, 'u'));
545
- if (keyIndent === undefined) {
546
- if (keyMatch) {
547
- keyIndent = keyMatch[1].length;
548
- }
549
- continue;
550
- }
551
- if (line.trim().length === 0) {
552
- continue;
553
- }
554
- const lineIndent = line.match(/^\s*/u)?.[0].length ?? 0;
555
- const itemMatch = line.match(/^\s*-\s+(.+)$/u);
556
- if (lineIndent <= keyIndent && !itemMatch) {
557
- break;
558
- }
559
- if (itemMatch) {
560
- const value = stripScalarMarkers(itemMatch[1]);
561
- if (value.length > 0) {
562
- values.push(value);
563
- }
564
- }
565
- }
566
- return values;
567
- }
568
486
  function validateContextDocuments(projectRoot, issues) {
569
487
  const contextRoot = path.join(projectRoot, '.mustflow', 'context');
570
488
  const contextFiles = listFilesRecursive(contextRoot).filter((relativePath) => relativePath.endsWith('.md'));
@@ -0,0 +1,13 @@
1
+ import { toPosixPath } from '../filesystem.js';
2
+ import { readMustflowTextFileResult } from '../mustflow-read.js';
3
+ import { pushStrictIssue } from './primitives.js';
4
+ export function readStrictMustflowText(projectRoot, relativePath, issues, options = {}) {
5
+ const result = readMustflowTextFileResult(projectRoot, relativePath, options);
6
+ if (result.ok) {
7
+ return result.content;
8
+ }
9
+ if (result.exists && result.error) {
10
+ pushStrictIssue(issues, `${toPosixPath(relativePath)} could not be read safely: ${result.error}`);
11
+ }
12
+ return undefined;
13
+ }
@@ -9,6 +9,7 @@ const LOCK_MUTEX_STALE_MS = 30_000;
9
9
  const LOCK_MUTEX_WAIT_MS = 1_000;
10
10
  const LOCK_MUTEX_SLEEP_MS = 25;
11
11
  const LOCK_MUTEX_SLEEP_BUFFER = new Int32Array(new SharedArrayBuffer(4));
12
+ const LOCK_MUTEX_RECOVERY_DIRECTORY = 'recovery';
12
13
  function sleep(milliseconds) {
13
14
  Atomics.wait(LOCK_MUTEX_SLEEP_BUFFER, 0, 0, milliseconds);
14
15
  }
@@ -24,6 +25,9 @@ function activeLockDirectory(projectRoot) {
24
25
  function activeLockMutexDirectory(projectRoot) {
25
26
  return path.join(activeLockRoot(projectRoot), 'mutex');
26
27
  }
28
+ function activeLockMutexRecoveryDirectory(mutex) {
29
+ return path.join(mutex, LOCK_MUTEX_RECOVERY_DIRECTORY);
30
+ }
27
31
  function normalizeEffect(effect) {
28
32
  return {
29
33
  source: effect.source,
@@ -192,6 +196,95 @@ function createRecord(projectRoot, intentName, effects, commandHash) {
192
196
  writes: [...new Set(writes)],
193
197
  };
194
198
  }
199
+ function readMutexOwner(ownerPath) {
200
+ try {
201
+ const owner = JSON.parse(readFileSync(ownerPath, 'utf8'));
202
+ if (typeof owner.started_at !== 'string' || typeof owner.token !== 'string') {
203
+ return null;
204
+ }
205
+ return {
206
+ pid: Number(owner.pid),
207
+ startedAt: owner.started_at,
208
+ token: owner.token,
209
+ };
210
+ }
211
+ catch {
212
+ return null;
213
+ }
214
+ }
215
+ function sameMutexOwner(left, right) {
216
+ return right !== null && left.pid === right.pid && left.startedAt === right.startedAt && left.token === right.token;
217
+ }
218
+ function mutexOwnerIsStale(owner) {
219
+ const ownerStartedAt = Date.parse(owner.startedAt);
220
+ const staleByAge = Number.isFinite(ownerStartedAt) && Date.now() - ownerStartedAt > LOCK_MUTEX_STALE_MS;
221
+ return !isProcessLive(owner.pid) || staleByAge;
222
+ }
223
+ function beginMutexRecovery(mutex) {
224
+ const recoveryPath = activeLockMutexRecoveryDirectory(mutex);
225
+ try {
226
+ mkdirSync(recoveryPath);
227
+ }
228
+ catch (error) {
229
+ if (!error || typeof error !== 'object' || !('code' in error) || error.code !== 'EEXIST') {
230
+ throw error;
231
+ }
232
+ try {
233
+ const recoveryStat = statSync(recoveryPath);
234
+ if (Date.now() - recoveryStat.mtimeMs > LOCK_MUTEX_STALE_MS) {
235
+ rmSync(recoveryPath, { recursive: true, force: true });
236
+ }
237
+ }
238
+ catch {
239
+ // A concurrent recovery may have already finished.
240
+ }
241
+ return null;
242
+ }
243
+ let active = true;
244
+ return () => {
245
+ if (!active) {
246
+ return;
247
+ }
248
+ active = false;
249
+ rmSync(recoveryPath, { recursive: true, force: true });
250
+ };
251
+ }
252
+ function recoverStaleMutexWithOwner(mutex, ownerPath, staleOwner) {
253
+ const releaseRecovery = beginMutexRecovery(mutex);
254
+ if (!releaseRecovery) {
255
+ return false;
256
+ }
257
+ try {
258
+ if (!sameMutexOwner(staleOwner, readMutexOwner(ownerPath)) || !mutexOwnerIsStale(staleOwner)) {
259
+ return false;
260
+ }
261
+ rmSync(mutex, { recursive: true, force: true });
262
+ return true;
263
+ }
264
+ finally {
265
+ releaseRecovery();
266
+ }
267
+ }
268
+ function recoverStaleMutexWithoutOwner(mutex) {
269
+ const releaseRecovery = beginMutexRecovery(mutex);
270
+ if (!releaseRecovery) {
271
+ return false;
272
+ }
273
+ try {
274
+ const ownerPath = path.join(mutex, 'owner.json');
275
+ if (readMutexOwner(ownerPath) !== null) {
276
+ return false;
277
+ }
278
+ rmSync(mutex, { recursive: true, force: true });
279
+ return true;
280
+ }
281
+ catch {
282
+ return false;
283
+ }
284
+ finally {
285
+ releaseRecovery();
286
+ }
287
+ }
195
288
  function acquireMutex(projectRoot) {
196
289
  const root = activeLockRoot(projectRoot);
197
290
  const mutex = activeLockMutexDirectory(projectRoot);
@@ -227,22 +320,29 @@ function acquireMutex(projectRoot) {
227
320
  throw error;
228
321
  }
229
322
  if (Date.now() - startedAt > LOCK_MUTEX_WAIT_MS) {
230
- try {
231
- const owner = JSON.parse(readFileSync(ownerPath, 'utf8'));
232
- const ownerPid = Number(owner.pid);
233
- const ownerStartedAt = typeof owner.started_at === 'string' ? Date.parse(owner.started_at) : Number.NaN;
234
- const staleByAge = Number.isFinite(ownerStartedAt) && Date.now() - ownerStartedAt > LOCK_MUTEX_STALE_MS;
235
- if (!isProcessLive(ownerPid) || staleByAge) {
236
- rmSync(mutex, { recursive: true, force: true });
323
+ const owner = readMutexOwner(ownerPath);
324
+ if (owner) {
325
+ if (mutexOwnerIsStale(owner) && recoverStaleMutexWithOwner(mutex, ownerPath, owner)) {
237
326
  startedAt = Date.now();
238
327
  continue;
239
328
  }
240
329
  }
241
- catch {
330
+ else {
331
+ const recoveryPath = activeLockMutexRecoveryDirectory(mutex);
332
+ try {
333
+ const recoveryStat = statSync(recoveryPath);
334
+ if (Date.now() - recoveryStat.mtimeMs <= LOCK_MUTEX_STALE_MS) {
335
+ throw new Error('active_run_lock_mutex_busy');
336
+ }
337
+ }
338
+ catch (recoveryError) {
339
+ if (recoveryError instanceof Error && recoveryError.message === 'active_run_lock_mutex_busy') {
340
+ throw recoveryError;
341
+ }
342
+ }
242
343
  try {
243
344
  const mutexStat = statSync(mutex);
244
- if (Date.now() - mutexStat.mtimeMs > LOCK_MUTEX_STALE_MS) {
245
- rmSync(mutex, { recursive: true, force: true });
345
+ if (Date.now() - mutexStat.mtimeMs > LOCK_MUTEX_STALE_MS && recoverStaleMutexWithoutOwner(mutex)) {
246
346
  startedAt = Date.now();
247
347
  continue;
248
348
  }
@@ -281,12 +281,25 @@ function serializedHistorySize(samples, today) {
281
281
  Buffer.byteLength(serialize(createSummary(samples, today)), 'utf8'));
282
282
  }
283
283
  function enforceSizeLimit(samples, today) {
284
- if (serializedHistorySize(samples, today) <= MAX_TOTAL_BYTES) {
284
+ const currentSize = serializedHistorySize(samples, today);
285
+ if (currentSize <= MAX_TOTAL_BYTES) {
285
286
  return samples;
286
287
  }
288
+ const averageBytesPerSample = Math.max(1, currentSize / Math.max(1, samples.length));
289
+ const estimatedDropCount = Math.floor((currentSize - MAX_TOTAL_BYTES) / averageBytesPerSample);
287
290
  let low = 1;
288
291
  let high = samples.length;
289
292
  let firstFittingIndex = samples.length;
293
+ const probeIndex = Math.max(1, Math.min(samples.length, estimatedDropCount));
294
+ if (probeIndex > 1) {
295
+ if (serializedHistorySize(samples.slice(probeIndex), today) <= MAX_TOTAL_BYTES) {
296
+ firstFittingIndex = probeIndex;
297
+ high = probeIndex - 1;
298
+ }
299
+ else {
300
+ low = probeIndex + 1;
301
+ }
302
+ }
290
303
  while (low <= high) {
291
304
  const middle = Math.floor((low + high) / 2);
292
305
  const candidate = samples.slice(middle);
@@ -99,7 +99,7 @@ function gitDiffLinesByPath(projectRoot, relativePaths) {
99
99
  if (uniquePaths.length === 0) {
100
100
  return new Map();
101
101
  }
102
- const result = spawnSync('git', ['diff', '--no-ext-diff', '--unified=0', '--', ...uniquePaths], {
102
+ const result = spawnSync('git', ['diff', 'HEAD', '--no-ext-diff', '--unified=0', '--', ...uniquePaths], {
103
103
  cwd: projectRoot,
104
104
  encoding: 'utf8',
105
105
  env: createCommandEnv(projectRoot, { policy: 'minimal', allowlist: [] }),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mustflow",
3
- "version": "2.31.0",
3
+ "version": "2.37.1",
4
4
  "description": "Agent workflow documents and CLI for mustflow repository roots.",
5
5
  "type": "module",
6
6
  "license": "MIT-0",
@@ -10,8 +10,8 @@ status_values = ["current", "stale", "needs_review", "missing"]
10
10
  [documents."agents.root"]
11
11
  source = "locales/en/AGENTS.md"
12
12
  source_locale = "en"
13
- revision = 14
14
- translations.ko = { path = "locales/ko/AGENTS.md", source_revision = 14, status = "current" }
13
+ revision = 15
14
+ translations.ko = { path = "locales/ko/AGENTS.md", source_revision = 15, status = "current" }
15
15
  translations.zh = { path = "locales/zh/AGENTS.md", source_revision = 11, status = "needs_review" }
16
16
  translations.es = { path = "locales/es/AGENTS.md", source_revision = 11, status = "needs_review" }
17
17
  translations.fr = { path = "locales/fr/AGENTS.md", source_revision = 11, status = "needs_review" }
@@ -40,8 +40,8 @@ translations.hi = { path = "locales/hi/.mustflow/context/PROJECT.md", source_rev
40
40
  [documents."docs.agent-workflow"]
41
41
  source = "locales/en/.mustflow/docs/agent-workflow.md"
42
42
  source_locale = "en"
43
- revision = 19
44
- translations.ko = { path = "locales/ko/.mustflow/docs/agent-workflow.md", source_revision = 19, status = "current" }
43
+ revision = 20
44
+ translations.ko = { path = "locales/ko/.mustflow/docs/agent-workflow.md", source_revision = 20, status = "current" }
45
45
  translations.zh = { path = "locales/zh/.mustflow/docs/agent-workflow.md", source_revision = 18, status = "needs_review" }
46
46
  translations.es = { path = "locales/es/.mustflow/docs/agent-workflow.md", source_revision = 18, status = "needs_review" }
47
47
  translations.fr = { path = "locales/fr/.mustflow/docs/agent-workflow.md", source_revision = 18, status = "needs_review" }
@@ -62,13 +62,13 @@ translations = {}
62
62
  [documents."skills.index"]
63
63
  source = "locales/en/.mustflow/skills/INDEX.md"
64
64
  source_locale = "en"
65
- revision = 99
65
+ revision = 108
66
66
  translations = {}
67
67
 
68
68
  [documents."skill.adapter-boundary"]
69
69
  source = "locales/en/.mustflow/skills/adapter-boundary/SKILL.md"
70
70
  source_locale = "en"
71
- revision = 11
71
+ revision = 12
72
72
  translations = {}
73
73
 
74
74
  [documents."skill.artifact-integrity-check"]
@@ -86,6 +86,18 @@ translations = {}
86
86
  [documents."skill.api-contract-change"]
87
87
  source = "locales/en/.mustflow/skills/api-contract-change/SKILL.md"
88
88
  source_locale = "en"
89
+ revision = 2
90
+ translations = {}
91
+
92
+ [documents."skill.backend-reliability-change"]
93
+ source = "locales/en/.mustflow/skills/backend-reliability-change/SKILL.md"
94
+ source_locale = "en"
95
+ revision = 1
96
+ translations = {}
97
+
98
+ [documents."skill.http-delivery-streaming"]
99
+ source = "locales/en/.mustflow/skills/http-delivery-streaming/SKILL.md"
100
+ source_locale = "en"
89
101
  revision = 1
90
102
  translations = {}
91
103
 
@@ -101,6 +113,12 @@ source_locale = "en"
101
113
  revision = 6
102
114
  translations = {}
103
115
 
116
+ [documents."skill.ai-generated-code-hardening"]
117
+ source = "locales/en/.mustflow/skills/ai-generated-code-hardening/SKILL.md"
118
+ source_locale = "en"
119
+ revision = 1
120
+ translations = {}
121
+
104
122
  [documents."skill.contract-sync-check"]
105
123
  source = "locales/en/.mustflow/skills/contract-sync-check/SKILL.md"
106
124
  source_locale = "en"
@@ -152,13 +170,13 @@ translations = {}
152
170
  [documents."skill.dependency-upgrade-review"]
153
171
  source = "locales/en/.mustflow/skills/dependency-upgrade-review/SKILL.md"
154
172
  source_locale = "en"
155
- revision = 1
173
+ revision = 3
156
174
  translations = {}
157
175
 
158
176
  [documents."skill.version-freshness-check"]
159
177
  source = "locales/en/.mustflow/skills/version-freshness-check/SKILL.md"
160
178
  source_locale = "en"
161
- revision = 1
179
+ revision = 6
162
180
  translations = {}
163
181
 
164
182
  [documents."skill.line-ending-hygiene"]
@@ -173,6 +191,12 @@ source_locale = "en"
173
191
  revision = 4
174
192
  translations = {}
175
193
 
194
+ [documents."skill.frontend-render-stability"]
195
+ source = "locales/en/.mustflow/skills/frontend-render-stability/SKILL.md"
196
+ source_locale = "en"
197
+ revision = 2
198
+ translations = {}
199
+
176
200
  [documents."skill.diff-risk-review"]
177
201
  source = "locales/en/.mustflow/skills/diff-risk-review/SKILL.md"
178
202
  source_locale = "en"
@@ -185,6 +209,12 @@ source_locale = "en"
185
209
  revision = 3
186
210
  translations = {}
187
211
 
212
+ [documents."skill.heuristic-candidate-selection"]
213
+ source = "locales/en/.mustflow/skills/heuristic-candidate-selection/SKILL.md"
214
+ source_locale = "en"
215
+ revision = 1
216
+ translations = {}
217
+
188
218
  [documents."skill.astro-code-change"]
189
219
  source = "locales/en/.mustflow/skills/astro-code-change/SKILL.md"
190
220
  source_locale = "en"
@@ -194,7 +224,7 @@ translations = {}
194
224
  [documents."skill.auth-permission-change"]
195
225
  source = "locales/en/.mustflow/skills/auth-permission-change/SKILL.md"
196
226
  source_locale = "en"
197
- revision = 1
227
+ revision = 2
198
228
  translations = {}
199
229
 
200
230
  [documents."skill.config-env-change"]
@@ -206,7 +236,7 @@ translations = {}
206
236
  [documents."skill.css-code-change"]
207
237
  source = "locales/en/.mustflow/skills/css-code-change/SKILL.md"
208
238
  source_locale = "en"
209
- revision = 2
239
+ revision = 3
210
240
  translations = {}
211
241
 
212
242
  [documents."skill.bun-code-change"]
@@ -248,7 +278,7 @@ translations = {}
248
278
  [documents."skill.go-code-change"]
249
279
  source = "locales/en/.mustflow/skills/go-code-change/SKILL.md"
250
280
  source_locale = "en"
251
- revision = 2
281
+ revision = 3
252
282
  translations = {}
253
283
 
254
284
  [documents."skill.hono-code-change"]
@@ -260,7 +290,7 @@ translations = {}
260
290
  [documents."skill.html-code-change"]
261
291
  source = "locales/en/.mustflow/skills/html-code-change/SKILL.md"
262
292
  source_locale = "en"
263
- revision = 2
293
+ revision = 3
264
294
  translations = {}
265
295
 
266
296
  [documents."skill.javascript-code-change"]
@@ -278,13 +308,13 @@ translations = {}
278
308
  [documents."skill.python-code-change"]
279
309
  source = "locales/en/.mustflow/skills/python-code-change/SKILL.md"
280
310
  source_locale = "en"
281
- revision = 2
311
+ revision = 3
282
312
  translations = {}
283
313
 
284
314
  [documents."skill.rust-code-change"]
285
315
  source = "locales/en/.mustflow/skills/rust-code-change/SKILL.md"
286
316
  source_locale = "en"
287
- revision = 3
317
+ revision = 4
288
318
  translations = {}
289
319
 
290
320
  [documents."skill.runtime-target-selection"]
@@ -308,25 +338,31 @@ translations = {}
308
338
  [documents."skill.tailwind-code-change"]
309
339
  source = "locales/en/.mustflow/skills/tailwind-code-change/SKILL.md"
310
340
  source_locale = "en"
311
- revision = 2
341
+ revision = 3
312
342
  translations = {}
313
343
 
314
344
  [documents."skill.tauri-code-change"]
315
345
  source = "locales/en/.mustflow/skills/tauri-code-change/SKILL.md"
316
346
  source_locale = "en"
317
- revision = 2
347
+ revision = 3
318
348
  translations = {}
319
349
 
320
350
  [documents."skill.typescript-code-change"]
321
351
  source = "locales/en/.mustflow/skills/typescript-code-change/SKILL.md"
322
352
  source_locale = "en"
323
- revision = 2
353
+ revision = 3
324
354
  translations = {}
325
355
 
326
356
  [documents."skill.unocss-code-change"]
327
357
  source = "locales/en/.mustflow/skills/unocss-code-change/SKILL.md"
328
358
  source_locale = "en"
329
- revision = 2
359
+ revision = 3
360
+ translations = {}
361
+
362
+ [documents."skill.service-boundary-architecture"]
363
+ source = "locales/en/.mustflow/skills/service-boundary-architecture/SKILL.md"
364
+ source_locale = "en"
365
+ revision = 1
330
366
  translations = {}
331
367
 
332
368
  [documents."skill.cli-output-contract-review"]
@@ -469,7 +505,7 @@ translations = {}
469
505
  [documents."skill.performance-budget-check"]
470
506
  source = "locales/en/.mustflow/skills/performance-budget-check/SKILL.md"
471
507
  source_locale = "en"
472
- revision = 19
508
+ revision = 20
473
509
  translations = {}
474
510
 
475
511
  [documents."skill.pattern-scout"]
@@ -478,6 +514,12 @@ source_locale = "en"
478
514
  revision = 2
479
515
  translations = {}
480
516
 
517
+ [documents."skill.proactive-risk-surfacing"]
518
+ source = "locales/en/.mustflow/skills/proactive-risk-surfacing/SKILL.md"
519
+ source_locale = "en"
520
+ revision = 1
521
+ translations = {}
522
+
481
523
  [documents."skill.process-execution-safety"]
482
524
  source = "locales/en/.mustflow/skills/process-execution-safety/SKILL.md"
483
525
  source_locale = "en"
@@ -559,13 +601,13 @@ translations = {}
559
601
  [documents."skill.security-privacy-review"]
560
602
  source = "locales/en/.mustflow/skills/security-privacy-review/SKILL.md"
561
603
  source_locale = "en"
562
- revision = 21
604
+ revision = 22
563
605
  translations = {}
564
606
 
565
607
  [documents."skill.security-regression-tests"]
566
608
  source = "locales/en/.mustflow/skills/security-regression-tests/SKILL.md"
567
609
  source_locale = "en"
568
- revision = 11
610
+ revision = 12
569
611
  translations = {}
570
612
 
571
613
  [documents."skill.search-ad-content-authoring"]