mustflow 2.85.4 → 2.99.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.
Files changed (78) hide show
  1. package/dist/cli/commands/script-pack.js +10 -0
  2. package/dist/cli/i18n/en.js +183 -0
  3. package/dist/cli/i18n/es.js +183 -0
  4. package/dist/cli/i18n/fr.js +183 -0
  5. package/dist/cli/i18n/hi.js +183 -0
  6. package/dist/cli/i18n/ko.js +183 -0
  7. package/dist/cli/i18n/zh.js +183 -0
  8. package/dist/cli/lib/script-pack-registry.js +284 -1
  9. package/dist/cli/script-packs/code-change-impact.js +6 -0
  10. package/dist/cli/script-packs/code-import-cycle.js +193 -0
  11. package/dist/cli/script-packs/docs-link-integrity.js +145 -0
  12. package/dist/cli/script-packs/repo-approval-gate.js +100 -0
  13. package/dist/cli/script-packs/repo-git-ignore-audit.js +119 -0
  14. package/dist/cli/script-packs/repo-manifest-lock-drift.js +122 -0
  15. package/dist/cli/script-packs/repo-merge-conflict-scan.js +123 -0
  16. package/dist/cli/script-packs/repo-skill-route-audit.js +86 -0
  17. package/dist/cli/script-packs/repo-version-source.js +92 -0
  18. package/dist/cli/script-packs/test-performance-report.js +247 -0
  19. package/dist/cli/script-packs/test-regression-selector.js +167 -0
  20. package/dist/core/change-impact.js +23 -51
  21. package/dist/core/change-surface-classification.js +198 -0
  22. package/dist/core/docs-link-integrity.js +443 -0
  23. package/dist/core/import-cycle.js +152 -0
  24. package/dist/core/public-json-contracts.js +116 -0
  25. package/dist/core/repo-approval-gate.js +116 -0
  26. package/dist/core/repo-git-ignore-audit.js +302 -0
  27. package/dist/core/repo-manifest-lock-drift.js +321 -0
  28. package/dist/core/repo-merge-conflict-scan.js +335 -0
  29. package/dist/core/repo-version-source.js +82 -0
  30. package/dist/core/script-pack-suggestions.js +77 -1
  31. package/dist/core/skill-route-audit.js +354 -0
  32. package/dist/core/test-performance-report.js +697 -0
  33. package/dist/core/test-regression-selector.js +335 -0
  34. package/package.json +1 -1
  35. package/schemas/README.md +40 -2
  36. package/schemas/change-impact-report.schema.json +35 -1
  37. package/schemas/import-cycle-report.schema.json +157 -0
  38. package/schemas/link-integrity-report.schema.json +176 -0
  39. package/schemas/repo-approval-gate-report.schema.json +115 -0
  40. package/schemas/repo-git-ignore-audit-report.schema.json +201 -0
  41. package/schemas/repo-manifest-lock-drift-report.schema.json +202 -0
  42. package/schemas/repo-merge-conflict-scan-report.schema.json +169 -0
  43. package/schemas/repo-version-source-report.schema.json +127 -0
  44. package/schemas/skill-route-audit-report.schema.json +144 -0
  45. package/schemas/test-performance-report.schema.json +319 -0
  46. package/schemas/test-regression-selector-report.schema.json +187 -0
  47. package/templates/default/i18n.toml +66 -18
  48. package/templates/default/locales/en/.mustflow/skills/INDEX.md +45 -8
  49. package/templates/default/locales/en/.mustflow/skills/api-access-control-review/SKILL.md +48 -27
  50. package/templates/default/locales/en/.mustflow/skills/api-failure-triage/SKILL.md +270 -0
  51. package/templates/default/locales/en/.mustflow/skills/auth-flow-triage/SKILL.md +192 -0
  52. package/templates/default/locales/en/.mustflow/skills/auth-permission-change/SKILL.md +59 -13
  53. package/templates/default/locales/en/.mustflow/skills/backend-log-evidence-review/SKILL.md +14 -5
  54. package/templates/default/locales/en/.mustflow/skills/cache-integrity-review/SKILL.md +30 -15
  55. package/templates/default/locales/en/.mustflow/skills/change-blast-radius-review/SKILL.md +45 -32
  56. package/templates/default/locales/en/.mustflow/skills/ci-pipeline-triage/SKILL.md +200 -0
  57. package/templates/default/locales/en/.mustflow/skills/clarifying-question-gate/SKILL.md +87 -13
  58. package/templates/default/locales/en/.mustflow/skills/docker-runtime-triage/SKILL.md +191 -0
  59. package/templates/default/locales/en/.mustflow/skills/go-code-change/SKILL.md +18 -13
  60. package/templates/default/locales/en/.mustflow/skills/line-ending-hygiene/SKILL.md +18 -10
  61. package/templates/default/locales/en/.mustflow/skills/llm-hallucination-control-review/SKILL.md +4 -1
  62. package/templates/default/locales/en/.mustflow/skills/motion-system-contract-review/SKILL.md +155 -0
  63. package/templates/default/locales/en/.mustflow/skills/next-action-menu/SKILL.md +177 -0
  64. package/templates/default/locales/en/.mustflow/skills/observability-debuggability-review/SKILL.md +15 -7
  65. package/templates/default/locales/en/.mustflow/skills/payment-integrity-review/SKILL.md +59 -35
  66. package/templates/default/locales/en/.mustflow/skills/powershell-code-change/SKILL.md +16 -6
  67. package/templates/default/locales/en/.mustflow/skills/prompt-contract-quality-review/SKILL.md +4 -1
  68. package/templates/default/locales/en/.mustflow/skills/python-code-change/SKILL.md +19 -10
  69. package/templates/default/locales/en/.mustflow/skills/rag-pipeline-triage/SKILL.md +206 -0
  70. package/templates/default/locales/en/.mustflow/skills/routes.toml +54 -0
  71. package/templates/default/locales/en/.mustflow/skills/rust-code-change/SKILL.md +10 -4
  72. package/templates/default/locales/en/.mustflow/skills/search-index-integrity-review/SKILL.md +181 -0
  73. package/templates/default/locales/en/.mustflow/skills/service-boundary-architecture/SKILL.md +37 -23
  74. package/templates/default/locales/en/.mustflow/skills/test-suite-performance-review/SKILL.md +9 -0
  75. package/templates/default/locales/en/.mustflow/skills/typescript-code-change/SKILL.md +14 -9
  76. package/templates/default/locales/en/.mustflow/skills/vector-search-integrity-review/SKILL.md +209 -0
  77. package/templates/default/locales/en/.mustflow/skills/version-freshness-check/SKILL.md +16 -14
  78. package/templates/default/manifest.toml +64 -1
@@ -0,0 +1,354 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { existsSync, readdirSync } from 'node:fs';
3
+ import path from 'node:path';
4
+ import { isRecord, readMustflowOwnedTomlFile } from './config-loading.js';
5
+ import { readUtf8FileInsideWithoutSymlinks } from './safe-filesystem.js';
6
+ import { parseSkillIndexRoutes } from './skill-route-alignment.js';
7
+ export const SKILL_ROUTE_AUDIT_PACK_ID = 'repo';
8
+ export const SKILL_ROUTE_AUDIT_SCRIPT_ID = 'skill-route-audit';
9
+ export const SKILL_ROUTE_AUDIT_SCRIPT_REF = `${SKILL_ROUTE_AUDIT_PACK_ID}/${SKILL_ROUTE_AUDIT_SCRIPT_ID}`;
10
+ const MAX_TEXT_BYTES = 1024 * 1024;
11
+ const SOURCE_SKILL_ROOT = '.mustflow/skills';
12
+ const SOURCE_INDEX_PATH = '.mustflow/skills/INDEX.md';
13
+ const SOURCE_ROUTES_PATH = '.mustflow/skills/routes.toml';
14
+ const TEMPLATE_SKILL_ROOT = 'templates/default/locales/en/.mustflow/skills';
15
+ const TEMPLATE_ROUTES_PATH = 'templates/default/locales/en/.mustflow/skills/routes.toml';
16
+ const TEMPLATE_MANIFEST_PATH = 'templates/default/manifest.toml';
17
+ const TEMPLATE_I18N_PATH = 'templates/default/i18n.toml';
18
+ const SKILL_PATH_PATTERN = /^\.mustflow\/skills\/([^/]+)\/SKILL\.md$/u;
19
+ function normalizePath(value) {
20
+ return value.replace(/\\/gu, '/');
21
+ }
22
+ function relativePathForSkill(skill) {
23
+ return `.mustflow/skills/${skill}/SKILL.md`;
24
+ }
25
+ function templatePathForSkill(skill) {
26
+ return `templates/default/locales/en/.mustflow/skills/${skill}/SKILL.md`;
27
+ }
28
+ function uniqueSorted(values) {
29
+ return [...new Set(values)].sort((left, right) => left.localeCompare(right));
30
+ }
31
+ function sha256(value) {
32
+ return `sha256:${createHash('sha256').update(value).digest('hex')}`;
33
+ }
34
+ function readProjectFile(projectRoot, relativePath) {
35
+ return readUtf8FileInsideWithoutSymlinks(projectRoot, path.join(projectRoot, ...relativePath.split('/')), {
36
+ maxBytes: MAX_TEXT_BYTES,
37
+ });
38
+ }
39
+ function readFrontmatterLines(content) {
40
+ if (!content.startsWith('---')) {
41
+ return [];
42
+ }
43
+ const firstLineEnd = content.indexOf('\n');
44
+ if (firstLineEnd < 0) {
45
+ return [];
46
+ }
47
+ const end = content.indexOf('\n---', firstLineEnd + 1);
48
+ if (end < 0) {
49
+ return [];
50
+ }
51
+ return content.slice(firstLineEnd + 1, end).split(/\r?\n/u);
52
+ }
53
+ function readFrontmatterScalar(lines, key) {
54
+ for (const line of lines) {
55
+ const match = /^([a-zA-Z0-9_]+):\s*(.*)$/u.exec(line.trim());
56
+ if (match?.[1] === key) {
57
+ return match[2].trim().replace(/^["']|["']$/gu, '') || null;
58
+ }
59
+ }
60
+ return null;
61
+ }
62
+ function readFrontmatterRevision(lines) {
63
+ const value = readFrontmatterScalar(lines, 'revision');
64
+ const parsed = value === null ? Number.NaN : Number.parseInt(value, 10);
65
+ return Number.isInteger(parsed) ? parsed : null;
66
+ }
67
+ function skillFromSkillPath(skillPath) {
68
+ return SKILL_PATH_PATTERN.exec(normalizePath(skillPath))?.[1] ?? null;
69
+ }
70
+ function readSkillFiles(projectRoot, rootRelativePath, issues, options = {}) {
71
+ const rootPath = path.join(projectRoot, ...rootRelativePath.split('/'));
72
+ const skills = new Map();
73
+ if (!existsSync(rootPath)) {
74
+ if (!options.optional) {
75
+ issues.push(`Missing skill root: ${rootRelativePath}`);
76
+ }
77
+ return skills;
78
+ }
79
+ for (const entry of readdirSync(rootPath, { withFileTypes: true })) {
80
+ if (!entry.isDirectory()) {
81
+ continue;
82
+ }
83
+ const skillPath = `${rootRelativePath}/${entry.name}/SKILL.md`;
84
+ const absoluteSkillPath = path.join(projectRoot, ...skillPath.split('/'));
85
+ if (!existsSync(absoluteSkillPath)) {
86
+ continue;
87
+ }
88
+ try {
89
+ const content = readProjectFile(projectRoot, skillPath);
90
+ const frontmatter = readFrontmatterLines(content);
91
+ skills.set(entry.name, {
92
+ name: entry.name,
93
+ path: skillPath,
94
+ contentHash: sha256(content.replace(/\r\n/gu, '\n')),
95
+ frontmatterName: readFrontmatterScalar(frontmatter, 'name'),
96
+ frontmatterRevision: readFrontmatterRevision(frontmatter),
97
+ });
98
+ }
99
+ catch (error) {
100
+ const message = error instanceof Error ? error.message : String(error);
101
+ issues.push(`Could not read ${skillPath}: ${message}`);
102
+ }
103
+ }
104
+ return skills;
105
+ }
106
+ function readRouteSkillNames(projectRoot, relativePath, issues, options = {}) {
107
+ try {
108
+ if (!existsSync(path.join(projectRoot, ...relativePath.split('/')))) {
109
+ if (!options.optional) {
110
+ issues.push(`Missing route metadata: ${relativePath}`);
111
+ }
112
+ return [];
113
+ }
114
+ const parsed = readMustflowOwnedTomlFile(projectRoot, relativePath);
115
+ if (!isRecord(parsed) || !isRecord(parsed.routes)) {
116
+ issues.push(`${relativePath} does not contain a [routes] table`);
117
+ return [];
118
+ }
119
+ return uniqueSorted(Object.keys(parsed.routes));
120
+ }
121
+ catch (error) {
122
+ const message = error instanceof Error ? error.message : String(error);
123
+ issues.push(`Could not read ${relativePath}: ${message}`);
124
+ return [];
125
+ }
126
+ }
127
+ function readIndexSkillNames(projectRoot, issues) {
128
+ try {
129
+ const content = readProjectFile(projectRoot, SOURCE_INDEX_PATH);
130
+ return uniqueSorted(parseSkillIndexRoutes(content).map((route) => skillFromSkillPath(route.skillPath)).filter(Boolean));
131
+ }
132
+ catch (error) {
133
+ const message = error instanceof Error ? error.message : String(error);
134
+ issues.push(`Could not read ${SOURCE_INDEX_PATH}: ${message}`);
135
+ return [];
136
+ }
137
+ }
138
+ function readStringArray(table, key) {
139
+ const value = table[key];
140
+ return Array.isArray(value) && value.every((entry) => typeof entry === 'string') ? [...value] : [];
141
+ }
142
+ function readTemplateManifest(projectRoot, issues) {
143
+ try {
144
+ if (!existsSync(path.join(projectRoot, ...TEMPLATE_MANIFEST_PATH.split('/')))) {
145
+ return { creates: [], profileEntries: [] };
146
+ }
147
+ const parsed = readMustflowOwnedTomlFile(projectRoot, TEMPLATE_MANIFEST_PATH);
148
+ if (!isRecord(parsed)) {
149
+ issues.push(`${TEMPLATE_MANIFEST_PATH} does not contain a TOML table`);
150
+ return { creates: [], profileEntries: [] };
151
+ }
152
+ const creates = readStringArray(parsed, 'creates').filter((entry) => SKILL_PATH_PATTERN.test(entry));
153
+ const profiles = isRecord(parsed.skill_profiles) ? parsed.skill_profiles : {};
154
+ const profileEntries = Object.values(profiles).flatMap((value) => Array.isArray(value) && value.every((entry) => typeof entry === 'string') ? value : []);
155
+ return {
156
+ creates: uniqueSorted(creates),
157
+ profileEntries: uniqueSorted(profileEntries),
158
+ };
159
+ }
160
+ catch (error) {
161
+ const message = error instanceof Error ? error.message : String(error);
162
+ issues.push(`Could not read ${TEMPLATE_MANIFEST_PATH}: ${message}`);
163
+ return { creates: [], profileEntries: [] };
164
+ }
165
+ }
166
+ function readI18nDocuments(projectRoot, issues) {
167
+ const documents = new Map();
168
+ try {
169
+ if (!existsSync(path.join(projectRoot, ...TEMPLATE_I18N_PATH.split('/')))) {
170
+ return documents;
171
+ }
172
+ const parsed = readMustflowOwnedTomlFile(projectRoot, TEMPLATE_I18N_PATH);
173
+ if (!isRecord(parsed) || !isRecord(parsed.documents)) {
174
+ issues.push(`${TEMPLATE_I18N_PATH} does not contain a [documents] table`);
175
+ return documents;
176
+ }
177
+ for (const [id, value] of Object.entries(parsed.documents)) {
178
+ if (!id.startsWith('skill.') || !isRecord(value)) {
179
+ continue;
180
+ }
181
+ documents.set(id, {
182
+ id,
183
+ source: typeof value.source === 'string' ? value.source : '',
184
+ revision: Number.isInteger(value.revision) ? Number(value.revision) : null,
185
+ });
186
+ }
187
+ }
188
+ catch (error) {
189
+ const message = error instanceof Error ? error.message : String(error);
190
+ issues.push(`Could not read ${TEMPLATE_I18N_PATH}: ${message}`);
191
+ }
192
+ return documents;
193
+ }
194
+ function makeFinding(code, severity, pathValue, skill, message, route = skill) {
195
+ return { code, severity, path: pathValue, skill, route, message };
196
+ }
197
+ function compareSetPresence(findings, left, right, options) {
198
+ const leftSet = new Set(left);
199
+ const rightSet = new Set(right);
200
+ for (const skill of left) {
201
+ if (!rightSet.has(skill)) {
202
+ findings.push(makeFinding(options.missingCode, 'high', options.missingPath(skill), skill, options.missingMessage(skill)));
203
+ }
204
+ }
205
+ for (const skill of right) {
206
+ if (!leftSet.has(skill)) {
207
+ findings.push(makeFinding(options.extraCode, 'medium', options.extraPath(skill), skill, options.extraMessage(skill)));
208
+ }
209
+ }
210
+ }
211
+ function createInputHash(reportInput) {
212
+ return sha256(JSON.stringify(reportInput));
213
+ }
214
+ export function auditSkillRoutes(projectRoot) {
215
+ const root = path.resolve(projectRoot);
216
+ const issues = [];
217
+ const sourceSkills = readSkillFiles(root, SOURCE_SKILL_ROOT, issues);
218
+ const templateSkills = readSkillFiles(root, TEMPLATE_SKILL_ROOT, issues, { optional: true });
219
+ const sourceSkillNames = uniqueSorted(sourceSkills.keys());
220
+ const templateSkillNames = uniqueSorted(templateSkills.keys());
221
+ const indexSkillNames = readIndexSkillNames(root, issues);
222
+ const routeSkillNames = readRouteSkillNames(root, SOURCE_ROUTES_PATH, issues);
223
+ const templateRouteSkillNames = readRouteSkillNames(root, TEMPLATE_ROUTES_PATH, issues, { optional: true });
224
+ const manifest = readTemplateManifest(root, issues);
225
+ const manifestCreateSkillNames = uniqueSorted(manifest.creates.map((entry) => skillFromSkillPath(entry)).filter(Boolean));
226
+ const i18nDocuments = readI18nDocuments(root, issues);
227
+ const i18nSkillNames = uniqueSorted([...i18nDocuments.keys()].map((id) => id.replace(/^skill\./u, '')));
228
+ const findings = [];
229
+ compareSetPresence(findings, sourceSkillNames, routeSkillNames, {
230
+ missingCode: 'skill_without_route',
231
+ extraCode: 'route_without_skill',
232
+ missingPath: relativePathForSkill,
233
+ extraPath: () => SOURCE_ROUTES_PATH,
234
+ missingMessage: (skill) => `${skill} has a source SKILL.md but no route metadata entry.`,
235
+ extraMessage: (skill) => `${SOURCE_ROUTES_PATH} declares ${skill}, but no source SKILL.md exists.`,
236
+ });
237
+ compareSetPresence(findings, sourceSkillNames, indexSkillNames, {
238
+ missingCode: 'skill_missing_index_entry',
239
+ extraCode: 'index_without_skill',
240
+ missingPath: relativePathForSkill,
241
+ extraPath: () => SOURCE_INDEX_PATH,
242
+ missingMessage: (skill) => `${skill} has a source SKILL.md but no INDEX.md route row.`,
243
+ extraMessage: (skill) => `${SOURCE_INDEX_PATH} references ${skill}, but no source SKILL.md exists.`,
244
+ });
245
+ for (const skill of routeSkillNames) {
246
+ if (!indexSkillNames.includes(skill)) {
247
+ findings.push(makeFinding('route_missing_index_entry', 'medium', SOURCE_ROUTES_PATH, skill, `${SOURCE_ROUTES_PATH} declares ${skill}, but INDEX.md has no matching route row.`));
248
+ }
249
+ }
250
+ for (const [skill, summary] of sourceSkills) {
251
+ if (summary.frontmatterName !== null && summary.frontmatterName !== skill) {
252
+ findings.push(makeFinding('frontmatter_name_mismatch', 'medium', summary.path, skill, `${summary.path} frontmatter name is ${summary.frontmatterName}, expected ${skill}.`));
253
+ }
254
+ }
255
+ if (templateSkillNames.length > 0) {
256
+ compareSetPresence(findings, sourceSkillNames, templateSkillNames, {
257
+ missingCode: 'template_missing_skill',
258
+ extraCode: 'template_extra_skill',
259
+ missingPath: templatePathForSkill,
260
+ extraPath: templatePathForSkill,
261
+ missingMessage: (skill) => `Template locale is missing ${skill}.`,
262
+ extraMessage: (skill) => `Template locale includes ${skill}, but source skills do not.`,
263
+ });
264
+ }
265
+ for (const skill of sourceSkillNames) {
266
+ const source = sourceSkills.get(skill);
267
+ const template = templateSkills.get(skill);
268
+ if (source && template && source.contentHash !== template.contentHash) {
269
+ findings.push(makeFinding('template_skill_drift', 'medium', template.path, skill, `${template.path} does not match ${source.path}.`));
270
+ }
271
+ }
272
+ if (templateRouteSkillNames.length > 0 && routeSkillNames.join('\n') !== templateRouteSkillNames.join('\n')) {
273
+ findings.push(makeFinding('template_routes_drift', 'high', TEMPLATE_ROUTES_PATH, null, `${TEMPLATE_ROUTES_PATH} route keys do not match ${SOURCE_ROUTES_PATH}.`, null));
274
+ }
275
+ if (manifestCreateSkillNames.length > 0) {
276
+ compareSetPresence(findings, sourceSkillNames, manifestCreateSkillNames, {
277
+ missingCode: 'manifest_missing_create',
278
+ extraCode: 'manifest_create_without_skill',
279
+ missingPath: relativePathForSkill,
280
+ extraPath: relativePathForSkill,
281
+ missingMessage: (skill) => `${TEMPLATE_MANIFEST_PATH} creates does not install ${skill}.`,
282
+ extraMessage: (skill) => `${TEMPLATE_MANIFEST_PATH} creates installs ${skill}, but no source SKILL.md exists.`,
283
+ });
284
+ }
285
+ const manifestProfileSkills = manifest.profileEntries;
286
+ const manifestProfileSkillSet = new Set(manifestProfileSkills);
287
+ if (manifestProfileSkills.length > 0) {
288
+ for (const skill of sourceSkillNames) {
289
+ if (!manifestProfileSkillSet.has(skill)) {
290
+ findings.push(makeFinding('manifest_profile_missing_skill', 'medium', TEMPLATE_MANIFEST_PATH, skill, `${TEMPLATE_MANIFEST_PATH} skill profiles do not include ${skill}.`));
291
+ }
292
+ }
293
+ }
294
+ for (const skill of manifestProfileSkills) {
295
+ if (!sourceSkills.has(skill)) {
296
+ findings.push(makeFinding('manifest_profile_unknown_skill', 'medium', TEMPLATE_MANIFEST_PATH, skill, `${TEMPLATE_MANIFEST_PATH} skill profiles reference ${skill}, but no source SKILL.md exists.`));
297
+ }
298
+ }
299
+ for (const skill of i18nDocuments.size > 0 ? sourceSkillNames : []) {
300
+ const document = i18nDocuments.get(`skill.${skill}`);
301
+ const source = sourceSkills.get(skill);
302
+ if (!document) {
303
+ findings.push(makeFinding('i18n_missing_document', 'medium', TEMPLATE_I18N_PATH, skill, `${TEMPLATE_I18N_PATH} does not declare documents."skill.${skill}".`));
304
+ continue;
305
+ }
306
+ const expectedSource = `locales/en/.mustflow/skills/${skill}/SKILL.md`;
307
+ if (document.source !== expectedSource) {
308
+ findings.push(makeFinding('i18n_source_mismatch', 'medium', TEMPLATE_I18N_PATH, skill, `documents."skill.${skill}".source is ${document.source}, expected ${expectedSource}.`));
309
+ }
310
+ if (source?.frontmatterRevision !== null && document.revision !== source?.frontmatterRevision) {
311
+ findings.push(makeFinding('i18n_revision_mismatch', 'medium', TEMPLATE_I18N_PATH, skill, `documents."skill.${skill}".revision is ${document.revision}, expected ${source?.frontmatterRevision}.`));
312
+ }
313
+ }
314
+ const inventory = {
315
+ source_skills: sourceSkillNames,
316
+ index_skills: indexSkillNames,
317
+ route_skills: routeSkillNames,
318
+ template_skills: templateSkillNames,
319
+ manifest_skill_creates: manifestCreateSkillNames,
320
+ manifest_profile_skills: manifestProfileSkills,
321
+ i18n_skill_documents: i18nSkillNames,
322
+ };
323
+ const status = issues.length > 0 ? 'partial' : findings.length > 0 ? 'issues_found' : 'ok';
324
+ return {
325
+ schema_version: '1',
326
+ command: 'script-pack',
327
+ pack_id: SKILL_ROUTE_AUDIT_PACK_ID,
328
+ script_id: SKILL_ROUTE_AUDIT_SCRIPT_ID,
329
+ script_ref: SKILL_ROUTE_AUDIT_SCRIPT_REF,
330
+ action: 'audit',
331
+ status,
332
+ ok: status !== 'partial',
333
+ mustflow_root: root,
334
+ input: {
335
+ source_skill_root: SOURCE_SKILL_ROOT,
336
+ template_skill_root: TEMPLATE_SKILL_ROOT,
337
+ template_manifest: TEMPLATE_MANIFEST_PATH,
338
+ template_i18n: TEMPLATE_I18N_PATH,
339
+ },
340
+ input_hash: createInputHash({ inventory, findings, issues }),
341
+ counts: {
342
+ source_skills: sourceSkillNames.length,
343
+ index_routes: indexSkillNames.length,
344
+ route_metadata: routeSkillNames.length,
345
+ template_skills: templateSkillNames.length,
346
+ manifest_skill_creates: manifestCreateSkillNames.length,
347
+ manifest_profile_entries: manifestProfileSkills.length,
348
+ i18n_skill_documents: i18nSkillNames.length,
349
+ },
350
+ inventory,
351
+ findings,
352
+ issues,
353
+ };
354
+ }