project-context
Version:
Собирает структуру проекта и содержимое файлов в Markdown для формирования контекстных подсказок (prompts).
127 lines • 5.24 kB
JavaScript
;
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.projectContext = projectContext;
const promises_1 = __importDefault(require("node:fs/promises"));
const node_path_1 = __importDefault(require("node:path"));
const micromatch_1 = __importDefault(require("micromatch"));
const DEFAULT_INCLUDE = ['**/*.ts', '**/*.tsx', '**/*.vue', '**/*.json', '**/*.md', '.editorconfig'];
const DEFAULT_EXCLUDE = ['node_modules', 'dist', 'build', 'out', 'coverage', '.git', 'package-lock.json', 'LICENSE'];
function collectFiles(root) {
return __awaiter(this, void 0, void 0, function* () {
const files = [];
function walk(current) {
return __awaiter(this, void 0, void 0, function* () {
const entries = yield promises_1.default.readdir(current, { withFileTypes: true });
for (const ent of entries) {
const res = node_path_1.default.join(current, ent.name);
if (ent.isDirectory()) {
yield walk(res);
}
else if (ent.isFile()) {
files.push(res);
}
}
});
}
yield walk(root);
return files;
});
}
function toPosix(p) {
return p.split(node_path_1.default.sep).join('/');
}
function isPlainPattern(p) {
// consider it "plain" if it contains no glob metacharacters
return !/[*?\[\]{}]/.test(p);
}
function shouldInclude(relPath, include, exclude) {
// exclude wins
for (const ex of exclude) {
if (!ex)
continue;
// direct micromatch support
try {
if (micromatch_1.default.isMatch(relPath, ex, { dot: true }))
return false;
}
catch (_) {
// ignore bad patterns
}
// treat plain patterns (no glob meta) as path prefixes
if (isPlainPattern(ex)) {
const normalized = ex.replace(/\\/g, '/');
if (relPath === normalized || relPath.startsWith(normalized + '/'))
return false;
}
}
// if include list is empty -> include everything (unless excluded)
if (!include || include.length === 0)
return true;
for (const inc of include) {
if (!inc)
continue;
try {
if (micromatch_1.default.isMatch(relPath, inc, { dot: true }))
return true;
}
catch (_) {
// ignore
}
if (isPlainPattern(inc)) {
const normalized = inc.replace(/\\/g, '/');
if (relPath === normalized || relPath.startsWith(normalized + '/'))
return true;
}
}
return false;
}
function fileSection(filePath, root) {
return __awaiter(this, void 0, void 0, function* () {
const rel = toPosix(node_path_1.default.relative(root, filePath));
const content = yield promises_1.default.readFile(filePath, 'utf8');
return `## ${rel}\n\n\`\`\`\n${content}\n\`\`\`\n`;
});
}
function joinParts(parts) {
return parts.join('\n');
}
// Reads directory recursively and returns markdown with filenames and contents.
// Supports include/exclude similar to tsconfig (using micromatch for matching).
function projectContext(dir_1) {
return __awaiter(this, arguments, void 0, function* (dir, options = {}) {
var _a;
const root = dir || process.cwd();
const limit = (_a = options.limit) !== null && _a !== void 0 ? _a : 10000;
const include = options.include ? options.include.slice() : DEFAULT_INCLUDE.slice();
const exclude = options.exclude ? options.exclude.slice() : DEFAULT_EXCLUDE.slice();
const files = yield collectFiles(root);
const parts = [];
// sort for deterministic output
files.sort();
for (const f of files) {
const rel = toPosix(node_path_1.default.relative(root, f));
if (!shouldInclude(rel, include, exclude))
continue;
const section = yield fileSection(f, root);
parts.push(section);
}
const out = joinParts(parts);
if (out.length > limit) {
throw new Error(`Output exceeds limit of ${limit} characters. Length is ${out.length}`);
}
return out;
});
}
//# sourceMappingURL=projectContext.js.map