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.
Files changed (42) hide show
  1. package/README.md +9 -75
  2. package/dist/cli/commands/contract-lint.js +2 -2
  3. package/dist/cli/commands/dashboard.js +14 -6
  4. package/dist/cli/commands/help.js +8 -9
  5. package/dist/cli/commands/impact.js +2 -3
  6. package/dist/cli/commands/init.js +61 -5
  7. package/dist/cli/commands/update.js +2 -2
  8. package/dist/cli/commands/version-sources.js +2 -3
  9. package/dist/cli/i18n/en.js +2 -0
  10. package/dist/cli/i18n/es.js +2 -0
  11. package/dist/cli/i18n/fr.js +2 -0
  12. package/dist/cli/i18n/hi.js +2 -0
  13. package/dist/cli/i18n/ko.js +2 -0
  14. package/dist/cli/i18n/zh.js +2 -0
  15. package/dist/cli/lib/agent-context.js +6 -11
  16. package/dist/cli/lib/local-index/index.js +14 -11
  17. package/dist/cli/lib/mustflow-read.js +41 -0
  18. package/dist/cli/lib/project-root.js +1 -2
  19. package/dist/cli/lib/repo-map.js +65 -16
  20. package/dist/cli/lib/templates.js +124 -8
  21. package/dist/cli/lib/toml.js +6 -1
  22. package/dist/cli/lib/validation/constants.js +2 -0
  23. package/dist/cli/lib/validation/index.js +291 -22
  24. package/dist/cli/lib/validation/primitives.js +2 -2
  25. package/dist/cli/lib/validation/test-selection.js +2 -2
  26. package/dist/core/bounded-output.js +32 -7
  27. package/dist/core/check-issues.js +7 -1
  28. package/dist/core/command-contract-validation.js +28 -4
  29. package/dist/core/command-env.js +1 -1
  30. package/dist/core/config-loading.js +9 -3
  31. package/dist/core/contract-lint.js +2 -1
  32. package/dist/core/safe-filesystem.js +11 -4
  33. package/dist/core/skill-route-alignment.js +1 -0
  34. package/dist/core/skill-route-explanation.js +9 -3
  35. package/dist/core/test-selection.js +2 -3
  36. package/dist/core/verification-scheduler.js +7 -6
  37. package/dist/core/version-sources.js +2 -3
  38. package/package.json +4 -2
  39. package/schemas/commands.schema.json +1 -0
  40. package/templates/default/locales/en/.mustflow/skills/INDEX.md +10 -6
  41. package/templates/default/locales/en/.mustflow/skills/routes.toml +2 -2
  42. 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 networkScope = warning.network ? ' with network = true' : '';
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${networkScope}; ${migration}`;
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"${networkScope}; ${migration}`;
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
- issues.push(commandContractWarning(formatCommandEnvInheritanceWarning(warning)));
283
+ const message = formatCommandEnvInheritanceWarning(warning);
284
+ issues.push(warning.severity === 'warning' ? commandContractWarning(message) : commandContractIssue(message));
261
285
  }
262
286
  return issues;
263
287
  }
@@ -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 = 'inherit';
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 { readTomlFile } from './toml.js';
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 = readTomlFile(resolveMustflowConfigPath(projectRoot, MUSTFLOW_CONFIG_RELATIVE_PATH));
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 = readTomlFile(resolveMustflowConfigPath(projectRoot, COMMANDS_CONFIG_RELATIVE_PATH));
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(readFileSync(skillIndexPath, 'utf8'));
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, readFileSync } from 'node:fs';
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) ? readFileSync(indexPath, 'utf8') : '';
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) ? readFileSync(absoluteSkillPath, 'utf8') : null;
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 = readTomlFile(configPath);
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(readFileSync(filePath, 'utf8'));
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 = readTomlFile(path.join(projectRoot, VERSIONING_CONFIG_PATH));
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",
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",
@@ -135,6 +135,7 @@
135
135
  "items": { "type": "string" }
136
136
  },
137
137
  "cmd": { "type": "string" },
138
+ "allow_shell": { "type": "boolean" },
138
139
  "cwd": { "type": "string" },
139
140
  "timeout_seconds": { "type": "integer" },
140
141
  "kill_after_seconds": { "type": "integer" },
@@ -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 primary skill that best describes the main work. Prefer the most specific matching
40
- skill over a broad architecture or review skill.
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 primary skills appear to match, choose the one tied to the files and behavior being
46
- changed now, then report the skipped plausible skills instead of reading every route.
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 primary route and at most two adjunct routes. Event routes
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 = "adjunct"
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 = "adjunct"
215
+ route_type = "primary"
216
216
  priority = 30
217
217
  applies_to_reasons = ["security_change", "privacy_change"]
218
218
 
@@ -1,6 +1,6 @@
1
1
  id = "default"
2
2
  name = "default"
3
- version = "2.22.3"
3
+ version = "2.22.5"
4
4
  description = "Minimal workflow for LLM agents to read, edit, and verify their work in a repository."
5
5
  common_root = "common"
6
6
  locales_root = "locales"