mustflow 2.114.6 → 2.114.12

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.
@@ -111,7 +111,29 @@ function createSkillRouteScriptPackSuggestions(mustflowRoot, report, paths) {
111
111
  issues: suggestionReport.issues,
112
112
  };
113
113
  }
114
+ function routeDependencyReason(candidate) {
115
+ return candidate.selection_reasons.find((reason) => reason.startsWith('route_dependency:')) ?? null;
116
+ }
117
+ function conflictHintCandidates(report) {
118
+ const candidates = [
119
+ ...(report.selected.main ? [report.selected.main] : []),
120
+ ...report.selected.adjuncts,
121
+ ...report.candidates,
122
+ ];
123
+ const seenSkills = new Set();
124
+ return candidates.filter((candidate) => {
125
+ if (seenSkills.has(candidate.skill) || candidate.route_card.route_dependencies.conflicts_with.length === 0) {
126
+ return false;
127
+ }
128
+ seenSkills.add(candidate.skill);
129
+ return true;
130
+ });
131
+ }
114
132
  function renderSkillRouteReport(report, lang, warnings = []) {
133
+ const dependencyReads = report.selected.adjuncts
134
+ .map((candidate) => ({ candidate, reason: routeDependencyReason(candidate) }))
135
+ .filter((entry) => entry.reason !== null);
136
+ const conflictHints = conflictHintCandidates(report);
115
137
  const lines = [
116
138
  'mustflow skill route',
117
139
  `${t(lang, 'label.mustflowRoot')}: ${resolveMustflowRoot()}`,
@@ -125,7 +147,19 @@ function renderSkillRouteReport(report, lang, warnings = []) {
125
147
  }
126
148
  else {
127
149
  for (const candidate of report.candidates) {
128
- lines.push(`- ${candidate.skill} (${candidate.route_type}, score ${candidate.score.toFixed(1)})`, ` path: ${candidate.skill_path}`, ` reasons: ${candidate.selection_reasons.join(', ') || t(lang, 'value.none')}`);
150
+ lines.push(`- ${candidate.skill} (${candidate.route_type}, score ${candidate.score.toFixed(1)})`, ` path: ${candidate.skill_path}`, ` reasons: ${candidate.selection_reasons.join(', ') || t(lang, 'value.none')}`, ` matched_dimensions: ${candidate.matched_dimensions.join(', ') || t(lang, 'value.none')}`);
151
+ }
152
+ }
153
+ if (dependencyReads.length > 0) {
154
+ lines.push('', 'Dependency reads');
155
+ for (const { candidate, reason } of dependencyReads) {
156
+ lines.push(`- ${candidate.skill}`, ` reason: ${reason}`, ` path: ${candidate.skill_path}`);
157
+ }
158
+ }
159
+ if (conflictHints.length > 0) {
160
+ lines.push('', 'Conflict hints');
161
+ for (const candidate of conflictHints) {
162
+ lines.push(`- ${candidate.skill}`, ` conflicts_with: ${candidate.route_card.route_dependencies.conflicts_with.join(', ')}`, ` path: ${candidate.skill_path}`);
129
163
  }
130
164
  }
131
165
  lines.push('', 'Read plan', ...report.read_plan.selected_skill_paths.map((skillPath) => `- read selected skill: ${skillPath}`), `- avoid by default: ${report.read_plan.avoid_by_default.join(', ') || t(lang, 'value.none')}`, `- fallback route metadata: ${report.read_plan.fallback_route_metadata.path}`, '', 'Source files', ...report.source_files.map((sourceFile) => `- ${sourceFile}`));
@@ -683,6 +683,63 @@ function readOptionalStringArray(value, label, issues) {
683
683
  }
684
684
  return value.map((entry) => entry.trim());
685
685
  }
686
+ function readOptionalSlugArray(value, label, issues) {
687
+ const values = readOptionalStringArray(value, label, issues);
688
+ for (const value of values) {
689
+ if (!/^[a-z][a-z0-9_-]*$/u.test(value)) {
690
+ pushStrictIssue(issues, `${label} entry "${value}" must use lowercase slug text`);
691
+ }
692
+ }
693
+ return values;
694
+ }
695
+ function readSkillRouteMetadataContexts(value, label, issues) {
696
+ if (value !== undefined && !isRecord(value)) {
697
+ pushStrictIssue(issues, `${label}.contexts must be a TOML table`);
698
+ }
699
+ const contexts = isRecord(value) ? value : {};
700
+ return {
701
+ fileTypes: readOptionalSlugArray(contexts.file_types, `${label}.contexts.file_types`, issues),
702
+ frameworks: readOptionalSlugArray(contexts.frameworks, `${label}.contexts.frameworks`, issues),
703
+ layers: readOptionalSlugArray(contexts.layers, `${label}.contexts.layers`, issues),
704
+ patternCategories: readOptionalSlugArray(contexts.pattern_categories, `${label}.contexts.pattern_categories`, issues),
705
+ positiveTerms: readOptionalSlugArray(contexts.positive_terms, `${label}.contexts.positive_terms`, issues),
706
+ negativeTerms: readOptionalSlugArray(contexts.negative_terms, `${label}.contexts.negative_terms`, issues),
707
+ };
708
+ }
709
+ function readSkillRouteUnlockRules(value, label, issues) {
710
+ if (value === undefined) {
711
+ return [];
712
+ }
713
+ if (!Array.isArray(value) || value.some((entry) => !isRecord(entry))) {
714
+ pushStrictIssue(issues, `${label} must be an array of TOML tables`);
715
+ return [];
716
+ }
717
+ return value
718
+ .map((entry, index) => {
719
+ const signal = typeof entry.signal === 'string' ? entry.signal.trim() : '';
720
+ const skill = typeof entry.skill === 'string' ? entry.skill.trim() : '';
721
+ if (!/^[a-z][a-z0-9_-]*$/u.test(signal)) {
722
+ pushStrictIssue(issues, `${label}[${index}].signal must use lowercase slug text`);
723
+ }
724
+ if (!/^[a-z][a-z0-9-]*$/u.test(skill)) {
725
+ pushStrictIssue(issues, `${label}[${index}].skill must be a skill folder name`);
726
+ }
727
+ return { signal, skill };
728
+ })
729
+ .filter((entry) => entry.signal && entry.skill);
730
+ }
731
+ function readSkillRouteMetadataDependencies(value, label, issues) {
732
+ if (value !== undefined && !isRecord(value)) {
733
+ pushStrictIssue(issues, `${label}.dependencies must be a TOML table`);
734
+ }
735
+ const dependencies = isRecord(value) ? value : {};
736
+ return {
737
+ requiresSkills: readOptionalSlugArray(dependencies.requires_skills, `${label}.dependencies.requires_skills`, issues),
738
+ suggestsAdjuncts: readOptionalSlugArray(dependencies.suggests_adjuncts, `${label}.dependencies.suggests_adjuncts`, issues),
739
+ conflictsWith: readOptionalSlugArray(dependencies.conflicts_with, `${label}.dependencies.conflicts_with`, issues),
740
+ unlocksOn: readSkillRouteUnlockRules(dependencies.unlocks_on, `${label}.dependencies.unlocks_on`, issues),
741
+ };
742
+ }
686
743
  function validateSkillRouteMetadataTable(skillName, route, issues) {
687
744
  const label = `${SKILL_ROUTES_METADATA_PATH} routes.${skillName}`;
688
745
  const rawCategory = typeof route.category === 'string' ? route.category : undefined;
@@ -693,6 +750,8 @@ function validateSkillRouteMetadataTable(skillName, route, issues) {
693
750
  const profiles = readOptionalStringArray(route.profiles, `${label}.profiles`, issues);
694
751
  const appliesToReasons = readOptionalStringArray(route.applies_to_reasons, `${label}.applies_to_reasons`, issues);
695
752
  const mutuallyExclusiveWith = readOptionalStringArray(route.mutually_exclusive_with, `${label}.mutually_exclusive_with`, issues);
753
+ const contexts = readSkillRouteMetadataContexts(route.contexts, label, issues);
754
+ const dependencies = readSkillRouteMetadataDependencies(route.dependencies, label, issues);
696
755
  if (!category) {
697
756
  pushStrictIssue(issues, `${label}.category must be one of ${[...ALLOWED_SKILL_ROUTE_CATEGORIES].join(', ')}`);
698
757
  }
@@ -718,6 +777,8 @@ function validateSkillRouteMetadataTable(skillName, route, issues) {
718
777
  routeType,
719
778
  priority: route.priority,
720
779
  mutuallyExclusiveWith,
780
+ contexts,
781
+ dependencies,
721
782
  };
722
783
  }
723
784
  function readSkillRouteMetadata(projectRoot, issues) {
@@ -759,6 +820,16 @@ function readSkillRouteMetadata(projectRoot, issues) {
759
820
  }
760
821
  return metadata;
761
822
  }
823
+ function validateAdjunctRouteDependency(metadata, issues, skillName, targetSkill, relation) {
824
+ const targetRoute = metadata.get(targetSkill);
825
+ if (!targetRoute || targetRoute.routeType === 'adjunct') {
826
+ return;
827
+ }
828
+ pushStrictIssue(issues, [
829
+ `${SKILL_ROUTES_METADATA_PATH} route "${skillName}" ${relation} route "${targetSkill}"`,
830
+ ` must point to an adjunct route, found "${targetRoute.routeType ?? 'unknown'}"`,
831
+ ].join(''));
832
+ }
762
833
  function validateSkillRouteMetadataAlignment(metadata, routeSkillNames, expectedSkillNames, issues) {
763
834
  for (const skillName of routeSkillNames) {
764
835
  if (!metadata.has(skillName)) {
@@ -783,6 +854,47 @@ function validateSkillRouteMetadataAlignment(metadata, routeSkillNames, expected
783
854
  pushStrictWarning(issues, `${SKILL_ROUTES_METADATA_PATH} route "${skillName}" lists "${otherSkillName}" as mutually exclusive but the reverse route does not`);
784
855
  }
785
856
  }
857
+ for (const requiredSkill of route.dependencies.requiresSkills) {
858
+ if (requiredSkill === skillName) {
859
+ pushStrictIssue(issues, `${SKILL_ROUTES_METADATA_PATH} route "${skillName}" cannot require itself`);
860
+ }
861
+ else if (!metadata.has(requiredSkill)) {
862
+ pushStrictIssue(issues, `${SKILL_ROUTES_METADATA_PATH} route "${skillName}" requires unknown route "${requiredSkill}"`);
863
+ }
864
+ }
865
+ for (const adjunctSkill of route.dependencies.suggestsAdjuncts) {
866
+ if (adjunctSkill === skillName) {
867
+ pushStrictIssue(issues, `${SKILL_ROUTES_METADATA_PATH} route "${skillName}" cannot suggest itself as an adjunct`);
868
+ }
869
+ else if (!metadata.has(adjunctSkill)) {
870
+ pushStrictIssue(issues, `${SKILL_ROUTES_METADATA_PATH} route "${skillName}" suggests unknown adjunct route "${adjunctSkill}"`);
871
+ }
872
+ else {
873
+ validateAdjunctRouteDependency(metadata, issues, skillName, adjunctSkill, 'suggests adjunct');
874
+ }
875
+ }
876
+ for (const conflictingSkill of route.dependencies.conflictsWith) {
877
+ if (conflictingSkill === skillName) {
878
+ pushStrictIssue(issues, `${SKILL_ROUTES_METADATA_PATH} route "${skillName}" cannot conflict with itself`);
879
+ }
880
+ else if (!metadata.has(conflictingSkill)) {
881
+ pushStrictIssue(issues, `${SKILL_ROUTES_METADATA_PATH} route "${skillName}" conflicts with unknown route "${conflictingSkill}"`);
882
+ }
883
+ else if (!metadata.get(conflictingSkill)?.dependencies.conflictsWith.includes(skillName)) {
884
+ pushStrictWarning(issues, `${SKILL_ROUTES_METADATA_PATH} route "${skillName}" conflicts with "${conflictingSkill}" but the reverse route does not`);
885
+ }
886
+ }
887
+ for (const unlockRule of route.dependencies.unlocksOn) {
888
+ if (unlockRule.skill === skillName) {
889
+ pushStrictIssue(issues, `${SKILL_ROUTES_METADATA_PATH} route "${skillName}" cannot unlock itself`);
890
+ }
891
+ else if (!metadata.has(unlockRule.skill)) {
892
+ pushStrictIssue(issues, `${SKILL_ROUTES_METADATA_PATH} route "${skillName}" unlocks unknown route "${unlockRule.skill}" on signal "${unlockRule.signal}"`);
893
+ }
894
+ else {
895
+ validateAdjunctRouteDependency(metadata, issues, skillName, unlockRule.skill, `unlocks on signal "${unlockRule.signal}"`);
896
+ }
897
+ }
786
898
  }
787
899
  }
788
900
  function validateSkillIndexRoutes(projectRoot, commandsToml, skillFiles, issues) {
@@ -1,3 +1,10 @@
1
+ const ROUTE_METADATA_UNKNOWN_REFERENCE_PATTERN = new RegExp('^Strict: \\.mustflow\\/skills\\/routes\\.toml route "[^"]+" ' +
2
+ '(?:references unknown mutually exclusive route|requires unknown route|' +
3
+ 'suggests unknown adjunct route|conflicts with unknown route|unlocks unknown route) ' +
4
+ '"[^"]+"(?: on signal "[^"]+")?$', 'u');
5
+ const ROUTE_METADATA_INVALID_DEPENDENCY_PATTERN = new RegExp('^Strict: \\.mustflow\\/skills\\/routes\\.toml route "[^"]+" ' +
6
+ '(?:suggests adjunct|unlocks on signal "[^"]+") route "[^"]+" ' +
7
+ 'must point to an adjunct route, found "[^"]+"$', 'u');
1
8
  const CHECK_ISSUE_ID_RULES = [
2
9
  ['mustflow.command_contract.intent_table_missing', /^Missing \[intents\] table in \.mustflow\/config\/commands\.toml$/u],
3
10
  ['mustflow.command_contract.intent_not_table', /^Intent [^\s]+ must be a TOML table$/u],
@@ -58,8 +65,13 @@ const CHECK_ISSUE_ID_RULES = [
58
65
  ['mustflow.skill.route_metadata_unlisted', /^Strict: \.mustflow\/skills\/routes\.toml route "[^"]+" is not listed in \.mustflow\/skills\/INDEX\.md$/u],
59
66
  ['mustflow.skill.route_metadata_missing_document', /^Strict: \.mustflow\/skills\/routes\.toml route "[^"]+" points to a missing skill document$/u],
60
67
  ['mustflow.skill.route_metadata_category_mismatch', /^Strict: \.mustflow\/skills\/INDEX\.md route "[^"]+" must appear under the .+ category section from \.mustflow\/skills\/routes\.toml$/u],
61
- ['mustflow.skill.route_metadata_unknown_reference', /^Strict: \.mustflow\/skills\/routes\.toml route "[^"]+" references unknown mutually exclusive route "[^"]+"$/u],
68
+ [
69
+ 'mustflow.skill.route_metadata_unknown_reference',
70
+ ROUTE_METADATA_UNKNOWN_REFERENCE_PATTERN,
71
+ ],
72
+ ['mustflow.skill.route_metadata_invalid_dependency', ROUTE_METADATA_INVALID_DEPENDENCY_PATTERN],
62
73
  ['mustflow.skill.route_metadata_asymmetric_exclusion', /^Strict warning: \.mustflow\/skills\/routes\.toml route "[^"]+" lists "[^"]+" as mutually exclusive but the reverse route does not$/u],
74
+ ['mustflow.skill.route_metadata_asymmetric_conflict', /^Strict warning: \.mustflow\/skills\/routes\.toml route "[^"]+" conflicts with "[^"]+" but the reverse route does not$/u],
63
75
  ['mustflow.skill.route_fixture_invalid', /^Strict: \.mustflow\/skills\/route-fixtures\.json (?:must|is not valid JSON|cases\[\d+\])/u],
64
76
  ['mustflow.skill.route_fixture_mismatch', /^Strict: Skill route fixture "[^"]+" (?:expected|forbids)/u],
65
77
  ['mustflow.skill.template_profile_empty_category', /^Strict: template profile "[^"]+" (?:skill index category ".+" has no route rows|route category gate references ".+" without route rows)$/u],
@@ -13,6 +13,10 @@ const DEFAULT_MAX_CANDIDATES = 5;
13
13
  const DEFAULT_MAX_MAIN = 1;
14
14
  const DEFAULT_MAX_ADJUNCTS = 2;
15
15
  const PATH_SKILL_HINT_SCORE = 25;
16
+ const PATTERN_SIGNAL_TERM_SCORE = 12;
17
+ const PATTERN_SIGNAL_MAX_SCORE = 48;
18
+ const NEGATIVE_SIGNAL_TERM_PENALTY = -25;
19
+ const NEGATIVE_SIGNAL_MAX_PENALTY = -75;
16
20
  const DOCS_TREE_MARKDOWN_PATH_PATTERN = /(?:^|\/)(?:docs|docs-site|documentation|\.mustflow\/docs|\.mustflow\/context)\/.+\.(?:md|mdx)$/u;
17
21
  const ROOT_DOCUMENT_BASENAMES = [
18
22
  'readme',
@@ -33,6 +37,12 @@ const ROOT_DOCUMENT_BASENAMES = [
33
37
  'architecture',
34
38
  'api',
35
39
  ];
40
+ const EMPTY_ROUTE_DEPENDENCIES = {
41
+ requires_skills: [],
42
+ suggests_adjuncts: [],
43
+ conflicts_with: [],
44
+ unlocks_on: [],
45
+ };
36
46
  const ROUTE_TYPE_WEIGHTS = {
37
47
  primary: 18,
38
48
  authoring: 16,
@@ -101,6 +111,46 @@ function readStringArrayFromTable(table, key) {
101
111
  ? value.map((entry) => entry.trim()).filter(Boolean)
102
112
  : [];
103
113
  }
114
+ function readRouteSignalProfile(route) {
115
+ const contexts = route.contexts;
116
+ if (!isRecord(contexts)) {
117
+ return {
118
+ positiveTerms: [],
119
+ negativeTerms: [],
120
+ };
121
+ }
122
+ return {
123
+ positiveTerms: tokenize(readStringArrayFromTable(contexts, 'positive_terms').join(' ')),
124
+ negativeTerms: tokenize(readStringArrayFromTable(contexts, 'negative_terms').join(' ')),
125
+ };
126
+ }
127
+ function readRouteUnlockRules(table) {
128
+ const value = table.unlocks_on;
129
+ if (!Array.isArray(value)) {
130
+ return [];
131
+ }
132
+ return value
133
+ .filter(isRecord)
134
+ .map((entry) => {
135
+ return {
136
+ signal: typeof entry.signal === 'string' ? entry.signal.trim() : '',
137
+ skill: typeof entry.skill === 'string' ? entry.skill.trim() : '',
138
+ };
139
+ })
140
+ .filter((entry) => entry.signal && entry.skill);
141
+ }
142
+ function readRouteDependencies(route) {
143
+ const dependencies = route.dependencies;
144
+ if (!isRecord(dependencies)) {
145
+ return EMPTY_ROUTE_DEPENDENCIES;
146
+ }
147
+ return {
148
+ requires_skills: readStringArrayFromTable(dependencies, 'requires_skills'),
149
+ suggests_adjuncts: readStringArrayFromTable(dependencies, 'suggests_adjuncts'),
150
+ conflicts_with: readStringArrayFromTable(dependencies, 'conflicts_with'),
151
+ unlocks_on: readRouteUnlockRules(dependencies),
152
+ };
153
+ }
104
154
  function readFrontmatterBlock(content) {
105
155
  if (!content.startsWith('---')) {
106
156
  return [];
@@ -174,6 +224,8 @@ function readSkillRouteMetadata(projectRoot) {
174
224
  priority: Number.isInteger(route.priority) ? Number(route.priority) : 0,
175
225
  appliesToReasons: readStringArrayFromTable(route, 'applies_to_reasons'),
176
226
  mutuallyExclusiveWith: readStringArrayFromTable(route, 'mutually_exclusive_with'),
227
+ signalProfile: readRouteSignalProfile(route),
228
+ dependencies: readRouteDependencies(route),
177
229
  });
178
230
  }
179
231
  }
@@ -271,6 +323,10 @@ function countMatches(needles, haystack) {
271
323
  const haystackSet = new Set(haystack);
272
324
  return needles.filter((needle) => haystackSet.has(needle)).length;
273
325
  }
326
+ function collectMatchedTerms(needles, haystack) {
327
+ const haystackSet = new Set(haystack);
328
+ return [...new Set(needles.filter((needle) => haystackSet.has(needle)))].sort((left, right) => left.localeCompare(right));
329
+ }
274
330
  function routeTextTerms(route, skillName) {
275
331
  return tokenize([
276
332
  skillName,
@@ -282,6 +338,65 @@ function routeTextTerms(route, skillName) {
282
338
  route.skillPath,
283
339
  ].join(' '));
284
340
  }
341
+ function createExcerptReference(skillPath, section) {
342
+ return {
343
+ source_path: skillPath,
344
+ section,
345
+ read_when: [
346
+ 'candidate scores tie within the same route category',
347
+ 'matched dimensions are too broad to choose a single skill',
348
+ 'task language overlaps a listed negative or conflicting signal',
349
+ ],
350
+ };
351
+ }
352
+ function createRouteCard(skillPath, matchedDimensions, routeDependencies) {
353
+ return {
354
+ source: 'route_metadata_and_skill_frontmatter',
355
+ index_read_policy: 'fallback_only',
356
+ compact_fields: [
357
+ 'skill',
358
+ 'skill_path',
359
+ 'trigger',
360
+ 'category',
361
+ 'route_type',
362
+ 'priority',
363
+ 'applies_to_reasons',
364
+ 'score_breakdown',
365
+ 'selection_reasons',
366
+ 'verification_intents',
367
+ 'route_dependencies',
368
+ ],
369
+ matched_dimensions: matchedDimensions,
370
+ route_dependencies: routeDependencies,
371
+ use_when_excerpt: createExcerptReference(skillPath, 'use-when'),
372
+ do_not_use_excerpt: createExcerptReference(skillPath, 'do-not-use-when'),
373
+ read_strategy: [
374
+ 'Read the selected SKILL.md before editing matching scope.',
375
+ 'For close ties, compare only Use When and Do Not Use When excerpts before loading full competing skills.',
376
+ 'Use route_dependencies to add required or suggested adjunct skill reads without loading the expanded index.',
377
+ 'Keep .mustflow/skills/INDEX.md out of the prompt unless route metadata and excerpts are insufficient.',
378
+ ],
379
+ };
380
+ }
381
+ function createPatternSignalBreakdown(signalProfile, taskTerms, pathTerms) {
382
+ if (signalProfile.positiveTerms.length === 0 && signalProfile.negativeTerms.length === 0) {
383
+ return {
384
+ positiveMatches: [],
385
+ negativeMatches: [],
386
+ patternScore: 0,
387
+ negativePenalty: 0,
388
+ };
389
+ }
390
+ const inputTerms = [...new Set([...taskTerms, ...pathTerms])];
391
+ const positiveMatches = collectMatchedTerms(signalProfile.positiveTerms, inputTerms);
392
+ const negativeMatches = collectMatchedTerms(signalProfile.negativeTerms, inputTerms);
393
+ return {
394
+ positiveMatches,
395
+ negativeMatches,
396
+ patternScore: Math.min(positiveMatches.length * PATTERN_SIGNAL_TERM_SCORE, PATTERN_SIGNAL_MAX_SCORE),
397
+ negativePenalty: Math.max(negativeMatches.length * NEGATIVE_SIGNAL_TERM_PENALTY, NEGATIVE_SIGNAL_MAX_PENALTY),
398
+ };
399
+ }
285
400
  function createCandidate(route, metadata, taskTerms, pathTerms, pathSkillHints, reasons) {
286
401
  const skill = skillNameFromPath(route.skillPath);
287
402
  const terms = routeTextTerms(route, skill);
@@ -289,19 +404,38 @@ function createCandidate(route, metadata, taskTerms, pathTerms, pathSkillHints,
289
404
  const taskMatches = countMatches(taskTerms, terms);
290
405
  const pathMatches = countMatches(pathTerms, terms);
291
406
  const pathSkillHintMatched = pathSkillHints.has(skill);
407
+ const patternSignals = createPatternSignalBreakdown(metadata.signalProfile, taskTerms, pathTerms);
292
408
  const breakdown = {
293
409
  reason_match: matchedReasons.length * 35,
294
410
  task_text_match: taskMatches * 6,
295
411
  path_match: pathMatches * 6 + (pathSkillHintMatched ? PATH_SKILL_HINT_SCORE : 0),
412
+ pattern_signal_match: patternSignals.patternScore,
413
+ negative_signal_penalty: patternSignals.negativePenalty,
296
414
  route_type_weight: ROUTE_TYPE_WEIGHTS[metadata.routeType] ?? 0,
297
415
  priority_weight: Math.max(0, Math.min(metadata.priority, 100)) / 5,
298
416
  };
299
417
  const score = Object.values(breakdown).reduce((total, value) => total + value, 0);
418
+ const matchedDimensions = [
419
+ ...(matchedReasons.length > 0 ? ['reason'] : []),
420
+ ...(taskMatches > 0 ? ['task_terms'] : []),
421
+ ...(pathMatches > 0 ? ['path_terms'] : []),
422
+ ...(pathSkillHintMatched ? ['path_skill_hint'] : []),
423
+ ...(patternSignals.positiveMatches.length > 0 ? ['pattern_signal'] : []),
424
+ ...(patternSignals.negativeMatches.length > 0 ? ['negative_signal'] : []),
425
+ ...(metadata.routeType !== 'unknown' ? ['route_type'] : []),
426
+ ...(metadata.priority > 0 ? ['priority'] : []),
427
+ ];
300
428
  const selectionReasons = [
301
429
  ...matchedReasons.map((reason) => `reason:${reason}`),
302
430
  ...(taskMatches > 0 ? [`task_terms:${taskMatches}`] : []),
303
431
  ...(pathMatches > 0 ? [`path_terms:${pathMatches}`] : []),
304
432
  ...(pathSkillHintMatched ? [`path_skill_hint:${skill}`] : []),
433
+ ...(patternSignals.positiveMatches.length > 0
434
+ ? [`pattern_terms:${patternSignals.positiveMatches.join('|')}`]
435
+ : []),
436
+ ...(patternSignals.negativeMatches.length > 0
437
+ ? [`negative_terms:${patternSignals.negativeMatches.join('|')}`]
438
+ : []),
305
439
  `route_type:${metadata.routeType}`,
306
440
  `priority:${metadata.priority}`,
307
441
  ];
@@ -316,6 +450,8 @@ function createCandidate(route, metadata, taskTerms, pathTerms, pathSkillHints,
316
450
  score,
317
451
  score_breakdown: breakdown,
318
452
  selection_reasons: selectionReasons,
453
+ matched_dimensions: matchedDimensions,
454
+ route_card: createRouteCard(route.skillPath, matchedDimensions, metadata.dependencies),
319
455
  verification_intents: route.commandIntents,
320
456
  };
321
457
  }
@@ -333,20 +469,107 @@ function sortCandidates(left, right) {
333
469
  function isSelectableMain(candidate) {
334
470
  return candidate.route_type === 'primary' || candidate.route_type === 'authoring' || candidate.route_type === 'external';
335
471
  }
336
- function selectAdjuncts(main, allCandidates, metadata) {
472
+ function hasDependencySignal(signal, dependencySignals, taskTerms, pathTerms) {
473
+ if (dependencySignals.has(signal)) {
474
+ return true;
475
+ }
476
+ const inputTerms = new Set([...taskTerms, ...pathTerms]);
477
+ const signalTerms = signal
478
+ .split('_')
479
+ .map((term) => (term === 'changed' ? 'change' : term))
480
+ .filter((term) => term !== 'or');
481
+ return signalTerms.length > 0 && signalTerms.every((term) => inputTerms.has(term));
482
+ }
483
+ function collectDependencySignals(paths, reasons, taskTerms, pathTerms) {
484
+ const dependencySignals = new Set(reasons);
485
+ const inputTerms = new Set([...taskTerms, ...pathTerms]);
486
+ if (inputTerms.has('output') &&
487
+ (inputTerms.has('machine') || inputTerms.has('json') || inputTerms.has('jsonl') || inputTerms.has('cli'))) {
488
+ dependencySignals.add('machine_output_changed');
489
+ }
490
+ if (inputTerms.has('schema') ||
491
+ inputTerms.has('fixture') ||
492
+ paths.some((pathValue) => /(?:^|\/)(?:schemas|fixtures|tests\/fixtures)(?:\/|$)/u.test(pathValue))) {
493
+ dependencySignals.add('schema_or_fixture_changed');
494
+ }
495
+ if (inputTerms.has('followup') ||
496
+ (inputTerms.has('follow') && inputTerms.has('up')) ||
497
+ (inputTerms.has('next') && inputTerms.has('action'))) {
498
+ dependencySignals.add('concrete_followup_exists');
499
+ }
500
+ return dependencySignals;
501
+ }
502
+ function addDependencySelectionReason(candidate, reason) {
503
+ const matchedDimensions = [...new Set([...candidate.matched_dimensions, 'route_dependency'])];
504
+ return {
505
+ ...candidate,
506
+ selection_reasons: [...new Set([...candidate.selection_reasons, reason])],
507
+ matched_dimensions: matchedDimensions,
508
+ route_card: {
509
+ ...candidate.route_card,
510
+ matched_dimensions: matchedDimensions,
511
+ },
512
+ };
513
+ }
514
+ function routeConflictsFor(candidate, metadata) {
515
+ const routeMetadata = metadata.get(candidate.skill);
516
+ return new Set([
517
+ ...(routeMetadata?.mutuallyExclusiveWith ?? []),
518
+ ...candidate.route_card.route_dependencies.conflicts_with,
519
+ ]);
520
+ }
521
+ function routesConflict(left, right, metadata) {
522
+ return routeConflictsFor(left, metadata).has(right.skill) || routeConflictsFor(right, metadata).has(left.skill);
523
+ }
524
+ function selectAdjuncts(main, scoredCandidates, allCandidatesBySkill, metadata, dependencySignals, taskTerms, pathTerms) {
337
525
  if (!main) {
338
526
  return [];
339
527
  }
340
- const mainMetadata = metadata.get(main.skill);
341
- const excluded = new Set([main.skill, ...(mainMetadata?.mutuallyExclusiveWith ?? [])]);
342
- return allCandidates
528
+ const selectedMain = main;
529
+ const mainMetadata = metadata.get(selectedMain.skill);
530
+ const excluded = new Set([
531
+ selectedMain.skill,
532
+ ...(mainMetadata?.mutuallyExclusiveWith ?? []),
533
+ ...selectedMain.route_card.route_dependencies.conflicts_with,
534
+ ]);
535
+ const selected = [];
536
+ function addDependencySkill(skill, reason) {
537
+ const dependencyCandidate = allCandidatesBySkill.get(skill);
538
+ if (!dependencyCandidate || excluded.has(dependencyCandidate.skill)) {
539
+ return;
540
+ }
541
+ if ([selectedMain, ...selected].some((candidate) => routesConflict(candidate, dependencyCandidate, metadata))) {
542
+ return;
543
+ }
544
+ if (selected.some((candidate) => candidate.skill === dependencyCandidate.skill)) {
545
+ return;
546
+ }
547
+ selected.push(addDependencySelectionReason(dependencyCandidate, reason));
548
+ }
549
+ for (const skill of selectedMain.route_card.route_dependencies.requires_skills) {
550
+ addDependencySkill(skill, `route_dependency:requires:${selectedMain.skill}`);
551
+ }
552
+ for (const skill of selectedMain.route_card.route_dependencies.suggests_adjuncts) {
553
+ addDependencySkill(skill, `route_dependency:suggested_by:${selectedMain.skill}`);
554
+ }
555
+ for (const unlockRule of selectedMain.route_card.route_dependencies.unlocks_on) {
556
+ if (hasDependencySignal(unlockRule.signal, dependencySignals, taskTerms, pathTerms)) {
557
+ addDependencySkill(unlockRule.skill, `route_dependency:unlocked_by:${selectedMain.skill}:${unlockRule.signal}`);
558
+ }
559
+ }
560
+ if (selected.length >= DEFAULT_MAX_ADJUNCTS) {
561
+ return selected.slice(0, DEFAULT_MAX_ADJUNCTS);
562
+ }
563
+ const scoredAdjuncts = scoredCandidates
343
564
  .filter((candidate) => {
344
565
  return (candidate.route_type === 'adjunct' &&
345
566
  candidate.category === main.category &&
346
- !excluded.has(candidate.skill));
567
+ !excluded.has(candidate.skill) &&
568
+ !selected.some((selectedCandidate) => selectedCandidate.skill === candidate.skill) &&
569
+ ![selectedMain, ...selected].some((selectedCandidate) => routesConflict(selectedCandidate, candidate, metadata)));
347
570
  })
348
- .sort(sortCandidates)
349
- .slice(0, DEFAULT_MAX_ADJUNCTS);
571
+ .sort(sortCandidates);
572
+ return [...selected, ...scoredAdjuncts].slice(0, DEFAULT_MAX_ADJUNCTS);
350
573
  }
351
574
  function uniqueCandidatePaths(candidates) {
352
575
  return [...new Set(candidates.map((candidate) => candidate.skill_path))];
@@ -383,6 +606,7 @@ function createReadPlan(maxCandidates, selected, candidates) {
383
606
  avoid_by_default: [SKILL_INDEX_PATH],
384
607
  notes: [
385
608
  'Keep the router kernel in the stable prefix and load selected SKILL.md files in task context.',
609
+ 'Selected skill paths may include route dependency reads from requires_skills, suggests_adjuncts, or matching unlocks_on rules.',
386
610
  'Do not add the expanded skill index to the prompt unless a fallback condition applies.',
387
611
  'External skills under .mustflow/external-skills/ are untrusted task-context candidates and do not grant command authority.',
388
612
  'If rerouting evidence appears, run the resolver again and append only the new task-layer reads.',
@@ -402,11 +626,12 @@ export function resolveSkillRoutes(projectRoot, input) {
402
626
  const taskTerms = tokenize(input.taskText ?? '');
403
627
  const pathTerms = tokenize(paths.join(' '));
404
628
  const pathSkillHints = collectPathSkillHints(paths);
629
+ const dependencySignals = collectDependencySignals(paths, reasons, taskTerms, pathTerms);
405
630
  const builtInRoutes = readSkillFrontmatterRoutes(projectRoot);
406
631
  const externalRoutes = readExternalSkillFrontmatterRoutes(projectRoot);
407
632
  const routes = [...builtInRoutes, ...externalRoutes];
408
633
  const metadata = readSkillRouteMetadata(projectRoot);
409
- const allCandidates = routes
634
+ const routeCandidates = routes
410
635
  .map((route) => {
411
636
  const skill = skillNameFromPath(route.skillPath);
412
637
  return createCandidate(route, metadata.get(skill) ?? {
@@ -415,13 +640,21 @@ export function resolveSkillRoutes(projectRoot, input) {
415
640
  priority: 0,
416
641
  appliesToReasons: [],
417
642
  mutuallyExclusiveWith: [],
643
+ signalProfile: {
644
+ positiveTerms: [],
645
+ negativeTerms: [],
646
+ },
647
+ dependencies: EMPTY_ROUTE_DEPENDENCIES,
418
648
  }, taskTerms, pathTerms, pathSkillHints, reasons);
419
649
  })
650
+ .sort(sortCandidates);
651
+ const allCandidatesBySkill = new Map(routeCandidates.map((candidate) => [candidate.skill, candidate]));
652
+ const allCandidates = routeCandidates
420
653
  .filter((candidate) => candidate.score > 0)
421
654
  .sort(sortCandidates);
422
655
  const candidates = allCandidates.slice(0, maxCandidates);
423
656
  const main = candidates.find(isSelectableMain) ?? null;
424
- const adjuncts = selectAdjuncts(main, candidates, metadata);
657
+ const adjuncts = selectAdjuncts(main, candidates, allCandidatesBySkill, metadata, dependencySignals, taskTerms, pathTerms);
425
658
  const selected = {
426
659
  main,
427
660
  adjuncts,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mustflow",
3
- "version": "2.114.6",
3
+ "version": "2.114.12",
4
4
  "description": "Agent workflow documents and CLI for mustflow repository roots.",
5
5
  "type": "module",
6
6
  "license": "MIT-0",
package/schemas/README.md CHANGED
@@ -189,9 +189,9 @@ Current schemas:
189
189
  candidates from direct imports, importers, same-basename siblings, parent configuration files, and
190
190
  package boundaries for source-oriented repository navigation
191
191
  - `skill-route-report.schema.json`: output of `mf skill route --json`, containing compact route
192
- candidates, selected main and adjunct skills, score breakdowns, route read plans, source route
193
- shards, and optional read-only script-pack helper suggestions without granting command authority
194
- or replacing selected `SKILL.md` reads
192
+ candidates, selected main and adjunct skills, score breakdowns, route cards with dependencies,
193
+ route read plans, source route shards, and optional read-only script-pack helper suggestions without
194
+ granting command authority or replacing selected `SKILL.md` reads
195
195
  - `skill-import-report.schema.json`: output of `mf skill import <github-url> --json`, containing
196
196
  GitHub source provenance, target `.mustflow/external-skills/<name>/` paths, imported file hashes,
197
197
  warnings for inert external scripts, rejection issues, and whether files were written
@@ -350,6 +350,8 @@
350
350
  "reason_match": { "type": "number" },
351
351
  "task_text_match": { "type": "number" },
352
352
  "path_match": { "type": "number" },
353
+ "pattern_signal_match": { "type": "number" },
354
+ "negative_signal_penalty": { "type": "number" },
353
355
  "route_type_weight": { "type": "number" },
354
356
  "priority_weight": { "type": "number" }
355
357
  }
@@ -358,12 +360,120 @@
358
360
  "type": "array",
359
361
  "items": { "type": "string" }
360
362
  },
363
+ "matched_dimensions": {
364
+ "type": "array",
365
+ "items": {
366
+ "type": "string",
367
+ "enum": [
368
+ "reason",
369
+ "task_terms",
370
+ "path_terms",
371
+ "path_skill_hint",
372
+ "pattern_signal",
373
+ "negative_signal",
374
+ "route_type",
375
+ "priority"
376
+ ]
377
+ }
378
+ },
379
+ "route_card": {
380
+ "type": "object",
381
+ "additionalProperties": false,
382
+ "required": [
383
+ "source",
384
+ "index_read_policy",
385
+ "compact_fields",
386
+ "matched_dimensions",
387
+ "route_dependencies",
388
+ "use_when_excerpt",
389
+ "do_not_use_excerpt",
390
+ "read_strategy"
391
+ ],
392
+ "properties": {
393
+ "source": { "const": "route_metadata_and_skill_frontmatter" },
394
+ "index_read_policy": { "const": "fallback_only" },
395
+ "compact_fields": {
396
+ "type": "array",
397
+ "items": { "type": "string" }
398
+ },
399
+ "matched_dimensions": {
400
+ "type": "array",
401
+ "items": {
402
+ "type": "string",
403
+ "enum": [
404
+ "reason",
405
+ "task_terms",
406
+ "path_terms",
407
+ "path_skill_hint",
408
+ "pattern_signal",
409
+ "negative_signal",
410
+ "route_type",
411
+ "priority"
412
+ ]
413
+ }
414
+ },
415
+ "route_dependencies": { "$ref": "#/$defs/routeDependencies" },
416
+ "use_when_excerpt": { "$ref": "#/$defs/routeExcerptReference" },
417
+ "do_not_use_excerpt": { "$ref": "#/$defs/routeExcerptReference" },
418
+ "read_strategy": {
419
+ "type": "array",
420
+ "items": { "type": "string" },
421
+ "minItems": 1
422
+ }
423
+ }
424
+ },
361
425
  "verification_intents": {
362
426
  "type": "array",
363
427
  "items": { "type": "string" }
364
428
  }
365
429
  }
366
430
  },
431
+ "routeDependencies": {
432
+ "type": "object",
433
+ "additionalProperties": false,
434
+ "required": ["requires_skills", "suggests_adjuncts", "conflicts_with", "unlocks_on"],
435
+ "properties": {
436
+ "requires_skills": {
437
+ "type": "array",
438
+ "items": { "type": "string" }
439
+ },
440
+ "suggests_adjuncts": {
441
+ "type": "array",
442
+ "items": { "type": "string" }
443
+ },
444
+ "conflicts_with": {
445
+ "type": "array",
446
+ "items": { "type": "string" }
447
+ },
448
+ "unlocks_on": {
449
+ "type": "array",
450
+ "items": { "$ref": "#/$defs/routeUnlockRule" }
451
+ }
452
+ }
453
+ },
454
+ "routeUnlockRule": {
455
+ "type": "object",
456
+ "additionalProperties": false,
457
+ "required": ["signal", "skill"],
458
+ "properties": {
459
+ "signal": { "type": "string" },
460
+ "skill": { "type": "string" }
461
+ }
462
+ },
463
+ "routeExcerptReference": {
464
+ "type": "object",
465
+ "additionalProperties": false,
466
+ "required": ["source_path", "section", "read_when"],
467
+ "properties": {
468
+ "source_path": { "type": "string" },
469
+ "section": { "type": "string", "enum": ["use-when", "do-not-use-when"] },
470
+ "read_when": {
471
+ "type": "array",
472
+ "items": { "type": "string" },
473
+ "minItems": 1
474
+ }
475
+ }
476
+ },
367
477
  "readPlanFile": {
368
478
  "type": "object",
369
479
  "additionalProperties": false,
@@ -10,12 +10,12 @@ status_values = ["current", "stale", "needs_review", "missing"]
10
10
  [documents."agents.root"]
11
11
  source = "locales/en/AGENTS.md"
12
12
  source_locale = "en"
13
- revision = 21
14
- translations.ko = { path = "locales/ko/AGENTS.md", source_revision = 21, status = "current" }
15
- translations.zh = { path = "locales/zh/AGENTS.md", source_revision = 21, status = "needs_review" }
16
- translations.es = { path = "locales/es/AGENTS.md", source_revision = 21, status = "needs_review" }
17
- translations.fr = { path = "locales/fr/AGENTS.md", source_revision = 21, status = "needs_review" }
18
- translations.hi = { path = "locales/hi/AGENTS.md", source_revision = 21, status = "needs_review" }
13
+ revision = 22
14
+ translations.ko = { path = "locales/ko/AGENTS.md", source_revision = 22, status = "current" }
15
+ translations.zh = { path = "locales/zh/AGENTS.md", source_revision = 22, status = "needs_review" }
16
+ translations.es = { path = "locales/es/AGENTS.md", source_revision = 22, status = "needs_review" }
17
+ translations.fr = { path = "locales/fr/AGENTS.md", source_revision = 22, status = "needs_review" }
18
+ translations.hi = { path = "locales/hi/AGENTS.md", source_revision = 22, status = "needs_review" }
19
19
 
20
20
  [documents."context.index"]
21
21
  source = "locales/en/.mustflow/context/INDEX.md"
@@ -48,18 +48,42 @@ route_type = "primary"
48
48
  priority = 80
49
49
  applies_to_reasons = ["code_change"]
50
50
 
51
+ [routes."composition-over-inheritance".contexts]
52
+ file_types = ["ts", "tsx", "js", "jsx", "py", "go", "rs"]
53
+ layers = ["domain", "service", "model"]
54
+ pattern_categories = ["delegation", "class_hierarchy"]
55
+ positive_terms = ["class", "composition", "delegate", "delegation", "extend", "inheritance", "mixin", "subclass"]
56
+ negative_terms = ["command", "facade", "state", "strategy"]
57
+
51
58
  [routes."strategy-pattern"]
52
59
  category = "architecture_patterns"
53
60
  route_type = "primary"
54
61
  priority = 80
55
62
  applies_to_reasons = ["code_change"]
56
63
 
64
+ [routes."strategy-pattern".dependencies]
65
+ conflicts_with = ["state-machine-pattern"]
66
+
67
+ [routes."strategy-pattern".contexts]
68
+ file_types = ["ts", "tsx", "js", "jsx", "py", "go", "rs"]
69
+ layers = ["domain", "service", "policy", "pricing", "permission", "provider_selection"]
70
+ pattern_categories = ["variant_selection", "algorithm_policy", "branch_extraction"]
71
+ positive_terms = ["algorithm", "branch", "discount", "plan", "policy", "pricing", "provider", "region", "selection", "strategy", "switch", "variant"]
72
+ negative_terms = ["adapter", "audit", "history", "idempotency", "lifecycle", "phase", "protocol", "retry", "state", "status", "transaction", "transition"]
73
+
57
74
  [routes."command-pattern"]
58
75
  category = "architecture_patterns"
59
76
  route_type = "primary"
60
77
  priority = 80
61
78
  applies_to_reasons = ["code_change", "behavior_change"]
62
79
 
80
+ [routes."command-pattern".contexts]
81
+ file_types = ["ts", "tsx", "js", "jsx", "py", "go", "rs"]
82
+ layers = ["application", "service", "workflow", "queue", "mutation"]
83
+ pattern_categories = ["durable_mutation", "undo_redo", "transaction_command"]
84
+ positive_terms = ["audit", "command", "durable", "execute", "idempotency", "mutation", "queue", "redo", "retry", "transaction", "undo"]
85
+ negative_terms = ["facade", "pricing", "provider", "state", "strategy", "transition", "variant"]
86
+
63
87
  [routes."command-contract-authoring"]
64
88
  category = "workflow_contracts"
65
89
  route_type = "authoring"
@@ -90,12 +114,25 @@ route_type = "primary"
90
114
  priority = 82
91
115
  applies_to_reasons = ["public_api_change", "behavior_change", "docs_change", "test_change", "package_metadata_change", "release_risk"]
92
116
 
117
+ [routes."public-json-contract-change".dependencies]
118
+ suggests_adjuncts = ["cli-output-contract-review", "completion-evidence-gate"]
119
+ unlocks_on = [
120
+ { signal = "machine_output_changed", skill = "cli-output-contract-review" },
121
+ { signal = "schema_or_fixture_changed", skill = "completion-evidence-gate" },
122
+ ]
123
+
93
124
  [routes."completion-evidence-gate"]
94
125
  category = "workflow_contracts"
95
126
  route_type = "adjunct"
96
127
  priority = 85
97
128
  applies_to_reasons = ["unknown_change", "code_change", "behavior_change", "public_api_change", "test_change", "docs_change", "mustflow_docs_change", "mustflow_config_change", "package_metadata_change", "release_risk"]
98
129
 
130
+ [routes."completion-evidence-gate".dependencies]
131
+ suggests_adjuncts = ["next-action-menu"]
132
+ unlocks_on = [
133
+ { signal = "concrete_followup_exists", skill = "next-action-menu" },
134
+ ]
135
+
99
136
  [routes."next-action-menu"]
100
137
  category = "workflow_contracts"
101
138
  route_type = "adjunct"
@@ -126,6 +163,13 @@ route_type = "primary"
126
163
  priority = 80
127
164
  applies_to_reasons = ["code_change"]
128
165
 
166
+ [routes."facade-pattern".contexts]
167
+ file_types = ["ts", "tsx", "js", "jsx", "py", "go", "rs"]
168
+ layers = ["application", "service", "orchestration", "integration"]
169
+ pattern_categories = ["subsystem_simplification", "orchestration_boundary"]
170
+ positive_terms = ["coordinate", "coordination", "facade", "orchestrate", "orchestration", "simplify", "subsystem", "wrapper"]
171
+ negative_terms = ["algorithm", "state", "strategy", "transition", "variant"]
172
+
129
173
  [routes."pure-core-imperative-shell"]
130
174
  category = "architecture_patterns"
131
175
  route_type = "primary"
@@ -138,6 +182,16 @@ route_type = "primary"
138
182
  priority = 80
139
183
  applies_to_reasons = ["code_change", "behavior_change"]
140
184
 
185
+ [routes."state-machine-pattern".dependencies]
186
+ conflicts_with = ["strategy-pattern"]
187
+
188
+ [routes."state-machine-pattern".contexts]
189
+ file_types = ["ts", "tsx", "js", "jsx", "py", "go", "rs"]
190
+ layers = ["domain", "workflow", "lifecycle", "state"]
191
+ pattern_categories = ["state_transition", "lifecycle_model", "allowed_action"]
192
+ positive_terms = ["allowed", "history", "irreversible", "lifecycle", "phase", "state", "status", "transition", "workflow"]
193
+ negative_terms = ["algorithm", "pricing", "provider", "strategy", "variant", "wrapper"]
194
+
141
195
  [routes."result-option"]
142
196
  category = "architecture_patterns"
143
197
  route_type = "primary"
@@ -2,7 +2,7 @@
2
2
  mustflow_doc: agents.root
3
3
  locale: en
4
4
  canonical: true
5
- revision: 21
5
+ revision: 22
6
6
  lifecycle: user-editable
7
7
  authority: binding
8
8
  ---
@@ -123,6 +123,15 @@ mustflow-managed details are under `.mustflow/`.
123
123
  - If the nested repository has no local preferences file, apply the nearest parent mustflow
124
124
  preferences as inherited defaults while still following the nested repository's `AGENTS.md` and
125
125
  command contract.
126
+ - When this mustflow root is used as a workspace for nested repositories, treat this root's
127
+ `.mustflow/skills/` router, routes, index, and installed `SKILL.md` files as a shared
128
+ workspace skill registry. Check the shared registry for task procedures in addition to the
129
+ nested repository's local routing files.
130
+ - Shared workspace skills provide procedure guidance only. They do not override the nested
131
+ repository's `AGENTS.md`, command contract, edit scope, safety rules, or source of truth.
132
+ - If a nested repository lacks a local skill index or uses a different local agent convention,
133
+ do not treat that absence as meaning no applicable workspace-level skill exists; consult the
134
+ shared workspace registry while keeping child-repository authority local.
126
135
  - In repository farms, prefer each child repository's own command contract for repository-owned
127
136
  commands. If the parent root intentionally orchestrates children, split parent-owned commands
128
137
  into repo-named fragments under `.mustflow/config/commands/`; use
@@ -2,7 +2,7 @@
2
2
  mustflow_doc: agents.root
3
3
  locale: es
4
4
  canonical: false
5
- revision: 10
5
+ revision: 11
6
6
  lifecycle: user-editable
7
7
  authority: binding
8
8
  ---
@@ -59,6 +59,9 @@ Los detalles gestionados por mustflow se encuentran en `.mustflow/`.
59
59
  - Las reglas de seguridad sobre secretos, privacidad, comandos destructivos y rutas permitidas de edición son acumulativas. Aplica la regla más estricta.
60
60
  - Al entrar en un repositorio anidado, vuelve a leer su `AGENTS.md` y `.mustflow/config/*.toml` antes de editar.
61
61
  - Si el repositorio anidado no tiene un archivo local de preferences, aplica las preferences del mustflow padre más cercano como valores heredados mientras sigues obedeciendo el `AGENTS.md` y el command contract del repositorio anidado.
62
+ - Cuando este mustflow root se use como workspace para repositorios anidados, trata el router, las routes, el index y los archivos `SKILL.md` instalados bajo `.mustflow/skills/` de este root como un shared workspace skill registry. Revisa ese registro compartido para elegir procedimientos de tarea junto con los archivos locales de routing del repositorio anidado.
63
+ - Las shared workspace skills solo orientan el procedimiento. No reemplazan el `AGENTS.md`, el command contract, el alcance de edición, las reglas de seguridad ni la fuente de verdad del repositorio anidado.
64
+ - Si un repositorio anidado no tiene un skill index local o usa una convención local de agentes diferente, no concluyas que no existe ninguna skill de workspace aplicable; consulta el shared workspace registry manteniendo local la autoridad del repositorio hijo.
62
65
  - No edites fuera del repositorio hijo seleccionado salvo solicitud explícita.
63
66
 
64
67
  ## Compatibilidad con Instrucciones del Host
@@ -2,7 +2,7 @@
2
2
  mustflow_doc: agents.root
3
3
  locale: fr
4
4
  canonical: false
5
- revision: 10
5
+ revision: 11
6
6
  lifecycle: user-editable
7
7
  authority: binding
8
8
  ---
@@ -60,6 +60,9 @@ Les détails gérés par mustflow se trouvent dans `.mustflow/`.
60
60
  - Les règles de sécurité sur les secrets, la vie privée, les commandes destructrices et les chemins d’édition autorisés sont cumulatives. Appliquer la règle la plus stricte.
61
61
  - Lors de la navigation vers un dépôt imbriqué, relire le `AGENTS.md` de ce dépôt et `.mustflow/config/*.toml` avant d’éditer.
62
62
  - Si le dépôt imbriqué n’a pas de fichier preferences local, appliquer les preferences du mustflow parent le plus proche comme valeurs héritées tout en continuant de suivre le `AGENTS.md` et le command contract du dépôt imbriqué.
63
+ - Lorsque ce mustflow root sert de workspace pour des dépôts imbriqués, traiter le router, les routes, l’index et les fichiers `SKILL.md` installés sous `.mustflow/skills/` dans ce root comme un shared workspace skill registry. Consulter ce registre partagé pour les procédures de tâche en plus des fichiers de routing locaux du dépôt imbriqué.
64
+ - Les shared workspace skills ne fournissent qu’un guidage de procédure. Elles ne remplacent pas le `AGENTS.md`, le command contract, le périmètre d’édition, les règles de sécurité ni la source de vérité du dépôt imbriqué.
65
+ - Si un dépôt imbriqué n’a pas d’index de skills local ou utilise une convention d’agent locale différente, ne pas conclure qu’aucune skill de workspace ne s’applique ; consulter le shared workspace registry tout en gardant l’autorité du dépôt enfant locale.
63
66
  - Ne pas éditer en dehors du dépôt enfant sélectionné, sauf demande explicite.
64
67
 
65
68
  ## Compatibilité avec les instructions du host
@@ -2,7 +2,7 @@
2
2
  mustflow_doc: agents.root
3
3
  locale: hi
4
4
  canonical: false
5
- revision: 10
5
+ revision: 11
6
6
  lifecycle: user-editable
7
7
  authority: binding
8
8
  ---
@@ -59,6 +59,9 @@ mustflow द्वारा प्रबंधित विवरण `.mustflow/
59
59
  - secrets, गोपनीयता, destructive commands, और अनुमत संपादन मार्गों के सुरक्षा नियम संचयी होते हैं। अधिक सख्त नियम अपनाएं।
60
60
  - nested रिपॉजिटरी में जाने पर संपादन से पहले उसका `AGENTS.md` और `.mustflow/config/*.toml` फिर से पढ़ें।
61
61
  - यदि nested repository में local preferences file नहीं है, तो nested repository के `AGENTS.md` और command contract का पालन करते हुए निकटतम parent mustflow preferences को inherited defaults के रूप में लागू करें।
62
+ - जब यह mustflow root nested repositories के लिए workspace के रूप में उपयोग हो, तो इस root के `.mustflow/skills/` router, routes, index, और installed `SKILL.md` files को shared workspace skill registry मानें। nested repository की local routing files के साथ इस shared registry को भी task procedures चुनने के लिए देखें।
63
+ - Shared workspace skills केवल procedure guidance देती हैं। वे nested repository के `AGENTS.md`, command contract, edit scope, safety rules, या source of truth को override नहीं करतीं।
64
+ - यदि किसी nested repository में local skill index नहीं है या वह अलग local agent convention उपयोग करती है, तो यह न मानें कि कोई workspace-level skill लागू नहीं है; child-repository authority को local रखते हुए shared workspace registry देखें।
62
65
  - स्पष्ट अनुरोध के बिना चुनी हुई child रिपॉजिटरी के बाहर संपादन न करें।
63
66
 
64
67
  ## होस्ट-विशिष्ट निर्देश अनुकूलता
@@ -2,7 +2,7 @@
2
2
  mustflow_doc: agents.root
3
3
  locale: ko
4
4
  canonical: false
5
- revision: 28
5
+ revision: 29
6
6
  lifecycle: user-editable
7
7
  authority: binding
8
8
  ---
@@ -74,6 +74,9 @@ mustflow가 관리하는 세부 문서와 설정은 `.mustflow/` 폴더 아래
74
74
  - 보안, 비밀 정보, 개인정보, 파괴적 명령, 수정 가능 경로 등 안전 규칙은 누적 적용하며 더 엄격한 규칙을 따릅니다.
75
75
  - 상위 작업 공간에서 하위 저장소로 진입한 경우, 하위 저장소의 `AGENTS.md`와 `.mustflow/config/*.toml`을 다시 읽은 뒤 작업합니다.
76
76
  - 하위 저장소에 로컬 preferences 파일이 없다면, 하위 저장소의 `AGENTS.md`와 command contract를 계속 따르면서 가장 가까운 상위 mustflow preferences를 상속 기본값으로 적용합니다.
77
+ - 이 mustflow 루트를 하위 저장소 작업 공간으로 사용하는 경우, 이 루트의 `.mustflow/skills/` router, routes, index, 설치된 `SKILL.md` 파일을 공유 workspace skill registry로 취급합니다. 하위 저장소의 로컬 라우팅 파일과 함께 이 공유 registry도 확인해 현재 작업에 맞는 절차를 선택합니다.
78
+ - 공유 workspace skill은 절차 안내일 뿐입니다. 하위 저장소의 `AGENTS.md`, command contract, 수정 범위, 안전 규칙, source of truth를 덮어쓰지 않습니다.
79
+ - 하위 저장소에 로컬 skill index가 없거나 다른 로컬 agent convention을 사용하더라도, 적용 가능한 workspace-level skill이 없다고 단정하지 않습니다. 하위 저장소의 권한은 로컬에 둔 채 공유 workspace registry를 함께 확인합니다.
77
80
  - repository farm에서는 각 하위 저장소가 소유한 명령은 해당 저장소의 command contract를 우선합니다. 상위 루트가 하위 저장소를 의도적으로 orchestration해야 한다면 상위 소유 명령을 `.mustflow/config/commands/` 아래 repo 이름별 fragment로 나누고, `mf workspace command-fragments`로 읽기 전용 안내를 확인합니다.
78
81
  - 명시적 요청 없이 선택한 하위 저장소 밖의 파일은 수정하지 않습니다.
79
82
 
@@ -2,7 +2,7 @@
2
2
  mustflow_doc: agents.root
3
3
  locale: zh
4
4
  canonical: false
5
- revision: 10
5
+ revision: 11
6
6
  lifecycle: user-editable
7
7
  authority: binding
8
8
  ---
@@ -60,6 +60,9 @@ authority: binding
60
60
  - 针对 secrets、隐私、破坏性命令和允许编辑路径的安全规则为累加,执行更严格的规则。
61
61
  - 进入嵌套仓库时,编辑前应重新阅读该仓库的 `AGENTS.md` 和 `.mustflow/config/*.toml`。
62
62
  - 如果嵌套仓库没有本地 preferences 文件,在继续遵循该仓库 `AGENTS.md` 和 command contract 的同时,使用最近父级 mustflow preferences 作为继承默认值。
63
+ - 当此 mustflow root 被用作嵌套仓库的 workspace 时,应将此 root 下 `.mustflow/skills/` 的 router、routes、index 以及已安装的 `SKILL.md` 文件视为 shared workspace skill registry。除嵌套仓库的本地 routing 文件外,也要检查该共享 registry 来选择任务流程。
64
+ - Shared workspace skills 只提供流程指导,不会覆盖嵌套仓库的 `AGENTS.md`、command contract、编辑范围、安全规则或事实来源。
65
+ - 如果嵌套仓库没有本地 skill index,或使用不同的本地 agent convention,不要因此认定没有适用的 workspace-level skill;在保持子仓库权限本地化的前提下检查 shared workspace registry。
63
66
  - 未经明确请求,不得编辑所选子仓库之外的内容。
64
67
 
65
68
  ## 宿主专用指令兼容性
@@ -1,6 +1,6 @@
1
1
  id = "default"
2
2
  name = "default"
3
- version = "2.114.6"
3
+ version = "2.114.12"
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"