mustflow 2.18.7 → 2.18.20
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 +4 -0
- package/dist/cli/commands/dashboard.js +68 -12
- package/dist/cli/commands/init.js +20 -20
- package/dist/cli/commands/run.js +1 -8
- package/dist/cli/commands/update.js +6 -11
- package/dist/cli/lib/dashboard-preferences.js +8 -6
- package/dist/cli/lib/filesystem.js +11 -1
- package/dist/cli/lib/local-index/index.js +30 -9
- package/dist/cli/lib/manifest-lock.js +38 -12
- package/dist/core/command-classification.js +0 -16
- package/dist/core/command-contract-rules.js +17 -3
- package/package.json +1 -1
- package/templates/default/i18n.toml +42 -6
- package/templates/default/locales/en/.mustflow/skills/INDEX.md +11 -5
- package/templates/default/locales/en/.mustflow/skills/cli-output-contract-review/SKILL.md +146 -0
- package/templates/default/locales/en/.mustflow/skills/command-contract-authoring/SKILL.md +121 -0
- package/templates/default/locales/en/.mustflow/skills/cross-platform-filesystem-safety/SKILL.md +137 -0
- package/templates/default/locales/en/.mustflow/skills/dependency-reality-check/SKILL.md +19 -6
- package/templates/default/locales/en/.mustflow/skills/external-prompt-injection-defense/SKILL.md +26 -10
- package/templates/default/locales/en/.mustflow/skills/llm-service-ux-review/SKILL.md +139 -0
- package/templates/default/locales/en/.mustflow/skills/process-execution-safety/SKILL.md +120 -0
- package/templates/default/locales/en/.mustflow/skills/routes.toml +38 -2
- package/templates/default/locales/en/.mustflow/skills/search-ad-content-authoring/SKILL.md +148 -0
- package/templates/default/locales/en/.mustflow/skills/security-privacy-review/SKILL.md +46 -12
- package/templates/default/locales/en/.mustflow/skills/security-regression-tests/SKILL.md +43 -12
- package/templates/default/locales/en/.mustflow/skills/ui-quality-gate/SKILL.md +34 -14
- package/templates/default/manifest.toml +23 -1
- package/dist/cli/commands/run/builtin-dispatch.js +0 -92
package/README.md
CHANGED
|
@@ -228,6 +228,10 @@ your-project/
|
|
|
228
228
|
│ └─ SKILL.md
|
|
229
229
|
├─ vertical-slice-tdd/
|
|
230
230
|
│ └─ SKILL.md
|
|
231
|
+
├─ llm-service-ux-review/
|
|
232
|
+
│ └─ SKILL.md
|
|
233
|
+
├─ search-ad-content-authoring/
|
|
234
|
+
│ └─ SKILL.md
|
|
231
235
|
├─ ui-quality-gate/
|
|
232
236
|
│ └─ SKILL.md
|
|
233
237
|
├─ visual-review-artifact/
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { randomBytes } from 'node:crypto';
|
|
2
|
-
import { existsSync, readFileSync } from 'node:fs';
|
|
2
|
+
import { existsSync, readFileSync, statSync } from 'node:fs';
|
|
3
3
|
import http from 'node:http';
|
|
4
4
|
import path from 'node:path';
|
|
5
5
|
import { openPathInFileManager, openUrlInBrowser } from '../lib/browser-open.js';
|
|
@@ -14,8 +14,8 @@ import { readGitChangedFiles } from '../lib/git-changes.js';
|
|
|
14
14
|
import { isRecord, readCommandContract, readPositiveInteger, readString, readStringArray, } from '../lib/command-contract.js';
|
|
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
|
-
import { inspectManifestLock } from '../lib/manifest-lock.js';
|
|
18
|
-
import { readLatestLocalVerificationReadModelQueries, readLocalCommandEffectGraphs, } from '../lib/local-index.js';
|
|
17
|
+
import { MANIFEST_LOCK_RELATIVE_PATH, inspectManifestLock } from '../lib/manifest-lock.js';
|
|
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
21
|
import { resolveMustflowRoot } from '../lib/project-root.js';
|
|
@@ -36,6 +36,61 @@ const RELEASE_FILE_PATTERNS = [
|
|
|
36
36
|
];
|
|
37
37
|
const SKILL_INDEX_RELATIVE_PATH = '.mustflow/skills/INDEX.md';
|
|
38
38
|
const LATEST_RUN_RELATIVE_PATH = '.mustflow/state/runs/latest.json';
|
|
39
|
+
const COMMANDS_RELATIVE_PATH = '.mustflow/config/commands.toml';
|
|
40
|
+
const AGENTS_RELATIVE_PATH = 'AGENTS.md';
|
|
41
|
+
const STATUS_BLOCK_CACHE_TTL_MS = 750;
|
|
42
|
+
const dashboardStatusBlockCache = new Map();
|
|
43
|
+
function dashboardStatusBlockCacheKey(projectRoot, blockName) {
|
|
44
|
+
return `${path.resolve(projectRoot)}\0${blockName}`;
|
|
45
|
+
}
|
|
46
|
+
function readFileSignature(filePath) {
|
|
47
|
+
try {
|
|
48
|
+
const stat = statSync(filePath);
|
|
49
|
+
return `${stat.mtimeMs}:${stat.size}`;
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
return 'missing';
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
function readProjectFileSignature(projectRoot, relativePath) {
|
|
56
|
+
return `${relativePath}=${readFileSignature(path.join(projectRoot, ...relativePath.split('/')))}`;
|
|
57
|
+
}
|
|
58
|
+
function readStatusBlockSignature(projectRoot, relativePaths) {
|
|
59
|
+
return relativePaths.map((relativePath) => readProjectFileSignature(projectRoot, relativePath)).join('|');
|
|
60
|
+
}
|
|
61
|
+
function readLocalIndexSignature(projectRoot) {
|
|
62
|
+
return `local_index=${readFileSignature(getLocalIndexDatabasePath(projectRoot))}`;
|
|
63
|
+
}
|
|
64
|
+
function readDashboardStatusBlock(projectRoot, blockName, signature, readBlock) {
|
|
65
|
+
const key = dashboardStatusBlockCacheKey(projectRoot, blockName);
|
|
66
|
+
const cached = dashboardStatusBlockCache.get(key);
|
|
67
|
+
const now = Date.now();
|
|
68
|
+
if (cached && cached.signature === signature && cached.expiresAt > now) {
|
|
69
|
+
return cached.value;
|
|
70
|
+
}
|
|
71
|
+
const value = readBlock();
|
|
72
|
+
dashboardStatusBlockCache.set(key, {
|
|
73
|
+
signature,
|
|
74
|
+
expiresAt: Date.now() + STATUS_BLOCK_CACHE_TTL_MS,
|
|
75
|
+
value,
|
|
76
|
+
});
|
|
77
|
+
return value;
|
|
78
|
+
}
|
|
79
|
+
async function readDashboardStatusBlockAsync(projectRoot, blockName, signature, readBlock) {
|
|
80
|
+
const key = dashboardStatusBlockCacheKey(projectRoot, blockName);
|
|
81
|
+
const cached = dashboardStatusBlockCache.get(key);
|
|
82
|
+
const now = Date.now();
|
|
83
|
+
if (cached && cached.signature === signature && cached.expiresAt > now) {
|
|
84
|
+
return cached.value;
|
|
85
|
+
}
|
|
86
|
+
const value = await readBlock();
|
|
87
|
+
dashboardStatusBlockCache.set(key, {
|
|
88
|
+
signature,
|
|
89
|
+
expiresAt: Date.now() + STATUS_BLOCK_CACHE_TTL_MS,
|
|
90
|
+
value,
|
|
91
|
+
});
|
|
92
|
+
return value;
|
|
93
|
+
}
|
|
39
94
|
function readFrontmatterLines(content) {
|
|
40
95
|
if (!content.startsWith('---')) {
|
|
41
96
|
return [];
|
|
@@ -677,16 +732,17 @@ function renderRunHistoryResponse(projectRoot) {
|
|
|
677
732
|
}
|
|
678
733
|
async function renderStatusResponse(projectRoot) {
|
|
679
734
|
const context = getAgentContext(projectRoot);
|
|
680
|
-
const manifest = inspectManifestLock(projectRoot);
|
|
735
|
+
const manifest = readDashboardStatusBlock(projectRoot, 'manifest', readStatusBlockSignature(projectRoot, [MANIFEST_LOCK_RELATIVE_PATH]), () => inspectManifestLock(projectRoot));
|
|
681
736
|
const lock = manifest.readResult.kind === 'present' ? manifest.readResult.lock : undefined;
|
|
682
|
-
const activeDocuments = listDocReviewEntries(projectRoot);
|
|
683
|
-
const
|
|
684
|
-
const
|
|
737
|
+
const activeDocuments = readDashboardStatusBlock(projectRoot, 'doc_review', readStatusBlockSignature(projectRoot, [DOC_REVIEW_LEDGER_RELATIVE_PATH]), () => listDocReviewEntries(projectRoot));
|
|
738
|
+
const rawCommandContractSignature = readStatusBlockSignature(projectRoot, [COMMANDS_RELATIVE_PATH]);
|
|
739
|
+
const rawCommandContract = readDashboardStatusBlock(projectRoot, 'raw_command_contract', rawCommandContractSignature, () => readDashboardCommandContract(projectRoot));
|
|
740
|
+
const commandContract = await readDashboardStatusBlockAsync(projectRoot, 'command_contract', `${rawCommandContractSignature}|${readLocalIndexSignature(projectRoot)}`, () => renderCommandContractResponse(projectRoot, rawCommandContract));
|
|
685
741
|
const gitChangedFilesResult = readGitChangedFiles(projectRoot);
|
|
686
742
|
const gitChangedFiles = gitChangedFilesResult.ok ? gitChangedFilesResult.files : [];
|
|
687
743
|
const packageMetadata = readPackageMetadata();
|
|
688
744
|
const verification = createDashboardVerificationSnapshot(projectRoot, rawCommandContract, commandContract.intents, gitChangedFiles, manifest.changedFiles, manifest.missingFiles);
|
|
689
|
-
const readModel = await readLatestLocalVerificationReadModelQueries(projectRoot);
|
|
745
|
+
const readModel = await readDashboardStatusBlockAsync(projectRoot, 'verification_read_model', readLocalIndexSignature(projectRoot), () => readLatestLocalVerificationReadModelQueries(projectRoot));
|
|
690
746
|
return {
|
|
691
747
|
schema_version: '1',
|
|
692
748
|
command: 'dashboard status',
|
|
@@ -696,12 +752,12 @@ async function renderStatusResponse(projectRoot) {
|
|
|
696
752
|
release: {
|
|
697
753
|
package_name: packageMetadata.name,
|
|
698
754
|
package_version: packageMetadata.version,
|
|
699
|
-
version_sources: detectVersionSources(projectRoot),
|
|
755
|
+
version_sources: readDashboardStatusBlock(projectRoot, 'version_sources', readStatusBlockSignature(projectRoot, ['package.json']), () => detectVersionSources(projectRoot)),
|
|
700
756
|
release_sensitive_changed_files: matchingFiles(gitChangedFiles, RELEASE_FILE_PATTERNS),
|
|
701
757
|
},
|
|
702
|
-
update: renderUpdateResponse(projectRoot),
|
|
703
|
-
run_history: renderRunHistoryResponse(projectRoot),
|
|
704
|
-
skills: renderSkillsResponse(projectRoot),
|
|
758
|
+
update: readDashboardStatusBlock(projectRoot, 'update', readStatusBlockSignature(projectRoot, [AGENTS_RELATIVE_PATH, MANIFEST_LOCK_RELATIVE_PATH]), () => renderUpdateResponse(projectRoot)),
|
|
759
|
+
run_history: readDashboardStatusBlock(projectRoot, 'run_history', readStatusBlockSignature(projectRoot, [LATEST_RUN_RELATIVE_PATH]), () => renderRunHistoryResponse(projectRoot)),
|
|
760
|
+
skills: readDashboardStatusBlock(projectRoot, 'skills', readStatusBlockSignature(projectRoot, [SKILL_INDEX_RELATIVE_PATH]), () => renderSkillsResponse(projectRoot)),
|
|
705
761
|
tracked_files: lock?.files.length ?? 0,
|
|
706
762
|
changed_files: manifest.changedFiles,
|
|
707
763
|
missing_files: manifest.missingFiles,
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { existsSync, readFileSync } 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';
|
|
5
5
|
import { printUsageError, renderHelp } from '../lib/cli-output.js';
|
|
6
|
-
import { ensureFileTargetInsideWithoutSymlinks, ensureInside, readUtf8FileInsideWithoutSymlinks, writeUtf8FileInsideWithoutSymlinks, } from '../lib/filesystem.js';
|
|
6
|
+
import { copyFileInsideWithoutSymlinks, ensureFileTargetInsideWithoutSymlinks, ensureInside, readUtf8FileInsideWithoutSymlinks, writeUtf8FileInsideWithoutSymlinks, } from '../lib/filesystem.js';
|
|
7
7
|
import { localeMessage, t } from '../lib/i18n.js';
|
|
8
8
|
import { isLocaleTag } from '../lib/locale-tags.js';
|
|
9
9
|
import { MANIFEST_LOCK_RELATIVE_PATH, sha256File } from '../lib/manifest-lock.js';
|
|
@@ -463,8 +463,11 @@ function parseOptions(args, reporter, lang) {
|
|
|
463
463
|
preferenceOverrides,
|
|
464
464
|
};
|
|
465
465
|
}
|
|
466
|
-
function
|
|
467
|
-
return
|
|
466
|
+
function readTemplateSourceText(templateRoot, sourcePath) {
|
|
467
|
+
return readUtf8FileInsideWithoutSymlinks(templateRoot, sourcePath);
|
|
468
|
+
}
|
|
469
|
+
function sameTemplateFileContent(projectRoot, templateRoot, source, targetPath) {
|
|
470
|
+
return (source.content ?? readTemplateSourceText(templateRoot, source.sourcePath)) === readUtf8FileInsideWithoutSymlinks(projectRoot, targetPath);
|
|
468
471
|
}
|
|
469
472
|
function formatLocaleChoice(locale) {
|
|
470
473
|
const label = LOCALE_LABELS[locale] ?? locale;
|
|
@@ -605,11 +608,11 @@ async function promptInitOptions(template, options, reporter, lang) {
|
|
|
605
608
|
function escapeRegExp(value) {
|
|
606
609
|
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
607
610
|
}
|
|
608
|
-
function planStatus(projectRoot, source, targetPath, options) {
|
|
611
|
+
function planStatus(projectRoot, templateRoot, source, targetPath, options) {
|
|
609
612
|
if (!existsSync(targetPath)) {
|
|
610
613
|
return 'create';
|
|
611
614
|
}
|
|
612
|
-
if (sameTemplateFileContent(projectRoot, source, targetPath)) {
|
|
615
|
+
if (sameTemplateFileContent(projectRoot, templateRoot, source, targetPath)) {
|
|
613
616
|
return 'unchanged';
|
|
614
617
|
}
|
|
615
618
|
if (options.force) {
|
|
@@ -620,17 +623,15 @@ function planStatus(projectRoot, source, targetPath, options) {
|
|
|
620
623
|
}
|
|
621
624
|
return 'conflict';
|
|
622
625
|
}
|
|
623
|
-
function writeTemplateFile(projectRoot, source, targetPath) {
|
|
626
|
+
function writeTemplateFile(projectRoot, templateRoot, source, targetPath) {
|
|
624
627
|
if (source.content !== undefined) {
|
|
625
628
|
writeUtf8FileInsideWithoutSymlinks(projectRoot, targetPath, source.content);
|
|
626
629
|
return;
|
|
627
630
|
}
|
|
628
|
-
|
|
629
|
-
mkdirSync(path.dirname(targetPath), { recursive: true });
|
|
630
|
-
copyFileSync(source.sourcePath, targetPath);
|
|
631
|
+
copyFileInsideWithoutSymlinks(templateRoot, source.sourcePath, projectRoot, targetPath);
|
|
631
632
|
}
|
|
632
633
|
function writePlannedFile(projectRoot, file) {
|
|
633
|
-
writeTemplateFile(projectRoot, file, file.targetPath);
|
|
634
|
+
writeTemplateFile(projectRoot, file.sourceRoot, file, file.targetPath);
|
|
634
635
|
}
|
|
635
636
|
function gitignoreFragmentPath(template) {
|
|
636
637
|
return path.join(template.templateRoot, template.manifest.commonRoot, GITIGNORE_FRAGMENT_RELATIVE_PATH);
|
|
@@ -646,11 +647,11 @@ function mergeGitignoreContent(existingContent, fragmentContent) {
|
|
|
646
647
|
}
|
|
647
648
|
return `${existingContent.trimEnd()}\n\n${normalizedFragment}\n`;
|
|
648
649
|
}
|
|
649
|
-
function planGitignoreStatus(projectRoot, sourcePath, targetPath) {
|
|
650
|
+
function planGitignoreStatus(projectRoot, sourceRoot, sourcePath, targetPath) {
|
|
650
651
|
if (!existsSync(targetPath)) {
|
|
651
652
|
return 'create';
|
|
652
653
|
}
|
|
653
|
-
const mergedContent = mergeGitignoreContent(readUtf8FileInsideWithoutSymlinks(projectRoot, targetPath),
|
|
654
|
+
const mergedContent = mergeGitignoreContent(readUtf8FileInsideWithoutSymlinks(projectRoot, targetPath), readTemplateSourceText(sourceRoot, sourcePath));
|
|
654
655
|
return mergedContent === readUtf8FileInsideWithoutSymlinks(projectRoot, targetPath) ? 'unchanged' : 'merge';
|
|
655
656
|
}
|
|
656
657
|
function renderPlanVerb(status) {
|
|
@@ -699,10 +700,11 @@ function buildPlannedFiles(template, selectedLocale, targetRoot, options) {
|
|
|
699
700
|
return {
|
|
700
701
|
relativePath: source.relativePath,
|
|
701
702
|
sourcePath: source.sourcePath,
|
|
703
|
+
sourceRoot: template.templateRoot,
|
|
702
704
|
sourceKind: source.sourceKind,
|
|
703
705
|
content: source.content,
|
|
704
706
|
targetPath,
|
|
705
|
-
status: planStatus(targetRoot, source, targetPath, options),
|
|
707
|
+
status: planStatus(targetRoot, template.templateRoot, source, targetPath, options),
|
|
706
708
|
lock: true,
|
|
707
709
|
};
|
|
708
710
|
});
|
|
@@ -714,9 +716,10 @@ function buildPlannedFiles(template, selectedLocale, targetRoot, options) {
|
|
|
714
716
|
plannedFiles.push({
|
|
715
717
|
relativePath: GITIGNORE_RELATIVE_PATH,
|
|
716
718
|
sourcePath,
|
|
719
|
+
sourceRoot: template.templateRoot,
|
|
717
720
|
sourceKind: 'common',
|
|
718
721
|
targetPath,
|
|
719
|
-
status: planGitignoreStatus(targetRoot, sourcePath, targetPath),
|
|
722
|
+
status: planGitignoreStatus(targetRoot, template.templateRoot, sourcePath, targetPath),
|
|
720
723
|
lock: false,
|
|
721
724
|
});
|
|
722
725
|
return plannedFiles;
|
|
@@ -730,10 +733,7 @@ function backupConflictingFiles(projectRoot, conflicts) {
|
|
|
730
733
|
for (const conflict of conflicts) {
|
|
731
734
|
const backupPath = path.join(backupRoot, conflict.relativePath);
|
|
732
735
|
ensureInside(backupRoot, backupPath);
|
|
733
|
-
|
|
734
|
-
ensureFileTargetInsideWithoutSymlinks(projectRoot, backupPath, { allowMissingLeaf: true });
|
|
735
|
-
mkdirSync(path.dirname(backupPath), { recursive: true });
|
|
736
|
-
copyFileSync(conflict.targetPath, backupPath);
|
|
736
|
+
copyFileInsideWithoutSymlinks(projectRoot, conflict.targetPath, projectRoot, backupPath);
|
|
737
737
|
}
|
|
738
738
|
return backupRoot;
|
|
739
739
|
}
|
|
@@ -960,7 +960,7 @@ export async function runInit(args, reporter, lang = 'en') {
|
|
|
960
960
|
if (file.status === 'merge') {
|
|
961
961
|
ensureFileTargetInsideWithoutSymlinks(targetRoot, file.targetPath);
|
|
962
962
|
const mergedContent = file.relativePath === GITIGNORE_RELATIVE_PATH
|
|
963
|
-
? mergeGitignoreContent(readUtf8FileInsideWithoutSymlinks(targetRoot, file.targetPath),
|
|
963
|
+
? mergeGitignoreContent(readUtf8FileInsideWithoutSymlinks(targetRoot, file.targetPath), readTemplateSourceText(file.sourceRoot, file.sourcePath))
|
|
964
964
|
: mergeAgentsContent(readUtf8FileInsideWithoutSymlinks(targetRoot, file.targetPath), selectedLocale);
|
|
965
965
|
writeUtf8FileInsideWithoutSymlinks(targetRoot, file.targetPath, mergedContent);
|
|
966
966
|
merged += 1;
|
package/dist/cli/commands/run.js
CHANGED
|
@@ -5,12 +5,11 @@ import { readCommandContract, readMustflowConfigIfExists } from '../../core/conf
|
|
|
5
5
|
import { resolveRunReceiptRetentionPolicy } from '../../core/retention-policy.js';
|
|
6
6
|
import { t } from '../lib/i18n.js';
|
|
7
7
|
import { resolveMustflowRoot } from '../lib/project-root.js';
|
|
8
|
-
import { createRunPlan, createRunPreview,
|
|
8
|
+
import { createRunPlan, createRunPreview, renderRunPreviewText, } from '../lib/run-plan.js';
|
|
9
9
|
import { writeRunReceipt, } from '../../core/run-receipt.js';
|
|
10
10
|
import { recordRunPerformanceHistory } from '../../core/run-performance-history.js';
|
|
11
11
|
import { RunProfiler } from '../../core/run-profile.js';
|
|
12
12
|
import { finishRunWriteTracking, startRunWriteTracking } from '../../core/run-write-drift.js';
|
|
13
|
-
import { runBuiltinArgvInProcess } from './run/builtin-dispatch.js';
|
|
14
13
|
import { getRunStatus, runArgvCommandStreaming, runShellCommandStreaming } from './run/executor.js';
|
|
15
14
|
import { emitOutput, isOutputLimitExceededError } from './run/output.js';
|
|
16
15
|
import { createPendingTimeoutTermination, getKillMethod, terminateProcessTree } from './run/process-tree.js';
|
|
@@ -179,12 +178,6 @@ export async function runRun(args, reporter, lang = 'en', options = {}) {
|
|
|
179
178
|
const childStartedAtMs = performance.now();
|
|
180
179
|
const startedAt = new Date();
|
|
181
180
|
const result = await profiler.measureAsync('child_command', async () => {
|
|
182
|
-
if (plan.commandArgv && isMustflowBuiltinIntent(plan.intent)) {
|
|
183
|
-
const builtinResult = await runBuiltinArgvInProcess(plan.commandArgv, plan.cwd, lang);
|
|
184
|
-
if (builtinResult) {
|
|
185
|
-
return builtinResult;
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
181
|
if (plan.commandArgv) {
|
|
189
182
|
streamedOutput = !json;
|
|
190
183
|
return runArgvCommandStreaming(plan.argvCommand, plan.cwd, env, plan.timeoutSeconds, plan.killAfterSeconds, plan.maxOutputBytes, stdoutTailBytes, stderrTailBytes, reporter, !json, true);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { createHash } from 'node:crypto';
|
|
2
|
-
import {
|
|
2
|
+
import { existsSync } from 'node:fs';
|
|
3
3
|
import path from 'node:path';
|
|
4
|
-
import { ensureFileTargetInsideWithoutSymlinks, ensureInside, writeUtf8FileInsideWithoutSymlinks, } from '../lib/filesystem.js';
|
|
4
|
+
import { copyFileInsideWithoutSymlinks, ensureFileTargetInsideWithoutSymlinks, ensureInside, writeUtf8FileInsideWithoutSymlinks, } from '../lib/filesystem.js';
|
|
5
5
|
import { MANIFEST_LOCK_RELATIVE_PATH, readManifestLock, sha256File } from '../lib/manifest-lock.js';
|
|
6
6
|
import { printUsageError, renderHelp } from '../lib/cli-output.js';
|
|
7
7
|
import { t } from '../lib/i18n.js';
|
|
@@ -70,14 +70,12 @@ function lockedTemplateSkillNames(files) {
|
|
|
70
70
|
function getInstalledTemplateFiles(projectRoot, template, lock) {
|
|
71
71
|
return getTemplateFiles(template, lock.templateLocale ?? template.manifest.defaultLocale, lock.templateProfile ?? template.manifest.defaultProfile, { extraSkillNames: lockedTemplateSkillNames(lock.files) });
|
|
72
72
|
}
|
|
73
|
-
function writeTemplateFile(projectRoot, source, targetPath) {
|
|
73
|
+
function writeTemplateFile(projectRoot, templateRoot, source, targetPath) {
|
|
74
74
|
if (source.content !== undefined) {
|
|
75
75
|
writeUtf8FileInsideWithoutSymlinks(projectRoot, targetPath, source.content);
|
|
76
76
|
return;
|
|
77
77
|
}
|
|
78
|
-
|
|
79
|
-
mkdirSync(path.dirname(targetPath), { recursive: true });
|
|
80
|
-
copyFileSync(source.sourcePath, targetPath);
|
|
78
|
+
copyFileInsideWithoutSymlinks(templateRoot, source.sourcePath, projectRoot, targetPath);
|
|
81
79
|
}
|
|
82
80
|
function templateTargetSafetyIssue(projectRoot, targetPath, allowMissingLeaf) {
|
|
83
81
|
try {
|
|
@@ -223,7 +221,7 @@ function copyTemplateFile(projectRoot, relativePath) {
|
|
|
223
221
|
ensureInside(template.templateRoot, source.sourcePath);
|
|
224
222
|
ensureInside(projectRoot, targetPath);
|
|
225
223
|
ensureFileTargetInsideWithoutSymlinks(projectRoot, targetPath, { allowMissingLeaf: true });
|
|
226
|
-
writeTemplateFile(projectRoot, source, targetPath);
|
|
224
|
+
writeTemplateFile(projectRoot, template.templateRoot, source, targetPath);
|
|
227
225
|
}
|
|
228
226
|
function backupUpdateFiles(projectRoot, items, reporter, lang) {
|
|
229
227
|
const updateItems = items.filter((item) => item.action === 'update');
|
|
@@ -237,10 +235,7 @@ function backupUpdateFiles(projectRoot, items, reporter, lang) {
|
|
|
237
235
|
const backupPath = path.join(backupRoot, item.relativePath);
|
|
238
236
|
ensureInside(projectRoot, sourcePath);
|
|
239
237
|
ensureInside(backupRoot, backupPath);
|
|
240
|
-
|
|
241
|
-
ensureFileTargetInsideWithoutSymlinks(projectRoot, backupPath, { allowMissingLeaf: true });
|
|
242
|
-
mkdirSync(path.dirname(backupPath), { recursive: true });
|
|
243
|
-
copyFileSync(sourcePath, backupPath);
|
|
238
|
+
copyFileInsideWithoutSymlinks(projectRoot, sourcePath, projectRoot, backupPath);
|
|
244
239
|
}
|
|
245
240
|
reporter.stdout(t(lang, 'update.backup.files', {
|
|
246
241
|
count: updateItems.length,
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import { existsSync
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { isRecord } from './command-contract.js';
|
|
4
|
+
import { readUtf8FileInsideWithoutSymlinks, writeUtf8FileInsideWithoutSymlinks } from './filesystem.js';
|
|
4
5
|
import { isLocaleTag } from './locale-tags.js';
|
|
5
|
-
import { markManifestLockFileCustomized } from './manifest-lock.js';
|
|
6
|
+
import { ensureManifestLockTargetSafe, markManifestLockFileCustomized } from './manifest-lock.js';
|
|
6
7
|
import { COMMIT_MESSAGE_STYLES, TEST_AUTHORING_POLICIES } from './preferences-options.js';
|
|
7
|
-
import {
|
|
8
|
+
import { parseTomlText } from './toml.js';
|
|
8
9
|
const PREFERENCES_RELATIVE_PATH = '.mustflow/config/preferences.toml';
|
|
9
10
|
export const DASHBOARD_PREFERENCE_SETTINGS = [
|
|
10
11
|
{
|
|
@@ -281,7 +282,7 @@ export function readDashboardPreferences(projectRoot) {
|
|
|
281
282
|
if (!existsSync(preferencesPath)) {
|
|
282
283
|
throw new Error('Missing .mustflow/config/preferences.toml. Run mf init first or switch to a mustflow root.');
|
|
283
284
|
}
|
|
284
|
-
const parsed =
|
|
285
|
+
const parsed = parseTomlText(readUtf8FileInsideWithoutSymlinks(projectRoot, preferencesPath));
|
|
285
286
|
if (!isRecord(parsed)) {
|
|
286
287
|
throw new Error('.mustflow/config/preferences.toml must contain a TOML table.');
|
|
287
288
|
}
|
|
@@ -390,7 +391,7 @@ function coerceUpdateValue(definition, value) {
|
|
|
390
391
|
export function updateDashboardPreferences(projectRoot, updates) {
|
|
391
392
|
const preferencesPath = getPreferencesPath(projectRoot);
|
|
392
393
|
const definitionsById = new Map(DASHBOARD_PREFERENCE_SETTINGS.map((definition) => [definition.id, definition]));
|
|
393
|
-
let content =
|
|
394
|
+
let content = readUtf8FileInsideWithoutSymlinks(projectRoot, preferencesPath);
|
|
394
395
|
for (const update of updates) {
|
|
395
396
|
const definition = definitionsById.get(update.id);
|
|
396
397
|
if (!definition) {
|
|
@@ -399,7 +400,8 @@ export function updateDashboardPreferences(projectRoot, updates) {
|
|
|
399
400
|
const value = coerceUpdateValue(definition, update.value);
|
|
400
401
|
content = setTomlScalar(content, definition.path, value);
|
|
401
402
|
}
|
|
402
|
-
|
|
403
|
+
ensureManifestLockTargetSafe(projectRoot);
|
|
404
|
+
writeUtf8FileInsideWithoutSymlinks(projectRoot, preferencesPath, content);
|
|
403
405
|
markManifestLockFileCustomized(projectRoot, PREFERENCES_RELATIVE_PATH);
|
|
404
406
|
return readDashboardPreferences(projectRoot);
|
|
405
407
|
}
|
|
@@ -48,11 +48,14 @@ export function ensureInsideWithoutSymlinks(parentPath, childPath, options = {})
|
|
|
48
48
|
}
|
|
49
49
|
}
|
|
50
50
|
export function readUtf8FileInsideWithoutSymlinks(parentPath, childPath) {
|
|
51
|
+
return readFileInsideWithoutSymlinks(parentPath, childPath).toString('utf8');
|
|
52
|
+
}
|
|
53
|
+
export function readFileInsideWithoutSymlinks(parentPath, childPath) {
|
|
51
54
|
const absoluteChildPath = path.resolve(childPath);
|
|
52
55
|
ensureInsideWithoutSymlinks(parentPath, absoluteChildPath);
|
|
53
56
|
const fileDescriptor = openSync(absoluteChildPath, constants.O_RDONLY | NOFOLLOW_FLAG);
|
|
54
57
|
try {
|
|
55
|
-
return readFileSync(fileDescriptor
|
|
58
|
+
return readFileSync(fileDescriptor);
|
|
56
59
|
}
|
|
57
60
|
finally {
|
|
58
61
|
closeSync(fileDescriptor);
|
|
@@ -79,6 +82,9 @@ export function ensureFileTargetInsideWithoutSymlinks(parentPath, childPath, opt
|
|
|
79
82
|
}
|
|
80
83
|
}
|
|
81
84
|
export function writeUtf8FileInsideWithoutSymlinks(parentPath, childPath, content) {
|
|
85
|
+
writeFileInsideWithoutSymlinks(parentPath, childPath, content);
|
|
86
|
+
}
|
|
87
|
+
export function writeFileInsideWithoutSymlinks(parentPath, childPath, content) {
|
|
82
88
|
const absoluteChildPath = path.resolve(childPath);
|
|
83
89
|
const directoryPath = path.dirname(absoluteChildPath);
|
|
84
90
|
ensureInsideWithoutSymlinks(parentPath, directoryPath, { allowMissingLeaf: true });
|
|
@@ -92,6 +98,10 @@ export function writeUtf8FileInsideWithoutSymlinks(parentPath, childPath, conten
|
|
|
92
98
|
closeSync(fileDescriptor);
|
|
93
99
|
}
|
|
94
100
|
}
|
|
101
|
+
export function copyFileInsideWithoutSymlinks(sourceParentPath, sourcePath, targetParentPath, targetPath) {
|
|
102
|
+
const content = readFileInsideWithoutSymlinks(sourceParentPath, sourcePath);
|
|
103
|
+
writeFileInsideWithoutSymlinks(targetParentPath, targetPath, content);
|
|
104
|
+
}
|
|
95
105
|
export function copyFileIfMissing(sourcePath, targetPath, relativePath) {
|
|
96
106
|
if (existsSync(targetPath)) {
|
|
97
107
|
return { status: 'skipped', relativePath };
|
|
@@ -5,6 +5,7 @@ import { isRecord, readCommandContract, readString, readStringArray } from '../c
|
|
|
5
5
|
import { listFilesRecursive, toPosixPath } from '../filesystem.js';
|
|
6
6
|
import { readTomlFile } from '../toml.js';
|
|
7
7
|
import { collectSourceAnchorIndexRecords, hasHighRiskSourceAnchorRiskTags, } from '../../../core/source-anchor-status.js';
|
|
8
|
+
import { listSourceAnchorFiles } from '../../../core/source-anchors.js';
|
|
8
9
|
import { normalizeCommandEffects } from '../../../core/command-effects.js';
|
|
9
10
|
import { listChangeClassificationRuleDescriptors } from '../../../core/change-classification.js';
|
|
10
11
|
import { DEFAULT_DATABASE_RELATIVE_PATH, DEFAULT_PROMPT_CACHE_STABLE_READ, DEFAULT_PROMPT_CACHE_TASK_SOURCES, DEFAULT_PROMPT_CACHE_VOLATILE_SOURCES, INDEX_CONFIG_RELATIVE_PATH, LOCAL_INDEX_CONTENT_MODE, LOCAL_INDEX_EXCLUDED_RAW_DATA_KINDS, LOCAL_INDEX_PARSER_VERSION, LOCAL_INDEX_SCHEMA_VERSION, LOCAL_INDEX_STORE_FULL_CONTENT, LATEST_RUN_STATE_RELATIVE_PATH, MAX_SEARCH_MATCH_SNIPPET_CHARS, MAX_SNIPPET_BYTES_PER_DOCUMENT, MUSTFLOW_RELATIVE_PATH, SEARCH_BACKEND_FTS5, SEARCH_BACKEND_TABLE_SCAN, SEARCH_MATCH_CONTEXT_AFTER_CHARS, SEARCH_MATCH_CONTEXT_BEFORE_CHARS, SEARCH_MATCH_TRUNCATION_MARKER, SEARCH_NGRAM_MAX_GRAMS_PER_TARGET, SEARCH_NGRAM_MAX_LENGTH, SEARCH_NGRAM_MAX_TOKEN_CHARS, SEARCH_NGRAM_MIN_LENGTH, SOURCE_INDEX_MAX_FILE_BYTES, TEST_DISABLE_FTS5_ENV, } from './constants.js';
|
|
@@ -308,12 +309,13 @@ function readIndexedFileMetadataRecord(projectRoot, relativePath, sourceScope) {
|
|
|
308
309
|
mtimeMs: Math.round(stats.mtimeMs),
|
|
309
310
|
};
|
|
310
311
|
}
|
|
311
|
-
function collectIndexedFileRecords(projectRoot, documents, sourceAnchors) {
|
|
312
|
+
function collectIndexedFileRecords(projectRoot, documents, sourceAnchors, sourceAnchorCandidatePaths = []) {
|
|
312
313
|
const records = new Map();
|
|
313
314
|
for (const document of documents) {
|
|
314
315
|
records.set(document.path, readIndexedFileRecord(projectRoot, document.path, 'workflow', document.contentHash));
|
|
315
316
|
}
|
|
316
|
-
|
|
317
|
+
const sourcePaths = new Set([...sourceAnchorCandidatePaths, ...sourceAnchors.map((anchor) => anchor.path)]);
|
|
318
|
+
for (const anchorPath of [...sourcePaths].sort((left, right) => left.localeCompare(right))) {
|
|
317
319
|
if (!records.has(anchorPath)) {
|
|
318
320
|
records.set(anchorPath, readIndexedFileRecord(projectRoot, anchorPath, 'source_anchor'));
|
|
319
321
|
}
|
|
@@ -323,15 +325,33 @@ function collectIndexedFileRecords(projectRoot, documents, sourceAnchors) {
|
|
|
323
325
|
}
|
|
324
326
|
return [...records.values()].sort((left, right) => left.path.localeCompare(right.path));
|
|
325
327
|
}
|
|
326
|
-
function
|
|
328
|
+
function collectSourceAnchorCandidatePaths(projectRoot, sourceConfig) {
|
|
329
|
+
return listSourceAnchorFiles(projectRoot, {
|
|
330
|
+
...sourceConfig,
|
|
331
|
+
excludeGeneratedOrVendor: true,
|
|
332
|
+
});
|
|
333
|
+
}
|
|
334
|
+
function collectFastPreflightIndexedFileMetadataRecords(projectRoot, includeSource, sourceConfig) {
|
|
335
|
+
const records = new Map();
|
|
336
|
+
for (const relativePath of getExistingIndexablePaths(projectRoot)) {
|
|
337
|
+
records.set(relativePath, readIndexedFileMetadataRecord(projectRoot, relativePath, 'workflow'));
|
|
338
|
+
}
|
|
327
339
|
if (includeSource) {
|
|
328
|
-
|
|
340
|
+
try {
|
|
341
|
+
for (const sourcePath of collectSourceAnchorCandidatePaths(projectRoot, sourceConfig)) {
|
|
342
|
+
if (!records.has(sourcePath)) {
|
|
343
|
+
records.set(sourcePath, readIndexedFileMetadataRecord(projectRoot, sourcePath, 'source_anchor'));
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
catch {
|
|
348
|
+
return null;
|
|
349
|
+
}
|
|
329
350
|
}
|
|
330
|
-
const records = getExistingIndexablePaths(projectRoot).map((relativePath) => readIndexedFileMetadataRecord(projectRoot, relativePath, 'workflow'));
|
|
331
351
|
if (existsSync(path.join(projectRoot, ...LATEST_RUN_STATE_RELATIVE_PATH.split('/')))) {
|
|
332
|
-
records.
|
|
352
|
+
records.set(LATEST_RUN_STATE_RELATIVE_PATH, readIndexedFileMetadataRecord(projectRoot, LATEST_RUN_STATE_RELATIVE_PATH, 'state'));
|
|
333
353
|
}
|
|
334
|
-
return records.sort((left, right) => left.path.localeCompare(right.path));
|
|
354
|
+
return [...records.values()].sort((left, right) => left.path.localeCompare(right.path));
|
|
335
355
|
}
|
|
336
356
|
function normalizeSearchText(value) {
|
|
337
357
|
return value.trim().replace(/\s+/g, ' ');
|
|
@@ -2170,7 +2190,7 @@ export async function createLocalIndex(projectRoot, options = {}) {
|
|
|
2170
2190
|
capabilities = detectLocalSearchCapabilities(capabilityDatabase);
|
|
2171
2191
|
capabilityDatabase.close();
|
|
2172
2192
|
if (incremental) {
|
|
2173
|
-
const preflightFiles = collectFastPreflightIndexedFileMetadataRecords(projectRoot, includeSource);
|
|
2193
|
+
const preflightFiles = collectFastPreflightIndexedFileMetadataRecords(projectRoot, includeSource, sourceConfig);
|
|
2174
2194
|
const preflightReuse = await readIncrementalPreflightReuse(SQL, databasePath, projectRoot, preflightFiles, sourceScopeHash, dryRun, indexMode);
|
|
2175
2195
|
if (preflightReuse.result) {
|
|
2176
2196
|
return preflightReuse.result;
|
|
@@ -2184,6 +2204,7 @@ export async function createLocalIndex(projectRoot, options = {}) {
|
|
|
2184
2204
|
const previousSourceAnchors = includeSource
|
|
2185
2205
|
? await readPreviousSourceAnchorSnapshots(databasePath).catch(() => [])
|
|
2186
2206
|
: [];
|
|
2207
|
+
const sourceAnchorCandidatePaths = includeSource ? collectSourceAnchorCandidatePaths(projectRoot, sourceConfig) : [];
|
|
2187
2208
|
const sourceAnchors = includeSource
|
|
2188
2209
|
? collectSourceAnchorIndexRecords(projectRoot, previousSourceAnchors, {
|
|
2189
2210
|
...sourceConfig,
|
|
@@ -2191,7 +2212,7 @@ export async function createLocalIndex(projectRoot, options = {}) {
|
|
|
2191
2212
|
})
|
|
2192
2213
|
: [];
|
|
2193
2214
|
const verificationEvidence = createVerificationEvidenceIndex(projectRoot);
|
|
2194
|
-
const indexedFiles = collectIndexedFileRecords(projectRoot, documents, sourceAnchors);
|
|
2215
|
+
const indexedFiles = collectIndexedFileRecords(projectRoot, documents, sourceAnchors, sourceAnchorCandidatePaths);
|
|
2195
2216
|
if (incremental) {
|
|
2196
2217
|
const reuseDecision = await readIncrementalReuseDecision(SQL, databasePath, indexedFiles, sourceScopeHash);
|
|
2197
2218
|
reusedExisting = reuseDecision.reusable;
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { createHash } from 'node:crypto';
|
|
2
|
-
import { existsSync
|
|
2
|
+
import { existsSync } from 'node:fs';
|
|
3
3
|
import path from 'node:path';
|
|
4
|
-
import { ensureInside } from './filesystem.js';
|
|
5
|
-
import {
|
|
4
|
+
import { ensureFileTargetInsideWithoutSymlinks, ensureInside, readFileInsideWithoutSymlinks, readUtf8FileInsideWithoutSymlinks, writeUtf8FileInsideWithoutSymlinks, } from './filesystem.js';
|
|
5
|
+
import { parseTomlText, stringifyToml } from './toml.js';
|
|
6
6
|
export const MANIFEST_LOCK_RELATIVE_PATH = '.mustflow/config/manifest.lock.toml';
|
|
7
7
|
function isRecord(value) {
|
|
8
8
|
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
@@ -48,20 +48,31 @@ function parseManifestLock(raw) {
|
|
|
48
48
|
};
|
|
49
49
|
}
|
|
50
50
|
export function sha256File(filePath) {
|
|
51
|
-
return `sha256:${createHash('sha256')
|
|
51
|
+
return `sha256:${createHash('sha256')
|
|
52
|
+
.update(readFileInsideWithoutSymlinks(path.dirname(filePath), filePath))
|
|
53
|
+
.digest('hex')}`;
|
|
54
|
+
}
|
|
55
|
+
function sha256ProjectFile(projectRoot, filePath) {
|
|
56
|
+
return `sha256:${createHash('sha256').update(readFileInsideWithoutSymlinks(projectRoot, filePath)).digest('hex')}`;
|
|
57
|
+
}
|
|
58
|
+
export function ensureManifestLockTargetSafe(projectRoot) {
|
|
59
|
+
const lockPath = path.join(projectRoot, MANIFEST_LOCK_RELATIVE_PATH);
|
|
60
|
+
ensureInside(projectRoot, lockPath);
|
|
61
|
+
ensureFileTargetInsideWithoutSymlinks(projectRoot, lockPath, { allowMissingLeaf: true });
|
|
62
|
+
return existsSync(lockPath);
|
|
52
63
|
}
|
|
53
64
|
export function markManifestLockFileCustomized(projectRoot, relativePath) {
|
|
54
65
|
const lockPath = path.join(projectRoot, MANIFEST_LOCK_RELATIVE_PATH);
|
|
55
66
|
const filePath = path.join(projectRoot, relativePath);
|
|
56
|
-
ensureInside(projectRoot, lockPath);
|
|
57
67
|
ensureInside(projectRoot, filePath);
|
|
58
|
-
if (!
|
|
68
|
+
if (!ensureManifestLockTargetSafe(projectRoot)) {
|
|
59
69
|
return false;
|
|
60
70
|
}
|
|
71
|
+
ensureFileTargetInsideWithoutSymlinks(projectRoot, filePath, { allowMissingLeaf: true });
|
|
61
72
|
if (!existsSync(filePath)) {
|
|
62
73
|
throw new Error(`Cannot refresh manifest lock for missing file: ${relativePath}`);
|
|
63
74
|
}
|
|
64
|
-
const parsed =
|
|
75
|
+
const parsed = parseTomlText(readUtf8FileInsideWithoutSymlinks(projectRoot, lockPath));
|
|
65
76
|
if (!isRecord(parsed)) {
|
|
66
77
|
throw new Error(`Invalid manifest lock: ${MANIFEST_LOCK_RELATIVE_PATH} must contain a TOML table`);
|
|
67
78
|
}
|
|
@@ -71,20 +82,27 @@ export function markManifestLockFileCustomized(projectRoot, relativePath) {
|
|
|
71
82
|
filesTable[relativePath] = {
|
|
72
83
|
source: typeof existingTable.source === 'string' ? existingTable.source : 'template_common',
|
|
73
84
|
last_action: 'customized',
|
|
74
|
-
content_hash:
|
|
85
|
+
content_hash: sha256ProjectFile(projectRoot, filePath),
|
|
75
86
|
};
|
|
76
87
|
parsed.files = filesTable;
|
|
77
|
-
|
|
88
|
+
writeUtf8FileInsideWithoutSymlinks(projectRoot, lockPath, stringifyToml(parsed));
|
|
78
89
|
return true;
|
|
79
90
|
}
|
|
80
91
|
export function readManifestLock(projectRoot) {
|
|
81
92
|
const lockPath = path.join(projectRoot, MANIFEST_LOCK_RELATIVE_PATH);
|
|
82
|
-
|
|
93
|
+
try {
|
|
94
|
+
ensureInside(projectRoot, lockPath);
|
|
95
|
+
ensureFileTargetInsideWithoutSymlinks(projectRoot, lockPath, { allowMissingLeaf: true });
|
|
96
|
+
}
|
|
97
|
+
catch (error) {
|
|
98
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
99
|
+
return { kind: 'invalid', lockPath, message };
|
|
100
|
+
}
|
|
83
101
|
if (!existsSync(lockPath)) {
|
|
84
102
|
return { kind: 'missing', lockPath };
|
|
85
103
|
}
|
|
86
104
|
try {
|
|
87
|
-
return { kind: 'present', lockPath, lock: parseManifestLock(
|
|
105
|
+
return { kind: 'present', lockPath, lock: parseManifestLock(parseTomlText(readUtf8FileInsideWithoutSymlinks(projectRoot, lockPath))) };
|
|
88
106
|
}
|
|
89
107
|
catch (error) {
|
|
90
108
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -121,7 +139,15 @@ export function inspectManifestLock(projectRoot) {
|
|
|
121
139
|
issues.push(`Locked file missing: ${lockedFile.relativePath}`);
|
|
122
140
|
continue;
|
|
123
141
|
}
|
|
124
|
-
|
|
142
|
+
let actualHash;
|
|
143
|
+
try {
|
|
144
|
+
actualHash = sha256ProjectFile(projectRoot, filePath);
|
|
145
|
+
}
|
|
146
|
+
catch (error) {
|
|
147
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
148
|
+
issues.push(`Locked file cannot be read safely: ${lockedFile.relativePath}: ${message}`);
|
|
149
|
+
continue;
|
|
150
|
+
}
|
|
125
151
|
if (actualHash !== lockedFile.contentHash) {
|
|
126
152
|
changedFiles.push(lockedFile.relativePath);
|
|
127
153
|
issues.push(`Lock hash mismatch: ${lockedFile.relativePath}`);
|
|
@@ -1,20 +1,4 @@
|
|
|
1
1
|
const MUSTFLOW_BIN_NAMES = new Set(['mf', 'mustflow']);
|
|
2
|
-
const IN_PROCESS_MUSTFLOW_BUILTIN_COMMANDS = new Set([
|
|
3
|
-
'check',
|
|
4
|
-
'classify',
|
|
5
|
-
'context',
|
|
6
|
-
'doctor',
|
|
7
|
-
'help',
|
|
8
|
-
'impact',
|
|
9
|
-
'line-endings',
|
|
10
|
-
'map',
|
|
11
|
-
'status',
|
|
12
|
-
'update',
|
|
13
|
-
'version-sources',
|
|
14
|
-
]);
|
|
15
2
|
export function isMustflowBinName(command) {
|
|
16
3
|
return MUSTFLOW_BIN_NAMES.has(command.toLowerCase());
|
|
17
4
|
}
|
|
18
|
-
export function canRunMustflowBuiltinInProcess(command) {
|
|
19
|
-
return command !== undefined && IN_PROCESS_MUSTFLOW_BUILTIN_COMMANDS.has(command);
|
|
20
|
-
}
|