mustflow 1.30.0 → 1.31.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 +12 -2
- package/dist/cli/commands/run.js +221 -48
- package/dist/cli/commands/upgrade.js +65 -0
- package/dist/cli/commands/verify.js +79 -7
- package/dist/cli/i18n/en.js +12 -0
- package/dist/cli/i18n/es.js +12 -0
- package/dist/cli/i18n/fr.js +12 -0
- package/dist/cli/i18n/hi.js +12 -0
- package/dist/cli/i18n/ko.js +12 -0
- package/dist/cli/i18n/zh.js +12 -0
- package/dist/cli/index.js +27 -46
- package/dist/cli/lib/command-registry.js +5 -0
- package/dist/cli/lib/dashboard-html.js +1 -1
- package/dist/cli/lib/local-index.js +11 -8
- package/dist/cli/lib/reporter.js +6 -0
- package/dist/cli/lib/run-plan.js +20 -3
- package/dist/cli/lib/validation.js +110 -1
- package/dist/core/bounded-output.js +38 -0
- package/dist/core/change-classification.js +6 -2
- package/dist/core/change-verification.js +240 -6
- package/dist/core/check-issues.js +6 -0
- package/dist/core/command-contract-validation.js +20 -0
- package/dist/core/command-effects.js +13 -0
- package/dist/core/contract-lint.js +95 -1
- package/dist/core/dashboard-verification.js +8 -0
- package/dist/core/public-json-contracts.js +7 -0
- package/dist/core/run-performance-history.js +307 -0
- package/dist/core/run-profile.js +87 -0
- package/dist/core/run-receipt.js +171 -4
- package/dist/core/run-write-drift.js +18 -2
- package/dist/core/skill-route-alignment.js +90 -0
- package/dist/core/test-selection.js +224 -0
- package/dist/core/verification-decision-graph.js +67 -0
- package/dist/core/verification-scheduler.js +96 -2
- package/package.json +1 -1
- package/schemas/README.md +6 -2
- package/schemas/change-verification-report.schema.json +153 -3
- package/schemas/commands.schema.json +47 -1
- package/schemas/contract-lint-report.schema.json +51 -0
- package/schemas/dashboard-export.schema.json +273 -0
- package/schemas/explain-report.schema.json +2 -0
- package/schemas/run-receipt.schema.json +109 -0
- package/templates/default/common/.mustflow/config/commands.toml +1 -1
- package/templates/default/manifest.toml +1 -1
package/dist/cli/i18n/es.js
CHANGED
|
@@ -27,6 +27,7 @@ export const esMessages = {
|
|
|
27
27
|
"command.contractLint.summary": "Revisa el contrato de comandos",
|
|
28
28
|
"command.status.summary": "Muestra el estado de la instalación local de mustflow",
|
|
29
29
|
"command.update.summary": "Previsualiza o aplica actualizaciones del flujo de trabajo mustflow",
|
|
30
|
+
"command.upgrade.summary": "Comprueba la versión del paquete y actualiza con seguridad los archivos de flujo instalados",
|
|
30
31
|
"command.map.summary": "Genera REPO_MAP.md",
|
|
31
32
|
"command.lineEndings.summary": "Inspecciona y normaliza la política de finales de línea",
|
|
32
33
|
"command.run.summary": "Ejecuta un comando configurado de una sola ejecución",
|
|
@@ -658,6 +659,17 @@ Lee estos archivos antes de trabajar:
|
|
|
658
659
|
"version.check.upToDate": "última versión {version}; ya está actualizado",
|
|
659
660
|
"version.check.updateCommand": "Comando de actualización:",
|
|
660
661
|
"version.error.checkFailed": "No se pudo consultar npm para una versión nueva: {message}",
|
|
662
|
+
"upgrade.help.summary": "Comprueba si el paquete mustflow instalado está actualizado y luego aplica de forma segura las actualizaciones de la plantilla incluida cuando sea posible.",
|
|
663
|
+
"upgrade.help.option.dryRun": "Comprueba el estado del paquete e imprime el plan de actualización del proyecto sin escribir archivos",
|
|
664
|
+
"upgrade.help.exit.ok": "El paquete estaba actualizado y la comprobación de actualización del proyecto terminó",
|
|
665
|
+
"upgrade.help.exit.fail": "Hace falta actualizar el paquete, hay un bloqueo de actualización del proyecto o la entrada es inválida",
|
|
666
|
+
"upgrade.title": "mustflow upgrade",
|
|
667
|
+
"upgrade.packageSection": "Paquete:",
|
|
668
|
+
"upgrade.projectSection": "Plantilla del proyecto:",
|
|
669
|
+
"upgrade.packageUpdateRequired": "Actualiza primero el paquete mustflow y luego vuelve a ejecutar `mf upgrade`.",
|
|
670
|
+
"upgrade.noFilesWritten": "No se escribieron archivos del proyecto.",
|
|
671
|
+
"upgrade.warning.versionCheckFailed": "No se pudo consultar npm para una versión nueva: {message}",
|
|
672
|
+
"upgrade.warning.continueWithBundledTemplate": "Continuando con la plantilla incluida en el CLI actual.",
|
|
661
673
|
"classify.help.summary": "Clasifica rutas cambiadas, superficies publicas y razones de verificacion sin modificar archivos.",
|
|
662
674
|
"classify.help.option.changed": "Lee rutas desde git status --short --untracked-files=all",
|
|
663
675
|
"classify.help.exit.ok": "La clasificacion de cambios fue inspeccionada e impresa",
|
package/dist/cli/i18n/fr.js
CHANGED
|
@@ -27,6 +27,7 @@ export const frMessages = {
|
|
|
27
27
|
"command.contractLint.summary": "Vérifie le contrat de commandes",
|
|
28
28
|
"command.status.summary": "Affiche l'état de l'installation locale de mustflow",
|
|
29
29
|
"command.update.summary": "Prévisualise ou applique les mises à jour du flux de travail mustflow",
|
|
30
|
+
"command.upgrade.summary": "Vérifie la version du paquet et met à jour en sécurité les fichiers de workflow installés",
|
|
30
31
|
"command.map.summary": "Génère REPO_MAP.md",
|
|
31
32
|
"command.lineEndings.summary": "Inspecte et normalise la politique de fins de ligne",
|
|
32
33
|
"command.run.summary": "Exécute une commande configurée à exécution unique",
|
|
@@ -658,6 +659,17 @@ Lisez ces fichiers avant de travailler :
|
|
|
658
659
|
"version.check.upToDate": "dernière version {version}; déjà à jour",
|
|
659
660
|
"version.check.updateCommand": "Commande de mise à jour :",
|
|
660
661
|
"version.error.checkFailed": "Impossible de vérifier une nouvelle version sur npm : {message}",
|
|
662
|
+
"upgrade.help.summary": "Vérifie si le paquet mustflow installé est à jour, puis applique en sécurité les mises à jour du modèle inclus quand c'est possible.",
|
|
663
|
+
"upgrade.help.option.dryRun": "Vérifie l'état du paquet et affiche le plan de mise à jour du projet sans écrire de fichiers",
|
|
664
|
+
"upgrade.help.exit.ok": "Le paquet était à jour et la vérification de mise à jour du projet est terminée",
|
|
665
|
+
"upgrade.help.exit.fail": "Une mise à jour du paquet est requise, un blocage de mise à jour du projet existe, ou l'entrée est invalide",
|
|
666
|
+
"upgrade.title": "mustflow upgrade",
|
|
667
|
+
"upgrade.packageSection": "Paquet :",
|
|
668
|
+
"upgrade.projectSection": "Modèle de projet :",
|
|
669
|
+
"upgrade.packageUpdateRequired": "Mettez d'abord à jour le paquet mustflow, puis relancez `mf upgrade`.",
|
|
670
|
+
"upgrade.noFilesWritten": "Aucun fichier du projet n'a été écrit.",
|
|
671
|
+
"upgrade.warning.versionCheckFailed": "Impossible de vérifier une nouvelle version sur npm : {message}",
|
|
672
|
+
"upgrade.warning.continueWithBundledTemplate": "Poursuite avec le modèle inclus dans le CLI actuel.",
|
|
661
673
|
"classify.help.summary": "Classe les chemins modifies, les surfaces publiques et les raisons de verification sans modifier les fichiers.",
|
|
662
674
|
"classify.help.option.changed": "Lire les chemins depuis git status --short --untracked-files=all",
|
|
663
675
|
"classify.help.exit.ok": "La classification des changements a ete inspectee et affichee",
|
package/dist/cli/i18n/hi.js
CHANGED
|
@@ -27,6 +27,7 @@ export const hiMessages = {
|
|
|
27
27
|
"command.contractLint.summary": "कमांड अनुबंध की जाँच करें",
|
|
28
28
|
"command.status.summary": "स्थानीय mustflow इंस्टॉल स्थिति दिखाएँ",
|
|
29
29
|
"command.update.summary": "mustflow वर्कफ़्लो अपडेट का पूर्वावलोकन करें या लागू करें",
|
|
30
|
+
"command.upgrade.summary": "Package version जाँचें और installed workflow files सुरक्षित रूप से update करें",
|
|
30
31
|
"command.map.summary": "REPO_MAP.md बनाएँ",
|
|
31
32
|
"command.lineEndings.summary": "लाइन-एंडिंग नीति की जाँच और सामान्यीकरण करें",
|
|
32
33
|
"command.run.summary": "कॉन्फ़िगर की गई एक-बार चलने वाली कमांड चलाएँ",
|
|
@@ -658,6 +659,17 @@ export const hiMessages = {
|
|
|
658
659
|
"version.check.upToDate": "latest {version}; पहले से up to date",
|
|
659
660
|
"version.check.updateCommand": "Update command:",
|
|
660
661
|
"version.error.checkFailed": "npm पर नया version जाँचा नहीं जा सका: {message}",
|
|
662
|
+
"upgrade.help.summary": "जाँचें कि installed mustflow package current है या नहीं, फिर संभव होने पर current CLI में bundled workflow template updates सुरक्षित रूप से लागू करें.",
|
|
663
|
+
"upgrade.help.option.dryRun": "Package status जाँचें और files लिखे बिना project update plan प्रिंट करें",
|
|
664
|
+
"upgrade.help.exit.ok": "Package current था और project update check पूरा हुआ",
|
|
665
|
+
"upgrade.help.exit.fail": "Package update चाहिए, project update blocker मिला, या input invalid है",
|
|
666
|
+
"upgrade.title": "mustflow upgrade",
|
|
667
|
+
"upgrade.packageSection": "Package:",
|
|
668
|
+
"upgrade.projectSection": "Project template:",
|
|
669
|
+
"upgrade.packageUpdateRequired": "पहले mustflow package update करें, फिर `mf upgrade` दोबारा चलाएँ.",
|
|
670
|
+
"upgrade.noFilesWritten": "कोई project file नहीं लिखी गई.",
|
|
671
|
+
"upgrade.warning.versionCheckFailed": "npm पर नया version जाँचा नहीं जा सका: {message}",
|
|
672
|
+
"upgrade.warning.continueWithBundledTemplate": "Current CLI में bundled template के साथ आगे बढ़ रहे हैं.",
|
|
661
673
|
"classify.help.summary": "फ़ाइल बदले बिना बदले पथ, सार्वजनिक सतह और जरूरी सत्यापन कारण वर्गीकृत करें.",
|
|
662
674
|
"classify.help.option.changed": "git status --short --untracked-files=all से पथ पढ़ें",
|
|
663
675
|
"classify.help.exit.ok": "बदलाव वर्गीकरण जांचकर प्रिंट किया गया",
|
package/dist/cli/i18n/ko.js
CHANGED
|
@@ -27,6 +27,7 @@ export const koMessages = {
|
|
|
27
27
|
"command.contractLint.summary": "명령 계약을 점검합니다",
|
|
28
28
|
"command.status.summary": "로컬 mustflow 설치 상태를 출력합니다",
|
|
29
29
|
"command.update.summary": "mustflow 워크플로우 갱신을 미리 보거나 적용합니다",
|
|
30
|
+
"command.upgrade.summary": "패키지 버전을 확인하고 설치된 워크플로우 파일을 안전하게 갱신합니다",
|
|
30
31
|
"command.map.summary": "REPO_MAP.md를 작성합니다",
|
|
31
32
|
"command.lineEndings.summary": "줄바꿈 정책을 검사하고 정규화합니다",
|
|
32
33
|
"command.run.summary": "설정된 일회성 명령을 실행합니다",
|
|
@@ -658,6 +659,17 @@ export const koMessages = {
|
|
|
658
659
|
"version.check.upToDate": "최신 {version}; 이미 최신 상태입니다",
|
|
659
660
|
"version.check.updateCommand": "업데이트 명령:",
|
|
660
661
|
"version.error.checkFailed": "npm에서 새 버전을 확인하지 못했습니다: {message}",
|
|
662
|
+
"upgrade.help.summary": "설치된 mustflow 패키지가 최신인지 확인한 뒤, 가능하면 현재 CLI에 포함된 워크플로우 템플릿 갱신을 안전하게 적용합니다.",
|
|
663
|
+
"upgrade.help.option.dryRun": "패키지 상태를 확인하고 파일을 쓰지 않은 채 프로젝트 갱신 계획을 출력합니다",
|
|
664
|
+
"upgrade.help.exit.ok": "패키지가 최신이고 프로젝트 갱신 확인이 완료되었습니다",
|
|
665
|
+
"upgrade.help.exit.fail": "패키지 업데이트가 필요하거나, 프로젝트 갱신 차단 항목이 있거나, 입력이 잘못되었습니다",
|
|
666
|
+
"upgrade.title": "mustflow upgrade",
|
|
667
|
+
"upgrade.packageSection": "패키지:",
|
|
668
|
+
"upgrade.projectSection": "프로젝트 템플릿:",
|
|
669
|
+
"upgrade.packageUpdateRequired": "mustflow 패키지를 먼저 업데이트한 뒤 `mf upgrade`를 다시 실행하세요.",
|
|
670
|
+
"upgrade.noFilesWritten": "프로젝트 파일은 쓰지 않았습니다.",
|
|
671
|
+
"upgrade.warning.versionCheckFailed": "npm에서 새 버전을 확인하지 못했습니다: {message}",
|
|
672
|
+
"upgrade.warning.continueWithBundledTemplate": "현재 CLI에 포함된 템플릿으로 계속 진행합니다.",
|
|
661
673
|
"classify.help.summary": "파일을 수정하지 않고 변경 경로, 공개 표면, 필요한 검증 이유를 분류합니다.",
|
|
662
674
|
"classify.help.option.changed": "git status --short --untracked-files=all에서 경로를 읽습니다",
|
|
663
675
|
"classify.help.exit.ok": "변경 분류를 확인하고 출력했습니다",
|
package/dist/cli/i18n/zh.js
CHANGED
|
@@ -27,6 +27,7 @@ export const zhMessages = {
|
|
|
27
27
|
"command.contractLint.summary": "检查命令契约",
|
|
28
28
|
"command.status.summary": "显示本地 mustflow 安装状态",
|
|
29
29
|
"command.update.summary": "预览或应用 mustflow 工作流更新",
|
|
30
|
+
"command.upgrade.summary": "检查包版本并安全更新已安装的工作流文件",
|
|
30
31
|
"command.map.summary": "生成 REPO_MAP.md",
|
|
31
32
|
"command.lineEndings.summary": "检查并规范化换行符策略",
|
|
32
33
|
"command.run.summary": "运行已配置的一次性命令",
|
|
@@ -658,6 +659,17 @@ export const zhMessages = {
|
|
|
658
659
|
"version.check.upToDate": "最新版本 {version};已是最新",
|
|
659
660
|
"version.check.updateCommand": "更新命令:",
|
|
660
661
|
"version.error.checkFailed": "无法从 npm 检查新版本:{message}",
|
|
662
|
+
"upgrade.help.summary": "检查已安装的 mustflow 包是否为最新,然后在可行时安全应用当前 CLI 捆绑的工作流模板更新。",
|
|
663
|
+
"upgrade.help.option.dryRun": "检查包状态并打印项目更新计划,不写入文件",
|
|
664
|
+
"upgrade.help.exit.ok": "包已是最新,项目更新检查已完成",
|
|
665
|
+
"upgrade.help.exit.fail": "需要先更新包、项目更新存在阻塞项,或输入无效",
|
|
666
|
+
"upgrade.title": "mustflow upgrade",
|
|
667
|
+
"upgrade.packageSection": "包:",
|
|
668
|
+
"upgrade.projectSection": "项目模板:",
|
|
669
|
+
"upgrade.packageUpdateRequired": "请先更新 mustflow 包,然后再次运行 `mf upgrade`。",
|
|
670
|
+
"upgrade.noFilesWritten": "未写入项目文件。",
|
|
671
|
+
"upgrade.warning.versionCheckFailed": "无法从 npm 检查新版本:{message}",
|
|
672
|
+
"upgrade.warning.continueWithBundledTemplate": "继续使用当前 CLI 捆绑的模板。",
|
|
661
673
|
"classify.help.summary": "在不修改文件的情况下分类变更路径、公开表面和所需验证原因。",
|
|
662
674
|
"classify.help.option.changed": "从 git status --short --untracked-files=all 读取路径",
|
|
663
675
|
"classify.help.exit.ok": "已检查并输出变更分类",
|
package/dist/cli/index.js
CHANGED
|
@@ -2,29 +2,6 @@
|
|
|
2
2
|
import { realpathSync } from 'node:fs';
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
import { fileURLToPath } from 'node:url';
|
|
5
|
-
import { runAdapters } from './commands/adapters.js';
|
|
6
|
-
import { runCheck } from './commands/check.js';
|
|
7
|
-
import { runClassify } from './commands/classify.js';
|
|
8
|
-
import { runContractLint } from './commands/contract-lint.js';
|
|
9
|
-
import { runContext } from './commands/context.js';
|
|
10
|
-
import { runDashboard } from './commands/dashboard.js';
|
|
11
|
-
import { runDoctor } from './commands/doctor.js';
|
|
12
|
-
import { runDocs } from './commands/docs.js';
|
|
13
|
-
import { runExplain } from './commands/explain.js';
|
|
14
|
-
import { runHelp } from './commands/help.js';
|
|
15
|
-
import { runHandoff } from './commands/handoff.js';
|
|
16
|
-
import { runImpact } from './commands/impact.js';
|
|
17
|
-
import { runInit } from './commands/init.js';
|
|
18
|
-
import { runIndex } from './commands/index.js';
|
|
19
|
-
import { runLineEndings } from './commands/line-endings.js';
|
|
20
|
-
import { runMap } from './commands/map.js';
|
|
21
|
-
import { runRun } from './commands/run.js';
|
|
22
|
-
import { runSearch } from './commands/search.js';
|
|
23
|
-
import { runStatus } from './commands/status.js';
|
|
24
|
-
import { runUpdate } from './commands/update.js';
|
|
25
|
-
import { runVerify } from './commands/verify.js';
|
|
26
|
-
import { runVersion } from './commands/version.js';
|
|
27
|
-
import { runVersionSources } from './commands/version-sources.js';
|
|
28
5
|
import { COMMAND_DEFINITIONS } from './lib/command-registry.js';
|
|
29
6
|
import { renderCliError, renderHelp } from './lib/cli-output.js';
|
|
30
7
|
import { DEFAULT_CLI_LANG, SUPPORTED_CLI_LANGS, isCliLang, t } from './lib/i18n.js';
|
|
@@ -59,6 +36,7 @@ function getTopLevelHelp(lang) {
|
|
|
59
36
|
'mf search mustflow_check',
|
|
60
37
|
'mf explain authority AGENTS.md',
|
|
61
38
|
'mf impact --changed',
|
|
39
|
+
'mf upgrade --dry-run',
|
|
62
40
|
'mf verify --changed --plan-only --json',
|
|
63
41
|
'mf verify --reason code_change',
|
|
64
42
|
'mf line-endings check',
|
|
@@ -125,73 +103,76 @@ export async function runCli(argv, reporter = consoleReporter) {
|
|
|
125
103
|
return 0;
|
|
126
104
|
}
|
|
127
105
|
if (command === '--version' || command === '-v' || command === 'version') {
|
|
128
|
-
return runVersion(args, reporter, parsed.lang);
|
|
106
|
+
return (await import('./commands/version.js')).runVersion(args, reporter, parsed.lang);
|
|
129
107
|
}
|
|
130
108
|
if (command === 'init') {
|
|
131
|
-
return runInit(args, reporter, parsed.lang);
|
|
109
|
+
return (await import('./commands/init.js')).runInit(args, reporter, parsed.lang);
|
|
132
110
|
}
|
|
133
111
|
if (command === 'adapters') {
|
|
134
|
-
return runAdapters(args, reporter, parsed.lang);
|
|
112
|
+
return (await import('./commands/adapters.js')).runAdapters(args, reporter, parsed.lang);
|
|
135
113
|
}
|
|
136
114
|
if (command === 'check') {
|
|
137
|
-
return runCheck(args, reporter, parsed.lang);
|
|
115
|
+
return (await import('./commands/check.js')).runCheck(args, reporter, parsed.lang);
|
|
138
116
|
}
|
|
139
117
|
if (command === 'classify') {
|
|
140
|
-
return runClassify(args, reporter, parsed.lang);
|
|
118
|
+
return (await import('./commands/classify.js')).runClassify(args, reporter, parsed.lang);
|
|
141
119
|
}
|
|
142
120
|
if (command === 'contract-lint') {
|
|
143
|
-
return runContractLint(args, reporter, parsed.lang);
|
|
121
|
+
return (await import('./commands/contract-lint.js')).runContractLint(args, reporter, parsed.lang);
|
|
144
122
|
}
|
|
145
123
|
if (command === 'status') {
|
|
146
|
-
return runStatus(args, reporter, parsed.lang);
|
|
124
|
+
return (await import('./commands/status.js')).runStatus(args, reporter, parsed.lang);
|
|
147
125
|
}
|
|
148
126
|
if (command === 'update') {
|
|
149
|
-
return runUpdate(args, reporter, parsed.lang);
|
|
127
|
+
return (await import('./commands/update.js')).runUpdate(args, reporter, parsed.lang);
|
|
128
|
+
}
|
|
129
|
+
if (command === 'upgrade') {
|
|
130
|
+
return (await import('./commands/upgrade.js')).runUpgrade(args, reporter, parsed.lang);
|
|
150
131
|
}
|
|
151
132
|
if (command === 'map') {
|
|
152
|
-
return runMap(args, reporter, parsed.lang);
|
|
133
|
+
return (await import('./commands/map.js')).runMap(args, reporter, parsed.lang);
|
|
153
134
|
}
|
|
154
135
|
if (command === 'line-endings') {
|
|
155
|
-
return runLineEndings(args, reporter, parsed.lang);
|
|
136
|
+
return (await import('./commands/line-endings.js')).runLineEndings(args, reporter, parsed.lang);
|
|
156
137
|
}
|
|
157
138
|
if (command === 'run') {
|
|
158
|
-
return runRun(args, reporter, parsed.lang);
|
|
139
|
+
return (await import('./commands/run.js')).runRun(args, reporter, parsed.lang);
|
|
159
140
|
}
|
|
160
141
|
if (command === 'context') {
|
|
161
|
-
return runContext(args, reporter, parsed.lang);
|
|
142
|
+
return (await import('./commands/context.js')).runContext(args, reporter, parsed.lang);
|
|
162
143
|
}
|
|
163
144
|
if (command === 'doctor') {
|
|
164
|
-
return runDoctor(args, reporter, parsed.lang);
|
|
145
|
+
return (await import('./commands/doctor.js')).runDoctor(args, reporter, parsed.lang);
|
|
165
146
|
}
|
|
166
147
|
if (command === 'docs') {
|
|
167
|
-
return runDocs(args, reporter, parsed.lang);
|
|
148
|
+
return (await import('./commands/docs.js')).runDocs(args, reporter, parsed.lang);
|
|
168
149
|
}
|
|
169
150
|
if (command === 'handoff') {
|
|
170
|
-
return runHandoff(args, reporter, parsed.lang);
|
|
151
|
+
return (await import('./commands/handoff.js')).runHandoff(args, reporter, parsed.lang);
|
|
171
152
|
}
|
|
172
153
|
if (command === 'index') {
|
|
173
|
-
return runIndex(args, reporter, parsed.lang);
|
|
154
|
+
return (await import('./commands/index.js')).runIndex(args, reporter, parsed.lang);
|
|
174
155
|
}
|
|
175
156
|
if (command === 'search') {
|
|
176
|
-
return runSearch(args, reporter, parsed.lang);
|
|
157
|
+
return (await import('./commands/search.js')).runSearch(args, reporter, parsed.lang);
|
|
177
158
|
}
|
|
178
159
|
if (command === 'dashboard') {
|
|
179
|
-
return runDashboard(args, reporter, parsed.lang);
|
|
160
|
+
return (await import('./commands/dashboard.js')).runDashboard(args, reporter, parsed.lang);
|
|
180
161
|
}
|
|
181
162
|
if (command === 'version-sources') {
|
|
182
|
-
return runVersionSources(args, reporter, parsed.lang);
|
|
163
|
+
return (await import('./commands/version-sources.js')).runVersionSources(args, reporter, parsed.lang);
|
|
183
164
|
}
|
|
184
165
|
if (command === 'verify') {
|
|
185
|
-
return runVerify(args, reporter, parsed.lang);
|
|
166
|
+
return (await import('./commands/verify.js')).runVerify(args, reporter, parsed.lang);
|
|
186
167
|
}
|
|
187
168
|
if (command === 'explain') {
|
|
188
|
-
return runExplain(args, reporter, parsed.lang);
|
|
169
|
+
return (await import('./commands/explain.js')).runExplain(args, reporter, parsed.lang);
|
|
189
170
|
}
|
|
190
171
|
if (command === 'impact') {
|
|
191
|
-
return runImpact(args, reporter, parsed.lang);
|
|
172
|
+
return (await import('./commands/impact.js')).runImpact(args, reporter, parsed.lang);
|
|
192
173
|
}
|
|
193
174
|
if (command === 'help') {
|
|
194
|
-
return runHelp(args, reporter, parsed.lang);
|
|
175
|
+
return (await import('./commands/help.js')).runHelp(args, reporter, parsed.lang);
|
|
195
176
|
}
|
|
196
177
|
reporter.stderr(renderCliError(t(parsed.lang, 'cli.error.unknownCommand', { command }), 'mf --help', parsed.lang));
|
|
197
178
|
reporter.stdout(getTopLevelHelp(parsed.lang));
|
|
@@ -1078,7 +1078,7 @@ function renderVerificationPanel() {
|
|
|
1078
1078
|
if (!entry) continue;
|
|
1079
1079
|
const effects = document.createElement("div");
|
|
1080
1080
|
effects.className = "verification-files";
|
|
1081
|
-
effects.textContent = message("dashboard.verification.effects") + ": " + entry.effects.map((effect) => effect.mode + " " + effect.path + " [" + effect.lock + "]").join(", ");
|
|
1081
|
+
effects.textContent = message("dashboard.verification.effects") + ": " + entry.effects.map((effect) => effect.mode + " " + (effect.path || effect.lock) + " [" + effect.lock + "]").join(", ");
|
|
1082
1082
|
details.appendChild(effects);
|
|
1083
1083
|
if (entry.conflicts.length > 0) {
|
|
1084
1084
|
const conflicts = document.createElement("div");
|
|
@@ -8,7 +8,7 @@ import { readTomlFile } from './toml.js';
|
|
|
8
8
|
import { collectSourceAnchorIndexRecords, } from '../../core/source-anchor-status.js';
|
|
9
9
|
import { normalizeCommandEffects } from '../../core/command-effects.js';
|
|
10
10
|
import { listChangeClassificationRuleDescriptors } from '../../core/change-classification.js';
|
|
11
|
-
const LOCAL_INDEX_SCHEMA_VERSION = '
|
|
11
|
+
const LOCAL_INDEX_SCHEMA_VERSION = '13';
|
|
12
12
|
const LOCAL_INDEX_PARSER_VERSION = '1';
|
|
13
13
|
const DEFAULT_DATABASE_RELATIVE_PATH = '.mustflow/cache/mustflow.sqlite';
|
|
14
14
|
const LOCAL_INDEX_CONTENT_MODE = 'metadata_and_snippets';
|
|
@@ -752,10 +752,9 @@ CREATE TABLE command_effects (
|
|
|
752
752
|
source TEXT NOT NULL,
|
|
753
753
|
access TEXT NOT NULL,
|
|
754
754
|
mode TEXT NOT NULL,
|
|
755
|
-
path TEXT
|
|
755
|
+
path TEXT,
|
|
756
756
|
lock TEXT NOT NULL,
|
|
757
|
-
concurrency TEXT NOT NULL
|
|
758
|
-
PRIMARY KEY (intent, source, access, mode, path, lock, concurrency)
|
|
757
|
+
concurrency TEXT NOT NULL
|
|
759
758
|
);
|
|
760
759
|
|
|
761
760
|
CREATE VIEW command_write_locks AS
|
|
@@ -1014,7 +1013,7 @@ function populateSearchTables(database, capabilities, documents, skills, skillRo
|
|
|
1014
1013
|
}
|
|
1015
1014
|
for (const intent of commandIntents) {
|
|
1016
1015
|
const effects = intent.effects
|
|
1017
|
-
.flatMap((effect) => [effect.lock, effect.path, effect.mode, effect.access, effect.concurrency])
|
|
1016
|
+
.flatMap((effect) => [effect.lock, effect.path ?? '', effect.mode, effect.access, effect.concurrency])
|
|
1018
1017
|
.join(' ');
|
|
1019
1018
|
insertSearchNgrams(database, 'command_intent', intent.name, [intent.name, intent.status, intent.lifecycle ?? '', intent.runPolicy ?? '', intent.description ?? '', effects], 'command_intent');
|
|
1020
1019
|
if (capabilities.backend === SEARCH_BACKEND_FTS5) {
|
|
@@ -1123,7 +1122,9 @@ function populateDatabase(database, capabilities, documents, skills, skillRoutes
|
|
|
1123
1122
|
insertDocumentTerm(database, '.mustflow/config/commands.toml', intent.description, 'command_description');
|
|
1124
1123
|
for (const effect of intent.effects) {
|
|
1125
1124
|
database.run('INSERT INTO command_effects (intent, source, access, mode, path, lock, concurrency) VALUES (?, ?, ?, ?, ?, ?, ?)', [effect.intent, effect.source, effect.access, effect.mode, effect.path, effect.lock, effect.concurrency]);
|
|
1126
|
-
|
|
1125
|
+
if (effect.path !== null) {
|
|
1126
|
+
insertDocumentTerm(database, '.mustflow/config/commands.toml', effect.path, 'command_effect_path');
|
|
1127
|
+
}
|
|
1127
1128
|
insertDocumentTerm(database, '.mustflow/config/commands.toml', effect.lock, 'command_effect_lock');
|
|
1128
1129
|
insertDocumentTerm(database, '.mustflow/config/commands.toml', effect.mode, 'command_effect_mode');
|
|
1129
1130
|
}
|
|
@@ -1624,7 +1625,7 @@ function getCommandEffects(database, intent) {
|
|
|
1624
1625
|
source: toSearchString(row.source),
|
|
1625
1626
|
access: toSearchString(row.access),
|
|
1626
1627
|
mode: toSearchString(row.mode),
|
|
1627
|
-
path: toSearchString(row.path),
|
|
1628
|
+
path: row.path === null || row.path === undefined ? null : toSearchString(row.path),
|
|
1628
1629
|
lock: toSearchString(row.lock),
|
|
1629
1630
|
concurrency: toSearchString(row.concurrency),
|
|
1630
1631
|
}));
|
|
@@ -1826,7 +1827,9 @@ export async function searchLocalIndex(projectRoot, query, options = {}) {
|
|
|
1826
1827
|
const description = toSearchString(row.description);
|
|
1827
1828
|
const effects = getCommandEffects(database, name);
|
|
1828
1829
|
const effectLocks = [...new Set(effects.map((effect) => effect.lock))].sort((left, right) => left.localeCompare(right));
|
|
1829
|
-
const effectPaths = [
|
|
1830
|
+
const effectPaths = [
|
|
1831
|
+
...new Set(effects.map((effect) => effect.path).filter((effectPath) => effectPath !== null)),
|
|
1832
|
+
].sort((left, right) => left.localeCompare(right));
|
|
1830
1833
|
const effectModes = [...new Set(effects.map((effect) => effect.mode))].sort((left, right) => left.localeCompare(right));
|
|
1831
1834
|
const primaryFields = [name];
|
|
1832
1835
|
const secondaryFields = [status, lifecycle, runPolicy, description, ...effectLocks, ...effectPaths, ...effectModes];
|
package/dist/cli/lib/reporter.js
CHANGED
package/dist/cli/lib/run-plan.js
CHANGED
|
@@ -26,6 +26,17 @@ function getRelativeProjectPath(projectRoot, targetPath) {
|
|
|
26
26
|
const relativePath = path.relative(projectRoot, targetPath);
|
|
27
27
|
return relativePath.length > 0 ? toPosixPath(relativePath) : '.';
|
|
28
28
|
}
|
|
29
|
+
function normalizeTestTargets(values) {
|
|
30
|
+
return [
|
|
31
|
+
...new Set((values ?? [])
|
|
32
|
+
.map((value) => value.trim().replace(/\\/g, '/'))
|
|
33
|
+
.filter((value) => value.length > 0 && !path.posix.isAbsolute(value) && !path.win32.isAbsolute(value))
|
|
34
|
+
.filter((value) => value.split('/').every((segment) => segment.length > 0 && segment !== '.' && segment !== '..'))),
|
|
35
|
+
].sort((left, right) => left.localeCompare(right));
|
|
36
|
+
}
|
|
37
|
+
function commandAcceptsTestTargets(intent) {
|
|
38
|
+
return isRecord(intent.selection) && intent.selection.accepts_test_targets === true;
|
|
39
|
+
}
|
|
29
40
|
function shouldUseShellForArgvExecutable(executablePath) {
|
|
30
41
|
return process.platform === 'win32' && executablePath.toLowerCase().endsWith('.cmd');
|
|
31
42
|
}
|
|
@@ -83,6 +94,7 @@ function readRunIntentMetadata(contract, intent) {
|
|
|
83
94
|
destructive: readBoolean(intent, 'destructive'),
|
|
84
95
|
envPolicy: env.policy,
|
|
85
96
|
envAllowlist: env.allowlist,
|
|
97
|
+
testTargets: [],
|
|
86
98
|
};
|
|
87
99
|
}
|
|
88
100
|
function createBlockedRunPlan(contract, intentName, intent, eligibility, reasonCode, detail) {
|
|
@@ -114,9 +126,10 @@ function createBlockedRunPlan(contract, intentName, intent, eligibility, reasonC
|
|
|
114
126
|
destructive: metadata?.destructive,
|
|
115
127
|
envPolicy: metadata?.envPolicy ?? null,
|
|
116
128
|
envAllowlist: metadata?.envAllowlist ?? [],
|
|
129
|
+
testTargets: [],
|
|
117
130
|
};
|
|
118
131
|
}
|
|
119
|
-
export function createRunPlan(projectRoot, contract, intentName) {
|
|
132
|
+
export function createRunPlan(projectRoot, contract, intentName, options = {}) {
|
|
120
133
|
const rawIntent = contract.intents[intentName];
|
|
121
134
|
const eligibility = evaluateCommandIntentEligibility(intentName, rawIntent);
|
|
122
135
|
if (!isRecord(rawIntent)) {
|
|
@@ -133,6 +146,8 @@ export function createRunPlan(projectRoot, contract, intentName) {
|
|
|
133
146
|
catch (error) {
|
|
134
147
|
return createBlockedRunPlan(contract, intentName, rawIntent, eligibility, 'cwd_outside_project', error instanceof Error ? error.message : String(error));
|
|
135
148
|
}
|
|
149
|
+
const testTargets = commandAcceptsTestTargets(rawIntent) ? normalizeTestTargets(options.testTargets) : [];
|
|
150
|
+
const commandArgv = metadata.commandArgv && testTargets.length > 0 ? [...metadata.commandArgv, ...testTargets] : metadata.commandArgv;
|
|
136
151
|
if (!metadata.timeoutSeconds || !metadata.mode) {
|
|
137
152
|
return createBlockedRunPlan(contract, intentName, rawIntent, eligibility, !metadata.timeoutSeconds ? 'missing_timeout' : 'missing_command_source', !metadata.timeoutSeconds ? 'Intent timeout_seconds is missing or invalid.' : 'Intent does not define argv or shell cmd.');
|
|
138
153
|
}
|
|
@@ -153,16 +168,17 @@ export function createRunPlan(projectRoot, contract, intentName) {
|
|
|
153
168
|
timeoutSeconds: metadata.timeoutSeconds,
|
|
154
169
|
maxOutputBytes: metadata.maxOutputBytes,
|
|
155
170
|
successExitCodes: metadata.successExitCodes,
|
|
156
|
-
commandArgv
|
|
171
|
+
commandArgv,
|
|
157
172
|
shellCommand: metadata.shellCommand,
|
|
158
173
|
mode: metadata.mode,
|
|
159
|
-
argvCommand:
|
|
174
|
+
argvCommand: commandArgv ? resolveArgvCommand(rawIntent, commandArgv) : undefined,
|
|
160
175
|
writes: metadata.writes,
|
|
161
176
|
effects: metadata.effects,
|
|
162
177
|
network: metadata.network,
|
|
163
178
|
destructive: metadata.destructive,
|
|
164
179
|
envPolicy: metadata.envPolicy,
|
|
165
180
|
envAllowlist: metadata.envAllowlist,
|
|
181
|
+
testTargets,
|
|
166
182
|
};
|
|
167
183
|
}
|
|
168
184
|
export function createRunPreview(plan, previewMode) {
|
|
@@ -195,6 +211,7 @@ export function createRunPreview(plan, previewMode) {
|
|
|
195
211
|
destructive: plan.destructive,
|
|
196
212
|
env_policy: plan.envPolicy,
|
|
197
213
|
env_allowlist: plan.envAllowlist,
|
|
214
|
+
test_targets: plan.testTargets,
|
|
198
215
|
success_exit_codes: plan.successExitCodes,
|
|
199
216
|
};
|
|
200
217
|
}
|
|
@@ -4,7 +4,7 @@ import { isRecord } from './command-contract.js';
|
|
|
4
4
|
import { validateCommandContractConfig, validateCommandContractStrictDefaults, } from '../../core/command-contract-validation.js';
|
|
5
5
|
import { ALLOWED_RETENTION_ON_LIMIT, ALLOWED_RETENTION_STORES, DEFAULT_RETENTION_LIMITS, readNestedRetentionTable, readRetentionTable, resolveRetentionLimits, } from '../../core/retention-policy.js';
|
|
6
6
|
import { formatManagedMarkdownLabel, getManagedMarkdownExpectation, } from '../../core/authority-resolution.js';
|
|
7
|
-
import { SKILL_INDEX_ROUTE_COLUMN_COUNT, SKILL_INDEX_ROUTE_COLUMNS, SKILL_INDEX_SKILL_PATH_COLUMN_INDEX, findSkillIndexRoutePathColumn, parseSkillIndexRoutes, readBacktickValues, } from '../../core/skill-route-alignment.js';
|
|
7
|
+
import { SKILL_INDEX_ROUTE_COLUMN_COUNT, SKILL_INDEX_ROUTE_COLUMNS, SKILL_INDEX_SKILL_PATH_COLUMN_INDEX, findSkillRouteConflictWarnings, findSkillIndexRoutePathColumn, parseSkillIndexRoutes, readBacktickValues, } from '../../core/skill-route-alignment.js';
|
|
8
8
|
import { validateTemplateVersionSync } from '../../core/release-version-validation.js';
|
|
9
9
|
import { validateSourceAnchorsInProject } from '../../core/source-anchor-validation.js';
|
|
10
10
|
import { listFilesRecursive, toPosixPath } from './filesystem.js';
|
|
@@ -30,6 +30,7 @@ const REQUIRED_SKILL_SECTION_IDS = [
|
|
|
30
30
|
'output-format',
|
|
31
31
|
];
|
|
32
32
|
const SKILL_SECTION_MARKER_PATTERN = /^<!--\s*mustflow-section:\s*([a-z][a-z0-9-]*)\s*-->\s*\r?\n##\s+.+$/gimu;
|
|
33
|
+
const TEST_SELECTION_CONFIG_PATH = '.mustflow/config/test-selection.toml';
|
|
33
34
|
const REQUIRED_FILES = [
|
|
34
35
|
'AGENTS.md',
|
|
35
36
|
'.mustflow/config/mustflow.toml',
|
|
@@ -99,6 +100,22 @@ const FORBIDDEN_VERIFICATION_SELECTION_AUTHORITY_FIELDS = [
|
|
|
99
100
|
'destructive',
|
|
100
101
|
];
|
|
101
102
|
const ALLOWED_VERIFICATION_SELECTION_STRATEGIES = new Set(['risk_based', 'targeted', 'full']);
|
|
103
|
+
const ALLOWED_TEST_SELECTION_RISKS = new Set(['low', 'medium', 'high', 'release_sensitive', 'security_sensitive']);
|
|
104
|
+
const FORBIDDEN_TEST_SELECTION_COMMAND_AUTHORITY_FIELDS = [
|
|
105
|
+
'argv',
|
|
106
|
+
'cmd',
|
|
107
|
+
'command',
|
|
108
|
+
'commands',
|
|
109
|
+
'command_intents',
|
|
110
|
+
'destructive',
|
|
111
|
+
'intents',
|
|
112
|
+
'lifecycle',
|
|
113
|
+
'network',
|
|
114
|
+
'required_after',
|
|
115
|
+
'run_policy',
|
|
116
|
+
'status',
|
|
117
|
+
'writes',
|
|
118
|
+
];
|
|
102
119
|
const TEST_AUTHORING_BOOLEAN_FIELDS = ['prefer_existing_tests', 'require_new_test_rationale'];
|
|
103
120
|
const ALLOWED_TEST_AUTHORING_POLICIES = new Set(TEST_AUTHORING_POLICIES);
|
|
104
121
|
const ALLOWED_TESTING_POLICIES = new Set(['behavior_contract']);
|
|
@@ -1079,6 +1096,9 @@ function validateSkillIndexRoutes(projectRoot, commandsToml, skillFiles, issues)
|
|
|
1079
1096
|
const routedSkillPaths = new Set();
|
|
1080
1097
|
const expectedSkillPaths = new Set(skillFiles.map((relativePath) => `.mustflow/skills/${relativePath}`));
|
|
1081
1098
|
const seenSkillPaths = new Set();
|
|
1099
|
+
for (const warning of findSkillRouteConflictWarnings(skillRoutes)) {
|
|
1100
|
+
pushStrictWarning(issues, `${SKILL_INDEX_PATH} ${warning}`);
|
|
1101
|
+
}
|
|
1082
1102
|
for (const route of skillRoutes) {
|
|
1083
1103
|
if (!route.skillPath.startsWith('.mustflow/skills/') || !route.skillPath.endsWith('/SKILL.md')) {
|
|
1084
1104
|
pushStrictIssue(issues, `${SKILL_INDEX_PATH} route "${route.skillPath}" must point to .mustflow/skills/<name>/SKILL.md`);
|
|
@@ -1223,6 +1243,94 @@ function validateStrictVerificationSelectionAuthority(preferencesToml, issues) {
|
|
|
1223
1243
|
}
|
|
1224
1244
|
}
|
|
1225
1245
|
}
|
|
1246
|
+
function validateNoTestSelectionCommandAuthorityFields(label, table, issues) {
|
|
1247
|
+
for (const field of FORBIDDEN_TEST_SELECTION_COMMAND_AUTHORITY_FIELDS) {
|
|
1248
|
+
if (hasOwn(table, field)) {
|
|
1249
|
+
pushStrictIssue(issues, `${TEST_SELECTION_CONFIG_PATH} ${label}.${field} cannot define command authority; use .mustflow/config/commands.toml`);
|
|
1250
|
+
}
|
|
1251
|
+
}
|
|
1252
|
+
}
|
|
1253
|
+
function validateTestSelectionIntentReference(value, label, commandsToml, issues) {
|
|
1254
|
+
if (typeof value !== 'string' || value.trim().length === 0) {
|
|
1255
|
+
pushStrictIssue(issues, `${TEST_SELECTION_CONFIG_PATH} ${label} must be a command intent name`);
|
|
1256
|
+
return;
|
|
1257
|
+
}
|
|
1258
|
+
if (!isDeclaredCommandIntent(commandsToml, value)) {
|
|
1259
|
+
pushStrictIssue(issues, `${TEST_SELECTION_CONFIG_PATH} ${label} references unknown command intent "${value}"`);
|
|
1260
|
+
return;
|
|
1261
|
+
}
|
|
1262
|
+
if (!isConfiguredCommandIntent(commandsToml, value)) {
|
|
1263
|
+
pushStrictIssue(issues, `${TEST_SELECTION_CONFIG_PATH} ${label} references command intent "${value}" that is not configured`);
|
|
1264
|
+
}
|
|
1265
|
+
}
|
|
1266
|
+
function validateTestSelectionRule(rule, index, commandsToml, issues) {
|
|
1267
|
+
const label = `rules[${index}]`;
|
|
1268
|
+
if (!isRecord(rule)) {
|
|
1269
|
+
pushStrictIssue(issues, `${TEST_SELECTION_CONFIG_PATH} ${label} must be a TOML table`);
|
|
1270
|
+
return;
|
|
1271
|
+
}
|
|
1272
|
+
validateNoTestSelectionCommandAuthorityFields(label, rule, issues);
|
|
1273
|
+
validateRequiredStringField(rule, 'id', `${TEST_SELECTION_CONFIG_PATH} ${label}.id`, issues);
|
|
1274
|
+
validateRequiredStringField(rule, 'reason', `${TEST_SELECTION_CONFIG_PATH} ${label}.reason`, issues);
|
|
1275
|
+
validateRequiredStringField(rule, 'risk', `${TEST_SELECTION_CONFIG_PATH} ${label}.risk`, issues);
|
|
1276
|
+
validateAllowedStringField(rule, 'risk', `${TEST_SELECTION_CONFIG_PATH} ${label}.risk`, ALLOWED_TEST_SELECTION_RISKS, issues);
|
|
1277
|
+
const match = validateNestedTable(rule, 'match', `${TEST_SELECTION_CONFIG_PATH} ${label}.match`, issues);
|
|
1278
|
+
if (!hasOwn(rule, 'match')) {
|
|
1279
|
+
pushStrictIssue(issues, `${TEST_SELECTION_CONFIG_PATH} ${label} must define match`);
|
|
1280
|
+
}
|
|
1281
|
+
if (match) {
|
|
1282
|
+
validateNoTestSelectionCommandAuthorityFields(`${label}.match`, match, issues);
|
|
1283
|
+
validatePathArrayField(match, 'paths', `${TEST_SELECTION_CONFIG_PATH} ${label}.match.paths`, issues);
|
|
1284
|
+
validateStringArrayField(match, 'surfaces', `${TEST_SELECTION_CONFIG_PATH} ${label}.match.surfaces`, issues);
|
|
1285
|
+
if (!hasOwn(match, 'paths')) {
|
|
1286
|
+
pushStrictIssue(issues, `${TEST_SELECTION_CONFIG_PATH} ${label}.match must define paths`);
|
|
1287
|
+
}
|
|
1288
|
+
if (!hasOwn(match, 'surfaces')) {
|
|
1289
|
+
pushStrictIssue(issues, `${TEST_SELECTION_CONFIG_PATH} ${label}.match must define surfaces`);
|
|
1290
|
+
}
|
|
1291
|
+
}
|
|
1292
|
+
const select = validateNestedTable(rule, 'select', `${TEST_SELECTION_CONFIG_PATH} ${label}.select`, issues);
|
|
1293
|
+
if (!hasOwn(rule, 'select')) {
|
|
1294
|
+
pushStrictIssue(issues, `${TEST_SELECTION_CONFIG_PATH} ${label} must define select`);
|
|
1295
|
+
}
|
|
1296
|
+
if (select) {
|
|
1297
|
+
validateNoTestSelectionCommandAuthorityFields(`${label}.select`, select, issues);
|
|
1298
|
+
validateTestSelectionIntentReference(select.intent, `${label}.select.intent`, commandsToml, issues);
|
|
1299
|
+
validateTestSelectionIntentReference(select.fallback_intent, `${label}.select.fallback_intent`, commandsToml, issues);
|
|
1300
|
+
validatePathArrayField(select, 'test_targets', `${TEST_SELECTION_CONFIG_PATH} ${label}.select.test_targets`, issues);
|
|
1301
|
+
}
|
|
1302
|
+
}
|
|
1303
|
+
function validateStrictTestSelectionConfig(projectRoot, commandsToml, issues) {
|
|
1304
|
+
const configPath = path.join(projectRoot, ...TEST_SELECTION_CONFIG_PATH.split('/'));
|
|
1305
|
+
if (!existsSync(configPath)) {
|
|
1306
|
+
return;
|
|
1307
|
+
}
|
|
1308
|
+
let parsed;
|
|
1309
|
+
try {
|
|
1310
|
+
parsed = readTomlFile(configPath);
|
|
1311
|
+
}
|
|
1312
|
+
catch (error) {
|
|
1313
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1314
|
+
pushStrictIssue(issues, `Invalid TOML in ${TEST_SELECTION_CONFIG_PATH}: ${message}`);
|
|
1315
|
+
return;
|
|
1316
|
+
}
|
|
1317
|
+
if (!isRecord(parsed)) {
|
|
1318
|
+
pushStrictIssue(issues, `${TEST_SELECTION_CONFIG_PATH} must contain a TOML table`);
|
|
1319
|
+
return;
|
|
1320
|
+
}
|
|
1321
|
+
validateNoTestSelectionCommandAuthorityFields('root', parsed, issues);
|
|
1322
|
+
validateRequiredStringField(parsed, 'schema_version', `${TEST_SELECTION_CONFIG_PATH}.schema_version`, issues);
|
|
1323
|
+
if (parsed.schema_version !== '1') {
|
|
1324
|
+
pushStrictIssue(issues, `${TEST_SELECTION_CONFIG_PATH}.schema_version must be "1"`);
|
|
1325
|
+
}
|
|
1326
|
+
if (!Array.isArray(parsed.rules) || parsed.rules.length === 0) {
|
|
1327
|
+
pushStrictIssue(issues, `${TEST_SELECTION_CONFIG_PATH} must define [[rules]]`);
|
|
1328
|
+
return;
|
|
1329
|
+
}
|
|
1330
|
+
for (const [index, rule] of parsed.rules.entries()) {
|
|
1331
|
+
validateTestSelectionRule(rule, index, commandsToml, issues);
|
|
1332
|
+
}
|
|
1333
|
+
}
|
|
1226
1334
|
function validateStrictCandidateContractModelConfigs(projectRoot, issues) {
|
|
1227
1335
|
for (const model of getContractModelDefinitions()) {
|
|
1228
1336
|
const configPath = path.join(projectRoot, ...model.filePath.split('/'));
|
|
@@ -1619,6 +1727,7 @@ function validateStrict(projectRoot, parsed, issues) {
|
|
|
1619
1727
|
validateStrictReleaseVersioningAuthority(parsed.preferencesToml, issues);
|
|
1620
1728
|
validateStrictVerificationSelectionAuthority(parsed.preferencesToml, issues);
|
|
1621
1729
|
validateStrictCandidateContractModelConfigs(projectRoot, issues);
|
|
1730
|
+
validateStrictTestSelectionConfig(projectRoot, parsed.commandsToml, issues);
|
|
1622
1731
|
validateStrictVersionSources(projectRoot, parsed.preferencesToml, parsed.versioningToml, issues);
|
|
1623
1732
|
validateStrictTemplateVersionSync(projectRoot, parsed.preferencesToml, issues);
|
|
1624
1733
|
validateStrictManagedMarkdownIdentities(projectRoot, issues);
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
export class BoundedOutputBuffer {
|
|
2
|
+
#maxTailBytes;
|
|
3
|
+
#chunks = [];
|
|
4
|
+
#tailBytes = 0;
|
|
5
|
+
#bytes = 0;
|
|
6
|
+
constructor(maxTailBytes) {
|
|
7
|
+
this.#maxTailBytes = Math.max(0, maxTailBytes);
|
|
8
|
+
}
|
|
9
|
+
append(chunk) {
|
|
10
|
+
const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk, 'utf8');
|
|
11
|
+
this.#bytes += buffer.byteLength;
|
|
12
|
+
if (this.#maxTailBytes === 0 || buffer.byteLength === 0) {
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
this.#chunks.push(buffer);
|
|
16
|
+
this.#tailBytes += buffer.byteLength;
|
|
17
|
+
while (this.#tailBytes > this.#maxTailBytes && this.#chunks.length > 0) {
|
|
18
|
+
const first = this.#chunks[0];
|
|
19
|
+
const overflow = this.#tailBytes - this.#maxTailBytes;
|
|
20
|
+
if (!first) {
|
|
21
|
+
break;
|
|
22
|
+
}
|
|
23
|
+
if (first.byteLength <= overflow) {
|
|
24
|
+
this.#chunks.shift();
|
|
25
|
+
this.#tailBytes -= first.byteLength;
|
|
26
|
+
continue;
|
|
27
|
+
}
|
|
28
|
+
this.#chunks[0] = first.subarray(overflow);
|
|
29
|
+
this.#tailBytes -= overflow;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
toSnapshot() {
|
|
33
|
+
return {
|
|
34
|
+
bytes: this.#bytes,
|
|
35
|
+
tail: Buffer.concat(this.#chunks, this.#tailBytes).toString('utf8'),
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
}
|