mustflow 2.112.8 → 2.112.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -401,7 +401,7 @@ npx mf init --set git.auto_commit=true
401
401
  - `--agent-lang`: Default language for final agent reports.
402
402
  - `--interactive`: Choose init settings via prompts.
403
403
  - `--yes`: Use default English init settings without prompts.
404
- - `--set`: Set an allowed preference during installation. Supported keys include `git.auto_stage`, `git.auto_commit`, `git.auto_push=false`, `git.commit_message.style`, `git.commit_message.language`, `git.commit_message.max_suggestions`, `git.commit_message.include_body`, `git.commit_message.split_when_multiple_concerns`, `reporting.commit_suggestion.enabled`, `language.memory.summary`, and boolean `release.versioning.*` fields such as `release.versioning.suggest_bump=false`, `verification.selection.*` fields, and `testing.authoring.*` fields. Versioning preferences do not assume a fixed version file; agents must locate the repository-specific version source before suggesting or editing versions. Repositories needing an explicit version source can add `.mustflow/config/versioning.toml`; `mf init` does not install this optional file by default. `git.commit_message.style` accepts `conventional`, `descriptive`, or `gitmoji`; `gitmoji` only changes the suggested message format. `git.commit_message.language` accepts `preserve_existing`, `agent_response`, `docs`, or a locale tag such as `ja`, `de`, or `pt-BR`. `testing.authoring.new_test_policy` accepts `evidence_required`, `manual_approval`, or `broad`.
404
+ - `--set`: Set an allowed preference during installation. Supported keys include `git.auto_stage`, `git.auto_commit`, `git.auto_push=false`, `git.commit_message.style`, `git.commit_message.language`, `git.commit_message.max_suggestions`, `git.commit_message.include_body`, `git.commit_message.gitmoji.map`, `git.commit_message.body.template`, `git.commit_message.body.require_validation_line`, `git.commit_message.split_when_multiple_concerns`, `reporting.commit_suggestion.enabled`, `language.memory.summary`, and boolean `release.versioning.*` fields such as `release.versioning.suggest_bump=false`, `verification.selection.*` fields, and `testing.authoring.*` fields. Versioning preferences do not assume a fixed version file; agents must locate the repository-specific version source before suggesting or editing versions. Repositories needing an explicit version source can add `.mustflow/config/versioning.toml`; `mf init` does not install this optional file by default. `git.commit_message.style` accepts `conventional`, `descriptive`, or `gitmoji`; `gitmoji` changes the suggested message format, while `git.commit_message.gitmoji.map=conventional_default` declares the conventional-type-to-Gitmoji mapping (`feat=✨`, `fix=🐛`, `docs=📝`, `refactor=♻️`, `test=✅`, `chore/build/config=🔧`). `git.commit_message.body.template=summary_validation` asks bodies to summarize the change and validation evidence; `git.commit_message.body.require_validation_line` accepts `true` or `false`. `git.commit_message.language` accepts `preserve_existing`, `agent_response`, `docs`, or a locale tag such as `ja`, `de`, or `pt-BR`. `testing.authoring.new_test_policy` accepts `evidence_required`, `manual_approval`, or `broad`.
405
405
  - `--product-source-locale`, `--product-locale`: Source and target locales for user-facing product strings.
406
406
  - `--lang`: CLI output language. Current values are `en`, `ko`, `zh`, `es`, `fr`, and `hi`.
407
407
 
@@ -8,7 +8,7 @@ import { localeMessage, t } from '../lib/i18n.js';
8
8
  import { isLocaleTag } from '../lib/locale-tags.js';
9
9
  import { MANIFEST_LOCK_RELATIVE_PATH, sha256File } from '../lib/manifest-lock.js';
10
10
  import { formatCliOptionParseError, hasCliOptionToken, parseCliOptions } from '../lib/option-parser.js';
11
- import { isCommitMessageStyle, isTestAuthoringPolicy } from '../lib/preferences-options.js';
11
+ import { isCommitMessageBodyTemplate, isCommitMessageGitmojiMap, isCommitMessageStyle, isTestAuthoringPolicy, } from '../lib/preferences-options.js';
12
12
  import { discoverNestedRepositories, getRepoMapConfig } from '../lib/repo-map.js';
13
13
  import { getDefaultTemplate, getTemplateFiles } from '../lib/templates.js';
14
14
  const MUSTFLOW_BLOCK_START = '<!-- mustflow:start schema=1 -->';
@@ -232,6 +232,43 @@ function createPreferenceOverride(key, value, reporter, lang) {
232
232
  renderedValue: tomlString(value),
233
233
  };
234
234
  }
235
+ if (key === 'git.commit_message.gitmoji.map') {
236
+ if (!isCommitMessageGitmojiMap(value)) {
237
+ reporter.stderr(t(lang, 'init.error.invalidPreferenceValue', { key, value }));
238
+ return undefined;
239
+ }
240
+ return {
241
+ key,
242
+ section: 'git.commit_message.gitmoji',
243
+ field: 'map',
244
+ renderedValue: tomlString(value),
245
+ };
246
+ }
247
+ if (key === 'git.commit_message.body.template') {
248
+ if (!isCommitMessageBodyTemplate(value)) {
249
+ reporter.stderr(t(lang, 'init.error.invalidPreferenceValue', { key, value }));
250
+ return undefined;
251
+ }
252
+ return {
253
+ key,
254
+ section: 'git.commit_message.body',
255
+ field: 'template',
256
+ renderedValue: tomlString(value),
257
+ };
258
+ }
259
+ if (key === 'git.commit_message.body.require_validation_line') {
260
+ const parsed = parseBoolean(value);
261
+ if (parsed === undefined) {
262
+ reporter.stderr(t(lang, 'init.error.invalidPreferenceValue', { key, value }));
263
+ return undefined;
264
+ }
265
+ return {
266
+ key,
267
+ section: 'git.commit_message.body',
268
+ field: 'require_validation_line',
269
+ renderedValue: String(parsed),
270
+ };
271
+ }
235
272
  if (key === 'git.commit_message.split_when_multiple_concerns') {
236
273
  const parsed = parseBoolean(value);
237
274
  if (parsed === undefined) {
@@ -512,6 +512,12 @@ export const enMessages = {
512
512
  "dashboard.setting.git.commit_message.include_body.description.never": "Never include a commit message body; suggest a subject line only.",
513
513
  "dashboard.setting.git.commit_message.include_body.description.when_non_trivial": "Include a body only when the change needs more context than the subject line.",
514
514
  "dashboard.setting.git.commit_message.include_body.description.always": "Always include a commit message body in suggestions.",
515
+ "dashboard.setting.git.commit_message.gitmoji.map": "Gitmoji map",
516
+ "dashboard.setting.git.commit_message.gitmoji.map.description.conventional_default": "Map conventional types to Gitmoji prefixes: feat=✨, fix=🐛, docs=📝, refactor=♻️, test=✅, chore=🔧.",
517
+ "dashboard.setting.git.commit_message.body.template": "Commit body template",
518
+ "dashboard.setting.git.commit_message.body.template.description.summary_validation": "Use a concise body with what changed, why it changed when useful, and validation evidence.",
519
+ "dashboard.setting.git.commit_message.body.require_validation_line": "Require validation line",
520
+ "dashboard.setting.git.commit_message.body.require_validation_line.description": "Ask suggested commit bodies to include the checks or evidence used to validate the change.",
515
521
  "dashboard.setting.git.commit_message.split_when_multiple_concerns": "Suggest split commits",
516
522
  "dashboard.setting.git.commit_message.avoid_sensitive_details": "Avoid sensitive details",
517
523
  "dashboard.setting.git.commit_message.avoid_sensitive_details.description": "Avoid secrets, credentials, personal data, and private incident details.",
@@ -512,6 +512,12 @@ export const esMessages = {
512
512
  "dashboard.setting.git.commit_message.include_body.description.never": "No incluye cuerpo en el mensaje de commit; solo sugiere el asunto.",
513
513
  "dashboard.setting.git.commit_message.include_body.description.when_non_trivial": "Incluye cuerpo solo cuando el cambio necesita más contexto que el asunto.",
514
514
  "dashboard.setting.git.commit_message.include_body.description.always": "Incluye siempre un cuerpo en las sugerencias de mensaje de commit.",
515
+ "dashboard.setting.git.commit_message.gitmoji.map": "Mapa Gitmoji",
516
+ "dashboard.setting.git.commit_message.gitmoji.map.description.conventional_default": "Mapea tipos conventional comunes a prefijos Gitmoji: feat, fix, docs, refactor, test y chore.",
517
+ "dashboard.setting.git.commit_message.body.template": "Plantilla del cuerpo de commit",
518
+ "dashboard.setting.git.commit_message.body.template.description.summary_validation": "Usa un cuerpo conciso con qué cambió, por qué cambió cuando sea útil y evidencia de validación.",
519
+ "dashboard.setting.git.commit_message.body.require_validation_line": "Exigir línea de validación",
520
+ "dashboard.setting.git.commit_message.body.require_validation_line.description": "Pide que los cuerpos sugeridos incluyan las comprobaciones o evidencias usadas para validar el cambio.",
515
521
  "dashboard.setting.git.commit_message.split_when_multiple_concerns": "Sugerir commits separados",
516
522
  "dashboard.setting.git.commit_message.avoid_sensitive_details": "Evitar detalles sensibles",
517
523
  "dashboard.setting.git.commit_message.avoid_sensitive_details.description": "Evita secretos, credenciales, datos personales y detalles privados de incidentes.",
@@ -512,6 +512,12 @@ export const frMessages = {
512
512
  "dashboard.setting.git.commit_message.include_body.description.never": "N'ajoute jamais de corps au message de commit ; suggère seulement le sujet.",
513
513
  "dashboard.setting.git.commit_message.include_body.description.when_non_trivial": "Ajoute un corps seulement quand le changement demande plus de contexte que le sujet.",
514
514
  "dashboard.setting.git.commit_message.include_body.description.always": "Ajoute toujours un corps aux suggestions de message de commit.",
515
+ "dashboard.setting.git.commit_message.gitmoji.map": "Carte Gitmoji",
516
+ "dashboard.setting.git.commit_message.gitmoji.map.description.conventional_default": "Associe les types conventional courants aux préfixes Gitmoji : feat, fix, docs, refactor, test et chore.",
517
+ "dashboard.setting.git.commit_message.body.template": "Modèle de corps de commit",
518
+ "dashboard.setting.git.commit_message.body.template.description.summary_validation": "Utilise un corps concis avec ce qui a changé, pourquoi quand c'est utile, et les preuves de validation.",
519
+ "dashboard.setting.git.commit_message.body.require_validation_line": "Exiger une ligne de validation",
520
+ "dashboard.setting.git.commit_message.body.require_validation_line.description": "Demande aux corps suggérés d'inclure les contrôles ou preuves utilisés pour valider le changement.",
515
521
  "dashboard.setting.git.commit_message.split_when_multiple_concerns": "Suggérer des commits séparés",
516
522
  "dashboard.setting.git.commit_message.avoid_sensitive_details": "Éviter les détails sensibles",
517
523
  "dashboard.setting.git.commit_message.avoid_sensitive_details.description": "Évite les secrets, identifiants, données personnelles et détails d'incident privés.",
@@ -512,6 +512,12 @@ export const hiMessages = {
512
512
  "dashboard.setting.git.commit_message.include_body.description.never": "commit message body कभी न जोड़ें; केवल subject line सुझाएँ।",
513
513
  "dashboard.setting.git.commit_message.include_body.description.when_non_trivial": "body तभी जोड़ें जब बदलाव को subject line से अधिक संदर्भ चाहिए।",
514
514
  "dashboard.setting.git.commit_message.include_body.description.always": "commit message सुझावों में हमेशा body जोड़ें।",
515
+ "dashboard.setting.git.commit_message.gitmoji.map": "Gitmoji map",
516
+ "dashboard.setting.git.commit_message.gitmoji.map.description.conventional_default": "सामान्य conventional types को Gitmoji prefixes से जोड़ें: feat, fix, docs, refactor, test, और chore.",
517
+ "dashboard.setting.git.commit_message.body.template": "Commit body template",
518
+ "dashboard.setting.git.commit_message.body.template.description.summary_validation": "क्या बदला, उपयोगी होने पर क्यों बदला, और validation evidence के साथ छोटा body इस्तेमाल करें.",
519
+ "dashboard.setting.git.commit_message.body.require_validation_line": "Validation line आवश्यक करें",
520
+ "dashboard.setting.git.commit_message.body.require_validation_line.description": "सुझाए गए commit bodies में बदलाव validate करने वाली checks या evidence शामिल करवाएँ.",
515
521
  "dashboard.setting.git.commit_message.split_when_multiple_concerns": "अलग commits सुझाएँ",
516
522
  "dashboard.setting.git.commit_message.avoid_sensitive_details": "संवेदनशील विवरण से बचें",
517
523
  "dashboard.setting.git.commit_message.avoid_sensitive_details.description": "secrets, credentials, personal data और private incident details से बचें।",
@@ -512,6 +512,12 @@ export const koMessages = {
512
512
  "dashboard.setting.git.commit_message.include_body.description.never": "커밋 메시지 본문을 넣지 않습니다. 제목 한 줄만 제안합니다.",
513
513
  "dashboard.setting.git.commit_message.include_body.description.when_non_trivial": "변경 내용을 제목만으로 설명하기 어려울 때만 본문을 함께 제안합니다.",
514
514
  "dashboard.setting.git.commit_message.include_body.description.always": "항상 커밋 메시지 본문을 넣습니다.",
515
+ "dashboard.setting.git.commit_message.gitmoji.map": "Gitmoji 매핑",
516
+ "dashboard.setting.git.commit_message.gitmoji.map.description.conventional_default": "conventional 타입을 Gitmoji 접두어에 연결합니다: feat=✨, fix=🐛, docs=📝, refactor=♻️, test=✅, chore=🔧.",
517
+ "dashboard.setting.git.commit_message.body.template": "커밋 본문 템플릿",
518
+ "dashboard.setting.git.commit_message.body.template.description.summary_validation": "무엇을 바꿨는지, 필요하면 왜 바꿨는지, 어떤 검증을 했는지 간결한 본문으로 제안합니다.",
519
+ "dashboard.setting.git.commit_message.body.require_validation_line": "검증 줄 요구",
520
+ "dashboard.setting.git.commit_message.body.require_validation_line.description": "커밋 본문 제안에 실행한 검사나 검증 근거를 포함하게 합니다.",
515
521
  "dashboard.setting.git.commit_message.split_when_multiple_concerns": "여러 주제면 커밋 분리 제안",
516
522
  "dashboard.setting.git.commit_message.avoid_sensitive_details": "민감한 세부 정보 제외",
517
523
  "dashboard.setting.git.commit_message.avoid_sensitive_details.description": "비밀값, 인증 정보, 개인정보, 비공개 장애 세부사항을 메시지에서 제외합니다.",
@@ -512,6 +512,12 @@ export const zhMessages = {
512
512
  "dashboard.setting.git.commit_message.include_body.description.never": "不包含提交正文;只建议标题行。",
513
513
  "dashboard.setting.git.commit_message.include_body.description.when_non_trivial": "只有当标题行不足以说明变更时,才建议提交正文。",
514
514
  "dashboard.setting.git.commit_message.include_body.description.always": "始终在建议中包含提交正文。",
515
+ "dashboard.setting.git.commit_message.gitmoji.map": "Gitmoji 映射",
516
+ "dashboard.setting.git.commit_message.gitmoji.map.description.conventional_default": "将常见 conventional 类型映射到 Gitmoji 前缀:feat、fix、docs、refactor、test 和 chore。",
517
+ "dashboard.setting.git.commit_message.body.template": "提交正文模板",
518
+ "dashboard.setting.git.commit_message.body.template.description.summary_validation": "使用简洁正文说明改了什么、必要时说明原因,并包含验证证据。",
519
+ "dashboard.setting.git.commit_message.body.require_validation_line": "要求验证行",
520
+ "dashboard.setting.git.commit_message.body.require_validation_line.description": "要求建议的提交正文包含用于验证改动的检查或证据。",
515
521
  "dashboard.setting.git.commit_message.split_when_multiple_concerns": "建议拆分提交",
516
522
  "dashboard.setting.git.commit_message.avoid_sensitive_details": "避免敏感细节",
517
523
  "dashboard.setting.git.commit_message.avoid_sensitive_details.description": "避免写入密钥、凭据、个人数据和非公开事故细节。",
@@ -286,6 +286,12 @@ export const DASHBOARD_UI_MESSAGE_KEYS = [
286
286
  'dashboard.setting.git.commit_message.include_body.description.never',
287
287
  'dashboard.setting.git.commit_message.include_body.description.when_non_trivial',
288
288
  'dashboard.setting.git.commit_message.include_body.description.always',
289
+ 'dashboard.setting.git.commit_message.gitmoji.map',
290
+ 'dashboard.setting.git.commit_message.gitmoji.map.description.conventional_default',
291
+ 'dashboard.setting.git.commit_message.body.template',
292
+ 'dashboard.setting.git.commit_message.body.template.description.summary_validation',
293
+ 'dashboard.setting.git.commit_message.body.require_validation_line',
294
+ 'dashboard.setting.git.commit_message.body.require_validation_line.description',
289
295
  'dashboard.setting.git.commit_message.split_when_multiple_concerns',
290
296
  'dashboard.setting.git.commit_message.avoid_sensitive_details',
291
297
  'dashboard.setting.git.commit_message.avoid_sensitive_details.description',
@@ -4,7 +4,7 @@ import { isRecord } from './command-contract.js';
4
4
  import { readUtf8FileInsideWithoutSymlinks, writeUtf8FileInsideWithoutSymlinks } from './filesystem.js';
5
5
  import { isLocaleTag } from './locale-tags.js';
6
6
  import { ensureManifestLockTargetSafe, markManifestLockFileCustomized } from './manifest-lock.js';
7
- import { COMMIT_MESSAGE_STYLES, TEST_AUTHORING_POLICIES } from './preferences-options.js';
7
+ import { COMMIT_MESSAGE_BODY_TEMPLATES, COMMIT_MESSAGE_GITMOJI_MAPS, COMMIT_MESSAGE_STYLES, TEST_AUTHORING_POLICIES, } from './preferences-options.js';
8
8
  import { parseTomlText } from './toml.js';
9
9
  const PREFERENCES_RELATIVE_PATH = '.mustflow/config/preferences.toml';
10
10
  export const DASHBOARD_PREFERENCE_SETTINGS = [
@@ -65,6 +65,29 @@ export const DASHBOARD_PREFERENCE_SETTINGS = [
65
65
  fallback: 'when_non_trivial',
66
66
  options: ['never', 'when_non_trivial', 'always'],
67
67
  },
68
+ {
69
+ id: 'git.commit_message.gitmoji.map',
70
+ path: ['git', 'commit_message', 'gitmoji', 'map'],
71
+ label: 'Gitmoji map',
72
+ kind: 'select',
73
+ fallback: 'conventional_default',
74
+ options: COMMIT_MESSAGE_GITMOJI_MAPS,
75
+ },
76
+ {
77
+ id: 'git.commit_message.body.template',
78
+ path: ['git', 'commit_message', 'body', 'template'],
79
+ label: 'Commit body template',
80
+ kind: 'select',
81
+ fallback: 'summary_validation',
82
+ options: COMMIT_MESSAGE_BODY_TEMPLATES,
83
+ },
84
+ {
85
+ id: 'git.commit_message.body.require_validation_line',
86
+ path: ['git', 'commit_message', 'body', 'require_validation_line'],
87
+ label: 'Require validation line',
88
+ kind: 'boolean',
89
+ fallback: true,
90
+ },
68
91
  {
69
92
  id: 'git.commit_message.split_when_multiple_concerns',
70
93
  path: ['git', 'commit_message', 'split_when_multiple_concerns'],
@@ -1,8 +1,16 @@
1
1
  export const COMMIT_MESSAGE_STYLES = ['conventional', 'descriptive', 'gitmoji'];
2
+ export const COMMIT_MESSAGE_GITMOJI_MAPS = ['conventional_default'];
3
+ export const COMMIT_MESSAGE_BODY_TEMPLATES = ['summary_validation'];
2
4
  export const TEST_AUTHORING_POLICIES = ['evidence_required', 'manual_approval', 'broad'];
3
5
  export function isCommitMessageStyle(value) {
4
6
  return COMMIT_MESSAGE_STYLES.includes(value);
5
7
  }
8
+ export function isCommitMessageGitmojiMap(value) {
9
+ return COMMIT_MESSAGE_GITMOJI_MAPS.includes(value);
10
+ }
11
+ export function isCommitMessageBodyTemplate(value) {
12
+ return COMMIT_MESSAGE_BODY_TEMPLATES.includes(value);
13
+ }
6
14
  export function isTestAuthoringPolicy(value) {
7
15
  return TEST_AUTHORING_POLICIES.includes(value);
8
16
  }
@@ -1,4 +1,4 @@
1
- import { COMMIT_MESSAGE_STYLES, TEST_AUTHORING_POLICIES } from '../preferences-options.js';
1
+ import { COMMIT_MESSAGE_BODY_TEMPLATES, COMMIT_MESSAGE_GITMOJI_MAPS, COMMIT_MESSAGE_STYLES, TEST_AUTHORING_POLICIES, } from '../preferences-options.js';
2
2
  import { VERSION_SOURCE_AUTHORITIES, VERSION_SOURCE_KINDS, } from '../../../core/version-sources.js';
3
3
  import { SKILL_ROUTE_CATEGORY_LABELS } from '../../../core/skill-route-alignment.js';
4
4
  export { SKILL_ROUTE_CATEGORY_LABELS };
@@ -66,6 +66,8 @@ export const FORBIDDEN_RELEASE_VERSIONING_CONTRACT_FIELDS = [
66
66
  export const ALLOWED_VERSION_SOURCE_KINDS = new Set(VERSION_SOURCE_KINDS);
67
67
  export const ALLOWED_VERSION_SOURCE_AUTHORITIES = new Set(VERSION_SOURCE_AUTHORITIES);
68
68
  export const ALLOWED_COMMIT_MESSAGE_STYLES = new Set(COMMIT_MESSAGE_STYLES);
69
+ export const ALLOWED_COMMIT_MESSAGE_GITMOJI_MAPS = new Set(COMMIT_MESSAGE_GITMOJI_MAPS);
70
+ export const ALLOWED_COMMIT_MESSAGE_BODY_TEMPLATES = new Set(COMMIT_MESSAGE_BODY_TEMPLATES);
69
71
  export const VERIFICATION_SELECTION_BOOLEAN_FIELDS = [
70
72
  'prefer_related_tests',
71
73
  'skip_docs_only_full_test',
@@ -21,7 +21,7 @@ import { VERSIONING_CONFIG_PATH, detectVersionSourcePaths, readDeclaredVersionSo
21
21
  import { normalizeTechnologyPreferencesTable, TECHNOLOGY_CONFIG_RELATIVE_PATH, } from '../../../core/technology-preferences.js';
22
22
  import { isPromptCacheStableLeafSkillSurface, measurePromptCacheReferenceBlockBytes, } from '../../../core/prompt-cache-rendering.js';
23
23
  import { validateSkillRouteFixtures } from '../../../core/skill-route-fixtures.js';
24
- 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_REPO_FLOW_DEGRADED_VALUES, 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, REPO_FLOW_DOC_ID, REPO_FLOW_GENERATOR, REPO_FLOW_LIFECYCLE, REPO_FLOW_PRIVACY_MODE, REPO_FLOW_RELATIVE_ROOT, REPO_FLOW_REMOTE_OR_BRANCH_PATTERNS, REPO_FLOW_SOURCE_FINGERPRINT_PATTERN, REPO_FLOW_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_FLOW_PATTERNS, VOLATILE_REPO_MAP_PATTERNS } from './constants.js';
24
+ import { ALLOWED_APPROVAL_ACTIONS, ALLOWED_APPROVAL_GATES, ALLOWED_BUDGET_LIMIT_ACTIONS, ALLOWED_CAPABILITY_STATES, ALLOWED_COMMIT_MESSAGE_BODY_TEMPLATES, ALLOWED_COMMIT_MESSAGE_GITMOJI_MAPS, 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_REPO_FLOW_DEGRADED_VALUES, 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, REPO_FLOW_DOC_ID, REPO_FLOW_GENERATOR, REPO_FLOW_LIFECYCLE, REPO_FLOW_PRIVACY_MODE, REPO_FLOW_RELATIVE_ROOT, REPO_FLOW_REMOTE_OR_BRANCH_PATTERNS, REPO_FLOW_SOURCE_FINGERPRINT_PATTERN, REPO_FLOW_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_FLOW_PATTERNS, VOLATILE_REPO_MAP_PATTERNS } from './constants.js';
25
25
  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';
26
26
  import { isConfiguredCommandIntent, isDeclaredCommandIntent } from './command-intents.js';
27
27
  import { parseSimpleFrontmatter, readFrontmatterList, readSkillSectionIds } from './frontmatter.js';
@@ -364,6 +364,15 @@ function validatePreferencesConfig(preferencesToml, issues) {
364
364
  validatePositiveIntegerField(commitMessage, 'max_suggestions', '[preferences.git.commit_message].max_suggestions', issues);
365
365
  validateBooleanField(commitMessage, 'split_when_multiple_concerns', '[preferences.git.commit_message].split_when_multiple_concerns', issues);
366
366
  validateBooleanField(commitMessage, 'avoid_sensitive_details', '[preferences.git.commit_message].avoid_sensitive_details', issues);
367
+ const commitMessageGitmoji = validateNestedTable(commitMessage, 'gitmoji', '[preferences.git.commit_message.gitmoji]', issues);
368
+ if (commitMessageGitmoji) {
369
+ validateAllowedStringField(commitMessageGitmoji, 'map', '[preferences.git.commit_message.gitmoji].map', ALLOWED_COMMIT_MESSAGE_GITMOJI_MAPS, issues);
370
+ }
371
+ const commitMessageBody = validateNestedTable(commitMessage, 'body', '[preferences.git.commit_message.body]', issues);
372
+ if (commitMessageBody) {
373
+ validateAllowedStringField(commitMessageBody, 'template', '[preferences.git.commit_message.body].template', ALLOWED_COMMIT_MESSAGE_BODY_TEMPLATES, issues);
374
+ validateBooleanField(commitMessageBody, 'require_validation_line', '[preferences.git.commit_message.body].require_validation_line', issues);
375
+ }
367
376
  }
368
377
  }
369
378
  const reporting = validateTable(preferencesToml, 'reporting', issues);
@@ -9,6 +9,7 @@ const LOCK_ROOT_RELATIVE_PATH = '.mustflow/state/locks';
9
9
  export const ACTIVE_RUN_LOCK_ID_ENV = 'MUSTFLOW_ACTIVE_RUN_LOCK_ID';
10
10
  const LOCK_MUTEX_STALE_MS = 30_000;
11
11
  const LOCK_MUTEX_WAIT_MS = 1_000;
12
+ const RUN_STATE_UPDATE_MUTEX_WAIT_MS = 35_000;
12
13
  const LOCK_MUTEX_SLEEP_MS = 25;
13
14
  const LOCK_MUTEX_SLEEP_BUFFER = new Int32Array(new SharedArrayBuffer(4));
14
15
  const LOCK_MUTEX_RECOVERY_DIRECTORY = 'mutex.recovery';
@@ -342,13 +343,14 @@ function recoverStaleMutexWithoutOwner(mutex) {
342
343
  releaseRecovery();
343
344
  }
344
345
  }
345
- function acquireMutex(projectRoot) {
346
+ function acquireMutex(projectRoot, options = {}) {
346
347
  const root = activeLockRoot(projectRoot);
347
348
  const mutex = activeLockMutexDirectory(projectRoot);
348
349
  const ownerPath = path.join(mutex, 'owner.json');
349
350
  const ownerToken = sha256(`${process.pid}:${Date.now()}:${process.hrtime.bigint()}`);
350
351
  mkdirSync(root, { recursive: true });
351
352
  const startedAt = Date.now();
353
+ const waitMs = options.waitMs ?? LOCK_MUTEX_WAIT_MS;
352
354
  while (true) {
353
355
  try {
354
356
  mkdirSync(mutex);
@@ -378,7 +380,7 @@ function acquireMutex(projectRoot) {
378
380
  if (!error || typeof error !== 'object' || !('code' in error) || error.code !== 'EEXIST') {
379
381
  throw error;
380
382
  }
381
- if (Date.now() - startedAt > LOCK_MUTEX_WAIT_MS) {
383
+ if (Date.now() - startedAt > waitMs) {
382
384
  const owner = readMutexOwner(ownerPath);
383
385
  if (owner) {
384
386
  if (mutexOwnerIsStale(owner) && recoverStaleMutexWithOwner(mutex, ownerPath, owner)) {
@@ -414,6 +416,15 @@ function acquireMutex(projectRoot) {
414
416
  }
415
417
  }
416
418
  }
419
+ export function withRunStateUpdateMutex(projectRoot, callback) {
420
+ const releaseMutex = acquireMutex(projectRoot, { waitMs: RUN_STATE_UPDATE_MUTEX_WAIT_MS });
421
+ try {
422
+ return callback();
423
+ }
424
+ finally {
425
+ releaseMutex();
426
+ }
427
+ }
417
428
  export function inspectActiveRunLocks(projectRoot, contract, intentName) {
418
429
  const effects = normalizeCommandEffects(projectRoot, contract, intentName);
419
430
  const records = readActiveRecords(projectRoot);
@@ -1,5 +1,6 @@
1
1
  import { existsSync, readFileSync } from 'node:fs';
2
2
  import path from 'node:path';
3
+ import { withRunStateUpdateMutex } from './active-run-locks.js';
3
4
  import { writeJsonFileInsideWithoutSymlinks } from './safe-filesystem.js';
4
5
  const PERFORMANCE_HISTORY_SCHEMA_VERSION = '1';
5
6
  const PERFORMANCE_HISTORY_DIR = path.join('.mustflow', 'state', 'perf');
@@ -318,14 +319,16 @@ export function recordRunPerformanceHistory(projectRoot, receipt) {
318
319
  return;
319
320
  }
320
321
  try {
321
- const historyDir = path.join(projectRoot, PERFORMANCE_HISTORY_DIR);
322
- const samplesPath = path.join(historyDir, PERFORMANCE_SAMPLES_FILE);
323
- const summaryPath = path.join(historyDir, PERFORMANCE_SUMMARY_FILE);
324
- const samples = enforceSizeLimit(pruneSamples([...readSamples(samplesPath), sample], sample.observed_day), sample.observed_day);
325
- const samplesFile = createSamplesFile(samples);
326
- const summaryFile = createSummary(samples, sample.observed_day);
327
- writeJsonFileInsideWithoutSymlinks(projectRoot, samplesPath, samplesFile);
328
- writeJsonFileInsideWithoutSymlinks(projectRoot, summaryPath, summaryFile);
322
+ withRunStateUpdateMutex(projectRoot, () => {
323
+ const historyDir = path.join(projectRoot, PERFORMANCE_HISTORY_DIR);
324
+ const samplesPath = path.join(historyDir, PERFORMANCE_SAMPLES_FILE);
325
+ const summaryPath = path.join(historyDir, PERFORMANCE_SUMMARY_FILE);
326
+ const samples = enforceSizeLimit(pruneSamples([...readSamples(samplesPath), sample], sample.observed_day), sample.observed_day);
327
+ const samplesFile = createSamplesFile(samples);
328
+ const summaryFile = createSummary(samples, sample.observed_day);
329
+ writeJsonFileInsideWithoutSymlinks(projectRoot, samplesPath, samplesFile);
330
+ writeJsonFileInsideWithoutSymlinks(projectRoot, summaryPath, summaryFile);
331
+ });
329
332
  }
330
333
  catch {
331
334
  // Performance history is a local optimization hint. A write failure must not affect command execution.
@@ -1,5 +1,6 @@
1
1
  import { performance } from 'node:perf_hooks';
2
2
  import path from 'node:path';
3
+ import { withRunStateUpdateMutex } from './active-run-locks.js';
3
4
  import { writeJsonFileInsideWithoutSymlinks } from './safe-filesystem.js';
4
5
  const RUN_PROFILE_SCHEMA_VERSION = '1';
5
6
  const RUN_PROFILE_ENV = 'MUSTFLOW_RUN_PROFILE';
@@ -75,7 +76,9 @@ export class RunProfiler {
75
76
  profile_path: getProfileRelativePath(),
76
77
  };
77
78
  const profilePath = path.join(input.projectRoot, RUN_PROFILE_DIR, LATEST_RUN_PROFILE);
78
- writeJsonFileInsideWithoutSymlinks(input.projectRoot, profilePath, profile);
79
+ withRunStateUpdateMutex(input.projectRoot, () => {
80
+ writeJsonFileInsideWithoutSymlinks(input.projectRoot, profilePath, profile);
81
+ });
79
82
  }
80
83
  recordPhase(name, startedAtMs) {
81
84
  this.phases.push({
@@ -1,5 +1,6 @@
1
1
  import { createHash } from 'node:crypto';
2
2
  import path from 'node:path';
3
+ import { withRunStateUpdateMutex } from './active-run-locks.js';
3
4
  import { createStateRunId } from './atomic-state-write.js';
4
5
  import { COMMAND_OUTPUT_LIMIT_SCOPE } from './command-output-limits.js';
5
6
  import { decodeUtf8Tail } from './bounded-output.js';
@@ -295,9 +296,11 @@ export function writeRunReceipt(projectRoot, receipt, policy) {
295
296
  if (relativeToRunDir.startsWith('..') || path.isAbsolute(relativeToRunDir)) {
296
297
  throw new Error(`Run receipt path must stay inside ${RUN_RECEIPT_DIR}`);
297
298
  }
298
- writeJsonFileInsideWithoutSymlinks(projectRoot, receiptPath, receipt);
299
- writeJsonFileInsideWithoutSymlinks(projectRoot, latestPath, receipt);
300
- if (policy) {
301
- updateRunReceiptState(projectRoot, policy);
302
- }
299
+ withRunStateUpdateMutex(projectRoot, () => {
300
+ writeJsonFileInsideWithoutSymlinks(projectRoot, receiptPath, receipt);
301
+ writeJsonFileInsideWithoutSymlinks(projectRoot, latestPath, receipt);
302
+ if (policy) {
303
+ updateRunReceiptState(projectRoot, policy);
304
+ }
305
+ });
303
306
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mustflow",
3
- "version": "2.112.8",
3
+ "version": "2.112.10",
4
4
  "description": "Agent workflow documents and CLI for mustflow repository roots.",
5
5
  "type": "module",
6
6
  "license": "MIT-0",
@@ -75,6 +75,16 @@ include_body = "when_non_trivial"
75
75
  split_when_multiple_concerns = true
76
76
  avoid_sensitive_details = true
77
77
 
78
+ [git.commit_message.gitmoji]
79
+ # conventional_default maps common conventional types to Gitmoji prefixes:
80
+ # feat=✨, fix=🐛, docs=📝, refactor=♻️, test=✅, chore/build/config=🔧.
81
+ map = "conventional_default"
82
+
83
+ [git.commit_message.body]
84
+ # summary_validation keeps suggested bodies concise and evidence-backed.
85
+ template = "summary_validation"
86
+ require_validation_line = true
87
+
78
88
  [reporting.commit_suggestion]
79
89
  enabled = true
80
90
  when = "files_changed"
@@ -1,6 +1,6 @@
1
1
  id = "default"
2
2
  name = "default"
3
- version = "2.112.8"
3
+ version = "2.112.10"
4
4
  description = "Minimal workflow for LLM agents to read, edit, and verify their work in a repository."
5
5
  common_root = "common"
6
6
  locales_root = "locales"