mustflow 2.22.5 → 2.22.9

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 (42) hide show
  1. package/README.md +8 -0
  2. package/dist/cli/commands/classify.js +2 -0
  3. package/dist/cli/commands/dashboard.js +9 -69
  4. package/dist/cli/commands/run/receipt.js +1 -0
  5. package/dist/cli/commands/run.js +14 -1
  6. package/dist/cli/commands/verify/evidence-input.js +269 -0
  7. package/dist/cli/commands/verify/input.js +212 -0
  8. package/dist/cli/commands/verify.js +23 -482
  9. package/dist/cli/i18n/en.js +3 -0
  10. package/dist/cli/i18n/es.js +3 -0
  11. package/dist/cli/i18n/fr.js +3 -0
  12. package/dist/cli/i18n/hi.js +3 -0
  13. package/dist/cli/i18n/ko.js +3 -0
  14. package/dist/cli/i18n/zh.js +3 -0
  15. package/dist/cli/lib/dashboard-export.js +2 -0
  16. package/dist/cli/lib/dashboard-mutations.js +79 -0
  17. package/dist/cli/lib/local-index/command-effect-index.js +25 -0
  18. package/dist/cli/lib/local-index/hashing.js +7 -0
  19. package/dist/cli/lib/local-index/index.js +127 -826
  20. package/dist/cli/lib/local-index/source-index.js +137 -0
  21. package/dist/cli/lib/local-index/verification-evidence.js +451 -0
  22. package/dist/cli/lib/local-index/workflow-documents.js +204 -0
  23. package/dist/cli/lib/run-root-trust.js +27 -0
  24. package/dist/core/change-classification-policy.js +47 -0
  25. package/dist/core/change-classification.js +10 -43
  26. package/dist/core/contract-lint.js +6 -2
  27. package/dist/core/correlation-id.js +16 -0
  28. package/dist/core/run-receipt.js +1 -0
  29. package/package.json +4 -1
  30. package/schemas/README.md +4 -0
  31. package/schemas/change-verification-report.schema.json +4 -0
  32. package/schemas/classify-report.schema.json +4 -0
  33. package/schemas/dashboard-export.schema.json +4 -0
  34. package/schemas/latest-run-pointer.schema.json +4 -0
  35. package/schemas/run-receipt.schema.json +4 -0
  36. package/schemas/verify-report.schema.json +4 -0
  37. package/schemas/verify-run-manifest.schema.json +4 -0
  38. package/templates/default/i18n.toml +3 -3
  39. package/templates/default/locales/en/.mustflow/skills/architecture-deepening-review/SKILL.md +25 -2
  40. package/templates/default/locales/en/.mustflow/skills/security-privacy-review/SKILL.md +9 -1
  41. package/templates/default/locales/en/.mustflow/skills/test-design-guard/SKILL.md +9 -1
  42. package/templates/default/manifest.toml +1 -1
@@ -0,0 +1,204 @@
1
+ import { existsSync } from 'node:fs';
2
+ import path from 'node:path';
3
+ import { listFilesRecursive, toPosixPath } from '../filesystem.js';
4
+ import { readMustflowTextFile } from '../mustflow-read.js';
5
+ import { MAX_SNIPPET_BYTES_PER_DOCUMENT } from './constants.js';
6
+ import { sha256Text } from './hashing.js';
7
+ export function getExistingIndexablePaths(projectRoot) {
8
+ const paths = new Set();
9
+ const addIfExists = (relativePath) => {
10
+ if (existsSync(path.join(projectRoot, ...relativePath.split('/')))) {
11
+ paths.add(relativePath);
12
+ }
13
+ };
14
+ addIfExists('AGENTS.md');
15
+ for (const relativePath of listFilesRecursive(path.join(projectRoot, '.mustflow', 'docs'))) {
16
+ if (relativePath.endsWith('.md')) {
17
+ paths.add(toPosixPath(path.join('.mustflow', 'docs', relativePath)));
18
+ }
19
+ }
20
+ for (const relativePath of listFilesRecursive(path.join(projectRoot, '.mustflow', 'context'))) {
21
+ if (relativePath.endsWith('.md')) {
22
+ paths.add(toPosixPath(path.join('.mustflow', 'context', relativePath)));
23
+ }
24
+ }
25
+ for (const relativePath of listFilesRecursive(path.join(projectRoot, '.mustflow', 'skills'))) {
26
+ if (relativePath === 'INDEX.md' || relativePath.endsWith('/SKILL.md')) {
27
+ paths.add(toPosixPath(path.join('.mustflow', 'skills', relativePath)));
28
+ }
29
+ }
30
+ for (const relativePath of listFilesRecursive(path.join(projectRoot, '.mustflow', 'config'))) {
31
+ if (relativePath.endsWith('.toml')) {
32
+ paths.add(toPosixPath(path.join('.mustflow', 'config', relativePath)));
33
+ }
34
+ }
35
+ return Array.from(paths).sort((left, right) => left.localeCompare(right));
36
+ }
37
+ export function readText(projectRoot, relativePath) {
38
+ return readMustflowTextFile(projectRoot, relativePath);
39
+ }
40
+ function getDocumentType(relativePath) {
41
+ if (relativePath === 'AGENTS.md') {
42
+ return 'agent_rules';
43
+ }
44
+ if (relativePath.startsWith('.mustflow/config/')) {
45
+ return 'config';
46
+ }
47
+ if (relativePath === '.mustflow/skills/INDEX.md') {
48
+ return 'skill_index';
49
+ }
50
+ if (relativePath === '.mustflow/context/INDEX.md') {
51
+ return 'context_index';
52
+ }
53
+ if (relativePath.startsWith('.mustflow/context/')) {
54
+ return 'context';
55
+ }
56
+ if (relativePath.endsWith('/SKILL.md')) {
57
+ return 'skill';
58
+ }
59
+ if (relativePath.startsWith('.mustflow/docs/')) {
60
+ return 'workflow_doc';
61
+ }
62
+ return 'document';
63
+ }
64
+ function parseFrontmatter(content) {
65
+ if (!content.startsWith('---')) {
66
+ return {};
67
+ }
68
+ const end = content.indexOf('\n---', 3);
69
+ if (end === -1) {
70
+ return {};
71
+ }
72
+ const result = {};
73
+ const rawFrontmatter = content.slice(3, end);
74
+ for (const line of rawFrontmatter.split(/\r?\n/)) {
75
+ const separatorIndex = line.indexOf(':');
76
+ if (separatorIndex === -1) {
77
+ continue;
78
+ }
79
+ const key = line.slice(0, separatorIndex).trim();
80
+ const value = line.slice(separatorIndex + 1).trim();
81
+ if (key.length > 0 && value.length > 0) {
82
+ result[key] = value;
83
+ }
84
+ }
85
+ return result;
86
+ }
87
+ function getTitle(relativePath, content) {
88
+ const heading = content.match(/^#\s+(.+)$/mu)?.[1]?.trim();
89
+ return heading && heading.length > 0 ? heading : path.posix.basename(relativePath);
90
+ }
91
+ function getSections(content) {
92
+ return [...content.matchAll(/^##\s+(.+)$/gmu)].map((match) => match[1]?.trim()).filter((value) => Boolean(value));
93
+ }
94
+ function truncateUtf8(value, maxBytes) {
95
+ const buffer = Buffer.from(value, 'utf8');
96
+ if (buffer.byteLength <= maxBytes) {
97
+ return value;
98
+ }
99
+ return buffer.subarray(0, maxBytes).toString('utf8').replace(/\uFFFD$/u, '');
100
+ }
101
+ export function collectDocumentsFromPaths(projectRoot, relativePaths) {
102
+ return relativePaths.map((relativePath) => {
103
+ const content = readText(projectRoot, relativePath);
104
+ const frontmatter = parseFrontmatter(content);
105
+ const revision = Number.parseInt(frontmatter.revision ?? '', 10);
106
+ return {
107
+ path: relativePath,
108
+ type: getDocumentType(relativePath),
109
+ title: getTitle(relativePath, content),
110
+ locale: frontmatter.locale ?? null,
111
+ revision: Number.isInteger(revision) ? revision : null,
112
+ contentHash: sha256Text(content),
113
+ contentSnippet: truncateUtf8(content, MAX_SNIPPET_BYTES_PER_DOCUMENT),
114
+ sections: getSections(content),
115
+ };
116
+ });
117
+ }
118
+ export function collectDocuments(projectRoot) {
119
+ return collectDocumentsFromPaths(projectRoot, getExistingIndexablePaths(projectRoot));
120
+ }
121
+ export function collectSkills(documents) {
122
+ return documents
123
+ .filter((document) => document.type === 'skill')
124
+ .map((document) => ({
125
+ name: document.path.split('/').at(-2) ?? document.title,
126
+ path: document.path,
127
+ title: document.title,
128
+ }))
129
+ .sort((left, right) => left.name.localeCompare(right.name));
130
+ }
131
+ function normalizeMarkdownCell(value) {
132
+ return value
133
+ .replace(/<br\s*\/?>/giu, ' ')
134
+ .replace(/`([^`]+)`/gu, '$1')
135
+ .replace(/\s+/gu, ' ')
136
+ .trim();
137
+ }
138
+ function parseMarkdownTableRow(line) {
139
+ return line
140
+ .trim()
141
+ .replace(/^\|/u, '')
142
+ .replace(/\|$/u, '')
143
+ .split('|')
144
+ .map((cell) => normalizeMarkdownCell(cell));
145
+ }
146
+ function isMarkdownSeparatorRow(cells) {
147
+ return cells.length > 0 && cells.every((cell) => /^:?-{3,}:?$/u.test(cell));
148
+ }
149
+ function skillNameFromPath(skillPath) {
150
+ return skillPath.split('/').at(-2) ?? path.posix.basename(skillPath, '.md');
151
+ }
152
+ export function splitVerificationIntents(value) {
153
+ return value
154
+ .split(',')
155
+ .map((item) => item.trim())
156
+ .filter(Boolean)
157
+ .sort((left, right) => left.localeCompare(right));
158
+ }
159
+ export function skillRouteKey(route) {
160
+ return `${route.skillName}\u0000${route.trigger}`;
161
+ }
162
+ export function collectSkillRoutes(projectRoot) {
163
+ const indexPath = path.join(projectRoot, '.mustflow', 'skills', 'INDEX.md');
164
+ if (!existsSync(indexPath)) {
165
+ return [];
166
+ }
167
+ const content = readMustflowTextFile(projectRoot, '.mustflow/skills/INDEX.md');
168
+ const routes = [];
169
+ let inRouteTable = false;
170
+ for (const line of content.split(/\r?\n/u)) {
171
+ if (!line.trim().startsWith('|')) {
172
+ if (inRouteTable && line.trim() === '') {
173
+ inRouteTable = false;
174
+ }
175
+ continue;
176
+ }
177
+ const cells = parseMarkdownTableRow(line);
178
+ if (cells.includes('Skill Document') && cells.includes('Trigger')) {
179
+ inRouteTable = true;
180
+ continue;
181
+ }
182
+ if (!inRouteTable || isMarkdownSeparatorRow(cells) || cells.length < 7) {
183
+ continue;
184
+ }
185
+ const [trigger, skillPath, requiredInput, editScope, risk, verificationIntents, expectedOutput] = cells;
186
+ if (!skillPath?.startsWith('.mustflow/skills/') || !skillPath.endsWith('/SKILL.md')) {
187
+ continue;
188
+ }
189
+ routes.push({
190
+ skillName: skillNameFromPath(skillPath),
191
+ skillPath,
192
+ trigger: trigger ?? '',
193
+ requiredInput: requiredInput ?? '',
194
+ editScope: editScope ?? '',
195
+ risk: risk ?? '',
196
+ verificationIntents: splitVerificationIntents(verificationIntents ?? ''),
197
+ expectedOutput: expectedOutput ?? '',
198
+ });
199
+ }
200
+ return routes.sort((left, right) => {
201
+ const skillOrder = left.skillName.localeCompare(right.skillName);
202
+ return skillOrder === 0 ? left.trigger.localeCompare(right.trigger) : skillOrder;
203
+ });
204
+ }
@@ -0,0 +1,27 @@
1
+ import { MANIFEST_LOCK_RELATIVE_PATH, readManifestLock } from './manifest-lock.js';
2
+ export const ALLOW_UNTRUSTED_ROOT_OPTION = '--allow-untrusted-root';
3
+ export function assessRunRootTrust(projectRoot) {
4
+ const readResult = readManifestLock(projectRoot);
5
+ if (readResult.kind === 'present') {
6
+ return {
7
+ trusted: true,
8
+ reason: 'manifest_lock_present',
9
+ manifestLockPath: readResult.lockPath,
10
+ detail: null,
11
+ };
12
+ }
13
+ if (readResult.kind === 'invalid') {
14
+ return {
15
+ trusted: false,
16
+ reason: 'manifest_lock_invalid',
17
+ manifestLockPath: readResult.lockPath,
18
+ detail: readResult.message,
19
+ };
20
+ }
21
+ return {
22
+ trusted: false,
23
+ reason: 'manifest_lock_missing',
24
+ manifestLockPath: readResult.lockPath,
25
+ detail: MANIFEST_LOCK_RELATIVE_PATH,
26
+ };
27
+ }
@@ -0,0 +1,47 @@
1
+ export const CHANGE_CLASSIFICATION_POLICY_VERSION = '1';
2
+ function surface(kind, category, isPublicSurface, validationReasons, affectedContracts, updatePolicy, driftChecks) {
3
+ return {
4
+ kind,
5
+ category,
6
+ isPublicSurface,
7
+ validationReasons,
8
+ affectedContracts,
9
+ updatePolicy,
10
+ driftChecks,
11
+ };
12
+ }
13
+ export const UNKNOWN_CHANGE_REASON = 'unknown_change';
14
+ export const UNKNOWN_SURFACE = surface('unclassified_path', 'unknown', false, [UNKNOWN_CHANGE_REASON], ['unclassified repository path'], 'not_applicable', ['classification rule coverage']);
15
+ function rule(id, match, changeKinds, surfaceContract) {
16
+ return {
17
+ id,
18
+ match,
19
+ changeKinds,
20
+ surface: surfaceContract,
21
+ };
22
+ }
23
+ export const CHANGE_CLASSIFICATION_RULE_DEFINITIONS = [
24
+ rule('readme_page', /^README\.md$/u, ['documentation'], surface('readme_page', 'documentation', true, ['docs_change', 'copy_change'], ['public documentation', 'command examples'], 'update', ['link targets', 'command examples', 'package metadata references'])),
25
+ rule('docs_site_translation', /^docs-site\/src\/content\/docs\/(?!en\/)[^/]+\//u, ['documentation', 'translation'], surface('docs_site_translation', 'documentation', true, ['docs_change', 'i18n_change'], ['documentation site', 'localized content', 'navigation links'], 'update_or_mark_stale', ['source page parity', 'navigation links', 'localized examples'])),
26
+ rule('docs_site_page', /^docs-site\/src\/content\/docs\//u, ['documentation'], surface('docs_site_page', 'documentation', true, ['docs_change'], ['documentation site', 'navigation links', 'localized content'], 'update', ['navigation links', 'localized copies', 'command examples'])),
27
+ rule('installed_template_translation', /^templates\/[^/]+\/locales\/[^/]+\//u, ['installed_template', 'translation'], surface('installed_template_translation', 'installed-template', true, ['i18n_change', 'template_version_change'], ['installed template files', 'localized workflow documents', 'template i18n metadata'], 'update_or_mark_stale', ['template i18n metadata', 'localized frontmatter', 'source revision'])),
28
+ rule('installed_template', /^templates\/[^/]+\//u, ['installed_template'], surface('installed_template', 'installed-template', true, ['template_version_change', 'packaging_change'], ['installed template files', 'package contents', 'template manifest'], 'update', ['template manifest', 'package inventory', 'localized copies'])),
29
+ rule('workflow_root', /^(AGENTS\.md|\.mustflow\/(?:docs|context|skills|config)\/)/u, ['workflow'], surface('workflow_root', 'workflow', true, ['mustflow_docs_change', 'mustflow_config_change'], ['agent workflow contract', 'command contract', 'installed workflow files'], 'update', ['strict workflow validation', 'installed template parity', 'skill route alignment'])),
30
+ rule('host_instruction', /^(CLAUDE\.md|GEMINI\.md|\.github\/copilot-instructions\.md|\.cursor\/rules\/[^/]+\.(?:md|mdc))$/u, ['workflow', 'host_instruction'], surface('host_instruction', 'workflow', true, ['mustflow_docs_change'], ['host instruction compatibility', 'agent workflow contract', 'command contract boundary'], 'update_or_mark_stale', ['host instruction conflicts', 'command contract boundary'])),
31
+ rule('example', /^examples\//u, ['example'], surface('example', 'example', true, ['docs_change', 'public_api_change'], ['generated examples', 'human-readable examples'], 'update', ['example commands', 'linked docs', 'public behavior claims'])),
32
+ rule('schema_contract', /^schemas\//u, ['schema'], surface('schema_contract', 'contract', true, ['public_api_change', 'release_risk'], ['JSON schema', 'machine-readable output contract'], 'update', ['schema tests', 'documented JSON fields', 'package inventory'])),
33
+ rule('package_metadata', /^package\.json$/u, ['package_metadata'], surface('package_metadata', 'release', true, ['package_metadata_change', 'release_risk'], ['npm package metadata', 'published package contents'], 'update', ['package metadata tests', 'version source discovery', 'published file inventory'])),
34
+ rule('test_fixture', /^tests\/fixtures\//u, ['test_fixture'], surface('test_fixture', 'test', false, ['test_change'], ['regression fixture inputs'], 'not_applicable', ['fixture safety', 'test route coverage'])),
35
+ rule('test_contract', /^tests\//u, ['test'], surface('test_contract', 'test', false, ['test_change'], ['test behavior contract'], 'not_applicable', ['related test selection'])),
36
+ rule('implementation', /^(src|scripts)\//u, ['implementation'], surface('implementation', 'code', false, ['code_change'], ['runtime behavior when exported through CLI or package output'], 'not_applicable', ['related tests', 'build output'])),
37
+ ];
38
+ export function listChangeClassificationPolicyRuleDescriptors() {
39
+ return CHANGE_CLASSIFICATION_RULE_DEFINITIONS.map((classificationRule) => ({
40
+ id: classificationRule.id,
41
+ patternKind: 'regexp',
42
+ pattern: classificationRule.match.source,
43
+ patternFlags: classificationRule.match.flags,
44
+ changeKinds: classificationRule.changeKinds,
45
+ surface: classificationRule.surface,
46
+ }));
47
+ }
@@ -1,47 +1,21 @@
1
+ import { CHANGE_CLASSIFICATION_RULE_DEFINITIONS, UNKNOWN_SURFACE, listChangeClassificationPolicyRuleDescriptors, } from './change-classification-policy.js';
1
2
  export const PUBLIC_SURFACE_UPDATE_POLICIES = [
2
3
  'update',
3
4
  'update_or_mark_stale',
4
5
  'not_applicable',
5
6
  ];
6
- function surface(kind, category, isPublicSurface, validationReasons, affectedContracts, updatePolicy, driftChecks) {
7
+ function compileRule(definition) {
7
8
  return {
8
- kind,
9
- category,
10
- isPublicSurface,
11
- validationReasons,
12
- affectedContracts,
13
- updatePolicy,
14
- driftChecks,
15
- };
16
- }
17
- const UNKNOWN_CHANGE_REASON = 'unknown_change';
18
- const UNKNOWN_SURFACE = surface('unclassified_path', 'unknown', false, [UNKNOWN_CHANGE_REASON], ['unclassified repository path'], 'not_applicable', ['classification rule coverage']);
19
- function rule(id, match, changeKinds, surfaceContract) {
20
- return {
21
- id,
9
+ id: definition.id,
22
10
  patternKind: 'regexp',
23
- pattern: match.source,
24
- patternFlags: match.flags,
25
- match,
26
- changeKinds,
27
- surface: surfaceContract,
11
+ pattern: definition.match.source,
12
+ patternFlags: definition.match.flags,
13
+ match: definition.match,
14
+ changeKinds: definition.changeKinds,
15
+ surface: definition.surface,
28
16
  };
29
17
  }
30
- const CHANGE_CLASSIFICATION_RULES = [
31
- rule('readme_page', /^README\.md$/u, ['documentation'], surface('readme_page', 'documentation', true, ['docs_change', 'copy_change'], ['public documentation', 'command examples'], 'update', ['link targets', 'command examples', 'package metadata references'])),
32
- rule('docs_site_translation', /^docs-site\/src\/content\/docs\/(?!en\/)[^/]+\//u, ['documentation', 'translation'], surface('docs_site_translation', 'documentation', true, ['docs_change', 'i18n_change'], ['documentation site', 'localized content', 'navigation links'], 'update_or_mark_stale', ['source page parity', 'navigation links', 'localized examples'])),
33
- rule('docs_site_page', /^docs-site\/src\/content\/docs\//u, ['documentation'], surface('docs_site_page', 'documentation', true, ['docs_change'], ['documentation site', 'navigation links', 'localized content'], 'update', ['navigation links', 'localized copies', 'command examples'])),
34
- rule('installed_template_translation', /^templates\/[^/]+\/locales\/[^/]+\//u, ['installed_template', 'translation'], surface('installed_template_translation', 'installed-template', true, ['i18n_change', 'template_version_change'], ['installed template files', 'localized workflow documents', 'template i18n metadata'], 'update_or_mark_stale', ['template i18n metadata', 'localized frontmatter', 'source revision'])),
35
- rule('installed_template', /^templates\/[^/]+\//u, ['installed_template'], surface('installed_template', 'installed-template', true, ['template_version_change', 'packaging_change'], ['installed template files', 'package contents', 'template manifest'], 'update', ['template manifest', 'package inventory', 'localized copies'])),
36
- rule('workflow_root', /^(AGENTS\.md|\.mustflow\/(?:docs|context|skills|config)\/)/u, ['workflow'], surface('workflow_root', 'workflow', true, ['mustflow_docs_change', 'mustflow_config_change'], ['agent workflow contract', 'command contract', 'installed workflow files'], 'update', ['strict workflow validation', 'installed template parity', 'skill route alignment'])),
37
- rule('host_instruction', /^(CLAUDE\.md|GEMINI\.md|\.github\/copilot-instructions\.md|\.cursor\/rules\/[^/]+\.(?:md|mdc))$/u, ['workflow', 'host_instruction'], surface('host_instruction', 'workflow', true, ['mustflow_docs_change'], ['host instruction compatibility', 'agent workflow contract', 'command contract boundary'], 'update_or_mark_stale', ['host instruction conflicts', 'command contract boundary'])),
38
- rule('example', /^examples\//u, ['example'], surface('example', 'example', true, ['docs_change', 'public_api_change'], ['generated examples', 'human-readable examples'], 'update', ['example commands', 'linked docs', 'public behavior claims'])),
39
- rule('schema_contract', /^schemas\//u, ['schema'], surface('schema_contract', 'contract', true, ['public_api_change', 'release_risk'], ['JSON schema', 'machine-readable output contract'], 'update', ['schema tests', 'documented JSON fields', 'package inventory'])),
40
- rule('package_metadata', /^package\.json$/u, ['package_metadata'], surface('package_metadata', 'release', true, ['package_metadata_change', 'release_risk'], ['npm package metadata', 'published package contents'], 'update', ['package metadata tests', 'version source discovery', 'published file inventory'])),
41
- rule('test_fixture', /^tests\/fixtures\//u, ['test_fixture'], surface('test_fixture', 'test', false, ['test_change'], ['regression fixture inputs'], 'not_applicable', ['fixture safety', 'test route coverage'])),
42
- rule('test_contract', /^tests\//u, ['test'], surface('test_contract', 'test', false, ['test_change'], ['test behavior contract'], 'not_applicable', ['related test selection'])),
43
- rule('implementation', /^(src|scripts)\//u, ['implementation'], surface('implementation', 'code', false, ['code_change'], ['runtime behavior when exported through CLI or package output'], 'not_applicable', ['related tests', 'build output'])),
44
- ];
18
+ const CHANGE_CLASSIFICATION_RULES = CHANGE_CLASSIFICATION_RULE_DEFINITIONS.map(compileRule);
45
19
  function uniqueSorted(values) {
46
20
  return [...new Set(values)].sort((left, right) => left.localeCompare(right));
47
21
  }
@@ -93,14 +67,7 @@ export function classifyChangePath(relativePath) {
93
67
  };
94
68
  }
95
69
  export function listChangeClassificationRuleDescriptors() {
96
- return CHANGE_CLASSIFICATION_RULES.map((classificationRule) => ({
97
- id: classificationRule.id,
98
- patternKind: classificationRule.patternKind,
99
- pattern: classificationRule.pattern,
100
- patternFlags: classificationRule.patternFlags,
101
- changeKinds: classificationRule.changeKinds,
102
- surface: classificationRule.surface,
103
- }));
70
+ return listChangeClassificationPolicyRuleDescriptors();
104
71
  }
105
72
  export function listChangeClassificationValidationReasons() {
106
73
  return uniqueSorted([
@@ -14,6 +14,7 @@ const CONTRACT_LINT_SOURCE_FILES = [
14
14
  '.mustflow/docs/agent-workflow.md',
15
15
  'AGENTS.md',
16
16
  'src/core/change-classification.ts',
17
+ 'src/core/change-classification-policy.ts',
17
18
  ];
18
19
  export const DOCUMENTED_VERIFICATION_REASONS = [
19
20
  'before_publish',
@@ -49,7 +50,10 @@ const RELEASE_SENSITIVE_REASONS = new Set([
49
50
  ]);
50
51
  const COMMANDS_CONFIG_PATH = '.mustflow/config/commands.toml';
51
52
  const SKILL_INDEX_PATH = '.mustflow/skills/INDEX.md';
52
- const CHANGE_CLASSIFICATION_SOURCE_PATH = 'src/core/change-classification.ts';
53
+ const CHANGE_CLASSIFICATION_SOURCE_PATHS = [
54
+ 'src/core/change-classification.ts',
55
+ 'src/core/change-classification-policy.ts',
56
+ ];
53
57
  const AGENT_WORKFLOW_PATH = '.mustflow/docs/agent-workflow.md';
54
58
  const PACKAGE_SCRIPT_RUNNERS = new Set(['bun', 'npm', 'pnpm', 'yarn']);
55
59
  const MAKEFILE_CANDIDATES = ['Makefile', 'makefile'];
@@ -413,7 +417,7 @@ function classifyReasonSource(reason, knownClassificationReasons, documentedVeri
413
417
  function buildRelatedDocs(source, relatedSkills) {
414
418
  const docs = [COMMANDS_CONFIG_PATH];
415
419
  if (source === 'classification') {
416
- docs.push(CHANGE_CLASSIFICATION_SOURCE_PATH);
420
+ docs.push(...CHANGE_CLASSIFICATION_SOURCE_PATHS);
417
421
  }
418
422
  if (source === 'documented') {
419
423
  docs.push(AGENT_WORKFLOW_PATH);
@@ -0,0 +1,16 @@
1
+ import { randomBytes } from 'node:crypto';
2
+ export const CORRELATION_ID_PATTERN = '^mf-[a-z][a-z0-9_-]*-[0-9a-f]{16}$';
3
+ const CORRELATION_ID_REGEX = new RegExp(CORRELATION_ID_PATTERN);
4
+ function normalizeCorrelationScope(scope) {
5
+ const normalized = scope
6
+ .toLowerCase()
7
+ .replace(/[^a-z0-9_-]+/g, '_')
8
+ .replace(/^_+|_+$/g, '');
9
+ return /^[a-z][a-z0-9_-]*$/.test(normalized) ? normalized : 'event';
10
+ }
11
+ export function createCorrelationId(scope) {
12
+ return `mf-${normalizeCorrelationScope(scope)}-${randomBytes(8).toString('hex')}`;
13
+ }
14
+ export function isCorrelationId(value) {
15
+ return typeof value === 'string' && CORRELATION_ID_REGEX.test(value);
16
+ }
@@ -225,6 +225,7 @@ export function createRunReceipt(input) {
225
225
  return {
226
226
  schema_version: RUN_RECEIPT_SCHEMA_VERSION,
227
227
  command: 'run',
228
+ correlation_id: input.correlationId,
228
229
  intent: input.intent,
229
230
  status: input.status,
230
231
  timed_out: input.timedOut,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mustflow",
3
- "version": "2.22.5",
3
+ "version": "2.22.9",
4
4
  "description": "Agent workflow documents and CLI for mustflow repository roots.",
5
5
  "type": "module",
6
6
  "license": "MIT-0",
@@ -36,10 +36,13 @@
36
36
  "test:coverage": "bun run build && node scripts/run-cli-tests.mjs coverage",
37
37
  "test:audit": "node scripts/audit-tests.mjs --json",
38
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",
39
41
  "test:full": "bun run build && node scripts/run-cli-tests.mjs full-auto",
40
42
  "test:full:auto": "bun run build && node scripts/run-cli-tests.mjs full-auto",
41
43
  "test:full:serial": "bun run build && node scripts/run-cli-tests.mjs full",
42
44
  "check": "bun run check:package && bun run test:full",
45
+ "check:core:node": "node scripts/run-node-core-check.mjs",
43
46
  "check:package": "node -e \"const fs=require('fs'); JSON.parse(fs.readFileSync('package.json','utf8')); console.log('package.json ok')\"",
44
47
  "check:typecheck": "tsc -p tsconfig.json --noEmit",
45
48
  "prepack": "npm run build",
package/schemas/README.md CHANGED
@@ -48,6 +48,10 @@ Current schemas:
48
48
  These schemas define stable, automation-facing fields. Human-readable command
49
49
  output is intentionally excluded.
50
50
 
51
+ Current `classify`, `verify`, `run`, dashboard export, and verify state outputs may include
52
+ `correlation_id` so local artifacts from one work incident can be connected without storing raw
53
+ transcripts, environment values, or hidden reasoning.
54
+
51
55
  The published schema surface is tracked in `src/core/public-json-contracts.ts`.
52
56
  Release tests verify consistency between this manifest, `schemas/*.schema.json`,
53
57
  `npm pack --dry-run --json`, and the installed package’s JSON command output.
@@ -29,6 +29,10 @@
29
29
  "classification_summary": {
30
30
  "$ref": "#/$defs/classificationSummary"
31
31
  },
32
+ "correlation_id": {
33
+ "type": "string",
34
+ "pattern": "^mf-[a-z][a-z0-9_-]*-[0-9a-f]{16}$"
35
+ },
32
36
  "verification_plan_id": {
33
37
  "type": "string",
34
38
  "pattern": "^sha256:[0-9a-f]{64}$"
@@ -16,6 +16,10 @@
16
16
  "properties": {
17
17
  "schema_version": { "const": "1" },
18
18
  "command": { "const": "classify" },
19
+ "correlation_id": {
20
+ "type": "string",
21
+ "pattern": "^mf-[a-z][a-z0-9_-]*-[0-9a-f]{16}$"
22
+ },
19
23
  "mustflow_root": { "type": "string" },
20
24
  "source": { "enum": ["changed", "paths"] },
21
25
  "files": {
@@ -20,6 +20,10 @@
20
20
  "properties": {
21
21
  "schema_version": { "const": "1" },
22
22
  "command": { "const": "dashboard export" },
23
+ "correlation_id": {
24
+ "type": "string",
25
+ "pattern": "^mf-[a-z][a-z0-9_-]*-[0-9a-f]{16}$"
26
+ },
23
27
  "format": { "enum": ["html", "json"] },
24
28
  "generated_at": { "type": "string" },
25
29
  "mustflow_root": { "type": "string" },
@@ -23,6 +23,10 @@
23
23
  "schema_version": { "const": "1" },
24
24
  "command": { "const": "verify" },
25
25
  "kind": { "const": "verify_run_summary" },
26
+ "correlation_id": {
27
+ "type": "string",
28
+ "pattern": "^mf-[a-z][a-z0-9_-]*-[0-9a-f]{16}$"
29
+ },
26
30
  "reason": { "type": "string" },
27
31
  "reasons": {
28
32
  "type": "array",
@@ -34,6 +34,10 @@
34
34
  "properties": {
35
35
  "schema_version": { "const": "1" },
36
36
  "command": { "const": "run" },
37
+ "correlation_id": {
38
+ "type": "string",
39
+ "pattern": "^mf-[a-z][a-z0-9_-]*-[0-9a-f]{16}$"
40
+ },
37
41
  "intent": { "type": "string" },
38
42
  "status": { "enum": ["passed", "failed", "timed_out", "start_failed", "output_limit_exceeded"] },
39
43
  "timed_out": { "type": "boolean" },
@@ -23,6 +23,10 @@
23
23
  "properties": {
24
24
  "schema_version": { "const": "1" },
25
25
  "command": { "const": "verify" },
26
+ "correlation_id": {
27
+ "type": "string",
28
+ "pattern": "^mf-[a-z][a-z0-9_-]*-[0-9a-f]{16}$"
29
+ },
26
30
  "mustflow_root": { "type": "string" },
27
31
  "reason": { "type": "string" },
28
32
  "reasons": {
@@ -20,6 +20,10 @@
20
20
  "properties": {
21
21
  "schema_version": { "const": "1" },
22
22
  "command": { "const": "verify" },
23
+ "correlation_id": {
24
+ "type": "string",
25
+ "pattern": "^mf-[a-z][a-z0-9_-]*-[0-9a-f]{16}$"
26
+ },
23
27
  "reason": { "type": "string" },
24
28
  "reasons": {
25
29
  "type": "array",
@@ -74,7 +74,7 @@ translations = {}
74
74
  [documents."skill.architecture-deepening-review"]
75
75
  source = "locales/en/.mustflow/skills/architecture-deepening-review/SKILL.md"
76
76
  source_locale = "en"
77
- revision = 1
77
+ revision = 2
78
78
  translations = {}
79
79
 
80
80
  [documents."skill.behavior-preserving-refactor"]
@@ -325,7 +325,7 @@ translations = {}
325
325
  [documents."skill.security-privacy-review"]
326
326
  source = "locales/en/.mustflow/skills/security-privacy-review/SKILL.md"
327
327
  source_locale = "en"
328
- revision = 16
328
+ revision = 17
329
329
  translations = {}
330
330
 
331
331
  [documents."skill.security-regression-tests"]
@@ -349,7 +349,7 @@ translations = {}
349
349
  [documents."skill.test-design-guard"]
350
350
  source = "locales/en/.mustflow/skills/test-design-guard/SKILL.md"
351
351
  source_locale = "en"
352
- revision = 1
352
+ revision = 2
353
353
  translations = {}
354
354
 
355
355
  [documents."skill.test-maintenance"]
@@ -2,7 +2,7 @@
2
2
  mustflow_doc: skill.architecture-deepening-review
3
3
  locale: en
4
4
  canonical: true
5
- revision: 1
5
+ revision: 2
6
6
  lifecycle: mustflow-owned
7
7
  authority: procedure
8
8
  name: architecture-deepening-review
@@ -37,6 +37,7 @@ This is a review-first skill. It helps decide whether code needs a deeper module
37
37
  ## Use When
38
38
 
39
39
  - The user asks for architecture review, module boundaries, structural improvement, codebase deepening, maintainability review, or testability improvement.
40
+ - The user asks where a design will break first as it grows, which responsibility boundary is most likely to blur, or whether a module, service, database owner, permission model, deployment unit, or failure boundary is still clear enough.
40
41
  - A file, module, service, handler, command, controller, or test suite looks broad enough that the next edit may add another responsibility.
41
42
  - Code exposes internal steps to many callers, repeats orchestration, or makes tests hard because policy, I/O, formatting, and dispatch are mixed.
42
43
  - A shallow wrapper adds naming without hiding complexity, or a helper has become a pass-through layer around many unrelated concerns.
@@ -57,6 +58,7 @@ This is a review-first skill. It helps decide whether code needs a deeper module
57
58
  - Target area, current pain, and the user-facing or maintainer-facing reason to inspect architecture.
58
59
  - Relevant source files, call sites, exports, tests, fixtures, schemas, templates, or documentation that show current behavior and ownership.
59
60
  - Local patterns for modules, boundaries, naming, errors, dependency direction, and tests.
61
+ - The data owner, write path, failure mode, and expected 3x, 10x, or 100x growth pressure when the review is about a design rather than only a file split.
60
62
  - Current changed-file list when the worktree is already dirty.
61
63
  - Relevant command-intent contract entries for verification.
62
64
 
@@ -88,7 +90,22 @@ This is a review-first skill. It helps decide whether code needs a deeper module
88
90
  3. Identify one to three candidate boundaries.
89
91
  - Each candidate must name the responsibility it would hide or clarify.
90
92
  - Reject candidates that only rename, wrap, or move code without lowering caller complexity or test cost.
91
- 4. Score each candidate from 1 to 9.
93
+ 4. Force the design through the ownership and failure questions before scoring.
94
+ - Name the first likely mixed-responsibility boundary. Common early failures are business rules leaking into controllers, repositories, external adapters, UI components, or framework-specific handlers.
95
+ - Name the final owner for important data. The owner is the module that protects the invariant, not necessarily the module that reads the value most often.
96
+ - Separate original state from cache, search index, analytics, summary, AI output, provider response, or other derived state.
97
+ - Identify every direct write path for high-impact fields such as status, role, permission, balance, quota, plan, entitlement, deleted state, payment state, or ownership.
98
+ - Ask whether a failure creates a visible failure state or silently creates false success. High-impact paths such as authorization, payment, entitlement, deletion, and destructive administration should fail closed.
99
+ - Ask whether duplicate requests, retries, webhook redelivery, queue replay, or worker restart can repeat a harmful effect. If yes, require an idempotency, ledger, outbox, or reconciliation boundary before calling the design safe.
100
+ 5. Check growth pressure in concrete stages.
101
+ - At 3x scale, look first for implementation-quality failures: missing indexes, N+1 reads, large responses, synchronous file or image work, repeated external calls, and insufficient connection pools.
102
+ - At 10x scale, look first for ownership and state failures: write hot spots, queue delay, cache invalidation, server-local files, scattered permission rules, external API rate limits, and deployment units that change for unrelated reasons.
103
+ - At 100x scale, look first for partitioning and operational failures: data split boundaries, tenant or region hot spots, retry storms, external dependency isolation, long deploy recovery, missing observability, and manual-only recovery paths.
104
+ 6. Check scaling direction without forcing premature distribution.
105
+ - A small team may start with one larger server or a simple server set, but request handlers should not depend on process memory, local uploads, duplicate cron execution, in-transaction external calls, or server-specific job state.
106
+ - Application servers should be able to become stateless. Databases may start with vertical scaling, but the design should not block read replicas, read models, queue-backed work, or future data partitioning.
107
+ - Horizontal scaling is only real if any server can handle the same request, workers can safely duplicate or retry work, and database writes do not all converge on an uncontrolled hot spot.
108
+ 7. Score each candidate from 1 to 9.
92
109
  - User value: whether the structure protects a user-visible or public contract.
93
110
  - Maintenance value: whether future changes become smaller or less error-prone.
94
111
  - Blast radius: how many callers, files, schemas, templates, or docs would change.
@@ -111,6 +128,8 @@ This is a review-first skill. It helps decide whether code needs a deeper module
111
128
 
112
129
  - The output contains a ranked architecture candidate list or one scoped structural change.
113
130
  - Any chosen change has a named reason tied to lower change cost, lower defect risk, or better testability.
131
+ - Important data has a named owner, write path, original-or-derived classification, and failure behavior when the reviewed design touches durable state.
132
+ - Growth pressure is either checked at 3x, 10x, and 100x or explicitly marked not relevant to the current architecture decision.
114
133
  - Behavior changes are excluded or explicitly moved to a separate follow-up.
115
134
  - Verification evidence or verification gaps are reported without claiming unrun checks passed.
116
135
 
@@ -145,6 +164,10 @@ Use documentation and release checks only when the review or chosen change touch
145
164
 
146
165
  - Review target and current pain
147
166
  - Evidence inspected
167
+ - Data owner, write path, and original-versus-derived state when relevant
168
+ - Failure mode, idempotency, and recovery boundary when relevant
169
+ - 3x, 10x, and 100x growth pressure when relevant
170
+ - Vertical versus horizontal scaling direction when relevant
148
171
  - Candidate boundaries and scores
149
172
  - Selected next action
150
173
  - Narrower skill used or intentionally avoided