mirror of
https://github.com/getcompanion-ai/co-mono.git
synced 2026-04-15 22:03:45 +00:00
fix(coding-agent): respect .gitignore/.ignore/.fdignore when scanning package resources
fixes #1072
This commit is contained in:
parent
678e3e28bd
commit
098f396cf3
5 changed files with 140 additions and 5 deletions
|
|
@ -2,7 +2,8 @@ import { spawn, spawnSync } from "node:child_process";
|
|||
import { createHash } from "node:crypto";
|
||||
import { existsSync, mkdirSync, readdirSync, readFileSync, rmSync, statSync, writeFileSync } from "node:fs";
|
||||
import { homedir, tmpdir } from "node:os";
|
||||
import { basename, dirname, join, relative, resolve } from "node:path";
|
||||
import { basename, dirname, join, relative, resolve, sep } from "node:path";
|
||||
import ignore from "ignore";
|
||||
import { minimatch } from "minimatch";
|
||||
import { CONFIG_DIR_NAME } from "../config.js";
|
||||
import { looksLikeGitUrl } from "../utils/git.js";
|
||||
|
|
@ -115,6 +116,57 @@ const FILE_PATTERNS: Record<ResourceType, RegExp> = {
|
|||
themes: /\.json$/,
|
||||
};
|
||||
|
||||
const IGNORE_FILE_NAMES = [".gitignore", ".ignore", ".fdignore"];
|
||||
|
||||
type IgnoreMatcher = ReturnType<typeof ignore>;
|
||||
|
||||
function toPosixPath(p: string): string {
|
||||
return p.split(sep).join("/");
|
||||
}
|
||||
|
||||
function prefixIgnorePattern(line: string, prefix: string): string | null {
|
||||
const trimmed = line.trim();
|
||||
if (!trimmed) return null;
|
||||
if (trimmed.startsWith("#") && !trimmed.startsWith("\\#")) return null;
|
||||
|
||||
let pattern = line;
|
||||
let negated = false;
|
||||
|
||||
if (pattern.startsWith("!")) {
|
||||
negated = true;
|
||||
pattern = pattern.slice(1);
|
||||
} else if (pattern.startsWith("\\!")) {
|
||||
pattern = pattern.slice(1);
|
||||
}
|
||||
|
||||
if (pattern.startsWith("/")) {
|
||||
pattern = pattern.slice(1);
|
||||
}
|
||||
|
||||
const prefixed = prefix ? `${prefix}${pattern}` : pattern;
|
||||
return negated ? `!${prefixed}` : prefixed;
|
||||
}
|
||||
|
||||
function addIgnoreRules(ig: IgnoreMatcher, dir: string, rootDir: string): void {
|
||||
const relativeDir = relative(rootDir, dir);
|
||||
const prefix = relativeDir ? `${toPosixPath(relativeDir)}/` : "";
|
||||
|
||||
for (const filename of IGNORE_FILE_NAMES) {
|
||||
const ignorePath = join(dir, filename);
|
||||
if (!existsSync(ignorePath)) continue;
|
||||
try {
|
||||
const content = readFileSync(ignorePath, "utf-8");
|
||||
const patterns = content
|
||||
.split(/\r?\n/)
|
||||
.map((line) => prefixIgnorePattern(line, prefix))
|
||||
.filter((line): line is string => Boolean(line));
|
||||
if (patterns.length > 0) {
|
||||
ig.add(patterns);
|
||||
}
|
||||
} catch {}
|
||||
}
|
||||
}
|
||||
|
||||
function isPattern(s: string): boolean {
|
||||
return s.startsWith("!") || s.startsWith("+") || s.startsWith("-") || s.includes("*") || s.includes("?");
|
||||
}
|
||||
|
|
@ -132,10 +184,20 @@ function splitPatterns(entries: string[]): { plain: string[]; patterns: string[]
|
|||
return { plain, patterns };
|
||||
}
|
||||
|
||||
function collectFiles(dir: string, filePattern: RegExp, skipNodeModules = true): string[] {
|
||||
function collectFiles(
|
||||
dir: string,
|
||||
filePattern: RegExp,
|
||||
skipNodeModules = true,
|
||||
ignoreMatcher?: IgnoreMatcher,
|
||||
rootDir?: string,
|
||||
): string[] {
|
||||
const files: string[] = [];
|
||||
if (!existsSync(dir)) return files;
|
||||
|
||||
const root = rootDir ?? dir;
|
||||
const ig = ignoreMatcher ?? ignore();
|
||||
addIgnoreRules(ig, dir, root);
|
||||
|
||||
try {
|
||||
const entries = readdirSync(dir, { withFileTypes: true });
|
||||
for (const entry of entries) {
|
||||
|
|
@ -156,8 +218,12 @@ function collectFiles(dir: string, filePattern: RegExp, skipNodeModules = true):
|
|||
}
|
||||
}
|
||||
|
||||
const relPath = toPosixPath(relative(root, fullPath));
|
||||
const ignorePath = isDir ? `${relPath}/` : relPath;
|
||||
if (ig.ignores(ignorePath)) continue;
|
||||
|
||||
if (isDir) {
|
||||
files.push(...collectFiles(fullPath, filePattern, skipNodeModules));
|
||||
files.push(...collectFiles(fullPath, filePattern, skipNodeModules, ig, root));
|
||||
} else if (isFile && filePattern.test(entry.name)) {
|
||||
files.push(fullPath);
|
||||
}
|
||||
|
|
@ -169,10 +235,19 @@ function collectFiles(dir: string, filePattern: RegExp, skipNodeModules = true):
|
|||
return files;
|
||||
}
|
||||
|
||||
function collectSkillEntries(dir: string, includeRootFiles = true): string[] {
|
||||
function collectSkillEntries(
|
||||
dir: string,
|
||||
includeRootFiles = true,
|
||||
ignoreMatcher?: IgnoreMatcher,
|
||||
rootDir?: string,
|
||||
): string[] {
|
||||
const entries: string[] = [];
|
||||
if (!existsSync(dir)) return entries;
|
||||
|
||||
const root = rootDir ?? dir;
|
||||
const ig = ignoreMatcher ?? ignore();
|
||||
addIgnoreRules(ig, dir, root);
|
||||
|
||||
try {
|
||||
const dirEntries = readdirSync(dir, { withFileTypes: true });
|
||||
for (const entry of dirEntries) {
|
||||
|
|
@ -193,8 +268,12 @@ function collectSkillEntries(dir: string, includeRootFiles = true): string[] {
|
|||
}
|
||||
}
|
||||
|
||||
const relPath = toPosixPath(relative(root, fullPath));
|
||||
const ignorePath = isDir ? `${relPath}/` : relPath;
|
||||
if (ig.ignores(ignorePath)) continue;
|
||||
|
||||
if (isDir) {
|
||||
entries.push(...collectSkillEntries(fullPath, false));
|
||||
entries.push(...collectSkillEntries(fullPath, false, ig, root));
|
||||
} else if (isFile) {
|
||||
const isRootMd = includeRootFiles && entry.name.endsWith(".md");
|
||||
const isSkillMd = !includeRootFiles && entry.name === "SKILL.md";
|
||||
|
|
@ -218,6 +297,9 @@ function collectAutoPromptEntries(dir: string): string[] {
|
|||
const entries: string[] = [];
|
||||
if (!existsSync(dir)) return entries;
|
||||
|
||||
const ig = ignore();
|
||||
addIgnoreRules(ig, dir, dir);
|
||||
|
||||
try {
|
||||
const dirEntries = readdirSync(dir, { withFileTypes: true });
|
||||
for (const entry of dirEntries) {
|
||||
|
|
@ -234,6 +316,9 @@ function collectAutoPromptEntries(dir: string): string[] {
|
|||
}
|
||||
}
|
||||
|
||||
const relPath = toPosixPath(relative(dir, fullPath));
|
||||
if (ig.ignores(relPath)) continue;
|
||||
|
||||
if (isFile && entry.name.endsWith(".md")) {
|
||||
entries.push(fullPath);
|
||||
}
|
||||
|
|
@ -249,6 +334,9 @@ function collectAutoThemeEntries(dir: string): string[] {
|
|||
const entries: string[] = [];
|
||||
if (!existsSync(dir)) return entries;
|
||||
|
||||
const ig = ignore();
|
||||
addIgnoreRules(ig, dir, dir);
|
||||
|
||||
try {
|
||||
const dirEntries = readdirSync(dir, { withFileTypes: true });
|
||||
for (const entry of dirEntries) {
|
||||
|
|
@ -265,6 +353,9 @@ function collectAutoThemeEntries(dir: string): string[] {
|
|||
}
|
||||
}
|
||||
|
||||
const relPath = toPosixPath(relative(dir, fullPath));
|
||||
if (ig.ignores(relPath)) continue;
|
||||
|
||||
if (isFile && entry.name.endsWith(".json")) {
|
||||
entries.push(fullPath);
|
||||
}
|
||||
|
|
@ -320,6 +411,9 @@ function collectAutoExtensionEntries(dir: string): string[] {
|
|||
const entries: string[] = [];
|
||||
if (!existsSync(dir)) return entries;
|
||||
|
||||
const ig = ignore();
|
||||
addIgnoreRules(ig, dir, dir);
|
||||
|
||||
try {
|
||||
const dirEntries = readdirSync(dir, { withFileTypes: true });
|
||||
for (const entry of dirEntries) {
|
||||
|
|
@ -340,6 +434,10 @@ function collectAutoExtensionEntries(dir: string): string[] {
|
|||
}
|
||||
}
|
||||
|
||||
const relPath = toPosixPath(relative(dir, fullPath));
|
||||
const ignorePath = isDir ? `${relPath}/` : relPath;
|
||||
if (ig.ignores(ignorePath)) continue;
|
||||
|
||||
if (isFile && (entry.name.endsWith(".ts") || entry.name.endsWith(".js"))) {
|
||||
entries.push(fullPath);
|
||||
} else if (isDir) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue