mustflow 2.22.9 → 2.22.13
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/dist/cli/commands/check.js +2 -2
- package/dist/cli/commands/help.js +1 -3
- package/dist/cli/commands/verify/state-paths.js +33 -0
- package/dist/cli/commands/verify.js +6 -29
- package/dist/cli/lib/doc-review-ledger.js +1 -3
- package/dist/cli/lib/manifest-lock.js +1 -3
- package/dist/cli/lib/repo-map-frontmatter.js +53 -0
- package/dist/cli/lib/repo-map.js +10 -57
- package/dist/cli/lib/validation/index.js +6 -2
- package/dist/core/check-issues.js +11 -7
- package/dist/core/command-contract-validation.js +22 -20
- package/package.json +13 -12
- package/templates/default/i18n.toml +14 -14
- package/templates/default/locales/en/.mustflow/skills/INDEX.md +1 -1
- package/templates/default/locales/en/.mustflow/skills/codebase-orientation/SKILL.md +18 -7
- package/templates/default/locales/en/.mustflow/skills/contract-sync-check/SKILL.md +14 -7
- package/templates/default/locales/en/.mustflow/skills/diff-risk-review/SKILL.md +10 -5
- package/templates/default/locales/en/.mustflow/skills/docs-update/SKILL.md +12 -5
- package/templates/default/locales/en/.mustflow/skills/failure-triage/SKILL.md +26 -5
- package/templates/default/locales/en/.mustflow/skills/pattern-scout/SKILL.md +13 -7
- package/templates/default/locales/en/.mustflow/skills/performance-budget-check/SKILL.md +53 -181
- package/templates/default/locales/en/.mustflow/skills/readme-authoring/SKILL.md +8 -2
- package/templates/default/locales/en/.mustflow/skills/release-notes-authoring/SKILL.md +4 -1
- package/templates/default/locales/en/.mustflow/skills/repro-first-debug/SKILL.md +15 -8
- package/templates/default/locales/en/.mustflow/skills/requirement-regression-guard/SKILL.md +10 -5
- package/templates/default/locales/en/.mustflow/skills/source-freshness-check/SKILL.md +5 -1
- package/templates/default/locales/en/.mustflow/skills/structure-discovery-gate/SKILL.md +2 -2
- package/templates/default/locales/en/.mustflow/skills/test-maintenance/SKILL.md +17 -4
- package/templates/default/locales/ko/.mustflow/context/INDEX.md +12 -12
- package/templates/default/locales/ko/.mustflow/context/PROJECT.md +6 -6
- package/templates/default/locales/ko/.mustflow/docs/agent-workflow.md +70 -70
- package/templates/default/locales/ko/AGENTS.md +14 -14
- package/templates/default/manifest.toml +1 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { printUsageError, renderHelp } from '../lib/cli-output.js';
|
|
2
2
|
import { t } from '../lib/i18n.js';
|
|
3
3
|
import { resolveMustflowRoot } from '../lib/project-root.js';
|
|
4
|
-
import { checkMustflowProjectReport
|
|
4
|
+
import { checkMustflowProjectReport } from '../lib/validation.js';
|
|
5
5
|
export function getCheckHelp(lang = 'en') {
|
|
6
6
|
return renderHelp({
|
|
7
7
|
usage: 'mf check [options]',
|
|
@@ -51,7 +51,7 @@ export function runCheck(args, reporter, lang = 'en') {
|
|
|
51
51
|
issues,
|
|
52
52
|
warningCount: warnings.length,
|
|
53
53
|
warnings,
|
|
54
|
-
issueDetails:
|
|
54
|
+
issueDetails: report.issueDetails,
|
|
55
55
|
}, null, 2));
|
|
56
56
|
return ok ? 0 : 1;
|
|
57
57
|
}
|
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
import { printUsageError, renderCliError, renderHelp } from '../lib/cli-output.js';
|
|
2
|
+
import { isRecord } from '../lib/command-contract.js';
|
|
2
3
|
import { t } from '../lib/i18n.js';
|
|
3
4
|
import { readMustflowTextFileIfExists } from '../lib/mustflow-read.js';
|
|
4
5
|
import { resolveMustflowRoot } from '../lib/project-root.js';
|
|
5
6
|
import { readMustflowTomlFile } from '../lib/toml.js';
|
|
6
|
-
function isRecord(value) {
|
|
7
|
-
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
8
|
-
}
|
|
9
7
|
function readTextIfExists(projectRoot, relativePath) {
|
|
10
8
|
return readMustflowTextFileIfExists(projectRoot, relativePath) ?? undefined;
|
|
11
9
|
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { createStateRunId } from '../../../core/atomic-state-write.js';
|
|
3
|
+
const RUN_STATE_DIR = path.join('.mustflow', 'state', 'runs');
|
|
4
|
+
const LATEST_RUN_RECEIPT_PATH = path.join(RUN_STATE_DIR, 'latest.json');
|
|
5
|
+
export function createVerifyRunStatePaths(projectRoot) {
|
|
6
|
+
const runDir = toPosixPath(path.join(RUN_STATE_DIR, createStateRunId('verify')));
|
|
7
|
+
const manifestPath = toPosixPath(path.join(runDir, 'manifest.json'));
|
|
8
|
+
const absoluteRunDir = path.join(projectRoot, runDir);
|
|
9
|
+
return {
|
|
10
|
+
runDir,
|
|
11
|
+
manifestPath,
|
|
12
|
+
absoluteRunDir,
|
|
13
|
+
absoluteIntentDir: path.join(absoluteRunDir, 'intents'),
|
|
14
|
+
absoluteManifestPath: path.join(projectRoot, manifestPath),
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
export function createVerifyIntentReceiptPath(statePaths, ordinal, intent) {
|
|
18
|
+
const fileName = `${String(ordinal).padStart(3, '0')}-${sanitizeIntentFilePart(intent)}.json`;
|
|
19
|
+
return {
|
|
20
|
+
receiptPath: toPosixPath(path.join(statePaths.runDir, 'intents', fileName)),
|
|
21
|
+
absoluteReceiptPath: path.join(statePaths.absoluteIntentDir, fileName),
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
export function resolveLatestVerifyRunReceiptPath(projectRoot) {
|
|
25
|
+
return path.join(projectRoot, LATEST_RUN_RECEIPT_PATH);
|
|
26
|
+
}
|
|
27
|
+
function toPosixPath(value) {
|
|
28
|
+
return value.split(path.sep).join('/');
|
|
29
|
+
}
|
|
30
|
+
function sanitizeIntentFilePart(value) {
|
|
31
|
+
const sanitized = value.replace(/[^A-Za-z0-9._-]+/g, '_').replace(/^_+|_+$/g, '');
|
|
32
|
+
return sanitized.length > 0 ? sanitized.slice(0, 80) : 'intent';
|
|
33
|
+
}
|
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
import { createHash } from 'node:crypto';
|
|
2
|
-
import path from 'node:path';
|
|
3
2
|
import { runRun } from './run.js';
|
|
4
3
|
import { createChangeVerificationReport, } from '../../core/change-verification.js';
|
|
5
4
|
import { createCorrelationId } from '../../core/correlation-id.js';
|
|
6
5
|
import { readUtf8FileInsideWithoutSymlinks, writeJsonFileInsideWithoutSymlinks } from '../../core/safe-filesystem.js';
|
|
7
6
|
import { createVerifyCompletionVerdict, } from '../../core/completion-verdict.js';
|
|
8
|
-
import { createStateRunId } from '../../core/atomic-state-write.js';
|
|
9
7
|
import { createExternalEvidenceRisks, } from '../../core/external-evidence.js';
|
|
10
8
|
import { createRepeatedFailureRisks, createVerificationFailureFingerprint, updateRepeatedFailureState, } from '../../core/repeated-failure.js';
|
|
11
9
|
import { countReproEvidenceVerdictEffects, createReproEvidenceRisks, } from '../../core/repro-evidence.js';
|
|
@@ -17,14 +15,13 @@ import { readCommandContract } from '../../core/config-loading.js';
|
|
|
17
15
|
import { DEFAULT_VERIFY_PARALLELISM, parseVerifyArgs, resolveVerifyParallelism, } from './verify/args.js';
|
|
18
16
|
import { createInputFromChanged, createSyntheticClassificationReport, planErrorMessageKey, readInputFromClassificationReport, resolveVerifyInputPath, writeChangedPlan, } from './verify/input.js';
|
|
19
17
|
import { readExternalEvidenceFile, readReproEvidenceFile } from './verify/evidence-input.js';
|
|
18
|
+
import { createVerifyIntentReceiptPath, createVerifyRunStatePaths, resolveLatestVerifyRunReceiptPath, } from './verify/state-paths.js';
|
|
20
19
|
import { printUsageError, renderHelp } from '../lib/cli-output.js';
|
|
21
20
|
import { t } from '../lib/i18n.js';
|
|
22
21
|
import { readLocalCommandEffectGraphs, readLocalPathSurfaces, readLocalSourceAnchorVerdictRisks, } from '../lib/local-index.js';
|
|
23
22
|
import { resolveMustflowRoot } from '../lib/project-root.js';
|
|
24
23
|
export { planErrorMessageKey, readInputFromClassificationReport } from './verify/input.js';
|
|
25
24
|
const VERIFY_SCHEMA_VERSION = '1';
|
|
26
|
-
const RUN_STATE_DIR = path.join('.mustflow', 'state', 'runs');
|
|
27
|
-
const LATEST_RUN_RECEIPT_PATH = path.join(RUN_STATE_DIR, 'latest.json');
|
|
28
25
|
function createBufferedOutput() {
|
|
29
26
|
const stdout = [];
|
|
30
27
|
const stderr = [];
|
|
@@ -80,25 +77,6 @@ export function getVerifyHelp(lang = 'en') {
|
|
|
80
77
|
function uniqueStrings(values) {
|
|
81
78
|
return [...new Set(values.map((value) => value.trim()).filter((value) => value.length > 0))];
|
|
82
79
|
}
|
|
83
|
-
function toPosixPath(value) {
|
|
84
|
-
return value.split(path.sep).join('/');
|
|
85
|
-
}
|
|
86
|
-
function createVerifyRunStatePaths(projectRoot) {
|
|
87
|
-
const runDir = toPosixPath(path.join(RUN_STATE_DIR, createStateRunId('verify')));
|
|
88
|
-
const manifestPath = toPosixPath(path.join(runDir, 'manifest.json'));
|
|
89
|
-
const absoluteRunDir = path.join(projectRoot, runDir);
|
|
90
|
-
return {
|
|
91
|
-
runDir,
|
|
92
|
-
manifestPath,
|
|
93
|
-
absoluteRunDir,
|
|
94
|
-
absoluteIntentDir: path.join(absoluteRunDir, 'intents'),
|
|
95
|
-
absoluteManifestPath: path.join(projectRoot, manifestPath),
|
|
96
|
-
};
|
|
97
|
-
}
|
|
98
|
-
function sanitizeIntentFilePart(value) {
|
|
99
|
-
const sanitized = value.replace(/[^A-Za-z0-9._-]+/g, '_').replace(/^_+|_+$/g, '');
|
|
100
|
-
return sanitized.length > 0 ? sanitized.slice(0, 80) : 'intent';
|
|
101
|
-
}
|
|
102
80
|
function skippedResult(candidate) {
|
|
103
81
|
return {
|
|
104
82
|
intent: candidate.intent || null,
|
|
@@ -572,7 +550,7 @@ function readVerificationFailureFingerprint(value) {
|
|
|
572
550
|
}
|
|
573
551
|
function readPreviousVerifyLatestSummary(projectRoot) {
|
|
574
552
|
try {
|
|
575
|
-
const parsed = JSON.parse(readUtf8FileInsideWithoutSymlinks(projectRoot,
|
|
553
|
+
const parsed = JSON.parse(readUtf8FileInsideWithoutSymlinks(projectRoot, resolveLatestVerifyRunReceiptPath(projectRoot)));
|
|
576
554
|
if (parsed.command !== 'verify' ||
|
|
577
555
|
parsed.kind !== 'verify_run_summary' ||
|
|
578
556
|
typeof parsed.verification_plan_id !== 'string' ||
|
|
@@ -652,9 +630,8 @@ function writeVerifyRunReceipts(projectRoot, output, report, sourceAnchorRisks,
|
|
|
652
630
|
let receiptSha256 = null;
|
|
653
631
|
let receipt = result.receipt;
|
|
654
632
|
if (result.intent && result.receipt) {
|
|
655
|
-
const
|
|
656
|
-
|
|
657
|
-
receiptPath = toPosixPath(path.join(statePaths.runDir, 'intents', fileName));
|
|
633
|
+
const receiptStatePath = createVerifyIntentReceiptPath(statePaths, index + 1, result.intent);
|
|
634
|
+
receiptPath = receiptStatePath.receiptPath;
|
|
658
635
|
receipt = {
|
|
659
636
|
...result.receipt,
|
|
660
637
|
verification_plan_id: output.verification_plan_id,
|
|
@@ -662,7 +639,7 @@ function writeVerifyRunReceipts(projectRoot, output, report, sourceAnchorRisks,
|
|
|
662
639
|
};
|
|
663
640
|
const receiptContent = `${JSON.stringify(receipt, null, 2)}\n`;
|
|
664
641
|
receiptSha256 = hashTextSha256(receiptContent);
|
|
665
|
-
writeJsonFileInsideWithoutSymlinks(projectRoot, absoluteReceiptPath, receipt);
|
|
642
|
+
writeJsonFileInsideWithoutSymlinks(projectRoot, receiptStatePath.absoluteReceiptPath, receipt);
|
|
666
643
|
}
|
|
667
644
|
receipts.push({
|
|
668
645
|
intent: result.intent,
|
|
@@ -791,7 +768,7 @@ function writeVerifyRunReceipts(projectRoot, output, report, sourceAnchorRisks,
|
|
|
791
768
|
run_dir: statePaths.runDir,
|
|
792
769
|
manifest_path: statePaths.manifestPath,
|
|
793
770
|
};
|
|
794
|
-
writeJsonFileInsideWithoutSymlinks(projectRoot,
|
|
771
|
+
writeJsonFileInsideWithoutSymlinks(projectRoot, resolveLatestVerifyRunReceiptPath(projectRoot), latest);
|
|
795
772
|
return outputWithReceiptPaths;
|
|
796
773
|
}
|
|
797
774
|
async function createVerifyOutput(input, planSource, projectRoot, lang, reproEvidence = null, externalChecks = [], parallelism = DEFAULT_VERIFY_PARALLELISM, parallelismReport = null) {
|
|
@@ -2,14 +2,12 @@ import { existsSync } from 'node:fs';
|
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { triageDocReview } from '../../core/doc-review-triage.js';
|
|
4
4
|
import { ensureInside, ensureInsideWithoutSymlinks, readUtf8FileInsideWithoutSymlinks, toPosixPath, writeUtf8FileInsideWithoutSymlinks, } from './filesystem.js';
|
|
5
|
+
import { isRecord } from './command-contract.js';
|
|
5
6
|
import { parseTomlText, stringifyToml } from './toml.js';
|
|
6
7
|
export const DOC_REVIEW_LEDGER_RELATIVE_PATH = '.mustflow/review/docs.toml';
|
|
7
8
|
export const DOC_REVIEW_STATUSES = ['pending', 'in_review', 'changes_made', 'approved', 'needs_human', 'ignored'];
|
|
8
9
|
export const DOC_REVIEW_ACTIVE_STATUSES = ['pending', 'in_review', 'changes_made', 'needs_human'];
|
|
9
10
|
export const REVIEWER_KINDS = ['human', 'llm', 'tool', 'external'];
|
|
10
|
-
function isRecord(value) {
|
|
11
|
-
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
12
|
-
}
|
|
13
11
|
function readOptionalString(value) {
|
|
14
12
|
return typeof value === 'string' && value.trim().length > 0 ? value : undefined;
|
|
15
13
|
}
|
|
@@ -2,11 +2,9 @@ import { createHash } from 'node:crypto';
|
|
|
2
2
|
import { existsSync } from 'node:fs';
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
import { ensureFileTargetInsideWithoutSymlinks, ensureInside, readFileInsideWithoutSymlinks, readUtf8FileInsideWithoutSymlinks, writeUtf8FileInsideWithoutSymlinks, } from './filesystem.js';
|
|
5
|
+
import { isRecord } from './command-contract.js';
|
|
5
6
|
import { parseTomlText, stringifyToml } from './toml.js';
|
|
6
7
|
export const MANIFEST_LOCK_RELATIVE_PATH = '.mustflow/config/manifest.lock.toml';
|
|
7
|
-
function isRecord(value) {
|
|
8
|
-
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
9
|
-
}
|
|
10
8
|
function readString(table, key, label) {
|
|
11
9
|
const value = table[key];
|
|
12
10
|
if (typeof value !== 'string' || value.trim().length === 0) {
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
const REPO_MAP_DOC_ID = 'repo-map';
|
|
3
|
+
const REPO_MAP_LIFECYCLE = 'generated';
|
|
4
|
+
const REPO_MAP_GENERATOR = 'mustflow';
|
|
5
|
+
const REPO_MAP_RELATIVE_ROOT = '.';
|
|
6
|
+
const REPO_MAP_SOURCE_POLICY = 'anchors_only';
|
|
7
|
+
const REPO_MAP_PRIVACY_MODE = 'minimal';
|
|
8
|
+
export function getRepoMapSourceFingerprint(input) {
|
|
9
|
+
const payload = {
|
|
10
|
+
depth: input.depth,
|
|
11
|
+
includeNested: input.includeNested,
|
|
12
|
+
gitLsFilesStatus: input.gitLsFilesStatus,
|
|
13
|
+
priorityPaths: [...input.configuredPriorityPaths].sort(),
|
|
14
|
+
anchors: input.anchors.map((anchor) => anchor.relativePath).sort(),
|
|
15
|
+
nestedRepositories: input.nestedRepositories
|
|
16
|
+
.map((repository) => ({
|
|
17
|
+
relativePath: repository.relativePath,
|
|
18
|
+
mustflow: repository.mustflow,
|
|
19
|
+
agentRules: repository.agentRules,
|
|
20
|
+
repoMap: repository.repoMap,
|
|
21
|
+
mustflowConfig: repository.mustflowConfig,
|
|
22
|
+
commandContract: repository.commandContract,
|
|
23
|
+
contextIndex: repository.contextIndex,
|
|
24
|
+
skillIndex: repository.skillIndex,
|
|
25
|
+
rootDocuments: repository.rootDocuments.map((document) => document.relativePath).sort(),
|
|
26
|
+
machineContracts: [...repository.machineContracts].sort(),
|
|
27
|
+
manifests: [...repository.manifests].sort(),
|
|
28
|
+
commandAdapters: [...repository.commandAdapters].sort(),
|
|
29
|
+
editingPolicies: [...repository.editingPolicies].sort(),
|
|
30
|
+
}))
|
|
31
|
+
.sort((left, right) => left.relativePath.localeCompare(right.relativePath)),
|
|
32
|
+
};
|
|
33
|
+
const digest = createHash('sha256').update(JSON.stringify(payload)).digest('hex');
|
|
34
|
+
return `sha256:${digest}`;
|
|
35
|
+
}
|
|
36
|
+
export function renderRepoMapFrontmatter(anchorCount, sourceFingerprint, gitLsFilesStatus) {
|
|
37
|
+
const degraded = gitLsFilesStatus !== 'ok';
|
|
38
|
+
return [
|
|
39
|
+
'---',
|
|
40
|
+
`mustflow_doc: ${REPO_MAP_DOC_ID}`,
|
|
41
|
+
`lifecycle: ${REPO_MAP_LIFECYCLE}`,
|
|
42
|
+
`generated_by: ${REPO_MAP_GENERATOR}`,
|
|
43
|
+
`relative_root: "${REPO_MAP_RELATIVE_ROOT}"`,
|
|
44
|
+
`source_policy: ${REPO_MAP_SOURCE_POLICY}`,
|
|
45
|
+
`privacy_mode: ${REPO_MAP_PRIVACY_MODE}`,
|
|
46
|
+
`anchor_count: ${anchorCount}`,
|
|
47
|
+
`degraded: ${degraded ? 'true' : 'false'}`,
|
|
48
|
+
`git_ls_files_status: ${gitLsFilesStatus}`,
|
|
49
|
+
`source_fingerprint: "${sourceFingerprint}"`,
|
|
50
|
+
'---',
|
|
51
|
+
'',
|
|
52
|
+
];
|
|
53
|
+
}
|
package/dist/cli/lib/repo-map.js
CHANGED
|
@@ -1,17 +1,12 @@
|
|
|
1
1
|
import { spawnSync } from 'node:child_process';
|
|
2
|
-
import { createHash } from 'node:crypto';
|
|
3
2
|
import { existsSync, lstatSync, readdirSync, realpathSync, statSync } from 'node:fs';
|
|
4
3
|
import path from 'node:path';
|
|
5
4
|
import { toPosixPath } from './filesystem.js';
|
|
5
|
+
import { getRepoMapSourceFingerprint, renderRepoMapFrontmatter, } from './repo-map-frontmatter.js';
|
|
6
6
|
import { writeUtf8FileInsideWithoutSymlinks } from '../../core/safe-filesystem.js';
|
|
7
|
+
import { isRecord } from './command-contract.js';
|
|
7
8
|
import { readMustflowTomlFile } from './toml.js';
|
|
8
9
|
const DEFAULT_DEPTH = 3;
|
|
9
|
-
const REPO_MAP_DOC_ID = 'repo-map';
|
|
10
|
-
const REPO_MAP_LIFECYCLE = 'generated';
|
|
11
|
-
const REPO_MAP_GENERATOR = 'mustflow';
|
|
12
|
-
const REPO_MAP_RELATIVE_ROOT = '.';
|
|
13
|
-
const REPO_MAP_SOURCE_POLICY = 'anchors_only';
|
|
14
|
-
const REPO_MAP_PRIVACY_MODE = 'minimal';
|
|
15
10
|
const GIT_LS_FILES_TIMEOUT_MS = 5_000;
|
|
16
11
|
const GIT_LS_FILES_MAX_BUFFER_BYTES = 1_048_576;
|
|
17
12
|
const EXCLUDED_SEGMENTS = new Set([
|
|
@@ -196,9 +191,6 @@ const EXACT_ANCHOR_DESCRIPTIONS = new Map([
|
|
|
196
191
|
['.mustflow/docs/agent-workflow.md', 'Shared workflow policy for agent work.'],
|
|
197
192
|
['.mustflow/skills/INDEX.md', 'Index of available procedural skills.'],
|
|
198
193
|
]);
|
|
199
|
-
function isRecord(value) {
|
|
200
|
-
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
201
|
-
}
|
|
202
194
|
function getStringArray(value) {
|
|
203
195
|
return Array.isArray(value) ? value.filter((entry) => typeof entry === 'string') : [];
|
|
204
196
|
}
|
|
@@ -624,52 +616,6 @@ function countNestedEntrypoints(repository) {
|
|
|
624
616
|
...repository.editingPolicies,
|
|
625
617
|
].filter(Boolean).length;
|
|
626
618
|
}
|
|
627
|
-
function getSourceFingerprint(depth, includeNested, configuredPriorityPaths, gitLsFilesStatus, anchors, nestedRepositories) {
|
|
628
|
-
const payload = {
|
|
629
|
-
depth,
|
|
630
|
-
includeNested,
|
|
631
|
-
gitLsFilesStatus,
|
|
632
|
-
priorityPaths: [...configuredPriorityPaths].sort(),
|
|
633
|
-
anchors: anchors.map((anchor) => anchor.relativePath).sort(),
|
|
634
|
-
nestedRepositories: nestedRepositories
|
|
635
|
-
.map((repository) => ({
|
|
636
|
-
relativePath: repository.relativePath,
|
|
637
|
-
mustflow: repository.mustflow,
|
|
638
|
-
agentRules: repository.agentRules,
|
|
639
|
-
repoMap: repository.repoMap,
|
|
640
|
-
mustflowConfig: repository.mustflowConfig,
|
|
641
|
-
commandContract: repository.commandContract,
|
|
642
|
-
contextIndex: repository.contextIndex,
|
|
643
|
-
skillIndex: repository.skillIndex,
|
|
644
|
-
rootDocuments: repository.rootDocuments.map((document) => document.relativePath).sort(),
|
|
645
|
-
machineContracts: [...repository.machineContracts].sort(),
|
|
646
|
-
manifests: [...repository.manifests].sort(),
|
|
647
|
-
commandAdapters: [...repository.commandAdapters].sort(),
|
|
648
|
-
editingPolicies: [...repository.editingPolicies].sort(),
|
|
649
|
-
}))
|
|
650
|
-
.sort((left, right) => left.relativePath.localeCompare(right.relativePath)),
|
|
651
|
-
};
|
|
652
|
-
const digest = createHash('sha256').update(JSON.stringify(payload)).digest('hex');
|
|
653
|
-
return `sha256:${digest}`;
|
|
654
|
-
}
|
|
655
|
-
function renderRepoMapFrontmatter(anchorCount, sourceFingerprint, gitLsFilesStatus) {
|
|
656
|
-
const degraded = gitLsFilesStatus !== 'ok';
|
|
657
|
-
return [
|
|
658
|
-
'---',
|
|
659
|
-
`mustflow_doc: ${REPO_MAP_DOC_ID}`,
|
|
660
|
-
`lifecycle: ${REPO_MAP_LIFECYCLE}`,
|
|
661
|
-
`generated_by: ${REPO_MAP_GENERATOR}`,
|
|
662
|
-
`relative_root: "${REPO_MAP_RELATIVE_ROOT}"`,
|
|
663
|
-
`source_policy: ${REPO_MAP_SOURCE_POLICY}`,
|
|
664
|
-
`privacy_mode: ${REPO_MAP_PRIVACY_MODE}`,
|
|
665
|
-
`anchor_count: ${anchorCount}`,
|
|
666
|
-
`degraded: ${degraded ? 'true' : 'false'}`,
|
|
667
|
-
`git_ls_files_status: ${gitLsFilesStatus}`,
|
|
668
|
-
`source_fingerprint: "${sourceFingerprint}"`,
|
|
669
|
-
'---',
|
|
670
|
-
'',
|
|
671
|
-
];
|
|
672
|
-
}
|
|
673
619
|
function renderSourceQuality(gitLsFilesStatus) {
|
|
674
620
|
if (gitLsFilesStatus === 'ok') {
|
|
675
621
|
return [];
|
|
@@ -701,7 +647,14 @@ export function generateRepoMap(projectRoot, options = {}) {
|
|
|
701
647
|
.filter((anchor) => Boolean(anchor));
|
|
702
648
|
const otherAnchors = anchors.filter((anchor) => !priorityPathSet.has(anchor.relativePath));
|
|
703
649
|
const anchorCount = anchors.length + nestedRepositories.reduce((total, repository) => total + countNestedEntrypoints(repository), 0);
|
|
704
|
-
const sourceFingerprint =
|
|
650
|
+
const sourceFingerprint = getRepoMapSourceFingerprint({
|
|
651
|
+
depth,
|
|
652
|
+
includeNested: mapConfig.includeNested,
|
|
653
|
+
configuredPriorityPaths,
|
|
654
|
+
gitLsFilesStatus,
|
|
655
|
+
anchors,
|
|
656
|
+
nestedRepositories,
|
|
657
|
+
});
|
|
705
658
|
return [
|
|
706
659
|
...renderRepoMapFrontmatter(anchorCount, sourceFingerprint, gitLsFilesStatus),
|
|
707
660
|
'# REPO_MAP.md',
|
|
@@ -2,6 +2,7 @@ import { existsSync, readFileSync, statSync } from 'node:fs';
|
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { isRecord } from '../command-contract.js';
|
|
4
4
|
import { validateCommandContractConfig, validateCommandContractStrictDefaults, } from '../../../core/command-contract-validation.js';
|
|
5
|
+
import { describeCheckIssues } from '../../../core/check-issues.js';
|
|
5
6
|
import { ALLOWED_RETENTION_ON_LIMIT, ALLOWED_RETENTION_STORES, DEFAULT_RETENTION_LIMITS, readNestedRetentionTable, readRetentionTable, resolveRetentionLimits, } from '../../../core/retention-policy.js';
|
|
6
7
|
import { formatManagedMarkdownLabel, getManagedMarkdownExpectation, } from '../../../core/authority-resolution.js';
|
|
7
8
|
import { SKILL_INDEX_ROUTE_COLUMN_COUNT, SKILL_INDEX_ROUTE_COLUMNS, SKILL_INDEX_SKILL_PATH_COLUMN_INDEX, findSkillRouteConflictWarnings, findSkillIndexRoutePathColumn, parseSkillIndexRoutes, readBacktickValues, } from '../../../core/skill-route-alignment.js';
|
|
@@ -1641,12 +1642,15 @@ function collectCheckIssues(projectRoot, options = {}) {
|
|
|
1641
1642
|
}
|
|
1642
1643
|
export function checkMustflowProjectReport(projectRoot, options = {}) {
|
|
1643
1644
|
const issues = collectCheckIssues(projectRoot, options);
|
|
1644
|
-
const
|
|
1645
|
-
const
|
|
1645
|
+
const errorIssues = issues.filter((issue) => issue.severity !== 'warning');
|
|
1646
|
+
const warningIssues = issues.filter((issue) => issue.severity === 'warning');
|
|
1647
|
+
const errors = errorIssues.map((issue) => issue.message);
|
|
1648
|
+
const warnings = warningIssues.map((issue) => issue.message);
|
|
1646
1649
|
return {
|
|
1647
1650
|
issues: errors,
|
|
1648
1651
|
warnings,
|
|
1649
1652
|
allMessages: [...errors, ...warnings],
|
|
1653
|
+
issueDetails: describeCheckIssues([...errorIssues, ...warningIssues]),
|
|
1650
1654
|
};
|
|
1651
1655
|
}
|
|
1652
1656
|
export function checkMustflowProject(projectRoot, options = {}) {
|
|
@@ -86,11 +86,15 @@ export function getCheckIssueId(message) {
|
|
|
86
86
|
}
|
|
87
87
|
return null;
|
|
88
88
|
}
|
|
89
|
-
export function describeCheckIssues(
|
|
90
|
-
return
|
|
91
|
-
|
|
92
|
-
severity
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
89
|
+
export function describeCheckIssues(issues) {
|
|
90
|
+
return issues.map((issue) => {
|
|
91
|
+
const message = typeof issue === 'string' ? issue : issue.message;
|
|
92
|
+
const severity = message.startsWith('Strict warning: ') ? 'warning' : 'error';
|
|
93
|
+
return {
|
|
94
|
+
id: typeof issue === 'string' ? getCheckIssueId(message) : issue.id ?? getCheckIssueId(message),
|
|
95
|
+
severity: typeof issue === 'string' ? severity : issue.severity ?? severity,
|
|
96
|
+
mode: message.startsWith('Strict') ? 'strict' : 'base',
|
|
97
|
+
message,
|
|
98
|
+
};
|
|
99
|
+
});
|
|
96
100
|
}
|
|
@@ -6,11 +6,11 @@ import { COMMAND_EFFECT_CONCURRENCY, COMMAND_EFFECT_MODES, COMMAND_EFFECT_TYPES,
|
|
|
6
6
|
import { commandIntentBlockedCommandPattern, commandIntentHasCommandSource, commandIntentNameIsSafe, } from './command-contract-rules.js';
|
|
7
7
|
import { MAX_COMMAND_OUTPUT_BYTES, commandMaxOutputBytesLimitMessage } from './command-output-limits.js';
|
|
8
8
|
import { SUCCESS_EXIT_CODES_CONTRACT_DESCRIPTION, successExitCodesAreValid } from './success-exit-codes.js';
|
|
9
|
-
function commandContractIssue(message) {
|
|
10
|
-
return { message };
|
|
9
|
+
function commandContractIssue(message, id) {
|
|
10
|
+
return id ? { id, message } : { message };
|
|
11
11
|
}
|
|
12
|
-
function commandContractWarning(message) {
|
|
13
|
-
return { message, severity: 'warning' };
|
|
12
|
+
function commandContractWarning(message, id) {
|
|
13
|
+
return id ? { id, message, severity: 'warning' } : { message, severity: 'warning' };
|
|
14
14
|
}
|
|
15
15
|
function hasOwn(table, key) {
|
|
16
16
|
return Object.prototype.hasOwnProperty.call(table, key);
|
|
@@ -56,7 +56,7 @@ function validatePositiveIntegerField(table, key, label, issues) {
|
|
|
56
56
|
function validateMaxOutputBytesField(table, key, label, issues) {
|
|
57
57
|
validatePositiveIntegerField(table, key, label, issues);
|
|
58
58
|
if (isPositiveInteger(table[key]) && Number(table[key]) > MAX_COMMAND_OUTPUT_BYTES) {
|
|
59
|
-
issues.push(commandContractIssue(commandMaxOutputBytesLimitMessage(label)));
|
|
59
|
+
issues.push(commandContractIssue(commandMaxOutputBytesLimitMessage(label), 'mustflow.command_contract.max_output_bytes_exceeds_limit'));
|
|
60
60
|
}
|
|
61
61
|
}
|
|
62
62
|
function validateAllowedStringField(table, key, label, allowedValues, issues) {
|
|
@@ -115,7 +115,7 @@ function validateCommandIntentEffects(intentName, intent, issues) {
|
|
|
115
115
|
return;
|
|
116
116
|
}
|
|
117
117
|
if (!Array.isArray(intent.effects) || intent.effects.some((entry) => !isRecord(entry))) {
|
|
118
|
-
issues.push(commandContractIssue(`[commands.intents.${intentName}].effects must be an array of tables
|
|
118
|
+
issues.push(commandContractIssue(`[commands.intents.${intentName}].effects must be an array of tables`, 'mustflow.command_contract.effects_invalid'));
|
|
119
119
|
return;
|
|
120
120
|
}
|
|
121
121
|
for (const effect of intent.effects) {
|
|
@@ -171,39 +171,39 @@ function validateCommandIntent(intentName, intent, issues) {
|
|
|
171
171
|
const lifecycle = typeof intent.lifecycle === 'string' ? intent.lifecycle : undefined;
|
|
172
172
|
const runPolicy = typeof intent.run_policy === 'string' ? intent.run_policy : undefined;
|
|
173
173
|
if (!lifecycle) {
|
|
174
|
-
issues.push(commandContractIssue(`Configured intent ${intentName} must define lifecycle
|
|
174
|
+
issues.push(commandContractIssue(`Configured intent ${intentName} must define lifecycle`, 'mustflow.command_contract.configured_missing_lifecycle'));
|
|
175
175
|
}
|
|
176
176
|
if (!runPolicy) {
|
|
177
|
-
issues.push(commandContractIssue(`Configured intent ${intentName} must define run_policy
|
|
177
|
+
issues.push(commandContractIssue(`Configured intent ${intentName} must define run_policy`, 'mustflow.command_contract.configured_missing_run_policy'));
|
|
178
178
|
}
|
|
179
179
|
if (lifecycle === 'oneshot') {
|
|
180
180
|
validatePositiveIntegerField(intent, 'timeout_seconds', `[commands.intents.${intentName}].timeout_seconds`, issues);
|
|
181
181
|
if (!hasOwn(intent, 'timeout_seconds')) {
|
|
182
|
-
issues.push(commandContractIssue(`Oneshot intent ${intentName} must define timeout_seconds
|
|
182
|
+
issues.push(commandContractIssue(`Oneshot intent ${intentName} must define timeout_seconds`, 'mustflow.command_contract.oneshot_missing_timeout'));
|
|
183
183
|
}
|
|
184
184
|
if (intent.stdin !== 'closed') {
|
|
185
|
-
issues.push(commandContractIssue(`Oneshot intent ${intentName} must set stdin = "closed"
|
|
185
|
+
issues.push(commandContractIssue(`Oneshot intent ${intentName} must set stdin = "closed"`, 'mustflow.command_contract.oneshot_stdin_not_closed'));
|
|
186
186
|
}
|
|
187
187
|
}
|
|
188
188
|
if (lifecycle && LONG_RUNNING_LIFECYCLES.has(lifecycle) && runPolicy === 'agent_allowed') {
|
|
189
|
-
issues.push(commandContractIssue(`Long-running intent ${intentName} must not use run_policy = "agent_allowed"
|
|
189
|
+
issues.push(commandContractIssue(`Long-running intent ${intentName} must not use run_policy = "agent_allowed"`, 'mustflow.command_contract.long_running_agent_allowed'));
|
|
190
190
|
}
|
|
191
191
|
if (intent.mode === 'shell' && runPolicy === 'agent_allowed' && intent.allow_shell !== true) {
|
|
192
|
-
issues.push(commandContractIssue(`Agent-runnable shell intent ${intentName} must set allow_shell = true
|
|
192
|
+
issues.push(commandContractIssue(`Agent-runnable shell intent ${intentName} must set allow_shell = true`, 'mustflow.command_contract.agent_shell_requires_allow'));
|
|
193
193
|
}
|
|
194
194
|
if (!commandIntentHasCommandSource(intent)) {
|
|
195
|
-
issues.push(commandContractIssue(`Configured intent ${intentName} must define argv or mode = "shell" with cmd
|
|
195
|
+
issues.push(commandContractIssue(`Configured intent ${intentName} must define argv or mode = "shell" with cmd`, 'mustflow.command_contract.executable_source_missing'));
|
|
196
196
|
}
|
|
197
197
|
const blockedCommandPattern = commandIntentBlockedCommandPattern(intent);
|
|
198
198
|
if (blockedCommandPattern?.code === 'shell_background_pattern') {
|
|
199
|
-
issues.push(commandContractIssue(`Shell intent ${intentName} contains a blocked long-running or background pattern
|
|
199
|
+
issues.push(commandContractIssue(`Shell intent ${intentName} contains a blocked long-running or background pattern`, 'mustflow.command_contract.shell_background_pattern'));
|
|
200
200
|
}
|
|
201
201
|
if (blockedCommandPattern?.code === 'long_running_command_pattern') {
|
|
202
|
-
issues.push(commandContractIssue(`Intent ${intentName} contains a blocked long-running or background command pattern
|
|
202
|
+
issues.push(commandContractIssue(`Intent ${intentName} contains a blocked long-running or background command pattern`, 'mustflow.command_contract.long_running_command_pattern'));
|
|
203
203
|
}
|
|
204
204
|
if (hasOwn(intent, 'success_exit_codes')) {
|
|
205
205
|
if (!successExitCodesAreValid(intent.success_exit_codes)) {
|
|
206
|
-
issues.push(commandContractIssue(`[commands.intents.${intentName}].success_exit_codes must be ${SUCCESS_EXIT_CODES_CONTRACT_DESCRIPTION}
|
|
206
|
+
issues.push(commandContractIssue(`[commands.intents.${intentName}].success_exit_codes must be ${SUCCESS_EXIT_CODES_CONTRACT_DESCRIPTION}`, 'mustflow.command_contract.success_exit_codes_invalid'));
|
|
207
207
|
}
|
|
208
208
|
}
|
|
209
209
|
validateCommandIntentEffects(intentName, intent, issues);
|
|
@@ -281,7 +281,9 @@ function validateCommandEnvInheritanceWarnings(commandsToml) {
|
|
|
281
281
|
const issues = [];
|
|
282
282
|
for (const warning of findCommandEnvInheritanceWarnings(commandsToml)) {
|
|
283
283
|
const message = formatCommandEnvInheritanceWarning(warning);
|
|
284
|
-
issues.push(warning.severity === 'warning'
|
|
284
|
+
issues.push(warning.severity === 'warning'
|
|
285
|
+
? commandContractWarning(message, 'mustflow.command_contract.broad_env_inheritance')
|
|
286
|
+
: commandContractIssue(message, 'mustflow.command_contract.broad_env_inheritance'));
|
|
285
287
|
}
|
|
286
288
|
return issues;
|
|
287
289
|
}
|
|
@@ -316,7 +318,7 @@ function validateProjectLocalBinWarnings(projectRoot, commandsToml) {
|
|
|
316
318
|
if (!projectLocalBinExecutableExists(projectRoot, executable)) {
|
|
317
319
|
continue;
|
|
318
320
|
}
|
|
319
|
-
issues.push(commandContractWarning(`configured agent-runnable intent ${intentName} uses bare executable "${executable}" that matches project-local node_modules/.bin; use a package-manager mediated command such as npm exec, pnpm exec, bun x, or yarn exec
|
|
321
|
+
issues.push(commandContractWarning(`configured agent-runnable intent ${intentName} uses bare executable "${executable}" that matches project-local node_modules/.bin; use a package-manager mediated command such as npm exec, pnpm exec, bun x, or yarn exec`, 'mustflow.command_contract.project_local_bin_bare_executable'));
|
|
320
322
|
}
|
|
321
323
|
return issues;
|
|
322
324
|
}
|
|
@@ -336,12 +338,12 @@ export function validateCommandContractConfig(commandsToml) {
|
|
|
336
338
|
validateCommandDefaults(commandsToml, issues);
|
|
337
339
|
validateCommandResources(commandsToml, issues);
|
|
338
340
|
if (!isRecord(commandsToml.intents)) {
|
|
339
|
-
issues.push(commandContractIssue('Missing [intents] table in .mustflow/config/commands.toml'));
|
|
341
|
+
issues.push(commandContractIssue('Missing [intents] table in .mustflow/config/commands.toml', 'mustflow.command_contract.intent_table_missing'));
|
|
340
342
|
return issues;
|
|
341
343
|
}
|
|
342
344
|
for (const [intentName, intent] of Object.entries(commandsToml.intents)) {
|
|
343
345
|
if (!isRecord(intent)) {
|
|
344
|
-
issues.push(commandContractIssue(`Intent ${intentName} must be a TOML table
|
|
346
|
+
issues.push(commandContractIssue(`Intent ${intentName} must be a TOML table`, 'mustflow.command_contract.intent_not_table'));
|
|
345
347
|
continue;
|
|
346
348
|
}
|
|
347
349
|
validateCommandIntent(intentName, intent, issues);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mustflow",
|
|
3
|
-
"version": "2.22.
|
|
3
|
+
"version": "2.22.13",
|
|
4
4
|
"description": "Agent workflow documents and CLI for mustflow repository roots.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT-0",
|
|
@@ -28,19 +28,20 @@
|
|
|
28
28
|
"scripts": {
|
|
29
29
|
"build": "node -e \"require('fs').rmSync('dist',{recursive:true,force:true})\" && tsc -p tsconfig.json",
|
|
30
30
|
"test": "bun run test:full",
|
|
31
|
-
"test:fast": "
|
|
32
|
-
"test:related": "
|
|
31
|
+
"test:fast": "node scripts/run-cli-tests.mjs --build fast",
|
|
32
|
+
"test:related": "node scripts/run-cli-tests.mjs --build related",
|
|
33
33
|
"test:related:cached": "node scripts/run-cli-tests.mjs related-cached",
|
|
34
|
-
"test:related:profile": "
|
|
35
|
-
"test:cli": "
|
|
36
|
-
"test:coverage": "
|
|
34
|
+
"test:related:profile": "node scripts/run-cli-tests.mjs --build related-profile",
|
|
35
|
+
"test:cli": "node scripts/run-cli-tests.mjs --build cli",
|
|
36
|
+
"test:coverage": "node scripts/run-cli-tests.mjs --build coverage",
|
|
37
37
|
"test:audit": "node scripts/audit-tests.mjs --json",
|
|
38
|
-
"test:release": "
|
|
39
|
-
"test:fast:node": "
|
|
40
|
-
"test:release:node": "
|
|
41
|
-
"test:full": "
|
|
42
|
-
"test:full:auto": "
|
|
43
|
-
"test:full:
|
|
38
|
+
"test:release": "node scripts/run-cli-tests.mjs --build release",
|
|
39
|
+
"test:fast:node": "node scripts/run-cli-tests.mjs --build-runner=npm fast",
|
|
40
|
+
"test:release:node": "node scripts/run-cli-tests.mjs --build-runner=npm release",
|
|
41
|
+
"test:full": "node scripts/run-cli-tests.mjs --build full-auto",
|
|
42
|
+
"test:full:auto": "node scripts/run-cli-tests.mjs --build full-auto",
|
|
43
|
+
"test:full:profile": "node scripts/run-cli-tests.mjs --build full-profile",
|
|
44
|
+
"test:full:serial": "node scripts/run-cli-tests.mjs --build full",
|
|
44
45
|
"check": "bun run check:package && bun run test:full",
|
|
45
46
|
"check:core:node": "node scripts/run-node-core-check.mjs",
|
|
46
47
|
"check:package": "node -e \"const fs=require('fs'); JSON.parse(fs.readFileSync('package.json','utf8')); console.log('package.json ok')\"",
|