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 +2 -0
- package/dist/cli/commands/skill.js +90 -4
- package/dist/cli/lib/command-registry.js +1 -1
- package/dist/cli/lib/external-skill-import.js +649 -5
- package/dist/core/public-json-contracts.js +9 -0
- package/package.json +1 -1
- package/schemas/README.md +4 -0
- package/schemas/skill-import-report.schema.json +75 -0
- package/schemas/skill-update-report.schema.json +256 -0
- package/templates/default/i18n.toml +1 -1
- package/templates/default/locales/en/.mustflow/skills/security-privacy-review/SKILL.md +4 -1
- package/templates/default/manifest.toml +1 -1
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' &&
|
|
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
|
-
|
|
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
|
{
|