mustflow 2.32.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 (42) 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 +55 -19
  15. package/templates/default/locales/en/.mustflow/docs/agent-workflow.md +15 -7
  16. package/templates/default/locales/en/.mustflow/skills/INDEX.md +20 -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/html-code-change/SKILL.md +20 -13
  27. package/templates/default/locales/en/.mustflow/skills/http-delivery-streaming/SKILL.md +205 -0
  28. package/templates/default/locales/en/.mustflow/skills/performance-budget-check/SKILL.md +9 -7
  29. package/templates/default/locales/en/.mustflow/skills/proactive-risk-surfacing/SKILL.md +198 -0
  30. package/templates/default/locales/en/.mustflow/skills/python-code-change/SKILL.md +27 -11
  31. package/templates/default/locales/en/.mustflow/skills/routes.toml +37 -1
  32. package/templates/default/locales/en/.mustflow/skills/rust-code-change/SKILL.md +41 -17
  33. package/templates/default/locales/en/.mustflow/skills/service-boundary-architecture/SKILL.md +167 -0
  34. package/templates/default/locales/en/.mustflow/skills/tailwind-code-change/SKILL.md +37 -23
  35. package/templates/default/locales/en/.mustflow/skills/tauri-code-change/SKILL.md +27 -10
  36. package/templates/default/locales/en/.mustflow/skills/typescript-code-change/SKILL.md +22 -4
  37. package/templates/default/locales/en/.mustflow/skills/unocss-code-change/SKILL.md +34 -15
  38. package/templates/default/locales/en/.mustflow/skills/version-freshness-check/SKILL.md +29 -5
  39. package/templates/default/locales/en/AGENTS.md +3 -2
  40. package/templates/default/locales/ko/.mustflow/docs/agent-workflow.md +13 -8
  41. package/templates/default/locales/ko/AGENTS.md +2 -2
  42. package/templates/default/manifest.toml +37 -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.32.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"
@@ -200,7 +224,7 @@ translations = {}
200
224
  [documents."skill.auth-permission-change"]
201
225
  source = "locales/en/.mustflow/skills/auth-permission-change/SKILL.md"
202
226
  source_locale = "en"
203
- revision = 1
227
+ revision = 2
204
228
  translations = {}
205
229
 
206
230
  [documents."skill.config-env-change"]
@@ -212,7 +236,7 @@ translations = {}
212
236
  [documents."skill.css-code-change"]
213
237
  source = "locales/en/.mustflow/skills/css-code-change/SKILL.md"
214
238
  source_locale = "en"
215
- revision = 2
239
+ revision = 3
216
240
  translations = {}
217
241
 
218
242
  [documents."skill.bun-code-change"]
@@ -254,7 +278,7 @@ translations = {}
254
278
  [documents."skill.go-code-change"]
255
279
  source = "locales/en/.mustflow/skills/go-code-change/SKILL.md"
256
280
  source_locale = "en"
257
- revision = 2
281
+ revision = 3
258
282
  translations = {}
259
283
 
260
284
  [documents."skill.hono-code-change"]
@@ -266,7 +290,7 @@ translations = {}
266
290
  [documents."skill.html-code-change"]
267
291
  source = "locales/en/.mustflow/skills/html-code-change/SKILL.md"
268
292
  source_locale = "en"
269
- revision = 2
293
+ revision = 3
270
294
  translations = {}
271
295
 
272
296
  [documents."skill.javascript-code-change"]
@@ -284,13 +308,13 @@ translations = {}
284
308
  [documents."skill.python-code-change"]
285
309
  source = "locales/en/.mustflow/skills/python-code-change/SKILL.md"
286
310
  source_locale = "en"
287
- revision = 2
311
+ revision = 3
288
312
  translations = {}
289
313
 
290
314
  [documents."skill.rust-code-change"]
291
315
  source = "locales/en/.mustflow/skills/rust-code-change/SKILL.md"
292
316
  source_locale = "en"
293
- revision = 3
317
+ revision = 4
294
318
  translations = {}
295
319
 
296
320
  [documents."skill.runtime-target-selection"]
@@ -314,25 +338,31 @@ translations = {}
314
338
  [documents."skill.tailwind-code-change"]
315
339
  source = "locales/en/.mustflow/skills/tailwind-code-change/SKILL.md"
316
340
  source_locale = "en"
317
- revision = 2
341
+ revision = 3
318
342
  translations = {}
319
343
 
320
344
  [documents."skill.tauri-code-change"]
321
345
  source = "locales/en/.mustflow/skills/tauri-code-change/SKILL.md"
322
346
  source_locale = "en"
323
- revision = 2
347
+ revision = 3
324
348
  translations = {}
325
349
 
326
350
  [documents."skill.typescript-code-change"]
327
351
  source = "locales/en/.mustflow/skills/typescript-code-change/SKILL.md"
328
352
  source_locale = "en"
329
- revision = 2
353
+ revision = 3
330
354
  translations = {}
331
355
 
332
356
  [documents."skill.unocss-code-change"]
333
357
  source = "locales/en/.mustflow/skills/unocss-code-change/SKILL.md"
334
358
  source_locale = "en"
335
- 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
336
366
  translations = {}
337
367
 
338
368
  [documents."skill.cli-output-contract-review"]
@@ -475,7 +505,7 @@ translations = {}
475
505
  [documents."skill.performance-budget-check"]
476
506
  source = "locales/en/.mustflow/skills/performance-budget-check/SKILL.md"
477
507
  source_locale = "en"
478
- revision = 19
508
+ revision = 20
479
509
  translations = {}
480
510
 
481
511
  [documents."skill.pattern-scout"]
@@ -484,6 +514,12 @@ source_locale = "en"
484
514
  revision = 2
485
515
  translations = {}
486
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
+
487
523
  [documents."skill.process-execution-safety"]
488
524
  source = "locales/en/.mustflow/skills/process-execution-safety/SKILL.md"
489
525
  source_locale = "en"
@@ -2,7 +2,7 @@
2
2
  mustflow_doc: docs.agent-workflow
3
3
  locale: en
4
4
  canonical: true
5
- revision: 19
5
+ revision: 20
6
6
  lifecycle: mustflow-owned
7
7
  authority: workflow-policy
8
8
  ---
@@ -139,15 +139,23 @@ Do not write turn counters, message counts, or session activity into the reposit
139
139
 
140
140
  Do not store hidden reasoning, secrets, full chat transcripts, full terminal output, raw event logs, or raw command logs in the project. If a host creates compacted summaries in the future, they must be source-linked and remain lower authority than current files and direct user instructions.
141
141
 
142
- ## Harness Contract Boundary
142
+ ## Harness and Runtime Surfaces
143
143
 
144
- mustflow is not an autonomous agent runtime. It is a repository-local contract layer for agent harnesses.
144
+ mustflow starts from repository-local workflow files and command execution boundaries, but it may
145
+ grow optional runtime, coordination, work-item, adapter, and harness surfaces. New surfaces must be
146
+ explicit, bounded, and reviewable instead of being smuggled in through prose, generated state, or
147
+ inferred commands.
145
148
 
146
- - Brain contract: `AGENTS.md`, this workflow file, and skill documents define the expected model behavior.
147
- - Hands contract: `.mustflow/config/commands.toml` and `mf run` define safe command execution.
148
- - Session contract: run records, bounded checkpoints, and compact handoffs provide evidence for recovery.
149
+ - Brain surface: `AGENTS.md`, this workflow file, and skill documents define the expected model behavior.
150
+ - Hands surface: `.mustflow/config/commands.toml` and `mf run` define safe command execution.
151
+ - Session surface: run records, bounded checkpoints, compact handoffs, and future work-item records
152
+ provide evidence for recovery.
153
+ - Runtime surface: worker folders, persona systems, fleet orchestration, service processes, and
154
+ autonomous loops require declared lifecycle, retention, isolation, approval, and verification rules.
149
155
 
150
- Do not create worker folders, persona systems, fleet orchestration, raw event logs, or autonomous loops unless the repository explicitly adds those optional surfaces.
156
+ Do not operate runtime or coordination surfaces from implicit conventions. Add or enable them through
157
+ explicit configuration, command contracts, tests, and documentation when the repository chooses to
158
+ support that capability.
151
159
 
152
160
  ## Long-Running Task Phases
153
161