mustflow 2.22.3 → 2.22.5
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 +9 -75
- package/dist/cli/commands/contract-lint.js +2 -2
- package/dist/cli/commands/dashboard.js +14 -6
- 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/update.js +2 -2
- package/dist/cli/commands/version-sources.js +2 -3
- package/dist/cli/i18n/en.js +2 -0
- package/dist/cli/i18n/es.js +2 -0
- package/dist/cli/i18n/fr.js +2 -0
- package/dist/cli/i18n/hi.js +2 -0
- package/dist/cli/i18n/ko.js +2 -0
- package/dist/cli/i18n/zh.js +2 -0
- package/dist/cli/lib/agent-context.js +6 -11
- package/dist/cli/lib/local-index/index.js +14 -11
- 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/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/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 +2 -1
- 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 -2
- package/schemas/commands.schema.json +1 -0
- package/templates/default/locales/en/.mustflow/skills/INDEX.md +10 -6
- package/templates/default/locales/en/.mustflow/skills/routes.toml +2 -2
- package/templates/default/manifest.toml +1 -1
|
@@ -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 = [
|
|
@@ -392,7 +393,7 @@ function readSkillPathsByIntent(projectRoot) {
|
|
|
392
393
|
if (!existsSync(skillIndexPath)) {
|
|
393
394
|
return skillPathsByIntent;
|
|
394
395
|
}
|
|
395
|
-
const routes = parseSkillIndexRoutes(
|
|
396
|
+
const routes = parseSkillIndexRoutes(readUtf8FileInsideWithoutSymlinks(projectRoot, skillIndexPath, { maxBytes: 1024 * 1024 }));
|
|
396
397
|
for (const route of routes) {
|
|
397
398
|
for (const intent of route.commandIntents) {
|
|
398
399
|
skillPathsByIntent.set(intent, [...(skillPathsByIntent.get(intent) ?? []), route.skillPath]);
|
|
@@ -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.5",
|
|
4
4
|
"description": "Agent workflow documents and CLI for mustflow repository roots.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT-0",
|
|
@@ -36,7 +36,9 @@
|
|
|
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:full": "bun run build && node scripts/run-cli-tests.mjs full",
|
|
39
|
+
"test:full": "bun run build && node scripts/run-cli-tests.mjs full-auto",
|
|
40
|
+
"test:full:auto": "bun run build && node scripts/run-cli-tests.mjs full-auto",
|
|
41
|
+
"test:full:serial": "bun run build && node scripts/run-cli-tests.mjs full",
|
|
40
42
|
"check": "bun run check:package && bun run test:full",
|
|
41
43
|
"check:package": "node -e \"const fs=require('fs'); JSON.parse(fs.readFileSync('package.json','utf8')); console.log('package.json ok')\"",
|
|
42
44
|
"check:typecheck": "tsc -p tsconfig.json --noEmit",
|
|
@@ -36,14 +36,18 @@ refer to `AGENTS.md` and `.mustflow/config/commands.toml` to implement the most
|
|
|
36
36
|
|
|
37
37
|
## Selection Convention
|
|
38
38
|
|
|
39
|
-
- Choose one
|
|
40
|
-
|
|
39
|
+
- Choose one main route: a `primary` route for ordinary work or an `authoring` route for
|
|
40
|
+
mustflow authoring and maintenance work. Prefer the most specific matching skill over a broad
|
|
41
|
+
architecture or review skill.
|
|
42
|
+
- Treat `authoring` routes as selectable main routes, not adjunct routes. Use them when the task
|
|
43
|
+
creates or maintains mustflow-owned skills, context files, command contracts, route metadata, or
|
|
44
|
+
public documentation entries.
|
|
41
45
|
- Add no more than two adjunct skills for secondary risks such as tests, documentation, security,
|
|
42
46
|
privacy, release, or contract drift.
|
|
43
47
|
- Treat event-triggered skills as inactive until the event occurs. For example, read
|
|
44
48
|
`failure-triage` only after a configured command intent or verification step fails.
|
|
45
|
-
- If several
|
|
46
|
-
changed now, then report the skipped plausible
|
|
49
|
+
- If several main routes appear to match, choose the one tied to the files and behavior being
|
|
50
|
+
changed now, then report the skipped plausible routes instead of reading every route.
|
|
47
51
|
|
|
48
52
|
## Classification Prefilter
|
|
49
53
|
|
|
@@ -78,8 +82,8 @@ test tasks from requiring a full read of architecture-pattern routes. Categories
|
|
|
78
82
|
|
|
79
83
|
## Specific Routes
|
|
80
84
|
|
|
81
|
-
After choosing a category, choose one
|
|
82
|
-
stay inactive until their event occurs.
|
|
85
|
+
After choosing a category, choose one main route (`primary` or `authoring`) and at most two adjunct
|
|
86
|
+
routes. Event routes stay inactive until their event occurs.
|
|
83
87
|
|
|
84
88
|
### Bug and Failure
|
|
85
89
|
|
|
@@ -2,7 +2,7 @@ schema_version = "1"
|
|
|
2
2
|
|
|
3
3
|
[routes."artifact-integrity-check"]
|
|
4
4
|
category = "ui_assets"
|
|
5
|
-
route_type = "
|
|
5
|
+
route_type = "primary"
|
|
6
6
|
priority = 80
|
|
7
7
|
applies_to_reasons = ["package_metadata_change", "release_risk"]
|
|
8
8
|
|
|
@@ -212,7 +212,7 @@ applies_to_reasons = ["test_change"]
|
|
|
212
212
|
|
|
213
213
|
[routes."security-privacy-review"]
|
|
214
214
|
category = "security_privacy"
|
|
215
|
-
route_type = "
|
|
215
|
+
route_type = "primary"
|
|
216
216
|
priority = 30
|
|
217
217
|
applies_to_reasons = ["security_change", "privacy_change"]
|
|
218
218
|
|