mustflow 2.113.0 → 2.114.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.
package/README.md CHANGED
@@ -290,6 +290,8 @@ mf run mustflow_update_apply
290
290
  | `mf handoff validate <path>` | Validate a restricted work-item or handoff JSON record without writing files. |
291
291
  | `mf context --json` | Print read order, command rules, context trust metadata, available capabilities, prompt-cache bundles, and recent run summary as JSON. |
292
292
  | `mf skill route` | Resolve compact skill route candidates from task text, paths, and reasons before reading selected skill documents. |
293
+ | `mf skill outdated` | Check installed external skills against their saved upstream provenance, report file hash drift, and refresh the local update-check timestamp without writing skill files. |
294
+ | `mf skill update <skill-name>\|--all` | Refresh installed external skills from their saved provenance source, refusing to overwrite local drift. Use `--dry-run` to preview and refresh the update-check timestamp. |
293
295
  | `mf map --stdout` | Print the current mustflow root map to stdout. |
294
296
  | `mf map --write` | Create or update `REPO_MAP.md`. |
295
297
  | `mf flow --stdout` | Print the current mustflow root design-flow map to stdout. |
@@ -5,7 +5,7 @@ import { resolveMustflowRoot } from '../lib/project-root.js';
5
5
  import { listScriptPackScripts } from '../lib/script-pack-registry.js';
6
6
  import { createScriptPackSuggestionReport, } from '../../core/script-pack-suggestions.js';
7
7
  import { resolveSkillRoutes } from '../../core/skill-route-resolution.js';
8
- import { createExternalSkillImportReport, } from '../lib/external-skill-import.js';
8
+ import { createExternalSkillImportReport, createExternalSkillUpdateReminder, createExternalSkillUpdateReport, } from '../lib/external-skill-import.js';
9
9
  const SKILL_OPTIONS = [
10
10
  { name: '--json', kind: 'boolean' },
11
11
  { name: '--task', kind: 'string' },
@@ -14,6 +14,7 @@ const SKILL_OPTIONS = [
14
14
  { name: '--max-candidates', kind: 'string' },
15
15
  { name: '--install', kind: 'boolean' },
16
16
  { name: '--dry-run', kind: 'boolean' },
17
+ { name: '--all', kind: 'boolean' },
17
18
  { name: '--name', kind: 'string' },
18
19
  { name: '--ref', kind: 'string' },
19
20
  { name: '--trust-scripts', kind: 'boolean' },
@@ -25,12 +26,15 @@ export function getSkillHelp(lang = 'en') {
25
26
  options: [
26
27
  { label: 'route', description: 'Resolve installed skill route candidates' },
27
28
  { label: 'import <github-url>', description: 'Preview or install an external SKILL.md under .mustflow/external-skills/' },
29
+ { label: 'outdated [skill-name...]', description: 'Check installed external skills for upstream file changes' },
30
+ { label: 'update <skill-name>|--all', description: 'Refresh installed external skills from their saved provenance source' },
28
31
  { label: '--task <text>', description: 'Task text used for route scoring' },
29
32
  { label: '--path <path>', description: 'Changed or expected path; may be repeated' },
30
33
  { label: '--reason <reason>', description: 'Classification or verification reason; may be repeated' },
31
34
  { label: '--max-candidates <count>', description: 'Maximum candidates to return, from 1 to 10' },
32
35
  { label: '--dry-run', description: 'Preview an external skill import without writing files; default for import' },
33
36
  { label: '--install', description: 'Install an external skill after previewing the same source' },
37
+ { label: '--all', description: 'Select every installed external skill for outdated or update checks' },
34
38
  { label: '--name <slug>', description: 'Override the installed external skill directory name' },
35
39
  { label: '--ref <ref>', description: 'Override the GitHub ref used for import' },
36
40
  { label: '--trust-scripts', description: 'Create command-contract intents for imported scripts; requires --install to write them' },
@@ -42,6 +46,9 @@ export function getSkillHelp(lang = 'en') {
42
46
  'mf skill route --reason docs_change --path docs-site/src/content/docs/en/commands/context.md',
43
47
  'mf skill import https://github.com/example/agent-skills/tree/main/review/security --dry-run --json',
44
48
  'mf skill import https://github.com/example/agent-skills/blob/main/review/security/SKILL.md --install',
49
+ 'mf skill outdated --json',
50
+ 'mf skill update concurrency-review --dry-run --json',
51
+ 'mf skill update --all --trust-scripts',
45
52
  ],
46
53
  exitCodes: [
47
54
  { label: '0', description: 'Skill route candidates were resolved' },
@@ -67,12 +74,14 @@ function parseSkillArgs(args) {
67
74
  json: hasParsedCliOption(parsed, '--json'),
68
75
  action: parsed.positionals[0] ?? null,
69
76
  sourceUrl: parsed.positionals[1] ?? null,
77
+ skillNames: parsed.positionals.slice(1),
70
78
  taskText: getParsedCliStringOption(parsed, '--task'),
71
79
  paths: getParsedCliStringOptions(parsed, '--path'),
72
80
  reasons: getParsedCliStringOptions(parsed, '--reason'),
73
81
  maxCandidates: parseMaxCandidates(getParsedCliStringOption(parsed, '--max-candidates')),
74
82
  install: hasParsedCliOption(parsed, '--install'),
75
83
  dryRun: hasParsedCliOption(parsed, '--dry-run'),
84
+ all: hasParsedCliOption(parsed, '--all'),
76
85
  name: getParsedCliStringOption(parsed, '--name'),
77
86
  ref: getParsedCliStringOption(parsed, '--ref'),
78
87
  trustScripts: hasParsedCliOption(parsed, '--trust-scripts'),
@@ -102,7 +111,7 @@ function createSkillRouteScriptPackSuggestions(mustflowRoot, report, paths) {
102
111
  issues: suggestionReport.issues,
103
112
  };
104
113
  }
105
- function renderSkillRouteReport(report, lang) {
114
+ function renderSkillRouteReport(report, lang, warnings = []) {
106
115
  const lines = [
107
116
  'mustflow skill route',
108
117
  `${t(lang, 'label.mustflowRoot')}: ${resolveMustflowRoot()}`,
@@ -120,6 +129,42 @@ function renderSkillRouteReport(report, lang) {
120
129
  }
121
130
  }
122
131
  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}`));
132
+ if (warnings.length > 0) {
133
+ lines.push('', 'Warnings', ...warnings.map((warning) => `- ${warning}`));
134
+ }
135
+ return lines.join('\n');
136
+ }
137
+ function renderSkillUpdateReport(report) {
138
+ const lines = [
139
+ `mustflow skill ${report.action}`,
140
+ `status: ${report.status}`,
141
+ `mode: ${report.mode}`,
142
+ `wrote_files: ${String(report.wrote_files)}`,
143
+ '',
144
+ 'Skills',
145
+ ];
146
+ if (report.skills.length === 0) {
147
+ lines.push('- none');
148
+ }
149
+ else {
150
+ for (const skill of report.skills) {
151
+ lines.push(`- ${skill.skill_name}: ${skill.status}`, ` source: ${skill.source?.source_url ?? 'none'}`, ` target: ${skill.target.skill_dir}`, ` changed_files: ${String(skill.changed_files.length)}`);
152
+ for (const changedFile of skill.changed_files) {
153
+ lines.push(` - ${changedFile.relative_path}: ${changedFile.status}`, ` current: ${changedFile.current_sha256 ?? 'none'}`, ` remote: ${changedFile.remote_sha256 ?? 'none'}`);
154
+ }
155
+ if (skill.issues.length > 0) {
156
+ for (const issue of skill.issues) {
157
+ lines.push(` issue: ${issue}`);
158
+ }
159
+ }
160
+ }
161
+ }
162
+ if (report.warnings.length > 0) {
163
+ lines.push('', 'Warnings', ...report.warnings.map((warning) => `- ${warning}`));
164
+ }
165
+ if (report.issues.length > 0) {
166
+ lines.push('', 'Issues', ...report.issues.map((issue) => `- ${issue}`));
167
+ }
123
168
  return lines.join('\n');
124
169
  }
125
170
  function renderSkillImportReport(report) {
@@ -174,7 +219,10 @@ export async function runSkill(args, reporter, lang = 'en') {
174
219
  printUsageError(reporter, formatCliOptionParseError(parsed.error, lang), 'mf skill --help', getSkillHelp(lang), lang);
175
220
  return 1;
176
221
  }
177
- if (parsed.action !== 'route' && parsed.action !== 'import') {
222
+ if (parsed.action !== 'route' &&
223
+ parsed.action !== 'import' &&
224
+ parsed.action !== 'outdated' &&
225
+ parsed.action !== 'update') {
178
226
  printUsageError(reporter, t(lang, parsed.action ? 'cli.error.unexpectedArgument' : 'cli.error.missingValue', {
179
227
  argument: parsed.action ?? '',
180
228
  option: 'route',
@@ -205,6 +253,30 @@ export async function runSkill(args, reporter, lang = 'en') {
205
253
  }
206
254
  return report.ok ? 0 : 1;
207
255
  }
256
+ if (parsed.action === 'outdated' || parsed.action === 'update') {
257
+ if (parsed.action === 'outdated' && (parsed.install || parsed.dryRun)) {
258
+ printUsageError(reporter, t(lang, 'cli.error.unexpectedValue', { option: '--install/--dry-run' }), 'mf skill --help', getSkillHelp(lang), lang);
259
+ return 1;
260
+ }
261
+ if (parsed.action === 'update' && parsed.install) {
262
+ printUsageError(reporter, t(lang, 'cli.error.unexpectedValue', { option: '--install' }), 'mf skill --help', getSkillHelp(lang), lang);
263
+ return 1;
264
+ }
265
+ const report = await createExternalSkillUpdateReport(resolveMustflowRoot(), {
266
+ action: parsed.action,
267
+ mode: parsed.action === 'outdated' ? 'check' : parsed.dryRun ? 'dry_run' : 'install',
268
+ skillNames: parsed.skillNames,
269
+ all: parsed.all,
270
+ trustScripts: parsed.trustScripts,
271
+ });
272
+ if (parsed.json) {
273
+ reporter.stdout(JSON.stringify(report, null, 2));
274
+ }
275
+ else {
276
+ reporter.stdout(renderSkillUpdateReport(report));
277
+ }
278
+ return report.ok ? 0 : 1;
279
+ }
208
280
  if (Number.isNaN(parsed.maxCandidates)) {
209
281
  printUsageError(reporter, t(lang, 'cli.error.unexpectedValue', { option: '--max-candidates' }), 'mf skill --help', getSkillHelp(lang), lang);
210
282
  return 1;
@@ -225,6 +297,7 @@ export async function runSkill(args, reporter, lang = 'en') {
225
297
  }, null, 2));
226
298
  return 0;
227
299
  }
228
- reporter.stdout(renderSkillRouteReport(report, lang));
300
+ const updateReminder = createExternalSkillUpdateReminder(mustflowRoot);
301
+ reporter.stdout(renderSkillRouteReport(report, lang, updateReminder ? [updateReminder] : []));
229
302
  return 0;
230
303
  }
@@ -242,7 +242,7 @@ export const COMMAND_DEFINITIONS = [
242
242
  id: 'skill',
243
243
  usage: 'mf skill',
244
244
  summaryKey: 'command.skill.summary',
245
- contract: commandContract(TEXT_JSON_OUTPUT, ['skill-route-report', 'skill-import-report']),
245
+ contract: commandContract(TEXT_JSON_OUTPUT, ['skill-route-report', 'skill-import-report', 'skill-update-report']),
246
246
  loadRunner: async () => (await import('../commands/skill.js')).runSkill,
247
247
  },
248
248
  {