mustflow 2.112.9 → 2.112.13

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/README.md +1 -1
  2. package/dist/cli/commands/contract-lint.js +3 -13
  3. package/dist/cli/commands/impact.js +2 -12
  4. package/dist/cli/commands/init.js +39 -14
  5. package/dist/cli/commands/onboard.js +3 -13
  6. package/dist/cli/commands/version-sources.js +2 -12
  7. package/dist/cli/i18n/en.js +6 -0
  8. package/dist/cli/i18n/es.js +6 -0
  9. package/dist/cli/i18n/fr.js +6 -0
  10. package/dist/cli/i18n/hi.js +6 -0
  11. package/dist/cli/i18n/ko.js +6 -0
  12. package/dist/cli/i18n/zh.js +6 -0
  13. package/dist/cli/lib/agent-context.js +2 -1
  14. package/dist/cli/lib/dashboard-locale.js +6 -0
  15. package/dist/cli/lib/dashboard-preferences.js +24 -1
  16. package/dist/cli/lib/preferences-options.js +8 -0
  17. package/dist/cli/lib/validation/constants.js +3 -1
  18. package/dist/cli/lib/validation/index.js +10 -1
  19. package/dist/core/preferences.js +79 -0
  20. package/dist/core/repo-version-source.js +9 -18
  21. package/package.json +1 -1
  22. package/templates/default/common/.mustflow/config/preferences.toml +10 -0
  23. package/templates/default/i18n.toml +19 -19
  24. package/templates/default/locales/en/.mustflow/docs/agent-workflow.md +10 -4
  25. package/templates/default/locales/en/.mustflow/skills/INDEX.md +4 -4
  26. package/templates/default/locales/en/.mustflow/skills/async-timing-boundary-review/SKILL.md +19 -5
  27. package/templates/default/locales/en/.mustflow/skills/backend-log-evidence-review/SKILL.md +10 -3
  28. package/templates/default/locales/en/.mustflow/skills/cache-integrity-review/SKILL.md +63 -21
  29. package/templates/default/locales/en/.mustflow/skills/concurrency-invariant-review/SKILL.md +13 -3
  30. package/templates/default/locales/en/.mustflow/skills/cross-platform-filesystem-safety/SKILL.md +10 -9
  31. package/templates/default/locales/en/.mustflow/skills/dependency-upgrade-review/SKILL.md +8 -3
  32. package/templates/default/locales/en/.mustflow/skills/memory-lifetime-review/SKILL.md +19 -3
  33. package/templates/default/locales/en/.mustflow/skills/observability-debuggability-review/SKILL.md +31 -21
  34. package/templates/default/locales/en/.mustflow/skills/race-condition-review/SKILL.md +38 -25
  35. package/templates/default/locales/en/.mustflow/skills/repro-first-debug/SKILL.md +43 -10
  36. package/templates/default/locales/en/.mustflow/skills/routes.toml +4 -4
  37. package/templates/default/locales/en/.mustflow/skills/security-flow-review/SKILL.md +19 -4
  38. package/templates/default/locales/en/.mustflow/skills/security-privacy-review/SKILL.md +8 -3
  39. package/templates/default/locales/en/AGENTS.md +9 -1
  40. package/templates/default/locales/es/AGENTS.md +2 -0
  41. package/templates/default/locales/fr/AGENTS.md +2 -0
  42. package/templates/default/locales/hi/AGENTS.md +2 -0
  43. package/templates/default/locales/ko/AGENTS.md +3 -1
  44. package/templates/default/locales/zh/AGENTS.md +2 -0
  45. package/templates/default/manifest.toml +1 -1
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
 
@@ -1,13 +1,11 @@
1
- import { existsSync } from 'node:fs';
2
- import path from 'node:path';
3
1
  import { lintCommandContract } from '../../core/contract-lint.js';
4
- import { readCommandContract, isRecord } from '../../core/config-loading.js';
2
+ import { readCommandContract } from '../../core/config-loading.js';
3
+ import { readEffectivePreferencesToml } from '../../core/preferences.js';
5
4
  import { releaseVersioningIsEnabled } from '../../core/version-sources.js';
6
5
  import { printUsageError, renderHelp } from '../lib/cli-output.js';
7
6
  import { t } from '../lib/i18n.js';
8
7
  import { formatCliOptionParseError, hasCliOptionToken, hasParsedCliOption, parseCliOptions, } from '../lib/option-parser.js';
9
8
  import { resolveMustflowRoot } from '../lib/project-root.js';
10
- import { readMustflowTomlFile } from '../lib/toml.js';
11
9
  const CONTRACT_LINT_SCHEMA_VERSION = '1';
12
10
  const CONTRACT_LINT_OPTIONS = [
13
11
  { name: '--coverage', kind: 'boolean' },
@@ -31,14 +29,6 @@ export function getContractLintHelp(lang = 'en') {
31
29
  ],
32
30
  }, lang);
33
31
  }
34
- function readPreferences(projectRoot) {
35
- const preferencesPath = path.join(projectRoot, '.mustflow', 'config', 'preferences.toml');
36
- if (!existsSync(preferencesPath)) {
37
- return undefined;
38
- }
39
- const preferences = readMustflowTomlFile(projectRoot, '.mustflow/config/preferences.toml');
40
- return isRecord(preferences) ? preferences : undefined;
41
- }
42
32
  function createContractLintOutput(projectRoot, coverage, suggest) {
43
33
  return {
44
34
  schema_version: CONTRACT_LINT_SCHEMA_VERSION,
@@ -48,7 +38,7 @@ function createContractLintOutput(projectRoot, coverage, suggest) {
48
38
  coverage,
49
39
  suggest,
50
40
  projectRoot,
51
- releaseVersioningEnabled: releaseVersioningIsEnabled(readPreferences(projectRoot)),
41
+ releaseVersioningEnabled: releaseVersioningIsEnabled(readEffectivePreferencesToml(projectRoot)),
52
42
  }),
53
43
  };
54
44
  }
@@ -1,12 +1,11 @@
1
1
  import { createChangeClassificationReport } from '../../core/change-classification.js';
2
+ import { readEffectivePreferencesToml } from '../../core/preferences.js';
2
3
  import { summarizeVersionImpact } from '../../core/version-impact.js';
3
4
  import { printUsageError, renderHelp } from '../lib/cli-output.js';
4
- import { isRecord } from '../lib/command-contract.js';
5
5
  import { requireGitChangedFiles } from '../lib/git-changes.js';
6
6
  import { t } from '../lib/i18n.js';
7
7
  import { formatCliOptionParseError, hasCliOptionToken, hasParsedCliOption, parseCliOptions, } from '../lib/option-parser.js';
8
8
  import { resolveMustflowRoot } from '../lib/project-root.js';
9
- import { readMustflowTomlFile } from '../lib/toml.js';
10
9
  import { detectVersionSources, releaseVersioningIsEnabled, } from '../../core/version-sources.js';
11
10
  const IMPACT_SCHEMA_VERSION = '1';
12
11
  const IMPACT_OPTIONS = [
@@ -38,15 +37,6 @@ function parseImpactArgs(args) {
38
37
  error: parsed.error,
39
38
  };
40
39
  }
41
- function readPreferences(projectRoot) {
42
- try {
43
- const preferences = readMustflowTomlFile(projectRoot, '.mustflow/config/preferences.toml');
44
- return isRecord(preferences) ? preferences : undefined;
45
- }
46
- catch {
47
- return undefined;
48
- }
49
- }
50
40
  function createImpactOutput(projectRoot, parsed) {
51
41
  const source = parsed.changed ? 'changed' : 'paths';
52
42
  const files = parsed.changed ? requireGitChangedFiles(projectRoot) : parsed.paths;
@@ -58,7 +48,7 @@ function createImpactOutput(projectRoot, parsed) {
58
48
  mustflow_root: projectRoot,
59
49
  source,
60
50
  files: classificationReport.files,
61
- versioning_enabled: releaseVersioningIsEnabled(readPreferences(projectRoot)),
51
+ versioning_enabled: releaseVersioningIsEnabled(readEffectivePreferencesToml(projectRoot)),
62
52
  version_sources: versionSources,
63
53
  classification_summary: classificationReport.summary,
64
54
  version_impact: summarizeVersionImpact(classificationReport, versionSources),
@@ -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 -->';
@@ -158,7 +158,7 @@ const VERIFICATION_SELECTION_BOOLEAN_FIELDS = new Set([
158
158
  ]);
159
159
  const TEST_AUTHORING_BOOLEAN_FIELDS = new Set(['prefer_existing_tests', 'require_new_test_rationale']);
160
160
  function createPreferenceOverride(key, value, reporter, lang) {
161
- if (key === 'git.auto_stage' || key === 'git.auto_commit') {
161
+ if (key === 'git.auto_stage' || key === 'git.auto_commit' || key === 'git.auto_push') {
162
162
  const parsed = parseBoolean(value);
163
163
  if (parsed === undefined) {
164
164
  reporter.stderr(t(lang, 'init.error.invalidPreferenceValue', { key, value }));
@@ -171,18 +171,6 @@ function createPreferenceOverride(key, value, reporter, lang) {
171
171
  renderedValue: String(parsed),
172
172
  };
173
173
  }
174
- if (key === 'git.auto_push') {
175
- if (value !== 'false') {
176
- reporter.stderr(t(lang, 'init.error.invalidPreferenceValue', { key, value }));
177
- return undefined;
178
- }
179
- return {
180
- key,
181
- section: 'git',
182
- field: 'auto_push',
183
- renderedValue: 'false',
184
- };
185
- }
186
174
  if (key === 'git.commit_message.language') {
187
175
  if (!isSupportedLanguagePreference(value)) {
188
176
  reporter.stderr(t(lang, 'init.error.invalidPreferenceValue', { key, value }));
@@ -232,6 +220,43 @@ function createPreferenceOverride(key, value, reporter, lang) {
232
220
  renderedValue: tomlString(value),
233
221
  };
234
222
  }
223
+ if (key === 'git.commit_message.gitmoji.map') {
224
+ if (!isCommitMessageGitmojiMap(value)) {
225
+ reporter.stderr(t(lang, 'init.error.invalidPreferenceValue', { key, value }));
226
+ return undefined;
227
+ }
228
+ return {
229
+ key,
230
+ section: 'git.commit_message.gitmoji',
231
+ field: 'map',
232
+ renderedValue: tomlString(value),
233
+ };
234
+ }
235
+ if (key === 'git.commit_message.body.template') {
236
+ if (!isCommitMessageBodyTemplate(value)) {
237
+ reporter.stderr(t(lang, 'init.error.invalidPreferenceValue', { key, value }));
238
+ return undefined;
239
+ }
240
+ return {
241
+ key,
242
+ section: 'git.commit_message.body',
243
+ field: 'template',
244
+ renderedValue: tomlString(value),
245
+ };
246
+ }
247
+ if (key === 'git.commit_message.body.require_validation_line') {
248
+ const parsed = parseBoolean(value);
249
+ if (parsed === undefined) {
250
+ reporter.stderr(t(lang, 'init.error.invalidPreferenceValue', { key, value }));
251
+ return undefined;
252
+ }
253
+ return {
254
+ key,
255
+ section: 'git.commit_message.body',
256
+ field: 'require_validation_line',
257
+ renderedValue: String(parsed),
258
+ };
259
+ }
235
260
  if (key === 'git.commit_message.split_when_multiple_concerns') {
236
261
  const parsed = parseBoolean(value);
237
262
  if (parsed === undefined) {
@@ -1,13 +1,11 @@
1
- import { existsSync } from 'node:fs';
2
- import path from 'node:path';
3
1
  import { lintCommandContract, } from '../../core/contract-lint.js';
4
- import { isRecord, readCommandContract, } from '../../core/config-loading.js';
2
+ import { readCommandContract } from '../../core/config-loading.js';
3
+ import { readEffectivePreferencesToml } from '../../core/preferences.js';
5
4
  import { releaseVersioningIsEnabled } from '../../core/version-sources.js';
6
5
  import { printUsageError, renderHelp } from '../lib/cli-output.js';
7
6
  import { t } from '../lib/i18n.js';
8
7
  import { formatCliOptionParseError, hasCliOptionToken, hasParsedCliOption, parseCliOptions, } from '../lib/option-parser.js';
9
8
  import { resolveMustflowRoot } from '../lib/project-root.js';
10
- import { readMustflowTomlFile } from '../lib/toml.js';
11
9
  const ONBOARD_COMMANDS_SCHEMA_VERSION = '1';
12
10
  const COMMAND_CONTRACT_PATH = '.mustflow/config/commands.toml';
13
11
  const ONBOARD_COMMANDS_OPTIONS = [
@@ -28,14 +26,6 @@ export function getOnboardHelp(lang = 'en') {
28
26
  ],
29
27
  }, lang);
30
28
  }
31
- function readPreferences(projectRoot) {
32
- const preferencesPath = path.join(projectRoot, '.mustflow', 'config', 'preferences.toml');
33
- if (!existsSync(preferencesPath)) {
34
- return undefined;
35
- }
36
- const preferences = readMustflowTomlFile(projectRoot, '.mustflow/config/preferences.toml');
37
- return isRecord(preferences) ? preferences : undefined;
38
- }
39
29
  function countSuggestionsByKind(suggestions, sourceKind) {
40
30
  return suggestions.filter((suggestion) => suggestion.sourceKind === sourceKind).length;
41
31
  }
@@ -56,7 +46,7 @@ function createOnboardCommandsOutput(projectRoot) {
56
46
  const report = lintCommandContract(readCommandContract(projectRoot), {
57
47
  suggest: true,
58
48
  projectRoot,
59
- releaseVersioningEnabled: releaseVersioningIsEnabled(readPreferences(projectRoot)),
49
+ releaseVersioningEnabled: releaseVersioningIsEnabled(readEffectivePreferencesToml(projectRoot)),
60
50
  });
61
51
  const suggestions = report.suggestions ?? [];
62
52
  return {
@@ -1,9 +1,8 @@
1
+ import { readEffectivePreferencesToml } from '../../core/preferences.js';
1
2
  import { printUsageError, renderHelp } from '../lib/cli-output.js';
2
- import { isRecord } from '../lib/command-contract.js';
3
3
  import { t } from '../lib/i18n.js';
4
4
  import { formatCliOptionParseError, hasCliOptionToken, hasParsedCliOption, parseCliOptions, } from '../lib/option-parser.js';
5
5
  import { resolveMustflowRoot } from '../lib/project-root.js';
6
- import { readMustflowTomlFile } from '../lib/toml.js';
7
6
  import { detectVersionSources, releaseVersioningIsEnabled, } from '../../core/version-sources.js';
8
7
  const VERSION_SOURCES_SCHEMA_VERSION = '1';
9
8
  const VERSION_SOURCES_OPTIONS = [
@@ -24,17 +23,8 @@ export function getVersionSourcesHelp(lang = 'en') {
24
23
  ],
25
24
  }, lang);
26
25
  }
27
- function readPreferences(projectRoot) {
28
- try {
29
- const preferences = readMustflowTomlFile(projectRoot, '.mustflow/config/preferences.toml');
30
- return isRecord(preferences) ? preferences : undefined;
31
- }
32
- catch {
33
- return undefined;
34
- }
35
- }
36
26
  function getVersionSourcesOutput(projectRoot) {
37
- const preferences = readPreferences(projectRoot);
27
+ const preferences = readEffectivePreferencesToml(projectRoot);
38
28
  return {
39
29
  schema_version: VERSION_SOURCES_SCHEMA_VERSION,
40
30
  command: 'version-sources',
@@ -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": "避免写入密钥、凭据、个人数据和非公开事故细节。",
@@ -10,6 +10,7 @@ import { MUSTFLOW_JSON_MAX_BYTES, readMustflowTextFile, readMustflowTextFileIfEx
10
10
  import { createRunPlan } from './run-plan.js';
11
11
  import { readMustflowTomlFile } from './toml.js';
12
12
  import { normalizeTechnologyPreferencesTable, TECHNOLOGY_CONFIG_RELATIVE_PATH, } from '../../core/technology-preferences.js';
13
+ import { readEffectivePreferencesToml } from '../../core/preferences.js';
13
14
  import { isPromptCacheStableLeafSkillSurface, measurePromptCacheReferenceBlockBytes, renderPromptCacheReferenceBlock, } from '../../core/prompt-cache-rendering.js';
14
15
  import { resolveSkillRoutes } from '../../core/skill-route-resolution.js';
15
16
  const CONTEXT_SCHEMA_VERSION = '1';
@@ -1487,7 +1488,7 @@ export async function getPromptCacheProfileContext(projectRoot, profile, options
1487
1488
  }
1488
1489
  export function getAgentContext(projectRoot) {
1489
1490
  const mustflow = readTomlTableIfExists(projectRoot, MUSTFLOW_RELATIVE_PATH);
1490
- const preferences = readTomlTableIfExists(projectRoot, PREFERENCES_RELATIVE_PATH);
1491
+ const preferences = readEffectivePreferencesToml(projectRoot);
1491
1492
  const authority = isRecord(mustflow?.authority) ? mustflow.authority : undefined;
1492
1493
  const capabilities = isRecord(mustflow?.capabilities) ? mustflow.capabilities : undefined;
1493
1494
  const readOrder = mustflow ? readStringArray(mustflow, 'read_order') ?? [] : [];
@@ -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);
@@ -0,0 +1,79 @@
1
+ import { existsSync } from 'node:fs';
2
+ import path from 'node:path';
3
+ import { isRecord, readMustflowOwnedTomlFile, } from './config-loading.js';
4
+ export const PREFERENCES_CONFIG_RELATIVE_PATH = '.mustflow/config/preferences.toml';
5
+ function hasMustflowMarker(directoryPath) {
6
+ return (existsSync(path.join(directoryPath, '.mustflow', 'config', 'mustflow.toml')) ||
7
+ existsSync(path.join(directoryPath, '.mustflow', 'config', 'commands.toml')));
8
+ }
9
+ function preferencesPath(projectRoot) {
10
+ return path.join(projectRoot, ...PREFERENCES_CONFIG_RELATIVE_PATH.split('/'));
11
+ }
12
+ function readPreferencesAtRoot(projectRoot) {
13
+ if (!existsSync(preferencesPath(projectRoot))) {
14
+ return undefined;
15
+ }
16
+ const parsed = readMustflowOwnedTomlFile(projectRoot, PREFERENCES_CONFIG_RELATIVE_PATH);
17
+ return isRecord(parsed) ? parsed : undefined;
18
+ }
19
+ function parentMustflowRoots(projectRoot) {
20
+ const roots = [];
21
+ let current = path.dirname(path.resolve(projectRoot));
22
+ while (true) {
23
+ if (hasMustflowMarker(current)) {
24
+ roots.push(current);
25
+ }
26
+ const parent = path.dirname(current);
27
+ if (parent === current) {
28
+ return roots;
29
+ }
30
+ current = parent;
31
+ }
32
+ }
33
+ function clonePreferenceValue(value) {
34
+ if (Array.isArray(value)) {
35
+ return value.map((entry) => clonePreferenceValue(entry));
36
+ }
37
+ if (isRecord(value)) {
38
+ return mergePreferenceTables({}, value);
39
+ }
40
+ return value;
41
+ }
42
+ function mergePreferenceTables(base, override) {
43
+ const merged = {};
44
+ for (const [key, value] of Object.entries(base)) {
45
+ merged[key] = clonePreferenceValue(value);
46
+ }
47
+ for (const [key, value] of Object.entries(override)) {
48
+ const existing = merged[key];
49
+ if (isRecord(existing) && isRecord(value)) {
50
+ merged[key] = mergePreferenceTables(existing, value);
51
+ }
52
+ else {
53
+ merged[key] = clonePreferenceValue(value);
54
+ }
55
+ }
56
+ return merged;
57
+ }
58
+ export function readLocalPreferencesToml(projectRoot) {
59
+ return readPreferencesAtRoot(projectRoot);
60
+ }
61
+ /**
62
+ * mf:anchor core.preferences.inheritance
63
+ * purpose: Resolve low-authority workflow preferences across nested mustflow roots.
64
+ * search: preferences inheritance, parent workspace defaults, git auto push, release versioning
65
+ * invariant: Parent preferences are defaults only; child-local values override field by field, and command contracts are never inherited here.
66
+ * risk: config
67
+ */
68
+ export function readEffectivePreferencesToml(projectRoot) {
69
+ const roots = [...parentMustflowRoots(projectRoot).reverse(), path.resolve(projectRoot)];
70
+ let effective;
71
+ for (const root of roots) {
72
+ const preferences = readPreferencesAtRoot(root);
73
+ if (!preferences) {
74
+ continue;
75
+ }
76
+ effective = effective ? mergePreferenceTables(effective, preferences) : mergePreferenceTables({}, preferences);
77
+ }
78
+ return effective;
79
+ }
@@ -1,7 +1,6 @@
1
1
  import { createHash } from 'node:crypto';
2
- import { existsSync } from 'node:fs';
3
2
  import path from 'node:path';
4
- import { isRecord, readMustflowOwnedTomlFile } from './config-loading.js';
3
+ import { readEffectivePreferencesToml } from './preferences.js';
5
4
  import { detectVersionSources, releaseVersioningIsEnabled, VERSIONING_CONFIG_PATH, } from './version-sources.js';
6
5
  export const REPO_VERSION_SOURCE_PACK_ID = 'repo';
7
6
  export const REPO_VERSION_SOURCE_SCRIPT_ID = 'version-source';
@@ -10,21 +9,6 @@ export const REPO_VERSION_SOURCE_PREFERENCES_PATH = '.mustflow/config/preference
10
9
  function sha256(value) {
11
10
  return `sha256:${createHash('sha256').update(value).digest('hex')}`;
12
11
  }
13
- function readPreferences(projectRoot, issues) {
14
- const preferencesPath = path.join(projectRoot, ...REPO_VERSION_SOURCE_PREFERENCES_PATH.split('/'));
15
- if (!existsSync(preferencesPath)) {
16
- return undefined;
17
- }
18
- try {
19
- const parsed = readMustflowOwnedTomlFile(projectRoot, REPO_VERSION_SOURCE_PREFERENCES_PATH);
20
- return isRecord(parsed) ? parsed : undefined;
21
- }
22
- catch (error) {
23
- const message = error instanceof Error ? error.message : String(error);
24
- issues.push(`Could not read ${REPO_VERSION_SOURCE_PREFERENCES_PATH}: ${message}`);
25
- return undefined;
26
- }
27
- }
28
12
  function countSources(sources) {
29
13
  return {
30
14
  sources: sources.length,
@@ -40,7 +24,14 @@ function createInputHash(reportInput) {
40
24
  export function inspectRepoVersionSource(projectRoot) {
41
25
  const root = path.resolve(projectRoot);
42
26
  const issues = [];
43
- const preferences = readPreferences(root, issues);
27
+ let preferences;
28
+ try {
29
+ preferences = readEffectivePreferencesToml(root);
30
+ }
31
+ catch (error) {
32
+ const message = error instanceof Error ? error.message : String(error);
33
+ issues.push(`Could not read local or inherited ${REPO_VERSION_SOURCE_PREFERENCES_PATH}: ${message}`);
34
+ }
44
35
  const versioningEnabled = releaseVersioningIsEnabled(preferences);
45
36
  const sources = detectVersionSources(root);
46
37
  const findings = [];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mustflow",
3
- "version": "2.112.9",
3
+ "version": "2.112.13",
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"