mustflow 2.22.4 → 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.
- package/README.md +17 -75
- package/dist/cli/commands/classify.js +2 -0
- package/dist/cli/commands/contract-lint.js +2 -2
- package/dist/cli/commands/dashboard.js +23 -75
- package/dist/cli/commands/help.js +8 -9
- package/dist/cli/commands/impact.js +2 -3
- package/dist/cli/commands/init.js +61 -5
- package/dist/cli/commands/run/receipt.js +1 -0
- package/dist/cli/commands/run.js +14 -1
- package/dist/cli/commands/update.js +2 -2
- package/dist/cli/commands/verify/evidence-input.js +269 -0
- package/dist/cli/commands/verify/input.js +212 -0
- package/dist/cli/commands/verify.js +23 -482
- package/dist/cli/commands/version-sources.js +2 -3
- package/dist/cli/i18n/en.js +5 -0
- package/dist/cli/i18n/es.js +5 -0
- package/dist/cli/i18n/fr.js +5 -0
- package/dist/cli/i18n/hi.js +5 -0
- package/dist/cli/i18n/ko.js +5 -0
- package/dist/cli/i18n/zh.js +5 -0
- package/dist/cli/lib/agent-context.js +6 -11
- package/dist/cli/lib/dashboard-export.js +2 -0
- package/dist/cli/lib/dashboard-mutations.js +79 -0
- package/dist/cli/lib/local-index/command-effect-index.js +25 -0
- package/dist/cli/lib/local-index/hashing.js +7 -0
- package/dist/cli/lib/local-index/index.js +127 -823
- package/dist/cli/lib/local-index/source-index.js +137 -0
- package/dist/cli/lib/local-index/verification-evidence.js +451 -0
- package/dist/cli/lib/local-index/workflow-documents.js +204 -0
- package/dist/cli/lib/mustflow-read.js +41 -0
- package/dist/cli/lib/project-root.js +1 -2
- package/dist/cli/lib/repo-map.js +65 -16
- package/dist/cli/lib/run-root-trust.js +27 -0
- package/dist/cli/lib/templates.js +124 -8
- package/dist/cli/lib/toml.js +6 -1
- package/dist/cli/lib/validation/constants.js +2 -0
- package/dist/cli/lib/validation/index.js +291 -22
- package/dist/cli/lib/validation/primitives.js +2 -2
- package/dist/cli/lib/validation/test-selection.js +2 -2
- package/dist/core/bounded-output.js +32 -7
- package/dist/core/change-classification-policy.js +47 -0
- package/dist/core/change-classification.js +10 -43
- package/dist/core/check-issues.js +7 -1
- package/dist/core/command-contract-validation.js +28 -4
- package/dist/core/command-env.js +1 -1
- package/dist/core/config-loading.js +9 -3
- package/dist/core/contract-lint.js +8 -3
- package/dist/core/correlation-id.js +16 -0
- package/dist/core/run-receipt.js +1 -0
- package/dist/core/safe-filesystem.js +11 -4
- package/dist/core/skill-route-alignment.js +1 -0
- package/dist/core/skill-route-explanation.js +9 -3
- package/dist/core/test-selection.js +2 -3
- package/dist/core/verification-scheduler.js +7 -6
- package/dist/core/version-sources.js +2 -3
- package/package.json +4 -1
- package/schemas/README.md +4 -0
- package/schemas/change-verification-report.schema.json +4 -0
- package/schemas/classify-report.schema.json +4 -0
- package/schemas/commands.schema.json +1 -0
- package/schemas/dashboard-export.schema.json +4 -0
- package/schemas/latest-run-pointer.schema.json +4 -0
- package/schemas/run-receipt.schema.json +4 -0
- package/schemas/verify-report.schema.json +4 -0
- package/schemas/verify-run-manifest.schema.json +4 -0
- package/templates/default/i18n.toml +3 -3
- package/templates/default/locales/en/.mustflow/skills/INDEX.md +10 -6
- package/templates/default/locales/en/.mustflow/skills/architecture-deepening-review/SKILL.md +25 -2
- package/templates/default/locales/en/.mustflow/skills/routes.toml +2 -2
- package/templates/default/locales/en/.mustflow/skills/security-privacy-review/SKILL.md +9 -1
- package/templates/default/locales/en/.mustflow/skills/test-design-guard/SKILL.md +9 -1
- package/templates/default/manifest.toml +1 -1
|
@@ -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
|
|
7
|
+
function compileRule(definition) {
|
|
7
8
|
return {
|
|
8
|
-
|
|
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:
|
|
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
|
|
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([
|
|
@@ -7,6 +7,7 @@ const CHECK_ISSUE_ID_RULES = [
|
|
|
7
7
|
['mustflow.command_contract.max_output_bytes_exceeds_limit', /^\[commands\.(?:defaults|intents\.[^\]]+)\]\.max_output_bytes must be less than or equal to \d+ per output stream$/u],
|
|
8
8
|
['mustflow.command_contract.oneshot_stdin_not_closed', /^Oneshot intent [^\s]+ must set stdin = "closed"$/u],
|
|
9
9
|
['mustflow.command_contract.long_running_agent_allowed', /^Long-running intent [^\s]+ must not use run_policy = "agent_allowed"$/u],
|
|
10
|
+
['mustflow.command_contract.agent_shell_requires_allow', /^Agent-runnable shell intent [^\s]+ must set allow_shell = true$/u],
|
|
10
11
|
['mustflow.command_contract.executable_source_missing', /^Configured intent [^\s]+ must define argv or mode = "shell" with cmd$/u],
|
|
11
12
|
['mustflow.command_contract.shell_background_pattern', /^Shell intent [^\s]+ contains a blocked long-running or background pattern$/u],
|
|
12
13
|
['mustflow.command_contract.long_running_command_pattern', /^Intent [^\s]+ contains a blocked long-running or background command pattern$/u],
|
|
@@ -17,7 +18,7 @@ const CHECK_ISSUE_ID_RULES = [
|
|
|
17
18
|
['mustflow.command_contract.effects_invalid', /^(?:Strict: )?(?:\[commands\.(?:resources|intents\.[^\]]+\.effects)[^\]]*\]|Command effect for intent [^\s]+ must define path, paths, or lock)/u],
|
|
18
19
|
['mustflow.command_contract.effect_path_escape', /^Strict: Command effect path must stay inside the current root:/u],
|
|
19
20
|
['mustflow.command_contract.shared_writes_without_effects', /^Strict warning: configured agent-runnable intents .+ share path:.+ through writes without explicit effects or resource locks$/u],
|
|
20
|
-
['mustflow.command_contract.broad_env_inheritance', /^Strict warning
|
|
21
|
+
['mustflow.command_contract.broad_env_inheritance', /^Strict(?: warning)?: configured agent-runnable intent [^\s]+ (?:implicitly inherits the host environment|uses env_policy = "inherit")/u],
|
|
21
22
|
['mustflow.command_contract.project_local_bin_bare_executable', /^Strict warning: configured agent-runnable intent [^\s]+ uses bare executable "[^"]+" that matches project-local node_modules\/\.bin/u],
|
|
22
23
|
['mustflow.prompt_cache.required', /^Strict: \[prompt_cache\] table is required$/u],
|
|
23
24
|
['mustflow.prompt_cache.volatile_in_stable', /^Strict: \[prompt_cache\.layers\.stable\]\.read must not include volatile path /u],
|
|
@@ -48,6 +49,11 @@ const CHECK_ISSUE_ID_RULES = [
|
|
|
48
49
|
['mustflow.skill.route_metadata_category_mismatch', /^Strict: \.mustflow\/skills\/INDEX\.md route "[^"]+" must appear under the .+ category section from \.mustflow\/skills\/routes\.toml$/u],
|
|
49
50
|
['mustflow.skill.route_metadata_unknown_reference', /^Strict: \.mustflow\/skills\/routes\.toml route "[^"]+" references unknown mutually exclusive route "[^"]+"$/u],
|
|
50
51
|
['mustflow.skill.route_metadata_asymmetric_exclusion', /^Strict warning: \.mustflow\/skills\/routes\.toml route "[^"]+" lists "[^"]+" as mutually exclusive but the reverse route does not$/u],
|
|
52
|
+
['mustflow.skill.template_profile_empty_category', /^Strict: template profile "[^"]+" (?:skill index category ".+" has no route rows|route category gate references ".+" without route rows)$/u],
|
|
53
|
+
['mustflow.skill.template_profile_dead_route', /^Strict: template profile "[^"]+" (?:\.mustflow\/skills\/INDEX\.md route "[^"]+" points to a skill not installed by that profile|\.mustflow\/skills\/routes\.toml route "[^"]+" points to a skill not installed by that profile|skill "[^"]+" is installed but not listed in \.mustflow\/skills\/INDEX\.md)$/u],
|
|
54
|
+
['mustflow.skill.template_profile_missing_main_route', /^Strict: template profile "[^"]+" skill category ".+" must include at least one primary or authoring route$/u],
|
|
55
|
+
['mustflow.skill.template_profile_command_intent_drift', /^Strict: template profile "[^"]+" \.mustflow\/skills\/INDEX\.md route "[^"]+" references (?:unknown command intent "[^"]+"|command intent "[^"]+" not declared by the skill frontmatter)$/u],
|
|
56
|
+
['mustflow.skill.template_profile_metadata_mismatch', /^Strict: template profile "[^"]+" (?:\.mustflow\/skills\/routes\.toml is missing metadata for route "[^"]+"|\.mustflow\/skills\/routes\.toml route "[^"]+" is not listed in \.mustflow\/skills\/INDEX\.md|\.mustflow\/skills\/INDEX\.md route "[^"]+" must appear under the .+ category section from \.mustflow\/skills\/routes\.toml)$/u],
|
|
51
57
|
['mustflow.skill.resource_unknown_command_intent', /^Strict: \.mustflow\/skills\/[^/]+\/resources\.toml script [^\s]+ references unknown command intent "[^"]+"$/u],
|
|
52
58
|
['mustflow.source_anchor.invalid_format', /^Strict: source anchor .+ has invalid format:/u],
|
|
53
59
|
['mustflow.source_anchor.duplicate_id', /^Strict: source anchor id "[^"]+" is duplicated:/u],
|
|
@@ -160,6 +160,7 @@ function validateCommandIntent(intentName, intent, issues) {
|
|
|
160
160
|
validateAllowedStringField(intent, 'lifecycle', `[commands.intents.${intentName}].lifecycle`, COMMAND_LIFECYCLES, issues);
|
|
161
161
|
validateAllowedStringField(intent, 'run_policy', `[commands.intents.${intentName}].run_policy`, COMMAND_RUN_POLICIES, issues);
|
|
162
162
|
validateAllowedStringField(intent, 'env_policy', `[commands.intents.${intentName}].env_policy`, COMMAND_ENV_POLICIES, issues);
|
|
163
|
+
validateBooleanField(intent, 'allow_shell', `[commands.intents.${intentName}].allow_shell`, issues);
|
|
163
164
|
validateStringArrayField(intent, 'env_allowlist', `[commands.intents.${intentName}].env_allowlist`, issues);
|
|
164
165
|
validateMaxOutputBytesField(intent, 'max_output_bytes', `[commands.intents.${intentName}].max_output_bytes`, issues);
|
|
165
166
|
validatePositiveIntegerField(intent, 'kill_after_seconds', `[commands.intents.${intentName}].kill_after_seconds`, issues);
|
|
@@ -187,6 +188,9 @@ function validateCommandIntent(intentName, intent, issues) {
|
|
|
187
188
|
if (lifecycle && LONG_RUNNING_LIFECYCLES.has(lifecycle) && runPolicy === 'agent_allowed') {
|
|
188
189
|
issues.push(commandContractIssue(`Long-running intent ${intentName} must not use run_policy = "agent_allowed"`));
|
|
189
190
|
}
|
|
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`));
|
|
193
|
+
}
|
|
190
194
|
if (!commandIntentHasCommandSource(intent)) {
|
|
191
195
|
issues.push(commandContractIssue(`Configured intent ${intentName} must define argv or mode = "shell" with cmd`));
|
|
192
196
|
}
|
|
@@ -238,26 +242,46 @@ export function findCommandEnvInheritanceWarnings(commandsToml) {
|
|
|
238
242
|
if (envPolicy.policy !== 'inherit') {
|
|
239
243
|
continue;
|
|
240
244
|
}
|
|
245
|
+
const reasons = readCommandEnvInheritanceRiskReasons(intent);
|
|
241
246
|
warnings.push({
|
|
242
247
|
intentName,
|
|
243
248
|
source: envPolicy.source,
|
|
249
|
+
severity: reasons.length > 0 ? 'error' : 'warning',
|
|
244
250
|
network: intent.network === true,
|
|
251
|
+
reasons,
|
|
245
252
|
});
|
|
246
253
|
}
|
|
247
254
|
return warnings;
|
|
248
255
|
}
|
|
256
|
+
function readCommandEnvInheritanceRiskReasons(intent) {
|
|
257
|
+
const reasons = [];
|
|
258
|
+
if (intent.network === true) {
|
|
259
|
+
reasons.push('network = true');
|
|
260
|
+
}
|
|
261
|
+
if (intent.destructive === true) {
|
|
262
|
+
reasons.push('destructive = true');
|
|
263
|
+
}
|
|
264
|
+
if (intent.mode === 'shell') {
|
|
265
|
+
reasons.push('mode = "shell"');
|
|
266
|
+
}
|
|
267
|
+
if (Array.isArray(intent.writes) && intent.writes.length > 0) {
|
|
268
|
+
reasons.push('declared writes');
|
|
269
|
+
}
|
|
270
|
+
return reasons;
|
|
271
|
+
}
|
|
249
272
|
function formatCommandEnvInheritanceWarning(warning) {
|
|
250
|
-
const
|
|
273
|
+
const reasonScope = warning.reasons.length > 0 ? ` (${warning.reasons.join(', ')})` : '';
|
|
251
274
|
const migration = 'set env_policy = "minimal" or "allowlist" unless broad host state is required';
|
|
252
275
|
if (warning.source === 'implicit') {
|
|
253
|
-
return `configured agent-runnable intent ${warning.intentName} implicitly inherits the host environment${
|
|
276
|
+
return `configured agent-runnable intent ${warning.intentName} implicitly inherits the host environment${reasonScope}; ${migration}`;
|
|
254
277
|
}
|
|
255
|
-
return `configured agent-runnable intent ${warning.intentName} uses env_policy = "inherit"${
|
|
278
|
+
return `configured agent-runnable intent ${warning.intentName} uses env_policy = "inherit"${reasonScope}; ${migration}`;
|
|
256
279
|
}
|
|
257
280
|
function validateCommandEnvInheritanceWarnings(commandsToml) {
|
|
258
281
|
const issues = [];
|
|
259
282
|
for (const warning of findCommandEnvInheritanceWarnings(commandsToml)) {
|
|
260
|
-
|
|
283
|
+
const message = formatCommandEnvInheritanceWarning(warning);
|
|
284
|
+
issues.push(warning.severity === 'warning' ? commandContractWarning(message) : commandContractIssue(message));
|
|
261
285
|
}
|
|
262
286
|
return issues;
|
|
263
287
|
}
|
package/dist/core/command-env.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
2
|
import { readString, readStringArray } from './config-loading.js';
|
|
3
3
|
export const COMMAND_ENV_POLICIES = new Set(['inherit', 'minimal', 'allowlist']);
|
|
4
|
-
export const DEFAULT_COMMAND_ENV_POLICY = '
|
|
4
|
+
export const DEFAULT_COMMAND_ENV_POLICY = 'minimal';
|
|
5
5
|
const BASE_MINIMAL_ENV_KEYS = [
|
|
6
6
|
'CI',
|
|
7
7
|
'COLORTERM',
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { existsSync } from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
-
import {
|
|
3
|
+
import { readUtf8FileInsideWithoutSymlinks } from './safe-filesystem.js';
|
|
4
|
+
import { parseTomlText } from './toml.js';
|
|
4
5
|
export const COMMAND_LIFECYCLES = new Set(['oneshot', 'server', 'watch', 'interactive', 'browser', 'background']);
|
|
5
6
|
export const LONG_RUNNING_LIFECYCLES = new Set(['server', 'watch', 'interactive', 'browser', 'background']);
|
|
6
7
|
export const COMMAND_RUN_POLICIES = new Set(['agent_allowed', 'requires_explicit_user_request', 'manual_only']);
|
|
@@ -12,8 +13,13 @@ export function isRecord(value) {
|
|
|
12
13
|
export function resolveMustflowConfigPath(projectRoot, relativePath) {
|
|
13
14
|
return path.join(projectRoot, ...relativePath.split('/'));
|
|
14
15
|
}
|
|
16
|
+
export function readMustflowOwnedTomlFile(projectRoot, relativePath) {
|
|
17
|
+
return parseTomlText(readUtf8FileInsideWithoutSymlinks(projectRoot, resolveMustflowConfigPath(projectRoot, relativePath), {
|
|
18
|
+
maxBytes: 256 * 1024,
|
|
19
|
+
}));
|
|
20
|
+
}
|
|
15
21
|
export function readMustflowConfig(projectRoot) {
|
|
16
|
-
const parsed =
|
|
22
|
+
const parsed = readMustflowOwnedTomlFile(projectRoot, MUSTFLOW_CONFIG_RELATIVE_PATH);
|
|
17
23
|
if (!isRecord(parsed)) {
|
|
18
24
|
throw new Error(`${MUSTFLOW_CONFIG_RELATIVE_PATH} must contain a TOML table`);
|
|
19
25
|
}
|
|
@@ -24,7 +30,7 @@ export function readMustflowConfigIfExists(projectRoot) {
|
|
|
24
30
|
return existsSync(configPath) ? readMustflowConfig(projectRoot) : undefined;
|
|
25
31
|
}
|
|
26
32
|
export function readCommandContract(projectRoot) {
|
|
27
|
-
const parsed =
|
|
33
|
+
const parsed = readMustflowOwnedTomlFile(projectRoot, COMMANDS_CONFIG_RELATIVE_PATH);
|
|
28
34
|
if (!isRecord(parsed)) {
|
|
29
35
|
throw new Error(`${COMMANDS_CONFIG_RELATIVE_PATH} must contain a TOML table`);
|
|
30
36
|
}
|
|
@@ -6,6 +6,7 @@ import { commandIntentBlockedCommandPattern, commandIntentHasCommandSource, comm
|
|
|
6
6
|
import { MAX_COMMAND_OUTPUT_BYTES } from './command-output-limits.js';
|
|
7
7
|
import { commandEffectsConflict, normalizeCommandEffects } from './command-effects.js';
|
|
8
8
|
import { listChangeClassificationValidationReasons } from './change-classification.js';
|
|
9
|
+
import { readUtf8FileInsideWithoutSymlinks } from './safe-filesystem.js';
|
|
9
10
|
import { parseSkillIndexRoutes } from './skill-route-alignment.js';
|
|
10
11
|
import { SUCCESS_EXIT_CODES_CONTRACT_DESCRIPTION, successExitCodesAreValid as successExitCodeValuesAreValid, } from './success-exit-codes.js';
|
|
11
12
|
const CONTRACT_LINT_SOURCE_FILES = [
|
|
@@ -13,6 +14,7 @@ const CONTRACT_LINT_SOURCE_FILES = [
|
|
|
13
14
|
'.mustflow/docs/agent-workflow.md',
|
|
14
15
|
'AGENTS.md',
|
|
15
16
|
'src/core/change-classification.ts',
|
|
17
|
+
'src/core/change-classification-policy.ts',
|
|
16
18
|
];
|
|
17
19
|
export const DOCUMENTED_VERIFICATION_REASONS = [
|
|
18
20
|
'before_publish',
|
|
@@ -48,7 +50,10 @@ const RELEASE_SENSITIVE_REASONS = new Set([
|
|
|
48
50
|
]);
|
|
49
51
|
const COMMANDS_CONFIG_PATH = '.mustflow/config/commands.toml';
|
|
50
52
|
const SKILL_INDEX_PATH = '.mustflow/skills/INDEX.md';
|
|
51
|
-
const
|
|
53
|
+
const CHANGE_CLASSIFICATION_SOURCE_PATHS = [
|
|
54
|
+
'src/core/change-classification.ts',
|
|
55
|
+
'src/core/change-classification-policy.ts',
|
|
56
|
+
];
|
|
52
57
|
const AGENT_WORKFLOW_PATH = '.mustflow/docs/agent-workflow.md';
|
|
53
58
|
const PACKAGE_SCRIPT_RUNNERS = new Set(['bun', 'npm', 'pnpm', 'yarn']);
|
|
54
59
|
const MAKEFILE_CANDIDATES = ['Makefile', 'makefile'];
|
|
@@ -392,7 +397,7 @@ function readSkillPathsByIntent(projectRoot) {
|
|
|
392
397
|
if (!existsSync(skillIndexPath)) {
|
|
393
398
|
return skillPathsByIntent;
|
|
394
399
|
}
|
|
395
|
-
const routes = parseSkillIndexRoutes(
|
|
400
|
+
const routes = parseSkillIndexRoutes(readUtf8FileInsideWithoutSymlinks(projectRoot, skillIndexPath, { maxBytes: 1024 * 1024 }));
|
|
396
401
|
for (const route of routes) {
|
|
397
402
|
for (const intent of route.commandIntents) {
|
|
398
403
|
skillPathsByIntent.set(intent, [...(skillPathsByIntent.get(intent) ?? []), route.skillPath]);
|
|
@@ -412,7 +417,7 @@ function classifyReasonSource(reason, knownClassificationReasons, documentedVeri
|
|
|
412
417
|
function buildRelatedDocs(source, relatedSkills) {
|
|
413
418
|
const docs = [COMMANDS_CONFIG_PATH];
|
|
414
419
|
if (source === 'classification') {
|
|
415
|
-
docs.push(
|
|
420
|
+
docs.push(...CHANGE_CLASSIFICATION_SOURCE_PATHS);
|
|
416
421
|
}
|
|
417
422
|
if (source === 'documented') {
|
|
418
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
|
+
}
|
package/dist/core/run-receipt.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { closeSync, constants, lstatSync, mkdirSync, openSync, readFileSync, renameSync, unlinkSync, writeFileSync, } from 'node:fs';
|
|
1
|
+
import { closeSync, constants, fstatSync, lstatSync, mkdirSync, openSync, readFileSync, renameSync, unlinkSync, writeFileSync, } from 'node:fs';
|
|
2
2
|
import { randomBytes } from 'node:crypto';
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
const NOFOLLOW_FLAG = typeof constants.O_NOFOLLOW === 'number' ? constants.O_NOFOLLOW : 0;
|
|
@@ -103,19 +103,26 @@ export function ensureFileTargetInsideWithoutSymlinks(parentPath, childPath, opt
|
|
|
103
103
|
throw error;
|
|
104
104
|
}
|
|
105
105
|
}
|
|
106
|
-
export function readFileInsideWithoutSymlinks(parentPath, childPath) {
|
|
106
|
+
export function readFileInsideWithoutSymlinks(parentPath, childPath, options = {}) {
|
|
107
107
|
const absoluteChildPath = path.resolve(childPath);
|
|
108
108
|
ensureInsideWithoutSymlinks(parentPath, absoluteChildPath);
|
|
109
109
|
const fileDescriptor = openSync(absoluteChildPath, constants.O_RDONLY | NOFOLLOW_FLAG);
|
|
110
110
|
try {
|
|
111
|
+
const stats = fstatSync(fileDescriptor);
|
|
112
|
+
if (!stats.isFile()) {
|
|
113
|
+
throw new Error(`Path must be a regular file: ${childPath}`);
|
|
114
|
+
}
|
|
115
|
+
if (options.maxBytes !== undefined && stats.size > options.maxBytes) {
|
|
116
|
+
throw new Error(`File exceeds maximum size ${options.maxBytes} bytes: ${childPath}`);
|
|
117
|
+
}
|
|
111
118
|
return readFileSync(fileDescriptor);
|
|
112
119
|
}
|
|
113
120
|
finally {
|
|
114
121
|
closeSync(fileDescriptor);
|
|
115
122
|
}
|
|
116
123
|
}
|
|
117
|
-
export function readUtf8FileInsideWithoutSymlinks(parentPath, childPath) {
|
|
118
|
-
return readFileInsideWithoutSymlinks(parentPath, childPath).toString('utf8');
|
|
124
|
+
export function readUtf8FileInsideWithoutSymlinks(parentPath, childPath, options = {}) {
|
|
125
|
+
return readFileInsideWithoutSymlinks(parentPath, childPath, options).toString('utf8');
|
|
119
126
|
}
|
|
120
127
|
export function writeFileInsideWithoutSymlinks(parentPath, childPath, content) {
|
|
121
128
|
const absoluteChildPath = path.resolve(childPath);
|
|
@@ -172,6 +172,7 @@ export function isSkillRouteAlignmentIssue(issue) {
|
|
|
172
172
|
return (issue.includes('.mustflow/skills/INDEX.md route') ||
|
|
173
173
|
issue.includes('.mustflow/skills/INDEX.md .mustflow/skills/') ||
|
|
174
174
|
issue.includes('.mustflow/skills/INDEX.md has duplicate route') ||
|
|
175
|
+
issue.includes('template profile "') ||
|
|
175
176
|
issue.endsWith(' is not listed in .mustflow/skills/INDEX.md'));
|
|
176
177
|
}
|
|
177
178
|
export function summarizeSkillRouteAlignment(issues) {
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
import { existsSync
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
+
import { readUtf8FileInsideWithoutSymlinks } from './safe-filesystem.js';
|
|
3
4
|
import { parseSkillIndexRoutes } from './skill-route-alignment.js';
|
|
5
|
+
const MUSTFLOW_TEXT_MAX_BYTES = 1024 * 1024;
|
|
4
6
|
const SKILL_INDEX_PATH = '.mustflow/skills/INDEX.md';
|
|
5
7
|
const SKILL_ROUTE_SOURCE_FILES = [
|
|
6
8
|
SKILL_INDEX_PATH,
|
|
@@ -84,11 +86,15 @@ function routeToSummary(route, skillContent) {
|
|
|
84
86
|
}
|
|
85
87
|
export function explainSkillRoute(projectRoot, target) {
|
|
86
88
|
const indexPath = path.join(projectRoot, ...SKILL_INDEX_PATH.split('/'));
|
|
87
|
-
const indexContent = existsSync(indexPath)
|
|
89
|
+
const indexContent = existsSync(indexPath)
|
|
90
|
+
? readUtf8FileInsideWithoutSymlinks(projectRoot, indexPath, { maxBytes: MUSTFLOW_TEXT_MAX_BYTES })
|
|
91
|
+
: '';
|
|
88
92
|
const routes = parseSkillIndexRoutes(indexContent);
|
|
89
93
|
for (const route of routes) {
|
|
90
94
|
const absoluteSkillPath = path.join(projectRoot, ...route.skillPath.split('/'));
|
|
91
|
-
const skillContent = existsSync(absoluteSkillPath)
|
|
95
|
+
const skillContent = existsSync(absoluteSkillPath)
|
|
96
|
+
? readUtf8FileInsideWithoutSymlinks(projectRoot, absoluteSkillPath, { maxBytes: MUSTFLOW_TEXT_MAX_BYTES })
|
|
97
|
+
: null;
|
|
92
98
|
if (!targetMatchesRoute(target, route, skillContent)) {
|
|
93
99
|
continue;
|
|
94
100
|
}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { existsSync } from 'node:fs';
|
|
2
|
-
import { isRecord, readStringArray, resolveMustflowConfigPath, } from './config-loading.js';
|
|
3
|
-
import { readTomlFile } from './toml.js';
|
|
2
|
+
import { isRecord, readMustflowOwnedTomlFile, readStringArray, resolveMustflowConfigPath, } from './config-loading.js';
|
|
4
3
|
import { classifyVerificationCandidate, } from './verification-plan.js';
|
|
5
4
|
export const TEST_SELECTION_CONFIG_RELATIVE_PATH = '.mustflow/config/test-selection.toml';
|
|
6
5
|
const STALE_OR_MISSING_RULES_NOTE = 'Project-declared test selection rules did not cover the current changed files; review .mustflow/config/test-selection.toml for stale or missing rules.';
|
|
@@ -73,7 +72,7 @@ function readRules(projectRoot) {
|
|
|
73
72
|
};
|
|
74
73
|
}
|
|
75
74
|
try {
|
|
76
|
-
const parsed =
|
|
75
|
+
const parsed = readMustflowOwnedTomlFile(projectRoot, TEST_SELECTION_CONFIG_RELATIVE_PATH);
|
|
77
76
|
if (!isRecord(parsed) || parsed.schema_version !== '1' || !Array.isArray(parsed.rules)) {
|
|
78
77
|
return {
|
|
79
78
|
status: 'invalid',
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { readFileSync } from 'node:fs';
|
|
2
1
|
import path from 'node:path';
|
|
3
2
|
import { commandEffectsConflict, normalizeCommandEffects, } from './command-effects.js';
|
|
3
|
+
import { readUtf8FileInsideWithoutSymlinks } from './safe-filesystem.js';
|
|
4
|
+
const MUSTFLOW_JSON_MAX_BYTES = 1024 * 1024;
|
|
4
5
|
function uniqueSorted(values) {
|
|
5
6
|
return [...new Set(values)].sort((left, right) => left.localeCompare(right));
|
|
6
7
|
}
|
|
@@ -18,9 +19,9 @@ function toScheduleEffect(effect) {
|
|
|
18
19
|
function isObject(value) {
|
|
19
20
|
return !!value && typeof value === 'object';
|
|
20
21
|
}
|
|
21
|
-
function readJsonFile(filePath) {
|
|
22
|
+
function readJsonFile(projectRoot, filePath) {
|
|
22
23
|
try {
|
|
23
|
-
return JSON.parse(
|
|
24
|
+
return JSON.parse(readUtf8FileInsideWithoutSymlinks(projectRoot, filePath, { maxBytes: MUSTFLOW_JSON_MAX_BYTES }));
|
|
24
25
|
}
|
|
25
26
|
catch {
|
|
26
27
|
return null;
|
|
@@ -56,7 +57,7 @@ function readVerifyManifestUndeclaredWriteIntents(projectRoot, latest) {
|
|
|
56
57
|
if (!manifestPath) {
|
|
57
58
|
return new Set();
|
|
58
59
|
}
|
|
59
|
-
const manifest = readJsonFile(manifestPath);
|
|
60
|
+
const manifest = readJsonFile(projectRoot, manifestPath);
|
|
60
61
|
if (!isObject(manifest) || manifest.command !== 'verify' || !Array.isArray(manifest.receipts)) {
|
|
61
62
|
return new Set();
|
|
62
63
|
}
|
|
@@ -69,7 +70,7 @@ function readVerifyManifestUndeclaredWriteIntents(projectRoot, latest) {
|
|
|
69
70
|
if (!receiptPath) {
|
|
70
71
|
continue;
|
|
71
72
|
}
|
|
72
|
-
const intent = getUndeclaredWriteIntent(readJsonFile(receiptPath));
|
|
73
|
+
const intent = getUndeclaredWriteIntent(readJsonFile(projectRoot, receiptPath));
|
|
73
74
|
if (intent) {
|
|
74
75
|
intents.add(intent);
|
|
75
76
|
}
|
|
@@ -78,7 +79,7 @@ function readVerifyManifestUndeclaredWriteIntents(projectRoot, latest) {
|
|
|
78
79
|
}
|
|
79
80
|
function readLatestUndeclaredWriteIntents(projectRoot) {
|
|
80
81
|
const latestPath = path.join(projectRoot, '.mustflow', 'state', 'runs', 'latest.json');
|
|
81
|
-
const parsed = readJsonFile(latestPath);
|
|
82
|
+
const parsed = readJsonFile(projectRoot, latestPath);
|
|
82
83
|
const directIntent = getUndeclaredWriteIntent(parsed);
|
|
83
84
|
if (directIntent) {
|
|
84
85
|
return new Set([directIntent]);
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { existsSync, readFileSync, readdirSync, statSync } from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
-
import { isRecord } from './config-loading.js';
|
|
4
|
-
import { readTomlFile } from './toml.js';
|
|
3
|
+
import { isRecord, readMustflowOwnedTomlFile } from './config-loading.js';
|
|
5
4
|
const RELEASE_VERSIONING_ACTIVE_FIELDS = [
|
|
6
5
|
'impact_check',
|
|
7
6
|
'suggest_bump',
|
|
@@ -155,7 +154,7 @@ function hasGoModuleVersionSource(projectRoot) {
|
|
|
155
154
|
}
|
|
156
155
|
export function readDeclaredVersionSources(projectRoot) {
|
|
157
156
|
try {
|
|
158
|
-
const versioningConfig =
|
|
157
|
+
const versioningConfig = readMustflowOwnedTomlFile(projectRoot, VERSIONING_CONFIG_PATH);
|
|
159
158
|
if (!isRecord(versioningConfig) || !Array.isArray(versioningConfig.sources)) {
|
|
160
159
|
return [];
|
|
161
160
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mustflow",
|
|
3
|
-
"version": "2.22.
|
|
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 =
|
|
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 =
|
|
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 =
|
|
352
|
+
revision = 2
|
|
353
353
|
translations = {}
|
|
354
354
|
|
|
355
355
|
[documents."skill.test-maintenance"]
|