mustflow 2.28.0 → 2.29.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/dist/cli/commands/context.js +1 -0
- package/dist/cli/commands/help.js +55 -1
- package/dist/cli/commands/tech.js +346 -0
- 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/index.js +1 -0
- package/dist/cli/lib/agent-context.js +16 -0
- package/dist/cli/lib/command-registry.js +6 -0
- package/dist/cli/lib/validation/index.js +11 -0
- package/dist/cli/lib/validation/primitives.js +5 -0
- package/dist/core/technology-preferences.js +189 -0
- package/package.json +1 -1
- package/schemas/context-report.schema.json +61 -0
- package/templates/default/common/.mustflow/config/mustflow.toml +8 -0
- package/templates/default/common/.mustflow/config/technology.toml +20 -0
- package/templates/default/i18n.toml +78 -12
- package/templates/default/locales/en/.mustflow/skills/INDEX.md +33 -1
- package/templates/default/locales/en/.mustflow/skills/code-review/SKILL.md +15 -5
- package/templates/default/locales/en/.mustflow/skills/codebase-orientation/SKILL.md +15 -8
- package/templates/default/locales/en/.mustflow/skills/command-intent-mapping-gate/SKILL.md +124 -0
- package/templates/default/locales/en/.mustflow/skills/completion-evidence-gate/SKILL.md +178 -0
- package/templates/default/locales/en/.mustflow/skills/contract-sync-check/SKILL.md +9 -3
- package/templates/default/locales/en/.mustflow/skills/dependency-reality-check/SKILL.md +6 -3
- package/templates/default/locales/en/.mustflow/skills/evidence-stall-breaker/SKILL.md +166 -0
- package/templates/default/locales/en/.mustflow/skills/external-prompt-injection-defense/SKILL.md +8 -6
- package/templates/default/locales/en/.mustflow/skills/provenance-license-gate/SKILL.md +131 -0
- package/templates/default/locales/en/.mustflow/skills/public-json-contract-change/SKILL.md +133 -0
- package/templates/default/locales/en/.mustflow/skills/restricted-handoff-resume/SKILL.md +122 -0
- package/templates/default/locales/en/.mustflow/skills/routes.toml +60 -0
- package/templates/default/locales/en/.mustflow/skills/runtime-target-selection/SKILL.md +203 -0
- package/templates/default/locales/en/.mustflow/skills/rust-code-change/SKILL.md +55 -18
- package/templates/default/locales/en/.mustflow/skills/secret-exposure-response/SKILL.md +125 -0
- package/templates/default/locales/en/.mustflow/skills/security-privacy-review/SKILL.md +10 -1
- package/templates/default/locales/en/.mustflow/skills/skill-authoring/SKILL.md +9 -5
- package/templates/default/locales/en/.mustflow/skills/source-freshness-check/SKILL.md +3 -2
- package/templates/default/locales/en/.mustflow/skills/structure-first-engineering/SKILL.md +205 -0
- package/templates/default/locales/en/.mustflow/skills/template-install-surface-sync/SKILL.md +131 -0
- package/templates/default/locales/en/AGENTS.md +8 -7
- package/templates/default/locales/ko/AGENTS.md +8 -7
- package/templates/default/manifest.toml +66 -1
|
@@ -77,6 +77,7 @@ export async function runContext(args, reporter, lang = 'en') {
|
|
|
77
77
|
reporter.stdout(`${t(lang, 'label.mustflowRoot')}: ${context.mustflow_root}`);
|
|
78
78
|
reporter.stdout(`${t(lang, 'label.commandContract')}: ${context.command_contract.exists ? t(lang, 'value.present') : t(lang, 'value.missing')}`);
|
|
79
79
|
reporter.stdout(`${t(lang, 'label.runnableIntents')}: ${context.command_contract.runnable_intents.length}`);
|
|
80
|
+
reporter.stdout(`Technology preferences: ${context.technology_preferences.exists ? t(lang, 'value.present') : t(lang, 'value.missing')} (${context.technology_preferences.count})`);
|
|
80
81
|
reporter.stdout(`${t(lang, 'label.latestRun')}: ${context.latest_run.exists ? t(lang, 'value.present') : t(lang, 'value.missing')}`);
|
|
81
82
|
return 0;
|
|
82
83
|
}
|
|
@@ -4,6 +4,7 @@ import { t } from '../lib/i18n.js';
|
|
|
4
4
|
import { readMustflowTextFileIfExists } from '../lib/mustflow-read.js';
|
|
5
5
|
import { resolveMustflowRoot } from '../lib/project-root.js';
|
|
6
6
|
import { readMustflowTomlFile } from '../lib/toml.js';
|
|
7
|
+
import { normalizeTechnologyPreferencesTable, TECHNOLOGY_CONFIG_RELATIVE_PATH, } from '../../core/technology-preferences.js';
|
|
7
8
|
function readTextIfExists(projectRoot, relativePath) {
|
|
8
9
|
return readMustflowTextFileIfExists(projectRoot, relativePath) ?? undefined;
|
|
9
10
|
}
|
|
@@ -58,6 +59,51 @@ function renderPreferencesHelp(projectRoot, lang) {
|
|
|
58
59
|
}
|
|
59
60
|
return lines.join('\n').trimEnd();
|
|
60
61
|
}
|
|
62
|
+
function renderTechnologyPreference(preference) {
|
|
63
|
+
const details = [
|
|
64
|
+
preference.scope.length > 0 ? `scope: ${preference.scope.join(', ')}` : null,
|
|
65
|
+
preference.ecosystem ? `ecosystem: ${preference.ecosystem}` : null,
|
|
66
|
+
preference.packages.length > 0 ? `packages: ${preference.packages.join(', ')}` : null,
|
|
67
|
+
].filter((value) => value !== null);
|
|
68
|
+
const lines = [`- [${preference.status}] ${preference.kind} ${preference.name} (${preference.id})`];
|
|
69
|
+
if (details.length > 0) {
|
|
70
|
+
lines.push(` ${details.join('; ')}`);
|
|
71
|
+
}
|
|
72
|
+
if (preference.rationale) {
|
|
73
|
+
lines.push(` why: ${preference.rationale}`);
|
|
74
|
+
}
|
|
75
|
+
for (const constraint of preference.constraints) {
|
|
76
|
+
lines.push(` constraint: ${constraint}`);
|
|
77
|
+
}
|
|
78
|
+
return lines;
|
|
79
|
+
}
|
|
80
|
+
function renderTechnologyHelp(projectRoot, lang) {
|
|
81
|
+
const technology = readTomlIfExists(projectRoot, TECHNOLOGY_CONFIG_RELATIVE_PATH);
|
|
82
|
+
if (!technology) {
|
|
83
|
+
return `Technology Preferences\n\n${renderMissing(TECHNOLOGY_CONFIG_RELATIVE_PATH, lang)}`;
|
|
84
|
+
}
|
|
85
|
+
const file = normalizeTechnologyPreferencesTable(technology, true);
|
|
86
|
+
const lines = [
|
|
87
|
+
'Technology Preferences',
|
|
88
|
+
'',
|
|
89
|
+
`Path: ${file.path}`,
|
|
90
|
+
`Authority: ${file.authority} (preferences only)`,
|
|
91
|
+
'Guidance:',
|
|
92
|
+
...file.guidance.map((item) => `- ${item}`),
|
|
93
|
+
'',
|
|
94
|
+
`Preferences: ${file.preferences.length}`,
|
|
95
|
+
];
|
|
96
|
+
if (file.preferences.length === 0) {
|
|
97
|
+
lines.push('No technology preferences recorded.');
|
|
98
|
+
}
|
|
99
|
+
for (const preference of file.preferences) {
|
|
100
|
+
lines.push(...renderTechnologyPreference(preference));
|
|
101
|
+
}
|
|
102
|
+
if (file.issues.length > 0) {
|
|
103
|
+
lines.push('', 'Issues:', ...file.issues.map((issue) => `- ${issue}`));
|
|
104
|
+
}
|
|
105
|
+
return lines.join('\n');
|
|
106
|
+
}
|
|
61
107
|
function renderPreferenceSection(lines, sectionName, section) {
|
|
62
108
|
const nestedSections = [];
|
|
63
109
|
const scalarLines = [];
|
|
@@ -97,9 +143,13 @@ export function getHelpHelp(lang = 'en') {
|
|
|
97
143
|
label: 'preferences',
|
|
98
144
|
description: t(lang, 'help.topic.preferences'),
|
|
99
145
|
},
|
|
146
|
+
{
|
|
147
|
+
label: 'technology',
|
|
148
|
+
description: 'Show framework, library, runtime, and tool preferences',
|
|
149
|
+
},
|
|
100
150
|
],
|
|
101
151
|
options: [{ label: '-h, --help', description: t(lang, 'cli.option.help') }],
|
|
102
|
-
examples: ['mf help workflow', 'mf help skills', 'mf help preferences'],
|
|
152
|
+
examples: ['mf help workflow', 'mf help skills', 'mf help preferences', 'mf help technology'],
|
|
103
153
|
exitCodes: [
|
|
104
154
|
{
|
|
105
155
|
label: '0',
|
|
@@ -139,6 +189,10 @@ export function runHelp(args, reporter, lang = 'en') {
|
|
|
139
189
|
reporter.stdout(renderPreferencesHelp(projectRoot, lang));
|
|
140
190
|
return 0;
|
|
141
191
|
}
|
|
192
|
+
if (topic === 'technology') {
|
|
193
|
+
reporter.stdout(renderTechnologyHelp(projectRoot, lang));
|
|
194
|
+
return 0;
|
|
195
|
+
}
|
|
142
196
|
reporter.stderr(renderCliError(t(lang, 'help.error.unknownTopic', { topic }), 'mf help --help', lang));
|
|
143
197
|
return 1;
|
|
144
198
|
}
|
|
@@ -0,0 +1,346 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { printUsageError, renderCliError, renderHelp } from '../lib/cli-output.js';
|
|
3
|
+
import { writeUtf8FileInsideWithoutSymlinks } from '../lib/filesystem.js';
|
|
4
|
+
import { isRecord } from '../lib/command-contract.js';
|
|
5
|
+
import { t } from '../lib/i18n.js';
|
|
6
|
+
import { formatCliOptionParseError, getParsedCliStringOption, hasCliOptionToken, hasParsedCliOption, parseCliOptions, } from '../lib/option-parser.js';
|
|
7
|
+
import { resolveMustflowRoot } from '../lib/project-root.js';
|
|
8
|
+
import { readMustflowTomlFile } from '../lib/toml.js';
|
|
9
|
+
import { createTechnologyPreferenceId, normalizeTechnologyKey, normalizeTechnologyName, normalizeTechnologyPreferencesTable, serializeTechnologyPreferences, technologyConfigExists, technologyPreferenceMatches, TECHNOLOGY_AUTHORITY, TECHNOLOGY_CONFIG_RELATIVE_PATH, TECHNOLOGY_GUIDANCE, TECHNOLOGY_KINDS, TECHNOLOGY_STATUSES, } from '../../core/technology-preferences.js';
|
|
10
|
+
const TECH_OPTIONS = [
|
|
11
|
+
{ name: '--json', kind: 'boolean' },
|
|
12
|
+
{ name: '--scope', kind: 'string' },
|
|
13
|
+
{ name: '--kind', kind: 'string' },
|
|
14
|
+
{ name: '--status', kind: 'string' },
|
|
15
|
+
{ name: '--ecosystem', kind: 'string' },
|
|
16
|
+
{ name: '--package', kind: 'string' },
|
|
17
|
+
{ name: '--why', kind: 'string' },
|
|
18
|
+
{ name: '--constraint', kind: 'string' },
|
|
19
|
+
];
|
|
20
|
+
function getRepeatedStringOptions(occurrences, name) {
|
|
21
|
+
return occurrences
|
|
22
|
+
.filter((occurrence) => occurrence.name === name && typeof occurrence.value === 'string')
|
|
23
|
+
.map((occurrence) => String(occurrence.value).trim())
|
|
24
|
+
.filter((value) => value.length > 0);
|
|
25
|
+
}
|
|
26
|
+
function isTechnologyKind(value) {
|
|
27
|
+
return value !== null && TECHNOLOGY_KINDS.includes(value);
|
|
28
|
+
}
|
|
29
|
+
function isTechnologyStatus(value) {
|
|
30
|
+
return value !== null && TECHNOLOGY_STATUSES.includes(value);
|
|
31
|
+
}
|
|
32
|
+
export function getTechHelp(lang = 'en') {
|
|
33
|
+
return renderHelp({
|
|
34
|
+
usage: 'mf tech <list|add|remove|suggest> [options]',
|
|
35
|
+
summary: 'Manage low-authority technology preferences for agents.',
|
|
36
|
+
options: [
|
|
37
|
+
{ label: '--json', description: t(lang, 'cli.option.json') },
|
|
38
|
+
{ label: '--scope <scope>', description: 'Filter or tag a project area such as frontend, backend, ui, data, or cli' },
|
|
39
|
+
{ label: '--kind <kind>', description: `Technology kind: ${TECHNOLOGY_KINDS.join(', ')}` },
|
|
40
|
+
{ label: '--status <status>', description: `Preference status: ${TECHNOLOGY_STATUSES.join(', ')}` },
|
|
41
|
+
{ label: '--ecosystem <ecosystem>', description: 'Package ecosystem or platform, such as npm, cargo, pip, go, or deno' },
|
|
42
|
+
{ label: '--package <package>', description: 'Package name to associate with the preference. Repeatable.' },
|
|
43
|
+
{ label: '--why <text>', description: 'Short rationale for the preference' },
|
|
44
|
+
{ label: '--constraint <text>', description: 'Guardrail agents must keep in mind. Repeatable.' },
|
|
45
|
+
{ label: '-h, --help', description: t(lang, 'cli.option.help') },
|
|
46
|
+
],
|
|
47
|
+
examples: [
|
|
48
|
+
'mf tech list',
|
|
49
|
+
'mf tech add framework nextjs --scope frontend --ecosystem npm --package next --package react --why "Preferred React app framework"',
|
|
50
|
+
'mf tech add language rust --scope backend --status preferred --why "Use for correctness-critical engines"',
|
|
51
|
+
'mf tech add library jquery --scope frontend --status avoid --why "Avoid new usage"',
|
|
52
|
+
'mf tech suggest --scope frontend',
|
|
53
|
+
'mf tech remove framework.frontend.nextjs',
|
|
54
|
+
],
|
|
55
|
+
exitCodes: [
|
|
56
|
+
{ label: '0', description: 'Technology preferences were inspected or updated' },
|
|
57
|
+
{ label: '1', description: t(lang, 'cli.common.invalidInput') },
|
|
58
|
+
],
|
|
59
|
+
}, lang);
|
|
60
|
+
}
|
|
61
|
+
function parseTechOptions(args, lang) {
|
|
62
|
+
const [actionToken, ...rest] = args;
|
|
63
|
+
const action = actionToken;
|
|
64
|
+
if (action !== 'list' && action !== 'add' && action !== 'remove' && action !== 'suggest') {
|
|
65
|
+
return {
|
|
66
|
+
action: 'list',
|
|
67
|
+
positionals: [],
|
|
68
|
+
json: false,
|
|
69
|
+
scope: [],
|
|
70
|
+
kind: null,
|
|
71
|
+
status: null,
|
|
72
|
+
ecosystem: null,
|
|
73
|
+
packages: [],
|
|
74
|
+
why: null,
|
|
75
|
+
constraints: [],
|
|
76
|
+
error: actionToken ? `Unknown tech action: ${actionToken}` : 'Specify a tech action: list, add, remove, or suggest',
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
const parsed = parseCliOptions(rest, TECH_OPTIONS, { allowPositionals: true });
|
|
80
|
+
if (parsed.error) {
|
|
81
|
+
return {
|
|
82
|
+
action,
|
|
83
|
+
positionals: parsed.positionals,
|
|
84
|
+
json: hasParsedCliOption(parsed, '--json'),
|
|
85
|
+
scope: [],
|
|
86
|
+
kind: null,
|
|
87
|
+
status: null,
|
|
88
|
+
ecosystem: null,
|
|
89
|
+
packages: [],
|
|
90
|
+
why: null,
|
|
91
|
+
constraints: [],
|
|
92
|
+
error: formatCliOptionParseError(parsed.error, lang),
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
const kind = getParsedCliStringOption(parsed, '--kind');
|
|
96
|
+
const status = getParsedCliStringOption(parsed, '--status');
|
|
97
|
+
if (kind !== null && !isTechnologyKind(kind)) {
|
|
98
|
+
return invalidParsed(action, parsed.positionals, hasParsedCliOption(parsed, '--json'), `Unsupported technology kind: ${kind}`);
|
|
99
|
+
}
|
|
100
|
+
if (status !== null && !isTechnologyStatus(status)) {
|
|
101
|
+
return invalidParsed(action, parsed.positionals, hasParsedCliOption(parsed, '--json'), `Unsupported technology status: ${status}`);
|
|
102
|
+
}
|
|
103
|
+
return {
|
|
104
|
+
action,
|
|
105
|
+
positionals: parsed.positionals,
|
|
106
|
+
json: hasParsedCliOption(parsed, '--json'),
|
|
107
|
+
scope: getRepeatedStringOptions(parsed.occurrences, '--scope'),
|
|
108
|
+
kind,
|
|
109
|
+
status,
|
|
110
|
+
ecosystem: getParsedCliStringOption(parsed, '--ecosystem'),
|
|
111
|
+
packages: getRepeatedStringOptions(parsed.occurrences, '--package'),
|
|
112
|
+
why: getParsedCliStringOption(parsed, '--why'),
|
|
113
|
+
constraints: getRepeatedStringOptions(parsed.occurrences, '--constraint'),
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
function invalidParsed(action, positionals, json, error) {
|
|
117
|
+
return {
|
|
118
|
+
action,
|
|
119
|
+
positionals,
|
|
120
|
+
json,
|
|
121
|
+
scope: [],
|
|
122
|
+
kind: null,
|
|
123
|
+
status: null,
|
|
124
|
+
ecosystem: null,
|
|
125
|
+
packages: [],
|
|
126
|
+
why: null,
|
|
127
|
+
constraints: [],
|
|
128
|
+
error,
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
function readTechnologyPreferences(projectRoot) {
|
|
132
|
+
if (!technologyConfigExists(projectRoot)) {
|
|
133
|
+
return normalizeTechnologyPreferencesTable(undefined, false);
|
|
134
|
+
}
|
|
135
|
+
const parsed = readMustflowTomlFile(projectRoot, TECHNOLOGY_CONFIG_RELATIVE_PATH);
|
|
136
|
+
if (!isRecord(parsed)) {
|
|
137
|
+
return {
|
|
138
|
+
...normalizeTechnologyPreferencesTable(undefined, true),
|
|
139
|
+
issues: [`${TECHNOLOGY_CONFIG_RELATIVE_PATH} must contain a TOML table`],
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
return normalizeTechnologyPreferencesTable(parsed, true);
|
|
143
|
+
}
|
|
144
|
+
function writeTechnologyPreferences(projectRoot, preferences) {
|
|
145
|
+
const targetPath = path.join(projectRoot, ...TECHNOLOGY_CONFIG_RELATIVE_PATH.split('/'));
|
|
146
|
+
writeUtf8FileInsideWithoutSymlinks(projectRoot, targetPath, serializeTechnologyPreferences(preferences));
|
|
147
|
+
}
|
|
148
|
+
function renderPreference(preference) {
|
|
149
|
+
const details = [
|
|
150
|
+
preference.scope.length > 0 ? `scope: ${preference.scope.join(', ')}` : null,
|
|
151
|
+
preference.ecosystem ? `ecosystem: ${preference.ecosystem}` : null,
|
|
152
|
+
preference.packages.length > 0 ? `packages: ${preference.packages.join(', ')}` : null,
|
|
153
|
+
].filter((value) => value !== null);
|
|
154
|
+
const lines = [`- [${preference.status}] ${preference.kind} ${preference.name} (${preference.id})`];
|
|
155
|
+
if (details.length > 0) {
|
|
156
|
+
lines.push(` ${details.join('; ')}`);
|
|
157
|
+
}
|
|
158
|
+
if (preference.rationale) {
|
|
159
|
+
lines.push(` why: ${preference.rationale}`);
|
|
160
|
+
}
|
|
161
|
+
for (const constraint of preference.constraints) {
|
|
162
|
+
lines.push(` constraint: ${constraint}`);
|
|
163
|
+
}
|
|
164
|
+
return lines;
|
|
165
|
+
}
|
|
166
|
+
function renderList(file, preferences) {
|
|
167
|
+
const lines = [
|
|
168
|
+
'mustflow technology preferences',
|
|
169
|
+
`Path: ${file.path}`,
|
|
170
|
+
`Authority: ${file.authority} (preferences only)`,
|
|
171
|
+
`Preferences: ${preferences.length}`,
|
|
172
|
+
];
|
|
173
|
+
if (file.issues.length > 0) {
|
|
174
|
+
lines.push('Issues:', ...file.issues.map((issue) => `- ${issue}`));
|
|
175
|
+
}
|
|
176
|
+
if (preferences.length === 0) {
|
|
177
|
+
lines.push('No technology preferences recorded.');
|
|
178
|
+
return lines.join('\n');
|
|
179
|
+
}
|
|
180
|
+
for (const preference of preferences) {
|
|
181
|
+
lines.push(...renderPreference(preference));
|
|
182
|
+
}
|
|
183
|
+
return lines.join('\n');
|
|
184
|
+
}
|
|
185
|
+
function filterPreferences(file, options) {
|
|
186
|
+
return file.preferences.filter((preference) => technologyPreferenceMatches(preference, {
|
|
187
|
+
scope: options.scope[0] ?? null,
|
|
188
|
+
kind: options.kind,
|
|
189
|
+
status: options.status,
|
|
190
|
+
}));
|
|
191
|
+
}
|
|
192
|
+
function runList(projectRoot, options, reporter) {
|
|
193
|
+
const file = readTechnologyPreferences(projectRoot);
|
|
194
|
+
const preferences = filterPreferences(file, options);
|
|
195
|
+
if (options.json) {
|
|
196
|
+
reporter.stdout(JSON.stringify({ ...file, preferences }, null, 2));
|
|
197
|
+
return file.issues.length === 0 ? 0 : 1;
|
|
198
|
+
}
|
|
199
|
+
reporter.stdout(renderList(file, preferences));
|
|
200
|
+
return file.issues.length === 0 ? 0 : 1;
|
|
201
|
+
}
|
|
202
|
+
function runSuggest(projectRoot, options, reporter) {
|
|
203
|
+
const file = readTechnologyPreferences(projectRoot);
|
|
204
|
+
const filtered = filterPreferences(file, {
|
|
205
|
+
...options,
|
|
206
|
+
status: null,
|
|
207
|
+
});
|
|
208
|
+
const preferred = filtered.filter((preference) => preference.status === 'preferred' || preference.status === 'allowed');
|
|
209
|
+
const avoid = filtered.filter((preference) => preference.status === 'avoid');
|
|
210
|
+
const payload = {
|
|
211
|
+
path: file.path,
|
|
212
|
+
authority: TECHNOLOGY_AUTHORITY,
|
|
213
|
+
guidance: TECHNOLOGY_GUIDANCE,
|
|
214
|
+
preferred,
|
|
215
|
+
avoid,
|
|
216
|
+
issues: file.issues,
|
|
217
|
+
};
|
|
218
|
+
if (options.json) {
|
|
219
|
+
reporter.stdout(JSON.stringify(payload, null, 2));
|
|
220
|
+
return file.issues.length === 0 ? 0 : 1;
|
|
221
|
+
}
|
|
222
|
+
const lines = ['mustflow technology suggestions', `Path: ${file.path}`, `Authority: ${TECHNOLOGY_AUTHORITY}`];
|
|
223
|
+
lines.push('Guidance:', ...TECHNOLOGY_GUIDANCE.map((item) => `- ${item}`));
|
|
224
|
+
lines.push('Preferred or allowed:');
|
|
225
|
+
lines.push(...(preferred.length === 0 ? ['- none'] : preferred.flatMap(renderPreference)));
|
|
226
|
+
lines.push('Avoid:');
|
|
227
|
+
lines.push(...(avoid.length === 0 ? ['- none'] : avoid.flatMap(renderPreference)));
|
|
228
|
+
if (file.issues.length > 0) {
|
|
229
|
+
lines.push('Issues:', ...file.issues.map((issue) => `- ${issue}`));
|
|
230
|
+
}
|
|
231
|
+
reporter.stdout(lines.join('\n'));
|
|
232
|
+
return file.issues.length === 0 ? 0 : 1;
|
|
233
|
+
}
|
|
234
|
+
function findExistingIndex(preferences, candidate) {
|
|
235
|
+
return preferences.findIndex((preference) => {
|
|
236
|
+
if (preference.id === candidate.id) {
|
|
237
|
+
return true;
|
|
238
|
+
}
|
|
239
|
+
return preference.kind === candidate.kind && normalizeTechnologyKey(preference.name) === normalizeTechnologyKey(candidate.name);
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
function runAdd(projectRoot, options, reporter) {
|
|
243
|
+
const [kindToken, nameToken] = options.positionals;
|
|
244
|
+
if (!isTechnologyKind(kindToken ?? null)) {
|
|
245
|
+
reporter.stderr(renderCliError('Missing or unsupported technology kind', 'mf tech --help'));
|
|
246
|
+
return 1;
|
|
247
|
+
}
|
|
248
|
+
if (!nameToken) {
|
|
249
|
+
reporter.stderr(renderCliError('Missing technology name', 'mf tech --help'));
|
|
250
|
+
return 1;
|
|
251
|
+
}
|
|
252
|
+
const kind = kindToken;
|
|
253
|
+
if (options.status !== null && !isTechnologyStatus(options.status)) {
|
|
254
|
+
reporter.stderr(renderCliError(`Unsupported technology status: ${options.status}`, 'mf tech --help'));
|
|
255
|
+
return 1;
|
|
256
|
+
}
|
|
257
|
+
const file = readTechnologyPreferences(projectRoot);
|
|
258
|
+
if (file.issues.length > 0) {
|
|
259
|
+
reporter.stderr(renderCliError(`Cannot update invalid ${TECHNOLOGY_CONFIG_RELATIVE_PATH}`, 'mf tech list'));
|
|
260
|
+
return 1;
|
|
261
|
+
}
|
|
262
|
+
const status = options.status ?? 'preferred';
|
|
263
|
+
const scope = options.scope.length > 0 ? options.scope : [];
|
|
264
|
+
const name = normalizeTechnologyName(nameToken);
|
|
265
|
+
const candidate = {
|
|
266
|
+
id: createTechnologyPreferenceId(kind, name, scope),
|
|
267
|
+
kind,
|
|
268
|
+
name,
|
|
269
|
+
status,
|
|
270
|
+
authority: TECHNOLOGY_AUTHORITY,
|
|
271
|
+
scope,
|
|
272
|
+
ecosystem: options.ecosystem,
|
|
273
|
+
packages: options.packages,
|
|
274
|
+
rationale: options.why,
|
|
275
|
+
constraints: options.constraints,
|
|
276
|
+
};
|
|
277
|
+
const preferences = [...file.preferences];
|
|
278
|
+
const existingIndex = findExistingIndex(preferences, candidate);
|
|
279
|
+
const action = existingIndex === -1 ? 'created' : 'updated';
|
|
280
|
+
if (existingIndex === -1) {
|
|
281
|
+
preferences.push(candidate);
|
|
282
|
+
}
|
|
283
|
+
else {
|
|
284
|
+
preferences[existingIndex] = candidate;
|
|
285
|
+
}
|
|
286
|
+
writeTechnologyPreferences(projectRoot, preferences);
|
|
287
|
+
if (options.json) {
|
|
288
|
+
reporter.stdout(JSON.stringify({ action, preference: candidate, path: TECHNOLOGY_CONFIG_RELATIVE_PATH }, null, 2));
|
|
289
|
+
return 0;
|
|
290
|
+
}
|
|
291
|
+
reporter.stdout(`${action === 'created' ? 'Created' : 'Updated'} ${candidate.id} in ${TECHNOLOGY_CONFIG_RELATIVE_PATH}`);
|
|
292
|
+
return 0;
|
|
293
|
+
}
|
|
294
|
+
function runRemove(projectRoot, options, reporter) {
|
|
295
|
+
const [target] = options.positionals;
|
|
296
|
+
if (!target) {
|
|
297
|
+
reporter.stderr(renderCliError('Missing technology id or name', 'mf tech --help'));
|
|
298
|
+
return 1;
|
|
299
|
+
}
|
|
300
|
+
const file = readTechnologyPreferences(projectRoot);
|
|
301
|
+
if (file.issues.length > 0) {
|
|
302
|
+
reporter.stderr(renderCliError(`Cannot update invalid ${TECHNOLOGY_CONFIG_RELATIVE_PATH}`, 'mf tech list'));
|
|
303
|
+
return 1;
|
|
304
|
+
}
|
|
305
|
+
const key = normalizeTechnologyKey(target);
|
|
306
|
+
const matches = file.preferences.filter((preference) => preference.id === target || normalizeTechnologyKey(preference.name) === key);
|
|
307
|
+
if (matches.length === 0) {
|
|
308
|
+
reporter.stderr(renderCliError(`No technology preference matched ${target}`, 'mf tech list'));
|
|
309
|
+
return 1;
|
|
310
|
+
}
|
|
311
|
+
if (matches.length > 1) {
|
|
312
|
+
reporter.stderr(renderCliError(`Multiple technology preferences matched ${target}; remove by id`, 'mf tech list'));
|
|
313
|
+
return 1;
|
|
314
|
+
}
|
|
315
|
+
const removed = matches[0];
|
|
316
|
+
const preferences = file.preferences.filter((preference) => preference.id !== removed.id);
|
|
317
|
+
writeTechnologyPreferences(projectRoot, preferences);
|
|
318
|
+
if (options.json) {
|
|
319
|
+
reporter.stdout(JSON.stringify({ action: 'removed', preference: removed, path: TECHNOLOGY_CONFIG_RELATIVE_PATH }, null, 2));
|
|
320
|
+
return 0;
|
|
321
|
+
}
|
|
322
|
+
reporter.stdout(`Removed ${removed.id} from ${TECHNOLOGY_CONFIG_RELATIVE_PATH}`);
|
|
323
|
+
return 0;
|
|
324
|
+
}
|
|
325
|
+
export function runTech(args, reporter, lang = 'en') {
|
|
326
|
+
if (hasCliOptionToken(args, '--help', ['-h'])) {
|
|
327
|
+
reporter.stdout(getTechHelp(lang));
|
|
328
|
+
return 0;
|
|
329
|
+
}
|
|
330
|
+
const options = parseTechOptions(args, lang);
|
|
331
|
+
if (options.error) {
|
|
332
|
+
printUsageError(reporter, options.error, 'mf tech --help', getTechHelp(lang), lang);
|
|
333
|
+
return 1;
|
|
334
|
+
}
|
|
335
|
+
const projectRoot = resolveMustflowRoot();
|
|
336
|
+
if (options.action === 'list') {
|
|
337
|
+
return runList(projectRoot, options, reporter);
|
|
338
|
+
}
|
|
339
|
+
if (options.action === 'suggest') {
|
|
340
|
+
return runSuggest(projectRoot, options, reporter);
|
|
341
|
+
}
|
|
342
|
+
if (options.action === 'add') {
|
|
343
|
+
return runAdd(projectRoot, options, reporter);
|
|
344
|
+
}
|
|
345
|
+
return runRemove(projectRoot, options, reporter);
|
|
346
|
+
}
|
package/dist/cli/i18n/en.js
CHANGED
|
@@ -41,6 +41,7 @@ export const enMessages = {
|
|
|
41
41
|
"command.lineEndings.summary": "Inspect and normalize line-ending policy",
|
|
42
42
|
"command.run.summary": "Run a configured oneshot command",
|
|
43
43
|
"command.context.summary": "Print machine-readable agent context",
|
|
44
|
+
"command.tech.summary": "Manage technology preferences for agents",
|
|
44
45
|
"command.doctor.summary": "Inspect mustflow health and next steps",
|
|
45
46
|
"command.docs.summary": "Track documentation review queue entries",
|
|
46
47
|
"command.handoff.summary": "Validate restricted work-item and handoff records",
|
package/dist/cli/i18n/es.js
CHANGED
|
@@ -41,6 +41,7 @@ export const esMessages = {
|
|
|
41
41
|
"command.lineEndings.summary": "Inspecciona y normaliza la política de finales de línea",
|
|
42
42
|
"command.run.summary": "Ejecuta un comando configurado de una sola ejecución",
|
|
43
43
|
"command.context.summary": "Imprime contexto de agente legible por máquinas",
|
|
44
|
+
"command.tech.summary": "Gestiona preferencias tecnológicas para agentes",
|
|
44
45
|
"command.doctor.summary": "Inspecciona la salud de mustflow y los siguientes pasos",
|
|
45
46
|
"command.docs.summary": "Rastrea la cola de revisión de documentación",
|
|
46
47
|
"command.handoff.summary": "Valida registros restringidos de trabajo y handoff",
|
package/dist/cli/i18n/fr.js
CHANGED
|
@@ -41,6 +41,7 @@ export const frMessages = {
|
|
|
41
41
|
"command.lineEndings.summary": "Inspecte et normalise la politique de fins de ligne",
|
|
42
42
|
"command.run.summary": "Exécute une commande configurée à exécution unique",
|
|
43
43
|
"command.context.summary": "Imprime le contexte d'agent lisible par machine",
|
|
44
|
+
"command.tech.summary": "Gère les préférences technologiques pour les agents",
|
|
44
45
|
"command.doctor.summary": "Inspecte l'état de mustflow et les prochaines étapes",
|
|
45
46
|
"command.docs.summary": "Suit la file de révision de documentation",
|
|
46
47
|
"command.handoff.summary": "Valide les enregistrements restreints de travail et de handoff",
|
package/dist/cli/i18n/hi.js
CHANGED
|
@@ -41,6 +41,7 @@ export const hiMessages = {
|
|
|
41
41
|
"command.lineEndings.summary": "लाइन-एंडिंग नीति की जाँच और सामान्यीकरण करें",
|
|
42
42
|
"command.run.summary": "कॉन्फ़िगर की गई एक-बार चलने वाली कमांड चलाएँ",
|
|
43
43
|
"command.context.summary": "मशीन-पठनीय एजेंट संदर्भ प्रिंट करें",
|
|
44
|
+
"command.tech.summary": "एजेंटों के लिए technology preferences प्रबंधित करें",
|
|
44
45
|
"command.doctor.summary": "mustflow स्वास्थ्य और अगले कदम जाँचें",
|
|
45
46
|
"command.docs.summary": "दस्तावेज़ review queue entries track करें",
|
|
46
47
|
"command.handoff.summary": "Restricted work-item और handoff records validate करें",
|
package/dist/cli/i18n/ko.js
CHANGED
|
@@ -41,6 +41,7 @@ export const koMessages = {
|
|
|
41
41
|
"command.lineEndings.summary": "줄바꿈 정책을 검사하고 정규화합니다",
|
|
42
42
|
"command.run.summary": "설정된 일회성 명령을 실행합니다",
|
|
43
43
|
"command.context.summary": "에이전트 작업 맥락을 출력합니다",
|
|
44
|
+
"command.tech.summary": "에이전트용 기술 선호를 관리합니다",
|
|
44
45
|
"command.doctor.summary": "mustflow 상태를 점검하고 후속 조치를 안내합니다",
|
|
45
46
|
"command.docs.summary": "문서 검수 대기열 항목을 추적합니다",
|
|
46
47
|
"command.handoff.summary": "제한된 작업 항목과 인수인계 기록을 검증합니다",
|
package/dist/cli/i18n/zh.js
CHANGED
|
@@ -41,6 +41,7 @@ export const zhMessages = {
|
|
|
41
41
|
"command.lineEndings.summary": "检查并规范化换行符策略",
|
|
42
42
|
"command.run.summary": "运行已配置的一次性命令",
|
|
43
43
|
"command.context.summary": "输出机器可读的代理上下文",
|
|
44
|
+
"command.tech.summary": "管理代理使用的技术偏好",
|
|
44
45
|
"command.doctor.summary": "检查 mustflow 健康状态和后续步骤",
|
|
45
46
|
"command.docs.summary": "跟踪文档审阅队列条目",
|
|
46
47
|
"command.handoff.summary": "验证受限的工作项和交接记录",
|
package/dist/cli/index.js
CHANGED
|
@@ -43,6 +43,7 @@ function getTopLevelHelp(lang) {
|
|
|
43
43
|
'mf workspace status --json',
|
|
44
44
|
'mf workspace verify --changed --plan-only --json',
|
|
45
45
|
'mf context --json',
|
|
46
|
+
'mf tech suggest --scope frontend',
|
|
46
47
|
'mf map --write',
|
|
47
48
|
'mf search mustflow_check',
|
|
48
49
|
'mf explain authority AGENTS.md',
|
|
@@ -9,10 +9,12 @@ import { inspectManifestLock } from './manifest-lock.js';
|
|
|
9
9
|
import { MUSTFLOW_JSON_MAX_BYTES, readMustflowTextFile, readMustflowTextFileIfExists, } from './mustflow-read.js';
|
|
10
10
|
import { createRunPlan } from './run-plan.js';
|
|
11
11
|
import { readMustflowTomlFile } from './toml.js';
|
|
12
|
+
import { normalizeTechnologyPreferencesTable, TECHNOLOGY_CONFIG_RELATIVE_PATH, } from '../../core/technology-preferences.js';
|
|
12
13
|
const CONTEXT_SCHEMA_VERSION = '1';
|
|
13
14
|
const COMMANDS_RELATIVE_PATH = '.mustflow/config/commands.toml';
|
|
14
15
|
const MUSTFLOW_RELATIVE_PATH = '.mustflow/config/mustflow.toml';
|
|
15
16
|
const PREFERENCES_RELATIVE_PATH = '.mustflow/config/preferences.toml';
|
|
17
|
+
const TECHNOLOGY_RELATIVE_PATH = TECHNOLOGY_CONFIG_RELATIVE_PATH;
|
|
16
18
|
const LATEST_RUN_RELATIVE_PATH = '.mustflow/state/runs/latest.json';
|
|
17
19
|
const CACHE_RELATIVE_PATH = '.mustflow/cache/';
|
|
18
20
|
const STATE_RELATIVE_PATH = '.mustflow/state/';
|
|
@@ -148,6 +150,19 @@ function readCommandContractContext(projectRoot) {
|
|
|
148
150
|
runnable_intents: runnableIntents,
|
|
149
151
|
};
|
|
150
152
|
}
|
|
153
|
+
function readTechnologyPreferencesContext(projectRoot) {
|
|
154
|
+
const technology = readTomlTableIfExists(projectRoot, TECHNOLOGY_RELATIVE_PATH);
|
|
155
|
+
const file = normalizeTechnologyPreferencesTable(technology, safeExists(projectRoot, TECHNOLOGY_RELATIVE_PATH));
|
|
156
|
+
return {
|
|
157
|
+
path: file.path,
|
|
158
|
+
exists: file.exists,
|
|
159
|
+
authority: file.authority,
|
|
160
|
+
guidance: file.guidance,
|
|
161
|
+
count: file.preferences.length,
|
|
162
|
+
preferences: file.preferences,
|
|
163
|
+
issues: file.issues,
|
|
164
|
+
};
|
|
165
|
+
}
|
|
151
166
|
function readEffectivePolicyContext(mustflow, preferences) {
|
|
152
167
|
const verification = readNestedTable(mustflow, 'verification');
|
|
153
168
|
const retention = readNestedTable(mustflow, 'retention');
|
|
@@ -342,6 +357,7 @@ export function getAgentContext(projectRoot) {
|
|
|
342
357
|
read_order: readPathContext(projectRoot, readOrder),
|
|
343
358
|
optional_read_order: readPathContext(projectRoot, optionalReadOrder),
|
|
344
359
|
command_contract: readCommandContractContext(projectRoot),
|
|
360
|
+
technology_preferences: readTechnologyPreferencesContext(projectRoot),
|
|
345
361
|
effective_policy: readEffectivePolicyContext(mustflow, preferences),
|
|
346
362
|
state_policy: readStatePolicyContext(),
|
|
347
363
|
blocked_actions: BLOCKED_ACTIONS,
|
|
@@ -101,6 +101,12 @@ export const COMMAND_DEFINITIONS = [
|
|
|
101
101
|
summaryKey: 'command.context.summary',
|
|
102
102
|
loadRunner: async () => (await import('../commands/context.js')).runContext,
|
|
103
103
|
},
|
|
104
|
+
{
|
|
105
|
+
id: 'tech',
|
|
106
|
+
usage: 'mf tech',
|
|
107
|
+
summaryKey: 'command.tech.summary',
|
|
108
|
+
loadRunner: async () => (await import('../commands/tech.js')).runTech,
|
|
109
|
+
},
|
|
104
110
|
{
|
|
105
111
|
id: 'doctor',
|
|
106
112
|
usage: 'mf doctor',
|
|
@@ -16,6 +16,7 @@ import { parseTomlText, readMustflowTomlFile } from '../toml.js';
|
|
|
16
16
|
import { MUSTFLOW_JSON_MAX_BYTES, readMustflowTextFileResult, } from '../mustflow-read.js';
|
|
17
17
|
import { getContractModelDefinitions, validateCandidateContractModelConfig, } from '../../../core/contract-models.js';
|
|
18
18
|
import { VERSIONING_CONFIG_PATH, detectVersionSourcePaths, readDeclaredVersionSources, releaseVersioningIsEnabled, } from '../../../core/version-sources.js';
|
|
19
|
+
import { normalizeTechnologyPreferencesTable, TECHNOLOGY_CONFIG_RELATIVE_PATH, } from '../../../core/technology-preferences.js';
|
|
19
20
|
import { ALLOWED_APPROVAL_ACTIONS, ALLOWED_APPROVAL_GATES, ALLOWED_BUDGET_LIMIT_ACTIONS, ALLOWED_CAPABILITY_STATES, ALLOWED_COMMIT_MESSAGE_STYLES, ALLOWED_COMPACTION_CATEGORIES, ALLOWED_COMPACTION_LONG_LIMIT_ACTIONS, ALLOWED_COMPACTION_RAW_LIMIT_ACTIONS, ALLOWED_COMPACTION_STATE_STORES, ALLOWED_COMPACTION_STRATEGIES, ALLOWED_CONTEXT_AUTHORITIES, ALLOWED_CONTEXT_DOCUMENT_AUTHORITIES, ALLOWED_CONTEXT_READ_POLICIES, ALLOWED_HANDOFF_MODES, ALLOWED_HARNESS_FRESH_CONTEXT_MODES, ALLOWED_HARNESS_MODES, ALLOWED_HARNESS_PHASES, ALLOWED_ISOLATION_PREFERENCES, ALLOWED_MAP_MODES, ALLOWED_MAP_PRIVACY_LEVELS, ALLOWED_PROJECT_PROFILES, ALLOWED_REPO_MAP_DEGRADED_VALUES, ALLOWED_REPO_MAP_GIT_LS_FILES_STATUSES, ALLOWED_PROMPT_CACHE_STABLE_PREFIX_POLICIES, ALLOWED_PROMPT_CACHE_STRATEGIES, ALLOWED_PROMPT_CACHE_TASK_READ_POLICIES, ALLOWED_REFRESH_CHECKPOINTS, ALLOWED_REFRESH_METHODS, ALLOWED_REFRESH_MODES, ALLOWED_REFRESH_STATE_STORES, ALLOWED_SKILL_RESOURCE_TYPES, ALLOWED_SKILL_ROUTE_CATEGORIES, ALLOWED_SKILL_ROUTE_PROFILES, ALLOWED_SKILL_ROUTE_TYPES, ALLOWED_STALE_TEST_ACTIONS, ALLOWED_TEST_AUTHORING_POLICIES, ALLOWED_TEST_DELETION_REASONS, ALLOWED_TESTING_POLICIES, ALLOWED_TRANSLATION_POLICIES, ALLOWED_VERIFICATION_SELECTION_STRATEGIES, ALLOWED_VERSION_SOURCE_AUTHORITIES, ALLOWED_VERSION_SOURCE_KINDS, CAPABILITY_BOOLEAN_FIELDS, CAPABILITY_STATE_FIELDS, CONTEXT_AUTHORITY_DRIFT_PATTERNS, DESIGN_TOKEN_DEFINITION_PATTERNS, FORBIDDEN_RELEASE_VERSIONING_CONTRACT_FIELDS, FORBIDDEN_TEST_DELETION_REASONS, FORBIDDEN_VERIFICATION_SELECTION_AUTHORITY_FIELDS, LOCAL_ABSOLUTE_PATH_PATTERNS, LOCAL_TASK_STATE_ROOTS, RAW_COMMAND_FENCE_PATTERN, RELEASE_VERSIONING_BOOLEAN_FIELDS, REPO_MAP_DOC_ID, REPO_MAP_GENERATOR, REPO_MAP_LIFECYCLE, REPO_MAP_PRIVACY_MODE, REPO_MAP_RELATIVE_ROOT, REPO_MAP_REMOTE_OR_BRANCH_PATTERNS, REPO_MAP_SOURCE_FINGERPRINT_PATTERN, REPO_MAP_SOURCE_POLICY, REQUIRED_AGENT_LOOP_PHASES, REQUIRED_SKILL_SCRIPT_RUN_POLICY, REQUIRED_SKILL_SECTION_IDS, ROUTER_INDEX_FILES, ROUTER_INDEX_PROCEDURE_SECTION_PATTERN, SECRET_LIKE_CONTEXT_PATTERNS, SKILL_COMMAND_PERMISSION_CLAIM_PATTERNS, SKILL_INDEX_PATH, SKILL_PACK_ID_PATTERN, SKILL_RESOURCE_MANIFEST, SKILL_RESOURCE_ROOTS, SKILL_RESOURCE_TYPE_BY_ROOT, SKILL_ROUTE_CATEGORY_LABELS, SKILL_ROUTES_METADATA_PATH, SKILL_SECTION_MARKER_PATTERN, SUPPORTED_SKILL_SCHEMA_VERSION, TEST_AUTHORING_BOOLEAN_FIELDS, VERIFICATION_SELECTION_BOOLEAN_FIELDS, VOLATILE_REPO_MAP_PATTERNS } from './constants.js';
|
|
20
21
|
import { hasOwn, isPositiveInteger, isSafeRelativePath, pushStrictIssue, pushStrictWarning, validateAllowedStringField, validateBooleanField, validateExactStringArrayField, validateNestedTable, validatePathArrayField, validatePathField, validatePositiveIntegerField, validateRequiredFiles, validateRequiredPathField, validateRequiredStringField, validateStringArrayField, validateStringArrayMembers, validateStringField, validateTable, validateToml, validateWorkspaceRoots } from './primitives.js';
|
|
21
22
|
import { isConfiguredCommandIntent, isDeclaredCommandIntent } from './command-intents.js';
|
|
@@ -419,6 +420,15 @@ function validatePreferencesConfig(preferencesToml, issues) {
|
|
|
419
420
|
validateStringArrayField(productI18n, 'do_not_translate', '[preferences.product_i18n].do_not_translate', issues);
|
|
420
421
|
}
|
|
421
422
|
}
|
|
423
|
+
function validateTechnologyConfig(technologyToml, issues) {
|
|
424
|
+
if (!technologyToml) {
|
|
425
|
+
return;
|
|
426
|
+
}
|
|
427
|
+
const technology = normalizeTechnologyPreferencesTable(technologyToml, true);
|
|
428
|
+
for (const issue of technology.issues) {
|
|
429
|
+
issues.push({ message: `${TECHNOLOGY_CONFIG_RELATIVE_PATH}: ${issue}` });
|
|
430
|
+
}
|
|
431
|
+
}
|
|
422
432
|
function validateVersioningConfig(versioningToml, issues) {
|
|
423
433
|
if (!versioningToml) {
|
|
424
434
|
return;
|
|
@@ -1634,6 +1644,7 @@ function collectCheckIssues(projectRoot, options = {}) {
|
|
|
1634
1644
|
const parsed = validateToml(projectRoot, issues);
|
|
1635
1645
|
validateMustflowConfig(parsed.mustflowToml, issues);
|
|
1636
1646
|
validatePreferencesConfig(parsed.preferencesToml, issues);
|
|
1647
|
+
validateTechnologyConfig(parsed.technologyToml, issues);
|
|
1637
1648
|
validateVersioningConfig(parsed.versioningToml, issues);
|
|
1638
1649
|
validateCommandIntents(parsed.commandsToml, issues);
|
|
1639
1650
|
validateSkills(projectRoot, issues);
|
|
@@ -3,6 +3,7 @@ import path from 'node:path';
|
|
|
3
3
|
import { isRecord } from '../command-contract.js';
|
|
4
4
|
import { readMustflowTomlFile } from '../toml.js';
|
|
5
5
|
import { REQUIRED_FILES, } from './constants.js';
|
|
6
|
+
import { TECHNOLOGY_CONFIG_RELATIVE_PATH } from '../../../core/technology-preferences.js';
|
|
6
7
|
import { VERSIONING_CONFIG_PATH } from '../../../core/version-sources.js';
|
|
7
8
|
export function hasOwn(table, key) {
|
|
8
9
|
return Object.prototype.hasOwnProperty.call(table, key);
|
|
@@ -34,6 +35,7 @@ export function validateToml(projectRoot, issues) {
|
|
|
34
35
|
'.mustflow/config/mustflow.toml',
|
|
35
36
|
'.mustflow/config/commands.toml',
|
|
36
37
|
'.mustflow/config/preferences.toml',
|
|
38
|
+
TECHNOLOGY_CONFIG_RELATIVE_PATH,
|
|
37
39
|
VERSIONING_CONFIG_PATH,
|
|
38
40
|
]) {
|
|
39
41
|
const filePath = path.join(projectRoot, relativePath);
|
|
@@ -55,6 +57,9 @@ export function validateToml(projectRoot, issues) {
|
|
|
55
57
|
if (relativePath.endsWith('preferences.toml')) {
|
|
56
58
|
parsedFiles.preferencesToml = parsed;
|
|
57
59
|
}
|
|
60
|
+
if (relativePath.endsWith('technology.toml')) {
|
|
61
|
+
parsedFiles.technologyToml = parsed;
|
|
62
|
+
}
|
|
58
63
|
if (relativePath.endsWith('versioning.toml')) {
|
|
59
64
|
parsedFiles.versioningToml = parsed;
|
|
60
65
|
}
|