mustflow 2.22.3 → 2.22.5
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 +9 -75
- package/dist/cli/commands/contract-lint.js +2 -2
- package/dist/cli/commands/dashboard.js +14 -6
- package/dist/cli/commands/help.js +8 -9
- package/dist/cli/commands/impact.js +2 -3
- package/dist/cli/commands/init.js +61 -5
- package/dist/cli/commands/update.js +2 -2
- package/dist/cli/commands/version-sources.js +2 -3
- package/dist/cli/i18n/en.js +2 -0
- package/dist/cli/i18n/es.js +2 -0
- package/dist/cli/i18n/fr.js +2 -0
- package/dist/cli/i18n/hi.js +2 -0
- package/dist/cli/i18n/ko.js +2 -0
- package/dist/cli/i18n/zh.js +2 -0
- package/dist/cli/lib/agent-context.js +6 -11
- package/dist/cli/lib/local-index/index.js +14 -11
- package/dist/cli/lib/mustflow-read.js +41 -0
- package/dist/cli/lib/project-root.js +1 -2
- package/dist/cli/lib/repo-map.js +65 -16
- package/dist/cli/lib/templates.js +124 -8
- package/dist/cli/lib/toml.js +6 -1
- package/dist/cli/lib/validation/constants.js +2 -0
- package/dist/cli/lib/validation/index.js +291 -22
- package/dist/cli/lib/validation/primitives.js +2 -2
- package/dist/cli/lib/validation/test-selection.js +2 -2
- package/dist/core/bounded-output.js +32 -7
- package/dist/core/check-issues.js +7 -1
- package/dist/core/command-contract-validation.js +28 -4
- package/dist/core/command-env.js +1 -1
- package/dist/core/config-loading.js +9 -3
- package/dist/core/contract-lint.js +2 -1
- package/dist/core/safe-filesystem.js +11 -4
- package/dist/core/skill-route-alignment.js +1 -0
- package/dist/core/skill-route-explanation.js +9 -3
- package/dist/core/test-selection.js +2 -3
- package/dist/core/verification-scheduler.js +7 -6
- package/dist/core/version-sources.js +2 -3
- package/package.json +4 -2
- package/schemas/commands.schema.json +1 -0
- package/templates/default/locales/en/.mustflow/skills/INDEX.md +10 -6
- package/templates/default/locales/en/.mustflow/skills/routes.toml +2 -2
- package/templates/default/manifest.toml +1 -1
package/README.md
CHANGED
|
@@ -147,7 +147,9 @@ mustflow is not an automatic project editor and is not tied to a single agent pr
|
|
|
147
147
|
|
|
148
148
|
## Installed files
|
|
149
149
|
|
|
150
|
-
`mf init` installs only the agent workflow into the current directory.
|
|
150
|
+
`mf init` installs only the agent workflow into the current directory. The exact skill files depend
|
|
151
|
+
on the selected profile; run `mf init --dry-run --profile <profile>` to preview the concrete plan
|
|
152
|
+
for a project before writing files.
|
|
151
153
|
|
|
152
154
|
```text
|
|
153
155
|
your-project/
|
|
@@ -166,83 +168,15 @@ your-project/
|
|
|
166
168
|
│ └─ agent-workflow.md
|
|
167
169
|
└─ skills/
|
|
168
170
|
├─ INDEX.md
|
|
169
|
-
├─
|
|
170
|
-
|
|
171
|
-
├─ behavior-preserving-refactor/
|
|
172
|
-
│ └─ SKILL.md
|
|
173
|
-
├─ code-review/
|
|
174
|
-
│ └─ SKILL.md
|
|
175
|
-
├─ codebase-orientation/
|
|
176
|
-
│ └─ SKILL.md
|
|
177
|
-
├─ contract-sync-check/
|
|
178
|
-
│ └─ SKILL.md
|
|
179
|
-
├─ date-number-audit/
|
|
180
|
-
│ └─ SKILL.md
|
|
181
|
-
├─ database-change-safety/
|
|
182
|
-
│ └─ SKILL.md
|
|
183
|
-
├─ dependency-reality-check/
|
|
184
|
-
│ └─ SKILL.md
|
|
185
|
-
├─ diff-risk-review/
|
|
186
|
-
│ └─ SKILL.md
|
|
187
|
-
├─ docs-prose-review/
|
|
188
|
-
│ └─ SKILL.md
|
|
189
|
-
├─ docs-update/
|
|
190
|
-
│ └─ SKILL.md
|
|
191
|
-
├─ external-prompt-injection-defense/
|
|
192
|
-
│ └─ SKILL.md
|
|
193
|
-
├─ external-skill-intake/
|
|
194
|
-
│ └─ SKILL.md
|
|
195
|
-
├─ failure-triage/
|
|
196
|
-
│ └─ SKILL.md
|
|
197
|
-
├─ instruction-conflict-scope-check/
|
|
198
|
-
│ └─ SKILL.md
|
|
199
|
-
├─ migration-safety-check/
|
|
200
|
-
│ └─ SKILL.md
|
|
201
|
-
├─ multi-agent-work-coordination/
|
|
202
|
-
│ └─ SKILL.md
|
|
203
|
-
├─ performance-budget-check/
|
|
204
|
-
│ └─ SKILL.md
|
|
205
|
-
├─ project-context-authoring/
|
|
206
|
-
│ └─ SKILL.md
|
|
207
|
-
├─ pattern-scout/
|
|
208
|
-
│ └─ SKILL.md
|
|
209
|
-
├─ repo-improvement-loop/
|
|
210
|
-
│ └─ SKILL.md
|
|
211
|
-
├─ requirement-regression-guard/
|
|
212
|
-
│ └─ SKILL.md
|
|
213
|
-
├─ repro-first-debug/
|
|
214
|
-
│ └─ SKILL.md
|
|
215
|
-
├─ security-privacy-review/
|
|
216
|
-
│ └─ SKILL.md
|
|
217
|
-
├─ source-freshness-check/
|
|
218
|
-
│ └─ SKILL.md
|
|
219
|
-
├─ structure-discovery-gate/
|
|
220
|
-
│ └─ SKILL.md
|
|
221
|
-
├─ security-regression-tests/
|
|
222
|
-
│ └─ SKILL.md
|
|
223
|
-
├─ skill-authoring/
|
|
224
|
-
│ └─ SKILL.md
|
|
225
|
-
├─ test-design-guard/
|
|
226
|
-
│ └─ SKILL.md
|
|
227
|
-
├─ test-maintenance/
|
|
228
|
-
│ └─ SKILL.md
|
|
229
|
-
├─ vertical-slice-tdd/
|
|
230
|
-
│ └─ SKILL.md
|
|
231
|
-
├─ llm-service-ux-review/
|
|
232
|
-
│ └─ SKILL.md
|
|
233
|
-
├─ search-ad-content-authoring/
|
|
234
|
-
│ └─ SKILL.md
|
|
235
|
-
├─ ui-quality-gate/
|
|
236
|
-
│ └─ SKILL.md
|
|
237
|
-
├─ visual-review-artifact/
|
|
238
|
-
│ ├─ SKILL.md
|
|
239
|
-
│ ├─ resources.toml
|
|
240
|
-
│ └─ assets/
|
|
241
|
-
│ └─ review-template.html
|
|
242
|
-
└─ web-asset-optimization/
|
|
171
|
+
├─ routes.toml
|
|
172
|
+
└─ <profile-selected-skill>/
|
|
243
173
|
└─ SKILL.md
|
|
244
174
|
```
|
|
245
175
|
|
|
176
|
+
Profiles select the installed skill surface. The package can include optional skill files that are
|
|
177
|
+
not copied into every project profile. Non-English workflow documents are localized when available;
|
|
178
|
+
skill procedures currently fall back to the canonical English skill files.
|
|
179
|
+
|
|
246
180
|
The default template does not create project-owned root documents or contract files such as `README.md`, `PROJECT.md`, `ROADMAP.md`, `DESIGN.md`, `GOVERNANCE.md`, `TESTING.md`, `API.md`, `project.contract.json`, or `openapi.yaml`. It also does not create CI configuration, general `docs/`, or general `skills/`. User projects may already use those names for their own files.
|
|
247
181
|
|
|
248
182
|
`mf init` creates `.gitignore` if it is missing. If `.gitignore` exists, mustflow updates only its managed block and preserves user rules.
|
|
@@ -2,11 +2,11 @@ import { existsSync } from 'node:fs';
|
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { lintCommandContract } from '../../core/contract-lint.js';
|
|
4
4
|
import { readCommandContract, isRecord } from '../../core/config-loading.js';
|
|
5
|
-
import { readTomlFile } from '../../core/toml.js';
|
|
6
5
|
import { releaseVersioningIsEnabled } from '../../core/version-sources.js';
|
|
7
6
|
import { printUsageError, renderHelp } from '../lib/cli-output.js';
|
|
8
7
|
import { t } from '../lib/i18n.js';
|
|
9
8
|
import { resolveMustflowRoot } from '../lib/project-root.js';
|
|
9
|
+
import { readMustflowTomlFile } from '../lib/toml.js';
|
|
10
10
|
const CONTRACT_LINT_SCHEMA_VERSION = '1';
|
|
11
11
|
export function getContractLintHelp(lang = 'en') {
|
|
12
12
|
return renderHelp({
|
|
@@ -30,7 +30,7 @@ function readPreferences(projectRoot) {
|
|
|
30
30
|
if (!existsSync(preferencesPath)) {
|
|
31
31
|
return undefined;
|
|
32
32
|
}
|
|
33
|
-
const preferences =
|
|
33
|
+
const preferences = readMustflowTomlFile(projectRoot, '.mustflow/config/preferences.toml');
|
|
34
34
|
return isRecord(preferences) ? preferences : undefined;
|
|
35
35
|
}
|
|
36
36
|
function createContractLintOutput(projectRoot, coverage, suggest) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { randomBytes } from 'node:crypto';
|
|
1
|
+
import { createHash, randomBytes } from 'node:crypto';
|
|
2
2
|
import { existsSync, readFileSync, statSync } from 'node:fs';
|
|
3
3
|
import http from 'node:http';
|
|
4
4
|
import path from 'node:path';
|
|
@@ -18,6 +18,7 @@ import { MANIFEST_LOCK_RELATIVE_PATH, inspectManifestLock } from '../lib/manifes
|
|
|
18
18
|
import { getLocalIndexDatabasePath, 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
|
+
import { MUSTFLOW_JSON_MAX_BYTES, readMustflowTextFile, readMustflowTextFileIfExists, } from '../lib/mustflow-read.js';
|
|
21
22
|
import { resolveMustflowRoot } from '../lib/project-root.js';
|
|
22
23
|
import { detectVersionSources } from '../../core/version-sources.js';
|
|
23
24
|
import { planUpdate, summarizePlan } from './update.js';
|
|
@@ -39,6 +40,7 @@ const LATEST_RUN_RELATIVE_PATH = '.mustflow/state/runs/latest.json';
|
|
|
39
40
|
const COMMANDS_RELATIVE_PATH = '.mustflow/config/commands.toml';
|
|
40
41
|
const AGENTS_RELATIVE_PATH = 'AGENTS.md';
|
|
41
42
|
const STATUS_BLOCK_CACHE_TTL_MS = 750;
|
|
43
|
+
const STATUS_SIGNATURE_HASH_MAX_BYTES = 1024 * 1024;
|
|
42
44
|
const dashboardStatusBlockCache = new Map();
|
|
43
45
|
function dashboardStatusBlockCacheKey(projectRoot, blockName) {
|
|
44
46
|
return `${path.resolve(projectRoot)}\0${blockName}`;
|
|
@@ -46,7 +48,11 @@ function dashboardStatusBlockCacheKey(projectRoot, blockName) {
|
|
|
46
48
|
function readFileSignature(filePath) {
|
|
47
49
|
try {
|
|
48
50
|
const stat = statSync(filePath);
|
|
49
|
-
|
|
51
|
+
if (!stat.isFile() || stat.size > STATUS_SIGNATURE_HASH_MAX_BYTES) {
|
|
52
|
+
return `${stat.mtimeMs}:${stat.ctimeMs}:${stat.size}:unhashed`;
|
|
53
|
+
}
|
|
54
|
+
const digest = createHash('sha256').update(readFileSync(filePath)).digest('hex');
|
|
55
|
+
return `${stat.mtimeMs}:${stat.ctimeMs}:${stat.size}:${digest}`;
|
|
50
56
|
}
|
|
51
57
|
catch {
|
|
52
58
|
return 'missing';
|
|
@@ -590,10 +596,12 @@ function renderSkillsResponse(projectRoot) {
|
|
|
590
596
|
routes: [],
|
|
591
597
|
};
|
|
592
598
|
}
|
|
593
|
-
const
|
|
599
|
+
const indexContent = readMustflowTextFile(projectRoot, SKILL_INDEX_RELATIVE_PATH);
|
|
600
|
+
const routes = parseSkillIndexRoutes(indexContent).map((route) => {
|
|
594
601
|
const skillPath = path.join(projectRoot, ...route.skillPath.split('/'));
|
|
595
|
-
const
|
|
596
|
-
const
|
|
602
|
+
const skillContent = readMustflowTextFileIfExists(projectRoot, route.skillPath);
|
|
603
|
+
const exists = skillContent !== null && existsSync(skillPath);
|
|
604
|
+
const declaredCommandIntents = skillContent ? readFrontmatterList(skillContent, 'command_intents') : [];
|
|
597
605
|
const sortedRouteIntents = [...route.commandIntents].sort((left, right) => left.localeCompare(right));
|
|
598
606
|
const sortedDeclaredIntents = [...declaredCommandIntents].sort((left, right) => left.localeCompare(right));
|
|
599
607
|
return {
|
|
@@ -686,7 +694,7 @@ function renderRunHistoryResponse(projectRoot) {
|
|
|
686
694
|
};
|
|
687
695
|
}
|
|
688
696
|
try {
|
|
689
|
-
const receipt = JSON.parse(
|
|
697
|
+
const receipt = JSON.parse(readMustflowTextFile(projectRoot, LATEST_RUN_RELATIVE_PATH, { maxBytes: MUSTFLOW_JSON_MAX_BYTES }));
|
|
690
698
|
if (!isRecord(receipt)) {
|
|
691
699
|
throw new Error('Run receipt must be a JSON object.');
|
|
692
700
|
}
|
|
@@ -1,23 +1,22 @@
|
|
|
1
|
-
import { existsSync, readFileSync } from 'node:fs';
|
|
2
|
-
import path from 'node:path';
|
|
3
1
|
import { printUsageError, renderCliError, renderHelp } from '../lib/cli-output.js';
|
|
4
2
|
import { t } from '../lib/i18n.js';
|
|
3
|
+
import { readMustflowTextFileIfExists } from '../lib/mustflow-read.js';
|
|
5
4
|
import { resolveMustflowRoot } from '../lib/project-root.js';
|
|
6
|
-
import {
|
|
5
|
+
import { readMustflowTomlFile } from '../lib/toml.js';
|
|
7
6
|
function isRecord(value) {
|
|
8
7
|
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
9
8
|
}
|
|
10
9
|
function readTextIfExists(projectRoot, relativePath) {
|
|
11
|
-
|
|
12
|
-
return existsSync(filePath) ? readFileSync(filePath, 'utf8') : undefined;
|
|
10
|
+
return readMustflowTextFileIfExists(projectRoot, relativePath) ?? undefined;
|
|
13
11
|
}
|
|
14
12
|
function readTomlIfExists(projectRoot, relativePath) {
|
|
15
|
-
|
|
16
|
-
|
|
13
|
+
try {
|
|
14
|
+
const parsed = readMustflowTomlFile(projectRoot, relativePath);
|
|
15
|
+
return isRecord(parsed) ? parsed : undefined;
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
17
18
|
return undefined;
|
|
18
19
|
}
|
|
19
|
-
const parsed = readTomlFile(filePath);
|
|
20
|
-
return isRecord(parsed) ? parsed : undefined;
|
|
21
20
|
}
|
|
22
21
|
function renderMissing(relativePath, lang) {
|
|
23
22
|
return t(lang, 'help.missingFile', { path: relativePath });
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import path from 'node:path';
|
|
2
1
|
import { createChangeClassificationReport } from '../../core/change-classification.js';
|
|
3
2
|
import { summarizeVersionImpact } from '../../core/version-impact.js';
|
|
4
3
|
import { printUsageError, renderHelp } from '../lib/cli-output.js';
|
|
@@ -6,7 +5,7 @@ import { isRecord } from '../lib/command-contract.js';
|
|
|
6
5
|
import { requireGitChangedFiles } from '../lib/git-changes.js';
|
|
7
6
|
import { t } from '../lib/i18n.js';
|
|
8
7
|
import { resolveMustflowRoot } from '../lib/project-root.js';
|
|
9
|
-
import {
|
|
8
|
+
import { readMustflowTomlFile } from '../lib/toml.js';
|
|
10
9
|
import { detectVersionSources, releaseVersioningIsEnabled, } from '../../core/version-sources.js';
|
|
11
10
|
const IMPACT_SCHEMA_VERSION = '1';
|
|
12
11
|
export function getImpactHelp(lang = 'en') {
|
|
@@ -47,7 +46,7 @@ function parseImpactArgs(args) {
|
|
|
47
46
|
}
|
|
48
47
|
function readPreferences(projectRoot) {
|
|
49
48
|
try {
|
|
50
|
-
const preferences =
|
|
49
|
+
const preferences = readMustflowTomlFile(projectRoot, '.mustflow/config/preferences.toml');
|
|
51
50
|
return isRecord(preferences) ? preferences : undefined;
|
|
52
51
|
}
|
|
53
52
|
catch {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { existsSync,
|
|
1
|
+
import { existsSync, readSync } from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { stdin as processStdin, stdout as processStdout } from 'node:process';
|
|
4
4
|
import { createInterface } from 'node:readline/promises';
|
|
@@ -13,6 +13,9 @@ const MUSTFLOW_BLOCK_START = '<!-- mustflow:start schema=1 -->';
|
|
|
13
13
|
const MUSTFLOW_BLOCK_END = '<!-- mustflow:end -->';
|
|
14
14
|
const GITIGNORE_RELATIVE_PATH = '.gitignore';
|
|
15
15
|
const GITIGNORE_FRAGMENT_RELATIVE_PATH = 'gitignore.mustflow';
|
|
16
|
+
const NON_INTERACTIVE_PROMPT_MAX_BYTES = 16 * 1024;
|
|
17
|
+
const NON_INTERACTIVE_PROMPT_MAX_RESPONSES = 64;
|
|
18
|
+
const NON_INTERACTIVE_PROMPT_READ_CHUNK_BYTES = 4096;
|
|
16
19
|
const LOCALE_LABELS = {
|
|
17
20
|
en: 'English',
|
|
18
21
|
ko: 'Korean',
|
|
@@ -473,9 +476,52 @@ function formatLocaleChoice(locale) {
|
|
|
473
476
|
const label = LOCALE_LABELS[locale] ?? locale;
|
|
474
477
|
return `${label} (${locale})`;
|
|
475
478
|
}
|
|
476
|
-
function
|
|
479
|
+
function readNonInteractivePromptInput() {
|
|
480
|
+
const chunks = [];
|
|
481
|
+
let totalBytes = 0;
|
|
482
|
+
for (;;) {
|
|
483
|
+
const remainingBudget = NON_INTERACTIVE_PROMPT_MAX_BYTES + 1 - totalBytes;
|
|
484
|
+
if (remainingBudget <= 0) {
|
|
485
|
+
return {
|
|
486
|
+
lines: [],
|
|
487
|
+
errorKey: 'init.error.promptInputTooLarge',
|
|
488
|
+
limit: NON_INTERACTIVE_PROMPT_MAX_BYTES,
|
|
489
|
+
};
|
|
490
|
+
}
|
|
491
|
+
const buffer = Buffer.alloc(Math.min(NON_INTERACTIVE_PROMPT_READ_CHUNK_BYTES, remainingBudget));
|
|
492
|
+
const bytesRead = readSync(0, buffer, 0, buffer.length, null);
|
|
493
|
+
if (bytesRead === 0) {
|
|
494
|
+
break;
|
|
495
|
+
}
|
|
496
|
+
totalBytes += bytesRead;
|
|
497
|
+
chunks.push(Buffer.from(buffer.subarray(0, bytesRead)));
|
|
498
|
+
if (totalBytes > NON_INTERACTIVE_PROMPT_MAX_BYTES) {
|
|
499
|
+
return {
|
|
500
|
+
lines: [],
|
|
501
|
+
errorKey: 'init.error.promptInputTooLarge',
|
|
502
|
+
limit: NON_INTERACTIVE_PROMPT_MAX_BYTES,
|
|
503
|
+
};
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
const lines = Buffer.concat(chunks, totalBytes).toString('utf8').split(/\r?\n/u);
|
|
507
|
+
if (lines.length > NON_INTERACTIVE_PROMPT_MAX_RESPONSES) {
|
|
508
|
+
return {
|
|
509
|
+
lines: [],
|
|
510
|
+
errorKey: 'init.error.promptInputTooManyResponses',
|
|
511
|
+
limit: NON_INTERACTIVE_PROMPT_MAX_RESPONSES,
|
|
512
|
+
};
|
|
513
|
+
}
|
|
514
|
+
return { lines };
|
|
515
|
+
}
|
|
516
|
+
function createPromptReader(reporter, lang) {
|
|
477
517
|
if (!processStdin.isTTY) {
|
|
478
|
-
const
|
|
518
|
+
const input = readNonInteractivePromptInput();
|
|
519
|
+
if (input.errorKey) {
|
|
520
|
+
const paramName = input.errorKey === 'init.error.promptInputTooLarge' ? 'maxBytes' : 'maxResponses';
|
|
521
|
+
reporter.stderr(t(lang, input.errorKey, { [paramName]: input.limit }));
|
|
522
|
+
return undefined;
|
|
523
|
+
}
|
|
524
|
+
const lines = [...input.lines];
|
|
479
525
|
return {
|
|
480
526
|
async question(prompt) {
|
|
481
527
|
reporter.stdout(prompt.trimEnd());
|
|
@@ -553,7 +599,10 @@ function addPromptedPreferenceOverride(overrides, key, value, reporter, lang) {
|
|
|
553
599
|
}
|
|
554
600
|
}
|
|
555
601
|
async function promptInitOptions(template, options, reporter, lang) {
|
|
556
|
-
const reader = createPromptReader(reporter);
|
|
602
|
+
const reader = createPromptReader(reporter, lang);
|
|
603
|
+
if (!reader) {
|
|
604
|
+
return undefined;
|
|
605
|
+
}
|
|
557
606
|
try {
|
|
558
607
|
const preferenceOverrides = [...options.preferenceOverrides];
|
|
559
608
|
const localeChoices = template.manifest.locales.map((locale) => ({
|
|
@@ -916,7 +965,14 @@ export async function runInit(args, reporter, lang = 'en') {
|
|
|
916
965
|
reporter.stderr(error instanceof Error ? error.message : String(error));
|
|
917
966
|
return 1;
|
|
918
967
|
}
|
|
919
|
-
|
|
968
|
+
let options = parsedOptions;
|
|
969
|
+
if (shouldPromptForInit(args, parsedOptions)) {
|
|
970
|
+
const promptedOptions = await promptInitOptions(template, parsedOptions, reporter, lang);
|
|
971
|
+
if (!promptedOptions) {
|
|
972
|
+
return 1;
|
|
973
|
+
}
|
|
974
|
+
options = promptedOptions;
|
|
975
|
+
}
|
|
920
976
|
const selectedLocale = options.locale ?? template.manifest.defaultLocale;
|
|
921
977
|
if (!validateInitSelection(template, options, reporter, lang)) {
|
|
922
978
|
return 1;
|
|
@@ -7,7 +7,7 @@ import { printUsageError, renderHelp } from '../lib/cli-output.js';
|
|
|
7
7
|
import { t } from '../lib/i18n.js';
|
|
8
8
|
import { resolveMustflowRoot } from '../lib/project-root.js';
|
|
9
9
|
import { getDefaultTemplate, getTemplateFiles, skillNameForTemplatePath } from '../lib/templates.js';
|
|
10
|
-
import {
|
|
10
|
+
import { readMustflowTomlFile, stringifyToml } from '../lib/toml.js';
|
|
11
11
|
import { createUpdateDiffPreview, shouldPreviewUpdateDiff } from '../lib/update-diff-preview.js';
|
|
12
12
|
const UPDATE_SCHEMA_VERSION = '1';
|
|
13
13
|
const CUSTOMIZED_LOCK_ACTION = 'customized';
|
|
@@ -250,7 +250,7 @@ function updateManifestLockAfterApply(projectRoot, appliedItems) {
|
|
|
250
250
|
const lockPath = path.join(projectRoot, MANIFEST_LOCK_RELATIVE_PATH);
|
|
251
251
|
ensureInside(projectRoot, lockPath);
|
|
252
252
|
ensureFileTargetInsideWithoutSymlinks(projectRoot, lockPath);
|
|
253
|
-
const parsed =
|
|
253
|
+
const parsed = readMustflowTomlFile(projectRoot, MANIFEST_LOCK_RELATIVE_PATH);
|
|
254
254
|
if (!isMutableTable(parsed)) {
|
|
255
255
|
throw new Error(`Invalid manifest lock: ${MANIFEST_LOCK_RELATIVE_PATH} must contain a TOML table`);
|
|
256
256
|
}
|
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
import path from 'node:path';
|
|
2
1
|
import { printUsageError, renderHelp } from '../lib/cli-output.js';
|
|
3
2
|
import { isRecord } from '../lib/command-contract.js';
|
|
4
3
|
import { t } from '../lib/i18n.js';
|
|
5
4
|
import { resolveMustflowRoot } from '../lib/project-root.js';
|
|
6
|
-
import {
|
|
5
|
+
import { readMustflowTomlFile } from '../lib/toml.js';
|
|
7
6
|
import { detectVersionSources, releaseVersioningIsEnabled, } from '../../core/version-sources.js';
|
|
8
7
|
const VERSION_SOURCES_SCHEMA_VERSION = '1';
|
|
9
8
|
export function getVersionSourcesHelp(lang = 'en') {
|
|
@@ -23,7 +22,7 @@ export function getVersionSourcesHelp(lang = 'en') {
|
|
|
23
22
|
}
|
|
24
23
|
function readPreferences(projectRoot) {
|
|
25
24
|
try {
|
|
26
|
-
const preferences =
|
|
25
|
+
const preferences = readMustflowTomlFile(projectRoot, '.mustflow/config/preferences.toml');
|
|
27
26
|
return isRecord(preferences) ? preferences : undefined;
|
|
28
27
|
}
|
|
29
28
|
catch {
|
package/dist/cli/i18n/en.js
CHANGED
|
@@ -583,6 +583,8 @@ Read these files before working:
|
|
|
583
583
|
"init.error.invalidPreference": "Invalid init preference override: {value}",
|
|
584
584
|
"init.error.invalidPreferenceValue": "Invalid value for {key}: {value}",
|
|
585
585
|
"init.error.unsupportedPreference": "Unsupported init preference setting: {key}",
|
|
586
|
+
"init.error.promptInputTooLarge": "Interactive init stdin input is too large; expected at most {maxBytes} bytes.",
|
|
587
|
+
"init.error.promptInputTooManyResponses": "Interactive init stdin input has too many responses; expected at most {maxResponses} lines.",
|
|
586
588
|
"init.prompt.locale": "Which language should mustflow documents use?",
|
|
587
589
|
"init.prompt.profile": "Which project profile should mustflow use?",
|
|
588
590
|
"init.prompt.agentLang": "Which language should agents use for final reports?",
|
package/dist/cli/i18n/es.js
CHANGED
|
@@ -583,6 +583,8 @@ Lee estos archivos antes de trabajar:
|
|
|
583
583
|
"init.error.invalidPreference": "Anulación de preferencia de inicio no válida: {value}",
|
|
584
584
|
"init.error.invalidPreferenceValue": "Valor no valido para {key}: {value}",
|
|
585
585
|
"init.error.unsupportedPreference": "Ajuste de preferencia de inicio no admitido: {key}",
|
|
586
|
+
"init.error.promptInputTooLarge": "La entrada estándar de init interactivo es demasiado grande; se esperaban como máximo {maxBytes} bytes.",
|
|
587
|
+
"init.error.promptInputTooManyResponses": "La entrada estándar de init interactivo tiene demasiadas respuestas; se esperaban como máximo {maxResponses} líneas.",
|
|
586
588
|
"init.prompt.locale": "¿Qué idioma deben usar los documentos mustflow?",
|
|
587
589
|
"init.prompt.profile": "¿Qué perfil de proyecto debe usar mustflow?",
|
|
588
590
|
"init.prompt.agentLang": "¿Qué idioma deben usar los agentes en los informes finales?",
|
package/dist/cli/i18n/fr.js
CHANGED
|
@@ -583,6 +583,8 @@ Lisez ces fichiers avant de travailler :
|
|
|
583
583
|
"init.error.invalidPreference": "Remplacement de préférence d'initialisation non valide : {value}",
|
|
584
584
|
"init.error.invalidPreferenceValue": "Valeur non valide pour {key} : {value}",
|
|
585
585
|
"init.error.unsupportedPreference": "Paramètre de préférence d'initialisation non pris en charge : {key}",
|
|
586
|
+
"init.error.promptInputTooLarge": "L'entrée standard de l'init interactif est trop volumineuse ; {maxBytes} octets maximum sont attendus.",
|
|
587
|
+
"init.error.promptInputTooManyResponses": "L'entrée standard de l'init interactif contient trop de réponses ; {maxResponses} lignes maximum sont attendues.",
|
|
586
588
|
"init.prompt.locale": "Quelle langue les documents mustflow doivent-ils utiliser ?",
|
|
587
589
|
"init.prompt.profile": "Quel profil de projet mustflow doit-il utiliser ?",
|
|
588
590
|
"init.prompt.agentLang": "Quelle langue les agents doivent-ils utiliser pour les rapports finaux ?",
|
package/dist/cli/i18n/hi.js
CHANGED
|
@@ -583,6 +583,8 @@ export const hiMessages = {
|
|
|
583
583
|
"init.error.invalidPreference": "अमान्य init preference override: {value}",
|
|
584
584
|
"init.error.invalidPreferenceValue": "{key} के लिए अमान्य मान: {value}",
|
|
585
585
|
"init.error.unsupportedPreference": "असमर्थित init preference setting: {key}",
|
|
586
|
+
"init.error.promptInputTooLarge": "Interactive init stdin input बहुत बड़ा है; अधिकतम {maxBytes} bytes अपेक्षित हैं।",
|
|
587
|
+
"init.error.promptInputTooManyResponses": "Interactive init stdin input में बहुत अधिक responses हैं; अधिकतम {maxResponses} lines अपेक्षित हैं।",
|
|
586
588
|
"init.prompt.locale": "mustflow दस्तावेज़ कौन सी भाषा उपयोग करें?",
|
|
587
589
|
"init.prompt.profile": "mustflow कौन सा project profile उपयोग करे?",
|
|
588
590
|
"init.prompt.agentLang": "एजेंट final reports के लिए कौन सी भाषा उपयोग करें?",
|
package/dist/cli/i18n/ko.js
CHANGED
|
@@ -583,6 +583,8 @@ export const koMessages = {
|
|
|
583
583
|
"init.error.invalidPreference": "초기 설정 항목 형식이 올바르지 않습니다: {value}",
|
|
584
584
|
"init.error.invalidPreferenceValue": "{key}에 사용할 수 없는 값입니다: {value}",
|
|
585
585
|
"init.error.unsupportedPreference": "지원하지 않는 초기 설정 항목입니다: {key}",
|
|
586
|
+
"init.error.promptInputTooLarge": "대화형 init 표준입력이 너무 큽니다. 최대 {maxBytes}바이트까지만 허용됩니다.",
|
|
587
|
+
"init.error.promptInputTooManyResponses": "대화형 init 표준입력 응답이 너무 많습니다. 최대 {maxResponses}줄까지만 허용됩니다.",
|
|
586
588
|
"init.prompt.locale": "mustflow 문서는 어떤 언어로 설치할까요?",
|
|
587
589
|
"init.prompt.profile": "이 저장소에는 어떤 프로젝트 유형을 사용할까요?",
|
|
588
590
|
"init.prompt.agentLang": "에이전트 최종 응답은 어떤 언어로 받을까요?",
|
package/dist/cli/i18n/zh.js
CHANGED
|
@@ -583,6 +583,8 @@ export const zhMessages = {
|
|
|
583
583
|
"init.error.invalidPreference": "无效的初始化偏好覆盖:{value}",
|
|
584
584
|
"init.error.invalidPreferenceValue": "{key} 的值无效:{value}",
|
|
585
585
|
"init.error.unsupportedPreference": "不支持的初始化偏好设置:{key}",
|
|
586
|
+
"init.error.promptInputTooLarge": "交互式 init 的标准输入过大;最多允许 {maxBytes} 字节。",
|
|
587
|
+
"init.error.promptInputTooManyResponses": "交互式 init 的标准输入响应过多;最多允许 {maxResponses} 行。",
|
|
586
588
|
"init.prompt.locale": "mustflow 文档应使用哪种语言?",
|
|
587
589
|
"init.prompt.profile": "mustflow 应使用哪个项目配置?",
|
|
588
590
|
"init.prompt.agentLang": "代理最终报告应使用哪种语言?",
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { existsSync
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
2
2
|
import { createHash } from 'node:crypto';
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
import { isRecord, readPositiveInteger, readString, readStringArray, } from './command-contract.js';
|
|
@@ -6,7 +6,8 @@ import { readRetentionStore } from '../../core/retention-policy.js';
|
|
|
6
6
|
import { toPosixPath } from './filesystem.js';
|
|
7
7
|
import { readLocalIndexPromptContext } from './local-index.js';
|
|
8
8
|
import { inspectManifestLock } from './manifest-lock.js';
|
|
9
|
-
import {
|
|
9
|
+
import { MUSTFLOW_JSON_MAX_BYTES, readMustflowTextFile, readMustflowTextFileIfExists, } from './mustflow-read.js';
|
|
10
|
+
import { readMustflowTomlFile } from './toml.js';
|
|
10
11
|
const CONTEXT_SCHEMA_VERSION = '1';
|
|
11
12
|
const COMMANDS_RELATIVE_PATH = '.mustflow/config/commands.toml';
|
|
12
13
|
const MUSTFLOW_RELATIVE_PATH = '.mustflow/config/mustflow.toml';
|
|
@@ -49,20 +50,14 @@ function safeExists(projectRoot, relativePath) {
|
|
|
49
50
|
return existsSync(resolved);
|
|
50
51
|
}
|
|
51
52
|
function safeRead(projectRoot, relativePath) {
|
|
52
|
-
|
|
53
|
-
const root = path.resolve(projectRoot);
|
|
54
|
-
const relative = path.relative(root, resolved);
|
|
55
|
-
if (relative.startsWith('..') || path.isAbsolute(relative) || !existsSync(resolved)) {
|
|
56
|
-
return null;
|
|
57
|
-
}
|
|
58
|
-
return readFileSync(resolved, 'utf8');
|
|
53
|
+
return readMustflowTextFileIfExists(projectRoot, relativePath);
|
|
59
54
|
}
|
|
60
55
|
function readTomlTableIfExists(projectRoot, relativePath) {
|
|
61
56
|
const filePath = path.join(projectRoot, ...relativePath.split('/'));
|
|
62
57
|
if (!existsSync(filePath)) {
|
|
63
58
|
return undefined;
|
|
64
59
|
}
|
|
65
|
-
const parsed =
|
|
60
|
+
const parsed = readMustflowTomlFile(projectRoot, relativePath);
|
|
66
61
|
return isRecord(parsed) ? parsed : undefined;
|
|
67
62
|
}
|
|
68
63
|
function readNestedTable(table, key) {
|
|
@@ -194,7 +189,7 @@ function readLatestRunContext(projectRoot) {
|
|
|
194
189
|
};
|
|
195
190
|
}
|
|
196
191
|
try {
|
|
197
|
-
const parsed = JSON.parse(
|
|
192
|
+
const parsed = JSON.parse(readMustflowTextFile(projectRoot, LATEST_RUN_RELATIVE_PATH, { maxBytes: MUSTFLOW_JSON_MAX_BYTES }));
|
|
198
193
|
if (!isRecord(parsed)) {
|
|
199
194
|
throw new Error('latest run receipt must contain a JSON object');
|
|
200
195
|
}
|
|
@@ -3,7 +3,8 @@ import { createHash } from 'node:crypto';
|
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
import { isRecord, readCommandContract, readString, readStringArray } from '../command-contract.js';
|
|
5
5
|
import { listFilesRecursive, toPosixPath } from '../filesystem.js';
|
|
6
|
-
import {
|
|
6
|
+
import { readMustflowTomlFile } from '../toml.js';
|
|
7
|
+
import { MUSTFLOW_JSON_MAX_BYTES, readMustflowTextFile } from '../mustflow-read.js';
|
|
7
8
|
import { collectSourceAnchorIndexRecords, hasHighRiskSourceAnchorRiskTags, } from '../../../core/source-anchor-status.js';
|
|
8
9
|
import { listSourceAnchorFiles } from '../../../core/source-anchors.js';
|
|
9
10
|
import { normalizeCommandEffects } from '../../../core/command-effects.js';
|
|
@@ -45,14 +46,14 @@ function getExistingIndexablePaths(projectRoot) {
|
|
|
45
46
|
return Array.from(paths).sort((left, right) => left.localeCompare(right));
|
|
46
47
|
}
|
|
47
48
|
function readText(projectRoot, relativePath) {
|
|
48
|
-
return
|
|
49
|
+
return readMustflowTextFile(projectRoot, relativePath);
|
|
49
50
|
}
|
|
50
51
|
function readMustflowToml(projectRoot) {
|
|
51
52
|
const mustflowPath = path.join(projectRoot, ...MUSTFLOW_RELATIVE_PATH.split('/'));
|
|
52
53
|
if (!existsSync(mustflowPath)) {
|
|
53
54
|
return undefined;
|
|
54
55
|
}
|
|
55
|
-
const parsed =
|
|
56
|
+
const parsed = readMustflowTomlFile(projectRoot, MUSTFLOW_RELATIVE_PATH);
|
|
56
57
|
return isRecord(parsed) ? parsed : undefined;
|
|
57
58
|
}
|
|
58
59
|
function readIndexToml(projectRoot) {
|
|
@@ -60,7 +61,7 @@ function readIndexToml(projectRoot) {
|
|
|
60
61
|
if (!existsSync(indexConfigPath)) {
|
|
61
62
|
return undefined;
|
|
62
63
|
}
|
|
63
|
-
const parsed =
|
|
64
|
+
const parsed = readMustflowTomlFile(projectRoot, INDEX_CONFIG_RELATIVE_PATH);
|
|
64
65
|
return isRecord(parsed) ? parsed : undefined;
|
|
65
66
|
}
|
|
66
67
|
function readNestedTable(table, key) {
|
|
@@ -227,7 +228,7 @@ function collectSkillRoutes(projectRoot) {
|
|
|
227
228
|
if (!existsSync(indexPath)) {
|
|
228
229
|
return [];
|
|
229
230
|
}
|
|
230
|
-
const content =
|
|
231
|
+
const content = readMustflowTextFile(projectRoot, '.mustflow/skills/INDEX.md');
|
|
231
232
|
const routes = [];
|
|
232
233
|
let inRouteTable = false;
|
|
233
234
|
for (const line of content.split(/\r?\n/u)) {
|
|
@@ -455,10 +456,11 @@ function detectLocalSearchCapabilities(database) {
|
|
|
455
456
|
function isJsonRecord(value) {
|
|
456
457
|
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
457
458
|
}
|
|
458
|
-
function readJsonRecord(
|
|
459
|
+
function readJsonRecord(projectRoot, relativePath) {
|
|
459
460
|
try {
|
|
460
|
-
const
|
|
461
|
-
|
|
461
|
+
const content = readMustflowTextFile(projectRoot, relativePath, { maxBytes: MUSTFLOW_JSON_MAX_BYTES });
|
|
462
|
+
const parsed = JSON.parse(content);
|
|
463
|
+
return isJsonRecord(parsed) ? { content, value: parsed } : null;
|
|
462
464
|
}
|
|
463
465
|
catch {
|
|
464
466
|
return null;
|
|
@@ -567,8 +569,8 @@ function createVerificationEvidenceIndex(projectRoot) {
|
|
|
567
569
|
failureFingerprintReadModels: [],
|
|
568
570
|
};
|
|
569
571
|
}
|
|
570
|
-
const
|
|
571
|
-
if (!
|
|
572
|
+
const latestRecord = readJsonRecord(projectRoot, LATEST_RUN_STATE_RELATIVE_PATH);
|
|
573
|
+
if (!latestRecord) {
|
|
572
574
|
return {
|
|
573
575
|
summaries: [],
|
|
574
576
|
verificationPlans: [],
|
|
@@ -586,7 +588,8 @@ function createVerificationEvidenceIndex(projectRoot) {
|
|
|
586
588
|
failureFingerprintReadModels: [],
|
|
587
589
|
};
|
|
588
590
|
}
|
|
589
|
-
const
|
|
591
|
+
const latest = latestRecord.value;
|
|
592
|
+
const sourceHash = sha256Bytes(Buffer.from(latestRecord.content));
|
|
590
593
|
const command = stringField(latest, 'command') ?? 'unknown';
|
|
591
594
|
const kind = stringField(latest, 'kind') ?? (command === 'verify' ? 'verify_run_summary' : 'run_receipt');
|
|
592
595
|
const evidenceModel = recordField(latest, 'evidence_model');
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { readUtf8FileInsideWithoutSymlinks } from './filesystem.js';
|
|
4
|
+
export const MUSTFLOW_TEXT_MAX_BYTES = 1024 * 1024;
|
|
5
|
+
export const MUSTFLOW_TOML_MAX_BYTES = 256 * 1024;
|
|
6
|
+
export const MUSTFLOW_JSON_MAX_BYTES = 1024 * 1024;
|
|
7
|
+
export function mustflowProjectPath(projectRoot, relativePath) {
|
|
8
|
+
return path.join(projectRoot, ...relativePath.split('/'));
|
|
9
|
+
}
|
|
10
|
+
function missingPath(error) {
|
|
11
|
+
return error instanceof Error && 'code' in error && error.code === 'ENOENT';
|
|
12
|
+
}
|
|
13
|
+
export function readMustflowTextFile(projectRoot, relativePath, options = {}) {
|
|
14
|
+
return readUtf8FileInsideWithoutSymlinks(projectRoot, mustflowProjectPath(projectRoot, relativePath), { maxBytes: options.maxBytes ?? MUSTFLOW_TEXT_MAX_BYTES });
|
|
15
|
+
}
|
|
16
|
+
export function readMustflowTextFileResult(projectRoot, relativePath, options = {}) {
|
|
17
|
+
const filePath = mustflowProjectPath(projectRoot, relativePath);
|
|
18
|
+
if (!existsSync(filePath)) {
|
|
19
|
+
return { ok: false, exists: false, error: null };
|
|
20
|
+
}
|
|
21
|
+
try {
|
|
22
|
+
return {
|
|
23
|
+
ok: true,
|
|
24
|
+
content: readMustflowTextFile(projectRoot, relativePath, options),
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
catch (error) {
|
|
28
|
+
if (missingPath(error)) {
|
|
29
|
+
return { ok: false, exists: false, error: null };
|
|
30
|
+
}
|
|
31
|
+
return {
|
|
32
|
+
ok: false,
|
|
33
|
+
exists: true,
|
|
34
|
+
error: error instanceof Error ? error.message : String(error),
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
export function readMustflowTextFileIfExists(projectRoot, relativePath, options = {}) {
|
|
39
|
+
const result = readMustflowTextFileResult(projectRoot, relativePath, options);
|
|
40
|
+
return result.ok ? result.content : null;
|
|
41
|
+
}
|
|
@@ -2,8 +2,7 @@ import { existsSync } from 'node:fs';
|
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
function hasMustflowMarker(directoryPath) {
|
|
4
4
|
return (existsSync(path.join(directoryPath, '.mustflow', 'config', 'mustflow.toml')) ||
|
|
5
|
-
existsSync(path.join(directoryPath, '.mustflow', 'config', 'commands.toml'))
|
|
6
|
-
existsSync(path.join(directoryPath, '.mustflow')));
|
|
5
|
+
existsSync(path.join(directoryPath, '.mustflow', 'config', 'commands.toml')));
|
|
7
6
|
}
|
|
8
7
|
export function findMustflowRoot(startPath = process.cwd()) {
|
|
9
8
|
let current = path.resolve(startPath);
|