mustflow 2.84.0 → 2.85.4
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 +11 -2
- package/dist/cli/commands/script-pack.js +4 -0
- package/dist/cli/i18n/en.js +79 -0
- package/dist/cli/i18n/es.js +79 -0
- package/dist/cli/i18n/fr.js +79 -0
- package/dist/cli/i18n/hi.js +79 -0
- package/dist/cli/i18n/ko.js +79 -0
- package/dist/cli/i18n/zh.js +79 -0
- package/dist/cli/lib/repo-map.js +27 -6
- package/dist/cli/lib/run-root-trust.js +15 -1
- package/dist/cli/lib/script-pack-registry.js +114 -0
- package/dist/cli/lib/validation/index.js +2 -2
- package/dist/cli/lib/validation/primitives.js +4 -1
- package/dist/cli/script-packs/code-change-impact.js +172 -0
- package/dist/cli/script-packs/code-dependency-graph.js +181 -0
- package/dist/cli/script-packs/repo-env-contract.js +156 -0
- package/dist/cli/script-packs/repo-secret-risk-scan.js +147 -0
- package/dist/core/change-impact.js +383 -0
- package/dist/core/change-verification.js +32 -5
- package/dist/core/config-loading.js +121 -4
- package/dist/core/dependency-graph.js +490 -0
- package/dist/core/env-contract.js +450 -0
- package/dist/core/line-endings.js +26 -13
- package/dist/core/public-json-contracts.js +51 -0
- package/dist/core/route-outline.js +57 -5
- package/dist/core/script-pack-suggestions.js +21 -1
- package/dist/core/secret-risk-scan.js +440 -0
- package/package.json +1 -1
- package/schemas/README.md +16 -0
- package/schemas/change-impact-report.schema.json +150 -0
- package/schemas/commands.schema.json +12 -0
- package/schemas/dependency-graph-report.schema.json +149 -0
- package/schemas/env-contract-report.schema.json +203 -0
- package/schemas/secret-risk-scan-report.schema.json +152 -0
- package/templates/default/i18n.toml +16 -10
- package/templates/default/locales/en/.mustflow/skills/INDEX.md +7 -2
- package/templates/default/locales/en/.mustflow/skills/architecture-deepening-review/SKILL.md +28 -11
- package/templates/default/locales/en/.mustflow/skills/astro-code-change/SKILL.md +71 -27
- package/templates/default/locales/en/.mustflow/skills/cross-agent-session-reference/SKILL.md +23 -8
- package/templates/default/locales/en/.mustflow/skills/dependency-upgrade-review/SKILL.md +3 -1
- package/templates/default/locales/en/.mustflow/skills/github-contribution-quality-gate/SKILL.md +48 -11
- package/templates/default/locales/en/.mustflow/skills/javascript-code-change/SKILL.md +15 -13
- package/templates/default/locales/en/.mustflow/skills/node-code-change/SKILL.md +16 -14
- package/templates/default/locales/en/.mustflow/skills/routes.toml +15 -9
- package/templates/default/locales/en/.mustflow/skills/security-privacy-review/SKILL.md +3 -1
- package/templates/default/locales/en/.mustflow/skills/test-suite-performance-review/SKILL.md +314 -0
- package/templates/default/locales/en/.mustflow/skills/typescript-code-change/SKILL.md +13 -10
- package/templates/default/manifest.toml +8 -1
package/dist/cli/i18n/zh.js
CHANGED
|
@@ -774,6 +774,8 @@ export const zhMessages = {
|
|
|
774
774
|
"scriptPack.pack.docs.summary": "Documentation reference utility scripts",
|
|
775
775
|
"scriptPack.pack.repo.summary": "Repository-boundary utility scripts",
|
|
776
776
|
"scriptPack.script.codeOutline.summary": "Scan TypeScript and JavaScript files for symbol headers and line ranges",
|
|
777
|
+
"scriptPack.script.codeDependencyGraph.summary": "Trace relative TypeScript and JavaScript dependency graph edges",
|
|
778
|
+
"scriptPack.script.codeChangeImpact.summary": "Analyze changed files for impact, script-pack, and verification hints",
|
|
777
779
|
"scriptPack.script.codeSymbolRead.summary": "Read a bounded source snippet by source anchor, symbol line, or explicit line range",
|
|
778
780
|
"scriptPack.script.codeRouteOutline.summary": "Scan Hono, Elysia, Axum, and NestJS route methods, paths, handlers, and lifecycle chains",
|
|
779
781
|
"scriptPack.script.codeExportDiff.summary": "Compare exported source signatures and return metadata across git refs",
|
|
@@ -912,6 +914,48 @@ export const zhMessages = {
|
|
|
912
914
|
"generatedBoundary.error.missingAction": "Specify a generated-boundary action: check",
|
|
913
915
|
"generatedBoundary.error.unknownAction": "Unknown generated-boundary action: {action}",
|
|
914
916
|
"generatedBoundary.error.missingPath": "Provide at least one path to check",
|
|
917
|
+
"dependencyGraph.help.summary": "Trace bounded relative import, export, require, and dynamic import edges for TypeScript and JavaScript source files.",
|
|
918
|
+
"dependencyGraph.help.option.maxDepth": "Maximum dependency depth from target files. Default: 2",
|
|
919
|
+
"dependencyGraph.help.option.maxFiles": "Maximum number of source files to scan. Default: 1000",
|
|
920
|
+
"dependencyGraph.help.option.maxFileBytes": "Maximum bytes to read from each source file. Default: 262144",
|
|
921
|
+
"dependencyGraph.help.option.maxNodes": "Maximum number of graph nodes to report. Default: 300",
|
|
922
|
+
"dependencyGraph.help.option.maxEdges": "Maximum number of graph edges to report. Default: 800",
|
|
923
|
+
"dependencyGraph.help.exit.ok": "The dependency graph completed without input or scan-limit findings",
|
|
924
|
+
"dependencyGraph.help.exit.fail": "The dependency graph found invalid input, unreadable paths, or scan limits",
|
|
925
|
+
"dependencyGraph.title": "mustflow dependency graph",
|
|
926
|
+
"dependencyGraph.label.targets": "Targets",
|
|
927
|
+
"dependencyGraph.label.nodes": "Nodes",
|
|
928
|
+
"dependencyGraph.label.edges": "Edges",
|
|
929
|
+
"dependencyGraph.label.cycles": "Cycles",
|
|
930
|
+
"dependencyGraph.label.truncated": "Truncated",
|
|
931
|
+
"dependencyGraph.label.cycleList": "Cycle hints",
|
|
932
|
+
"dependencyGraph.label.findings": "Findings",
|
|
933
|
+
"dependencyGraph.label.issues": "Issues",
|
|
934
|
+
"dependencyGraph.clean": "No relative dependency edges were found.",
|
|
935
|
+
"dependencyGraph.error.missingAction": "Specify a dependency-graph action: scan",
|
|
936
|
+
"dependencyGraph.error.unknownAction": "Unknown dependency-graph action: {action}",
|
|
937
|
+
"dependencyGraph.error.missingPath": "Provide at least one path to scan",
|
|
938
|
+
"dependencyGraph.error.invalidPositiveInteger": "{option} must be a positive safe integer: {value}",
|
|
939
|
+
"changeImpact.help.summary": "Analyze git changes and return bounded file-impact, script-pack, and verification hints.",
|
|
940
|
+
"changeImpact.help.option.base": "Git base ref to compare from. Default: HEAD",
|
|
941
|
+
"changeImpact.help.option.head": "Git head ref to compare to. Omit to compare the base with the working tree.",
|
|
942
|
+
"changeImpact.help.option.maxFiles": "Maximum number of changed files to inspect. Default: 200",
|
|
943
|
+
"changeImpact.help.option.maxImpacts": "Maximum number of impact candidates to report. Default: 300",
|
|
944
|
+
"changeImpact.help.option.maxFileBytes": "Maximum bytes to read from each source file while tracing dependencies. Default: 262144",
|
|
945
|
+
"changeImpact.help.exit.ok": "The change-impact analysis completed without input or scan-limit findings",
|
|
946
|
+
"changeImpact.help.exit.fail": "The change-impact analysis found invalid git input or scan limits",
|
|
947
|
+
"changeImpact.title": "mustflow change impact",
|
|
948
|
+
"changeImpact.label.changedFiles": "Changed files",
|
|
949
|
+
"changeImpact.label.impacts": "Impacts",
|
|
950
|
+
"changeImpact.label.truncated": "Truncated",
|
|
951
|
+
"changeImpact.label.scriptHints": "Script hints",
|
|
952
|
+
"changeImpact.label.verificationHints": "Verification hints",
|
|
953
|
+
"changeImpact.label.findings": "Findings",
|
|
954
|
+
"changeImpact.label.issues": "Issues",
|
|
955
|
+
"changeImpact.clean": "No changed files were found for the selected git range.",
|
|
956
|
+
"changeImpact.error.missingAction": "Specify a change-impact action: analyze",
|
|
957
|
+
"changeImpact.error.unknownAction": "Unknown change-impact action: {action}",
|
|
958
|
+
"changeImpact.error.invalidPositiveInteger": "{option} must be a positive safe integer: {value}",
|
|
915
959
|
"relatedFiles.help.summary": "Map direct imports, importers, sibling files, and nearby config boundaries for source-oriented repository navigation.",
|
|
916
960
|
"relatedFiles.help.option.maxFiles": "Maximum number of source or related files to scan. Default: 1000",
|
|
917
961
|
"relatedFiles.help.option.maxFileBytes": "Maximum bytes to read from each source file. Default: 262144",
|
|
@@ -948,6 +992,41 @@ export const zhMessages = {
|
|
|
948
992
|
"configChain.error.unknownAction": "Unknown config-chain action: {action}",
|
|
949
993
|
"configChain.error.missingPath": "Provide at least one path to inspect",
|
|
950
994
|
"configChain.error.invalidPositiveInteger": "{option} must be a positive safe integer: {value}",
|
|
995
|
+
"scriptPack.script.envContract.summary": "Scan repository environment-variable contracts without reading secret values",
|
|
996
|
+
"envContract.help.summary": "Scan code, CI, docs, config, and env examples for environment variable contract drift without printing env values.",
|
|
997
|
+
"envContract.help.option.maxFiles": "Maximum number of files to inspect. Default: 1000",
|
|
998
|
+
"envContract.help.option.maxFileBytes": "Maximum bytes to read from each inspected file. Default: 262144",
|
|
999
|
+
"envContract.help.option.maxKeys": "Maximum number of env keys to report. Default: 300",
|
|
1000
|
+
"envContract.help.exit.ok": "The env contract scan completed without blocking findings",
|
|
1001
|
+
"envContract.help.exit.fail": "The env contract scan found invalid input, unreadable files, or contract findings",
|
|
1002
|
+
"envContract.title": "mustflow env contract",
|
|
1003
|
+
"envContract.label.files": "Files",
|
|
1004
|
+
"envContract.label.keys": "Keys",
|
|
1005
|
+
"envContract.label.findings": "Findings",
|
|
1006
|
+
"envContract.label.truncated": "Truncated",
|
|
1007
|
+
"envContract.label.issues": "Issues",
|
|
1008
|
+
"envContract.clean": "No environment variable references were found.",
|
|
1009
|
+
"envContract.error.missingAction": "Specify an env-contract action: scan",
|
|
1010
|
+
"envContract.error.unknownAction": "Unknown env-contract action: {action}",
|
|
1011
|
+
"envContract.error.invalidPositiveInteger": "{option} must be a positive safe integer: {value}",
|
|
1012
|
+
"scriptPack.script.secretRiskScan.summary": "Scan repository files for plausible secrets without printing secret values",
|
|
1013
|
+
"secretRiskScan.help.summary": "Scan code, docs, config, CI, and examples for plausible hardcoded secrets without printing secret values.",
|
|
1014
|
+
"secretRiskScan.help.option.maxFiles": "Maximum number of files to inspect. Default: 1000",
|
|
1015
|
+
"secretRiskScan.help.option.maxFileBytes": "Maximum bytes to read from each inspected file. Default: 262144",
|
|
1016
|
+
"secretRiskScan.help.option.maxFindings": "Maximum number of findings to report. Default: 200",
|
|
1017
|
+
"secretRiskScan.help.exit.ok": "The secret-risk scan completed without blocking findings",
|
|
1018
|
+
"secretRiskScan.help.exit.fail": "The secret-risk scan found invalid input, unreadable files, or secret-risk findings",
|
|
1019
|
+
"secretRiskScan.title": "mustflow secret risk scan",
|
|
1020
|
+
"secretRiskScan.label.files": "Files",
|
|
1021
|
+
"secretRiskScan.label.findings": "Findings",
|
|
1022
|
+
"secretRiskScan.label.highOrCritical": "High or critical",
|
|
1023
|
+
"secretRiskScan.label.skippedSecretFiles": "Skipped secret files",
|
|
1024
|
+
"secretRiskScan.label.truncated": "Truncated",
|
|
1025
|
+
"secretRiskScan.label.issues": "Issues",
|
|
1026
|
+
"secretRiskScan.clean": "No plausible hardcoded secrets were found.",
|
|
1027
|
+
"secretRiskScan.error.missingAction": "Specify a secret-risk-scan action: scan",
|
|
1028
|
+
"secretRiskScan.error.unknownAction": "Unknown secret-risk-scan action: {action}",
|
|
1029
|
+
"secretRiskScan.error.invalidPositiveInteger": "{option} must be a positive safe integer: {value}",
|
|
951
1030
|
"run.help.summary": "从 .mustflow/config/commands.toml 运行已配置的一次性命令。",
|
|
952
1031
|
"run.help.option.dryRun": "输出命令计划但不执行",
|
|
953
1032
|
"run.help.option.planOnly": "--dry-run 的别名",
|
package/dist/cli/lib/repo-map.js
CHANGED
|
@@ -661,7 +661,7 @@ function renderSourceQuality(gitLsFilesStatus) {
|
|
|
661
661
|
'',
|
|
662
662
|
];
|
|
663
663
|
}
|
|
664
|
-
|
|
664
|
+
function createRepoMapRenderModel(projectRoot, options = {}) {
|
|
665
665
|
const depth = options.depth ?? DEFAULT_DEPTH;
|
|
666
666
|
const config = getRepoMapConfig(projectRoot);
|
|
667
667
|
const configuredPriorityPaths = config.priorityPaths;
|
|
@@ -675,10 +675,6 @@ export function generateRepoMap(projectRoot, options = {}) {
|
|
|
675
675
|
const anchorDiscovery = discoverAnchors(projectRoot, depth, priorityPathSet, nestedRepositories, workspaceRootPrefixes);
|
|
676
676
|
const anchors = anchorDiscovery.anchors;
|
|
677
677
|
const gitLsFilesStatus = anchorDiscovery.gitLsFilesStatus;
|
|
678
|
-
const priorityAnchors = configuredPriorityPaths
|
|
679
|
-
.map((relativePath) => anchors.find((anchor) => anchor.relativePath === relativePath))
|
|
680
|
-
.filter((anchor) => Boolean(anchor));
|
|
681
|
-
const otherAnchors = anchors.filter((anchor) => !priorityPathSet.has(anchor.relativePath));
|
|
682
678
|
const anchorCount = anchors.length + nestedRepositories.reduce((total, repository) => total + countNestedEntrypoints(repository), 0);
|
|
683
679
|
const sourceFingerprint = getRepoMapSourceFingerprint({
|
|
684
680
|
depth,
|
|
@@ -688,8 +684,33 @@ export function generateRepoMap(projectRoot, options = {}) {
|
|
|
688
684
|
anchors,
|
|
689
685
|
nestedRepositories,
|
|
690
686
|
});
|
|
687
|
+
return {
|
|
688
|
+
configuredPriorityPaths,
|
|
689
|
+
priorityPathSet,
|
|
690
|
+
mapConfig,
|
|
691
|
+
nestedRepositories,
|
|
692
|
+
anchors,
|
|
693
|
+
gitLsFilesStatus,
|
|
694
|
+
anchorCount,
|
|
695
|
+
sourceFingerprint,
|
|
696
|
+
};
|
|
697
|
+
}
|
|
698
|
+
export function getExpectedRepoMapSourceFingerprint(projectRoot, options = {}) {
|
|
699
|
+
return createRepoMapRenderModel(projectRoot, options).sourceFingerprint;
|
|
700
|
+
}
|
|
701
|
+
export function generateRepoMap(projectRoot, options = {}) {
|
|
702
|
+
const model = createRepoMapRenderModel(projectRoot, options);
|
|
703
|
+
const configuredPriorityPaths = model.configuredPriorityPaths;
|
|
704
|
+
const priorityPathSet = model.priorityPathSet;
|
|
705
|
+
const nestedRepositories = model.nestedRepositories;
|
|
706
|
+
const anchors = model.anchors;
|
|
707
|
+
const gitLsFilesStatus = model.gitLsFilesStatus;
|
|
708
|
+
const priorityAnchors = configuredPriorityPaths
|
|
709
|
+
.map((relativePath) => anchors.find((anchor) => anchor.relativePath === relativePath))
|
|
710
|
+
.filter((anchor) => Boolean(anchor));
|
|
711
|
+
const otherAnchors = anchors.filter((anchor) => !priorityPathSet.has(anchor.relativePath));
|
|
691
712
|
return [
|
|
692
|
-
...renderRepoMapFrontmatter(anchorCount, sourceFingerprint, gitLsFilesStatus),
|
|
713
|
+
...renderRepoMapFrontmatter(model.anchorCount, model.sourceFingerprint, gitLsFilesStatus),
|
|
693
714
|
'# REPO_MAP.md',
|
|
694
715
|
'',
|
|
695
716
|
'This file is an agent navigation map for the current mustflow root. It is not a full file listing.',
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { MANIFEST_LOCK_RELATIVE_PATH, inspectManifestLock } from './manifest-lock.js';
|
|
2
|
+
import { readCommandContractIncludePaths } from '../../core/config-loading.js';
|
|
2
3
|
export const ALLOW_UNTRUSTED_ROOT_OPTION = '--allow-untrusted-root';
|
|
3
4
|
const REQUIRED_RUN_TRUST_LOCK_PATHS = [
|
|
4
5
|
'AGENTS.md',
|
|
@@ -17,7 +18,20 @@ export function assessRunRootTrust(projectRoot) {
|
|
|
17
18
|
};
|
|
18
19
|
}
|
|
19
20
|
const trackedPaths = new Set(readResult.lock.files.map((file) => file.relativePath));
|
|
20
|
-
|
|
21
|
+
let requiredPaths = REQUIRED_RUN_TRUST_LOCK_PATHS;
|
|
22
|
+
try {
|
|
23
|
+
requiredPaths = [...REQUIRED_RUN_TRUST_LOCK_PATHS, ...readCommandContractIncludePaths(projectRoot)];
|
|
24
|
+
}
|
|
25
|
+
catch (error) {
|
|
26
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
27
|
+
return {
|
|
28
|
+
trusted: false,
|
|
29
|
+
reason: 'manifest_lock_invalid',
|
|
30
|
+
manifestLockPath: readResult.lockPath,
|
|
31
|
+
detail: message,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
const missingRequiredPath = requiredPaths.find((relativePath) => !trackedPaths.has(relativePath));
|
|
21
35
|
if (missingRequiredPath) {
|
|
22
36
|
return {
|
|
23
37
|
trusted: false,
|
|
@@ -36,6 +36,64 @@ export const SCRIPT_PACKS = [
|
|
|
36
36
|
reportSchemaFile: 'code-outline-report.schema.json',
|
|
37
37
|
loadRunner: async () => (await import('../script-packs/code-outline.js')).runCodeOutlineScript,
|
|
38
38
|
},
|
|
39
|
+
{
|
|
40
|
+
packId: 'code',
|
|
41
|
+
id: 'dependency-graph',
|
|
42
|
+
ref: scriptRef('code', 'dependency-graph'),
|
|
43
|
+
usage: 'mf script-pack run code/dependency-graph scan <path...> [options]',
|
|
44
|
+
summaryKey: 'scriptPack.script.codeDependencyGraph.summary',
|
|
45
|
+
actions: ['scan'],
|
|
46
|
+
useWhen: [
|
|
47
|
+
'Trace relative TypeScript and JavaScript import, export, require, and dynamic import edges before changing a module.',
|
|
48
|
+
'Build a bounded dependency graph with nodes, edges, depth, importer counts, import counts, and cycle hints for source impact orientation.',
|
|
49
|
+
],
|
|
50
|
+
phases: ['before_change', 'during_change', 'review'],
|
|
51
|
+
readOnly: true,
|
|
52
|
+
mutates: false,
|
|
53
|
+
network: false,
|
|
54
|
+
inputs: ['path', 'max_files', 'max_file_bytes', 'max_depth', 'max_nodes', 'max_edges'],
|
|
55
|
+
outputs: ['human_summary', 'json_report', 'dependency_graph', 'cycle_hints'],
|
|
56
|
+
relatedSkills: [
|
|
57
|
+
'change-blast-radius-review',
|
|
58
|
+
'codebase-orientation',
|
|
59
|
+
'javascript-code-change',
|
|
60
|
+
'module-boundary-review',
|
|
61
|
+
'typescript-code-change',
|
|
62
|
+
],
|
|
63
|
+
riskLevel: 'low',
|
|
64
|
+
cost: 'low',
|
|
65
|
+
reportSchemaFile: 'dependency-graph-report.schema.json',
|
|
66
|
+
loadRunner: async () => (await import('../script-packs/code-dependency-graph.js')).runCodeDependencyGraphScript,
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
packId: 'code',
|
|
70
|
+
id: 'change-impact',
|
|
71
|
+
ref: scriptRef('code', 'change-impact'),
|
|
72
|
+
usage: 'mf script-pack run code/change-impact analyze [path...] [options]',
|
|
73
|
+
summaryKey: 'scriptPack.script.codeChangeImpact.summary',
|
|
74
|
+
actions: ['analyze'],
|
|
75
|
+
useWhen: [
|
|
76
|
+
'Analyze current git changes and produce bounded file, script-pack, and verification hints after a change.',
|
|
77
|
+
'Classify changed source, test, docs, schema, package, template, and workflow surfaces before choosing follow-up checks.',
|
|
78
|
+
],
|
|
79
|
+
phases: ['after_change', 'review'],
|
|
80
|
+
readOnly: true,
|
|
81
|
+
mutates: false,
|
|
82
|
+
network: false,
|
|
83
|
+
inputs: ['path', 'base_ref', 'head_ref', 'max_files', 'max_impacts', 'max_file_bytes'],
|
|
84
|
+
outputs: ['human_summary', 'json_report', 'changed_files', 'impact_candidates', 'verification_hints'],
|
|
85
|
+
relatedSkills: [
|
|
86
|
+
'change-blast-radius-review',
|
|
87
|
+
'completion-evidence-gate',
|
|
88
|
+
'javascript-code-change',
|
|
89
|
+
'public-json-contract-change',
|
|
90
|
+
'typescript-code-change',
|
|
91
|
+
],
|
|
92
|
+
riskLevel: 'low',
|
|
93
|
+
cost: 'low',
|
|
94
|
+
reportSchemaFile: 'change-impact-report.schema.json',
|
|
95
|
+
loadRunner: async () => (await import('../script-packs/code-change-impact.js')).runCodeChangeImpactScript,
|
|
96
|
+
},
|
|
39
97
|
{
|
|
40
98
|
packId: 'code',
|
|
41
99
|
id: 'symbol-read',
|
|
@@ -229,6 +287,62 @@ export const SCRIPT_PACKS = [
|
|
|
229
287
|
reportSchemaFile: 'config-chain-report.schema.json',
|
|
230
288
|
loadRunner: async () => (await import('../script-packs/repo-config-chain.js')).runRepoConfigChainScript,
|
|
231
289
|
},
|
|
290
|
+
{
|
|
291
|
+
packId: 'repo',
|
|
292
|
+
id: 'env-contract',
|
|
293
|
+
ref: scriptRef('repo', 'env-contract'),
|
|
294
|
+
usage: 'mf script-pack run repo/env-contract scan [path...] [options]',
|
|
295
|
+
summaryKey: 'scriptPack.script.envContract.summary',
|
|
296
|
+
actions: ['scan'],
|
|
297
|
+
useWhen: [
|
|
298
|
+
'Scan code, CI, docs, and env example files for environment variable contract drift without reading real secret env files.',
|
|
299
|
+
'Review env keys that are used in code, declared in examples, referenced in CI, documented, missing from examples, or public-prefix secret-like names.',
|
|
300
|
+
],
|
|
301
|
+
phases: ['before_change', 'during_change', 'after_change', 'review'],
|
|
302
|
+
readOnly: true,
|
|
303
|
+
mutates: false,
|
|
304
|
+
network: false,
|
|
305
|
+
inputs: ['path', 'max_files', 'max_file_bytes', 'max_keys'],
|
|
306
|
+
outputs: ['human_summary', 'json_report', 'env_keys', 'env_contract_findings'],
|
|
307
|
+
relatedSkills: [
|
|
308
|
+
'config-env-change',
|
|
309
|
+
'public-json-contract-change',
|
|
310
|
+
'security-privacy-review',
|
|
311
|
+
'typescript-code-change',
|
|
312
|
+
],
|
|
313
|
+
riskLevel: 'low',
|
|
314
|
+
cost: 'low',
|
|
315
|
+
reportSchemaFile: 'env-contract-report.schema.json',
|
|
316
|
+
loadRunner: async () => (await import('../script-packs/repo-env-contract.js')).runRepoEnvContractScript,
|
|
317
|
+
},
|
|
318
|
+
{
|
|
319
|
+
packId: 'repo',
|
|
320
|
+
id: 'secret-risk-scan',
|
|
321
|
+
ref: scriptRef('repo', 'secret-risk-scan'),
|
|
322
|
+
usage: 'mf script-pack run repo/secret-risk-scan scan [path...] [options]',
|
|
323
|
+
summaryKey: 'scriptPack.script.secretRiskScan.summary',
|
|
324
|
+
actions: ['scan'],
|
|
325
|
+
useWhen: [
|
|
326
|
+
'Scan code, docs, config, CI, and examples for plausible hardcoded secrets without printing secret values.',
|
|
327
|
+
'Review provider token prefixes, Bearer tokens, private key markers, secret-like assignments, and realistic env-example values.',
|
|
328
|
+
],
|
|
329
|
+
phases: ['before_change', 'during_change', 'after_change', 'review'],
|
|
330
|
+
readOnly: true,
|
|
331
|
+
mutates: false,
|
|
332
|
+
network: false,
|
|
333
|
+
inputs: ['path', 'max_files', 'max_file_bytes', 'max_findings'],
|
|
334
|
+
outputs: ['human_summary', 'json_report', 'secret_risk_findings', 'redacted_fingerprints'],
|
|
335
|
+
relatedSkills: [
|
|
336
|
+
'config-env-change',
|
|
337
|
+
'public-json-contract-change',
|
|
338
|
+
'security-privacy-review',
|
|
339
|
+
'typescript-code-change',
|
|
340
|
+
],
|
|
341
|
+
riskLevel: 'medium',
|
|
342
|
+
cost: 'low',
|
|
343
|
+
reportSchemaFile: 'secret-risk-scan-report.schema.json',
|
|
344
|
+
loadRunner: async () => (await import('../script-packs/repo-secret-risk-scan.js')).runRepoSecretRiskScanScript,
|
|
345
|
+
},
|
|
232
346
|
{
|
|
233
347
|
packId: 'repo',
|
|
234
348
|
id: 'generated-boundary',
|
|
@@ -11,7 +11,7 @@ import { validateSourceAnchorsInProject } from '../../../core/source-anchor-vali
|
|
|
11
11
|
import { listFilesRecursive, toPosixPath } from '../filesystem.js';
|
|
12
12
|
import { readGitChangedFiles } from '../git-changes.js';
|
|
13
13
|
import { inspectManifestLock } from '../manifest-lock.js';
|
|
14
|
-
import {
|
|
14
|
+
import { getExpectedRepoMapSourceFingerprint } from '../repo-map.js';
|
|
15
15
|
import { parseTomlText, readMustflowTomlFile } from '../toml.js';
|
|
16
16
|
import { MUSTFLOW_JSON_MAX_BYTES } from '../mustflow-read.js';
|
|
17
17
|
import { getContractModelDefinitions, validateCandidateContractModelConfig, } from '../../../core/contract-models.js';
|
|
@@ -1464,7 +1464,7 @@ function validateStrictRepoMap(projectRoot, issues) {
|
|
|
1464
1464
|
}
|
|
1465
1465
|
else {
|
|
1466
1466
|
const currentSourceFingerprint = frontmatter.source_fingerprint;
|
|
1467
|
-
const expectedSourceFingerprint =
|
|
1467
|
+
const expectedSourceFingerprint = getExpectedRepoMapSourceFingerprint(projectRoot);
|
|
1468
1468
|
if (expectedSourceFingerprint && currentSourceFingerprint !== expectedSourceFingerprint) {
|
|
1469
1469
|
pushStrictIssue(issues, 'REPO_MAP.md source_fingerprint is stale; regenerate with mf map --write');
|
|
1470
1470
|
}
|
|
@@ -2,6 +2,7 @@ import { existsSync } from 'node:fs';
|
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { isRecord } from '../command-contract.js';
|
|
4
4
|
import { readMustflowTomlFile } from '../toml.js';
|
|
5
|
+
import { COMMANDS_CONFIG_RELATIVE_PATH, readResolvedCommandContractToml, } from '../../../core/config-loading.js';
|
|
5
6
|
import { REQUIRED_FILES, } from './constants.js';
|
|
6
7
|
import { TECHNOLOGY_CONFIG_RELATIVE_PATH } from '../../../core/technology-preferences.js';
|
|
7
8
|
import { VERSIONING_CONFIG_PATH } from '../../../core/version-sources.js';
|
|
@@ -43,7 +44,9 @@ export function validateToml(projectRoot, issues) {
|
|
|
43
44
|
continue;
|
|
44
45
|
}
|
|
45
46
|
try {
|
|
46
|
-
const parsed =
|
|
47
|
+
const parsed = relativePath === COMMANDS_CONFIG_RELATIVE_PATH
|
|
48
|
+
? readResolvedCommandContractToml(projectRoot)
|
|
49
|
+
: readMustflowTomlFile(projectRoot, relativePath);
|
|
47
50
|
if (!isRecord(parsed)) {
|
|
48
51
|
issues.push({ message: `${relativePath} must contain a TOML table` });
|
|
49
52
|
continue;
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import { printUsageError, renderHelp } from '../lib/cli-output.js';
|
|
2
|
+
import { t } from '../lib/i18n.js';
|
|
3
|
+
import { formatCliOptionParseError, getParsedCliStringOption, hasCliOptionToken, hasParsedCliOption, parseCliOptions, } from '../lib/option-parser.js';
|
|
4
|
+
import { resolveMustflowRoot } from '../lib/project-root.js';
|
|
5
|
+
import { CHANGE_IMPACT_SCRIPT_REF, inspectChangeImpact } from '../../core/change-impact.js';
|
|
6
|
+
const CHANGE_IMPACT_OPTIONS = [
|
|
7
|
+
{ name: '--json', kind: 'boolean' },
|
|
8
|
+
{ name: '--base', kind: 'string' },
|
|
9
|
+
{ name: '--head', kind: 'string' },
|
|
10
|
+
{ name: '--max-files', kind: 'string' },
|
|
11
|
+
{ name: '--max-impacts', kind: 'string' },
|
|
12
|
+
{ name: '--max-file-bytes', kind: 'string' },
|
|
13
|
+
];
|
|
14
|
+
function parsePositiveInteger(value, option, lang) {
|
|
15
|
+
if (value === null) {
|
|
16
|
+
return { value: null };
|
|
17
|
+
}
|
|
18
|
+
if (!/^[1-9]\d*$/u.test(value)) {
|
|
19
|
+
return { value: null, error: t(lang, 'changeImpact.error.invalidPositiveInteger', { option, value }) };
|
|
20
|
+
}
|
|
21
|
+
const parsed = Number(value);
|
|
22
|
+
if (!Number.isSafeInteger(parsed)) {
|
|
23
|
+
return { value: null, error: t(lang, 'changeImpact.error.invalidPositiveInteger', { option, value }) };
|
|
24
|
+
}
|
|
25
|
+
return { value: parsed };
|
|
26
|
+
}
|
|
27
|
+
export function getCodeChangeImpactHelp(lang = 'en') {
|
|
28
|
+
return renderHelp({
|
|
29
|
+
usage: 'mf script-pack run code/change-impact analyze [path...] [options]',
|
|
30
|
+
summary: t(lang, 'changeImpact.help.summary'),
|
|
31
|
+
options: [
|
|
32
|
+
{ label: '--base <ref>', description: t(lang, 'changeImpact.help.option.base') },
|
|
33
|
+
{ label: '--head <ref>', description: t(lang, 'changeImpact.help.option.head') },
|
|
34
|
+
{ label: '--max-files <count>', description: t(lang, 'changeImpact.help.option.maxFiles') },
|
|
35
|
+
{ label: '--max-impacts <count>', description: t(lang, 'changeImpact.help.option.maxImpacts') },
|
|
36
|
+
{ label: '--max-file-bytes <bytes>', description: t(lang, 'changeImpact.help.option.maxFileBytes') },
|
|
37
|
+
{ label: '--json', description: t(lang, 'cli.option.json') },
|
|
38
|
+
{ label: '-h, --help', description: t(lang, 'cli.option.help') },
|
|
39
|
+
],
|
|
40
|
+
examples: [
|
|
41
|
+
'mf script-pack run code/change-impact analyze --base HEAD --json',
|
|
42
|
+
'mf script-pack run code/change-impact analyze src --base main --head HEAD --json',
|
|
43
|
+
'mf script-pack run code/change-impact analyze src/core --max-impacts 80 --json',
|
|
44
|
+
],
|
|
45
|
+
exitCodes: [
|
|
46
|
+
{ label: '0', description: t(lang, 'changeImpact.help.exit.ok') },
|
|
47
|
+
{ label: '1', description: t(lang, 'changeImpact.help.exit.fail') },
|
|
48
|
+
],
|
|
49
|
+
}, lang);
|
|
50
|
+
}
|
|
51
|
+
function parseChangeImpactOptions(args, lang) {
|
|
52
|
+
const [action, ...rest] = args;
|
|
53
|
+
const parsed = parseCliOptions(rest, CHANGE_IMPACT_OPTIONS, { allowPositionals: true });
|
|
54
|
+
const json = hasParsedCliOption(parsed, '--json');
|
|
55
|
+
const baseRef = getParsedCliStringOption(parsed, '--base');
|
|
56
|
+
const headRef = getParsedCliStringOption(parsed, '--head');
|
|
57
|
+
const maxFiles = parsePositiveInteger(getParsedCliStringOption(parsed, '--max-files'), '--max-files', lang);
|
|
58
|
+
const maxImpacts = parsePositiveInteger(getParsedCliStringOption(parsed, '--max-impacts'), '--max-impacts', lang);
|
|
59
|
+
const maxFileBytes = parsePositiveInteger(getParsedCliStringOption(parsed, '--max-file-bytes'), '--max-file-bytes', lang);
|
|
60
|
+
const positiveOptions = [maxFiles, maxImpacts, maxFileBytes];
|
|
61
|
+
if (action !== 'analyze') {
|
|
62
|
+
return {
|
|
63
|
+
action: 'analyze',
|
|
64
|
+
json,
|
|
65
|
+
baseRef,
|
|
66
|
+
headRef,
|
|
67
|
+
paths: parsed.positionals,
|
|
68
|
+
maxFiles: maxFiles.value,
|
|
69
|
+
maxImpacts: maxImpacts.value,
|
|
70
|
+
maxFileBytes: maxFileBytes.value,
|
|
71
|
+
error: action ? t(lang, 'changeImpact.error.unknownAction', { action }) : t(lang, 'changeImpact.error.missingAction'),
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
if (parsed.error) {
|
|
75
|
+
return {
|
|
76
|
+
action,
|
|
77
|
+
json,
|
|
78
|
+
baseRef,
|
|
79
|
+
headRef,
|
|
80
|
+
paths: parsed.positionals,
|
|
81
|
+
maxFiles: maxFiles.value,
|
|
82
|
+
maxImpacts: maxImpacts.value,
|
|
83
|
+
maxFileBytes: maxFileBytes.value,
|
|
84
|
+
error: formatCliOptionParseError(parsed.error, lang),
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
for (const candidate of positiveOptions) {
|
|
88
|
+
if (candidate.error) {
|
|
89
|
+
return {
|
|
90
|
+
action,
|
|
91
|
+
json,
|
|
92
|
+
baseRef,
|
|
93
|
+
headRef,
|
|
94
|
+
paths: parsed.positionals,
|
|
95
|
+
maxFiles: maxFiles.value,
|
|
96
|
+
maxImpacts: maxImpacts.value,
|
|
97
|
+
maxFileBytes: maxFileBytes.value,
|
|
98
|
+
error: candidate.error,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return {
|
|
103
|
+
action,
|
|
104
|
+
json,
|
|
105
|
+
baseRef,
|
|
106
|
+
headRef,
|
|
107
|
+
paths: parsed.positionals,
|
|
108
|
+
maxFiles: maxFiles.value,
|
|
109
|
+
maxImpacts: maxImpacts.value,
|
|
110
|
+
maxFileBytes: maxFileBytes.value,
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
function renderChangeImpactSummary(report, lang) {
|
|
114
|
+
const lines = [
|
|
115
|
+
t(lang, 'changeImpact.title'),
|
|
116
|
+
`${t(lang, 'scriptPack.label.script')}: ${CHANGE_IMPACT_SCRIPT_REF}`,
|
|
117
|
+
`${t(lang, 'label.status')}: ${report.status}`,
|
|
118
|
+
`${t(lang, 'changeImpact.label.changedFiles')}: ${report.changed_files.length}`,
|
|
119
|
+
`${t(lang, 'changeImpact.label.impacts')}: ${report.impacts.length}`,
|
|
120
|
+
`${t(lang, 'changeImpact.label.truncated')}: ${report.truncated ? t(lang, 'value.yes') : t(lang, 'value.no')}`,
|
|
121
|
+
];
|
|
122
|
+
for (const impact of report.impacts.slice(0, 40)) {
|
|
123
|
+
lines.push(`- ${impact.path}: ${impact.relationship} (${impact.reason})`);
|
|
124
|
+
}
|
|
125
|
+
if (report.script_hints.length > 0) {
|
|
126
|
+
lines.push(t(lang, 'changeImpact.label.scriptHints'));
|
|
127
|
+
for (const hint of report.script_hints) {
|
|
128
|
+
lines.push(`- ${hint.script_ref}: ${hint.command}`);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
if (report.verification_hints.length > 0) {
|
|
132
|
+
lines.push(t(lang, 'changeImpact.label.verificationHints'));
|
|
133
|
+
for (const hint of report.verification_hints) {
|
|
134
|
+
lines.push(`- ${hint.intent}: ${hint.reason}`);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
if (report.findings.length > 0) {
|
|
138
|
+
lines.push(t(lang, 'changeImpact.label.findings'), ...report.findings.map((finding) => `- ${finding.path}: ${finding.code} (${finding.message})`));
|
|
139
|
+
}
|
|
140
|
+
if (report.issues.length > 0) {
|
|
141
|
+
lines.push(t(lang, 'changeImpact.label.issues'), ...report.issues.map((issue) => `- ${issue}`));
|
|
142
|
+
}
|
|
143
|
+
if (report.changed_files.length === 0 && report.findings.length === 0 && report.issues.length === 0) {
|
|
144
|
+
lines.push(t(lang, 'changeImpact.clean'));
|
|
145
|
+
}
|
|
146
|
+
return lines.join('\n');
|
|
147
|
+
}
|
|
148
|
+
export function runCodeChangeImpactScript(args, reporter, lang = 'en') {
|
|
149
|
+
if (hasCliOptionToken(args, '--help', ['-h'])) {
|
|
150
|
+
reporter.stdout(getCodeChangeImpactHelp(lang));
|
|
151
|
+
return 0;
|
|
152
|
+
}
|
|
153
|
+
const options = parseChangeImpactOptions(args, lang);
|
|
154
|
+
if (options.error) {
|
|
155
|
+
printUsageError(reporter, options.error, 'mf script-pack run code/change-impact --help', getCodeChangeImpactHelp(lang), lang);
|
|
156
|
+
return 1;
|
|
157
|
+
}
|
|
158
|
+
const report = inspectChangeImpact(resolveMustflowRoot(), {
|
|
159
|
+
baseRef: options.baseRef ?? undefined,
|
|
160
|
+
headRef: options.headRef,
|
|
161
|
+
paths: options.paths,
|
|
162
|
+
maxFiles: options.maxFiles ?? undefined,
|
|
163
|
+
maxImpacts: options.maxImpacts ?? undefined,
|
|
164
|
+
maxFileBytes: options.maxFileBytes ?? undefined,
|
|
165
|
+
});
|
|
166
|
+
if (options.json) {
|
|
167
|
+
reporter.stdout(JSON.stringify(report, null, 2));
|
|
168
|
+
return report.ok ? 0 : 1;
|
|
169
|
+
}
|
|
170
|
+
reporter.stdout(renderChangeImpactSummary(report, lang));
|
|
171
|
+
return report.ok ? 0 : 1;
|
|
172
|
+
}
|