mustflow 1.31.0 → 2.16.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 +23 -9
- package/dist/cli/commands/classify.js +61 -6
- package/dist/cli/commands/contract-lint.js +13 -4
- package/dist/cli/commands/dashboard.js +77 -2
- package/dist/cli/commands/explain-verify.js +11 -1
- package/dist/cli/commands/index.js +14 -0
- package/dist/cli/commands/run.js +4 -1
- package/dist/cli/commands/verify.js +986 -43
- package/dist/cli/i18n/en.js +61 -10
- package/dist/cli/i18n/es.js +61 -10
- package/dist/cli/i18n/fr.js +61 -10
- package/dist/cli/i18n/hi.js +61 -10
- package/dist/cli/i18n/ko.js +61 -10
- package/dist/cli/i18n/zh.js +61 -10
- package/dist/cli/lib/dashboard-export.js +62 -12
- package/dist/cli/lib/dashboard-html/client-script.js +1936 -0
- package/dist/cli/lib/dashboard-html/locale-bootstrap.js +8 -0
- package/dist/cli/lib/dashboard-html/styles.js +572 -0
- package/dist/cli/lib/dashboard-html/template.js +134 -0
- package/dist/cli/lib/dashboard-html/types.js +1 -0
- package/dist/cli/lib/dashboard-html.js +1 -1907
- package/dist/cli/lib/dashboard-locale.js +37 -0
- package/dist/cli/lib/local-index/constants.js +48 -0
- package/dist/cli/lib/local-index/index.js +2951 -0
- package/dist/cli/lib/local-index/sql.js +15 -0
- package/dist/cli/lib/local-index/types.js +1 -0
- package/dist/cli/lib/local-index.js +1 -1911
- package/dist/cli/lib/run-plan.js +76 -1
- package/dist/cli/lib/templates.js +18 -1
- package/dist/cli/lib/validation/command-intents.js +11 -0
- package/dist/cli/lib/validation/constants.js +238 -0
- package/dist/cli/lib/validation/index.js +1384 -0
- package/dist/cli/lib/validation/primitives.js +198 -0
- package/dist/cli/lib/validation/test-selection.js +95 -0
- package/dist/cli/lib/validation/types.js +1 -0
- package/dist/cli/lib/validation.js +1 -1770
- package/dist/core/check-issues.js +6 -0
- package/dist/core/completion-verdict.js +341 -0
- package/dist/core/contract-lint.js +221 -6
- package/dist/core/external-evidence.js +9 -0
- package/dist/core/public-json-contracts.js +21 -0
- package/dist/core/repeated-failure.js +179 -0
- package/dist/core/repro-evidence.js +134 -0
- package/dist/core/scope-risk.js +64 -0
- package/dist/core/skill-route-alignment.js +20 -0
- package/dist/core/source-anchor-status.js +4 -1
- package/dist/core/test-selection.js +3 -0
- package/dist/core/validation-ratchet.js +196 -0
- package/dist/core/verification-evidence.js +249 -0
- package/examples/README.md +12 -4
- package/package.json +3 -3
- package/schemas/README.md +13 -3
- package/schemas/change-verification-report.schema.json +16 -2
- package/schemas/commands.schema.json +4 -0
- package/schemas/contract-lint-report.schema.json +29 -0
- package/schemas/dashboard-export.schema.json +310 -0
- package/schemas/explain-report.schema.json +173 -1
- package/schemas/latest-run-pointer.schema.json +601 -0
- package/schemas/run-receipt.schema.json +4 -0
- package/schemas/test-selection.schema.json +81 -0
- package/schemas/verify-report.schema.json +578 -1
- package/schemas/verify-run-manifest.schema.json +627 -0
- package/templates/default/i18n.toml +1 -1
- package/templates/default/locales/en/.mustflow/skills/INDEX.md +124 -29
- package/templates/default/locales/en/.mustflow/skills/routes.toml +289 -0
- package/templates/default/manifest.toml +29 -2
package/README.md
CHANGED
|
@@ -14,10 +14,24 @@ The core concept is straightforward: place `AGENTS.md` at the project root and k
|
|
|
14
14
|
- Security: [SECURITY.md](https://github.com/0disoft/mustflow/blob/main/SECURITY.md)
|
|
15
15
|
- Changelog: [CHANGELOG.md](https://github.com/0disoft/mustflow/blob/main/CHANGELOG.md)
|
|
16
16
|
|
|
17
|
+
## Choose your path
|
|
18
|
+
|
|
19
|
+
- Use mustflow in your repository: start with [Quick start](#quick-start), then review the [no-guessing workflow](#no-guessing-workflow) and [`examples/minimal-js/`](examples/minimal-js/).
|
|
20
|
+
- Contribute to mustflow: read [CONTRIBUTING.md](CONTRIBUTING.md), then run only configured command intents from [`.mustflow/config/commands.toml`](.mustflow/config/commands.toml).
|
|
21
|
+
- Build an AI coding tool or agent harness: use `AGENTS.md` and `mf context --json` for repository context, then consume JSON output and schemas from `mf classify`, `mf verify`, `mf run`, `mf dashboard`, and [`schemas/`](schemas/).
|
|
22
|
+
|
|
17
23
|
## No-guessing workflow
|
|
18
24
|
|
|
19
25
|
The initial mustflow path is deliberately narrow.
|
|
20
26
|
|
|
27
|
+
Authority stays narrow:
|
|
28
|
+
|
|
29
|
+
- Current user instructions define the task goal unless they are unsafe.
|
|
30
|
+
- Host safety, sandbox, and approval gates still apply.
|
|
31
|
+
- Repository work rules come from the nearest `AGENTS.md` and `.mustflow/config/*.toml`.
|
|
32
|
+
- Command execution authority comes only from `.mustflow/config/commands.toml`.
|
|
33
|
+
- Skills, context files, preferences, generated maps, search results, cache, and state files guide or explain work. They do not grant command permission.
|
|
34
|
+
|
|
21
35
|
```sh
|
|
22
36
|
npm install -D mustflow
|
|
23
37
|
npx mf init --yes
|
|
@@ -27,9 +41,9 @@ npx mf check --strict
|
|
|
27
41
|
After changes to code, templates, schemas, or documentation, classify the changed paths and review the verification plan before running any commands.
|
|
28
42
|
|
|
29
43
|
```sh
|
|
30
|
-
npx mf classify --changed --
|
|
31
|
-
npx mf verify --from-
|
|
32
|
-
npx mf verify --from-
|
|
44
|
+
npx mf classify --changed --write .mustflow/state/change-classification.json
|
|
45
|
+
npx mf verify --from-classification .mustflow/state/change-classification.json --plan-only --json
|
|
46
|
+
npx mf verify --from-classification .mustflow/state/change-classification.json --json
|
|
33
47
|
```
|
|
34
48
|
|
|
35
49
|
The plan is based on change classification and the `required_after` metadata in `.mustflow/config/commands.toml`. A command runs only if its declared intent is configured, one-shot, agent-allowed, closed-stdin, bounded by a timeout, and backed by an explicit command source.
|
|
@@ -239,9 +253,9 @@ If a project already has optional root Markdown files such as `README.md`, `PROJ
|
|
|
239
253
|
npx mf init --yes
|
|
240
254
|
npx mf doctor
|
|
241
255
|
npx mf check --strict
|
|
242
|
-
npx mf classify --changed --
|
|
243
|
-
npx mf verify --from-
|
|
244
|
-
npx mf verify --from-
|
|
256
|
+
npx mf classify --changed --write .mustflow/state/change-classification.json
|
|
257
|
+
npx mf verify --from-classification .mustflow/state/change-classification.json --plan-only --json
|
|
258
|
+
npx mf verify --from-classification .mustflow/state/change-classification.json --json
|
|
245
259
|
```
|
|
246
260
|
|
|
247
261
|
Create the optional local search index if search capabilities are needed. Run the normal command
|
|
@@ -294,8 +308,8 @@ mf run mustflow_update_apply
|
|
|
294
308
|
| `mf check` | Validate mustflow files, TOML configuration, and skill document shape. |
|
|
295
309
|
| `mf check --strict` | Run additional safety checks for document identity, authority/lifecycle metadata, skill index/body alignment, skill metadata, command boundaries, version-source discovery, retention policy, output limits, raw logs, and secret-like context. |
|
|
296
310
|
| `mf adapters status` | Inspect existing host-specific instruction and adapter files without generating adapter files or granting command authority. |
|
|
297
|
-
| `mf classify --changed` | Classify changed paths, public surfaces, and validation reasons
|
|
298
|
-
| `mf contract-lint` | Inspect `.mustflow/config/commands.toml` for command-contract errors and warnings without running commands. |
|
|
311
|
+
| `mf classify --changed` | Classify changed paths, public surfaces, and validation reasons. Add `--write <path>` to save the classification report. |
|
|
312
|
+
| `mf contract-lint` | Inspect `.mustflow/config/commands.toml` for command-contract errors and warnings without running commands. Add `--suggest` to print non-runnable candidate snippets from existing command files. |
|
|
299
313
|
| `mf doctor` | Inspect the current mustflow root without writing files. |
|
|
300
314
|
| `mf docs review list` | Show documents still waiting for prose review after agent edits. |
|
|
301
315
|
| `mf docs review add <path>` | Add or refresh a document review queue entry. |
|
|
@@ -356,7 +370,7 @@ npx mf init --product-source-locale en --product-locale ko-KR
|
|
|
356
370
|
npx mf init --set git.auto_commit=true
|
|
357
371
|
```
|
|
358
372
|
|
|
359
|
-
- `--profile`: Project profile. The default is `minimal`. Profiles also select the installed skill surface: `minimal` installs core everyday coding skills,
|
|
373
|
+
- `--profile`: Project profile. The default is `minimal`. Profiles also select the installed skill surface: `minimal` installs core everyday coding skills, `patterns` adds architecture-pattern procedures, and `oss`, `team`, `product`, and `library` add opt-in skill groups without removing optional skill files from the package.
|
|
360
374
|
- `--locale`: Installed mustflow document language. The default template currently supports `en`, `ko`, `zh`, `es`, `fr`, and `hi`. The default template includes localized documents for all these locales.
|
|
361
375
|
- `--agent-lang`: Default language for final agent reports.
|
|
362
376
|
- `--interactive`: Choose init settings via prompts.
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { mkdirSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
1
3
|
import { createChangeClassificationReport, } from '../../core/change-classification.js';
|
|
2
4
|
import { printUsageError, renderHelp } from '../lib/cli-output.js';
|
|
3
5
|
import { readGitChangedFiles } from '../lib/git-changes.js';
|
|
@@ -10,10 +12,15 @@ export function getClassifyHelp(lang = 'en') {
|
|
|
10
12
|
summary: t(lang, 'classify.help.summary'),
|
|
11
13
|
options: [
|
|
12
14
|
{ label: '--changed', description: t(lang, 'classify.help.option.changed') },
|
|
15
|
+
{ label: '--write <path>', description: t(lang, 'classify.help.option.write') },
|
|
13
16
|
{ label: '--json', description: t(lang, 'cli.option.json') },
|
|
14
17
|
{ label: '-h, --help', description: t(lang, 'cli.option.help') },
|
|
15
18
|
],
|
|
16
|
-
examples: [
|
|
19
|
+
examples: [
|
|
20
|
+
'mf classify --changed',
|
|
21
|
+
'mf classify --changed --write .mustflow/state/change-classification.json',
|
|
22
|
+
'mf classify README.md schemas/verify-report.schema.json --json',
|
|
23
|
+
],
|
|
17
24
|
exitCodes: [
|
|
18
25
|
{ label: '0', description: t(lang, 'classify.help.exit.ok') },
|
|
19
26
|
{ label: '1', description: t(lang, 'cli.common.invalidInput') },
|
|
@@ -24,7 +31,9 @@ function parseClassifyArgs(args) {
|
|
|
24
31
|
const paths = [];
|
|
25
32
|
let json = false;
|
|
26
33
|
let changed = false;
|
|
27
|
-
|
|
34
|
+
let writePath;
|
|
35
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
36
|
+
const arg = args[index];
|
|
28
37
|
if (arg === '--json') {
|
|
29
38
|
json = true;
|
|
30
39
|
continue;
|
|
@@ -33,12 +42,29 @@ function parseClassifyArgs(args) {
|
|
|
33
42
|
changed = true;
|
|
34
43
|
continue;
|
|
35
44
|
}
|
|
45
|
+
if (arg === '--write') {
|
|
46
|
+
const value = args[index + 1];
|
|
47
|
+
if (!value || value.startsWith('-')) {
|
|
48
|
+
return { json, changed, writePath, paths, error: 'missing_write_value' };
|
|
49
|
+
}
|
|
50
|
+
writePath = value;
|
|
51
|
+
index += 1;
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
if (arg.startsWith('--write=')) {
|
|
55
|
+
const value = arg.slice('--write='.length);
|
|
56
|
+
if (value.length === 0) {
|
|
57
|
+
return { json, changed, writePath, paths, error: 'missing_write_value' };
|
|
58
|
+
}
|
|
59
|
+
writePath = value;
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
36
62
|
if (arg.startsWith('-')) {
|
|
37
|
-
return { json, changed, paths, error: arg };
|
|
63
|
+
return { json, changed, writePath, paths, error: arg };
|
|
38
64
|
}
|
|
39
65
|
paths.push(arg);
|
|
40
66
|
}
|
|
41
|
-
return { json, changed, paths };
|
|
67
|
+
return { json, changed, writePath, paths };
|
|
42
68
|
}
|
|
43
69
|
export function createClassifyOutput(projectRoot, source, paths) {
|
|
44
70
|
const files = source === 'changed' ? readGitChangedFiles(projectRoot) : paths;
|
|
@@ -75,6 +101,19 @@ function renderClassifyOutput(output, lang) {
|
|
|
75
101
|
}
|
|
76
102
|
return lines.join('\n');
|
|
77
103
|
}
|
|
104
|
+
function resolveWritePath(projectRoot, inputPath) {
|
|
105
|
+
const resolved = path.resolve(projectRoot, inputPath);
|
|
106
|
+
const relative = path.relative(projectRoot, resolved);
|
|
107
|
+
if (relative.startsWith('..') || path.isAbsolute(relative)) {
|
|
108
|
+
throw new Error('write_path_outside_root');
|
|
109
|
+
}
|
|
110
|
+
return resolved;
|
|
111
|
+
}
|
|
112
|
+
function writeClassifyOutput(projectRoot, inputPath, output) {
|
|
113
|
+
const outputPath = resolveWritePath(projectRoot, inputPath);
|
|
114
|
+
mkdirSync(path.dirname(outputPath), { recursive: true });
|
|
115
|
+
writeFileSync(outputPath, `${JSON.stringify(output, null, 2)}\n`, 'utf8');
|
|
116
|
+
}
|
|
78
117
|
export function runClassify(args, reporter, lang = 'en') {
|
|
79
118
|
if (args.includes('--help') || args.includes('-h')) {
|
|
80
119
|
reporter.stdout(getClassifyHelp(lang));
|
|
@@ -82,7 +121,10 @@ export function runClassify(args, reporter, lang = 'en') {
|
|
|
82
121
|
}
|
|
83
122
|
const parsed = parseClassifyArgs(args);
|
|
84
123
|
if (parsed.error) {
|
|
85
|
-
|
|
124
|
+
const message = parsed.error === 'missing_write_value'
|
|
125
|
+
? t(lang, 'cli.error.missingValue', { option: '--write' })
|
|
126
|
+
: t(lang, 'cli.error.unknownOption', { option: parsed.error });
|
|
127
|
+
printUsageError(reporter, message, 'mf classify --help', getClassifyHelp(lang), lang);
|
|
86
128
|
return 1;
|
|
87
129
|
}
|
|
88
130
|
if (parsed.changed && parsed.paths.length > 0) {
|
|
@@ -93,7 +135,20 @@ export function runClassify(args, reporter, lang = 'en') {
|
|
|
93
135
|
printUsageError(reporter, t(lang, 'classify.error.missingInput'), 'mf classify --help', getClassifyHelp(lang), lang);
|
|
94
136
|
return 1;
|
|
95
137
|
}
|
|
96
|
-
const
|
|
138
|
+
const projectRoot = resolveMustflowRoot();
|
|
139
|
+
const output = createClassifyOutput(projectRoot, parsed.changed ? 'changed' : 'paths', parsed.paths);
|
|
140
|
+
if (parsed.writePath) {
|
|
141
|
+
try {
|
|
142
|
+
writeClassifyOutput(projectRoot, parsed.writePath, output);
|
|
143
|
+
}
|
|
144
|
+
catch (error) {
|
|
145
|
+
const message = error instanceof Error && error.message === 'write_path_outside_root'
|
|
146
|
+
? t(lang, 'classify.error.write_path_outside_root')
|
|
147
|
+
: t(lang, 'cli.common.invalidInput');
|
|
148
|
+
printUsageError(reporter, message, 'mf classify --help', getClassifyHelp(lang), lang);
|
|
149
|
+
return 1;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
97
152
|
if (parsed.json) {
|
|
98
153
|
reporter.stdout(JSON.stringify(output, null, 2));
|
|
99
154
|
return 0;
|
|
@@ -14,10 +14,11 @@ export function getContractLintHelp(lang = 'en') {
|
|
|
14
14
|
summary: t(lang, 'contractLint.help.summary'),
|
|
15
15
|
options: [
|
|
16
16
|
{ label: '--coverage', description: t(lang, 'contractLint.help.option.coverage') },
|
|
17
|
+
{ label: '--suggest', description: t(lang, 'contractLint.help.option.suggest') },
|
|
17
18
|
{ label: '--json', description: t(lang, 'cli.option.json') },
|
|
18
19
|
{ label: '-h, --help', description: t(lang, 'cli.option.help') },
|
|
19
20
|
],
|
|
20
|
-
examples: ['mf contract-lint', 'mf contract-lint --coverage', 'mf contract-lint --coverage --json'],
|
|
21
|
+
examples: ['mf contract-lint', 'mf contract-lint --coverage', 'mf contract-lint --suggest', 'mf contract-lint --coverage --json'],
|
|
21
22
|
exitCodes: [
|
|
22
23
|
{ label: '0', description: t(lang, 'contractLint.help.exit.ok') },
|
|
23
24
|
{ label: '1', description: t(lang, 'contractLint.help.exit.fail') },
|
|
@@ -32,13 +33,14 @@ function readPreferences(projectRoot) {
|
|
|
32
33
|
const preferences = readTomlFile(preferencesPath);
|
|
33
34
|
return isRecord(preferences) ? preferences : undefined;
|
|
34
35
|
}
|
|
35
|
-
function createContractLintOutput(projectRoot, coverage) {
|
|
36
|
+
function createContractLintOutput(projectRoot, coverage, suggest) {
|
|
36
37
|
return {
|
|
37
38
|
schema_version: CONTRACT_LINT_SCHEMA_VERSION,
|
|
38
39
|
command: 'contract-lint',
|
|
39
40
|
mustflow_root: projectRoot,
|
|
40
41
|
report: lintCommandContract(readCommandContract(projectRoot), {
|
|
41
42
|
coverage,
|
|
43
|
+
suggest,
|
|
42
44
|
projectRoot,
|
|
43
45
|
releaseVersioningEnabled: releaseVersioningIsEnabled(readPreferences(projectRoot)),
|
|
44
46
|
}),
|
|
@@ -63,6 +65,13 @@ function renderContractLintOutput(output, lang) {
|
|
|
63
65
|
if (output.report.coverage) {
|
|
64
66
|
lines.push('', t(lang, 'contractLint.label.coverage'), `${t(lang, 'contractLint.label.classificationReasons')}: ${output.report.coverage.knownClassificationReasons.length}`, `${t(lang, 'contractLint.label.requiredAfterReasons')}: ${output.report.coverage.requiredAfterReasons.length}`, `${t(lang, 'contractLint.label.runnableReasons')}: ${output.report.coverage.runnableReasons.length}`, `${t(lang, 'contractLint.label.coverageFindings')}: ${output.report.coverage.findings.length}`);
|
|
65
67
|
}
|
|
68
|
+
if (output.report.suggestions) {
|
|
69
|
+
lines.push('', t(lang, 'contractLint.label.suggestions'));
|
|
70
|
+
for (const suggestion of output.report.suggestions) {
|
|
71
|
+
lines.push(`- ${suggestion.sourceFile}:${suggestion.sourceName} -> ${suggestion.suggestedIntent}`);
|
|
72
|
+
lines.push(...suggestion.snippet.split('\n').map((line) => ` ${line}`));
|
|
73
|
+
}
|
|
74
|
+
}
|
|
66
75
|
if (output.report.issues.length > 0) {
|
|
67
76
|
lines.push('', t(lang, 'contractLint.label.issues'));
|
|
68
77
|
for (const issue of output.report.issues) {
|
|
@@ -77,13 +86,13 @@ export function runContractLint(args, reporter, lang = 'en') {
|
|
|
77
86
|
reporter.stdout(getContractLintHelp(lang));
|
|
78
87
|
return 0;
|
|
79
88
|
}
|
|
80
|
-
const supported = new Set(['--coverage', '--json']);
|
|
89
|
+
const supported = new Set(['--coverage', '--suggest', '--json']);
|
|
81
90
|
const unsupported = args.filter((arg) => !supported.has(arg));
|
|
82
91
|
if (unsupported.length > 0) {
|
|
83
92
|
printUsageError(reporter, t(lang, 'cli.error.unknownOption', { option: unsupported[0] }), 'mf contract-lint --help', getContractLintHelp(lang), lang);
|
|
84
93
|
return 1;
|
|
85
94
|
}
|
|
86
|
-
const output = createContractLintOutput(resolveMustflowRoot(), args.includes('--coverage'));
|
|
95
|
+
const output = createContractLintOutput(resolveMustflowRoot(), args.includes('--coverage'), args.includes('--suggest'));
|
|
87
96
|
if (args.includes('--json')) {
|
|
88
97
|
reporter.stdout(JSON.stringify(output, null, 2));
|
|
89
98
|
}
|
|
@@ -15,7 +15,7 @@ import { isRecord, readCommandContract, readPositiveInteger, readString, readStr
|
|
|
15
15
|
import { readDashboardPreferences, updateDashboardPreferences, } from '../lib/dashboard-preferences.js';
|
|
16
16
|
import { DOC_REVIEW_LEDGER_RELATIVE_PATH, isDocReviewStatus, isReviewerKind, listDocReviewEntries, markDocReviewEntry, } from '../lib/doc-review-ledger.js';
|
|
17
17
|
import { inspectManifestLock } from '../lib/manifest-lock.js';
|
|
18
|
-
import { readLocalCommandEffectGraphs } from '../lib/local-index.js';
|
|
18
|
+
import { readLatestLocalVerificationReadModelQueries, readLocalCommandEffectGraphs, } from '../lib/local-index.js';
|
|
19
19
|
import { readPackageMetadata } from '../lib/package-info.js';
|
|
20
20
|
import { t } from '../lib/i18n.js';
|
|
21
21
|
import { resolveMustflowRoot } from '../lib/project-root.js';
|
|
@@ -25,6 +25,7 @@ const DEFAULT_DASHBOARD_HOST = '127.0.0.1';
|
|
|
25
25
|
const DEFAULT_DASHBOARD_PORT = 0;
|
|
26
26
|
const MAX_REQUEST_BYTES = 64 * 1024;
|
|
27
27
|
const LOCAL_DASHBOARD_HOSTS = new Set(['127.0.0.1', 'localhost', '::1']);
|
|
28
|
+
const DOC_REVIEW_BULK_PAYLOAD_FIELDS = ['paths', 'documents', 'entries'];
|
|
28
29
|
const RELEASE_FILE_PATTERNS = [
|
|
29
30
|
/^package\.json$/u,
|
|
30
31
|
/^bun\.lockb?$/u,
|
|
@@ -296,6 +297,11 @@ function readDocReviewPayload(value) {
|
|
|
296
297
|
throw new Error('Request body must be a JSON object.');
|
|
297
298
|
}
|
|
298
299
|
const payload = value;
|
|
300
|
+
for (const field of DOC_REVIEW_BULK_PAYLOAD_FIELDS) {
|
|
301
|
+
if (field in payload) {
|
|
302
|
+
throw new Error('Bulk documentation review updates require a separate confirmed flow.');
|
|
303
|
+
}
|
|
304
|
+
}
|
|
299
305
|
const status = readRequiredStringField(payload, 'status');
|
|
300
306
|
if (status !== 'approved' && status !== 'needs_human' && status !== 'ignored') {
|
|
301
307
|
throw new Error('status must be approved, needs_human, or ignored.');
|
|
@@ -377,6 +383,70 @@ function toDashboardCommandEffectGraph(graph) {
|
|
|
377
383
|
})),
|
|
378
384
|
};
|
|
379
385
|
}
|
|
386
|
+
function toDashboardVerificationReadModel(readModel) {
|
|
387
|
+
return {
|
|
388
|
+
source: readModel.source,
|
|
389
|
+
authority: readModel.authority,
|
|
390
|
+
command_authority: readModel.commandAuthority,
|
|
391
|
+
grants_command_authority: readModel.grantsCommandAuthority,
|
|
392
|
+
status: readModel.status,
|
|
393
|
+
database_path: readModel.databasePath,
|
|
394
|
+
index_fresh: readModel.indexFresh,
|
|
395
|
+
stale_paths: readModel.stalePaths,
|
|
396
|
+
plan_id: readModel.planId,
|
|
397
|
+
refresh_hint: readModel.refreshHint,
|
|
398
|
+
uncovered_criteria: readModel.uncoveredCriteria.map((criterion) => ({
|
|
399
|
+
criterion_id: criterion.criterionId,
|
|
400
|
+
source: criterion.source,
|
|
401
|
+
reason: criterion.reason,
|
|
402
|
+
surface: criterion.surface,
|
|
403
|
+
path_hash: criterion.pathHash,
|
|
404
|
+
coverage_status: criterion.coverageStatus,
|
|
405
|
+
receipt_count: criterion.receiptCount,
|
|
406
|
+
gap_count: criterion.gapCount,
|
|
407
|
+
risk_count: criterion.riskCount,
|
|
408
|
+
})),
|
|
409
|
+
severe_risks: readModel.severeRisks.map((risk) => ({
|
|
410
|
+
source_path: risk.sourcePath,
|
|
411
|
+
ordinal: risk.ordinal,
|
|
412
|
+
code: risk.code,
|
|
413
|
+
severity: risk.severity,
|
|
414
|
+
detail_hash: risk.detailHash,
|
|
415
|
+
})),
|
|
416
|
+
non_passing_receipts: readModel.nonPassingReceipts.map((receipt) => ({
|
|
417
|
+
receipt_hash: receipt.receiptHash,
|
|
418
|
+
plan_id: receipt.planId,
|
|
419
|
+
intent: receipt.intent,
|
|
420
|
+
status: receipt.status,
|
|
421
|
+
command_fingerprint: receipt.commandFingerprint,
|
|
422
|
+
contract_fingerprint: receipt.contractFingerprint,
|
|
423
|
+
current_state_hash: receipt.currentStateHash,
|
|
424
|
+
write_drift_status: receipt.writeDriftStatus,
|
|
425
|
+
})),
|
|
426
|
+
repeated_failure_fingerprints: readModel.repeatedFailureFingerprints.map((fingerprint) => ({
|
|
427
|
+
source_path: fingerprint.sourcePath,
|
|
428
|
+
fingerprint: fingerprint.fingerprint,
|
|
429
|
+
verification_plan_id: fingerprint.verificationPlanId,
|
|
430
|
+
status: fingerprint.status,
|
|
431
|
+
failed_intents: fingerprint.failedIntents,
|
|
432
|
+
primary_reason: fingerprint.primaryReason,
|
|
433
|
+
failed_intents_hash: fingerprint.failedIntentsHash,
|
|
434
|
+
risk_codes_hash: fingerprint.riskCodesHash,
|
|
435
|
+
affected_surfaces_hash: fingerprint.affectedSurfacesHash,
|
|
436
|
+
seen_count: fingerprint.seenCount,
|
|
437
|
+
requires_new_evidence: fingerprint.requiresNewEvidence,
|
|
438
|
+
})),
|
|
439
|
+
validation_weakening_signals: readModel.validationWeakeningSignals.map((signal) => ({
|
|
440
|
+
signal_id: signal.signalId,
|
|
441
|
+
plan_id: signal.planId,
|
|
442
|
+
code: signal.code,
|
|
443
|
+
severity: signal.severity,
|
|
444
|
+
path_hash: signal.pathHash,
|
|
445
|
+
before_hash: signal.beforeHash,
|
|
446
|
+
after_hash: signal.afterHash,
|
|
447
|
+
})),
|
|
448
|
+
};
|
|
449
|
+
}
|
|
380
450
|
async function readCommandEffectGraphMap(projectRoot, intentNames) {
|
|
381
451
|
if (intentNames.length === 0) {
|
|
382
452
|
return { graphs: new Map() };
|
|
@@ -614,6 +684,8 @@ async function renderStatusResponse(projectRoot) {
|
|
|
614
684
|
const commandContract = await renderCommandContractResponse(projectRoot, rawCommandContract);
|
|
615
685
|
const gitChangedFiles = readGitChangedFiles(projectRoot);
|
|
616
686
|
const packageMetadata = readPackageMetadata();
|
|
687
|
+
const verification = createDashboardVerificationSnapshot(projectRoot, rawCommandContract, commandContract.intents, gitChangedFiles, manifest.changedFiles, manifest.missingFiles);
|
|
688
|
+
const readModel = await readLatestLocalVerificationReadModelQueries(projectRoot);
|
|
617
689
|
return {
|
|
618
690
|
schema_version: '1',
|
|
619
691
|
command: 'dashboard status',
|
|
@@ -635,7 +707,10 @@ async function renderStatusResponse(projectRoot) {
|
|
|
635
707
|
issues: manifest.issues,
|
|
636
708
|
runnable_intents: context.command_contract.runnable_intents,
|
|
637
709
|
command_contract: commandContract,
|
|
638
|
-
verification:
|
|
710
|
+
verification: {
|
|
711
|
+
...verification,
|
|
712
|
+
read_model: toDashboardVerificationReadModel(readModel),
|
|
713
|
+
},
|
|
639
714
|
latest_run: context.latest_run,
|
|
640
715
|
active_review_documents: activeDocuments.length,
|
|
641
716
|
};
|
|
@@ -4,7 +4,7 @@ import { createVerificationDecisionGraph, } from '../../core/verification-decisi
|
|
|
4
4
|
import { createVerificationPlan, } from '../../core/verification-plan.js';
|
|
5
5
|
import { createVerificationSchedule } from '../../core/verification-scheduler.js';
|
|
6
6
|
import { t } from '../lib/i18n.js';
|
|
7
|
-
import { readLocalCommandEffectGraphs, } from '../lib/local-index.js';
|
|
7
|
+
import { readLatestLocalVerificationReadModelQueries, readLocalCommandEffectGraphs, } from '../lib/local-index.js';
|
|
8
8
|
import { planErrorMessageKey, readInputFromPlan } from './verify.js';
|
|
9
9
|
export function parseExplainVerifyArgs(args) {
|
|
10
10
|
let reason;
|
|
@@ -104,6 +104,7 @@ export async function getVerifyExplainOutput(schemaVersion, projectRoot, reasons
|
|
|
104
104
|
...new Set(plans.flatMap((plan) => plan.candidates.map((candidate) => candidate.intent).filter((intent) => intent.length > 0))),
|
|
105
105
|
];
|
|
106
106
|
const graphsByIntent = intentNames.length > 0 ? await readLocalCommandEffectGraphs(projectRoot, intentNames) : new Map();
|
|
107
|
+
const readModel = await readLatestLocalVerificationReadModelQueries(projectRoot);
|
|
107
108
|
const requirements = plans.map((plan) => {
|
|
108
109
|
const candidates = plan.candidates.map((candidate) => {
|
|
109
110
|
const command = candidate.intent ? explainCommandIntent(contract, candidate.intent).intent : null;
|
|
@@ -166,6 +167,7 @@ export async function getVerifyExplainOutput(schemaVersion, projectRoot, reasons
|
|
|
166
167
|
skippedCount,
|
|
167
168
|
requirements,
|
|
168
169
|
decisionGraph,
|
|
170
|
+
readModel,
|
|
169
171
|
},
|
|
170
172
|
},
|
|
171
173
|
};
|
|
@@ -182,7 +184,15 @@ export function renderVerifyExplainDecision(decision, lang) {
|
|
|
182
184
|
`- skipped: ${verification.skippedCount}`,
|
|
183
185
|
`- decision_graph_nodes: ${verification.decisionGraph.summary.nodeCount}`,
|
|
184
186
|
`- decision_graph_gaps: ${verification.decisionGraph.summary.gapCount}`,
|
|
187
|
+
`- read_model: ${verification.readModel.status}`,
|
|
188
|
+
`- read_model_plan_id: ${verification.readModel.planId ?? t(lang, 'value.none')}`,
|
|
185
189
|
];
|
|
190
|
+
if (verification.readModel.refreshHint) {
|
|
191
|
+
lines.push(`- read_model_refresh_hint: ${verification.readModel.refreshHint}`);
|
|
192
|
+
}
|
|
193
|
+
if (verification.readModel.status === 'fresh') {
|
|
194
|
+
lines.push(`- uncovered_criteria: ${verification.readModel.uncoveredCriteria.length}`, `- severe_risks: ${verification.readModel.severeRisks.length}`, `- non_passing_receipts: ${verification.readModel.nonPassingReceipts.length}`, `- repeated_failures_requiring_new_evidence: ${verification.readModel.repeatedFailureFingerprints.length}`, `- validation_weakening_signals: ${verification.readModel.validationWeakeningSignals.length}`);
|
|
195
|
+
}
|
|
186
196
|
for (const requirement of verification.requirements) {
|
|
187
197
|
lines.push(`- required_after: ${requirement.reason}`);
|
|
188
198
|
if (requirement.matchingIntents.length > 0) {
|
|
@@ -42,7 +42,21 @@ function renderIndexSummary(result, lang) {
|
|
|
42
42
|
`skill_routes: ${result.skill_route_count}`,
|
|
43
43
|
`${t(lang, 'label.commandIntents')}: ${result.command_intent_count}`,
|
|
44
44
|
`command_effects: ${result.command_effect_count}`,
|
|
45
|
+
`verification_evidence_summaries: ${result.verification_evidence_summary_count}`,
|
|
46
|
+
`verification_plans: ${result.verification_plan_count}`,
|
|
47
|
+
`acceptance_criteria: ${result.acceptance_criteria_count}`,
|
|
48
|
+
`criterion_coverage: ${result.criterion_coverage_count}`,
|
|
49
|
+
`verification_receipt_summaries: ${result.verification_receipt_summary_count}`,
|
|
50
|
+
`command_receipt_summaries: ${result.command_receipt_summary_count}`,
|
|
51
|
+
`verification_coverage_states: ${result.verification_coverage_state_count}`,
|
|
52
|
+
`verification_risk_signals: ${result.verification_risk_signal_count}`,
|
|
53
|
+
`validation_ratchet_signals: ${result.validation_ratchet_signal_count}`,
|
|
54
|
+
`completion_verdict_summaries: ${result.completion_verdict_summary_count}`,
|
|
55
|
+
`repro_routes: ${result.repro_route_count}`,
|
|
56
|
+
`repro_observations: ${result.repro_observation_count}`,
|
|
57
|
+
`failure_fingerprints: ${result.failure_fingerprint_count}`,
|
|
45
58
|
`source_anchors: ${result.source_anchor_count}`,
|
|
59
|
+
`source_anchor_risk_signals: ${result.source_anchor_risk_signal_count}`,
|
|
46
60
|
`index_mode: ${result.index_mode}`,
|
|
47
61
|
`reused_existing: ${result.reused_existing ? 'yes' : 'no'}`,
|
|
48
62
|
`${t(lang, 'label.wroteFiles')}: ${result.wrote_files ? 'yes' : 'no'}`,
|
package/dist/cli/commands/run.js
CHANGED
|
@@ -367,6 +367,9 @@ function reportRunPlanFailure(plan, reporter, lang) {
|
|
|
367
367
|
message = t(lang, 'run.error.unknownIntent', { intent: plan.intentName });
|
|
368
368
|
break;
|
|
369
369
|
}
|
|
370
|
+
if (plan.suggestedIntentSnippet) {
|
|
371
|
+
message = `${message}\n\n${t(lang, 'run.label.suggestedIntentSnippet')}:\n${plan.suggestedIntentSnippet}`;
|
|
372
|
+
}
|
|
370
373
|
reporter.stderr(renderCliError(message, 'mf help commands', lang));
|
|
371
374
|
}
|
|
372
375
|
export function getRunHelp(lang = 'en') {
|
|
@@ -439,7 +442,7 @@ export async function runRun(args, reporter, lang = 'en', options = {}) {
|
|
|
439
442
|
reporter.stdout(JSON.stringify(createRunPreview(plan, previewMode), null, 2));
|
|
440
443
|
}
|
|
441
444
|
else {
|
|
442
|
-
reporter.stdout(renderRunPreviewText(plan, previewMode));
|
|
445
|
+
reporter.stdout(renderRunPreviewText(plan, previewMode, lang));
|
|
443
446
|
}
|
|
444
447
|
});
|
|
445
448
|
profiler.writeLatest({
|