mustflow 2.22.5 → 2.22.9

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 (42) hide show
  1. package/README.md +8 -0
  2. package/dist/cli/commands/classify.js +2 -0
  3. package/dist/cli/commands/dashboard.js +9 -69
  4. package/dist/cli/commands/run/receipt.js +1 -0
  5. package/dist/cli/commands/run.js +14 -1
  6. package/dist/cli/commands/verify/evidence-input.js +269 -0
  7. package/dist/cli/commands/verify/input.js +212 -0
  8. package/dist/cli/commands/verify.js +23 -482
  9. package/dist/cli/i18n/en.js +3 -0
  10. package/dist/cli/i18n/es.js +3 -0
  11. package/dist/cli/i18n/fr.js +3 -0
  12. package/dist/cli/i18n/hi.js +3 -0
  13. package/dist/cli/i18n/ko.js +3 -0
  14. package/dist/cli/i18n/zh.js +3 -0
  15. package/dist/cli/lib/dashboard-export.js +2 -0
  16. package/dist/cli/lib/dashboard-mutations.js +79 -0
  17. package/dist/cli/lib/local-index/command-effect-index.js +25 -0
  18. package/dist/cli/lib/local-index/hashing.js +7 -0
  19. package/dist/cli/lib/local-index/index.js +127 -826
  20. package/dist/cli/lib/local-index/source-index.js +137 -0
  21. package/dist/cli/lib/local-index/verification-evidence.js +451 -0
  22. package/dist/cli/lib/local-index/workflow-documents.js +204 -0
  23. package/dist/cli/lib/run-root-trust.js +27 -0
  24. package/dist/core/change-classification-policy.js +47 -0
  25. package/dist/core/change-classification.js +10 -43
  26. package/dist/core/contract-lint.js +6 -2
  27. package/dist/core/correlation-id.js +16 -0
  28. package/dist/core/run-receipt.js +1 -0
  29. package/package.json +4 -1
  30. package/schemas/README.md +4 -0
  31. package/schemas/change-verification-report.schema.json +4 -0
  32. package/schemas/classify-report.schema.json +4 -0
  33. package/schemas/dashboard-export.schema.json +4 -0
  34. package/schemas/latest-run-pointer.schema.json +4 -0
  35. package/schemas/run-receipt.schema.json +4 -0
  36. package/schemas/verify-report.schema.json +4 -0
  37. package/schemas/verify-run-manifest.schema.json +4 -0
  38. package/templates/default/i18n.toml +3 -3
  39. package/templates/default/locales/en/.mustflow/skills/architecture-deepening-review/SKILL.md +25 -2
  40. package/templates/default/locales/en/.mustflow/skills/security-privacy-review/SKILL.md +9 -1
  41. package/templates/default/locales/en/.mustflow/skills/test-design-guard/SKILL.md +9 -1
  42. package/templates/default/manifest.toml +1 -1
@@ -0,0 +1,212 @@
1
+ import path from 'node:path';
2
+ import { createClassifyOutput } from '../classify.js';
3
+ import { createCorrelationId, isCorrelationId } from '../../../core/correlation-id.js';
4
+ import { readUtf8FileInsideWithoutSymlinks, writeJsonFileInsideWithoutSymlinks } from '../../../core/safe-filesystem.js';
5
+ function uniqueStrings(values) {
6
+ return [...new Set(values.map((value) => value.trim()).filter((value) => value.length > 0))];
7
+ }
8
+ function isStringArray(value) {
9
+ return Array.isArray(value) && value.every((item) => typeof item === 'string');
10
+ }
11
+ function isPlainRecord(value) {
12
+ return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
13
+ }
14
+ function isUpdatePolicy(value) {
15
+ return value === 'update' || value === 'update_or_mark_stale' || value === 'not_applicable';
16
+ }
17
+ function isUpdatePolicyArray(value) {
18
+ return Array.isArray(value) && value.every((item) => isUpdatePolicy(item));
19
+ }
20
+ function readPlanRoot(value) {
21
+ if (!isPlainRecord(value)) {
22
+ return null;
23
+ }
24
+ const root = value.mustflow_root;
25
+ return typeof root === 'string' && root.length > 0 ? root : null;
26
+ }
27
+ function readPlanCorrelationId(value) {
28
+ if (!isPlainRecord(value)) {
29
+ return null;
30
+ }
31
+ return isCorrelationId(value.correlation_id) ? value.correlation_id : null;
32
+ }
33
+ function readStrictClassificationSummary(value) {
34
+ if (!isPlainRecord(value)) {
35
+ return null;
36
+ }
37
+ if (!Number.isInteger(value.fileCount) ||
38
+ Number(value.fileCount) < 0 ||
39
+ !Number.isInteger(value.publicSurfaceCount) ||
40
+ Number(value.publicSurfaceCount) < 0 ||
41
+ !isStringArray(value.changeKinds) ||
42
+ !isStringArray(value.validationReasons) ||
43
+ !isUpdatePolicyArray(value.updatePolicies) ||
44
+ !isStringArray(value.driftChecks) ||
45
+ !isStringArray(value.affectedContracts)) {
46
+ return null;
47
+ }
48
+ return {
49
+ fileCount: Number(value.fileCount),
50
+ publicSurfaceCount: Number(value.publicSurfaceCount),
51
+ changeKinds: [...value.changeKinds],
52
+ validationReasons: uniqueStrings(value.validationReasons),
53
+ updatePolicies: [...value.updatePolicies],
54
+ driftChecks: [...value.driftChecks],
55
+ affectedContracts: [...value.affectedContracts],
56
+ };
57
+ }
58
+ function readStrictClassification(value) {
59
+ if (!isPlainRecord(value) || typeof value.path !== 'string' || !isStringArray(value.changeKinds)) {
60
+ return null;
61
+ }
62
+ const surface = value.surface;
63
+ if (!isPlainRecord(surface)) {
64
+ return null;
65
+ }
66
+ if (typeof surface.kind !== 'string' ||
67
+ typeof surface.category !== 'string' ||
68
+ typeof surface.isPublicSurface !== 'boolean' ||
69
+ !isStringArray(surface.validationReasons) ||
70
+ !isStringArray(surface.affectedContracts) ||
71
+ !isUpdatePolicy(surface.updatePolicy) ||
72
+ !isStringArray(surface.driftChecks)) {
73
+ return null;
74
+ }
75
+ return {
76
+ path: value.path,
77
+ changeKinds: [...value.changeKinds],
78
+ surface: {
79
+ kind: surface.kind,
80
+ category: surface.category,
81
+ isPublicSurface: surface.isPublicSurface,
82
+ validationReasons: [...surface.validationReasons],
83
+ affectedContracts: [...surface.affectedContracts],
84
+ updatePolicy: surface.updatePolicy,
85
+ driftChecks: [...surface.driftChecks],
86
+ },
87
+ };
88
+ }
89
+ function readStrictClassifications(value) {
90
+ if (!Array.isArray(value)) {
91
+ return null;
92
+ }
93
+ const classifications = value.map(readStrictClassification);
94
+ return classifications.every((classification) => classification !== null)
95
+ ? classifications
96
+ : null;
97
+ }
98
+ function readStrictClassifyPlan(projectRoot, plan) {
99
+ if (!isPlainRecord(plan)) {
100
+ throw new Error('unsupported_plan_source');
101
+ }
102
+ if (plan.schema_version !== '1' || plan.command !== 'classify') {
103
+ throw new Error('unsupported_plan_source');
104
+ }
105
+ if (readPlanRoot(plan) !== projectRoot) {
106
+ throw new Error('plan_root_mismatch');
107
+ }
108
+ const source = plan.source;
109
+ const files = plan.files;
110
+ const classifications = readStrictClassifications(plan.classifications);
111
+ const summary = readStrictClassificationSummary(plan.summary);
112
+ if ((source !== 'changed' && source !== 'paths') || !isStringArray(files) || !classifications || !summary) {
113
+ throw new Error('invalid_plan_file');
114
+ }
115
+ if (summary.validationReasons.length === 0) {
116
+ throw new Error('missing_plan_reasons');
117
+ }
118
+ return {
119
+ source,
120
+ files: [...files],
121
+ classifications,
122
+ summary,
123
+ };
124
+ }
125
+ function emptyClassificationSummary(validationReasons) {
126
+ return {
127
+ fileCount: 0,
128
+ publicSurfaceCount: 0,
129
+ changeKinds: [],
130
+ validationReasons,
131
+ updatePolicies: [],
132
+ driftChecks: [],
133
+ affectedContracts: [],
134
+ };
135
+ }
136
+ export function createSyntheticClassificationReport(reasons, source = 'paths', files = []) {
137
+ return {
138
+ source,
139
+ files,
140
+ classifications: [],
141
+ summary: emptyClassificationSummary(reasons),
142
+ };
143
+ }
144
+ export function resolveVerifyInputPath(projectRoot, inputPath) {
145
+ const resolved = path.resolve(projectRoot, inputPath);
146
+ const relative = path.relative(projectRoot, resolved);
147
+ if (relative.startsWith('..') || path.isAbsolute(relative)) {
148
+ throw new Error('plan_path_outside_root');
149
+ }
150
+ return resolved;
151
+ }
152
+ export function readVerifyJsonInputFile(projectRoot, inputPath, invalidCode) {
153
+ const inputFilePath = resolveVerifyInputPath(projectRoot, inputPath);
154
+ let content;
155
+ try {
156
+ content = readUtf8FileInsideWithoutSymlinks(projectRoot, inputFilePath);
157
+ }
158
+ catch (error) {
159
+ if (error instanceof Error && error.message.startsWith('Path must not contain symlinks:')) {
160
+ throw new Error('input_path_contains_symlink');
161
+ }
162
+ throw new Error(invalidCode);
163
+ }
164
+ try {
165
+ return JSON.parse(content);
166
+ }
167
+ catch {
168
+ throw new Error(invalidCode);
169
+ }
170
+ }
171
+ export function readInputFromClassificationReport(projectRoot, inputPath) {
172
+ const parsed = readVerifyJsonInputFile(projectRoot, inputPath, 'invalid_plan_file');
173
+ const classificationReport = readStrictClassifyPlan(projectRoot, parsed);
174
+ return {
175
+ correlationId: readPlanCorrelationId(parsed) ?? createCorrelationId('verify'),
176
+ reasons: classificationReport.summary.validationReasons,
177
+ classificationReport,
178
+ };
179
+ }
180
+ export function createInputFromChanged(projectRoot) {
181
+ const plan = createClassifyOutput(projectRoot, 'changed', []);
182
+ return {
183
+ plan,
184
+ input: {
185
+ correlationId: plan.correlation_id,
186
+ reasons: plan.summary.validationReasons,
187
+ classificationReport: plan,
188
+ },
189
+ };
190
+ }
191
+ export function writeChangedPlan(projectRoot, inputPath, plan) {
192
+ const planPath = resolveVerifyInputPath(projectRoot, inputPath);
193
+ writeJsonFileInsideWithoutSymlinks(projectRoot, planPath, plan);
194
+ }
195
+ export function planErrorMessageKey(code) {
196
+ switch (code) {
197
+ case 'plan_path_outside_root':
198
+ return 'verify.error.plan_path_outside_root';
199
+ case 'input_path_contains_symlink':
200
+ return 'verify.error.input_path_contains_symlink';
201
+ case 'missing_plan_reasons':
202
+ return 'verify.error.missing_plan_reasons';
203
+ case 'unsupported_plan_source':
204
+ return 'verify.error.unsupported_plan_source';
205
+ case 'plan_root_mismatch':
206
+ return 'verify.error.plan_root_mismatch';
207
+ case 'git_changed_files_unavailable':
208
+ return 'verify.error.changed_files_unavailable';
209
+ default:
210
+ return 'verify.error.invalid_plan_file';
211
+ }
212
+ }