scai 0.1.174 → 0.1.176

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.
@@ -28,6 +28,8 @@ import { structuralPreloadStep } from "./structuralPreloadStep.js";
28
28
  import { extractFileReferences } from "../utils/extractFileReferences.js";
29
29
  import { PREFILTER_STOP_WORDS } from "../fileRules/stopWords.js";
30
30
  import { MAX_WELL_KNOWN_REPO_FILES, WELL_KNOWN_REPO_FILE_BASENAMES } from "../fileRules/wellKnownRepoFiles.js";
31
+ import { isPathIgnoredByFolderGlobs } from "../fileRules/ignoredPaths.js";
32
+ import { canExecutePhase, canExecuteRoute, canExecuteScope } from "./guardPolicy.js";
31
33
  import chalk from "chalk";
32
34
  import path from "path";
33
35
  import fs from "fs";
@@ -172,12 +174,12 @@ export class MainAgent {
172
174
  break;
173
175
  }
174
176
  // ---------------- INFORMATION ACQUISITION ----------------
175
- const canRouteSearchExpansion = this.canExecuteRoute("search-expand");
177
+ const canRouteSearchExpansion = canExecuteRoute(this.context, "search-expand");
176
178
  if (!canRouteSearchExpansion) {
177
179
  this.logLine("PLAN", "infoPlanGen", undefined, "skipped (routing disallows search expansion)", { highlight: false });
178
180
  }
179
- else if (this.canExecutePhase("planning") &&
180
- this.canExecuteScope("planning")) {
181
+ else if (canExecutePhase(this.context, "planning") &&
182
+ canExecuteScope(this.context, "planning")) {
181
183
  const t = this.startTimer();
182
184
  await infoPlanGenStep.run(this.context);
183
185
  const infoPlan = this.context.analysis?.planSuggestion?.plan ?? { steps: [] };
@@ -208,7 +210,7 @@ export class MainAgent {
208
210
  */
209
211
  async runResearch() {
210
212
  var _a, _b;
211
- if (!this.canExecuteRoute("research")) {
213
+ if (!canExecuteRoute(this.context, "research")) {
212
214
  this.logLine("RESEARCH", "taskStepSeed", undefined, "skipped (route disallows research)");
213
215
  return;
214
216
  }
@@ -298,7 +300,7 @@ export class MainAgent {
298
300
  var _a, _b;
299
301
  if (!this.context.task)
300
302
  return;
301
- if (!this.canExecutePhase("planning") || !this.canExecuteScope("planning"))
303
+ if (!canExecutePhase(this.context, "planning") || !canExecuteScope(this.context, "planning"))
302
304
  return;
303
305
  (_a = this.context).analysis || (_a.analysis = {});
304
306
  (_b = this.context.task).taskSteps || (_b.taskSteps = []);
@@ -347,6 +349,7 @@ export class MainAgent {
347
349
  .filter(filePath => !!filePath && !filePath.startsWith("__research__/") && fs.existsSync(filePath))
348
350
  .sort((a, b) => rankPath(a) - rankPath(b))
349
351
  .slice(0, 16);
352
+ this.ensureWorkingFilesLoaded(plannedPaths, "Planned for execution");
350
353
  let seededCount = 0;
351
354
  const seeded = [];
352
355
  for (const filePath of plannedPaths) {
@@ -409,6 +412,15 @@ export class MainAgent {
409
412
  const routing = this.context.analysis.routingDecision;
410
413
  if (!routing)
411
414
  return;
415
+ const intentCategory = (this.context.analysis.intent?.intentCategory ?? "").toLowerCase();
416
+ const isEditLikeIntent = intentCategory === "codingtask" ||
417
+ intentCategory === "refactortask" ||
418
+ intentCategory === "request" ||
419
+ intentCategory === "docsandcomments";
420
+ const canWrite = this.context.executionControl?.constraints?.allowFileWrites ?? false;
421
+ if (!isEditLikeIntent || !canWrite) {
422
+ return;
423
+ }
412
424
  const selectedFiles = this.context.analysis.focus?.selectedFiles ?? [];
413
425
  if (selectedFiles.length === 0)
414
426
  return;
@@ -468,6 +480,9 @@ export class MainAgent {
468
480
  // Step-level iterations
469
481
  // ---------------------------
470
482
  const stepAction = await this.runStepIterations(taskStep);
483
+ if (stepAction === "expand-scope") {
484
+ this.applyScopeExpansionRouteHint();
485
+ }
471
486
  this.finishTaskStep(taskStep, stepCount, stepAction);
472
487
  }
473
488
  this.logLine("TASK", "Max task step limit reached — stopping work loop", undefined, undefined, { highlight: false });
@@ -507,6 +522,8 @@ export class MainAgent {
507
522
  return "request-feedback";
508
523
  if (nextAction === "redo-step")
509
524
  continue;
525
+ if (nextAction === "expand-scope")
526
+ return "expand-scope";
510
527
  }
511
528
  return "continue";
512
529
  }
@@ -522,7 +539,7 @@ export class MainAgent {
522
539
  await this.executeResearchTaskStep(taskStep);
523
540
  return;
524
541
  }
525
- if (this.canExecutePhase("analysis") && this.canExecuteScope("analysis")) {
542
+ if (canExecutePhase(this.context, "analysis") && canExecuteScope(this.context, "analysis")) {
526
543
  const tAnalysis = this.startTimer();
527
544
  await analysisPlanGenStep.run(this.context);
528
545
  this.logLine("PLAN", "analysisPlanGen", tAnalysis(), undefined, { highlight: false });
@@ -535,7 +552,9 @@ export class MainAgent {
535
552
  if (this.context.analysis)
536
553
  this.context.analysis.planSuggestion = undefined;
537
554
  }
538
- if (this.canExecutePhase("transform") && this.canExecuteScope("transform")) {
555
+ if (canExecutePhase(this.context, "transform") &&
556
+ canExecuteScope(this.context, "transform") &&
557
+ canExecuteRoute(this.context, "transform")) {
539
558
  const tTransform = this.startTimer();
540
559
  await transformPlanGenStep.run(this.context);
541
560
  this.logLine("PLAN", "transformPlanGen", tTransform(), undefined, { highlight: false });
@@ -548,7 +567,7 @@ export class MainAgent {
548
567
  }
549
568
  if (this.context.analysis)
550
569
  this.context.analysis.planSuggestion = undefined;
551
- if (this.canExecutePhase("write") && this.canExecuteScope("write")) {
570
+ if (canExecutePhase(this.context, "write") && canExecuteScope(this.context, "write")) {
552
571
  const tWrite = this.startTimer();
553
572
  await writeFileStep.run({ query: this.query, context: this.context });
554
573
  this.logLine("WRITE", "writeFileStep", tWrite());
@@ -1155,6 +1174,8 @@ export class MainAgent {
1155
1174
  const existsOrResearch = (filePath) => {
1156
1175
  if (filePath.startsWith("__research__/"))
1157
1176
  return true;
1177
+ if (isPathIgnoredByFolderGlobs(filePath))
1178
+ return false;
1158
1179
  return fs.existsSync(filePath);
1159
1180
  };
1160
1181
  let removedRelated = 0;
@@ -1208,7 +1229,7 @@ export class MainAgent {
1208
1229
  */
1209
1230
  getTaskStepBudget() {
1210
1231
  const scope = this.context.analysis?.scopeType ?? "repo-wide";
1211
- if (this.canExecuteRoute("research"))
1232
+ if (canExecuteRoute(this.context, "research"))
1212
1233
  return 10;
1213
1234
  if (scope === "multi-file")
1214
1235
  return 7;
@@ -1221,7 +1242,7 @@ export class MainAgent {
1221
1242
  * Example: require at least two analyzed files plus one understanding signal.
1222
1243
  */
1223
1244
  isResearchGateSatisfied() {
1224
- if (!this.canExecuteRoute("research"))
1245
+ if (!canExecuteRoute(this.context, "research"))
1225
1246
  return true;
1226
1247
  const scope = this.context.analysis?.scopeType ?? "repo-wide";
1227
1248
  const researchPlanCount = this.context.task?.taskSteps?.filter(s => typeof s.action === "string" && s.action.startsWith("research-")).length ?? 0;
@@ -1292,28 +1313,45 @@ export class MainAgent {
1292
1313
  this.context.task.status = status;
1293
1314
  this.persistTaskDataForRun();
1294
1315
  }
1316
+ applyScopeExpansionRouteHint() {
1317
+ var _a;
1318
+ (_a = this.context).analysis || (_a.analysis = {});
1319
+ const route = this.context.analysis.routingDecision;
1320
+ if (!route)
1321
+ return;
1322
+ route.decision = "needs-info";
1323
+ route.allowSearch = true;
1324
+ route.scopeLocked = false;
1325
+ route.rationale = `${route.rationale}; stepReasoning=expand-scope`;
1326
+ this.logLine("TASK", "Route updated from step reasoning", undefined, "expand-scope requested; re-enabled search expansion");
1327
+ }
1295
1328
  startTaskStep(taskStep, stepCount) {
1329
+ this.ensureWorkingFilesLoaded([taskStep.filePath], "Current task step");
1330
+ const db = getDbForRepo();
1331
+ this.ensureTaskIdentityForPersistence(db);
1296
1332
  this.context.task.currentStep = taskStep;
1297
1333
  taskStep.taskId = this.taskId;
1298
1334
  taskStep.stepIndex = stepCount;
1299
1335
  taskStep.status = "pending";
1300
- persistTaskStepInsert(taskStep, getDbForRepo());
1336
+ persistTaskStepInsert(taskStep, db);
1301
1337
  const displayPath = this.formatTaskStepDisplayPath(taskStep.filePath);
1302
1338
  this.logLine("NEW STEP", `Processing taskStep ${stepCount}`, undefined, displayPath, { highlight: true });
1303
1339
  taskStep.startTime = Date.now();
1304
- persistTaskStepStart(taskStep, getDbForRepo());
1340
+ persistTaskStepStart(taskStep, db);
1305
1341
  }
1306
1342
  finishTaskStep(taskStep, stepCount, stepAction) {
1343
+ const db = getDbForRepo();
1344
+ this.ensureTaskIdentityForPersistence(db);
1307
1345
  const displayPath = this.formatTaskStepDisplayPath(taskStep.filePath);
1308
1346
  taskStep.endTime = Date.now();
1309
1347
  if (stepAction === "complete") {
1310
1348
  taskStep.status = "completed";
1311
- persistTaskStepCompletion(taskStep, getDbForRepo());
1349
+ persistTaskStepCompletion(taskStep, db);
1312
1350
  this.logLine("STEP-DONE", `Completed taskStep ${stepCount}`, undefined, displayPath, { highlight: false });
1313
1351
  return;
1314
1352
  }
1315
1353
  taskStep.status = "pending";
1316
- persistTaskStepCompletion(taskStep, getDbForRepo());
1354
+ persistTaskStepCompletion(taskStep, db);
1317
1355
  this.logLine("STEP", `Pending taskStep ${stepCount}`, undefined, displayPath);
1318
1356
  }
1319
1357
  /**
@@ -1325,69 +1363,6 @@ export class MainAgent {
1325
1363
  ? filePath.replace("__research__/", "research/")
1326
1364
  : filePath;
1327
1365
  }
1328
- /* ───────────── execution gates ───────────── */
1329
- /**
1330
- * Gate model:
1331
- * 1) Phase + scope gates decide coarse permissions (what broad work is allowed).
1332
- * 2) Route gate decides finer sub-decisions within those allowed areas (what to do next).
1333
- */
1334
- /**
1335
- * Gate 1: Is this kind of work allowed at all?
1336
- * Plain meaning: checks capability rules (e.g. read-only vs file-writing).
1337
- * Example: for docs-only mode, analysis/planning are blocked, and writes are limited.
1338
- */
1339
- canExecutePhase(phase) {
1340
- const constraints = this.context.executionControl?.constraints;
1341
- const docsOnly = constraints?.docsOnly ?? false;
1342
- let allowed = false;
1343
- switch (phase) {
1344
- case "analysis":
1345
- case "planning":
1346
- allowed = !docsOnly;
1347
- break;
1348
- case "transform":
1349
- case "write":
1350
- allowed = constraints?.allowFileWrites ?? false;
1351
- break;
1352
- }
1353
- return allowed;
1354
- }
1355
- /* ───────────── scope gates ───────────── */
1356
- /**
1357
- * Gate 2: Is this work allowed for the current scope size?
1358
- * Plain meaning: checks scope rules (none/single/multi/repo-wide).
1359
- * Example: if scope is "analysis", only analysis/planning run and transform/write are blocked.
1360
- */
1361
- canExecuteScope(phase) {
1362
- const scope = this.context.analysis?.scopeType ?? "repo-wide";
1363
- let allowed = false;
1364
- switch (scope) {
1365
- case "none":
1366
- allowed = phase === "analysis" || phase === "planning";
1367
- break;
1368
- default:
1369
- allowed = true;
1370
- }
1371
- return allowed;
1372
- }
1373
- /**
1374
- * Gate 3: Does this request path want this action right now?
1375
- * Plain meaning: checks route-specific intent from routingDecision.
1376
- * Example: search expansion is skipped when routing says allowSearch=false.
1377
- */
1378
- canExecuteRoute(action) {
1379
- const routing = this.context.analysis?.routingDecision;
1380
- switch (action) {
1381
- case "search-expand":
1382
- return routing?.allowSearch ?? true;
1383
- case "transform":
1384
- return routing?.allowTransform ?? true;
1385
- case "research":
1386
- return routing?.allowResearch ?? false;
1387
- default:
1388
- return true;
1389
- }
1390
- }
1391
1366
  /* ----------------------------------- */
1392
1367
  /* ------------- helpers ------------- */
1393
1368
  /* ----------------------------------- */
@@ -1433,6 +1408,68 @@ export class MainAgent {
1433
1408
  console.log(`[USER OUTPUT] ${message}`);
1434
1409
  });
1435
1410
  }
1411
+ /**
1412
+ * Ensures workingFiles has file capsules with code for the given paths.
1413
+ * Example: planned verify-relevant files are hydrated before transform starts.
1414
+ */
1415
+ ensureWorkingFilesLoaded(paths, reason) {
1416
+ var _a;
1417
+ (_a = this.context).workingFiles || (_a.workingFiles = []);
1418
+ const indexByPath = new Map(this.context.workingFiles.map(file => [file.path, file]));
1419
+ for (const filePath of paths) {
1420
+ if (!filePath || filePath.startsWith("__research__/"))
1421
+ continue;
1422
+ if (!fs.existsSync(filePath))
1423
+ continue;
1424
+ const existing = indexByPath.get(filePath);
1425
+ if (existing && typeof existing.code === "string" && existing.code.length > 0) {
1426
+ continue;
1427
+ }
1428
+ let code;
1429
+ try {
1430
+ code = fs.readFileSync(filePath, "utf-8");
1431
+ }
1432
+ catch {
1433
+ code = undefined;
1434
+ }
1435
+ if (existing) {
1436
+ existing.code = code;
1437
+ existing.selectionReason || (existing.selectionReason = reason);
1438
+ }
1439
+ else {
1440
+ const capsule = { path: filePath, code, selectionReason: reason };
1441
+ this.context.workingFiles.push(capsule);
1442
+ indexByPath.set(filePath, capsule);
1443
+ }
1444
+ }
1445
+ }
1446
+ /**
1447
+ * Ensures the current task id exists in the active DB before step persistence.
1448
+ * Example: if repo/db context switched, re-create task row and rebind step taskIds.
1449
+ */
1450
+ ensureTaskIdentityForPersistence(db) {
1451
+ var _a;
1452
+ const activeTaskId = this.taskId ?? this.context.task?.id;
1453
+ if (!activeTaskId) {
1454
+ this.taskId = bootTaskForRepo(this.context, db, this.logLine.bind(this));
1455
+ this.context.task.id = this.taskId;
1456
+ return;
1457
+ }
1458
+ const row = db.prepare("SELECT id FROM tasks WHERE id = ?").get(activeTaskId);
1459
+ if (row?.id) {
1460
+ this.taskId = activeTaskId;
1461
+ this.context.task.id = activeTaskId;
1462
+ return;
1463
+ }
1464
+ const reboundTaskId = bootTaskForRepo(this.context, db, this.logLine.bind(this));
1465
+ this.taskId = reboundTaskId;
1466
+ this.context.task.id = reboundTaskId;
1467
+ (_a = this.context.task).taskSteps || (_a.taskSteps = []);
1468
+ for (const step of this.context.task.taskSteps) {
1469
+ step.taskId = reboundTaskId;
1470
+ }
1471
+ this.logLine("TASK", "taskId rebound", undefined, `previous=${activeTaskId}, rebound=${reboundTaskId}`);
1472
+ }
1436
1473
  }
1437
1474
  function scoreCandidateFiles(filePaths, relatedFileScores, retrievalQuery) {
1438
1475
  const explicitRefs = extractFileReferences(retrievalQuery, { lowercase: true });
@@ -3,6 +3,7 @@ import { generate } from "../lib/generate.js";
3
3
  import { logInputOutput } from "../utils/promptLogHelper.js";
4
4
  import { cleanupModule } from "../pipeline/modules/cleanupModule.js";
5
5
  import path from "path";
6
+ import { isPathIgnoredByFolderGlobs } from "../fileRules/ignoredPaths.js";
6
7
  export async function fileCheckStep(context) {
7
8
  var _a;
8
9
  context.analysis ?? (context.analysis = {});
@@ -10,9 +11,7 @@ export async function fileCheckStep(context) {
10
11
  const intent = context.analysis.intent;
11
12
  const planSuggestion = context.analysis.planSuggestion?.text ?? "";
12
13
  // Step 1: gather known files from initContext only
13
- const knownFiles = new Set([
14
- ...(context.initContext?.relatedFiles ?? []),
15
- ]);
14
+ const knownFiles = new Set((context.initContext?.relatedFiles ?? []).filter(filePath => !isPathIgnoredByFolderGlobs(filePath)));
16
15
  // Step 2: extract file names from normalizedQuery or planSuggestion
17
16
  const extractedFiles = extractFilesFromAnalysis(context.analysis);
18
17
  // Step 3: populate focus with safe defaults
@@ -138,16 +137,25 @@ Task:
138
137
  // Merge parsed output safely
139
138
  if (Array.isArray(parsed.selectedFiles)) {
140
139
  const existing = new Set(context.analysis.focus.selectedFiles);
140
+ const safeSelected = parsed.selectedFiles
141
+ .filter((f) => typeof f === "string" && knownFiles.has(f) && !isPathIgnoredByFolderGlobs(f));
141
142
  context.analysis.focus.selectedFiles = [
142
143
  ...context.analysis.focus.selectedFiles,
143
- ...parsed.selectedFiles.filter((f) => !existing.has(f))
144
+ ...safeSelected.filter((f) => !existing.has(f))
144
145
  ];
145
146
  }
146
147
  if (Array.isArray(parsed.candidateFiles)) {
147
148
  const existing = new Set(context.analysis.focus.candidateFiles);
149
+ const safeCandidates = parsed.candidateFiles
150
+ .filter((f) => typeof f === "string")
151
+ .filter((f) => {
152
+ if (knownFiles.has(f))
153
+ return !isPathIgnoredByFolderGlobs(f);
154
+ return /^[\w\-./]+\.(js|ts|jsx|tsx|py|java|go|rs|cpp|c|cs|md)$/i.test(f);
155
+ });
148
156
  context.analysis.focus.candidateFiles = [
149
157
  ...context.analysis.focus.candidateFiles,
150
- ...parsed.candidateFiles.filter((f) => !existing.has(f))
158
+ ...safeCandidates.filter((f) => !existing.has(f))
151
159
  ];
152
160
  }
153
161
  if (typeof parsed.rationale === "string") {
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Gate 1: capability constraints (execution control).
3
+ */
4
+ export function canExecutePhase(context, phase) {
5
+ const constraints = context.executionControl?.constraints;
6
+ const docsOnly = constraints?.docsOnly ?? false;
7
+ switch (phase) {
8
+ case "analysis":
9
+ case "planning":
10
+ return !docsOnly;
11
+ case "transform":
12
+ case "write":
13
+ return constraints?.allowFileWrites ?? false;
14
+ default:
15
+ return false;
16
+ }
17
+ }
18
+ /**
19
+ * Gate 2: scope constraints (none/single/multi/repo-wide).
20
+ */
21
+ export function canExecuteScope(context, phase) {
22
+ const scope = context.analysis?.scopeType ?? "repo-wide";
23
+ if (scope === "none") {
24
+ return phase === "analysis" || phase === "planning";
25
+ }
26
+ return true;
27
+ }
28
+ /**
29
+ * Gate 3: route constraints (dynamic routing decisions).
30
+ */
31
+ export function canExecuteRoute(context, action) {
32
+ const routing = context.analysis?.routingDecision;
33
+ switch (action) {
34
+ case "search-expand":
35
+ return routing?.allowSearch ?? true;
36
+ case "transform":
37
+ return routing?.allowTransform ?? true;
38
+ case "research":
39
+ return routing?.allowResearch ?? false;
40
+ default:
41
+ return true;
42
+ }
43
+ }
@@ -43,3 +43,20 @@ export const IGNORED_FOLDER_GLOBS = [
43
43
  '**/debug.log',
44
44
  '**/Dockerfile',
45
45
  ];
46
+ export const IGNORED_DIR_NAMES = Array.from(new Set(IGNORED_FOLDER_GLOBS
47
+ .map((pattern) => {
48
+ const match = pattern.match(/^\*\*\/([^*\/]+)\/\*\*$/);
49
+ return match?.[1];
50
+ })
51
+ .filter((name) => !!name)));
52
+ /**
53
+ * Coarse path guard used by runtime search/selection fallback paths.
54
+ * Matches the same ignore-glob intent used during indexing.
55
+ */
56
+ export function isPathIgnoredByFolderGlobs(filePath) {
57
+ const normalizedPath = String(filePath ?? "").replace(/\\/g, "/");
58
+ return IGNORED_FOLDER_GLOBS.some((pattern) => {
59
+ const cleanPattern = pattern.replace(/^\*\*\/?/, "").replace(/\/\*\*$/, "");
60
+ return cleanPattern.length > 0 && normalizedPath.includes(cleanPattern);
61
+ });
62
+ }
@@ -1,11 +1,12 @@
1
1
  // File: src/modules/fileSearchModule.ts
2
- import { execSync } from "child_process";
2
+ import { execFileSync } from "child_process";
3
3
  import path from "path";
4
4
  import fs from "fs";
5
5
  import { plannerSearchFiles } from "../../db/fileIndex.js";
6
6
  import { Config } from "../../config.js";
7
7
  import { getDbForRepo } from "../../db/client.js";
8
8
  import { IGNORED_EXTENSIONS } from "../../fileRules/ignoredExtensions.js";
9
+ import { IGNORED_DIR_NAMES, isPathIgnoredByFolderGlobs } from "../../fileRules/ignoredPaths.js";
9
10
  import { logInputOutput } from "../../utils/promptLogHelper.js";
10
11
  async function fetchSummariesForPaths(paths) {
11
12
  if (paths.length === 0)
@@ -63,14 +64,15 @@ export const fileSearchModule = {
63
64
  // -------------------------------------------------
64
65
  if (results.length === 0) {
65
66
  try {
66
- const exclude = IGNORED_EXTENSIONS
67
- .map(ext => `--exclude=*${ext}`)
68
- .join(" ");
69
- const stdout = execSync(`grep -ril ${exclude} "${subQuery}" "${repoRoot}"`, { encoding: "utf8" });
67
+ const excludeExtArgs = IGNORED_EXTENSIONS.map(ext => `--exclude=*${ext}`);
68
+ const excludeDirArgs = IGNORED_DIR_NAMES.map(dir => `--exclude-dir=${dir}`);
69
+ const grepArgs = ["-ril", ...excludeDirArgs, ...excludeExtArgs, subQuery, repoRoot];
70
+ const stdout = execFileSync("grep", grepArgs, { encoding: "utf8" });
70
71
  results = stdout
71
72
  .split("\n")
72
73
  .filter(Boolean)
73
- .map(f => ({ path: f }));
74
+ .map(f => ({ path: f }))
75
+ .filter(record => !isPathIgnoredByFolderGlobs(record.path));
74
76
  }
75
77
  catch (err) {
76
78
  if (err?.status !== 1) {
@@ -1,8 +1,11 @@
1
1
  import fs from "fs/promises";
2
2
  import path from "path";
3
+ import { isPathIgnoredByFolderGlobs } from "../fileRules/ignoredPaths.js";
3
4
  export async function resolveTargetsToFiles(targets, exts) {
4
5
  const files = [];
5
6
  for (const target of targets) {
7
+ if (isPathIgnoredByFolderGlobs(target))
8
+ continue;
6
9
  const stat = await fs.stat(target);
7
10
  if (stat.isDirectory()) {
8
11
  files.push(...await collectFilesRecursive(target, exts));
@@ -18,6 +21,8 @@ async function collectFilesRecursive(dir, exts) {
18
21
  const files = [];
19
22
  for (const entry of entries) {
20
23
  const fullPath = path.join(dir, entry.name);
24
+ if (isPathIgnoredByFolderGlobs(fullPath))
25
+ continue;
21
26
  if (entry.isDirectory()) {
22
27
  files.push(...await collectFilesRecursive(fullPath, exts));
23
28
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "scai",
3
- "version": "0.1.174",
3
+ "version": "0.1.176",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "scai": "./dist/index.js"