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.
Files changed (33) hide show
  1. package/dist/cli/commands/check.js +2 -2
  2. package/dist/cli/commands/help.js +1 -3
  3. package/dist/cli/commands/verify/state-paths.js +33 -0
  4. package/dist/cli/commands/verify.js +6 -29
  5. package/dist/cli/lib/doc-review-ledger.js +1 -3
  6. package/dist/cli/lib/manifest-lock.js +1 -3
  7. package/dist/cli/lib/repo-map-frontmatter.js +53 -0
  8. package/dist/cli/lib/repo-map.js +10 -57
  9. package/dist/cli/lib/validation/index.js +6 -2
  10. package/dist/core/check-issues.js +11 -7
  11. package/dist/core/command-contract-validation.js +22 -20
  12. package/package.json +13 -12
  13. package/templates/default/i18n.toml +14 -14
  14. package/templates/default/locales/en/.mustflow/skills/INDEX.md +1 -1
  15. package/templates/default/locales/en/.mustflow/skills/codebase-orientation/SKILL.md +18 -7
  16. package/templates/default/locales/en/.mustflow/skills/contract-sync-check/SKILL.md +14 -7
  17. package/templates/default/locales/en/.mustflow/skills/diff-risk-review/SKILL.md +10 -5
  18. package/templates/default/locales/en/.mustflow/skills/docs-update/SKILL.md +12 -5
  19. package/templates/default/locales/en/.mustflow/skills/failure-triage/SKILL.md +26 -5
  20. package/templates/default/locales/en/.mustflow/skills/pattern-scout/SKILL.md +13 -7
  21. package/templates/default/locales/en/.mustflow/skills/performance-budget-check/SKILL.md +53 -181
  22. package/templates/default/locales/en/.mustflow/skills/readme-authoring/SKILL.md +8 -2
  23. package/templates/default/locales/en/.mustflow/skills/release-notes-authoring/SKILL.md +4 -1
  24. package/templates/default/locales/en/.mustflow/skills/repro-first-debug/SKILL.md +15 -8
  25. package/templates/default/locales/en/.mustflow/skills/requirement-regression-guard/SKILL.md +10 -5
  26. package/templates/default/locales/en/.mustflow/skills/source-freshness-check/SKILL.md +5 -1
  27. package/templates/default/locales/en/.mustflow/skills/structure-discovery-gate/SKILL.md +2 -2
  28. package/templates/default/locales/en/.mustflow/skills/test-maintenance/SKILL.md +17 -4
  29. package/templates/default/locales/ko/.mustflow/context/INDEX.md +12 -12
  30. package/templates/default/locales/ko/.mustflow/context/PROJECT.md +6 -6
  31. package/templates/default/locales/ko/.mustflow/docs/agent-workflow.md +70 -70
  32. package/templates/default/locales/ko/AGENTS.md +14 -14
  33. 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, describeCheckIssues } from '../lib/validation.js';
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: describeCheckIssues([...issues, ...warnings]),
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, path.join(projectRoot, LATEST_RUN_RECEIPT_PATH)));
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 fileName = `${String(index + 1).padStart(3, '0')}-${sanitizeIntentFilePart(result.intent)}.json`;
656
- const absoluteReceiptPath = path.join(statePaths.absoluteIntentDir, fileName);
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, path.join(projectRoot, LATEST_RUN_RECEIPT_PATH), latest);
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
+ }
@@ -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 = getSourceFingerprint(depth, mapConfig.includeNested, configuredPriorityPaths, gitLsFilesStatus, anchors, nestedRepositories);
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 errors = issues.filter((issue) => issue.severity !== 'warning').map((issue) => issue.message);
1645
- const warnings = issues.filter((issue) => issue.severity === 'warning').map((issue) => issue.message);
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(messages) {
90
- return messages.map((message) => ({
91
- id: getCheckIssueId(message),
92
- severity: message.startsWith('Strict warning: ') ? 'warning' : 'error',
93
- mode: message.startsWith('Strict') ? 'strict' : 'base',
94
- message,
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' ? commandContractWarning(message) : commandContractIssue(message));
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.9",
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": "bun run build && node scripts/run-cli-tests.mjs fast",
32
- "test:related": "bun run build && node scripts/run-cli-tests.mjs 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": "bun run build && node scripts/run-cli-tests.mjs related-profile",
35
- "test:cli": "bun run build && node scripts/run-cli-tests.mjs cli",
36
- "test:coverage": "bun run build && node scripts/run-cli-tests.mjs 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": "bun run build && node scripts/run-cli-tests.mjs release",
39
- "test:fast:node": "npm run build && node scripts/run-cli-tests.mjs fast",
40
- "test:release:node": "npm run build && node scripts/run-cli-tests.mjs release",
41
- "test:full": "bun run build && node scripts/run-cli-tests.mjs full-auto",
42
- "test:full:auto": "bun run build && node scripts/run-cli-tests.mjs full-auto",
43
- "test:full:serial": "bun run build && node scripts/run-cli-tests.mjs 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')\"",