mustflow 2.70.0 → 2.74.1
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 +20 -6
- package/dist/cli/commands/api.js +17 -0
- package/dist/cli/commands/check.js +38 -26
- package/dist/cli/commands/doctor.js +17 -5
- package/dist/cli/commands/evidence.js +71 -0
- package/dist/cli/commands/index.js +24 -9
- package/dist/cli/commands/map.js +20 -7
- package/dist/cli/commands/run.js +2 -1
- package/dist/cli/commands/script-pack.js +124 -0
- package/dist/cli/commands/update.js +52 -39
- package/dist/cli/commands/verify.js +50 -15
- package/dist/cli/commands/workspace.js +2 -0
- package/dist/cli/i18n/en.js +38 -0
- package/dist/cli/i18n/es.js +38 -0
- package/dist/cli/i18n/fr.js +38 -0
- package/dist/cli/i18n/hi.js +38 -0
- package/dist/cli/i18n/ko.js +38 -0
- package/dist/cli/i18n/zh.js +38 -0
- package/dist/cli/index.js +1 -0
- package/dist/cli/lib/active-command-lock.js +96 -0
- package/dist/cli/lib/agent-context.js +179 -10
- package/dist/cli/lib/command-registry.js +6 -0
- package/dist/cli/lib/dashboard-export.js +1 -0
- package/dist/cli/lib/script-pack-registry.js +27 -0
- package/dist/cli/script-packs/core-text-budget.js +241 -0
- package/dist/core/active-run-locks.js +7 -1
- package/dist/core/change-verification.js +10 -0
- package/dist/core/completion-verdict.js +14 -1
- package/dist/core/complexity-budget.js +206 -0
- package/dist/core/conflict-ledger.js +122 -0
- package/dist/core/failure-replay-capsule.js +213 -0
- package/dist/core/public-json-contracts.js +27 -0
- package/dist/core/risk-priced-evidence.js +213 -0
- package/dist/core/script-check-result.js +1 -0
- package/dist/core/text-budget.js +262 -0
- package/dist/core/verification-evidence.js +61 -13
- package/package.json +1 -1
- package/schemas/README.md +23 -11
- package/schemas/change-verification-report.schema.json +29 -0
- package/schemas/context-report.schema.json +58 -2
- package/schemas/dashboard-export.schema.json +42 -1
- package/schemas/diff-risk.schema.json +6 -0
- package/schemas/evidence-report.schema.json +45 -0
- package/schemas/latest-run-pointer.schema.json +50 -1
- package/schemas/script-pack-catalog.schema.json +68 -0
- package/schemas/text-budget-report.schema.json +131 -0
- package/schemas/verification-plan.schema.json +32 -0
- package/schemas/verify-report.schema.json +360 -1
- package/schemas/verify-run-manifest.schema.json +50 -1
- package/schemas/workspace-verification-plan.schema.json +32 -0
- package/templates/default/i18n.toml +2 -2
- package/templates/default/locales/en/.mustflow/skills/INDEX.md +2 -2
- package/templates/default/locales/en/.mustflow/skills/adapter-boundary/SKILL.md +19 -2
- package/templates/default/locales/en/.mustflow/skills/routes.toml +1 -1
- package/templates/default/manifest.toml +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { ACTIVE_RUN_LOCK_ID_ENV, acquireActiveRunLock, } from '../../core/active-run-locks.js';
|
|
2
|
+
import { renderCliError } from './cli-output.js';
|
|
3
|
+
import { t } from './i18n.js';
|
|
4
|
+
export const REPO_MAP_WRITE_EFFECTS = [
|
|
5
|
+
{ type: 'write', mode: 'replace', path: 'REPO_MAP.md', concurrency: 'exclusive' },
|
|
6
|
+
];
|
|
7
|
+
export const LOCAL_INDEX_WRITE_EFFECTS = [
|
|
8
|
+
{
|
|
9
|
+
type: 'write',
|
|
10
|
+
mode: 'replace',
|
|
11
|
+
path: '.mustflow/cache/**',
|
|
12
|
+
lock: 'local_index_cache',
|
|
13
|
+
concurrency: 'exclusive',
|
|
14
|
+
},
|
|
15
|
+
];
|
|
16
|
+
export const MUSTFLOW_UPDATE_APPLY_EFFECTS = [
|
|
17
|
+
{ type: 'write', mode: 'replace', path: 'AGENTS.md', concurrency: 'exclusive' },
|
|
18
|
+
{ type: 'write', mode: 'replace', path: '.mustflow/config/manifest.lock.toml', concurrency: 'exclusive' },
|
|
19
|
+
{ type: 'write', mode: 'replace', path: '.mustflow/config/commands.toml', concurrency: 'exclusive' },
|
|
20
|
+
{ type: 'write', mode: 'replace', path: '.mustflow/config/mustflow.toml', concurrency: 'exclusive' },
|
|
21
|
+
{ type: 'write', mode: 'replace', path: '.mustflow/config/preferences.toml', concurrency: 'exclusive' },
|
|
22
|
+
{ type: 'write', mode: 'replace', path: '.mustflow/context/**', concurrency: 'exclusive' },
|
|
23
|
+
{ type: 'write', mode: 'replace', path: '.mustflow/docs/**', concurrency: 'exclusive' },
|
|
24
|
+
{ type: 'write', mode: 'replace', path: '.mustflow/skills/**', concurrency: 'exclusive' },
|
|
25
|
+
{ type: 'write', mode: 'replace', path: '.mustflow/backups/**', concurrency: 'exclusive' },
|
|
26
|
+
];
|
|
27
|
+
export const GENERATED_SURFACE_READ_EFFECTS = [
|
|
28
|
+
{ type: 'read', mode: 'read', path: 'REPO_MAP.md', concurrency: 'shared' },
|
|
29
|
+
{ type: 'read', mode: 'read', path: '.mustflow/config/manifest.lock.toml', concurrency: 'shared' },
|
|
30
|
+
{
|
|
31
|
+
type: 'read',
|
|
32
|
+
mode: 'read',
|
|
33
|
+
path: '.mustflow/cache/**',
|
|
34
|
+
lock: 'local_index_cache',
|
|
35
|
+
concurrency: 'shared',
|
|
36
|
+
},
|
|
37
|
+
];
|
|
38
|
+
function effectToToml(effect) {
|
|
39
|
+
const output = {
|
|
40
|
+
type: effect.type,
|
|
41
|
+
};
|
|
42
|
+
if (effect.mode) {
|
|
43
|
+
output.mode = effect.mode;
|
|
44
|
+
}
|
|
45
|
+
if (effect.path) {
|
|
46
|
+
output.path = effect.path;
|
|
47
|
+
}
|
|
48
|
+
if (effect.paths) {
|
|
49
|
+
output.paths = [...effect.paths];
|
|
50
|
+
}
|
|
51
|
+
if (effect.lock) {
|
|
52
|
+
output.lock = effect.lock;
|
|
53
|
+
}
|
|
54
|
+
if (effect.concurrency) {
|
|
55
|
+
output.concurrency = effect.concurrency;
|
|
56
|
+
}
|
|
57
|
+
return output;
|
|
58
|
+
}
|
|
59
|
+
function createSyntheticCommandContract(intentName, effects) {
|
|
60
|
+
return {
|
|
61
|
+
defaults: { default_cwd: '.' },
|
|
62
|
+
resources: {},
|
|
63
|
+
intents: {
|
|
64
|
+
[intentName]: {
|
|
65
|
+
cwd: '.',
|
|
66
|
+
writes: [],
|
|
67
|
+
effects: effects.map(effectToToml),
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
function parentRunId() {
|
|
73
|
+
const value = process.env[ACTIVE_RUN_LOCK_ID_ENV]?.trim();
|
|
74
|
+
return value && value.length > 0 ? value : null;
|
|
75
|
+
}
|
|
76
|
+
export function acquireActiveCommandLock(projectRoot, displayName, effects) {
|
|
77
|
+
return acquireActiveRunLock(projectRoot, createSyntheticCommandContract(displayName, effects), displayName, {
|
|
78
|
+
commandHash: null,
|
|
79
|
+
ignoreRunId: parentRunId(),
|
|
80
|
+
ignorePid: process.ppid,
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
export function renderActiveCommandLockConflictMessage(displayName, conflicts, lang) {
|
|
84
|
+
const [first] = conflicts;
|
|
85
|
+
const detail = first
|
|
86
|
+
? t(lang, 'run.error.activeLockConflictDetail', {
|
|
87
|
+
lock: first.lock,
|
|
88
|
+
intent: first.conflictsWithIntent,
|
|
89
|
+
pid: first.conflictsWithPid,
|
|
90
|
+
})
|
|
91
|
+
: t(lang, 'run.error.activeLockConflictUnknown');
|
|
92
|
+
return t(lang, 'run.error.activeLockConflict', { intent: displayName, detail });
|
|
93
|
+
}
|
|
94
|
+
export function reportActiveCommandLockConflict(reporter, displayName, conflicts, helpCommand, lang) {
|
|
95
|
+
reporter.stderr(renderCliError(renderActiveCommandLockConflictMessage(displayName, conflicts, lang), helpCommand, lang));
|
|
96
|
+
}
|
|
@@ -87,10 +87,20 @@ function readNumber(table, key) {
|
|
|
87
87
|
return typeof value === 'number' && Number.isFinite(value) ? value : null;
|
|
88
88
|
}
|
|
89
89
|
function readPathContext(projectRoot, paths) {
|
|
90
|
-
return paths.map((relativePath) =>
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
90
|
+
return paths.map((relativePath) => {
|
|
91
|
+
const normalizedPath = toPosixPath(relativePath);
|
|
92
|
+
const content = safeRead(projectRoot, normalizedPath);
|
|
93
|
+
return {
|
|
94
|
+
path: normalizedPath,
|
|
95
|
+
exists: content !== null,
|
|
96
|
+
trust: createContextTrust({
|
|
97
|
+
relativePath: normalizedPath,
|
|
98
|
+
cacheLayer: null,
|
|
99
|
+
contentHash: content === null ? null : sha256(content),
|
|
100
|
+
exists: content !== null,
|
|
101
|
+
}),
|
|
102
|
+
};
|
|
103
|
+
});
|
|
94
104
|
}
|
|
95
105
|
function readScalarObject(table) {
|
|
96
106
|
if (!table) {
|
|
@@ -111,6 +121,100 @@ function readScalarObject(table) {
|
|
|
111
121
|
function sha256(content) {
|
|
112
122
|
return `sha256:${createHash('sha256').update(content).digest('hex')}`;
|
|
113
123
|
}
|
|
124
|
+
function trustSourceKind(relativePath, sourceKind) {
|
|
125
|
+
if (sourceKind === 'runtime_volatile') {
|
|
126
|
+
return 'runtime_volatile';
|
|
127
|
+
}
|
|
128
|
+
if (relativePath === null) {
|
|
129
|
+
return sourceKind === 'dynamic_selection' ? 'source_placeholder' : 'unknown_file';
|
|
130
|
+
}
|
|
131
|
+
if (relativePath === COMMANDS_RELATIVE_PATH) {
|
|
132
|
+
return 'command_contract';
|
|
133
|
+
}
|
|
134
|
+
if (relativePath === LATEST_RUN_RELATIVE_PATH || relativePath.startsWith('.mustflow/state/')) {
|
|
135
|
+
return 'generated_state';
|
|
136
|
+
}
|
|
137
|
+
if (relativePath.startsWith('.mustflow/cache/')) {
|
|
138
|
+
return 'generated_cache';
|
|
139
|
+
}
|
|
140
|
+
if (relativePath.startsWith('.mustflow/skills/') && relativePath.endsWith('/SKILL.md')) {
|
|
141
|
+
return 'skill_file';
|
|
142
|
+
}
|
|
143
|
+
if (relativePath.startsWith('.mustflow/context/')) {
|
|
144
|
+
return 'context_file';
|
|
145
|
+
}
|
|
146
|
+
if (relativePath === 'AGENTS.md' ||
|
|
147
|
+
relativePath.startsWith('.mustflow/docs/') ||
|
|
148
|
+
relativePath.startsWith('.mustflow/config/') ||
|
|
149
|
+
relativePath.startsWith('.mustflow/skills/')) {
|
|
150
|
+
return 'workflow_file';
|
|
151
|
+
}
|
|
152
|
+
return 'unknown_file';
|
|
153
|
+
}
|
|
154
|
+
function trustAuthority(relativePath, sourceKind) {
|
|
155
|
+
if (relativePath === 'AGENTS.md') {
|
|
156
|
+
return 'binding';
|
|
157
|
+
}
|
|
158
|
+
if (relativePath === COMMANDS_RELATIVE_PATH) {
|
|
159
|
+
return 'command_contract';
|
|
160
|
+
}
|
|
161
|
+
if (relativePath === MUSTFLOW_RELATIVE_PATH ||
|
|
162
|
+
relativePath === PREFERENCES_RELATIVE_PATH ||
|
|
163
|
+
relativePath === TECHNOLOGY_RELATIVE_PATH) {
|
|
164
|
+
return 'configuration';
|
|
165
|
+
}
|
|
166
|
+
if (relativePath?.startsWith('.mustflow/docs/')) {
|
|
167
|
+
return 'workflow_policy';
|
|
168
|
+
}
|
|
169
|
+
if (relativePath?.startsWith('.mustflow/skills/') && relativePath.endsWith('/SKILL.md')) {
|
|
170
|
+
return 'procedure';
|
|
171
|
+
}
|
|
172
|
+
if (relativePath?.startsWith('.mustflow/context/')) {
|
|
173
|
+
return 'contextual';
|
|
174
|
+
}
|
|
175
|
+
if (sourceKind === 'generated_cache') {
|
|
176
|
+
return 'generated';
|
|
177
|
+
}
|
|
178
|
+
if (sourceKind === 'generated_state') {
|
|
179
|
+
return 'evidence_only';
|
|
180
|
+
}
|
|
181
|
+
if (sourceKind === 'runtime_volatile') {
|
|
182
|
+
return 'volatile';
|
|
183
|
+
}
|
|
184
|
+
if (sourceKind === 'source_placeholder') {
|
|
185
|
+
return 'hint';
|
|
186
|
+
}
|
|
187
|
+
return 'unknown';
|
|
188
|
+
}
|
|
189
|
+
function createContextTrust(input) {
|
|
190
|
+
const normalizedPath = input.relativePath === null ? null : toPosixPath(input.relativePath);
|
|
191
|
+
const sourceKind = trustSourceKind(normalizedPath, input.sourceKind ?? null);
|
|
192
|
+
const authority = trustAuthority(normalizedPath, sourceKind);
|
|
193
|
+
const freshness = sourceKind === 'runtime_volatile'
|
|
194
|
+
? 'runtime_volatile'
|
|
195
|
+
: input.contentHash !== null
|
|
196
|
+
? 'hash_verified'
|
|
197
|
+
: input.exists === false
|
|
198
|
+
? 'missing'
|
|
199
|
+
: sourceKind === 'source_placeholder'
|
|
200
|
+
? 'dynamic'
|
|
201
|
+
: 'unchecked';
|
|
202
|
+
return {
|
|
203
|
+
source_kind: sourceKind,
|
|
204
|
+
authority,
|
|
205
|
+
cache_layer: input.cacheLayer,
|
|
206
|
+
freshness,
|
|
207
|
+
content_hash: input.contentHash,
|
|
208
|
+
can_instruct_agent: authority === 'binding' ||
|
|
209
|
+
authority === 'workflow_policy' ||
|
|
210
|
+
authority === 'configuration' ||
|
|
211
|
+
authority === 'command_contract' ||
|
|
212
|
+
authority === 'procedure' ||
|
|
213
|
+
authority === 'contextual',
|
|
214
|
+
grants_command_authority: authority === 'command_contract',
|
|
215
|
+
notes: input.notes ?? [],
|
|
216
|
+
};
|
|
217
|
+
}
|
|
114
218
|
function estimateTokens(renderedBytes) {
|
|
115
219
|
return Math.ceil(renderedBytes / 4);
|
|
116
220
|
}
|
|
@@ -396,10 +500,19 @@ function readStablePromptCacheLayer(projectRoot, mustflow) {
|
|
|
396
500
|
const read = readOptionalStringArray(layer, 'read') ?? [...DEFAULT_PROMPT_CACHE_STABLE_READ];
|
|
397
501
|
const documents = read.map((relativePath) => {
|
|
398
502
|
const content = safeRead(projectRoot, relativePath);
|
|
503
|
+
const contentHash = content === null ? null : sha256(content);
|
|
504
|
+
const normalizedPath = toPosixPath(relativePath);
|
|
399
505
|
return {
|
|
400
|
-
path:
|
|
506
|
+
path: normalizedPath,
|
|
401
507
|
exists: content !== null,
|
|
402
|
-
content_hash:
|
|
508
|
+
content_hash: contentHash,
|
|
509
|
+
trust: createContextTrust({
|
|
510
|
+
relativePath: normalizedPath,
|
|
511
|
+
cacheLayer: 'stable',
|
|
512
|
+
contentHash,
|
|
513
|
+
exists: content !== null,
|
|
514
|
+
sourceKind: 'file_reference',
|
|
515
|
+
}),
|
|
403
516
|
};
|
|
404
517
|
});
|
|
405
518
|
const cacheMaterial = documents
|
|
@@ -515,6 +628,7 @@ function readStablePromptBundleLayer(projectRoot, mustflow) {
|
|
|
515
628
|
blocks: read.map((relativePath, index) => {
|
|
516
629
|
const path = toPosixPath(relativePath);
|
|
517
630
|
const content = safeRead(projectRoot, relativePath);
|
|
631
|
+
const contentHash = content === null ? null : sha256(content);
|
|
518
632
|
const renderedBytes = content === null ? null : measurePromptCacheReferenceBlockBytes(relativePath, content);
|
|
519
633
|
const issue = content === null ? `stable prefix document is missing: ${path}` : null;
|
|
520
634
|
return {
|
|
@@ -527,12 +641,19 @@ function readStablePromptBundleLayer(projectRoot, mustflow) {
|
|
|
527
641
|
source_kind: 'file_reference',
|
|
528
642
|
selection_policy: 'always_rendered',
|
|
529
643
|
content_included: false,
|
|
530
|
-
content_hash:
|
|
644
|
+
content_hash: contentHash,
|
|
531
645
|
rendered_digest: content === null ? null : renderedDigest(relativePath, content),
|
|
532
646
|
rendered_bytes: renderedBytes,
|
|
533
647
|
estimated_tokens: renderedBytes === null ? null : estimateTokens(renderedBytes),
|
|
534
648
|
cacheability: 'provider_prefix_candidate',
|
|
535
649
|
reload_on: ['stable_file_hash_changed'],
|
|
650
|
+
trust: createContextTrust({
|
|
651
|
+
relativePath: path,
|
|
652
|
+
cacheLayer: 'stable',
|
|
653
|
+
contentHash,
|
|
654
|
+
exists: content !== null,
|
|
655
|
+
sourceKind: 'file_reference',
|
|
656
|
+
}),
|
|
536
657
|
issue,
|
|
537
658
|
};
|
|
538
659
|
}),
|
|
@@ -589,6 +710,14 @@ function readTaskPromptBundleLayer(projectRoot, mustflow, routeReport) {
|
|
|
589
710
|
estimated_tokens: renderedBytes === null ? null : estimateTokens(renderedBytes),
|
|
590
711
|
cacheability: taskSourceCacheability(source, selectionPolicy),
|
|
591
712
|
reload_on: taskSourceReloadOn(source, selectionPolicy),
|
|
713
|
+
trust: createContextTrust({
|
|
714
|
+
relativePath: routeCandidateContent !== null ? SKILL_ROUTE_CANDIDATES_VIRTUAL_PATH : referencePath,
|
|
715
|
+
cacheLayer: 'task',
|
|
716
|
+
contentHash,
|
|
717
|
+
exists: routeCandidateContent !== null || content !== null ? true : sourceKind === 'file_reference' ? false : null,
|
|
718
|
+
sourceKind,
|
|
719
|
+
notes: source === 'skill_route_candidates' ? ['derived from route resolver input'] : [],
|
|
720
|
+
}),
|
|
592
721
|
issue,
|
|
593
722
|
};
|
|
594
723
|
if (source !== 'matching_skill') {
|
|
@@ -597,6 +726,7 @@ function readTaskPromptBundleLayer(projectRoot, mustflow, routeReport) {
|
|
|
597
726
|
const selectedBlocks = selectedSkillPaths.map((skillPath, selectedIndex) => {
|
|
598
727
|
const normalizedPath = toPosixPath(skillPath);
|
|
599
728
|
const skillContent = safeRead(projectRoot, normalizedPath);
|
|
729
|
+
const skillContentHash = skillContent === null ? null : sha256(skillContent);
|
|
600
730
|
const skillRenderedBytes = skillContent === null ? null : measurePromptCacheReferenceBlockBytes(normalizedPath, skillContent);
|
|
601
731
|
const skillIssue = skillContent === null
|
|
602
732
|
? taskSourceIssue(normalizedPath, 'hash_only_deferred')
|
|
@@ -611,12 +741,20 @@ function readTaskPromptBundleLayer(projectRoot, mustflow, routeReport) {
|
|
|
611
741
|
source_kind: skillContent === null ? 'dynamic_selection' : 'file_reference',
|
|
612
742
|
selection_policy: 'selected_at_runtime',
|
|
613
743
|
content_included: false,
|
|
614
|
-
content_hash:
|
|
744
|
+
content_hash: skillContentHash,
|
|
615
745
|
rendered_digest: skillContent === null ? null : renderedDigest(normalizedPath, skillContent),
|
|
616
746
|
rendered_bytes: skillRenderedBytes,
|
|
617
747
|
estimated_tokens: skillRenderedBytes === null ? null : estimateTokens(skillRenderedBytes),
|
|
618
748
|
cacheability: 'runtime_selection',
|
|
619
749
|
reload_on: ['route_resolution_changed', 'source_file_hash_changed'],
|
|
750
|
+
trust: createContextTrust({
|
|
751
|
+
relativePath: normalizedPath,
|
|
752
|
+
cacheLayer: 'task',
|
|
753
|
+
contentHash: skillContentHash,
|
|
754
|
+
exists: skillContent !== null,
|
|
755
|
+
sourceKind: skillContent === null ? 'dynamic_selection' : 'file_reference',
|
|
756
|
+
notes: ['selected by task route resolver'],
|
|
757
|
+
}),
|
|
620
758
|
issue: skillIssue,
|
|
621
759
|
};
|
|
622
760
|
});
|
|
@@ -646,6 +784,14 @@ function readVolatilePromptBundleLayer(mustflow) {
|
|
|
646
784
|
estimated_tokens: null,
|
|
647
785
|
cacheability: 'volatile_suffix',
|
|
648
786
|
reload_on: ['volatile_state_changed'],
|
|
787
|
+
trust: createContextTrust({
|
|
788
|
+
relativePath: null,
|
|
789
|
+
cacheLayer: 'volatile',
|
|
790
|
+
contentHash: null,
|
|
791
|
+
exists: null,
|
|
792
|
+
sourceKind: 'runtime_volatile',
|
|
793
|
+
notes: ['not rendered in stable or task prompt cache layers'],
|
|
794
|
+
}),
|
|
649
795
|
issue,
|
|
650
796
|
})),
|
|
651
797
|
};
|
|
@@ -663,10 +809,27 @@ function readPromptBundle(projectRoot, mustflow, profile, routeReport = null) {
|
|
|
663
809
|
}
|
|
664
810
|
const blocks = layers.flatMap((layer) => layer.blocks);
|
|
665
811
|
const shapeMaterial = blocks
|
|
666
|
-
.map((block) => [
|
|
812
|
+
.map((block) => [
|
|
813
|
+
block.cache_layer,
|
|
814
|
+
block.order,
|
|
815
|
+
block.kind,
|
|
816
|
+
block.path ?? '',
|
|
817
|
+
block.source ?? '',
|
|
818
|
+
block.selection_policy,
|
|
819
|
+
block.cacheability,
|
|
820
|
+
block.trust.authority,
|
|
821
|
+
block.trust.source_kind,
|
|
822
|
+
].join('\0'))
|
|
667
823
|
.join('\n');
|
|
668
824
|
const bundleMaterial = blocks
|
|
669
|
-
.map((block) => [
|
|
825
|
+
.map((block) => [
|
|
826
|
+
block.id,
|
|
827
|
+
block.content_hash ?? '',
|
|
828
|
+
block.rendered_digest ?? '',
|
|
829
|
+
block.trust.freshness,
|
|
830
|
+
block.trust.content_hash ?? '',
|
|
831
|
+
block.issue ?? '',
|
|
832
|
+
].join('\0'))
|
|
670
833
|
.join('\n');
|
|
671
834
|
return {
|
|
672
835
|
schema_version: '1',
|
|
@@ -744,6 +907,9 @@ function blockLocation(block) {
|
|
|
744
907
|
function compareStringArray(left, right) {
|
|
745
908
|
return left.length === right.length && left.every((entry, index) => entry === right[index]);
|
|
746
909
|
}
|
|
910
|
+
function contextTrustMatches(left, right) {
|
|
911
|
+
return JSON.stringify(left ?? null) === JSON.stringify(right ?? null);
|
|
912
|
+
}
|
|
747
913
|
function changedBlockFields(current, baseline) {
|
|
748
914
|
const fields = [];
|
|
749
915
|
if (current.id !== baseline.id) {
|
|
@@ -779,6 +945,9 @@ function changedBlockFields(current, baseline) {
|
|
|
779
945
|
if (current.cacheability !== baseline.cacheability) {
|
|
780
946
|
fields.push('cacheability');
|
|
781
947
|
}
|
|
948
|
+
if (!contextTrustMatches(current.trust, baseline.trust)) {
|
|
949
|
+
fields.push('trust');
|
|
950
|
+
}
|
|
782
951
|
if (!compareStringArray(current.reload_on, baseline.reload_on)) {
|
|
783
952
|
fields.push('reload_on');
|
|
784
953
|
}
|
|
@@ -95,6 +95,12 @@ export const COMMAND_DEFINITIONS = [
|
|
|
95
95
|
summaryKey: 'command.quality.summary',
|
|
96
96
|
loadRunner: async () => (await import('../commands/quality.js')).runQuality,
|
|
97
97
|
},
|
|
98
|
+
{
|
|
99
|
+
id: 'script-pack',
|
|
100
|
+
usage: 'mf script-pack',
|
|
101
|
+
summaryKey: 'command.scriptPack.summary',
|
|
102
|
+
loadRunner: async () => (await import('../commands/script-pack.js')).runScriptPack,
|
|
103
|
+
},
|
|
98
104
|
{
|
|
99
105
|
id: 'run',
|
|
100
106
|
usage: 'mf run',
|
|
@@ -448,6 +448,7 @@ function createDashboardHarnessReport(statusValue, docsReviewValue) {
|
|
|
448
448
|
verification: {
|
|
449
449
|
completion_verdict: completionVerdict,
|
|
450
450
|
evidence_model: evidenceModel,
|
|
451
|
+
conflict_ledger: evidenceModel.conflict_ledger,
|
|
451
452
|
changed_file_count: changedFileCount,
|
|
452
453
|
changed_surfaces: changedSurfaces,
|
|
453
454
|
decision_graph_summary: decisionGraphSummary,
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
function scriptRef(packId, scriptId) {
|
|
2
|
+
return `${packId}/${scriptId}`;
|
|
3
|
+
}
|
|
4
|
+
export const SCRIPT_PACKS = [
|
|
5
|
+
{
|
|
6
|
+
id: 'core',
|
|
7
|
+
summaryKey: 'scriptPack.pack.core.summary',
|
|
8
|
+
scripts: [
|
|
9
|
+
{
|
|
10
|
+
packId: 'core',
|
|
11
|
+
id: 'text-budget',
|
|
12
|
+
ref: scriptRef('core', 'text-budget'),
|
|
13
|
+
usage: 'mf script-pack run core/text-budget check <path...> [options]',
|
|
14
|
+
summaryKey: 'scriptPack.script.textBudget.summary',
|
|
15
|
+
actions: ['check'],
|
|
16
|
+
reportSchemaFile: 'text-budget-report.schema.json',
|
|
17
|
+
loadRunner: async () => (await import('../script-packs/core-text-budget.js')).runCoreTextBudgetScript,
|
|
18
|
+
},
|
|
19
|
+
],
|
|
20
|
+
},
|
|
21
|
+
];
|
|
22
|
+
export function listScriptPackScripts() {
|
|
23
|
+
return SCRIPT_PACKS.flatMap((pack) => pack.scripts);
|
|
24
|
+
}
|
|
25
|
+
export function findScriptPackScript(ref) {
|
|
26
|
+
return listScriptPackScripts().find((script) => script.ref === ref);
|
|
27
|
+
}
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
import { printUsageError, renderHelp } from '../lib/cli-output.js';
|
|
2
|
+
import { t } from '../lib/i18n.js';
|
|
3
|
+
import { formatCliOptionParseError, getParsedCliStringOption, hasCliOptionToken, hasParsedCliOption, parseCliOptions, } from '../lib/option-parser.js';
|
|
4
|
+
import { resolveMustflowRoot } from '../lib/project-root.js';
|
|
5
|
+
import { inspectTextBudget, TEXT_BUDGET_SCRIPT_REF, TEXT_BUDGET_UNITS, } from '../../core/text-budget.js';
|
|
6
|
+
const TEXT_BUDGET_OPTIONS = [
|
|
7
|
+
{ name: '--json', kind: 'boolean' },
|
|
8
|
+
{ name: '--min', kind: 'string' },
|
|
9
|
+
{ name: '--max', kind: 'string' },
|
|
10
|
+
{ name: '--exact', kind: 'string' },
|
|
11
|
+
{ name: '--unit', kind: 'string' },
|
|
12
|
+
{ name: '--json-pointer', kind: 'string' },
|
|
13
|
+
];
|
|
14
|
+
export function getCoreTextBudgetHelp(lang = 'en') {
|
|
15
|
+
return renderHelp({
|
|
16
|
+
usage: 'mf script-pack run core/text-budget check <path...> [options]',
|
|
17
|
+
summary: t(lang, 'textBudget.help.summary'),
|
|
18
|
+
options: [
|
|
19
|
+
{ label: '--min <count>', description: t(lang, 'textBudget.help.option.min') },
|
|
20
|
+
{ label: '--max <count>', description: t(lang, 'textBudget.help.option.max') },
|
|
21
|
+
{ label: '--exact <count>', description: t(lang, 'textBudget.help.option.exact') },
|
|
22
|
+
{ label: '--unit <unit>', description: t(lang, 'textBudget.help.option.unit') },
|
|
23
|
+
{ label: '--json-pointer <pointer>', description: t(lang, 'textBudget.help.option.jsonPointer') },
|
|
24
|
+
{ label: '--json', description: t(lang, 'cli.option.json') },
|
|
25
|
+
{ label: '-h, --help', description: t(lang, 'cli.option.help') },
|
|
26
|
+
],
|
|
27
|
+
examples: [
|
|
28
|
+
'mf script-pack run core/text-budget check README.md --max 5000',
|
|
29
|
+
'mf script-pack run core/text-budget check package.json --json-pointer /description --max 80 --json',
|
|
30
|
+
'mf script-pack run core/text-budget check docs/intro.md --min 20 --max 120 --unit word',
|
|
31
|
+
],
|
|
32
|
+
exitCodes: [
|
|
33
|
+
{ label: '0', description: t(lang, 'textBudget.help.exit.ok') },
|
|
34
|
+
{ label: '1', description: t(lang, 'textBudget.help.exit.fail') },
|
|
35
|
+
],
|
|
36
|
+
}, lang);
|
|
37
|
+
}
|
|
38
|
+
function parseNonNegativeInteger(value, option, lang) {
|
|
39
|
+
if (value === null) {
|
|
40
|
+
return { value: null };
|
|
41
|
+
}
|
|
42
|
+
if (!/^(?:0|[1-9]\d*)$/u.test(value)) {
|
|
43
|
+
return { value: null, error: t(lang, 'textBudget.error.invalidNumber', { option, value }) };
|
|
44
|
+
}
|
|
45
|
+
const parsed = Number(value);
|
|
46
|
+
if (!Number.isSafeInteger(parsed)) {
|
|
47
|
+
return { value: null, error: t(lang, 'textBudget.error.invalidNumber', { option, value }) };
|
|
48
|
+
}
|
|
49
|
+
return { value: parsed };
|
|
50
|
+
}
|
|
51
|
+
function parseTextBudgetUnit(value, lang) {
|
|
52
|
+
if (value === null) {
|
|
53
|
+
return { value: 'grapheme' };
|
|
54
|
+
}
|
|
55
|
+
if (TEXT_BUDGET_UNITS.includes(value)) {
|
|
56
|
+
return { value: value };
|
|
57
|
+
}
|
|
58
|
+
return {
|
|
59
|
+
value: 'grapheme',
|
|
60
|
+
error: t(lang, 'textBudget.error.invalidUnit', { unit: value, allowed: TEXT_BUDGET_UNITS.join(', ') }),
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
function parseTextBudgetOptions(args, lang) {
|
|
64
|
+
const [action, ...rest] = args;
|
|
65
|
+
const parsed = parseCliOptions(rest, TEXT_BUDGET_OPTIONS, {
|
|
66
|
+
allowPositionals: true,
|
|
67
|
+
allowEmptyStringValues: true,
|
|
68
|
+
});
|
|
69
|
+
const json = hasParsedCliOption(parsed, '--json');
|
|
70
|
+
const min = parseNonNegativeInteger(getParsedCliStringOption(parsed, '--min'), '--min', lang);
|
|
71
|
+
const max = parseNonNegativeInteger(getParsedCliStringOption(parsed, '--max'), '--max', lang);
|
|
72
|
+
const exact = parseNonNegativeInteger(getParsedCliStringOption(parsed, '--exact'), '--exact', lang);
|
|
73
|
+
const unit = parseTextBudgetUnit(getParsedCliStringOption(parsed, '--unit'), lang);
|
|
74
|
+
const jsonPointer = getParsedCliStringOption(parsed, '--json-pointer');
|
|
75
|
+
if (action !== 'check') {
|
|
76
|
+
return {
|
|
77
|
+
action: 'check',
|
|
78
|
+
json,
|
|
79
|
+
paths: parsed.positionals,
|
|
80
|
+
min: min.value,
|
|
81
|
+
max: max.value,
|
|
82
|
+
exact: exact.value,
|
|
83
|
+
unit: unit.value,
|
|
84
|
+
jsonPointer,
|
|
85
|
+
error: action ? t(lang, 'textBudget.error.unknownAction', { action }) : t(lang, 'textBudget.error.missingAction'),
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
if (parsed.error) {
|
|
89
|
+
return {
|
|
90
|
+
action,
|
|
91
|
+
json,
|
|
92
|
+
paths: parsed.positionals,
|
|
93
|
+
min: min.value,
|
|
94
|
+
max: max.value,
|
|
95
|
+
exact: exact.value,
|
|
96
|
+
unit: unit.value,
|
|
97
|
+
jsonPointer,
|
|
98
|
+
error: formatCliOptionParseError(parsed.error, lang),
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
for (const candidate of [min, max, exact, unit]) {
|
|
102
|
+
if (candidate.error) {
|
|
103
|
+
return {
|
|
104
|
+
action,
|
|
105
|
+
json,
|
|
106
|
+
paths: parsed.positionals,
|
|
107
|
+
min: min.value,
|
|
108
|
+
max: max.value,
|
|
109
|
+
exact: exact.value,
|
|
110
|
+
unit: unit.value,
|
|
111
|
+
jsonPointer,
|
|
112
|
+
error: candidate.error,
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
if (parsed.positionals.length === 0) {
|
|
117
|
+
return {
|
|
118
|
+
action,
|
|
119
|
+
json,
|
|
120
|
+
paths: parsed.positionals,
|
|
121
|
+
min: min.value,
|
|
122
|
+
max: max.value,
|
|
123
|
+
exact: exact.value,
|
|
124
|
+
unit: unit.value,
|
|
125
|
+
jsonPointer,
|
|
126
|
+
error: t(lang, 'textBudget.error.missingPath'),
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
if (exact.value !== null && (min.value !== null || max.value !== null)) {
|
|
130
|
+
return {
|
|
131
|
+
action,
|
|
132
|
+
json,
|
|
133
|
+
paths: parsed.positionals,
|
|
134
|
+
min: min.value,
|
|
135
|
+
max: max.value,
|
|
136
|
+
exact: exact.value,
|
|
137
|
+
unit: unit.value,
|
|
138
|
+
jsonPointer,
|
|
139
|
+
error: t(lang, 'textBudget.error.exactConflict'),
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
if (exact.value === null && min.value === null && max.value === null) {
|
|
143
|
+
return {
|
|
144
|
+
action,
|
|
145
|
+
json,
|
|
146
|
+
paths: parsed.positionals,
|
|
147
|
+
min: min.value,
|
|
148
|
+
max: max.value,
|
|
149
|
+
exact: exact.value,
|
|
150
|
+
unit: unit.value,
|
|
151
|
+
jsonPointer,
|
|
152
|
+
error: t(lang, 'textBudget.error.missingBudget'),
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
if (min.value !== null && max.value !== null && min.value > max.value) {
|
|
156
|
+
return {
|
|
157
|
+
action,
|
|
158
|
+
json,
|
|
159
|
+
paths: parsed.positionals,
|
|
160
|
+
min: min.value,
|
|
161
|
+
max: max.value,
|
|
162
|
+
exact: exact.value,
|
|
163
|
+
unit: unit.value,
|
|
164
|
+
jsonPointer,
|
|
165
|
+
error: t(lang, 'textBudget.error.minGreaterThanMax'),
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
return {
|
|
169
|
+
action,
|
|
170
|
+
json,
|
|
171
|
+
paths: parsed.positionals,
|
|
172
|
+
min: min.value,
|
|
173
|
+
max: max.value,
|
|
174
|
+
exact: exact.value,
|
|
175
|
+
unit: unit.value,
|
|
176
|
+
jsonPointer,
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
function renderBudget(report) {
|
|
180
|
+
if (report.policy.exact !== null) {
|
|
181
|
+
return `exact ${report.policy.exact} ${report.policy.unit}`;
|
|
182
|
+
}
|
|
183
|
+
const lower = report.policy.min === null ? null : `min ${report.policy.min}`;
|
|
184
|
+
const upper = report.policy.max === null ? null : `max ${report.policy.max}`;
|
|
185
|
+
return [lower, upper].filter((part) => part !== null).join(', ') + ` ${report.policy.unit}`;
|
|
186
|
+
}
|
|
187
|
+
function renderTextBudgetSummary(report, lang) {
|
|
188
|
+
const lines = [
|
|
189
|
+
t(lang, 'textBudget.title'),
|
|
190
|
+
`${t(lang, 'scriptPack.label.script')}: ${TEXT_BUDGET_SCRIPT_REF}`,
|
|
191
|
+
`${t(lang, 'label.status')}: ${report.status}`,
|
|
192
|
+
`${t(lang, 'textBudget.label.budget')}: ${renderBudget(report)}`,
|
|
193
|
+
`${t(lang, 'textBudget.label.checkedTargets')}: ${report.metrics.length}`,
|
|
194
|
+
`${t(lang, 'textBudget.label.findings')}: ${report.findings.length}`,
|
|
195
|
+
];
|
|
196
|
+
if (report.metrics.length > 0) {
|
|
197
|
+
lines.push(t(lang, 'textBudget.label.metrics'));
|
|
198
|
+
for (const metric of report.metrics) {
|
|
199
|
+
const pointer = metric.json_pointer === null ? '' : ` ${metric.json_pointer}`;
|
|
200
|
+
lines.push(`- ${metric.path}${pointer}: ${metric.value} ${metric.unit}`);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
if (report.findings.length > 0) {
|
|
204
|
+
lines.push(t(lang, 'textBudget.label.findings'));
|
|
205
|
+
for (const finding of report.findings) {
|
|
206
|
+
lines.push(`- ${finding.path}: ${finding.code} (${finding.message})`);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
if (report.issues.length > 0) {
|
|
210
|
+
lines.push(t(lang, 'textBudget.label.issues'), ...report.issues.map((issue) => `- ${issue}`));
|
|
211
|
+
}
|
|
212
|
+
if (report.findings.length === 0 && report.issues.length === 0) {
|
|
213
|
+
lines.push(t(lang, 'textBudget.clean'));
|
|
214
|
+
}
|
|
215
|
+
return lines.join('\n');
|
|
216
|
+
}
|
|
217
|
+
export function runCoreTextBudgetScript(args, reporter, lang = 'en') {
|
|
218
|
+
if (hasCliOptionToken(args, '--help', ['-h'])) {
|
|
219
|
+
reporter.stdout(getCoreTextBudgetHelp(lang));
|
|
220
|
+
return 0;
|
|
221
|
+
}
|
|
222
|
+
const options = parseTextBudgetOptions(args, lang);
|
|
223
|
+
if (options.error) {
|
|
224
|
+
printUsageError(reporter, options.error, 'mf script-pack run core/text-budget --help', getCoreTextBudgetHelp(lang), lang);
|
|
225
|
+
return 1;
|
|
226
|
+
}
|
|
227
|
+
const report = inspectTextBudget(resolveMustflowRoot(), {
|
|
228
|
+
paths: options.paths,
|
|
229
|
+
min: options.min,
|
|
230
|
+
max: options.max,
|
|
231
|
+
exact: options.exact,
|
|
232
|
+
unit: options.unit,
|
|
233
|
+
jsonPointer: options.jsonPointer,
|
|
234
|
+
});
|
|
235
|
+
if (options.json) {
|
|
236
|
+
reporter.stdout(JSON.stringify(report, null, 2));
|
|
237
|
+
return report.ok ? 0 : 1;
|
|
238
|
+
}
|
|
239
|
+
reporter.stdout(renderTextBudgetSummary(report, lang));
|
|
240
|
+
return report.ok ? 0 : 1;
|
|
241
|
+
}
|