mustflow 2.112.14 → 2.114.0

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,8 +14,10 @@ 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' },
20
+ { name: '--trust-scripts', kind: 'boolean' },
19
21
  ];
20
22
  export function getSkillHelp(lang = 'en') {
21
23
  return renderHelp({
@@ -24,14 +26,18 @@ export function getSkillHelp(lang = 'en') {
24
26
  options: [
25
27
  { label: 'route', description: 'Resolve installed skill route candidates' },
26
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' },
27
31
  { label: '--task <text>', description: 'Task text used for route scoring' },
28
32
  { label: '--path <path>', description: 'Changed or expected path; may be repeated' },
29
33
  { label: '--reason <reason>', description: 'Classification or verification reason; may be repeated' },
30
34
  { label: '--max-candidates <count>', description: 'Maximum candidates to return, from 1 to 10' },
31
35
  { label: '--dry-run', description: 'Preview an external skill import without writing files; default for import' },
32
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' },
33
38
  { label: '--name <slug>', description: 'Override the installed external skill directory name' },
34
39
  { label: '--ref <ref>', description: 'Override the GitHub ref used for import' },
40
+ { label: '--trust-scripts', description: 'Create command-contract intents for imported scripts; requires --install to write them' },
35
41
  { label: '--json', description: t(lang, 'cli.option.json') },
36
42
  { label: '-h, --help', description: t(lang, 'cli.option.help') },
37
43
  ],
@@ -40,6 +46,9 @@ export function getSkillHelp(lang = 'en') {
40
46
  'mf skill route --reason docs_change --path docs-site/src/content/docs/en/commands/context.md',
41
47
  'mf skill import https://github.com/example/agent-skills/tree/main/review/security --dry-run --json',
42
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',
43
52
  ],
44
53
  exitCodes: [
45
54
  { label: '0', description: 'Skill route candidates were resolved' },
@@ -65,14 +74,17 @@ function parseSkillArgs(args) {
65
74
  json: hasParsedCliOption(parsed, '--json'),
66
75
  action: parsed.positionals[0] ?? null,
67
76
  sourceUrl: parsed.positionals[1] ?? null,
77
+ skillNames: parsed.positionals.slice(1),
68
78
  taskText: getParsedCliStringOption(parsed, '--task'),
69
79
  paths: getParsedCliStringOptions(parsed, '--path'),
70
80
  reasons: getParsedCliStringOptions(parsed, '--reason'),
71
81
  maxCandidates: parseMaxCandidates(getParsedCliStringOption(parsed, '--max-candidates')),
72
82
  install: hasParsedCliOption(parsed, '--install'),
73
83
  dryRun: hasParsedCliOption(parsed, '--dry-run'),
84
+ all: hasParsedCliOption(parsed, '--all'),
74
85
  name: getParsedCliStringOption(parsed, '--name'),
75
86
  ref: getParsedCliStringOption(parsed, '--ref'),
87
+ trustScripts: hasParsedCliOption(parsed, '--trust-scripts'),
76
88
  error: parsed.error,
77
89
  };
78
90
  }
@@ -99,7 +111,7 @@ function createSkillRouteScriptPackSuggestions(mustflowRoot, report, paths) {
99
111
  issues: suggestionReport.issues,
100
112
  };
101
113
  }
102
- function renderSkillRouteReport(report, lang) {
114
+ function renderSkillRouteReport(report, lang, warnings = []) {
103
115
  const lines = [
104
116
  'mustflow skill route',
105
117
  `${t(lang, 'label.mustflowRoot')}: ${resolveMustflowRoot()}`,
@@ -117,6 +129,42 @@ function renderSkillRouteReport(report, lang) {
117
129
  }
118
130
  }
119
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
+ }
120
168
  return lines.join('\n');
121
169
  }
122
170
  function renderSkillImportReport(report) {
@@ -138,6 +186,15 @@ function renderSkillImportReport(report) {
138
186
  lines.push(`- ${file.relative_path} (${file.kind}, ${file.bytes} bytes, ${file.sha256})`);
139
187
  }
140
188
  }
189
+ if (report.script_trust) {
190
+ lines.push('', 'Script trust', `- requested: ${String(report.script_trust.requested)}`, `- status: ${report.script_trust.status}`, `- command authority: ${String(report.script_trust.grants_command_authority)}`);
191
+ if (report.script_trust.fragment_path) {
192
+ lines.push(`- command fragment: ${report.script_trust.fragment_path}`);
193
+ }
194
+ for (const intent of report.script_trust.intents) {
195
+ lines.push(`- intent: ${intent.intent}`);
196
+ }
197
+ }
141
198
  if (report.warnings.length > 0) {
142
199
  lines.push('', 'Warnings', ...report.warnings.map((warning) => `- ${warning}`));
143
200
  }
@@ -162,7 +219,10 @@ export async function runSkill(args, reporter, lang = 'en') {
162
219
  printUsageError(reporter, formatCliOptionParseError(parsed.error, lang), 'mf skill --help', getSkillHelp(lang), lang);
163
220
  return 1;
164
221
  }
165
- if (parsed.action !== 'route' && parsed.action !== 'import') {
222
+ if (parsed.action !== 'route' &&
223
+ parsed.action !== 'import' &&
224
+ parsed.action !== 'outdated' &&
225
+ parsed.action !== 'update') {
166
226
  printUsageError(reporter, t(lang, parsed.action ? 'cli.error.unexpectedArgument' : 'cli.error.missingValue', {
167
227
  argument: parsed.action ?? '',
168
228
  option: 'route',
@@ -183,6 +243,7 @@ export async function runSkill(args, reporter, lang = 'en') {
183
243
  mode,
184
244
  name: parsed.name,
185
245
  ref: parsed.ref,
246
+ trustScripts: parsed.trustScripts,
186
247
  });
187
248
  if (parsed.json) {
188
249
  reporter.stdout(JSON.stringify(report, null, 2));
@@ -192,6 +253,30 @@ export async function runSkill(args, reporter, lang = 'en') {
192
253
  }
193
254
  return report.ok ? 0 : 1;
194
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
+ }
195
280
  if (Number.isNaN(parsed.maxCandidates)) {
196
281
  printUsageError(reporter, t(lang, 'cli.error.unexpectedValue', { option: '--max-candidates' }), 'mf skill --help', getSkillHelp(lang), lang);
197
282
  return 1;
@@ -212,6 +297,7 @@ export async function runSkill(args, reporter, lang = 'en') {
212
297
  }, null, 2));
213
298
  return 0;
214
299
  }
215
- reporter.stdout(renderSkillRouteReport(report, lang));
300
+ const updateReminder = createExternalSkillUpdateReminder(mustflowRoot);
301
+ reporter.stdout(renderSkillRouteReport(report, lang, updateReminder ? [updateReminder] : []));
216
302
  return 0;
217
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
  {