mustflow 2.103.3 → 2.103.10
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/run.js +11 -0
- package/dist/cli/i18n/en.js +2 -0
- package/dist/cli/i18n/es.js +2 -0
- package/dist/cli/i18n/fr.js +2 -0
- package/dist/cli/i18n/hi.js +2 -0
- package/dist/cli/i18n/ko.js +2 -0
- package/dist/cli/i18n/zh.js +2 -0
- package/dist/cli/lib/external-skill-import.js +78 -14
- package/dist/cli/lib/local-index/sql.js +9 -1
- package/dist/cli/lib/run-plan.js +37 -0
- package/dist/core/change-impact.js +16 -0
- package/dist/core/code-outline.js +3 -13
- package/dist/core/config-chain.js +3 -13
- package/dist/core/dependency-graph.js +3 -13
- package/dist/core/docs-link-integrity.js +23 -4
- package/dist/core/env-contract.js +3 -13
- package/dist/core/export-diff.js +3 -3
- package/dist/core/ignored-directories.js +40 -0
- package/dist/core/reference-drift.js +4 -2
- package/dist/core/related-files.js +3 -13
- package/dist/core/repo-merge-conflict-scan.js +3 -9
- package/dist/core/route-outline.js +3 -13
- package/dist/core/script-pack-suggestions.js +23 -12
- package/dist/core/secret-risk-scan.js +3 -13
- package/dist/core/skill-route-resolution.js +21 -1
- package/package.json +2 -2
- package/schemas/link-integrity-report.schema.json +1 -0
- package/schemas/reference-drift-report.schema.json +1 -0
- package/templates/default/i18n.toml +19 -7
- package/templates/default/locales/en/.mustflow/skills/ai-generated-code-hardening/SKILL.md +30 -7
- package/templates/default/locales/en/.mustflow/skills/api-request-performance-review/SKILL.md +12 -6
- package/templates/default/locales/en/.mustflow/skills/completion-evidence-gate/SKILL.md +20 -9
- package/templates/default/locales/en/.mustflow/skills/hot-path-performance-review/SKILL.md +20 -15
- package/templates/default/locales/en/.mustflow/skills/next-action-menu/SKILL.md +22 -7
- package/templates/default/locales/en/.mustflow/skills/quadratic-scan-review/SKILL.md +21 -19
- package/templates/default/locales/en/.mustflow/skills/react-code-change/SKILL.md +54 -8
- package/templates/default/locales/en/.mustflow/skills/vertical-slice-tdd/SKILL.md +22 -8
- package/templates/default/manifest.toml +1 -1
package/dist/cli/commands/run.js
CHANGED
|
@@ -79,6 +79,14 @@ function reportRunPlanFailure(plan, reporter, lang) {
|
|
|
79
79
|
detail: getRunPlanDetail(plan, lang, 'run.error.blockedLongRunningCommandDetail'),
|
|
80
80
|
});
|
|
81
81
|
break;
|
|
82
|
+
case 'network_requires_approval':
|
|
83
|
+
case 'destructive_requires_approval':
|
|
84
|
+
case 'approval_policy_unreadable':
|
|
85
|
+
message = t(lang, 'run.error.approvalRequired', {
|
|
86
|
+
intent: plan.intentName,
|
|
87
|
+
detail: getRunPlanDetail(plan, lang, 'run.error.approvalRequiredDetail'),
|
|
88
|
+
});
|
|
89
|
+
break;
|
|
82
90
|
case 'cwd_outside_project':
|
|
83
91
|
message = t(lang, 'run.error.cwdOutsideProject', {
|
|
84
92
|
intent: plan.intentName,
|
|
@@ -327,6 +335,9 @@ export async function runRun(args, reporter, lang = 'en', options = {}) {
|
|
|
327
335
|
return plan.ok ? 0 : 1;
|
|
328
336
|
}
|
|
329
337
|
if (!plan.ok) {
|
|
338
|
+
if (json) {
|
|
339
|
+
reporter.stdout(JSON.stringify(createRunPreview(plan, 'plan-only'), null, 2));
|
|
340
|
+
}
|
|
330
341
|
reportRunPlanFailure(plan, reporter, lang);
|
|
331
342
|
writeLatestProfile(profiler, options, {
|
|
332
343
|
projectRoot,
|
package/dist/cli/i18n/en.js
CHANGED
|
@@ -1238,6 +1238,8 @@ Read these files before working:
|
|
|
1238
1238
|
"run.error.blockedShellBackgroundDetail": "Shell commands must not spawn background work.",
|
|
1239
1239
|
"run.error.blockedLongRunningCommand": 'Intent "{intent}" is blocked. {detail}',
|
|
1240
1240
|
"run.error.blockedLongRunningCommandDetail": "Command argv must describe a finite one-shot command, not a development server, watcher, shell wrapper, interpreter loop, or background process.",
|
|
1241
|
+
"run.error.approvalRequired": 'Intent "{intent}" requires approval. {detail}',
|
|
1242
|
+
"run.error.approvalRequiredDetail": "The repository approval policy requires explicit approval before this command intent can run.",
|
|
1241
1243
|
"run.error.cwdOutsideProject": 'Command "{intent}" has an invalid cwd: {detail}',
|
|
1242
1244
|
"run.error.cwdOutsideProjectDetail": "Intent cwd must stay inside the current root.",
|
|
1243
1245
|
"run.error.invalidTestTarget": 'Command "{intent}" received an invalid test target. {detail}',
|
package/dist/cli/i18n/es.js
CHANGED
|
@@ -1238,6 +1238,8 @@ Lee estos archivos antes de trabajar:
|
|
|
1238
1238
|
"run.error.blockedShellBackgroundDetail": "Los comandos de shell no deben iniciar trabajo en segundo plano.",
|
|
1239
1239
|
"run.error.blockedLongRunningCommand": 'La intención "{intent}" está bloqueada. {detail}',
|
|
1240
1240
|
"run.error.blockedLongRunningCommandDetail": "argv debe describir un comando finito de una sola ejecución, no un servidor de desarrollo, watcher, envoltorio de shell, bucle de intérprete o proceso en segundo plano.",
|
|
1241
|
+
"run.error.approvalRequired": 'La intención "{intent}" requiere aprobación. {detail}',
|
|
1242
|
+
"run.error.approvalRequiredDetail": "La política de aprobación del repositorio requiere aprobación explícita antes de ejecutar esta intención de comando.",
|
|
1241
1243
|
"run.error.cwdOutsideProject": 'El comando "{intent}" tiene un cwd no válido: {detail}',
|
|
1242
1244
|
"run.error.cwdOutsideProjectDetail": "El cwd de la intención debe permanecer dentro de la raíz actual.",
|
|
1243
1245
|
"run.error.invalidTestTarget": 'El comando "{intent}" recibió un objetivo de prueba no válido. {detail}',
|
package/dist/cli/i18n/fr.js
CHANGED
|
@@ -1238,6 +1238,8 @@ Lisez ces fichiers avant de travailler :
|
|
|
1238
1238
|
"run.error.blockedShellBackgroundDetail": "Les commandes shell ne doivent pas lancer de travail en arrière-plan.",
|
|
1239
1239
|
"run.error.blockedLongRunningCommand": 'L’intention "{intent}" est bloquée. {detail}',
|
|
1240
1240
|
"run.error.blockedLongRunningCommandDetail": "argv doit décrire une commande ponctuelle finie, pas un serveur de développement, un watcher, un wrapper shell, une boucle d'interpréteur ou un processus en arrière-plan.",
|
|
1241
|
+
"run.error.approvalRequired": 'L’intention "{intent}" nécessite une approbation. {detail}',
|
|
1242
|
+
"run.error.approvalRequiredDetail": "La politique d’approbation du dépôt exige une approbation explicite avant d’exécuter cette intention de commande.",
|
|
1241
1243
|
"run.error.cwdOutsideProject": 'La commande "{intent}" a un cwd non valide : {detail}',
|
|
1242
1244
|
"run.error.cwdOutsideProjectDetail": "Le cwd de l’intention doit rester dans la racine actuelle.",
|
|
1243
1245
|
"run.error.invalidTestTarget": 'La commande "{intent}" a reçu une cible de test invalide. {detail}',
|
package/dist/cli/i18n/hi.js
CHANGED
|
@@ -1238,6 +1238,8 @@ export const hiMessages = {
|
|
|
1238
1238
|
"run.error.blockedShellBackgroundDetail": "Shell commands background work शुरू नहीं कर सकतीं।",
|
|
1239
1239
|
"run.error.blockedLongRunningCommand": 'इंटेंट "{intent}" अवरुद्ध है। {detail}',
|
|
1240
1240
|
"run.error.blockedLongRunningCommandDetail": "argv में finite one-shot command होना चाहिए, development server, watcher, shell wrapper, interpreter loop, या background process नहीं।",
|
|
1241
|
+
"run.error.approvalRequired": 'इंटेंट "{intent}" को approval चाहिए। {detail}',
|
|
1242
|
+
"run.error.approvalRequiredDetail": "Repository approval policy के अनुसार इस command intent को चलाने से पहले explicit approval चाहिए।",
|
|
1241
1243
|
"run.error.cwdOutsideProject": 'कमांड "{intent}" का cwd अमान्य है: {detail}',
|
|
1242
1244
|
"run.error.cwdOutsideProjectDetail": "Intent cwd current root के अंदर रहना चाहिए।",
|
|
1243
1245
|
"run.error.invalidTestTarget": 'कमांड "{intent}" को अमान्य test target मिला। {detail}',
|
package/dist/cli/i18n/ko.js
CHANGED
|
@@ -1238,6 +1238,8 @@ export const koMessages = {
|
|
|
1238
1238
|
"run.error.blockedShellBackgroundDetail": "셸 명령은 백그라운드 작업을 시작하면 안 됩니다.",
|
|
1239
1239
|
"run.error.blockedLongRunningCommand": '명령 의도 "{intent}"가 차단되었습니다. {detail}',
|
|
1240
1240
|
"run.error.blockedLongRunningCommandDetail": "argv는 개발 서버, 감시 명령, 셸 래퍼, 인터프리터 반복 작업, 백그라운드 프로세스가 아니라 끝나는 단발성 명령이어야 합니다.",
|
|
1241
|
+
"run.error.approvalRequired": '명령 의도 "{intent}"는 승인이 필요합니다. {detail}',
|
|
1242
|
+
"run.error.approvalRequiredDetail": "저장소 승인 정책상 이 명령 의도를 실행하기 전에 명시적 승인이 필요합니다.",
|
|
1241
1243
|
"run.error.cwdOutsideProject": '명령 "{intent}"의 실행 위치(cwd)가 올바르지 않습니다: {detail}',
|
|
1242
1244
|
"run.error.cwdOutsideProjectDetail": "명령 실행 위치(cwd)는 현재 루트 안에 있어야 합니다.",
|
|
1243
1245
|
"run.error.invalidTestTarget": '명령 "{intent}"에 올바르지 않은 테스트 대상이 전달되었습니다. {detail}',
|
package/dist/cli/i18n/zh.js
CHANGED
|
@@ -1238,6 +1238,8 @@ export const zhMessages = {
|
|
|
1238
1238
|
"run.error.blockedShellBackgroundDetail": "Shell 命令不得启动后台工作。",
|
|
1239
1239
|
"run.error.blockedLongRunningCommand": '意图 "{intent}" 已被阻止。{detail}',
|
|
1240
1240
|
"run.error.blockedLongRunningCommandDetail": "argv 必须描述会结束的单次命令,而不是开发服务器、监听命令、shell 包装器、解释器循环或后台进程。",
|
|
1241
|
+
"run.error.approvalRequired": '意图 "{intent}" 需要审批。{detail}',
|
|
1242
|
+
"run.error.approvalRequiredDetail": "仓库审批策略要求在运行此命令意图前获得明确审批。",
|
|
1241
1243
|
"run.error.cwdOutsideProject": '命令 "{intent}" 的 cwd 无效:{detail}',
|
|
1242
1244
|
"run.error.cwdOutsideProjectDetail": "意图 cwd 必须位于当前根目录内。",
|
|
1243
1245
|
"run.error.invalidTestTarget": '命令 "{intent}" 收到无效测试目标。{detail}',
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { existsSync } from 'node:fs';
|
|
1
|
+
import { existsSync, renameSync, rmSync } from 'node:fs';
|
|
2
2
|
import { createHash } from 'node:crypto';
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
import { ensureFileTargetInsideWithoutSymlinks, writeJsonFileInsideWithoutSymlinks, writeUtf8FileInsideWithoutSymlinks, } from '../../core/safe-filesystem.js';
|
|
@@ -185,6 +185,52 @@ function validateImportRelativeFilePath(relativePath) {
|
|
|
185
185
|
function normalizeTextContent(content) {
|
|
186
186
|
return content.replace(/\r\n?/gu, '\n');
|
|
187
187
|
}
|
|
188
|
+
function readFrontmatterParts(content) {
|
|
189
|
+
if (!content.startsWith('---')) {
|
|
190
|
+
return {
|
|
191
|
+
name: null,
|
|
192
|
+
description: null,
|
|
193
|
+
body: content,
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
const firstLineEnd = content.indexOf('\n');
|
|
197
|
+
const end = firstLineEnd >= 0 ? content.indexOf('\n---', firstLineEnd + 1) : -1;
|
|
198
|
+
if (firstLineEnd < 0 || end < 0) {
|
|
199
|
+
return {
|
|
200
|
+
name: null,
|
|
201
|
+
description: null,
|
|
202
|
+
body: content,
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
const frontmatter = content.slice(firstLineEnd + 1, end).split(/\r?\n/u);
|
|
206
|
+
const bodyStart = content.indexOf('\n', end + 1);
|
|
207
|
+
return {
|
|
208
|
+
name: readFrontmatterScalar(content, 'name'),
|
|
209
|
+
description: readFrontmatterScalar(content, 'description'),
|
|
210
|
+
body: bodyStart >= 0 ? content.slice(bodyStart + 1) : '',
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
function renderYamlScalar(key, value) {
|
|
214
|
+
return `${key}: ${JSON.stringify(value)}`;
|
|
215
|
+
}
|
|
216
|
+
function sanitizeExternalSkillMarkdown(content) {
|
|
217
|
+
const frontmatter = readFrontmatterParts(content);
|
|
218
|
+
const lines = [
|
|
219
|
+
'---',
|
|
220
|
+
...(frontmatter.name ? [renderYamlScalar('name', frontmatter.name)] : []),
|
|
221
|
+
...(frontmatter.description ? [renderYamlScalar('description', frontmatter.description)] : []),
|
|
222
|
+
'external_authority: untrusted',
|
|
223
|
+
'---',
|
|
224
|
+
frontmatter.body,
|
|
225
|
+
];
|
|
226
|
+
return lines.join('\n');
|
|
227
|
+
}
|
|
228
|
+
function normalizeImportedSkillFiles(files) {
|
|
229
|
+
return files.map((file) => ({
|
|
230
|
+
...file,
|
|
231
|
+
content: file.relativePath === 'SKILL.md' ? sanitizeExternalSkillMarkdown(file.content) : file.content,
|
|
232
|
+
}));
|
|
233
|
+
}
|
|
188
234
|
function hashContent(content) {
|
|
189
235
|
return `sha256:${createHash('sha256').update(content).digest('hex')}`;
|
|
190
236
|
}
|
|
@@ -314,21 +360,38 @@ function createTarget(skillName) {
|
|
|
314
360
|
};
|
|
315
361
|
}
|
|
316
362
|
function writeImportedSkillFiles(projectRoot, target, source, files, fileReport, warnings) {
|
|
317
|
-
const
|
|
318
|
-
|
|
363
|
+
const targetPath = path.join(projectRoot, ...target.skill_dir.split('/'));
|
|
364
|
+
const skillPath = path.join(targetPath, 'SKILL.md');
|
|
365
|
+
if (existsSync(targetPath)) {
|
|
319
366
|
throw new Error(`External skill already exists: ${target.skill_dir}`);
|
|
320
367
|
}
|
|
321
368
|
ensureFileTargetInsideWithoutSymlinks(projectRoot, skillPath, { allowMissingLeaf: true });
|
|
322
|
-
|
|
323
|
-
|
|
369
|
+
const tempSkillDir = `${EXTERNAL_SKILL_ROOT}/.${target.skill_name}.tmp-${process.pid}-${Date.now()}`;
|
|
370
|
+
const tempTarget = {
|
|
371
|
+
...target,
|
|
372
|
+
skill_dir: tempSkillDir,
|
|
373
|
+
provenance_path: `${tempSkillDir}/${PROVENANCE_FILE}`,
|
|
374
|
+
};
|
|
375
|
+
const tempPath = path.join(projectRoot, ...tempTarget.skill_dir.split('/'));
|
|
376
|
+
const tempSkillPath = path.join(tempPath, 'SKILL.md');
|
|
377
|
+
ensureFileTargetInsideWithoutSymlinks(projectRoot, tempSkillPath, { allowMissingLeaf: true });
|
|
378
|
+
try {
|
|
379
|
+
for (const file of files) {
|
|
380
|
+
writeUtf8FileInsideWithoutSymlinks(projectRoot, path.join(projectRoot, ...tempTarget.skill_dir.split('/'), ...file.relativePath.split('/')), file.content);
|
|
381
|
+
}
|
|
382
|
+
writeJsonFileInsideWithoutSymlinks(projectRoot, path.join(projectRoot, ...tempTarget.provenance_path.split('/')), {
|
|
383
|
+
schema_version: '1',
|
|
384
|
+
kind: 'external_skill_source',
|
|
385
|
+
source,
|
|
386
|
+
files: fileReport,
|
|
387
|
+
warnings,
|
|
388
|
+
});
|
|
389
|
+
renameSync(tempPath, targetPath);
|
|
390
|
+
}
|
|
391
|
+
catch (error) {
|
|
392
|
+
rmSync(tempPath, { recursive: true, force: true });
|
|
393
|
+
throw error;
|
|
324
394
|
}
|
|
325
|
-
writeJsonFileInsideWithoutSymlinks(projectRoot, path.join(projectRoot, ...target.provenance_path.split('/')), {
|
|
326
|
-
schema_version: '1',
|
|
327
|
-
kind: 'external_skill_source',
|
|
328
|
-
source,
|
|
329
|
-
files: fileReport,
|
|
330
|
-
warnings,
|
|
331
|
-
});
|
|
332
395
|
}
|
|
333
396
|
function rejectionReport(mode, issue) {
|
|
334
397
|
return {
|
|
@@ -355,10 +418,11 @@ export async function createExternalSkillImportReport(projectRoot, inputUrl, opt
|
|
|
355
418
|
if (typeof fetchImpl !== 'function') {
|
|
356
419
|
throw new Error('This runtime does not provide fetch.');
|
|
357
420
|
}
|
|
358
|
-
const
|
|
359
|
-
const skillName = targetNameForSkill(
|
|
421
|
+
const sourceFiles = await loadExternalSkillFiles(fetchImpl, parsed);
|
|
422
|
+
const skillName = targetNameForSkill(sourceFiles, parsed, options.name);
|
|
360
423
|
const target = createTarget(skillName);
|
|
361
424
|
const source = externalSkillSourceFromParsed(inputUrl, parsed);
|
|
425
|
+
const files = normalizeImportedSkillFiles(sourceFiles);
|
|
362
426
|
const reports = fileReports(files);
|
|
363
427
|
const warnings = [
|
|
364
428
|
...(reports.some((file) => file.kind === 'script')
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { createRequire } from 'node:module';
|
|
2
|
-
|
|
2
|
+
let sqlJsPromise = null;
|
|
3
|
+
async function initializeSqlJs() {
|
|
3
4
|
const require = createRequire(import.meta.url);
|
|
4
5
|
const wasmPath = require.resolve('sql.js/dist/sql-wasm.wasm');
|
|
5
6
|
const sqlJsModule = (await import('sql.js'));
|
|
@@ -13,3 +14,10 @@ export async function loadSqlJs() {
|
|
|
13
14
|
},
|
|
14
15
|
});
|
|
15
16
|
}
|
|
17
|
+
export async function loadSqlJs() {
|
|
18
|
+
sqlJsPromise ??= initializeSqlJs().catch((error) => {
|
|
19
|
+
sqlJsPromise = null;
|
|
20
|
+
throw error;
|
|
21
|
+
});
|
|
22
|
+
return sqlJsPromise;
|
|
23
|
+
}
|
package/dist/cli/lib/run-plan.js
CHANGED
|
@@ -7,6 +7,7 @@ import { evaluateCommandIntentEligibility, } from '../../core/command-intent-eli
|
|
|
7
7
|
import { inspectActiveRunLocks, } from '../../core/active-run-locks.js';
|
|
8
8
|
import { isRecord, readPositiveInteger, readString, readStringArray, } from '../../core/config-loading.js';
|
|
9
9
|
import { DEFAULT_COMMAND_MAX_OUTPUT_BYTES, COMMAND_OUTPUT_LIMIT_SCOPE, } from '../../core/command-output-limits.js';
|
|
10
|
+
import { checkRepoApprovalGate } from '../../core/repo-approval-gate.js';
|
|
10
11
|
import { normalizeSuccessExitCodes } from '../../core/success-exit-codes.js';
|
|
11
12
|
import { normalizeSafeTestTargetPath, TEST_TARGET_PATH_ERROR } from '../../core/test-target-paths.js';
|
|
12
13
|
import { evaluateCommandPreconditions, } from '../../core/command-preconditions.js';
|
|
@@ -124,6 +125,38 @@ function readRunIntentMetadata(contract, intent) {
|
|
|
124
125
|
relatedOneshotChecks: readStringArray(intent, 'related_oneshot_checks') ?? [],
|
|
125
126
|
};
|
|
126
127
|
}
|
|
128
|
+
function createApprovalBlock(projectRoot, metadata) {
|
|
129
|
+
const actionTypes = [];
|
|
130
|
+
if (metadata.network === true) {
|
|
131
|
+
actionTypes.push('network_access');
|
|
132
|
+
}
|
|
133
|
+
if (metadata.destructive === true) {
|
|
134
|
+
actionTypes.push('destructive_command');
|
|
135
|
+
}
|
|
136
|
+
if (actionTypes.length === 0) {
|
|
137
|
+
return null;
|
|
138
|
+
}
|
|
139
|
+
const approvalReport = checkRepoApprovalGate(projectRoot, actionTypes);
|
|
140
|
+
if (approvalReport.issues.length > 0) {
|
|
141
|
+
return {
|
|
142
|
+
reasonCode: 'approval_policy_unreadable',
|
|
143
|
+
detail: `Could not evaluate ${approvalReport.input.policy_path}: ${approvalReport.issues.join(' ')}`,
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
const requiredActions = approvalReport.decisions
|
|
147
|
+
.filter((decision) => decision.approval_required)
|
|
148
|
+
.map((decision) => decision.action_type);
|
|
149
|
+
if (requiredActions.length === 0) {
|
|
150
|
+
return null;
|
|
151
|
+
}
|
|
152
|
+
const reasonCode = requiredActions.includes('destructive_command')
|
|
153
|
+
? 'destructive_requires_approval'
|
|
154
|
+
: 'network_requires_approval';
|
|
155
|
+
return {
|
|
156
|
+
reasonCode,
|
|
157
|
+
detail: `Action ${requiredActions.map((action) => JSON.stringify(action)).join(', ')} requires explicit approval before mf run can execute this intent.`,
|
|
158
|
+
};
|
|
159
|
+
}
|
|
127
160
|
function createBlockedRunPlan(contract, intentName, intent, eligibility, reasonCode, detail, preconditions = []) {
|
|
128
161
|
const metadata = intent ? readRunIntentMetadata(contract, intent) : null;
|
|
129
162
|
return {
|
|
@@ -177,6 +210,10 @@ export function createRunPlan(projectRoot, contract, intentName, options = {}) {
|
|
|
177
210
|
return createBlockedRunPlan(contract, intentName, rawIntent, eligibility, eligibility.code, eligibility.detail, preconditions);
|
|
178
211
|
}
|
|
179
212
|
const metadata = readRunIntentMetadata(contract, rawIntent);
|
|
213
|
+
const approvalBlock = createApprovalBlock(projectRoot, metadata);
|
|
214
|
+
if (approvalBlock) {
|
|
215
|
+
return createBlockedRunPlan(contract, intentName, rawIntent, eligibility, approvalBlock.reasonCode, approvalBlock.detail, preconditions);
|
|
216
|
+
}
|
|
180
217
|
const maxOutputBytesLimitDetail = getCommandMaxOutputBytesLimitDetail(contract, rawIntent);
|
|
181
218
|
if (maxOutputBytesLimitDetail) {
|
|
182
219
|
return createBlockedRunPlan(contract, intentName, rawIntent, eligibility, 'max_output_bytes_exceeds_limit', maxOutputBytesLimitDetail, preconditions);
|
|
@@ -195,6 +195,13 @@ function addDependencyImpacts(root, changedFiles, impacts, policy, findings, iss
|
|
|
195
195
|
issues.push(`dependency-graph: ${issue}`);
|
|
196
196
|
}
|
|
197
197
|
}
|
|
198
|
+
for (const dependencyFinding of dependencyReport.findings) {
|
|
199
|
+
const mappedFinding = mapDependencyGraphTruncationFinding(dependencyFinding);
|
|
200
|
+
if (mappedFinding === null) {
|
|
201
|
+
continue;
|
|
202
|
+
}
|
|
203
|
+
findings.push(mappedFinding);
|
|
204
|
+
}
|
|
198
205
|
const changedPathSet = new Set(sourcePaths);
|
|
199
206
|
for (const edge of dependencyReport.edges) {
|
|
200
207
|
if (!changedPathSet.has(edge.target_path) || changedPathSet.has(edge.source_path)) {
|
|
@@ -209,6 +216,15 @@ function addDependencyImpacts(root, changedFiles, impacts, policy, findings, iss
|
|
|
209
216
|
}, policy, findings, issues);
|
|
210
217
|
}
|
|
211
218
|
}
|
|
219
|
+
function mapDependencyGraphTruncationFinding(finding) {
|
|
220
|
+
if (finding.code === 'dependency_graph_max_files_exceeded') {
|
|
221
|
+
return makeFinding('change_impact_max_files_exceeded', 'high', finding.path, `Dependency graph input was truncated while computing change impact: ${finding.message}`);
|
|
222
|
+
}
|
|
223
|
+
if (finding.code === 'dependency_graph_max_nodes_exceeded' || finding.code === 'dependency_graph_max_edges_exceeded') {
|
|
224
|
+
return makeFinding('change_impact_max_impacts_exceeded', 'high', finding.path, `Dependency graph impact expansion was truncated while computing change impact: ${finding.message}`);
|
|
225
|
+
}
|
|
226
|
+
return null;
|
|
227
|
+
}
|
|
212
228
|
function createScriptHints(changedFiles) {
|
|
213
229
|
const sourcePaths = changedFiles.filter((file) => file.surface === 'source').map((file) => file.path);
|
|
214
230
|
const selectorRelevantFiles = changedFiles.filter((file) => ['source', 'test', 'schema', 'config', 'package', 'template', 'workflow', 'unknown'].includes(file.surface));
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { createHash } from 'node:crypto';
|
|
2
2
|
import { existsSync, lstatSync, readdirSync } from 'node:fs';
|
|
3
3
|
import path from 'node:path';
|
|
4
|
+
import { DEFAULT_IGNORED_DIRECTORIES, isIgnoredDirectoryPath } from './ignored-directories.js';
|
|
4
5
|
import { ensureInside, ensureInsideWithoutSymlinks, readFileInsideWithoutSymlinks } from './safe-filesystem.js';
|
|
5
6
|
import { listSourceAnchorFiles, parseSourceAnchorsInContent, sourceAnchorTextContainsSecretLike, splitSourceAnchorList, } from './source-anchors.js';
|
|
6
7
|
export const CODE_PACK_ID = 'code';
|
|
@@ -19,17 +20,7 @@ const SVELTE_EXTENSIONS = ['.svelte'];
|
|
|
19
20
|
const GO_EXTENSIONS = ['.go'];
|
|
20
21
|
const RUST_EXTENSIONS = ['.rs'];
|
|
21
22
|
const PYTHON_EXTENSIONS = ['.py'];
|
|
22
|
-
const IGNORED_DIRECTORIES =
|
|
23
|
-
'.git',
|
|
24
|
-
'.mustflow/cache',
|
|
25
|
-
'.mustflow/state',
|
|
26
|
-
'node_modules',
|
|
27
|
-
'dist',
|
|
28
|
-
'build',
|
|
29
|
-
'coverage',
|
|
30
|
-
'.next',
|
|
31
|
-
'.turbo',
|
|
32
|
-
];
|
|
23
|
+
const IGNORED_DIRECTORIES = DEFAULT_IGNORED_DIRECTORIES;
|
|
33
24
|
const ERROR_OUTLINE_CODES = new Set([
|
|
34
25
|
'code_outline_path_outside_root',
|
|
35
26
|
'code_outline_unreadable_path',
|
|
@@ -97,8 +88,7 @@ export function languageForPath(filePath) {
|
|
|
97
88
|
return languageAdapterForPath(filePath)?.languageForPath(filePath) ?? null;
|
|
98
89
|
}
|
|
99
90
|
function isIgnoredDirectory(relativePath) {
|
|
100
|
-
|
|
101
|
-
return IGNORED_DIRECTORIES.some((directory) => normalized === directory || normalized.startsWith(`${directory}/`));
|
|
91
|
+
return isIgnoredDirectoryPath(normalizeRelativePath(relativePath), IGNORED_DIRECTORIES);
|
|
102
92
|
}
|
|
103
93
|
function makeOutlineFinding(code, severity, pathValue, message) {
|
|
104
94
|
return { code, severity, path: pathValue, message };
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { createHash } from 'node:crypto';
|
|
2
2
|
import { existsSync, lstatSync, readdirSync } from 'node:fs';
|
|
3
3
|
import path from 'node:path';
|
|
4
|
+
import { DEFAULT_IGNORED_DIRECTORIES, isIgnoredDirectoryPath } from './ignored-directories.js';
|
|
4
5
|
import { ensureInside, ensureInsideWithoutSymlinks, readFileInsideWithoutSymlinks } from './safe-filesystem.js';
|
|
5
6
|
export const CONFIG_CHAIN_PACK_ID = 'repo';
|
|
6
7
|
export const CONFIG_CHAIN_SCRIPT_ID = 'config-chain';
|
|
@@ -37,17 +38,7 @@ const CONFIG_FILE_NAMES = [
|
|
|
37
38
|
'.mustflow/config/commands.toml',
|
|
38
39
|
'.mustflow/config/mustflow.toml',
|
|
39
40
|
];
|
|
40
|
-
const IGNORED_DIRECTORIES =
|
|
41
|
-
'.git',
|
|
42
|
-
'.mustflow/cache',
|
|
43
|
-
'.mustflow/state',
|
|
44
|
-
'node_modules',
|
|
45
|
-
'dist',
|
|
46
|
-
'build',
|
|
47
|
-
'coverage',
|
|
48
|
-
'.next',
|
|
49
|
-
'.turbo',
|
|
50
|
-
];
|
|
41
|
+
const IGNORED_DIRECTORIES = DEFAULT_IGNORED_DIRECTORIES;
|
|
51
42
|
const ERROR_CODES = new Set([
|
|
52
43
|
'config_chain_path_outside_root',
|
|
53
44
|
'config_chain_unreadable_path',
|
|
@@ -133,8 +124,7 @@ function isConfigFile(relativePath) {
|
|
|
133
124
|
/^tsconfig(?:\..*)?\.json$/u.test(name));
|
|
134
125
|
}
|
|
135
126
|
function isIgnoredDirectory(relativePath) {
|
|
136
|
-
|
|
137
|
-
return IGNORED_DIRECTORIES.some((directory) => normalized === directory || normalized.startsWith(`${directory}/`));
|
|
127
|
+
return isIgnoredDirectoryPath(normalizeRelativePath(relativePath), IGNORED_DIRECTORIES);
|
|
138
128
|
}
|
|
139
129
|
function normalizeTargetPath(projectRoot, targetPath) {
|
|
140
130
|
const absolutePath = path.resolve(process.cwd(), targetPath);
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { createHash } from 'node:crypto';
|
|
2
2
|
import { existsSync, lstatSync, readdirSync } from 'node:fs';
|
|
3
3
|
import path from 'node:path';
|
|
4
|
+
import { DEFAULT_IGNORED_DIRECTORIES, isIgnoredDirectoryPath } from './ignored-directories.js';
|
|
4
5
|
import { ensureInside, ensureInsideWithoutSymlinks, readFileInsideWithoutSymlinks } from './safe-filesystem.js';
|
|
5
6
|
export const DEPENDENCY_GRAPH_PACK_ID = 'code';
|
|
6
7
|
export const DEPENDENCY_GRAPH_SCRIPT_ID = 'dependency-graph';
|
|
@@ -14,17 +15,7 @@ const MAX_ISSUES = 50;
|
|
|
14
15
|
const MAX_CYCLES = 20;
|
|
15
16
|
const SOURCE_EXTENSIONS = ['.ts', '.tsx', '.mts', '.cts', '.js', '.jsx', '.mjs', '.cjs'];
|
|
16
17
|
const RESOLVE_EXTENSIONS = [...SOURCE_EXTENSIONS, '.json'];
|
|
17
|
-
const IGNORED_DIRECTORIES =
|
|
18
|
-
'.git',
|
|
19
|
-
'.mustflow/cache',
|
|
20
|
-
'.mustflow/state',
|
|
21
|
-
'node_modules',
|
|
22
|
-
'dist',
|
|
23
|
-
'build',
|
|
24
|
-
'coverage',
|
|
25
|
-
'.next',
|
|
26
|
-
'.turbo',
|
|
27
|
-
];
|
|
18
|
+
const IGNORED_DIRECTORIES = DEFAULT_IGNORED_DIRECTORIES;
|
|
28
19
|
const ERROR_CODES = new Set([
|
|
29
20
|
'dependency_graph_path_outside_root',
|
|
30
21
|
'dependency_graph_unreadable_path',
|
|
@@ -64,8 +55,7 @@ function isSourceLanguage(language) {
|
|
|
64
55
|
return language !== 'json' && language !== 'other';
|
|
65
56
|
}
|
|
66
57
|
function isIgnoredDirectory(relativePath) {
|
|
67
|
-
|
|
68
|
-
return IGNORED_DIRECTORIES.some((directory) => normalized === directory || normalized.startsWith(`${directory}/`));
|
|
58
|
+
return isIgnoredDirectoryPath(normalizeRelativePath(relativePath), IGNORED_DIRECTORIES);
|
|
69
59
|
}
|
|
70
60
|
function makeFinding(code, severity, pathValue, message) {
|
|
71
61
|
return { code, severity, path: pathValue, message };
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { createHash } from 'node:crypto';
|
|
2
2
|
import { existsSync, lstatSync, readdirSync } from 'node:fs';
|
|
3
3
|
import path from 'node:path';
|
|
4
|
+
import { DEFAULT_IGNORED_DIRECTORIES, isIgnoredDirectoryPath } from './ignored-directories.js';
|
|
4
5
|
import { ensureInside, ensureInsideWithoutSymlinks, readFileInsideWithoutSymlinks } from './safe-filesystem.js';
|
|
5
6
|
export const LINK_INTEGRITY_PACK_ID = 'docs';
|
|
6
7
|
export const LINK_INTEGRITY_SCRIPT_ID = 'link-integrity';
|
|
@@ -11,7 +12,7 @@ const MAX_ISSUES = 50;
|
|
|
11
12
|
const DEFAULT_PATHS = ['README.md', 'schemas/README.md', 'docs-site/src/content/docs'];
|
|
12
13
|
const PATH_FILTERS = ['*.md', '*.mdx'];
|
|
13
14
|
const CHECKED_LINK_KINDS = ['local_file', 'local_anchor'];
|
|
14
|
-
const IGNORED_DIRECTORIES =
|
|
15
|
+
const IGNORED_DIRECTORIES = DEFAULT_IGNORED_DIRECTORIES;
|
|
15
16
|
const ERROR_CODES = new Set([
|
|
16
17
|
'link_integrity_path_outside_root',
|
|
17
18
|
'link_integrity_unreadable_path',
|
|
@@ -59,7 +60,7 @@ function addCandidate(candidates, findings, issues, policy, candidate) {
|
|
|
59
60
|
}
|
|
60
61
|
function collectDocumentsFromDirectory(projectRoot, absoluteDirectory, candidates, findings, issues, policy) {
|
|
61
62
|
const relativeDirectory = normalizeRelativePath(path.relative(projectRoot, absoluteDirectory));
|
|
62
|
-
if (
|
|
63
|
+
if (isIgnoredDirectoryPath(relativeDirectory, IGNORED_DIRECTORIES)) {
|
|
63
64
|
return;
|
|
64
65
|
}
|
|
65
66
|
let entries;
|
|
@@ -173,9 +174,26 @@ function splitTarget(target) {
|
|
|
173
174
|
const anchor = rawAnchor === undefined ? null : decodeUriComponentSafe(rawAnchor);
|
|
174
175
|
return { pathPart, anchor };
|
|
175
176
|
}
|
|
177
|
+
function stripHtmlTagText(value) {
|
|
178
|
+
let result = '';
|
|
179
|
+
let tagDepth = 0;
|
|
180
|
+
for (const char of value) {
|
|
181
|
+
if (char === '<') {
|
|
182
|
+
tagDepth += 1;
|
|
183
|
+
continue;
|
|
184
|
+
}
|
|
185
|
+
if (char === '>') {
|
|
186
|
+
tagDepth = Math.max(0, tagDepth - 1);
|
|
187
|
+
continue;
|
|
188
|
+
}
|
|
189
|
+
if (tagDepth === 0) {
|
|
190
|
+
result += char;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
return result;
|
|
194
|
+
}
|
|
176
195
|
function slugHeading(value) {
|
|
177
|
-
return value
|
|
178
|
-
.replace(/<[^>]+>/gu, '')
|
|
196
|
+
return stripHtmlTagText(value)
|
|
179
197
|
.replace(/[`*_~]/gu, '')
|
|
180
198
|
.replace(/\[([^\]]+)\]\([^)]+\)/gu, '$1')
|
|
181
199
|
.toLocaleLowerCase()
|
|
@@ -369,6 +387,7 @@ export function checkLinkIntegrity(projectRoot, options) {
|
|
|
369
387
|
max_file_bytes: options.maxFileBytes ?? DEFAULT_MAX_FILE_BYTES,
|
|
370
388
|
default_paths: [...DEFAULT_PATHS],
|
|
371
389
|
path_filters: [...PATH_FILTERS],
|
|
390
|
+
ignored_directories: [...IGNORED_DIRECTORIES],
|
|
372
391
|
checked_link_kinds: [...CHECKED_LINK_KINDS],
|
|
373
392
|
};
|
|
374
393
|
const findings = [];
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { createHash } from 'node:crypto';
|
|
2
2
|
import { existsSync, lstatSync, readdirSync } from 'node:fs';
|
|
3
3
|
import path from 'node:path';
|
|
4
|
+
import { DEFAULT_IGNORED_DIRECTORIES, isIgnoredDirectoryPath } from './ignored-directories.js';
|
|
4
5
|
import { ensureInside, ensureInsideWithoutSymlinks, readFileInsideWithoutSymlinks } from './safe-filesystem.js';
|
|
5
6
|
export const ENV_CONTRACT_PACK_ID = 'repo';
|
|
6
7
|
export const ENV_CONTRACT_SCRIPT_ID = 'env-contract';
|
|
@@ -36,17 +37,7 @@ const ENV_EXAMPLE_NAMES = [
|
|
|
36
37
|
'.dev.vars.example',
|
|
37
38
|
];
|
|
38
39
|
const SECRET_ENV_NAMES = ['.env', '.env.local', '.env.production', '.env.development', '.dev.vars'];
|
|
39
|
-
const IGNORED_DIRECTORIES =
|
|
40
|
-
'.git',
|
|
41
|
-
'.mustflow/cache',
|
|
42
|
-
'.mustflow/state',
|
|
43
|
-
'node_modules',
|
|
44
|
-
'dist',
|
|
45
|
-
'build',
|
|
46
|
-
'coverage',
|
|
47
|
-
'.next',
|
|
48
|
-
'.turbo',
|
|
49
|
-
];
|
|
40
|
+
const IGNORED_DIRECTORIES = DEFAULT_IGNORED_DIRECTORIES;
|
|
50
41
|
const ERROR_CODES = new Set([
|
|
51
42
|
'env_contract_path_outside_root',
|
|
52
43
|
'env_contract_unreadable_path',
|
|
@@ -66,8 +57,7 @@ function pushIssue(issues, issue) {
|
|
|
66
57
|
}
|
|
67
58
|
}
|
|
68
59
|
function isIgnoredDirectory(relativePath) {
|
|
69
|
-
|
|
70
|
-
return IGNORED_DIRECTORIES.some((directory) => normalized === directory || normalized.startsWith(`${directory}/`));
|
|
60
|
+
return isIgnoredDirectoryPath(normalizeRelativePath(relativePath), IGNORED_DIRECTORIES);
|
|
71
61
|
}
|
|
72
62
|
function isEnvExampleFile(relativePath) {
|
|
73
63
|
const name = path.basename(relativePath).toLowerCase();
|
package/dist/core/export-diff.js
CHANGED
|
@@ -3,6 +3,7 @@ import { createHash } from 'node:crypto';
|
|
|
3
3
|
import { existsSync, readFileSync } from 'node:fs';
|
|
4
4
|
import path from 'node:path';
|
|
5
5
|
import { extractSymbols, languageForPath } from './code-outline.js';
|
|
6
|
+
import { DEFAULT_IGNORED_DIRECTORIES, isIgnoredDirectoryPath } from './ignored-directories.js';
|
|
6
7
|
import { ensureInsideWithoutSymlinks, readFileInsideWithoutSymlinks } from './safe-filesystem.js';
|
|
7
8
|
export const CODE_EXPORT_DIFF_SCRIPT_ID = 'export-diff';
|
|
8
9
|
export const CODE_EXPORT_DIFF_SCRIPT_REF = `code/${CODE_EXPORT_DIFF_SCRIPT_ID}`;
|
|
@@ -10,7 +11,7 @@ const DEFAULT_BASE_REF = 'HEAD';
|
|
|
10
11
|
const DEFAULT_MAX_FILES = 100;
|
|
11
12
|
const DEFAULT_MAX_FILE_BYTES = 1024 * 1024;
|
|
12
13
|
const SUPPORTED_EXTENSIONS = ['.ts', '.tsx', '.mts', '.cts', '.js', '.jsx', '.mjs', '.cjs'];
|
|
13
|
-
const IGNORED_DIRECTORIES =
|
|
14
|
+
const IGNORED_DIRECTORIES = DEFAULT_IGNORED_DIRECTORIES;
|
|
14
15
|
const ERROR_CODES = new Set([
|
|
15
16
|
'export_diff_git_unavailable',
|
|
16
17
|
'export_diff_invalid_ref',
|
|
@@ -46,8 +47,7 @@ function makeFinding(code, severity, pathValue, message) {
|
|
|
46
47
|
return { code, severity, path: pathValue, message };
|
|
47
48
|
}
|
|
48
49
|
function isIgnoredPath(relativePath) {
|
|
49
|
-
|
|
50
|
-
return IGNORED_DIRECTORIES.some((directory) => normalized === directory || normalized.startsWith(`${directory}/`));
|
|
50
|
+
return isIgnoredDirectoryPath(normalizeRelativePath(relativePath), IGNORED_DIRECTORIES);
|
|
51
51
|
}
|
|
52
52
|
function isSupportedPath(relativePath) {
|
|
53
53
|
return SUPPORTED_EXTENSIONS.includes(path.extname(relativePath).toLowerCase());
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
export const DEFAULT_IGNORED_DIRECTORIES = [
|
|
2
|
+
'.git',
|
|
3
|
+
'.mustflow/cache',
|
|
4
|
+
'.mustflow/state',
|
|
5
|
+
'node_modules',
|
|
6
|
+
'dist',
|
|
7
|
+
'build',
|
|
8
|
+
'coverage',
|
|
9
|
+
'.next',
|
|
10
|
+
'.turbo',
|
|
11
|
+
'.astro',
|
|
12
|
+
];
|
|
13
|
+
function normalizeDirectoryPath(value) {
|
|
14
|
+
return value.replace(/\\/gu, '/').replace(/^\.\/+/u, '').replace(/\/+$/u, '') || '.';
|
|
15
|
+
}
|
|
16
|
+
function containsSegmentSequence(segments, sequence) {
|
|
17
|
+
if (sequence.length === 0 || sequence.length > segments.length) {
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
for (let index = 0; index <= segments.length - sequence.length; index += 1) {
|
|
21
|
+
if (sequence.every((segment, offset) => segments[index + offset] === segment)) {
|
|
22
|
+
return true;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
export function isIgnoredDirectoryPath(relativePath, ignoredDirectories = DEFAULT_IGNORED_DIRECTORIES) {
|
|
28
|
+
const normalized = normalizeDirectoryPath(relativePath);
|
|
29
|
+
if (normalized === '.') {
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
const segments = normalized.split('/').filter((segment) => segment.length > 0);
|
|
33
|
+
return ignoredDirectories.some((entry) => {
|
|
34
|
+
const ignoredSegments = normalizeDirectoryPath(entry).split('/').filter((segment) => segment.length > 0);
|
|
35
|
+
if (ignoredSegments.length === 1) {
|
|
36
|
+
return segments.includes(ignoredSegments[0] ?? '');
|
|
37
|
+
}
|
|
38
|
+
return containsSegmentSequence(segments, ignoredSegments);
|
|
39
|
+
});
|
|
40
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { createHash } from 'node:crypto';
|
|
2
2
|
import { existsSync, lstatSync, readdirSync } from 'node:fs';
|
|
3
3
|
import path from 'node:path';
|
|
4
|
+
import { DEFAULT_IGNORED_DIRECTORIES, isIgnoredDirectoryPath } from './ignored-directories.js';
|
|
4
5
|
import { ensureInside, ensureInsideWithoutSymlinks, readFileInsideWithoutSymlinks } from './safe-filesystem.js';
|
|
5
6
|
export const REFERENCE_DRIFT_PACK_ID = 'docs';
|
|
6
7
|
export const REFERENCE_DRIFT_SCRIPT_ID = 'reference-drift';
|
|
@@ -16,7 +17,7 @@ const CHECKED_REFERENCE_KINDS = [
|
|
|
16
17
|
'schema_file',
|
|
17
18
|
'repo_path',
|
|
18
19
|
];
|
|
19
|
-
const IGNORED_DIRECTORIES =
|
|
20
|
+
const IGNORED_DIRECTORIES = DEFAULT_IGNORED_DIRECTORIES;
|
|
20
21
|
const ERROR_CODES = new Set([
|
|
21
22
|
'reference_drift_path_outside_root',
|
|
22
23
|
'reference_drift_unreadable_path',
|
|
@@ -64,7 +65,7 @@ function addCandidate(candidates, findings, issues, policy, candidate) {
|
|
|
64
65
|
}
|
|
65
66
|
function collectDocumentsFromDirectory(projectRoot, absoluteDirectory, candidates, findings, issues, policy) {
|
|
66
67
|
const relativeDirectory = normalizeRelativePath(path.relative(projectRoot, absoluteDirectory));
|
|
67
|
-
if (
|
|
68
|
+
if (isIgnoredDirectoryPath(relativeDirectory, IGNORED_DIRECTORIES)) {
|
|
68
69
|
return;
|
|
69
70
|
}
|
|
70
71
|
let entries;
|
|
@@ -318,6 +319,7 @@ export function checkReferenceDrift(projectRoot, options) {
|
|
|
318
319
|
max_file_bytes: options.maxFileBytes ?? DEFAULT_MAX_FILE_BYTES,
|
|
319
320
|
default_paths: [...DEFAULT_PATHS],
|
|
320
321
|
path_filters: [...PATH_FILTERS],
|
|
322
|
+
ignored_directories: [...IGNORED_DIRECTORIES],
|
|
321
323
|
checked_reference_kinds: [...CHECKED_REFERENCE_KINDS],
|
|
322
324
|
};
|
|
323
325
|
const commandNames = new Set(options.commandNames);
|