mustflow 2.117.1 → 2.118.0
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 +1 -0
- package/dist/cli/commands/run/args.js +5 -0
- package/dist/cli/commands/run/execution.js +7 -6
- package/dist/cli/commands/run/preview.js +6 -5
- package/dist/cli/commands/run/receipt.js +1 -0
- package/dist/cli/commands/run.js +2 -1
- package/dist/cli/commands/workspace.js +216 -81
- package/dist/cli/i18n/en.js +1 -0
- package/dist/cli/i18n/es.js +1 -0
- package/dist/cli/i18n/fr.js +1 -0
- package/dist/cli/i18n/hi.js +1 -0
- package/dist/cli/i18n/ko.js +1 -0
- package/dist/cli/i18n/zh.js +1 -0
- package/dist/cli/lib/manifest-lock.js +10 -1
- package/dist/cli/lib/repo-map.js +5 -0
- package/dist/cli/lib/run-context.js +136 -0
- package/dist/cli/lib/run-root-trust.js +22 -15
- package/dist/cli/lib/validation/index.js +8 -0
- package/dist/core/config-loading.js +107 -14
- package/dist/core/run-receipt.js +1 -0
- package/dist/core/workspace-command-authority.js +98 -0
- package/package.json +1 -1
- package/schemas/run-receipt.schema.json +10 -0
- package/schemas/workspace-command-catalog.schema.json +3 -0
- package/schemas/workspace-command-fragments.schema.json +2 -0
- package/schemas/workspace-status.schema.json +4 -1
- package/schemas/workspace-verification-plan.schema.json +3 -0
- package/templates/default/common/.mustflow/config/mustflow.toml +2 -0
- package/templates/default/i18n.toml +7 -1
- package/templates/default/locales/en/.mustflow/skills/INDEX.md +2 -1
- package/templates/default/locales/en/.mustflow/skills/ai-game-asset-production/SKILL.md +209 -0
- package/templates/default/locales/en/.mustflow/skills/ai-game-asset-production/references/asset-contract-validation.md +106 -0
- package/templates/default/locales/en/.mustflow/skills/ai-game-asset-production/references/raster-alpha-atlas-checklist.md +109 -0
- package/templates/default/locales/en/.mustflow/skills/ai-game-asset-production/references/tile-animation-checklist.md +98 -0
- package/templates/default/locales/en/.mustflow/skills/routes.toml +6 -0
- package/templates/default/manifest.toml +6 -1
package/README.md
CHANGED
|
@@ -322,6 +322,7 @@ mf run mustflow_update_apply
|
|
|
322
322
|
| `mf script-pack run core/text-budget check <path...> --max <count>` | Check exact text length budgets for files using grapheme counts by default. |
|
|
323
323
|
| `mf script-pack run core/text-budget check package.json --json-pointer /description --max <count> --json` | Check a JSON string field and print the stable report schema. |
|
|
324
324
|
| `mf run <intent>` | Run an allowed one-shot command. |
|
|
325
|
+
| `mf run <intent> --repo <path>` | Run one delegated scoped workspace contract from the workspace root. |
|
|
325
326
|
| `mf run <intent> --wait` | Wait for conflicting active run locks before executing the command. |
|
|
326
327
|
| `mf run <intent> --dry-run --json` | Preview whether an intent is runnable and what command metadata would be used, without executing it. |
|
|
327
328
|
| `mf index` | Build a SQLite index for mustflow docs, skill routes, command rules, command-effect locks, and file fingerprints. Use `--incremental` to reuse a compatible fresh index without rewriting it. |
|
|
@@ -12,6 +12,7 @@ const RUN_OPTIONS = [
|
|
|
12
12
|
{ name: ALLOW_UNTRUSTED_ROOT_OPTION, kind: 'boolean' },
|
|
13
13
|
{ name: '--wait-timeout', kind: 'string' },
|
|
14
14
|
{ name: '--allow-approval', kind: 'string' },
|
|
15
|
+
{ name: '--repo', kind: 'string' },
|
|
15
16
|
];
|
|
16
17
|
export function hasRunHelpOption(args) {
|
|
17
18
|
return hasCliOptionToken(args, '--help', ['-h']);
|
|
@@ -43,6 +44,7 @@ export function parseRunArguments(args) {
|
|
|
43
44
|
allowApprovals,
|
|
44
45
|
wait: hasParsedCliOption(parsed, '--wait'),
|
|
45
46
|
waitTimeoutSeconds,
|
|
47
|
+
repository: getParsedCliStringOption(parsed, '--repo'),
|
|
46
48
|
intentName: intentName ?? null,
|
|
47
49
|
extra,
|
|
48
50
|
invalidApprovalAction: null,
|
|
@@ -73,6 +75,7 @@ export function parseRunArguments(args) {
|
|
|
73
75
|
allowApprovals,
|
|
74
76
|
wait: hasParsedCliOption(parsed, '--wait'),
|
|
75
77
|
waitTimeoutSeconds,
|
|
78
|
+
repository: getParsedCliStringOption(parsed, '--repo'),
|
|
76
79
|
intentName: intentName ?? null,
|
|
77
80
|
extra,
|
|
78
81
|
invalidApprovalAction,
|
|
@@ -90,12 +93,14 @@ export function getRunHelp(lang = 'en') {
|
|
|
90
93
|
{ label: '--wait', description: t(lang, 'run.help.option.wait') },
|
|
91
94
|
{ label: '--wait-timeout <seconds>', description: t(lang, 'run.help.option.waitTimeout') },
|
|
92
95
|
{ label: '--allow-approval <action>', description: t(lang, 'run.help.option.allowApproval') },
|
|
96
|
+
{ label: '--repo <path>', description: t(lang, 'run.help.option.repository') },
|
|
93
97
|
{ label: ALLOW_UNTRUSTED_ROOT_OPTION, description: t(lang, 'run.help.option.allowUntrustedRoot') },
|
|
94
98
|
{ label: '-h, --help', description: t(lang, 'cli.option.help') },
|
|
95
99
|
],
|
|
96
100
|
examples: [
|
|
97
101
|
'mf run test',
|
|
98
102
|
'mf run lint --json',
|
|
103
|
+
'mf run test --repo projects/example --json',
|
|
99
104
|
'mf run release_npm_publish --allow-approval release --allow-approval network_access --json',
|
|
100
105
|
],
|
|
101
106
|
exitCodes: [
|
|
@@ -2,7 +2,6 @@ import { createHash } from 'node:crypto';
|
|
|
2
2
|
import { performance } from 'node:perf_hooks';
|
|
3
3
|
import { ACTIVE_RUN_LOCK_ID_ENV, acquireActiveRunLock } from '../../../core/active-run-locks.js';
|
|
4
4
|
import { createCommandEnv } from '../../../core/command-env.js';
|
|
5
|
-
import { readCommandContract, readMustflowConfigIfExists } from '../../../core/config-loading.js';
|
|
6
5
|
import { createCorrelationId } from '../../../core/correlation-id.js';
|
|
7
6
|
import { recordRunPerformanceHistory } from '../../../core/run-performance-history.js';
|
|
8
7
|
import { RunProfiler } from '../../../core/run-profile.js';
|
|
@@ -11,8 +10,8 @@ import { finishRunWriteTracking, startRunWriteTracking } from '../../../core/run
|
|
|
11
10
|
import { resolveRunReceiptRetentionPolicy } from '../../../core/retention-policy.js';
|
|
12
11
|
import { renderCliError } from '../../lib/cli-output.js';
|
|
13
12
|
import { t } from '../../lib/i18n.js';
|
|
14
|
-
import { resolveMustflowRoot } from '../../lib/project-root.js';
|
|
15
13
|
import { assessRunRootTrust } from '../../lib/run-root-trust.js';
|
|
14
|
+
import { resolveRunCommandContext } from '../../lib/run-context.js';
|
|
16
15
|
import { createRunPlan, createRunPreview, } from '../../lib/run-plan.js';
|
|
17
16
|
import { getRunStatus, runArgvCommandStreaming, runShellCommandStreaming } from './executor.js';
|
|
18
17
|
import { emitOutput, isOutputLimitExceededError } from './output.js';
|
|
@@ -185,8 +184,9 @@ function createRunProgressReporter(input) {
|
|
|
185
184
|
export async function executeRunCommand(request, reporter, lang = 'en', options = {}) {
|
|
186
185
|
const executorStartedAtMs = performance.now();
|
|
187
186
|
const profiler = new RunProfiler();
|
|
188
|
-
const
|
|
189
|
-
const
|
|
187
|
+
const runContext = profiler.measure('root_detection', () => resolveRunCommandContext({ repository: request.repository, intentName: request.intentName }));
|
|
188
|
+
const projectRoot = runContext.projectRoot;
|
|
189
|
+
const rootTrust = profiler.measure('root_trust', () => assessRunRootTrust(projectRoot, { requiredPaths: runContext.trustPaths }));
|
|
190
190
|
const jsonLikeOutput = request.outputMode !== 'text';
|
|
191
191
|
if (!request.allowUntrustedRoot && !rootTrust.trusted) {
|
|
192
192
|
const message = rootTrust.reason === 'manifest_lock_invalid'
|
|
@@ -195,7 +195,7 @@ export async function executeRunCommand(request, reporter, lang = 'en', options
|
|
|
195
195
|
reporter.stderr(renderCliError(message, 'mf run --help', lang));
|
|
196
196
|
return { exitCode: 1, receipt: null };
|
|
197
197
|
}
|
|
198
|
-
const contract = profiler.measure('command_contract', () =>
|
|
198
|
+
const contract = profiler.measure('command_contract', () => runContext.contract);
|
|
199
199
|
const plan = profiler.measure('plan_creation', () => createRunPlan(projectRoot, contract, request.intentName, {
|
|
200
200
|
testTargets: options.testTargets,
|
|
201
201
|
approvedActions: request.allowApprovals,
|
|
@@ -235,7 +235,7 @@ export async function executeRunCommand(request, reporter, lang = 'en', options
|
|
|
235
235
|
return { exitCode: 1, receipt: null };
|
|
236
236
|
}
|
|
237
237
|
try {
|
|
238
|
-
const runReceiptPolicy = profiler.measure('retention_policy', () => resolveRunReceiptRetentionPolicy(
|
|
238
|
+
const runReceiptPolicy = profiler.measure('retention_policy', () => resolveRunReceiptRetentionPolicy(runContext.mustflowConfig));
|
|
239
239
|
const env = profiler.measure('environment', () => createCommandEnv(projectRoot, { policy: plan.envPolicy, allowlist: plan.envAllowlist }));
|
|
240
240
|
env[ACTIVE_RUN_LOCK_ID_ENV] = activeRunLock.handle.record.run_id;
|
|
241
241
|
const writeTracker = profiler.measure('write_drift_before', () => startRunWriteTracking(projectRoot, contract, request.intentName, {
|
|
@@ -299,6 +299,7 @@ export async function executeRunCommand(request, reporter, lang = 'en', options
|
|
|
299
299
|
phaseTimings: profiler.getReceiptPhases(),
|
|
300
300
|
stdoutTailBytes: runReceiptPolicy.stdoutTailBytes,
|
|
301
301
|
stderrTailBytes: runReceiptPolicy.stderrTailBytes,
|
|
302
|
+
workspaceScope: runContext.workspaceScope,
|
|
302
303
|
}));
|
|
303
304
|
if (options.writeLatestReceipt !== false) {
|
|
304
305
|
profiler.measure('receipt_write', () => writeRunReceipt(projectRoot, receipt, runReceiptPolicy));
|
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import { readCommandContract } from '../../../core/config-loading.js';
|
|
2
1
|
import { RunProfiler } from '../../../core/run-profile.js';
|
|
3
|
-
import {
|
|
2
|
+
import { resolveRunCommandContext } from '../../lib/run-context.js';
|
|
4
3
|
import { createRunPlan, createRunPreview, renderRunPreviewText, } from '../../lib/run-plan.js';
|
|
5
4
|
import { writeLatestRunProfile } from './profile.js';
|
|
6
5
|
export function getRunPreviewMode(input) {
|
|
@@ -8,15 +7,17 @@ export function getRunPreviewMode(input) {
|
|
|
8
7
|
}
|
|
9
8
|
export function executeRunPreviewCommand(input, reporter, lang, options) {
|
|
10
9
|
const profiler = new RunProfiler();
|
|
11
|
-
const
|
|
12
|
-
const
|
|
10
|
+
const runContext = profiler.measure('root_detection', () => resolveRunCommandContext({ repository: input.repository, intentName: input.intentName }));
|
|
11
|
+
const projectRoot = runContext.projectRoot;
|
|
12
|
+
const contract = profiler.measure('command_contract', () => runContext.contract);
|
|
13
13
|
const plan = profiler.measure('plan_creation', () => createRunPlan(projectRoot, contract, input.intentName, {
|
|
14
14
|
testTargets: options.testTargets,
|
|
15
15
|
approvedActions: input.allowApprovals,
|
|
16
16
|
}));
|
|
17
17
|
profiler.measure('preview_render', () => {
|
|
18
18
|
if (input.json) {
|
|
19
|
-
|
|
19
|
+
const preview = createRunPreview(plan, input.previewMode);
|
|
20
|
+
reporter.stdout(JSON.stringify(runContext.workspaceScope ? { ...preview, workspace_scope: runContext.workspaceScope } : preview, null, 2));
|
|
20
21
|
}
|
|
21
22
|
else {
|
|
22
23
|
reporter.stdout(renderRunPreviewText(plan, input.previewMode, lang));
|
|
@@ -37,6 +37,7 @@ export function assembleRunReceipt(input) {
|
|
|
37
37
|
selected_target_count: Math.max(1, input.plan.testTargets.length),
|
|
38
38
|
fallback_used: false,
|
|
39
39
|
},
|
|
40
|
+
workspaceScope: input.workspaceScope,
|
|
40
41
|
stdoutTailBytes: input.stdoutTailBytes,
|
|
41
42
|
stderrTailBytes: input.stderrTailBytes,
|
|
42
43
|
receiptPath: createRunReceiptRelativePath(),
|
package/dist/cli/commands/run.js
CHANGED
|
@@ -69,8 +69,9 @@ export async function runRun(args, reporter, lang = 'en', options = {}) {
|
|
|
69
69
|
allowApprovals: parsedArgs.allowApprovals,
|
|
70
70
|
wait: parsedArgs.wait,
|
|
71
71
|
waitTimeoutSeconds: parsedArgs.waitTimeoutSeconds,
|
|
72
|
+
repository: parsedArgs.repository,
|
|
72
73
|
}, reporter, lang, options);
|
|
73
74
|
return result.exitCode;
|
|
74
75
|
}
|
|
75
|
-
return executeRunPreviewCommand({ intentName, json, previewMode, allowApprovals: parsedArgs.allowApprovals }, reporter, lang, options);
|
|
76
|
+
return executeRunPreviewCommand({ intentName, json, previewMode, allowApprovals: parsedArgs.allowApprovals, repository: parsedArgs.repository }, reporter, lang, options);
|
|
76
77
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
1
2
|
import path from 'node:path';
|
|
2
3
|
import { createClassifyOutput } from './classify.js';
|
|
3
4
|
import { createChangeVerificationReport, } from '../../core/change-verification.js';
|
|
@@ -8,7 +9,8 @@ import { formatCliOptionParseError, hasCliOptionToken, hasParsedCliOption, parse
|
|
|
8
9
|
import { resolveMustflowRoot } from '../lib/project-root.js';
|
|
9
10
|
import { getRepoMapConfig, discoverNestedRepositories } from '../lib/repo-map.js';
|
|
10
11
|
import { createRunPlan } from '../lib/run-plan.js';
|
|
11
|
-
import {
|
|
12
|
+
import { assertScopedCommandIntentIsolation } from '../lib/run-context.js';
|
|
13
|
+
import { readCommandContract, readScopedCommandContract, readString, readStringArray, } from '../../core/config-loading.js';
|
|
12
14
|
const DEFAULT_WORKSPACE_SCAN_ROOT = 'projects';
|
|
13
15
|
const WORKSPACE_SCAN_SCHEMA_VERSION = '1';
|
|
14
16
|
const WORKSPACE_STATUS_SCHEMA_VERSION = '1';
|
|
@@ -95,6 +97,9 @@ function createAdHocWorkspaceConfig(base, projectsDir) {
|
|
|
95
97
|
...base,
|
|
96
98
|
enabled: true,
|
|
97
99
|
roots: [projectsDir],
|
|
100
|
+
authorityMode: 'repository_local',
|
|
101
|
+
delegatedContracts: [],
|
|
102
|
+
delegatedContractCount: 0,
|
|
98
103
|
};
|
|
99
104
|
}
|
|
100
105
|
function slugCommandFragmentSegment(segment) {
|
|
@@ -195,16 +200,19 @@ function commandFragmentNextActions(repositoryCount) {
|
|
|
195
200
|
function getIntentNames(intents) {
|
|
196
201
|
return Object.keys(intents).sort((left, right) => left.localeCompare(right));
|
|
197
202
|
}
|
|
198
|
-
function
|
|
203
|
+
function summarizeLocalCommand(repositoryRoot, repository) {
|
|
199
204
|
if (!repository.commandContract) {
|
|
200
205
|
return {
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
206
|
+
surface: {
|
|
207
|
+
path: null,
|
|
208
|
+
exists: false,
|
|
209
|
+
parse_error: null,
|
|
210
|
+
total_intents: null,
|
|
211
|
+
runnable_count: null,
|
|
212
|
+
runnable_intents: [],
|
|
213
|
+
blocked_count: null,
|
|
214
|
+
},
|
|
215
|
+
contract: null,
|
|
208
216
|
};
|
|
209
217
|
}
|
|
210
218
|
try {
|
|
@@ -212,45 +220,129 @@ function summarizeCommandSurface(repositoryRoot, repository) {
|
|
|
212
220
|
const intentNames = getIntentNames(contract.intents);
|
|
213
221
|
const runnableIntents = intentNames.filter((intentName) => createRunPlan(repositoryRoot, contract, intentName).ok);
|
|
214
222
|
return {
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
223
|
+
surface: {
|
|
224
|
+
path: repository.commandContract,
|
|
225
|
+
exists: true,
|
|
226
|
+
parse_error: null,
|
|
227
|
+
total_intents: intentNames.length,
|
|
228
|
+
runnable_count: runnableIntents.length,
|
|
229
|
+
runnable_intents: runnableIntents,
|
|
230
|
+
blocked_count: Math.max(0, intentNames.length - runnableIntents.length),
|
|
231
|
+
},
|
|
232
|
+
contract,
|
|
222
233
|
};
|
|
223
234
|
}
|
|
224
235
|
catch (error) {
|
|
225
236
|
return {
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
237
|
+
surface: {
|
|
238
|
+
path: repository.commandContract,
|
|
239
|
+
exists: true,
|
|
240
|
+
parse_error: error instanceof Error ? error.message : String(error),
|
|
241
|
+
total_intents: null,
|
|
242
|
+
runnable_count: null,
|
|
243
|
+
runnable_intents: [],
|
|
244
|
+
blocked_count: null,
|
|
245
|
+
},
|
|
246
|
+
contract: null,
|
|
233
247
|
};
|
|
234
248
|
}
|
|
235
249
|
}
|
|
236
|
-
function
|
|
237
|
-
|
|
238
|
-
|
|
250
|
+
function normalizeRepositoryPath(value) {
|
|
251
|
+
return value.replace(/\\/gu, '/').replace(/\/+$/gu, '');
|
|
252
|
+
}
|
|
253
|
+
function findDelegatedContract(workspace, repository) {
|
|
254
|
+
if (workspace.authorityMode !== 'delegated_scoped' || repository.commandContract) {
|
|
255
|
+
return undefined;
|
|
256
|
+
}
|
|
257
|
+
const repositoryPath = normalizeRepositoryPath(repository.relativePath);
|
|
258
|
+
return workspace.delegatedContracts.find((contract) => contract.repository === repositoryPath);
|
|
259
|
+
}
|
|
260
|
+
function delegatedIntentIsRunnable(projectRoot, contract, scope, intentName) {
|
|
261
|
+
try {
|
|
262
|
+
assertScopedCommandIntentIsolation(projectRoot, scope, contract, intentName);
|
|
263
|
+
return createRunPlan(projectRoot, contract, intentName).ok;
|
|
264
|
+
}
|
|
265
|
+
catch {
|
|
266
|
+
return false;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
function summarizeDelegatedCommand(projectRoot, scope) {
|
|
270
|
+
const contractPath = `.mustflow/config/${scope.file}`;
|
|
271
|
+
const contractExists = existsSync(path.resolve(projectRoot, '.mustflow', 'config', ...scope.file.split('/')));
|
|
272
|
+
try {
|
|
273
|
+
const contract = readScopedCommandContract(projectRoot, scope.file, `workspace:${scope.repository}`, scope.repository);
|
|
274
|
+
const intentNames = getIntentNames(contract.intents);
|
|
275
|
+
const runnableIntents = intentNames.filter((intentName) => delegatedIntentIsRunnable(projectRoot, contract, scope, intentName));
|
|
276
|
+
return {
|
|
277
|
+
surface: {
|
|
278
|
+
path: contractPath,
|
|
279
|
+
exists: true,
|
|
280
|
+
parse_error: null,
|
|
281
|
+
total_intents: intentNames.length,
|
|
282
|
+
runnable_count: runnableIntents.length,
|
|
283
|
+
runnable_intents: runnableIntents,
|
|
284
|
+
blocked_count: Math.max(0, intentNames.length - runnableIntents.length),
|
|
285
|
+
},
|
|
286
|
+
contract,
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
catch (error) {
|
|
290
|
+
return {
|
|
291
|
+
surface: {
|
|
292
|
+
path: contractPath,
|
|
293
|
+
exists: contractExists,
|
|
294
|
+
parse_error: error instanceof Error ? error.message : String(error),
|
|
295
|
+
total_intents: null,
|
|
296
|
+
runnable_count: null,
|
|
297
|
+
runnable_intents: [],
|
|
298
|
+
blocked_count: null,
|
|
299
|
+
},
|
|
300
|
+
contract: null,
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
function summarizeCommandSelection(projectRoot, repositoryRoot, repository, workspace) {
|
|
305
|
+
const delegatedContract = findDelegatedContract(workspace, repository);
|
|
306
|
+
if (delegatedContract) {
|
|
307
|
+
const summary = summarizeDelegatedCommand(projectRoot, delegatedContract);
|
|
308
|
+
return {
|
|
309
|
+
authority: 'delegated_scoped',
|
|
310
|
+
...summary,
|
|
311
|
+
scope: delegatedContract,
|
|
312
|
+
planningRoot: projectRoot,
|
|
313
|
+
};
|
|
239
314
|
}
|
|
315
|
+
const summary = summarizeLocalCommand(repositoryRoot, repository);
|
|
316
|
+
return {
|
|
317
|
+
authority: repository.commandContract ? 'repository_local' : null,
|
|
318
|
+
...summary,
|
|
319
|
+
scope: null,
|
|
320
|
+
planningRoot: repositoryRoot,
|
|
321
|
+
};
|
|
322
|
+
}
|
|
323
|
+
function repositoryStatus(commandSurface, authority) {
|
|
240
324
|
if (commandSurface.parse_error) {
|
|
241
325
|
return 'contract_invalid';
|
|
242
326
|
}
|
|
327
|
+
if (!commandSurface.exists) {
|
|
328
|
+
return 'contract_missing';
|
|
329
|
+
}
|
|
330
|
+
if (authority === 'delegated_scoped') {
|
|
331
|
+
return 'delegated_ready';
|
|
332
|
+
}
|
|
243
333
|
return 'mustflow_ready';
|
|
244
334
|
}
|
|
245
|
-
function summarizeRepository(projectRoot, repository) {
|
|
335
|
+
function summarizeRepository(projectRoot, repository, workspace) {
|
|
246
336
|
const repositoryRoot = path.resolve(projectRoot, repository.relativePath);
|
|
247
|
-
const
|
|
337
|
+
const commandSelection = summarizeCommandSelection(projectRoot, repositoryRoot, repository, workspace);
|
|
338
|
+
const commandSurface = commandSelection.surface;
|
|
248
339
|
const issues = commandSurface.parse_error ? [commandSurface.parse_error] : [];
|
|
249
340
|
return {
|
|
250
341
|
relative_path: repository.relativePath,
|
|
251
|
-
status: repositoryStatus(commandSurface),
|
|
342
|
+
status: repositoryStatus(commandSurface, commandSelection.authority),
|
|
252
343
|
git_repository: true,
|
|
253
344
|
mustflow: repository.mustflow,
|
|
345
|
+
command_authority: commandSelection.authority,
|
|
254
346
|
agent_rules: repository.agentRules ?? null,
|
|
255
347
|
repo_map: repository.repoMap ?? null,
|
|
256
348
|
mustflow_config: repository.mustflowConfig ?? null,
|
|
@@ -276,7 +368,7 @@ function createWorkspaceStatusOutput() {
|
|
|
276
368
|
const projectRoot = resolveMustflowRoot();
|
|
277
369
|
const config = getRepoMapConfig(projectRoot);
|
|
278
370
|
const nestedRepositories = discoverNestedRepositories(projectRoot, { ...config.map, includeNested: true }, config.workspace);
|
|
279
|
-
const repositories = nestedRepositories.map((repository) => summarizeRepository(projectRoot, repository));
|
|
371
|
+
const repositories = nestedRepositories.map((repository) => summarizeRepository(projectRoot, repository, config.workspace));
|
|
280
372
|
const issues = config.workspace.enabled && config.workspace.roots.length > 0 && repositories.length === 0
|
|
281
373
|
? ['No nested git repositories were discovered under configured workspace roots.']
|
|
282
374
|
: [];
|
|
@@ -295,7 +387,7 @@ function createWorkspaceScanOutput(projectsDir) {
|
|
|
295
387
|
const projectRoot = resolveMustflowRoot();
|
|
296
388
|
const config = getRepoMapConfig(projectRoot);
|
|
297
389
|
const workspace = createAdHocWorkspaceConfig(config.workspace, projectsDir);
|
|
298
|
-
const repositories = discoverNestedRepositories(projectRoot, { ...config.map, includeNested: true }, workspace).map((repository) => summarizeRepository(projectRoot, repository));
|
|
390
|
+
const repositories = discoverNestedRepositories(projectRoot, { ...config.map, includeNested: true }, workspace).map((repository) => summarizeRepository(projectRoot, repository, workspace));
|
|
299
391
|
const issues = repositories.length === 0
|
|
300
392
|
? [t('en', 'workspace.scan.issue.noneDiscovered', { projectsDir })]
|
|
301
393
|
: [];
|
|
@@ -326,6 +418,8 @@ function workspaceConfigOutput(config) {
|
|
|
326
418
|
return {
|
|
327
419
|
enabled: config.enabled,
|
|
328
420
|
roots: config.roots,
|
|
421
|
+
authority_mode: config.authorityMode,
|
|
422
|
+
delegated_contract_count: config.delegatedContractCount,
|
|
329
423
|
max_depth: config.maxDepth,
|
|
330
424
|
max_repositories: config.maxRepositories,
|
|
331
425
|
follow_symlinks: config.followSymlinks,
|
|
@@ -353,10 +447,13 @@ function readIntentString(rawIntent, key) {
|
|
|
353
447
|
function safeRunCommand(intentName) {
|
|
354
448
|
return /^[A-Za-z0-9_-]+$/u.test(intentName) ? `mf run ${intentName}` : null;
|
|
355
449
|
}
|
|
356
|
-
function createCatalogIntent(
|
|
450
|
+
function createCatalogIntent(planningRoot, scope, repositoryPath, contract, intentName) {
|
|
357
451
|
const rawIntent = contract.intents[intentName];
|
|
358
452
|
try {
|
|
359
|
-
|
|
453
|
+
if (scope) {
|
|
454
|
+
assertScopedCommandIntentIsolation(planningRoot, scope, contract, intentName);
|
|
455
|
+
}
|
|
456
|
+
const plan = createRunPlan(planningRoot, contract, intentName);
|
|
360
457
|
return {
|
|
361
458
|
name: intentName,
|
|
362
459
|
description: readIntentString(rawIntent, 'description'),
|
|
@@ -395,43 +492,31 @@ function createCatalogIntent(repositoryRoot, repositoryPath, contract, intentNam
|
|
|
395
492
|
};
|
|
396
493
|
}
|
|
397
494
|
}
|
|
398
|
-
function createCatalogRepository(projectRoot, repository) {
|
|
495
|
+
function createCatalogRepository(projectRoot, repository, workspace) {
|
|
399
496
|
const repositoryRoot = path.resolve(projectRoot, repository.relativePath);
|
|
400
|
-
const
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
status: 'contract_missing',
|
|
405
|
-
command_contract: commandSurface,
|
|
406
|
-
intent_count: 0,
|
|
407
|
-
runnable_count: 0,
|
|
408
|
-
blocked_count: 0,
|
|
409
|
-
intents: [],
|
|
410
|
-
issues: ['Command contract is missing.'],
|
|
411
|
-
};
|
|
412
|
-
}
|
|
413
|
-
let contract;
|
|
414
|
-
try {
|
|
415
|
-
contract = readCommandContract(repositoryRoot);
|
|
416
|
-
}
|
|
417
|
-
catch (error) {
|
|
418
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
497
|
+
const commandSelection = summarizeCommandSelection(projectRoot, repositoryRoot, repository, workspace);
|
|
498
|
+
const commandSurface = commandSelection.surface;
|
|
499
|
+
if (!commandSelection.contract) {
|
|
500
|
+
const contractInvalid = commandSurface.parse_error !== null;
|
|
419
501
|
return {
|
|
420
502
|
relative_path: repository.relativePath,
|
|
421
|
-
status: 'contract_invalid',
|
|
503
|
+
status: contractInvalid ? 'contract_invalid' : 'contract_missing',
|
|
504
|
+
command_authority: commandSelection.authority,
|
|
422
505
|
command_contract: commandSurface,
|
|
423
506
|
intent_count: 0,
|
|
424
507
|
runnable_count: 0,
|
|
425
508
|
blocked_count: 0,
|
|
426
509
|
intents: [],
|
|
427
|
-
issues: [
|
|
510
|
+
issues: [commandSurface.parse_error ?? 'Command contract is missing.'],
|
|
428
511
|
};
|
|
429
512
|
}
|
|
430
|
-
const
|
|
513
|
+
const contract = commandSelection.contract;
|
|
514
|
+
const intents = getIntentNames(contract.intents).map((intentName) => createCatalogIntent(commandSelection.planningRoot, commandSelection.scope, repository.relativePath, contract, intentName));
|
|
431
515
|
const runnableCount = intents.filter((intent) => intent.runnable).length;
|
|
432
516
|
return {
|
|
433
517
|
relative_path: repository.relativePath,
|
|
434
518
|
status: 'available',
|
|
519
|
+
command_authority: commandSelection.authority,
|
|
435
520
|
command_contract: commandSurface,
|
|
436
521
|
intent_count: intents.length,
|
|
437
522
|
runnable_count: runnableCount,
|
|
@@ -443,7 +528,7 @@ function createCatalogRepository(projectRoot, repository) {
|
|
|
443
528
|
function createWorkspaceCommandCatalogOutput() {
|
|
444
529
|
const projectRoot = resolveMustflowRoot();
|
|
445
530
|
const { config, repositories: nestedRepositories } = readWorkspaceRepositories(projectRoot);
|
|
446
|
-
const repositories = nestedRepositories.map((repository) => createCatalogRepository(projectRoot, repository));
|
|
531
|
+
const repositories = nestedRepositories.map((repository) => createCatalogRepository(projectRoot, repository, config.workspace));
|
|
447
532
|
return {
|
|
448
533
|
schema_version: WORKSPACE_COMMAND_CATALOG_SCHEMA_VERSION,
|
|
449
534
|
command: 'workspace command-catalog',
|
|
@@ -462,7 +547,13 @@ function createWorkspaceCommandFragmentsOutput(projectsDir) {
|
|
|
462
547
|
const config = getRepoMapConfig(projectRoot);
|
|
463
548
|
const workspace = projectsDir ? createAdHocWorkspaceConfig(config.workspace, projectsDir) : config.workspace;
|
|
464
549
|
const nestedRepositories = discoverNestedRepositories(projectRoot, { ...config.map, includeNested: true }, workspace);
|
|
465
|
-
const
|
|
550
|
+
const repositoryLocalWorkspace = {
|
|
551
|
+
...workspace,
|
|
552
|
+
authorityMode: 'repository_local',
|
|
553
|
+
delegatedContracts: [],
|
|
554
|
+
delegatedContractCount: 0,
|
|
555
|
+
};
|
|
556
|
+
const repositories = nestedRepositories.map((repository) => summarizeRepository(projectRoot, repository, repositoryLocalWorkspace));
|
|
466
557
|
const includePaths = createCommandFragmentPaths(repositories);
|
|
467
558
|
const suggestions = repositories.map((repository) => createCommandFragmentSuggestion(repository, includePaths.get(repository.relative_path) ?? `${COMMAND_FRAGMENT_INCLUDE_PREFIX}/repository.toml`));
|
|
468
559
|
const includeEntries = suggestions.map((suggestion) => suggestion.include_entry);
|
|
@@ -487,10 +578,11 @@ function createWorkspaceCommandFragmentsOutput(projectsDir) {
|
|
|
487
578
|
next_actions: commandFragmentNextActions(repositories.length),
|
|
488
579
|
};
|
|
489
580
|
}
|
|
490
|
-
function createUnavailableVerificationRepository(repository, commandSurface, status, classification, issue) {
|
|
581
|
+
function createUnavailableVerificationRepository(repository, authority, commandSurface, status, classification, issue) {
|
|
491
582
|
return {
|
|
492
583
|
relative_path: repository.relativePath,
|
|
493
584
|
status,
|
|
585
|
+
command_authority: authority,
|
|
494
586
|
command_contract: commandSurface,
|
|
495
587
|
changed_file_count: classification ? classification.summary.fileCount : null,
|
|
496
588
|
changed_files: classification ? classification.files : [],
|
|
@@ -505,6 +597,40 @@ function createUnavailableVerificationRepository(repository, commandSurface, sta
|
|
|
505
597
|
issues: [issue],
|
|
506
598
|
};
|
|
507
599
|
}
|
|
600
|
+
function workspaceRelativeChangePath(repositoryPath, filePath) {
|
|
601
|
+
const repository = normalizeRepositoryPath(repositoryPath);
|
|
602
|
+
const file = filePath.replace(/\\/gu, '/').replace(/^\.\//u, '');
|
|
603
|
+
return `${repository}/${file}`;
|
|
604
|
+
}
|
|
605
|
+
function repositoryRelativeChangePath(repositoryPath, filePath) {
|
|
606
|
+
const prefix = `${normalizeRepositoryPath(repositoryPath)}/`;
|
|
607
|
+
return filePath.startsWith(prefix) ? filePath.slice(prefix.length) : filePath;
|
|
608
|
+
}
|
|
609
|
+
function rebaseClassificationForWorkspaceRoot(classification, repositoryPath) {
|
|
610
|
+
return {
|
|
611
|
+
source: classification.source,
|
|
612
|
+
files: classification.files.map((filePath) => workspaceRelativeChangePath(repositoryPath, filePath)),
|
|
613
|
+
classifications: classification.classifications.map((entry) => ({
|
|
614
|
+
...entry,
|
|
615
|
+
path: workspaceRelativeChangePath(repositoryPath, entry.path),
|
|
616
|
+
})),
|
|
617
|
+
summary: classification.summary,
|
|
618
|
+
};
|
|
619
|
+
}
|
|
620
|
+
function scopedPlanningContract(projectRoot, contract, scope) {
|
|
621
|
+
return {
|
|
622
|
+
...contract,
|
|
623
|
+
intents: Object.fromEntries(Object.entries(contract.intents).filter(([intentName]) => {
|
|
624
|
+
try {
|
|
625
|
+
assertScopedCommandIntentIsolation(projectRoot, scope, contract, intentName);
|
|
626
|
+
return true;
|
|
627
|
+
}
|
|
628
|
+
catch {
|
|
629
|
+
return false;
|
|
630
|
+
}
|
|
631
|
+
})),
|
|
632
|
+
};
|
|
633
|
+
}
|
|
508
634
|
function selectedIntentsForVerificationReport(repositoryPath, report) {
|
|
509
635
|
return report.schedule.entries.map((entry) => ({
|
|
510
636
|
intent: entry.intent,
|
|
@@ -516,44 +642,45 @@ function selectedIntentsForVerificationReport(repositoryPath, report) {
|
|
|
516
642
|
}
|
|
517
643
|
/**
|
|
518
644
|
* mf:anchor cli.workspace.verify-plan
|
|
519
|
-
* purpose: Build per-repository verification plans from each
|
|
520
|
-
* search: workspace verify, changed files, plan only, command contract,
|
|
521
|
-
* invariant: Workspace verification output selects
|
|
645
|
+
* purpose: Build per-repository verification plans from each repository's effective local or delegated scoped command contract.
|
|
646
|
+
* search: workspace verify, changed files, plan only, command contract, delegated repository
|
|
647
|
+
* invariant: Workspace verification output selects only repository-scoped intents and never runs their raw commands.
|
|
522
648
|
* risk: config, state
|
|
523
649
|
*/
|
|
524
|
-
function createVerificationRepository(projectRoot, repository) {
|
|
650
|
+
function createVerificationRepository(projectRoot, repository, workspace) {
|
|
525
651
|
const repositoryRoot = path.resolve(projectRoot, repository.relativePath);
|
|
526
|
-
const
|
|
652
|
+
const commandSelection = summarizeCommandSelection(projectRoot, repositoryRoot, repository, workspace);
|
|
653
|
+
const commandSurface = commandSelection.surface;
|
|
527
654
|
let classification = null;
|
|
528
655
|
try {
|
|
529
656
|
classification = createClassifyOutput(repositoryRoot, 'changed', []);
|
|
530
657
|
}
|
|
531
658
|
catch (error) {
|
|
532
659
|
const message = error instanceof Error ? error.message : String(error);
|
|
533
|
-
return createUnavailableVerificationRepository(repository, commandSurface, 'git_unavailable', null, message);
|
|
534
|
-
}
|
|
535
|
-
if (!
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
contract
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
}
|
|
660
|
+
return createUnavailableVerificationRepository(repository, commandSelection.authority, commandSurface, 'git_unavailable', null, message);
|
|
661
|
+
}
|
|
662
|
+
if (!commandSelection.contract) {
|
|
663
|
+
const contractInvalid = commandSurface.parse_error !== null;
|
|
664
|
+
return createUnavailableVerificationRepository(repository, commandSelection.authority, commandSurface, contractInvalid ? 'contract_invalid' : 'contract_missing', classification, commandSurface.parse_error ?? 'Command contract is missing.');
|
|
665
|
+
}
|
|
666
|
+
const contract = commandSelection.scope
|
|
667
|
+
? scopedPlanningContract(projectRoot, commandSelection.contract, commandSelection.scope)
|
|
668
|
+
: commandSelection.contract;
|
|
669
|
+
const planningClassification = commandSelection.scope
|
|
670
|
+
? rebaseClassificationForWorkspaceRoot(classification, repository.relativePath)
|
|
671
|
+
: classification;
|
|
546
672
|
let report;
|
|
547
673
|
try {
|
|
548
|
-
report = createChangeVerificationReport(
|
|
674
|
+
report = createChangeVerificationReport(planningClassification, contract, commandSelection.planningRoot);
|
|
549
675
|
}
|
|
550
676
|
catch (error) {
|
|
551
677
|
const message = error instanceof Error ? error.message : String(error);
|
|
552
|
-
return createUnavailableVerificationRepository(repository, commandSurface, 'plan_unavailable', classification, message);
|
|
678
|
+
return createUnavailableVerificationRepository(repository, commandSelection.authority, commandSurface, 'plan_unavailable', classification, message);
|
|
553
679
|
}
|
|
554
680
|
return {
|
|
555
681
|
relative_path: repository.relativePath,
|
|
556
682
|
status: 'available',
|
|
683
|
+
command_authority: commandSelection.authority,
|
|
557
684
|
command_contract: commandSurface,
|
|
558
685
|
changed_file_count: classification.summary.fileCount,
|
|
559
686
|
changed_files: classification.files,
|
|
@@ -566,7 +693,9 @@ function createVerificationRepository(projectRoot, repository) {
|
|
|
566
693
|
selected_intents: selectedIntentsForVerificationReport(repository.relativePath, report),
|
|
567
694
|
gaps: report.gaps.map((gap) => ({
|
|
568
695
|
reason: gap.reason,
|
|
569
|
-
files:
|
|
696
|
+
files: commandSelection.scope
|
|
697
|
+
? gap.files.map((filePath) => repositoryRelativeChangePath(repository.relativePath, filePath))
|
|
698
|
+
: gap.files,
|
|
570
699
|
surfaces: gap.surfaces,
|
|
571
700
|
detail: gap.detail,
|
|
572
701
|
})),
|
|
@@ -576,7 +705,7 @@ function createVerificationRepository(projectRoot, repository) {
|
|
|
576
705
|
function createWorkspaceVerificationPlanOutput() {
|
|
577
706
|
const projectRoot = resolveMustflowRoot();
|
|
578
707
|
const { config, repositories: nestedRepositories } = readWorkspaceRepositories(projectRoot);
|
|
579
|
-
const repositories = nestedRepositories.map((repository) => createVerificationRepository(projectRoot, repository));
|
|
708
|
+
const repositories = nestedRepositories.map((repository) => createVerificationRepository(projectRoot, repository, config.workspace));
|
|
580
709
|
return {
|
|
581
710
|
schema_version: WORKSPACE_VERIFICATION_PLAN_SCHEMA_VERSION,
|
|
582
711
|
command: 'workspace verify',
|
|
@@ -607,6 +736,7 @@ function renderWorkspaceScan(output) {
|
|
|
607
736
|
for (const repository of output.repositories) {
|
|
608
737
|
lines.push(`- ${repository.relative_path} (${repository.status})`);
|
|
609
738
|
lines.push(` mustflow: ${repository.mustflow ? 'yes' : 'no'}`);
|
|
739
|
+
lines.push(` command authority: ${repository.command_authority ?? 'none'}`);
|
|
610
740
|
lines.push(` command contract: ${repository.command_contract.path ?? 'missing'}`);
|
|
611
741
|
}
|
|
612
742
|
lines.push('', 'Next actions:');
|
|
@@ -631,6 +761,7 @@ function renderWorkspaceStatus(output) {
|
|
|
631
761
|
for (const repository of output.repositories) {
|
|
632
762
|
lines.push(`- ${repository.relative_path} (${repository.status})`);
|
|
633
763
|
lines.push(` mustflow: ${repository.mustflow ? 'yes' : 'no'}`);
|
|
764
|
+
lines.push(` command authority: ${repository.command_authority ?? 'none'}`);
|
|
634
765
|
lines.push(` command contract: ${repository.command_contract.path ?? 'missing'}`);
|
|
635
766
|
if (repository.command_contract.runnable_count !== null) {
|
|
636
767
|
lines.push(` runnable intents: ${repository.command_contract.runnable_count}`);
|
|
@@ -657,6 +788,8 @@ function renderWorkspaceCommandCatalog(output) {
|
|
|
657
788
|
}
|
|
658
789
|
for (const repository of output.repositories) {
|
|
659
790
|
lines.push(`- ${repository.relative_path} (${repository.status})`);
|
|
791
|
+
lines.push(` command authority: ${repository.command_authority ?? 'none'}`);
|
|
792
|
+
lines.push(` command contract: ${repository.command_contract.path ?? 'missing'}`);
|
|
660
793
|
if (repository.intents.length === 0) {
|
|
661
794
|
lines.push(` intents: none`);
|
|
662
795
|
}
|
|
@@ -718,6 +851,8 @@ function renderWorkspaceVerificationPlan(output) {
|
|
|
718
851
|
}
|
|
719
852
|
for (const repository of output.repositories) {
|
|
720
853
|
lines.push(`- ${repository.relative_path} (${repository.status})`);
|
|
854
|
+
lines.push(` command authority: ${repository.command_authority ?? 'none'}`);
|
|
855
|
+
lines.push(` command contract: ${repository.command_contract.path ?? 'missing'}`);
|
|
721
856
|
lines.push(` changed files: ${repository.changed_file_count ?? 'unknown'}`);
|
|
722
857
|
lines.push(` selected intents: ${repository.selected_intent_count}`);
|
|
723
858
|
for (const selected of repository.selected_intents) {
|