mustflow 2.22.5 → 2.22.12
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 +8 -0
- package/dist/cli/commands/check.js +2 -2
- package/dist/cli/commands/classify.js +2 -0
- package/dist/cli/commands/dashboard.js +9 -69
- package/dist/cli/commands/help.js +1 -3
- package/dist/cli/commands/run/receipt.js +1 -0
- package/dist/cli/commands/run.js +14 -1
- package/dist/cli/commands/verify/evidence-input.js +269 -0
- package/dist/cli/commands/verify/input.js +212 -0
- package/dist/cli/commands/verify/state-paths.js +33 -0
- package/dist/cli/commands/verify.js +29 -511
- package/dist/cli/i18n/en.js +3 -0
- package/dist/cli/i18n/es.js +3 -0
- package/dist/cli/i18n/fr.js +3 -0
- package/dist/cli/i18n/hi.js +3 -0
- package/dist/cli/i18n/ko.js +3 -0
- package/dist/cli/i18n/zh.js +3 -0
- package/dist/cli/lib/dashboard-export.js +2 -0
- package/dist/cli/lib/dashboard-mutations.js +79 -0
- package/dist/cli/lib/doc-review-ledger.js +1 -3
- package/dist/cli/lib/local-index/command-effect-index.js +25 -0
- package/dist/cli/lib/local-index/hashing.js +7 -0
- package/dist/cli/lib/local-index/index.js +127 -826
- package/dist/cli/lib/local-index/source-index.js +137 -0
- package/dist/cli/lib/local-index/verification-evidence.js +451 -0
- package/dist/cli/lib/local-index/workflow-documents.js +204 -0
- package/dist/cli/lib/manifest-lock.js +1 -3
- package/dist/cli/lib/repo-map-frontmatter.js +53 -0
- package/dist/cli/lib/repo-map.js +10 -57
- package/dist/cli/lib/run-root-trust.js +27 -0
- package/dist/cli/lib/validation/index.js +6 -2
- package/dist/core/change-classification-policy.js +47 -0
- package/dist/core/change-classification.js +10 -43
- package/dist/core/check-issues.js +11 -7
- package/dist/core/command-contract-validation.js +22 -20
- package/dist/core/contract-lint.js +6 -2
- package/dist/core/correlation-id.js +16 -0
- package/dist/core/run-receipt.js +1 -0
- package/package.json +4 -1
- package/schemas/README.md +4 -0
- package/schemas/change-verification-report.schema.json +4 -0
- package/schemas/classify-report.schema.json +4 -0
- package/schemas/dashboard-export.schema.json +4 -0
- package/schemas/latest-run-pointer.schema.json +4 -0
- package/schemas/run-receipt.schema.json +4 -0
- package/schemas/verify-report.schema.json +4 -0
- package/schemas/verify-run-manifest.schema.json +4 -0
- package/templates/default/i18n.toml +3 -3
- package/templates/default/locales/en/.mustflow/skills/architecture-deepening-review/SKILL.md +25 -2
- package/templates/default/locales/en/.mustflow/skills/security-privacy-review/SKILL.md +9 -1
- package/templates/default/locales/en/.mustflow/skills/test-design-guard/SKILL.md +9 -1
- package/templates/default/locales/ko/.mustflow/context/INDEX.md +12 -12
- package/templates/default/locales/ko/.mustflow/context/PROJECT.md +6 -6
- package/templates/default/locales/ko/.mustflow/docs/agent-workflow.md +70 -70
- package/templates/default/locales/ko/AGENTS.md +14 -14
- package/templates/default/manifest.toml +1 -1
package/dist/cli/i18n/en.js
CHANGED
|
@@ -652,6 +652,7 @@ Read these files before working:
|
|
|
652
652
|
"run.help.option.dryRun": "Print a non-executing command plan",
|
|
653
653
|
"run.help.option.planOnly": "Alias for --dry-run",
|
|
654
654
|
"run.help.option.json": "Print the run record or command plan as JSON",
|
|
655
|
+
"run.help.option.allowUntrustedRoot": "Allow one execution from a root with a missing or invalid manifest lock after manual review",
|
|
655
656
|
"run.help.exit.ok": "The command completed with an allowed exit code",
|
|
656
657
|
"run.help.exit.fail": "The command was invalid, refused, timed out, or failed",
|
|
657
658
|
"run.label.suggestedIntentSnippet": "Suggested command contract snippet",
|
|
@@ -674,6 +675,8 @@ Read these files before working:
|
|
|
674
675
|
"run.error.maxOutputBytes": 'Command "{intent}" has invalid max_output_bytes. {detail}',
|
|
675
676
|
"run.error.maxOutputBytesDetail": "The output limit must stay within the allowed maximum.",
|
|
676
677
|
"run.error.conflictingPreviewModes": "Use either --dry-run or --plan-only, not both",
|
|
678
|
+
"run.error.untrustedRootMissing": "Refused to execute commands because {path} is missing. Run mf init/update to install the workflow, or pass --allow-untrusted-root after reviewing AGENTS.md and .mustflow/config/commands.toml.",
|
|
679
|
+
"run.error.untrustedRootInvalid": "Refused to execute commands because the manifest lock is invalid: {detail}. Restore or regenerate it, or pass --allow-untrusted-root after reviewing AGENTS.md and .mustflow/config/commands.toml.",
|
|
677
680
|
"run.error.timedOut": 'Command "{intent}" timed out after {seconds} seconds',
|
|
678
681
|
"run.error.outputLimitExceeded": 'Command "{intent}" exceeded max_output_bytes: {message}',
|
|
679
682
|
"run.error.startFailed": 'Command "{intent}" failed to start: {message}',
|
package/dist/cli/i18n/es.js
CHANGED
|
@@ -652,6 +652,7 @@ Lee estos archivos antes de trabajar:
|
|
|
652
652
|
"run.help.option.dryRun": "Imprime un plan de comando sin ejecutarlo",
|
|
653
653
|
"run.help.option.planOnly": "Alias de --dry-run",
|
|
654
654
|
"run.help.option.json": "Imprime el registro de ejecución o el plan de comando como JSON",
|
|
655
|
+
"run.help.option.allowUntrustedRoot": "Permite una ejecución desde una raíz con bloqueo de manifiesto ausente o inválido tras revisión manual",
|
|
655
656
|
"run.help.exit.ok": "El comando se completo con un codigo de salida permitido",
|
|
656
657
|
"run.help.exit.fail": "El comando no era válido, fue rechazado, agotó el tiempo o falló",
|
|
657
658
|
"run.label.suggestedIntentSnippet": "Snippet sugerido para el contrato de comandos",
|
|
@@ -674,6 +675,8 @@ Lee estos archivos antes de trabajar:
|
|
|
674
675
|
"run.error.maxOutputBytes": 'El comando "{intent}" tiene max_output_bytes no válido. {detail}',
|
|
675
676
|
"run.error.maxOutputBytesDetail": "El límite de salida debe permanecer dentro del máximo permitido.",
|
|
676
677
|
"run.error.conflictingPreviewModes": "Usa --dry-run o --plan-only, no ambos",
|
|
678
|
+
"run.error.untrustedRootMissing": "Se rechazó ejecutar comandos porque falta {path}. Ejecuta mf init/update para instalar el flujo, o usa --allow-untrusted-root tras revisar AGENTS.md y .mustflow/config/commands.toml.",
|
|
679
|
+
"run.error.untrustedRootInvalid": "Se rechazó ejecutar comandos porque el bloqueo de manifiesto no es válido: {detail}. Restáuralo o regenéralo, o usa --allow-untrusted-root tras revisar AGENTS.md y .mustflow/config/commands.toml.",
|
|
677
680
|
"run.error.timedOut": 'El comando "{intent}" agotó el tiempo después de {seconds} segundos',
|
|
678
681
|
"run.error.outputLimitExceeded": 'El comando "{intent}" superó max_output_bytes: {message}',
|
|
679
682
|
"run.error.startFailed": 'No se pudo iniciar el comando "{intent}": {message}',
|
package/dist/cli/i18n/fr.js
CHANGED
|
@@ -652,6 +652,7 @@ Lisez ces fichiers avant de travailler :
|
|
|
652
652
|
"run.help.option.dryRun": "Imprime un plan de commande sans l'exécuter",
|
|
653
653
|
"run.help.option.planOnly": "Alias de --dry-run",
|
|
654
654
|
"run.help.option.json": "Imprime l'enregistrement d'exécution ou le plan de commande en JSON",
|
|
655
|
+
"run.help.option.allowUntrustedRoot": "Autorise une seule exécution depuis une racine sans verrou de manifeste valide après revue manuelle",
|
|
655
656
|
"run.help.exit.ok": "La commande s'est terminée avec un code de sortie autorisé",
|
|
656
657
|
"run.help.exit.fail": "La commande était non valide, refusée, expirée ou a échoué",
|
|
657
658
|
"run.label.suggestedIntentSnippet": "Extrait suggéré de contrat de commande",
|
|
@@ -674,6 +675,8 @@ Lisez ces fichiers avant de travailler :
|
|
|
674
675
|
"run.error.maxOutputBytes": 'La commande "{intent}" a une valeur max_output_bytes non valide. {detail}',
|
|
675
676
|
"run.error.maxOutputBytesDetail": "La limite de sortie doit rester dans le maximum autorisé.",
|
|
676
677
|
"run.error.conflictingPreviewModes": "Utilisez --dry-run ou --plan-only, pas les deux",
|
|
678
|
+
"run.error.untrustedRootMissing": "Exécution refusée car {path} est absent. Lancez mf init/update pour installer le workflow, ou ajoutez --allow-untrusted-root après avoir relu AGENTS.md et .mustflow/config/commands.toml.",
|
|
679
|
+
"run.error.untrustedRootInvalid": "Exécution refusée car le verrou de manifeste est invalide : {detail}. Restaurez-le ou régénérez-le, ou ajoutez --allow-untrusted-root après avoir relu AGENTS.md et .mustflow/config/commands.toml.",
|
|
677
680
|
"run.error.timedOut": 'La commande "{intent}" a expiré après {seconds} secondes',
|
|
678
681
|
"run.error.outputLimitExceeded": 'La commande "{intent}" a dépassé max_output_bytes : {message}',
|
|
679
682
|
"run.error.startFailed": 'Impossible de démarrer la commande "{intent}" : {message}',
|
package/dist/cli/i18n/hi.js
CHANGED
|
@@ -652,6 +652,7 @@ export const hiMessages = {
|
|
|
652
652
|
"run.help.option.dryRun": "कमांड चलाए बिना उसका plan प्रिंट करें",
|
|
653
653
|
"run.help.option.planOnly": "--dry-run का alias",
|
|
654
654
|
"run.help.option.json": "Run record या command plan को JSON के रूप में प्रिंट करें",
|
|
655
|
+
"run.help.option.allowUntrustedRoot": "Manual review के बाद missing या invalid manifest lock वाली root से एक execution allow करें",
|
|
655
656
|
"run.help.exit.ok": "कमांड अनुमत exit code के साथ पूरी हुई",
|
|
656
657
|
"run.help.exit.fail": "कमांड अमान्य थी, अस्वीकार हुई, timed out हुई या विफल हुई",
|
|
657
658
|
"run.label.suggestedIntentSnippet": "Suggested command contract snippet",
|
|
@@ -674,6 +675,8 @@ export const hiMessages = {
|
|
|
674
675
|
"run.error.maxOutputBytes": 'कमांड "{intent}" में max_output_bytes अमान्य है। {detail}',
|
|
675
676
|
"run.error.maxOutputBytesDetail": "Output limit अनुमत maximum के अंदर रहनी चाहिए।",
|
|
676
677
|
"run.error.conflictingPreviewModes": "--dry-run या --plan-only में से एक इस्तेमाल करें, दोनों नहीं",
|
|
678
|
+
"run.error.untrustedRootMissing": "{path} missing है, इसलिए commands execute करने से मना किया गया। Workflow install करने के लिए mf init/update चलाएँ, या AGENTS.md और .mustflow/config/commands.toml review करने के बाद --allow-untrusted-root पास करें।",
|
|
679
|
+
"run.error.untrustedRootInvalid": "Manifest lock invalid है, इसलिए commands execute करने से मना किया गया: {detail}. इसे restore या regenerate करें, या AGENTS.md और .mustflow/config/commands.toml review करने के बाद --allow-untrusted-root पास करें।",
|
|
677
680
|
"run.error.timedOut": 'कमांड "{intent}" {seconds} सेकंड बाद time out हुई',
|
|
678
681
|
"run.error.outputLimitExceeded": 'कमांड "{intent}" ने max_output_bytes सीमा पार की: {message}',
|
|
679
682
|
"run.error.startFailed": 'कमांड "{intent}" शुरू नहीं हो सकी: {message}',
|
package/dist/cli/i18n/ko.js
CHANGED
|
@@ -652,6 +652,7 @@ export const koMessages = {
|
|
|
652
652
|
"run.help.option.dryRun": "실행하지 않고 명령 계획을 출력합니다",
|
|
653
653
|
"run.help.option.planOnly": "--dry-run과 같은 동작입니다",
|
|
654
654
|
"run.help.option.json": "실행 결과 또는 명령 계획을 JSON으로 출력합니다",
|
|
655
|
+
"run.help.option.allowUntrustedRoot": "잠금 파일이 없거나 올바르지 않은 루트에서 수동 검토 후 이번 실행만 허용합니다",
|
|
655
656
|
"run.help.exit.ok": "명령이 허용된 종료 코드로 완료되었습니다",
|
|
656
657
|
"run.help.exit.fail": "명령이 잘못되었거나, 거부되었거나, 시간 초과되었거나, 실패했습니다",
|
|
657
658
|
"run.label.suggestedIntentSnippet": "제안 명령 계약 조각",
|
|
@@ -674,6 +675,8 @@ export const koMessages = {
|
|
|
674
675
|
"run.error.maxOutputBytes": '명령 "{intent}"의 max_output_bytes 값이 올바르지 않습니다. {detail}',
|
|
675
676
|
"run.error.maxOutputBytesDetail": "출력 상한은 허용된 최댓값 안에 있어야 합니다.",
|
|
676
677
|
"run.error.conflictingPreviewModes": "--dry-run과 --plan-only 중 하나만 사용하세요",
|
|
678
|
+
"run.error.untrustedRootMissing": "{path}이 없어 명령 실행을 거부했습니다. mf init/update로 워크플로우를 설치하거나, AGENTS.md와 .mustflow/config/commands.toml을 검토한 뒤 --allow-untrusted-root를 붙이세요.",
|
|
679
|
+
"run.error.untrustedRootInvalid": "잠금 파일이 올바르지 않아 명령 실행을 거부했습니다: {detail}. 파일을 복구하거나 다시 생성하거나, AGENTS.md와 .mustflow/config/commands.toml을 검토한 뒤 --allow-untrusted-root를 붙이세요.",
|
|
677
680
|
"run.error.timedOut": '명령 "{intent}"가 {seconds}초 뒤 시간 초과되었습니다',
|
|
678
681
|
"run.error.outputLimitExceeded": '명령 "{intent}"가 max_output_bytes 제한을 넘었습니다: {message}',
|
|
679
682
|
"run.error.startFailed": '명령 "{intent}"를 시작하지 못했습니다: {message}',
|
package/dist/cli/i18n/zh.js
CHANGED
|
@@ -652,6 +652,7 @@ export const zhMessages = {
|
|
|
652
652
|
"run.help.option.dryRun": "输出命令计划但不执行",
|
|
653
653
|
"run.help.option.planOnly": "--dry-run 的别名",
|
|
654
654
|
"run.help.option.json": "将运行记录或命令计划输出为 JSON",
|
|
655
|
+
"run.help.option.allowUntrustedRoot": "人工复核后,允许从缺失或无效清单锁的根目录执行一次命令",
|
|
655
656
|
"run.help.exit.ok": "命令已以允许的退出码完成",
|
|
656
657
|
"run.help.exit.fail": "命令无效、被拒绝、超时或失败",
|
|
657
658
|
"run.label.suggestedIntentSnippet": "建议的命令契约片段",
|
|
@@ -674,6 +675,8 @@ export const zhMessages = {
|
|
|
674
675
|
"run.error.maxOutputBytes": '命令 "{intent}" 的 max_output_bytes 无效。{detail}',
|
|
675
676
|
"run.error.maxOutputBytesDetail": "输出限制必须保持在允许的最大值内。",
|
|
676
677
|
"run.error.conflictingPreviewModes": "只能使用 --dry-run 或 --plan-only,不能同时使用",
|
|
678
|
+
"run.error.untrustedRootMissing": "已拒绝执行命令,因为缺少 {path}。请运行 mf init/update 安装工作流,或在检查 AGENTS.md 和 .mustflow/config/commands.toml 后传入 --allow-untrusted-root。",
|
|
679
|
+
"run.error.untrustedRootInvalid": "已拒绝执行命令,因为清单锁无效:{detail}。请恢复或重新生成它,或在检查 AGENTS.md 和 .mustflow/config/commands.toml 后传入 --allow-untrusted-root。",
|
|
677
680
|
"run.error.timedOut": '命令 "{intent}" 在 {seconds} 秒后超时',
|
|
678
681
|
"run.error.outputLimitExceeded": '命令 "{intent}" 超过 max_output_bytes:{message}',
|
|
679
682
|
"run.error.startFailed": '命令 "{intent}" 启动失败:{message}',
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Buffer } from 'node:buffer';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { createDashboardCompletionVerdict } from '../../core/completion-verdict.js';
|
|
4
|
+
import { createCorrelationId } from '../../core/correlation-id.js';
|
|
4
5
|
import { createDashboardEvidenceModel } from '../../core/verification-evidence.js';
|
|
5
6
|
import { redactSecretLikeText } from '../../core/secret-redaction.js';
|
|
6
7
|
import { ensureFileTargetInsideWithoutSymlinks, ensureInside, toPosixPath, writeUtf8FileInsideWithoutSymlinks, } from './filesystem.js';
|
|
@@ -487,6 +488,7 @@ export function createDashboardExportSnapshot(input) {
|
|
|
487
488
|
const snapshot = {
|
|
488
489
|
schema_version: '1',
|
|
489
490
|
command: 'dashboard export',
|
|
491
|
+
correlation_id: createCorrelationId('dashboard'),
|
|
490
492
|
format: input.format,
|
|
491
493
|
generated_at: new Date().toISOString(),
|
|
492
494
|
mustflow_root: input.projectRoot,
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { openPathInFileManager } from './browser-open.js';
|
|
4
|
+
import { updateDashboardPreferences, } from './dashboard-preferences.js';
|
|
5
|
+
import { isReviewerKind, markDocReviewEntry, } from './doc-review-ledger.js';
|
|
6
|
+
const DOC_REVIEW_BULK_PAYLOAD_FIELDS = ['paths', 'documents', 'entries'];
|
|
7
|
+
function readPreferenceUpdatePayload(value) {
|
|
8
|
+
if (typeof value !== 'object' || value === null || Array.isArray(value)) {
|
|
9
|
+
throw new Error('Request body must be a JSON object.');
|
|
10
|
+
}
|
|
11
|
+
const updates = value.updates;
|
|
12
|
+
if (!Array.isArray(updates)) {
|
|
13
|
+
throw new Error('Request body must include an updates array.');
|
|
14
|
+
}
|
|
15
|
+
return updates.map((entry) => {
|
|
16
|
+
if (typeof entry !== 'object' || entry === null || Array.isArray(entry)) {
|
|
17
|
+
throw new Error('Each update must be a JSON object.');
|
|
18
|
+
}
|
|
19
|
+
const update = entry;
|
|
20
|
+
if (typeof update.id !== 'string' || update.id.trim().length === 0) {
|
|
21
|
+
throw new Error('Each update must include an id.');
|
|
22
|
+
}
|
|
23
|
+
return { id: update.id, value: update.value };
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
function readOptionalStringField(value, key) {
|
|
27
|
+
const field = value[key];
|
|
28
|
+
return typeof field === 'string' && field.trim().length > 0 ? field.trim() : undefined;
|
|
29
|
+
}
|
|
30
|
+
function readRequiredStringField(value, key) {
|
|
31
|
+
const field = readOptionalStringField(value, key);
|
|
32
|
+
if (!field) {
|
|
33
|
+
throw new Error(`${key} is required.`);
|
|
34
|
+
}
|
|
35
|
+
return field;
|
|
36
|
+
}
|
|
37
|
+
function readDocReviewPayload(value) {
|
|
38
|
+
if (typeof value !== 'object' || value === null || Array.isArray(value)) {
|
|
39
|
+
throw new Error('Request body must be a JSON object.');
|
|
40
|
+
}
|
|
41
|
+
const payload = value;
|
|
42
|
+
for (const field of DOC_REVIEW_BULK_PAYLOAD_FIELDS) {
|
|
43
|
+
if (field in payload) {
|
|
44
|
+
throw new Error('Bulk documentation review updates require a separate confirmed flow.');
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
const status = readRequiredStringField(payload, 'status');
|
|
48
|
+
if (status !== 'approved' && status !== 'needs_human' && status !== 'ignored') {
|
|
49
|
+
throw new Error('status must be approved, needs_human, or ignored.');
|
|
50
|
+
}
|
|
51
|
+
const reviewerKind = readRequiredStringField(payload, 'reviewerKind');
|
|
52
|
+
if (!isReviewerKind(reviewerKind)) {
|
|
53
|
+
throw new Error('reviewerKind must be human, llm, tool, or external.');
|
|
54
|
+
}
|
|
55
|
+
return {
|
|
56
|
+
path: readRequiredStringField(payload, 'path'),
|
|
57
|
+
status,
|
|
58
|
+
reviewerKind,
|
|
59
|
+
reviewerId: readRequiredStringField(payload, 'reviewerId'),
|
|
60
|
+
reviewerLabel: readOptionalStringField(payload, 'reviewerLabel'),
|
|
61
|
+
reviewerProvider: readOptionalStringField(payload, 'reviewerProvider'),
|
|
62
|
+
reviewerModel: readOptionalStringField(payload, 'reviewerModel'),
|
|
63
|
+
reviewerCommandIntent: readOptionalStringField(payload, 'reviewerCommandIntent'),
|
|
64
|
+
summary: readOptionalStringField(payload, 'summary'),
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
export function updateDashboardPreferencesFromPayload(projectRoot, payload) {
|
|
68
|
+
return updateDashboardPreferences(projectRoot, readPreferenceUpdatePayload(payload));
|
|
69
|
+
}
|
|
70
|
+
export function markDashboardDocReviewFromPayload(projectRoot, payload) {
|
|
71
|
+
markDocReviewEntry(projectRoot, readDocReviewPayload(payload));
|
|
72
|
+
}
|
|
73
|
+
export function openDashboardMustflowFolder(projectRoot) {
|
|
74
|
+
const mustflowPath = path.join(projectRoot, '.mustflow');
|
|
75
|
+
if (!existsSync(mustflowPath)) {
|
|
76
|
+
return 'missing';
|
|
77
|
+
}
|
|
78
|
+
return openPathInFileManager(mustflowPath) ? 'opened' : 'unavailable';
|
|
79
|
+
}
|
|
@@ -2,14 +2,12 @@ import { existsSync } from 'node:fs';
|
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { triageDocReview } from '../../core/doc-review-triage.js';
|
|
4
4
|
import { ensureInside, ensureInsideWithoutSymlinks, readUtf8FileInsideWithoutSymlinks, toPosixPath, writeUtf8FileInsideWithoutSymlinks, } from './filesystem.js';
|
|
5
|
+
import { isRecord } from './command-contract.js';
|
|
5
6
|
import { parseTomlText, stringifyToml } from './toml.js';
|
|
6
7
|
export const DOC_REVIEW_LEDGER_RELATIVE_PATH = '.mustflow/review/docs.toml';
|
|
7
8
|
export const DOC_REVIEW_STATUSES = ['pending', 'in_review', 'changes_made', 'approved', 'needs_human', 'ignored'];
|
|
8
9
|
export const DOC_REVIEW_ACTIVE_STATUSES = ['pending', 'in_review', 'changes_made', 'needs_human'];
|
|
9
10
|
export const REVIEWER_KINDS = ['human', 'llm', 'tool', 'external'];
|
|
10
|
-
function isRecord(value) {
|
|
11
|
-
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
12
|
-
}
|
|
13
11
|
function readOptionalString(value) {
|
|
14
12
|
return typeof value === 'string' && value.trim().length > 0 ? value : undefined;
|
|
15
13
|
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { isRecord, readCommandContract, readString } from '../command-contract.js';
|
|
4
|
+
import { normalizeCommandEffects } from '../../../core/command-effects.js';
|
|
5
|
+
export function collectCommandIntents(projectRoot) {
|
|
6
|
+
if (!existsSync(path.join(projectRoot, '.mustflow', 'config', 'commands.toml'))) {
|
|
7
|
+
return [];
|
|
8
|
+
}
|
|
9
|
+
const contract = readCommandContract(projectRoot);
|
|
10
|
+
const intents = [];
|
|
11
|
+
for (const [name, intent] of Object.entries(contract.intents).sort(([left], [right]) => left.localeCompare(right))) {
|
|
12
|
+
if (!isRecord(intent)) {
|
|
13
|
+
continue;
|
|
14
|
+
}
|
|
15
|
+
intents.push({
|
|
16
|
+
name,
|
|
17
|
+
status: readString(intent, 'status') ?? 'unknown',
|
|
18
|
+
lifecycle: readString(intent, 'lifecycle') ?? null,
|
|
19
|
+
runPolicy: readString(intent, 'run_policy') ?? null,
|
|
20
|
+
description: readString(intent, 'description') ?? null,
|
|
21
|
+
effects: normalizeCommandEffects(projectRoot, contract, name),
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
return intents;
|
|
25
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
export function sha256Text(content) {
|
|
3
|
+
return `sha256:${createHash('sha256').update(content).digest('hex')}`;
|
|
4
|
+
}
|
|
5
|
+
export function sha256Bytes(content) {
|
|
6
|
+
return `sha256:${createHash('sha256').update(content).digest('hex')}`;
|
|
7
|
+
}
|