mustflow 2.22.3 → 2.22.5

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/README.md +9 -75
  2. package/dist/cli/commands/contract-lint.js +2 -2
  3. package/dist/cli/commands/dashboard.js +14 -6
  4. package/dist/cli/commands/help.js +8 -9
  5. package/dist/cli/commands/impact.js +2 -3
  6. package/dist/cli/commands/init.js +61 -5
  7. package/dist/cli/commands/update.js +2 -2
  8. package/dist/cli/commands/version-sources.js +2 -3
  9. package/dist/cli/i18n/en.js +2 -0
  10. package/dist/cli/i18n/es.js +2 -0
  11. package/dist/cli/i18n/fr.js +2 -0
  12. package/dist/cli/i18n/hi.js +2 -0
  13. package/dist/cli/i18n/ko.js +2 -0
  14. package/dist/cli/i18n/zh.js +2 -0
  15. package/dist/cli/lib/agent-context.js +6 -11
  16. package/dist/cli/lib/local-index/index.js +14 -11
  17. package/dist/cli/lib/mustflow-read.js +41 -0
  18. package/dist/cli/lib/project-root.js +1 -2
  19. package/dist/cli/lib/repo-map.js +65 -16
  20. package/dist/cli/lib/templates.js +124 -8
  21. package/dist/cli/lib/toml.js +6 -1
  22. package/dist/cli/lib/validation/constants.js +2 -0
  23. package/dist/cli/lib/validation/index.js +291 -22
  24. package/dist/cli/lib/validation/primitives.js +2 -2
  25. package/dist/cli/lib/validation/test-selection.js +2 -2
  26. package/dist/core/bounded-output.js +32 -7
  27. package/dist/core/check-issues.js +7 -1
  28. package/dist/core/command-contract-validation.js +28 -4
  29. package/dist/core/command-env.js +1 -1
  30. package/dist/core/config-loading.js +9 -3
  31. package/dist/core/contract-lint.js +2 -1
  32. package/dist/core/safe-filesystem.js +11 -4
  33. package/dist/core/skill-route-alignment.js +1 -0
  34. package/dist/core/skill-route-explanation.js +9 -3
  35. package/dist/core/test-selection.js +2 -3
  36. package/dist/core/verification-scheduler.js +7 -6
  37. package/dist/core/version-sources.js +2 -3
  38. package/package.json +4 -2
  39. package/schemas/commands.schema.json +1 -0
  40. package/templates/default/locales/en/.mustflow/skills/INDEX.md +10 -6
  41. package/templates/default/locales/en/.mustflow/skills/routes.toml +2 -2
  42. package/templates/default/manifest.toml +1 -1
@@ -11,13 +11,15 @@ import { listFilesRecursive, toPosixPath } from '../filesystem.js';
11
11
  import { readGitChangedFiles } from '../git-changes.js';
12
12
  import { inspectManifestLock } from '../manifest-lock.js';
13
13
  import { generateRepoMap } from '../repo-map.js';
14
- import { readTomlFile } from '../toml.js';
14
+ import { parseTomlText, readMustflowTomlFile } from '../toml.js';
15
+ import { MUSTFLOW_JSON_MAX_BYTES, readMustflowTextFileResult, } from '../mustflow-read.js';
15
16
  import { getContractModelDefinitions, validateCandidateContractModelConfig, } from '../../../core/contract-models.js';
16
17
  import { VERSIONING_CONFIG_PATH, detectVersionSourcePaths, readDeclaredVersionSources, releaseVersioningIsEnabled, } from '../../../core/version-sources.js';
17
- 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_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';
18
+ 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';
18
19
  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';
19
20
  import { isConfiguredCommandIntent, isDeclaredCommandIntent } from './command-intents.js';
20
21
  import { validateStrictTestSelectionConfig } from './test-selection.js';
22
+ import { getDefaultTemplate, getTemplateFiles } from '../templates.js';
21
23
  export { describeCheckIssues, getCheckIssueId, } from '../../../core/check-issues.js';
22
24
  function validateMustflowConfig(mustflowToml, issues) {
23
25
  if (!mustflowToml) {
@@ -454,13 +456,16 @@ function validateSkills(projectRoot, issues) {
454
456
  const skillsRoot = path.join(projectRoot, '.mustflow', 'skills');
455
457
  const skillFiles = listFilesRecursive(skillsRoot).filter((relativePath) => relativePath.endsWith('/SKILL.md'));
456
458
  for (const relativePath of skillFiles) {
457
- const absolutePath = path.join(skillsRoot, relativePath);
458
- const content = readFileSync(absolutePath, 'utf8');
459
+ const normalizedPath = `.mustflow/skills/${toPosixPath(relativePath)}`;
460
+ const content = readStrictMustflowText(projectRoot, normalizedPath, issues);
461
+ if (content === undefined) {
462
+ continue;
463
+ }
459
464
  const sectionIds = readSkillSectionIds(content);
460
465
  const missingSectionIds = REQUIRED_SKILL_SECTION_IDS.filter((sectionId) => !sectionIds.has(sectionId));
461
466
  if (missingSectionIds.length > 0) {
462
467
  issues.push({
463
- message: `Missing required skill section ids in .mustflow/skills/${toPosixPath(relativePath)}: ${missingSectionIds.join(', ')}`,
468
+ message: `Missing required skill section ids in ${normalizedPath}: ${missingSectionIds.join(', ')}`,
464
469
  });
465
470
  }
466
471
  }
@@ -490,6 +495,16 @@ function parseSimpleFrontmatter(content) {
490
495
  }
491
496
  return frontmatter;
492
497
  }
498
+ function readStrictMustflowText(projectRoot, relativePath, issues, options = {}) {
499
+ const result = readMustflowTextFileResult(projectRoot, relativePath, options);
500
+ if (result.ok) {
501
+ return result.content;
502
+ }
503
+ if (result.exists && result.error) {
504
+ pushStrictIssue(issues, `${toPosixPath(relativePath)} could not be read safely: ${result.error}`);
505
+ }
506
+ return undefined;
507
+ }
493
508
  function readFrontmatterLines(content) {
494
509
  if (!content.startsWith('---')) {
495
510
  return [];
@@ -540,7 +555,10 @@ function validateContextDocuments(projectRoot, issues) {
540
555
  const contextFiles = listFilesRecursive(contextRoot).filter((relativePath) => relativePath.endsWith('.md'));
541
556
  for (const relativePath of contextFiles) {
542
557
  const normalizedPath = `.mustflow/context/${toPosixPath(relativePath)}`;
543
- const content = readFileSync(path.join(contextRoot, relativePath), 'utf8');
558
+ const content = readStrictMustflowText(projectRoot, normalizedPath, issues);
559
+ if (content === undefined) {
560
+ continue;
561
+ }
544
562
  const frontmatter = parseSimpleFrontmatter(content);
545
563
  if (frontmatter.kind !== 'mustflow-context') {
546
564
  issues.push({ message: `${normalizedPath} frontmatter kind must be "mustflow-context"` });
@@ -630,7 +648,10 @@ function validateStrictRouterIndexes(projectRoot, issues) {
630
648
  if (!existsSync(filePath)) {
631
649
  continue;
632
650
  }
633
- const content = readFileSync(filePath, 'utf8');
651
+ const content = readStrictMustflowText(projectRoot, relativePath, issues);
652
+ if (content === undefined) {
653
+ continue;
654
+ }
634
655
  if (ROUTER_INDEX_PROCEDURE_SECTION_PATTERN.test(content)) {
635
656
  pushStrictIssue(issues, `${relativePath} must stay a routing index and must not embed skill procedure sections`);
636
657
  }
@@ -724,7 +745,7 @@ function readSkillRouteMetadata(projectRoot, issues) {
724
745
  }
725
746
  let parsed;
726
747
  try {
727
- parsed = readTomlFile(metadataPath);
748
+ parsed = readMustflowTomlFile(projectRoot, SKILL_ROUTES_METADATA_PATH);
728
749
  }
729
750
  catch (error) {
730
751
  const message = error instanceof Error ? error.message : String(error);
@@ -787,7 +808,10 @@ function validateSkillIndexRoutes(projectRoot, commandsToml, skillFiles, issues)
787
808
  if (!existsSync(skillIndexPath)) {
788
809
  return;
789
810
  }
790
- const skillIndexContent = readFileSync(skillIndexPath, 'utf8');
811
+ const skillIndexContent = readStrictMustflowText(projectRoot, SKILL_INDEX_PATH, issues);
812
+ if (skillIndexContent === undefined) {
813
+ return;
814
+ }
791
815
  validateSkillIndexRouteShape(skillIndexContent, issues);
792
816
  const skillRoutes = parseSkillIndexRoutes(skillIndexContent);
793
817
  const routedSkillPaths = new Set();
@@ -824,7 +848,11 @@ function validateSkillIndexRoutes(projectRoot, commandsToml, skillFiles, issues)
824
848
  pushStrictIssue(issues, `${SKILL_INDEX_PATH} route ${route.skillPath} points to a missing skill document`);
825
849
  continue;
826
850
  }
827
- const skillCommandIntents = new Set(readFrontmatterList(readFileSync(absoluteSkillPath, 'utf8'), 'command_intents'));
851
+ const skillContent = readStrictMustflowText(projectRoot, route.skillPath, issues);
852
+ if (skillContent === undefined) {
853
+ continue;
854
+ }
855
+ const skillCommandIntents = new Set(readFrontmatterList(skillContent, 'command_intents'));
828
856
  for (const intentName of route.commandIntents) {
829
857
  if (!isDeclaredCommandIntent(commandsToml, intentName)) {
830
858
  pushStrictIssue(issues, `${SKILL_INDEX_PATH} route ${route.skillPath} references unknown command intent "${intentName}"`);
@@ -843,6 +871,223 @@ function validateSkillIndexRoutes(projectRoot, commandsToml, skillFiles, issues)
843
871
  validateSkillRouteMetadataAlignment(routeMetadata, routedSkillNames, expectedSkillNames, issues);
844
872
  }
845
873
  }
874
+ function templateFileContent(file) {
875
+ return file.content ?? readFileSync(file.sourcePath, 'utf8');
876
+ }
877
+ function skillRouteCategoryByLabel(label) {
878
+ for (const [category, categoryLabel] of Object.entries(SKILL_ROUTE_CATEGORY_LABELS)) {
879
+ if (categoryLabel === label) {
880
+ return category;
881
+ }
882
+ }
883
+ return undefined;
884
+ }
885
+ function splitSkillIndexTableRow(line) {
886
+ const trimmed = line.trim();
887
+ if (!trimmed.startsWith('|') || !trimmed.endsWith('|')) {
888
+ return undefined;
889
+ }
890
+ return trimmed.slice(1, -1).split('|').map((cell) => cell.trim());
891
+ }
892
+ function collectSkillIndexCategoryHeadings(content) {
893
+ const categories = new Set();
894
+ let currentSection;
895
+ for (const line of content.split(/\r?\n/u)) {
896
+ const heading = /^(#{2,3})\s+(.+?)\s*$/u.exec(line.trim());
897
+ if (!heading) {
898
+ continue;
899
+ }
900
+ const [, level, title] = heading;
901
+ if (level === '##') {
902
+ currentSection = title;
903
+ continue;
904
+ }
905
+ if (currentSection === 'Specific Routes' && level === '###') {
906
+ const category = skillRouteCategoryByLabel(title);
907
+ if (category) {
908
+ categories.add(category);
909
+ }
910
+ }
911
+ }
912
+ return categories;
913
+ }
914
+ function collectSkillIndexCategoryGateRows(content) {
915
+ const categories = new Set();
916
+ let currentSection;
917
+ for (const line of content.split(/\r?\n/u)) {
918
+ const heading = /^(#{2,3})\s+(.+?)\s*$/u.exec(line.trim());
919
+ if (heading) {
920
+ const [, level, title] = heading;
921
+ if (level === '##') {
922
+ currentSection = title;
923
+ }
924
+ continue;
925
+ }
926
+ if (currentSection !== 'Route Category Gate') {
927
+ continue;
928
+ }
929
+ const cells = splitSkillIndexTableRow(line);
930
+ if (!cells || cells.length === 0 || /^:?-{3,}:?$/u.test(cells[0] ?? '') || cells[0] === 'Category') {
931
+ continue;
932
+ }
933
+ const category = skillRouteCategoryByLabel(cells[0] ?? '');
934
+ if (category) {
935
+ categories.add(category);
936
+ }
937
+ }
938
+ return categories;
939
+ }
940
+ function readSkillRouteMetadataFromTomlContent(content, sourceLabel, issues) {
941
+ let parsed;
942
+ try {
943
+ parsed = parseTomlText(content);
944
+ }
945
+ catch (error) {
946
+ const message = error instanceof Error ? error.message : String(error);
947
+ pushStrictIssue(issues, `Invalid TOML in ${sourceLabel}: ${message}`);
948
+ return new Map();
949
+ }
950
+ if (!isRecord(parsed)) {
951
+ pushStrictIssue(issues, `${sourceLabel} must contain a TOML table`);
952
+ return new Map();
953
+ }
954
+ if (parsed.schema_version !== '1') {
955
+ pushStrictIssue(issues, `${sourceLabel} schema_version must be "1"`);
956
+ }
957
+ if (!isRecord(parsed.routes)) {
958
+ pushStrictIssue(issues, `${sourceLabel} must define a [routes] table`);
959
+ return new Map();
960
+ }
961
+ const metadata = new Map();
962
+ for (const [skillName, route] of Object.entries(parsed.routes)) {
963
+ if (!/^[a-z][a-z0-9-]*$/u.test(skillName)) {
964
+ pushStrictIssue(issues, `${sourceLabel} route key "${skillName}" must be a skill folder name`);
965
+ continue;
966
+ }
967
+ if (!isRecord(route)) {
968
+ pushStrictIssue(issues, `${sourceLabel} routes.${skillName} must be a TOML table`);
969
+ continue;
970
+ }
971
+ metadata.set(skillName, validateSkillRouteMetadataTable(skillName, route, issues));
972
+ }
973
+ return metadata;
974
+ }
975
+ function validateTemplateProfileSkillRoutes(profile, files, issues) {
976
+ const contentByPath = new Map(files.map((file) => [file.relativePath, templateFileContent(file)]));
977
+ const indexContent = contentByPath.get(SKILL_INDEX_PATH);
978
+ const routesContent = contentByPath.get(SKILL_ROUTES_METADATA_PATH);
979
+ const commandsContent = contentByPath.get('.mustflow/config/commands.toml');
980
+ if (!indexContent) {
981
+ pushStrictIssue(issues, `template profile "${profile}" is missing generated ${SKILL_INDEX_PATH}`);
982
+ return;
983
+ }
984
+ if (!routesContent) {
985
+ pushStrictIssue(issues, `template profile "${profile}" is missing generated ${SKILL_ROUTES_METADATA_PATH}`);
986
+ return;
987
+ }
988
+ let commandsToml;
989
+ if (commandsContent) {
990
+ try {
991
+ const parsedCommands = parseTomlText(commandsContent);
992
+ if (isRecord(parsedCommands)) {
993
+ commandsToml = parsedCommands;
994
+ }
995
+ }
996
+ catch {
997
+ commandsToml = undefined;
998
+ }
999
+ }
1000
+ const routeMetadata = readSkillRouteMetadataFromTomlContent(routesContent, `template profile "${profile}" ${SKILL_ROUTES_METADATA_PATH}`, issues);
1001
+ const skillRoutes = parseSkillIndexRoutes(indexContent);
1002
+ const categoryHeadings = collectSkillIndexCategoryHeadings(indexContent);
1003
+ const categoryGateRows = collectSkillIndexCategoryGateRows(indexContent);
1004
+ const routesByCategory = new Map();
1005
+ const selectedSkillContents = new Map();
1006
+ const routedSkillNames = new Set();
1007
+ for (const [relativePath, content] of contentByPath.entries()) {
1008
+ const skillName = skillRouteName(relativePath);
1009
+ if (skillName) {
1010
+ selectedSkillContents.set(skillName, content);
1011
+ }
1012
+ }
1013
+ for (const route of skillRoutes) {
1014
+ const routeSkillName = skillRouteName(route.skillPath);
1015
+ if (!routeSkillName) {
1016
+ pushStrictIssue(issues, `template profile "${profile}" ${SKILL_INDEX_PATH} route "${route.skillPath}" must point to .mustflow/skills/<name>/SKILL.md`);
1017
+ continue;
1018
+ }
1019
+ routedSkillNames.add(routeSkillName);
1020
+ const metadata = routeMetadata.get(routeSkillName);
1021
+ if (!selectedSkillContents.has(routeSkillName)) {
1022
+ pushStrictIssue(issues, `template profile "${profile}" ${SKILL_INDEX_PATH} route "${routeSkillName}" points to a skill not installed by that profile`);
1023
+ }
1024
+ if (!metadata) {
1025
+ pushStrictIssue(issues, `template profile "${profile}" ${SKILL_ROUTES_METADATA_PATH} is missing metadata for route "${routeSkillName}"`);
1026
+ }
1027
+ if (metadata?.category && route.category !== metadata.category) {
1028
+ pushStrictIssue(issues, `template profile "${profile}" ${SKILL_INDEX_PATH} route "${routeSkillName}" must appear under the ${SKILL_ROUTE_CATEGORY_LABELS[metadata.category]} category section from ${SKILL_ROUTES_METADATA_PATH}`);
1029
+ }
1030
+ if (metadata?.category) {
1031
+ routesByCategory.set(metadata.category, [...(routesByCategory.get(metadata.category) ?? []), routeSkillName]);
1032
+ }
1033
+ const skillContent = selectedSkillContents.get(routeSkillName);
1034
+ const skillCommandIntents = new Set(skillContent ? readFrontmatterList(skillContent, 'command_intents') : []);
1035
+ for (const intentName of route.commandIntents) {
1036
+ if (commandsToml && !isDeclaredCommandIntent(commandsToml, intentName)) {
1037
+ pushStrictIssue(issues, `template profile "${profile}" ${SKILL_INDEX_PATH} route "${routeSkillName}" references unknown command intent "${intentName}"`);
1038
+ }
1039
+ if (!skillCommandIntents.has(intentName)) {
1040
+ pushStrictIssue(issues, `template profile "${profile}" ${SKILL_INDEX_PATH} route "${routeSkillName}" references command intent "${intentName}" not declared by the skill frontmatter`);
1041
+ }
1042
+ }
1043
+ }
1044
+ for (const [skillName, metadata] of routeMetadata.entries()) {
1045
+ if (!routedSkillNames.has(skillName)) {
1046
+ pushStrictIssue(issues, `template profile "${profile}" ${SKILL_ROUTES_METADATA_PATH} route "${skillName}" is not listed in ${SKILL_INDEX_PATH}`);
1047
+ }
1048
+ if (!selectedSkillContents.has(skillName)) {
1049
+ pushStrictIssue(issues, `template profile "${profile}" ${SKILL_ROUTES_METADATA_PATH} route "${skillName}" points to a skill not installed by that profile`);
1050
+ }
1051
+ }
1052
+ for (const skillName of selectedSkillContents.keys()) {
1053
+ if (!routedSkillNames.has(skillName)) {
1054
+ pushStrictIssue(issues, `template profile "${profile}" skill "${skillName}" is installed but not listed in ${SKILL_INDEX_PATH}`);
1055
+ }
1056
+ }
1057
+ for (const category of categoryHeadings) {
1058
+ if (!routesByCategory.has(category)) {
1059
+ pushStrictIssue(issues, `template profile "${profile}" skill index category "${SKILL_ROUTE_CATEGORY_LABELS[category]}" has no route rows`);
1060
+ }
1061
+ }
1062
+ for (const category of categoryGateRows) {
1063
+ if (!routesByCategory.has(category)) {
1064
+ pushStrictIssue(issues, `template profile "${profile}" route category gate references "${SKILL_ROUTE_CATEGORY_LABELS[category]}" without route rows`);
1065
+ }
1066
+ }
1067
+ for (const [category, skillNames] of routesByCategory.entries()) {
1068
+ const hasSelectableMainRoute = skillNames.some((skillName) => {
1069
+ const routeType = routeMetadata.get(skillName)?.routeType;
1070
+ return routeType === 'primary' || routeType === 'authoring';
1071
+ });
1072
+ if (!hasSelectableMainRoute) {
1073
+ pushStrictIssue(issues, `template profile "${profile}" skill category "${SKILL_ROUTE_CATEGORY_LABELS[category]}" must include at least one primary or authoring route`);
1074
+ }
1075
+ }
1076
+ }
1077
+ function validateStrictTemplateSkillProfiles(issues) {
1078
+ let template;
1079
+ try {
1080
+ template = getDefaultTemplate();
1081
+ }
1082
+ catch (error) {
1083
+ const message = error instanceof Error ? error.message : String(error);
1084
+ pushStrictIssue(issues, `default template skill profiles could not be loaded: ${message}`);
1085
+ return;
1086
+ }
1087
+ for (const profile of template.manifest.profiles) {
1088
+ validateTemplateProfileSkillRoutes(profile, getTemplateFiles(template, template.manifest.defaultLocale, profile), issues);
1089
+ }
1090
+ }
846
1091
  function listManagedMarkdownDocuments(projectRoot) {
847
1092
  const documents = [];
848
1093
  for (const relativePath of ['AGENTS.md', '.mustflow/skills/INDEX.md']) {
@@ -872,7 +1117,10 @@ function validateStrictManagedMarkdownIdentities(projectRoot, issues) {
872
1117
  if (!expectation) {
873
1118
  continue;
874
1119
  }
875
- const content = readFileSync(path.join(projectRoot, relativePath), 'utf8');
1120
+ const content = readStrictMustflowText(projectRoot, relativePath, issues);
1121
+ if (content === undefined) {
1122
+ continue;
1123
+ }
876
1124
  const frontmatter = parseSimpleFrontmatter(content);
877
1125
  const documentLabel = formatManagedMarkdownLabel(relativePath, expectation);
878
1126
  if (frontmatter.mustflow_doc !== expectation.docId) {
@@ -964,7 +1212,7 @@ function validateStrictCandidateContractModelConfigs(projectRoot, issues) {
964
1212
  }
965
1213
  let parsed;
966
1214
  try {
967
- parsed = readTomlFile(configPath);
1215
+ parsed = readMustflowTomlFile(projectRoot, model.filePath);
968
1216
  }
969
1217
  catch (error) {
970
1218
  const message = error instanceof Error ? error.message : String(error);
@@ -1015,9 +1263,9 @@ function listSkillDirectories(skillsRoot) {
1015
1263
  }
1016
1264
  return [...skillNames].sort();
1017
1265
  }
1018
- function readSkillResourceManifest(manifestPath, manifestLabel, issues) {
1266
+ function readSkillResourceManifest(projectRoot, manifestLabel, issues) {
1019
1267
  try {
1020
- const parsed = readTomlFile(manifestPath);
1268
+ const parsed = readMustflowTomlFile(projectRoot, manifestLabel);
1021
1269
  if (!isRecord(parsed)) {
1022
1270
  pushStrictIssue(issues, `${manifestLabel} must contain a TOML table`);
1023
1271
  return undefined;
@@ -1099,13 +1347,13 @@ function validateSkillResourceTable(skillDir, manifestLabel, resourcePath, resou
1099
1347
  }
1100
1348
  return normalizedPath;
1101
1349
  }
1102
- function validateSkillResourceManifest(skillDir, manifestLabel, commandsToml, issues) {
1350
+ function validateSkillResourceManifest(projectRoot, skillDir, manifestLabel, commandsToml, issues) {
1103
1351
  const declaredResources = new Set();
1104
1352
  const manifestPath = path.join(skillDir, SKILL_RESOURCE_MANIFEST);
1105
1353
  if (!existsSync(manifestPath)) {
1106
1354
  return declaredResources;
1107
1355
  }
1108
- const manifest = readSkillResourceManifest(manifestPath, manifestLabel, issues);
1356
+ const manifest = readSkillResourceManifest(projectRoot, manifestLabel, issues);
1109
1357
  if (!manifest) {
1110
1358
  return declaredResources;
1111
1359
  }
@@ -1173,13 +1421,15 @@ function validateStrictSkills(projectRoot, commandsToml, issues) {
1173
1421
  pushStrictIssue(issues, `.mustflow/skills/${skillName} is a skill folder without SKILL.md`);
1174
1422
  }
1175
1423
  const manifestLabel = `.mustflow/skills/${skillName}/${SKILL_RESOURCE_MANIFEST}`;
1176
- const declaredResources = validateSkillResourceManifest(skillDir, manifestLabel, commandsToml, issues);
1424
+ const declaredResources = validateSkillResourceManifest(projectRoot, skillDir, manifestLabel, commandsToml, issues);
1177
1425
  validateDeclaredSkillScripts(skillDir, skillName, declaredResources, issues);
1178
1426
  }
1179
1427
  for (const relativePath of skillFiles) {
1180
- const absolutePath = path.join(skillsRoot, relativePath);
1181
- const content = readFileSync(absolutePath, 'utf8');
1182
1428
  const normalizedRelativePath = toPosixPath(relativePath);
1429
+ const content = readStrictMustflowText(projectRoot, `.mustflow/skills/${normalizedRelativePath}`, issues);
1430
+ if (content === undefined) {
1431
+ continue;
1432
+ }
1183
1433
  const skillName = normalizedRelativePath.split('/')[0] ?? '';
1184
1434
  const skillLabel = `.mustflow/skills/${normalizedRelativePath}`;
1185
1435
  const frontmatter = parseSimpleFrontmatter(content);
@@ -1206,7 +1456,10 @@ function validateStrictRepoMap(projectRoot, issues) {
1206
1456
  if (!existsSync(repoMapPath)) {
1207
1457
  return;
1208
1458
  }
1209
- const content = readFileSync(repoMapPath, 'utf8');
1459
+ const content = readStrictMustflowText(projectRoot, 'REPO_MAP.md', issues);
1460
+ if (content === undefined) {
1461
+ return;
1462
+ }
1210
1463
  const frontmatter = parseSimpleFrontmatter(content);
1211
1464
  if (frontmatter.mustflow_doc !== REPO_MAP_DOC_ID) {
1212
1465
  pushStrictIssue(issues, `REPO_MAP.md frontmatter mustflow_doc must be "${REPO_MAP_DOC_ID}"`);
@@ -1229,6 +1482,12 @@ function validateStrictRepoMap(projectRoot, issues) {
1229
1482
  if (!/^[1-9]\d*$/u.test(frontmatter.anchor_count ?? '')) {
1230
1483
  pushStrictIssue(issues, 'REPO_MAP.md frontmatter anchor_count must be a positive integer');
1231
1484
  }
1485
+ if (!ALLOWED_REPO_MAP_DEGRADED_VALUES.has(frontmatter.degraded ?? '')) {
1486
+ pushStrictIssue(issues, 'REPO_MAP.md frontmatter degraded must be true or false');
1487
+ }
1488
+ if (!ALLOWED_REPO_MAP_GIT_LS_FILES_STATUSES.has(frontmatter.git_ls_files_status ?? '')) {
1489
+ pushStrictIssue(issues, 'REPO_MAP.md frontmatter git_ls_files_status must be ok, timeout, max_buffer, or error');
1490
+ }
1232
1491
  if (!REPO_MAP_SOURCE_FINGERPRINT_PATTERN.test(frontmatter.source_fingerprint ?? '')) {
1233
1492
  pushStrictIssue(issues, 'REPO_MAP.md frontmatter source_fingerprint must be sha256:<64 lowercase hex characters>');
1234
1493
  }
@@ -1253,7 +1512,10 @@ function validateStrictContextDocuments(projectRoot, limits, issues) {
1253
1512
  for (const relativePath of contextFiles) {
1254
1513
  const normalizedPath = `.mustflow/context/${toPosixPath(relativePath)}`;
1255
1514
  const absolutePath = path.join(contextRoot, relativePath);
1256
- const content = readFileSync(absolutePath, 'utf8');
1515
+ const content = readStrictMustflowText(projectRoot, normalizedPath, issues);
1516
+ if (content === undefined) {
1517
+ continue;
1518
+ }
1257
1519
  if (exceedsKiBLimit(absolutePath, limits.contextMaxFileKb)) {
1258
1520
  pushStrictIssue(issues, formatStorageLimitMessage(normalizedPath, '[retention.context].max_file_kb', absolutePath, limits.contextMaxFileKb));
1259
1521
  }
@@ -1293,7 +1555,13 @@ function validateStrictRunReceipt(projectRoot, issues) {
1293
1555
  return;
1294
1556
  }
1295
1557
  try {
1296
- const parsed = JSON.parse(readFileSync(latestRunPath, 'utf8'));
1558
+ const content = readStrictMustflowText(projectRoot, '.mustflow/state/runs/latest.json', issues, {
1559
+ maxBytes: MUSTFLOW_JSON_MAX_BYTES,
1560
+ });
1561
+ if (content === undefined) {
1562
+ return;
1563
+ }
1564
+ const parsed = JSON.parse(content);
1297
1565
  if (!isRecord(parsed)) {
1298
1566
  pushStrictIssue(issues, '.mustflow/state/runs/latest.json must contain a JSON object');
1299
1567
  }
@@ -1348,6 +1616,7 @@ function validateStrict(projectRoot, parsed, issues) {
1348
1616
  validateStrictManagedMarkdownIdentities(projectRoot, issues);
1349
1617
  validateStrictRouterIndexes(projectRoot, issues);
1350
1618
  validateStrictSkills(projectRoot, parsed.commandsToml, issues);
1619
+ validateStrictTemplateSkillProfiles(issues);
1351
1620
  validateStrictRepoMap(projectRoot, issues);
1352
1621
  validateStrictContextDocuments(projectRoot, retentionLimits, issues);
1353
1622
  validateStrictSourceAnchors(projectRoot, issues);
@@ -1,7 +1,7 @@
1
1
  import { existsSync } from 'node:fs';
2
2
  import path from 'node:path';
3
3
  import { isRecord } from '../command-contract.js';
4
- import { readTomlFile } from '../toml.js';
4
+ import { readMustflowTomlFile } from '../toml.js';
5
5
  import { REQUIRED_FILES, } from './constants.js';
6
6
  import { VERSIONING_CONFIG_PATH } from '../../../core/version-sources.js';
7
7
  export function hasOwn(table, key) {
@@ -41,7 +41,7 @@ export function validateToml(projectRoot, issues) {
41
41
  continue;
42
42
  }
43
43
  try {
44
- const parsed = readTomlFile(filePath);
44
+ const parsed = readMustflowTomlFile(projectRoot, relativePath);
45
45
  if (!isRecord(parsed)) {
46
46
  issues.push({ message: `${relativePath} must contain a TOML table` });
47
47
  continue;
@@ -1,7 +1,7 @@
1
1
  import { existsSync } from 'node:fs';
2
2
  import path from 'node:path';
3
3
  import { isRecord } from '../command-contract.js';
4
- import { readTomlFile } from '../toml.js';
4
+ import { readMustflowTomlFile } from '../toml.js';
5
5
  import { ALLOWED_TEST_SELECTION_RISKS, FORBIDDEN_TEST_SELECTION_COMMAND_AUTHORITY_FIELDS, TEST_SELECTION_CONFIG_PATH, } from './constants.js';
6
6
  import { isConfiguredCommandIntent, isDeclaredCommandIntent } from './command-intents.js';
7
7
  import { hasOwn, pushStrictIssue, validateAllowedStringField, validateNestedTable, validatePathArrayField, validateRequiredStringField, validateStringArrayField, } from './primitives.js';
@@ -69,7 +69,7 @@ export function validateStrictTestSelectionConfig(projectRoot, commandsToml, iss
69
69
  }
70
70
  let parsed;
71
71
  try {
72
- parsed = readTomlFile(configPath);
72
+ parsed = readMustflowTomlFile(projectRoot, TEST_SELECTION_CONFIG_PATH);
73
73
  }
74
74
  catch (error) {
75
75
  const message = error instanceof Error ? error.message : String(error);
@@ -22,6 +22,8 @@ export function decodeUtf8Tail(buffer, maxTailBytes) {
22
22
  export class BoundedOutputBuffer {
23
23
  #maxTailBytes;
24
24
  #chunks = [];
25
+ #headIndex = 0;
26
+ #headOffset = 0;
25
27
  #tailBytes = 0;
26
28
  #bytes = 0;
27
29
  constructor(maxTailBytes) {
@@ -35,26 +37,49 @@ export class BoundedOutputBuffer {
35
37
  }
36
38
  this.#chunks.push(buffer);
37
39
  this.#tailBytes += buffer.byteLength;
38
- while (this.#tailBytes > this.#maxTailBytes && this.#chunks.length > 0) {
39
- const first = this.#chunks[0];
40
+ while (this.#tailBytes > this.#maxTailBytes && this.#headIndex < this.#chunks.length) {
41
+ const first = this.#chunks[this.#headIndex];
40
42
  const overflow = this.#tailBytes - this.#maxTailBytes;
41
43
  if (!first) {
42
44
  break;
43
45
  }
44
- if (first.byteLength <= overflow) {
45
- this.#chunks.shift();
46
- this.#tailBytes -= first.byteLength;
46
+ const firstAvailableBytes = first.byteLength - this.#headOffset;
47
+ if (firstAvailableBytes <= overflow) {
48
+ this.#headIndex += 1;
49
+ this.#headOffset = 0;
50
+ this.#tailBytes -= firstAvailableBytes;
47
51
  continue;
48
52
  }
49
- this.#chunks[0] = first.subarray(overflow);
53
+ this.#headOffset += overflow;
50
54
  this.#tailBytes -= overflow;
51
55
  }
56
+ this.#compactRetainedChunks();
52
57
  }
53
58
  toSnapshot() {
54
- const tail = decodeUtf8Tail(Buffer.concat(this.#chunks, this.#tailBytes), this.#maxTailBytes);
59
+ const tail = decodeUtf8Tail(Buffer.concat(this.#retainedChunks(), this.#tailBytes), this.#maxTailBytes);
55
60
  return {
56
61
  bytes: this.#bytes,
57
62
  tail: tail.text,
58
63
  };
59
64
  }
65
+ #retainedChunks() {
66
+ if (this.#tailBytes === 0 || this.#headIndex >= this.#chunks.length) {
67
+ return [];
68
+ }
69
+ const chunks = this.#chunks.slice(this.#headIndex);
70
+ if (this.#headOffset > 0 && chunks[0]) {
71
+ chunks[0] = chunks[0].subarray(this.#headOffset);
72
+ }
73
+ return chunks;
74
+ }
75
+ #compactRetainedChunks() {
76
+ if (this.#headIndex === 0) {
77
+ return;
78
+ }
79
+ if (this.#headIndex < 64 && this.#headIndex * 2 < this.#chunks.length) {
80
+ return;
81
+ }
82
+ this.#chunks = this.#chunks.slice(this.#headIndex);
83
+ this.#headIndex = 0;
84
+ }
60
85
  }
@@ -7,6 +7,7 @@ const CHECK_ISSUE_ID_RULES = [
7
7
  ['mustflow.command_contract.max_output_bytes_exceeds_limit', /^\[commands\.(?:defaults|intents\.[^\]]+)\]\.max_output_bytes must be less than or equal to \d+ per output stream$/u],
8
8
  ['mustflow.command_contract.oneshot_stdin_not_closed', /^Oneshot intent [^\s]+ must set stdin = "closed"$/u],
9
9
  ['mustflow.command_contract.long_running_agent_allowed', /^Long-running intent [^\s]+ must not use run_policy = "agent_allowed"$/u],
10
+ ['mustflow.command_contract.agent_shell_requires_allow', /^Agent-runnable shell intent [^\s]+ must set allow_shell = true$/u],
10
11
  ['mustflow.command_contract.executable_source_missing', /^Configured intent [^\s]+ must define argv or mode = "shell" with cmd$/u],
11
12
  ['mustflow.command_contract.shell_background_pattern', /^Shell intent [^\s]+ contains a blocked long-running or background pattern$/u],
12
13
  ['mustflow.command_contract.long_running_command_pattern', /^Intent [^\s]+ contains a blocked long-running or background command pattern$/u],
@@ -17,7 +18,7 @@ const CHECK_ISSUE_ID_RULES = [
17
18
  ['mustflow.command_contract.effects_invalid', /^(?:Strict: )?(?:\[commands\.(?:resources|intents\.[^\]]+\.effects)[^\]]*\]|Command effect for intent [^\s]+ must define path, paths, or lock)/u],
18
19
  ['mustflow.command_contract.effect_path_escape', /^Strict: Command effect path must stay inside the current root:/u],
19
20
  ['mustflow.command_contract.shared_writes_without_effects', /^Strict warning: configured agent-runnable intents .+ share path:.+ through writes without explicit effects or resource locks$/u],
20
- ['mustflow.command_contract.broad_env_inheritance', /^Strict warning: configured agent-runnable intent [^\s]+ (?:implicitly inherits the host environment|uses env_policy = "inherit")/u],
21
+ ['mustflow.command_contract.broad_env_inheritance', /^Strict(?: warning)?: configured agent-runnable intent [^\s]+ (?:implicitly inherits the host environment|uses env_policy = "inherit")/u],
21
22
  ['mustflow.command_contract.project_local_bin_bare_executable', /^Strict warning: configured agent-runnable intent [^\s]+ uses bare executable "[^"]+" that matches project-local node_modules\/\.bin/u],
22
23
  ['mustflow.prompt_cache.required', /^Strict: \[prompt_cache\] table is required$/u],
23
24
  ['mustflow.prompt_cache.volatile_in_stable', /^Strict: \[prompt_cache\.layers\.stable\]\.read must not include volatile path /u],
@@ -48,6 +49,11 @@ const CHECK_ISSUE_ID_RULES = [
48
49
  ['mustflow.skill.route_metadata_category_mismatch', /^Strict: \.mustflow\/skills\/INDEX\.md route "[^"]+" must appear under the .+ category section from \.mustflow\/skills\/routes\.toml$/u],
49
50
  ['mustflow.skill.route_metadata_unknown_reference', /^Strict: \.mustflow\/skills\/routes\.toml route "[^"]+" references unknown mutually exclusive route "[^"]+"$/u],
50
51
  ['mustflow.skill.route_metadata_asymmetric_exclusion', /^Strict warning: \.mustflow\/skills\/routes\.toml route "[^"]+" lists "[^"]+" as mutually exclusive but the reverse route does not$/u],
52
+ ['mustflow.skill.template_profile_empty_category', /^Strict: template profile "[^"]+" (?:skill index category ".+" has no route rows|route category gate references ".+" without route rows)$/u],
53
+ ['mustflow.skill.template_profile_dead_route', /^Strict: template profile "[^"]+" (?:\.mustflow\/skills\/INDEX\.md route "[^"]+" points to a skill not installed by that profile|\.mustflow\/skills\/routes\.toml route "[^"]+" points to a skill not installed by that profile|skill "[^"]+" is installed but not listed in \.mustflow\/skills\/INDEX\.md)$/u],
54
+ ['mustflow.skill.template_profile_missing_main_route', /^Strict: template profile "[^"]+" skill category ".+" must include at least one primary or authoring route$/u],
55
+ ['mustflow.skill.template_profile_command_intent_drift', /^Strict: template profile "[^"]+" \.mustflow\/skills\/INDEX\.md route "[^"]+" references (?:unknown command intent "[^"]+"|command intent "[^"]+" not declared by the skill frontmatter)$/u],
56
+ ['mustflow.skill.template_profile_metadata_mismatch', /^Strict: template profile "[^"]+" (?:\.mustflow\/skills\/routes\.toml is missing metadata for route "[^"]+"|\.mustflow\/skills\/routes\.toml route "[^"]+" is not listed in \.mustflow\/skills\/INDEX\.md|\.mustflow\/skills\/INDEX\.md route "[^"]+" must appear under the .+ category section from \.mustflow\/skills\/routes\.toml)$/u],
51
57
  ['mustflow.skill.resource_unknown_command_intent', /^Strict: \.mustflow\/skills\/[^/]+\/resources\.toml script [^\s]+ references unknown command intent "[^"]+"$/u],
52
58
  ['mustflow.source_anchor.invalid_format', /^Strict: source anchor .+ has invalid format:/u],
53
59
  ['mustflow.source_anchor.duplicate_id', /^Strict: source anchor id "[^"]+" is duplicated:/u],