paqad-ai
Version:
Spec-driven development framework — AI agents that think before they type
6,009 lines • 200 kB
JavaScript
import { createRequire as __paqadCreateRequire } from 'module'; const require = __paqadCreateRequire(import.meta.url);
import {
loadOptionalPackage
} from "./chunk-GVVZTJJR.js";
import {
atomicWriteFile,
getPacksForFrameworks
} from "./chunk-K4E6ACLH.js";
import {
Bm25Index
} from "./chunk-CECHOUNN.js";
import {
readProjectProfile,
writeProjectProfile
} from "./chunk-AK3HYELM.js";
import {
allocateChatOrdinal,
currentChatOrdinal,
mirrorRagRow
} from "./chunk-5ETIY5I3.js";
import {
appendRunCancelledEvent,
readRawModuleMap
} from "./chunk-Y3O45QYV.js";
import {
require_out
} from "./chunk-P4RPU2KB.js";
import {
toPosixPath
} from "./chunk-LYNDVGNE.js";
import {
CancelledError,
FrameworkError,
engineLog,
getProjectSecret,
isCancelledError,
pluginOptionOrEnv,
redactSecrets,
writeProjectSecret
} from "./chunk-D6VEUSB6.js";
import {
readGitState
} from "./chunk-BOEVU2BD.js";
import {
getDefaultEmbeddingModel,
layeredConfigMap,
normalizeIntelligenceConfig,
require_ajv,
require_dist,
setConfigValue,
syncFrameworkConfig
} from "./chunk-ACJQW7SB.js";
import {
stampSessionRow
} from "./chunk-Q353EDKY.js";
import {
resolveSessionId
} from "./chunk-SG6LXLDH.js";
import {
slugifyAscii
} from "./chunk-AZLA53A6.js";
import {
PATHS
} from "./chunk-IHMBVS6H.js";
import {
__toESM,
init_esm_shims
} from "./chunk-IDFRWBMN.js";
// src/rag/service.ts
init_esm_shims();
import { createHash as createHash6, randomUUID } from "crypto";
import { existsSync as existsSync6, statSync } from "fs";
import { mkdir as mkdir4, readdir, rename as rename2, rm as rm3 } from "fs/promises";
import { extname, join as join11, resolve, sep as sep2 } from "path";
// src/context/ast-chunker.ts
init_esm_shims();
import { createHash } from "crypto";
var CHUNKER_VERSION = "cast-blurb-v1";
var AstChunker = class {
constructor(maxChunkChars = 2e3, merge = true) {
this.maxChunkChars = maxChunkChars;
this.merge = merge;
}
maxChunkChars;
merge;
chunk(filePath, content) {
const raw = this.chunkRaw(filePath, content);
return this.merge ? castMerge(raw, this.maxChunkChars) : raw;
}
chunkRaw(filePath, content) {
const lang = this.detectLanguage(filePath);
try {
switch (lang) {
case "typescript":
return this.parseTypeScript(filePath, content);
case "php":
return this.parsePhp(filePath, content);
case "dart":
return this.parseDart(filePath, content);
default:
return this.fallbackSplit(filePath, content);
}
} catch {
return this.fallbackSplit(filePath, content);
}
}
detectLanguage(filePath) {
if (filePath.endsWith(".ts") || filePath.endsWith(".tsx") || filePath.endsWith(".js") || filePath.endsWith(".jsx")) {
return "typescript";
}
if (filePath.endsWith(".php")) return "php";
if (filePath.endsWith(".dart")) return "dart";
return "unknown";
}
parseTypeScript(filePath, content) {
const chunks = [];
const blockPattern = /^(export\s+)?((?:async\s+)?function[\s*]+\w+|class\s+\w+|const\s+\w+\s*=\s*(?:async\s+)?\(|export\s+(?:default\s+)?(?:async\s+)?function|export\s+class\s+\w+)/gm;
const boundaries = [];
let match2;
while ((match2 = blockPattern.exec(content)) !== null) {
boundaries.push(match2.index);
}
boundaries.push(content.length);
for (let i = 0; i < boundaries.length - 1; i++) {
const start = boundaries[i];
const end = boundaries[i + 1];
const segment = content.slice(start, end);
if (segment.trim().length === 0) continue;
const nonWhitespaceCount = segment.replace(/\s/g, "").length;
if (nonWhitespaceCount > this.maxChunkChars) {
const subChunks = this.splitLargeSegment(filePath, segment);
chunks.push(...subChunks);
} else {
const nameMatch = /(?:function|class|const)\s+(\w+)/.exec(segment);
chunks.push(this.makeChunk(filePath, segment, "function", nameMatch?.[1] ?? `block_${i}`));
}
}
if (chunks.length === 0) return this.fallbackSplit(filePath, content);
return chunks;
}
parsePhp(filePath, content) {
const chunks = [];
const pattern = /^\s*(?:(?:public|protected|private|static|abstract|final)\s+)?(?:function|class|interface|trait)\s+\w+/gm;
const boundaries = [];
let match2;
while ((match2 = pattern.exec(content)) !== null) {
boundaries.push(match2.index);
}
boundaries.push(content.length);
for (let i = 0; i < boundaries.length - 1; i++) {
const segment = content.slice(boundaries[i], boundaries[i + 1]);
if (segment.trim().length === 0) continue;
const nameMatch = /(?:function|class|interface|trait)\s+(\w+)/.exec(segment);
chunks.push(this.makeChunk(filePath, segment, "function", nameMatch?.[1] ?? `block_${i}`));
}
if (chunks.length === 0) return this.fallbackSplit(filePath, content);
return chunks;
}
parseDart(filePath, content) {
const chunks = [];
const pattern = /^(\s*(?:abstract\s+)?class\s+\w+|^\s*\w[\w<>?]*\s+\w+\s*\()/gm;
const boundaries = [];
let match2;
while ((match2 = pattern.exec(content)) !== null) {
boundaries.push(match2.index);
}
boundaries.push(content.length);
for (let i = 0; i < boundaries.length - 1; i++) {
const segment = content.slice(boundaries[i], boundaries[i + 1]);
if (segment.trim().length === 0) continue;
const nameMatch = /(?:class|\w+)\s+(\w+)/.exec(segment);
chunks.push(this.makeChunk(filePath, segment, "class", nameMatch?.[1] ?? `block_${i}`));
}
if (chunks.length === 0) return this.fallbackSplit(filePath, content);
return chunks;
}
splitLargeSegment(filePath, segment) {
const methodPattern = /(?:async\s+)?(?:public|private|protected|static)?\s*(?:async\s+)?\w+\s*\([^)]*\)\s*(?::\s*\w+)?\s*\{/g;
const boundaries = [0];
let match2;
while ((match2 = methodPattern.exec(segment)) !== null) {
if (match2.index > 0) boundaries.push(match2.index);
}
boundaries.push(segment.length);
const chunks = [];
for (let i = 0; i < boundaries.length - 1; i++) {
const sub = segment.slice(boundaries[i], boundaries[i + 1]);
if (sub.trim().length > 0) {
chunks.push(this.makeChunk(filePath, sub, "method", `method_${i}`));
}
}
return chunks.length > 0 ? chunks : this.fallbackSplit(filePath, segment);
}
fallbackSplit(filePath, content) {
const paragraphs = content.split(/\n\s*\n/);
const chunks = [];
let buffer = "";
for (const para of paragraphs) {
const combined = buffer ? buffer + "\n\n" + para : para;
const nonWS = combined.replace(/\s/g, "").length;
if (nonWS > this.maxChunkChars && buffer) {
chunks.push(this.makeChunk(filePath, buffer, "fallback", `para_${chunks.length}`));
buffer = para;
} else {
buffer = combined;
}
}
if (buffer.trim()) {
chunks.push(this.makeChunk(filePath, buffer, "fallback", `para_${chunks.length}`));
}
return chunks.length > 0 ? chunks : [this.makeChunk(filePath, content, "fallback", "full")];
}
makeChunk(filePath, content, nodeType, name) {
const nonWS = content.replace(/\s/g, "");
return {
id: createHash("sha256").update(`${filePath}:${name}:${content}`).digest("hex"),
source_file: filePath,
ast_node_type: nodeType,
ast_node_path: name,
exported_symbols: this.extractExportedSymbols(content),
content: content.trim(),
char_count: nonWS.length,
content_hash: createHash("sha256").update(content).digest("hex")
};
}
extractExportedSymbols(content) {
const symbols = [];
const pattern = /export\s+(?:default\s+)?(?:function|class|const|interface|type|enum)\s+(\w+)/g;
let match2;
while ((match2 = pattern.exec(content)) !== null) {
symbols.push(match2[1]);
}
return symbols;
}
};
function castMerge(chunks, targetChars) {
const result = [];
let buffer = [];
let bufferChars = 0;
const flush = () => {
if (buffer.length === 0) {
return;
}
result.push(buffer.length === 1 ? buffer[0] : mergeChunks(buffer));
buffer = [];
bufferChars = 0;
};
for (const chunk of chunks) {
const breaksRun = buffer.length > 0 && (chunk.source_file !== buffer[0].source_file || bufferChars + chunk.char_count > targetChars);
if (breaksRun) {
flush();
}
buffer.push(chunk);
bufferChars += chunk.char_count;
}
flush();
return result;
}
function mergeChunks(chunks) {
const source_file = chunks[0].source_file;
const content = chunks.map((c) => c.content).join("\n\n");
const ast_node_path = chunks.map((c) => c.ast_node_path).join("+");
const exported_symbols = [...new Set(chunks.flatMap((c) => c.exported_symbols))];
const char_count = chunks.reduce((sum, c) => sum + c.char_count, 0);
const modified_at_ms = chunks.reduce(
(max, c) => c.modified_at_ms === void 0 ? max : Math.max(max ?? 0, c.modified_at_ms),
void 0
);
return {
id: createHash("sha256").update(`${source_file}:${ast_node_path}:${content}`).digest("hex"),
source_file,
ast_node_type: chunks[0].ast_node_type,
ast_node_path,
exported_symbols,
content,
char_count,
content_hash: createHash("sha256").update(content).digest("hex"),
...modified_at_ms === void 0 ? {} : { modified_at_ms }
};
}
// src/rag/contextual-blurb.ts
init_esm_shims();
function buildContextualBlurb(chunk, ctx = {}) {
const parts = [chunk.source_file];
const signature = chunk.ast_node_path?.trim();
if (signature && signature !== "full") {
parts.push(`\u203A ${signature}`);
}
if (chunk.exported_symbols && chunk.exported_symbols.length > 0) {
parts.push(`exports ${chunk.exported_symbols.join(", ")}`);
}
if (ctx.moduleRole) {
parts.push(`module: ${ctx.moduleRole}`);
}
return `[${parts.join(" \xB7 ")}]`;
}
function contextualizeChunkText(chunk, ctx = {}) {
return `${buildContextualBlurb(chunk, ctx)}
${chunk.content}`;
}
function buildModuleRoleResolver(projectRoot) {
let map;
try {
map = readRawModuleMap(projectRoot);
} catch {
return () => void 0;
}
if (!map) {
return () => void 0;
}
const entries = [];
for (const mod of map.modules) {
if (!mod.name) {
continue;
}
for (const source of mod.sources) {
const prefix2 = source.replace(/\\/g, "/").replace(/[*?[].*$/, "").replace(/\/+$/, "");
if (prefix2) {
entries.push({ prefix: prefix2, name: mod.name });
}
}
}
entries.sort((a, b) => b.prefix.length - a.prefix.length);
return (file) => {
const normalized = file.replace(/\\/g, "/").replace(/^\.\//, "");
const match2 = entries.find(
(entry) => normalized === entry.prefix || normalized.startsWith(`${entry.prefix}/`)
);
return match2?.name;
};
}
// src/context/chunk-index.ts
init_esm_shims();
import { readFile, writeFile, mkdir, stat } from "fs/promises";
import { join, dirname } from "path";
import { createHash as createHash2 } from "crypto";
var ChunkIndexManager = class {
constructor(projectRoot) {
this.projectRoot = projectRoot;
}
projectRoot;
get indexPath() {
return join(this.projectRoot, ".paqad", "context", "chunk-index.json");
}
async load() {
try {
const raw = await readFile(this.indexPath, "utf8");
return JSON.parse(raw);
} catch {
return null;
}
}
async save(index) {
await mkdir(dirname(this.indexPath), { recursive: true });
await writeFile(this.indexPath, JSON.stringify(index, null, 2), "utf8");
}
async rebuild(files, chunker) {
const entries = [];
for (const filePath of files) {
try {
const content = await readFile(filePath, "utf8");
const fileHash = this.computeFileHash(content);
const fileStat = await stat(filePath);
const chunks = chunker.chunk(filePath, content);
entries.push({
source_file: filePath,
source_file_hash: fileHash,
modified_at: fileStat.mtime.toISOString(),
chunks
});
} catch {
}
}
const index = {
version: 1,
generated_at: (/* @__PURE__ */ new Date()).toISOString(),
entries
};
await this.save(index);
return index;
}
async incrementalUpdate(changedFiles, index, chunker) {
const changedSet = new Set(changedFiles);
const updatedEntries = index.entries.filter((e) => !changedSet.has(e.source_file));
for (const filePath of changedFiles) {
try {
const content = await readFile(filePath, "utf8");
const fileHash = this.computeFileHash(content);
const fileStat = await stat(filePath);
const chunks = chunker.chunk(filePath, content);
updatedEntries.push({
source_file: filePath,
source_file_hash: fileHash,
modified_at: fileStat.mtime.toISOString(),
chunks
});
} catch {
}
}
const updated = {
...index,
generated_at: (/* @__PURE__ */ new Date()).toISOString(),
entries: updatedEntries
};
await this.save(updated);
return updated;
}
async isStale(index) {
const changedFiles = [];
for (const entry of index.entries) {
try {
const content = await readFile(entry.source_file, "utf8");
const currentHash = this.computeFileHash(content);
if (currentHash !== entry.source_file_hash) {
changedFiles.push(entry.source_file);
}
} catch {
changedFiles.push(entry.source_file);
}
}
return { stale: changedFiles.length > 0, changedFiles };
}
async sync(files, chunker) {
const normalizedFiles = [...new Set(files)].sort();
const index = await this.load();
if (!index) {
return {
index: await this.rebuild(normalizedFiles, chunker),
changed_files: [],
added_files: [...normalizedFiles],
deleted_files: [],
updated: true
};
}
const fileSet = new Set(normalizedFiles);
const indexedFiles = new Set(index.entries.map((entry) => entry.source_file));
const addedFiles = normalizedFiles.filter((filePath) => !indexedFiles.has(filePath));
const deletedFiles = index.entries.map((entry) => entry.source_file).filter((filePath) => !fileSet.has(filePath));
const changedFiles = [];
for (const entry of index.entries) {
if (!fileSet.has(entry.source_file)) {
continue;
}
try {
const content = await readFile(entry.source_file, "utf8");
const currentHash = this.computeFileHash(content);
if (currentHash !== entry.source_file_hash) {
changedFiles.push(entry.source_file);
}
} catch {
deletedFiles.push(entry.source_file);
}
}
const changedSet = /* @__PURE__ */ new Set([...addedFiles, ...changedFiles]);
const deletedSet = new Set(deletedFiles);
if (changedSet.size === 0 && deletedSet.size === 0) {
return {
index,
changed_files: changedFiles,
added_files: addedFiles,
deleted_files: deletedFiles,
updated: false
};
}
const updatedEntries = index.entries.filter(
(entry) => !changedSet.has(entry.source_file) && !deletedSet.has(entry.source_file)
);
for (const filePath of changedSet) {
try {
const content = await readFile(filePath, "utf8");
const fileHash = this.computeFileHash(content);
const fileStat = await stat(filePath);
const chunks = chunker.chunk(filePath, content);
updatedEntries.push({
source_file: filePath,
source_file_hash: fileHash,
modified_at: fileStat.mtime.toISOString(),
chunks
});
} catch {
}
}
const updated = {
...index,
generated_at: (/* @__PURE__ */ new Date()).toISOString(),
entries: updatedEntries.sort(
(left, right) => left.source_file.localeCompare(right.source_file)
)
};
await this.save(updated);
return {
index: updated,
changed_files: changedFiles,
added_files: addedFiles,
deleted_files: deletedFiles,
updated: true
};
}
computeFileHash(content) {
return createHash2("sha256").update(content).digest("hex");
}
};
// src/context/reranker.ts
init_esm_shims();
var PassthroughReranker = class {
backend = "passthrough";
model = "none";
async rerank(_query, chunks, candidatePoolSize = 50) {
const pool = chunks.slice(0, candidatePoolSize);
const ids = pool.map((c) => c.id);
return {
chunks: pool,
pre_rerank_ids: ids,
post_rerank_ids: ids,
latency_ms: 0
};
}
};
var LocalReranker = class {
backend = "local";
model;
pipelinePromise;
constructor(model = "Xenova/ms-marco-MiniLM-L-6-v2") {
this.model = model;
}
getPipeline() {
if (!this.pipelinePromise) {
this.pipelinePromise = this.loadPipeline();
}
return this.pipelinePromise;
}
async loadPipeline() {
const { pipeline } = await loadOptionalPackage("@huggingface/transformers");
return pipeline("text-classification", this.model, { dtype: "q8" });
}
async rerank(query, chunks, candidatePoolSize = 50) {
const pool = chunks.slice(0, candidatePoolSize);
const preIds = pool.map((c) => c.id);
const start = Date.now();
const classifier = await this.getPipeline();
const scored = await Promise.all(
pool.map(async (chunk) => {
const result = await classifier({ text: query, text_pair: chunk.content });
const item = Array.isArray(result) ? result[0] : result;
return { chunk, score: item?.score ?? 0 };
})
);
scored.sort((a, b) => b.score - a.score);
const reranked = scored.map((s) => s.chunk);
return {
chunks: reranked,
pre_rerank_ids: preIds,
post_rerank_ids: reranked.map((c) => c.id),
latency_ms: Date.now() - start
};
}
};
var CohereReranker = class {
backend = "cohere";
model;
apiKey;
constructor(model = "rerank-english-v3.0", apiKey = "") {
this.model = model;
this.apiKey = apiKey;
}
async rerank(query, chunks, candidatePoolSize = 50) {
const pool = chunks.slice(0, candidatePoolSize);
const preIds = pool.map((c) => c.id);
const start = Date.now();
const response = await fetch("https://api.cohere.com/v1/rerank", {
method: "POST",
headers: {
Authorization: `Bearer ${this.apiKey}`,
"Content-Type": "application/json"
},
body: JSON.stringify({
model: this.model,
query,
documents: pool.map((c) => c.content)
})
});
if (!response.ok) {
throw new Error(`Cohere rerank failed: ${response.status} ${await response.text()}`);
}
const data = await response.json();
const reranked = data.results.map((r) => pool[r.index]);
return {
chunks: reranked,
pre_rerank_ids: preIds,
post_rerank_ids: reranked.map((c) => c.id),
latency_ms: Date.now() - start
};
}
};
function createReranker(config) {
if (!config?.enabled) {
return new PassthroughReranker();
}
switch (config.backend) {
case "local":
return new LocalReranker(config.model);
case "cohere":
return new CohereReranker(
config.model,
pluginOptionOrEnv("COHERE_API_KEY") || config.api_key || ""
);
default:
return new PassthroughReranker();
}
}
// src/patterns/pattern-rag.ts
init_esm_shims();
import { createHash as createHash4 } from "crypto";
import { homedir as homedir3 } from "os";
// src/core/math/cosine.ts
init_esm_shims();
function cosineSimilarity(left, right) {
if (left.length !== right.length || left.length === 0) {
return 0;
}
let dot = 0;
let leftNorm = 0;
let rightNorm = 0;
for (let index = 0; index < left.length; index++) {
dot += left[index] * right[index];
leftNorm += left[index] * left[index];
rightNorm += right[index] * right[index];
}
if (leftNorm === 0 || rightNorm === 0) {
return 0;
}
return dot / (Math.sqrt(leftNorm) * Math.sqrt(rightNorm));
}
// src/rag/providers.ts
init_esm_shims();
import { existsSync as existsSync2 } from "fs";
import { rm } from "fs/promises";
import { homedir } from "os";
import { join as join3 } from "path";
// src/rag/removed-provider.ts
init_esm_shims();
var import_yaml = __toESM(require_dist(), 1);
import { existsSync, readFileSync } from "fs";
import { join as join2 } from "path";
var REMOVED_EMBEDDING_PROVIDERS = ["voyageai"];
var REMOVED_EMBEDDING_PROVIDER_MESSAGE = "The Voyage embedding provider was removed from paqad-ai. Choose local or openai instead: set rag_embedding_provider (or PAQAD_RAG_EMBEDDING_PROVIDER) to local or openai, or run `paqad-ai rag init --provider local`.";
var RemovedEmbeddingProviderError = class extends Error {
constructor(provider) {
super(REMOVED_EMBEDDING_PROVIDER_MESSAGE);
this.provider = provider;
this.name = "RemovedEmbeddingProviderError";
}
provider;
};
function findRemovedEmbeddingProvider(value) {
if (typeof value !== "string") {
return void 0;
}
const normalized = value.trim().toLowerCase();
return REMOVED_EMBEDDING_PROVIDERS.find((name) => name === normalized);
}
function profileEmbeddingProvider(projectRoot) {
const path2 = join2(projectRoot, PATHS.PROJECT_PROFILE);
if (!existsSync(path2)) {
return void 0;
}
try {
const parsed = import_yaml.default.parse(readFileSync(path2, "utf8"));
return parsed?.intelligence?.embedding_provider;
} catch {
return void 0;
}
}
function resolveRemovedEmbeddingProvider(projectRoot, env = process.env) {
const configured = layeredConfigMap(projectRoot, env).get("rag_embedding_provider")?.trim();
return findRemovedEmbeddingProvider(
configured ? configured : profileEmbeddingProvider(projectRoot)
);
}
function assertEmbeddingProviderAvailable(projectRoot, explicit, env = process.env) {
const removed = explicit !== void 0 && explicit.trim() !== "" ? findRemovedEmbeddingProvider(explicit) : resolveRemovedEmbeddingProvider(projectRoot, env);
if (removed) {
throw new RemovedEmbeddingProviderError(removed);
}
}
// src/rag/types.ts
init_esm_shims();
var EmbeddingProviderError = class extends Error {
constructor(provider, code, message, cause) {
super(message);
this.provider = provider;
this.code = code;
this.cause = cause;
this.name = "EmbeddingProviderError";
}
provider;
code;
cause;
};
function isEmbeddingProviderError(error) {
return error instanceof EmbeddingProviderError;
}
var SUPPORTED_EXTRACTION_KINDS = [
"ocr",
"caption"
];
var SUPPORTED_VISION_EXTENSIONS = [
".png",
".jpg",
".jpeg",
".gif",
".webp",
".tiff",
".bmp",
".avif",
".heic"
];
var RagIngestError = class extends FrameworkError {
constructor(code, message, details) {
super(message, { code, details });
this.name = "RagIngestError";
}
};
function isRagIngestError(error) {
return error instanceof RagIngestError;
}
function toCrsCollectionId(id) {
if (id.trim().length === 0) {
throw new Error("CRS collection id must be a non-empty string");
}
return id;
}
var EmbeddingBacklogOverflow = class extends Error {
constructor(dropped_count, message) {
super(message ?? `Embedding backlog overflow: dropped ${dropped_count} pending chunk(s)`);
this.dropped_count = dropped_count;
this.name = "EmbeddingBacklogOverflow";
}
dropped_count;
};
function isEmbeddingBacklogOverflow(error) {
return error instanceof EmbeddingBacklogOverflow;
}
// src/rag/providers.ts
var REMOTE_RETRY_DELAYS_MS = [250, 500, 1e3];
var LOCAL_EMBEDDING_DTYPE = "q8";
function localModelsRoot() {
return join3(homedir(), ".paqad", "models");
}
function localModelDir(model) {
return join3(localModelsRoot(), model);
}
function providerError(provider, code, message, cause) {
return new EmbeddingProviderError(provider, code, message, cause);
}
function classifyRemoteError(provider, error) {
if (error instanceof EmbeddingProviderError) {
return error;
}
const status = typeof error === "object" && error !== null && "status" in error ? Number(error.status) : typeof error === "object" && error !== null && "response" in error && typeof error.response?.status === "number" ? Number(error.response?.status) : void 0;
const message = error instanceof Error ? error.message : String(error);
if (status === 401 || status === 403) {
return providerError(provider, "invalid_api_key", "Invalid OPENAI_API_KEY", error);
}
if (status === 429 || /rate.?limit/i.test(message)) {
return providerError(
provider,
"rate_limited",
`${provider} embedding rate limit reached`,
error
);
}
return providerError(provider, "provider_error", message, error);
}
function sleep(ms) {
return new Promise((resolve2) => setTimeout(resolve2, ms));
}
async function withRemoteRetry(provider, operation, onProgress) {
for (let attempt = 0; attempt <= REMOTE_RETRY_DELAYS_MS.length; attempt++) {
try {
return await operation();
} catch (error) {
const classified = classifyRemoteError(provider, error);
if (classified.code !== "rate_limited" || attempt === REMOTE_RETRY_DELAYS_MS.length) {
throw classified;
}
const delayMs = REMOTE_RETRY_DELAYS_MS[attempt];
onProgress?.({
phase: "build",
message: `${classified.message}; retrying in ${delayMs}ms`
});
await sleep(delayMs);
}
}
throw providerError(provider, "provider_error", `Unexpected ${provider} retry failure`);
}
var LocalEmbeddingProvider = class {
constructor(intelligence, onProgress, onLoadFailed) {
this.onProgress = onProgress;
this.onLoadFailed = onLoadFailed;
this.model = intelligence.embedding_model ?? getDefaultEmbeddingModel("local");
}
onProgress;
onLoadFailed;
name = "local";
model;
extractorPromise;
async validate() {
await this.getExtractor();
}
async embed(input) {
const extractor = await this.getExtractor();
const batch = Array.isArray(input) ? input : [input];
const output = await extractor(batch, { pooling: "mean", normalize: true });
const rawValues = typeof output === "object" && output !== null && "tolist" in output && typeof output.tolist === "function" ? output.tolist() : output;
return Array.isArray(rawValues[0]) ? rawValues : [rawValues];
}
async getExtractor() {
if (!this.extractorPromise) {
const loading = this.loadExtractor();
this.extractorPromise = loading;
loading.catch(() => {
if (this.extractorPromise === loading) this.extractorPromise = void 0;
this.onLoadFailed?.();
});
}
return this.extractorPromise;
}
async loadExtractor() {
const modelPath = localModelDir(this.model);
const cached = existsSync2(modelPath);
this.onProgress?.({
phase: "load",
message: cached ? `Loading cached local embedding model ${this.model}` : `Preparing local embedding model ${this.model}. This shared download only happens once.`
});
try {
const { pipeline, env } = await loadOptionalPackage("@huggingface/transformers");
const modelsDir = toPosixPath(localModelsRoot());
env.cacheDir = modelsDir;
env.localModelPath = modelsDir;
env.allowLocalModels = true;
env.allowRemoteModels = !cached;
return pipeline("feature-extraction", this.model, {
dtype: LOCAL_EMBEDDING_DTYPE,
progress_callback: (progress) => {
const loaded = progress.loaded ?? 0;
const total = progress.total ?? 0;
const percent = total > 0 ? Math.round(loaded / total * 100) : void 0;
this.onProgress?.({
phase: "download",
message: progress.status ?? `Downloading local model ${this.model}. This shared download only happens once.`,
loaded,
total,
percent
});
}
});
} catch (error) {
await rm(modelPath, { recursive: true, force: true }).catch(() => void 0);
throw providerError(
"local",
"download_failed",
`Failed to load local model ${this.model}`,
error
);
}
}
};
var OpenAiEmbeddingProvider = class {
constructor(projectRoot, intelligence, onProgress) {
this.projectRoot = projectRoot;
this.onProgress = onProgress;
this.model = intelligence.embedding_model ?? getDefaultEmbeddingModel("openai");
}
projectRoot;
onProgress;
name = "openai";
model;
clientPromise;
async validate() {
await this.embed("validation");
}
async embed(input) {
const client = await this.getClient();
const response = await withRemoteRetry(
"openai",
() => client.embeddings.create({
model: this.model,
input
}),
this.onProgress
);
return response.data.map((entry) => entry.embedding);
}
async getClient() {
if (!this.clientPromise) {
this.clientPromise = this.createClient();
}
return this.clientPromise;
}
async createClient() {
const apiKey = getProjectSecret(this.projectRoot, "OPENAI_API_KEY");
if (!apiKey) {
throw providerError("openai", "missing_api_key", "Missing OPENAI_API_KEY");
}
const { OpenAI } = await loadOptionalPackage("openai");
return new OpenAI({ apiKey });
}
};
var localProviderCache = /* @__PURE__ */ new Map();
var providerCacheEnabled = false;
function enableEmbeddingProviderCache() {
providerCacheEnabled = true;
}
function resetEmbeddingProviderCache() {
localProviderCache.clear();
providerCacheEnabled = false;
}
function localProviderKey(intelligence) {
const model = intelligence.embedding_model ?? getDefaultEmbeddingModel("local");
return ["local", model, LOCAL_EMBEDDING_DTYPE, localModelsRoot()].join("|");
}
function cachedLocalProvider(intelligence) {
const key = localProviderKey(intelligence);
const cached = localProviderCache.get(key);
if (cached) {
return cached;
}
const provider = new LocalEmbeddingProvider(
intelligence,
void 0,
() => {
if (localProviderCache.get(key) === provider) localProviderCache.delete(key);
}
);
localProviderCache.set(key, provider);
return provider;
}
async function createEmbeddingProvider(projectRoot, intelligence, onProgress) {
assertEmbeddingProviderAvailable(projectRoot, intelligence.embedding_provider);
switch (intelligence.embedding_provider) {
case "openai":
return new OpenAiEmbeddingProvider(projectRoot, intelligence, onProgress);
case "local":
default:
if (providerCacheEnabled && onProgress === void 0) {
return cachedLocalProvider(intelligence);
}
return new LocalEmbeddingProvider(intelligence, onProgress);
}
}
// src/rag/vector-index.ts
init_esm_shims();
import { mkdir as mkdir2, readFile as readFile2, rename, rm as rm2, stat as stat2, writeFile as writeFile2 } from "fs/promises";
import { existsSync as existsSync3 } from "fs";
import { join as join5 } from "path";
import { dirname as dirname2 } from "path";
// src/rag/crs-paths.ts
init_esm_shims();
import { createHash as createHash3 } from "crypto";
import { join as join4 } from "path";
var SAFE_ID = /[^A-Za-z0-9._-]/g;
function escapeCollectionId(id) {
const raw = String(id);
if (raw.trim().length === 0) {
throw new Error("CRS collection id must be a non-empty string");
}
const safe = raw.replace(SAFE_ID, "_").replace(/\.{2,}/g, "_");
if (safe === ".") {
throw new Error(`CRS collection id escapes to an unusable directory name: ${raw}`);
}
if (safe === raw) {
return safe;
}
const hash = createHash3("sha256").update(raw).digest("hex").slice(0, 8);
return `${safe}-${hash}`;
}
function crsCollectionDir(projectRoot, collectionId) {
return join4(projectRoot, PATHS.CRS_DIR, escapeCollectionId(collectionId));
}
function crsCollectionPaths(collectionId) {
const dir = join4(PATHS.CRS_DIR, escapeCollectionId(collectionId));
return { indexPath: join4(dir, "index.json"), metaPath: join4(dir, "meta.json") };
}
function crsCollectionLayout(projectRoot, collectionId) {
const escaped = escapeCollectionId(collectionId);
const relDir = join4(PATHS.CRS_DIR, escaped);
return {
escaped,
crsRootAbs: join4(projectRoot, PATHS.CRS_DIR),
relDir,
absDir: join4(projectRoot, relDir),
indexPath: join4(relDir, "index.json"),
metaPath: join4(relDir, "meta.json")
};
}
// src/rag/vector-index.ts
var CorruptVectorIndexError = class extends Error {
constructor(kind, filePath, cause) {
super(`Corrupt vector ${kind} file at ${filePath}`);
this.kind = kind;
this.filePath = filePath;
this.cause = cause;
this.name = "CorruptVectorIndexError";
}
kind;
filePath;
cause;
};
async function atomicWrite(path2, content) {
await mkdir2(dirname2(path2), { recursive: true });
const tmp = `${path2}.tmp`;
await writeFile2(tmp, content, "utf8");
await rename(tmp, path2);
}
var COLLECTION_LOCK_RETRIES = 50;
var COLLECTION_LOCK_DELAY_MS = 10;
var delay = (ms) => new Promise((resolve2) => setTimeout(resolve2, ms));
async function withCollectionLock(dir, fn) {
const lockPath = join5(dir, ".lock");
await mkdir2(dir, { recursive: true });
let acquired = false;
for (let attempt = 0; attempt < COLLECTION_LOCK_RETRIES; attempt++) {
try {
await mkdir2(lockPath);
acquired = true;
break;
} catch {
await delay(COLLECTION_LOCK_DELAY_MS);
}
}
try {
return await fn();
} finally {
if (acquired) {
await rm2(lockPath, { recursive: true, force: true });
}
}
}
var FileVectorIndex = class _FileVectorIndex {
constructor(indexPath = PATHS.VECTOR_INDEX, metaPath = PATHS.VECTOR_META) {
this.indexPath = indexPath;
this.metaPath = metaPath;
}
indexPath;
metaPath;
async load(projectRoot) {
const path2 = this.resolveExistingIndexPath(projectRoot);
if (!existsSync3(path2)) {
return null;
}
try {
const raw = await readFile2(path2, "utf8");
return JSON.parse(raw);
} catch (error) {
throw new CorruptVectorIndexError("index", path2, error);
}
}
async loadMeta(projectRoot) {
const path2 = this.resolve(projectRoot, this.metaPath);
if (!existsSync3(path2)) {
return null;
}
try {
const raw = await readFile2(path2, "utf8");
return JSON.parse(raw);
} catch (error) {
throw new CorruptVectorIndexError("meta", path2, error);
}
}
async save(projectRoot, payload, meta) {
const target = this.resolve(projectRoot, this.indexPath);
await atomicWrite(target, JSON.stringify(payload, null, 2));
await atomicWrite(this.resolve(projectRoot, this.metaPath), JSON.stringify(meta, null, 2));
const legacyPath = this.resolveLegacyIndexPath(projectRoot);
if (legacyPath !== target && existsSync3(legacyPath)) {
await rm2(legacyPath, { force: true });
}
}
async clear(projectRoot) {
await rm2(dirname2(this.resolve(projectRoot, this.indexPath)), { recursive: true, force: true });
}
async query(projectRoot, vector, topN) {
const payload = await this.load(projectRoot);
if (!payload) {
return [];
}
return payload.items.map((item) => ({ item, score: cosineSimilarity(vector, item.vector) })).sort((left, right) => right.score - left.score).slice(0, topN);
}
async status(projectRoot) {
const indexPath = this.resolveExistingIndexPath(projectRoot);
if (!existsSync3(indexPath)) {
return { present: false, sizeBytes: 0, meta: null, corrupt: false };
}
const info = await stat2(indexPath);
let meta;
try {
meta = await this.loadMeta(projectRoot);
} catch (error) {
if (error instanceof CorruptVectorIndexError) {
return {
present: true,
sizeBytes: info.size,
meta: null,
corrupt: true,
reason: error.kind === "meta" ? "vector metadata is unreadable" : "vector index payload is unreadable"
};
}
throw error;
}
try {
await this.load(projectRoot);
} catch (error) {
if (error instanceof CorruptVectorIndexError) {
return {
present: true,
sizeBytes: info.size,
meta,
corrupt: true,
reason: error.kind === "meta" ? "vector metadata is unreadable" : "vector index payload is unreadable"
};
}
throw error;
}
return {
present: true,
sizeBytes: info.size,
meta,
corrupt: meta === null,
reason: meta === null ? "vector metadata is missing" : void 0
};
}
async replaceAll(projectRoot, items, metaInput, baseBranch) {
const dimensions = items[0]?.vector.length ?? 0;
const meta = {
version: 1,
provider: metaInput.provider,
model: metaInput.model,
built_at: (/* @__PURE__ */ new Date()).toISOString(),
chunk_count: items.length,
embedding_dimensions: dimensions,
...metaInput.chunker_version ? { chunker_version: metaInput.chunker_version } : {},
...readGitState(projectRoot, baseBranch ? { baseBranch } : {})
};
const payload = { version: 1, dimensions, items };
await this.save(projectRoot, payload, meta);
return meta;
}
static async create(projectRoot, collectionId) {
const dir = crsCollectionDir(projectRoot, collectionId);
await mkdir2(dir, { recursive: true });
const { indexPath, metaPath } = crsCollectionPaths(collectionId);
const absIndex = join5(projectRoot, indexPath);
const absMeta = join5(projectRoot, metaPath);
if (existsSync3(absIndex) || existsSync3(absMeta)) {
return;
}
const payload = { version: 1, dimensions: 0, items: [] };
const meta = {
version: 1,
provider: "local",
model: "",
built_at: (/* @__PURE__ */ new Date()).toISOString(),
chunk_count: 0,
embedding_dimensions: 0
};
await atomicWrite(absIndex, JSON.stringify(payload, null, 2));
await atomicWrite(absMeta, JSON.stringify(meta, null, 2));
}
static async destroy(projectRoot, collectionId, options) {
const dir = crsCollectionDir(projectRoot, collectionId);
if (!existsSync3(dir)) {
return 0;
}
const { indexPath, metaPath } = crsCollectionPaths(collectionId);
const index = new _FileVectorIndex(indexPath, metaPath);
if (options?.sourceSessionId !== void 0) {
const sessionId = options.sourceSessionId;
return withCollectionLock(dir, async () => {
const payload = await index.load(projectRoot);
if (!payload) {
return 0;
}
const kept = payload.items.filter((item) => item.source_session_id !== sessionId);
const removed = payload.items.length - kept.length;
if (removed > 0) {
const meta = await index.loadMeta(projectRoot);
await index.replaceAll(projectRoot, kept, {
provider: meta?.provider ?? "local",
model: meta?.model ?? ""
});
}
return removed;
});
}
return withCollectionLock(dir, async () => {
let count = 0;
try {
const payload = await index.load(projectRoot);
count = payload?.items.length ?? 0;
} catch {
}
await rm2(dir, { recursive: true, force: true });
return count;
});
}
resolve(projectRoot, relativePath) {
return join5(projectRoot, relativePath);
}
resolveExistingIndexPath(projectRoot) {
const current = this.resolve(projectRoot, this.indexPath);
if (existsSync3(current)) {
return current;
}
return this.resolveLegacyIndexPath(projectRoot);
}
resolveLegacyIndexPath(projectRoot) {
if (!this.indexPath.endsWith(".json")) {
return this.resolve(projectRoot, this.indexPath);
}
return this.resolve(projectRoot, this.indexPath.replace(/\.json$/, ".bin"));
}
};
// src/patterns/pattern-store.ts
init_esm_shims();
import { readFile as readFile3, writeFile as writeFile3, mkdir as mkdir3, unlink } from "fs/promises";
import { join as join6, dirname as dirname3 } from "path";
import { homedir as homedir2 } from "os";
var GLOBAL_PATTERNS_DIR = join6(homedir2(), ".paqad", "patterns");
var PatternStore = class {
get indexPath() {
return join6(GLOBAL_PATTERNS_DIR, "index.json");
}
get entriesDir() {
return join6(GLOBAL_PATTERNS_DIR, "entries");
}
async save(pattern) {
await mkdir3(this.entriesDir, { recursive: true });
const entryPath = join6(this.entriesDir, `${pattern.id}.json`);
await writeFile3(entryPath, JSON.stringify(pattern, null, 2), "utf8");
await this.updateIndex(pattern);
}
async load(id) {
try {
const raw = await readFile3(join6(this.entriesDir, `${id}.json`), "utf8");
return JSON.parse(raw);
} catch {
return null;
}
}
async loadIndex() {
try {
const raw = await readFile3(this.indexPath, "utf8");
return JSON.parse(raw);
} catch {
return { version: 1, entries: [] };
}
}
async updateIndex(pattern) {
const index = await this.loadIndex();
const existingIdx = index.entries.findIndex((e) => e.id === pattern.id);
const entry = {
id: pattern.id,
category: pattern.category,
stack_filter: pattern.stack_filter,
tags: pattern.tags,
created_at: pattern.created_at,
problem_preview: pattern.problem.slice(0, 100)
};
if (existingIdx >= 0) {
index.entries[existingIdx] = entry;
} else {
index.entries.push(entry);
}
await mkdir3(dirname3(this.indexPath), { recursive: true });
await writeFile3(this.indexPath, JSON.stringify(index, null, 2), "utf8");
}
async delete(id) {
try {
await unlink(join6(this.entriesDir, `${id}.json`));
} catch (error) {
if (error.code !== "ENOENT") {
throw error;
}
}
const index = await this.loadIndex();
index.entries = index.entries.filter((e) => e.id !== id);
await writeFile3(this.indexPath, JSON.stringify(index, null, 2), "utf8");
}
async list(filter2) {
const index = await this.loadIndex();
let entries = index.entries;
if (filter2?.domain) {
entries = entries.filter((e) => e.stack_filter.domain === filter2.domain);
}
if (filter2?.frameworks && filter2.frameworks.length > 0) {
entries = entries.filter(
(e) => filter2.frameworks.some((f) => e.stack_filter.frameworks.includes(f))
);
}
if (filter2?.category) {
entries = entries.filter((e) => e.category === filter2.category);
}
if (filter2?.keywords && filter2.keywords.length > 0) {
entries = entries.filter(
(e) => filter2.keywords.some(
(kw) => e.problem_preview.toLowerCase().includes(kw.toLowerCase()) || e.tags.some((t) => t.toLowerCase().includes(kw.toLowerCase()))
)
);
}
const patterns = [];
for (const entry of entries) {
const pattern = await this.load(entry.id);
if (pattern) patterns.push(pattern);
}
return patterns;
}
};
// src/patterns/pattern-suggester.ts
init_esm_shims();
var STALENESS_THRESHOLD_DAYS = 180;
var STALE_SCORE_PENALTY = 0.15;
var PatternSuggester = class {
constructor(store, relevanceThreshold = 0.3, semanticScorer) {
this.store = store;
this.relevanceThreshold = relevanceThreshold;
this.semanticScorer = semanticScorer;
}
store;
relevanceThreshold;
semanticScorer;
async suggest(keywords, domain, frameworks, limit = 3) {
const filter2 = { domain, frameworks };
const patterns = await this.store.list(filter2);
const semanticScores = await Promise.all(
patterns.map(async (pattern) => ({
id: pattern.id,
score: this.semanticScorer ? await this.semanticScorer(pattern, keywords) : 0
}))
);
const semanticScoreMap = new Map(semanticScores.map((entry) => [entry.id, entry.score]));
const scored = patterns.map((p) => ({
pattern: p,
is_stale: this.isStale(p),
score: this.score(
p,
keywords,
frameworks,
semanticScoreMap.get(p.id) ?? 0,
this.isStale(p)
)
})).filter(({ score }) => score >= this.relevanceThreshold).sort((a, b) => b.score - a.score).slice(0, limit);
return scored;
}
score(pattern, keywords, frameworks, semanticScore, isStale) {
let score = 0;
const frameworkMatch = frameworks.filter(
(f) => pattern.stack_filter.frameworks.includes(f)
).length;
if (frameworks.length > 0) {
score += frameworkMatch / frameworks.length * 0.25;
}
if (keywords.length > 0) {
const text = `${pattern.problem} ${pattern.tags.join(" ")}`.toLowerCase();
const matches = keywords.filter((kw) => text.includes(kw.toLowerCase())).length;
score += matches / keywords.length * 0.35;
}
return score + semanticScore * 0.4 - (isStale ? STALE_SCORE_PENALTY : 0);
}
isStale(pattern) {
const ageMs = Date.now() - new Date(pattern.created_at).getTime();
const ageDays = ageMs / (1e3 * 60 * 60 * 24);
return ageDays > STALENESS_THRESHOLD_DAYS;
}
};
// src/patterns/pattern-rag.ts
function patternText(pattern) {
return [
pattern.problem,
pattern.solution,
...pattern.tags,
...pattern.files_involved,
...pattern.stack_filter.frameworks,
...pattern.stack_filter.traits
].join("\n");
}
function fingerprint(pattern) {
return createHash4("sha1").update(patternText(pattern)).digest("hex");
}
function sanitizeNamespaceSegment(value) {
const normalized = slugifyAscii(value.trim());
return normalized.length > 0 ? normalized : createHash4("sha1").update(value).digest("hex").slice(0, 12);
}
function getGlobalPatternVectorPaths(provider, model) {
if (!provider || !model) {
return {
index: PATHS.GLOBAL_PATTERN_VECTOR_INDEX,
meta: PATHS.GLOBAL_PATTERN_VECTOR_META
};
}
const namespace = `${sanitizeNamespaceSegment(provider)}/${sanitizeNamespaceSegment(model)}`;
return {
index: `${PATHS.GLOBAL_PATTERN_VECTORS_DIR}/${namespace}/index.json`,
meta: `${PATHS.GLOBAL_PATTERN_VECTORS_DIR}/${namespace}/meta.json`
};
}
var PatternVectorService = class {
constructor(store = new PatternStore(), providerFactory = createEmbeddingProvider) {
this.store = store;
this.providerFactory = providerFactory;
}
store;
providerFactory;
getIndex(projectRoot) {
const profile = readProjectProfile(projectRoot);
const intelligence = normalizeIntelligenceConfig(profile?.intelligence);
const paths = getGlobalPatternVectorPaths(
intelligence.embedding_provider,
intelligence.embedding_provider ? intelligence.embedding_model ?? getDefaultEmbeddingModel(intelligence.embedding_provider) : void 0
);
return new FileVectorIndex(paths.index, paths.meta);
}
async getStatus(projectRoot) {
const profile = readProjectProfile(projectRoot);
const intelligence = normalizeIntelligenceConfig(profile?.intelligence);
const index = this.getIndex(projectRoot);
const status = await index.status(homedir3());
const expectedModel = intelligence.embedding_provider ? intelligence.embedding_model ?? getDefaultEmbeddingModel(intelligence.embedding_provider) : void 0;
const valid = Boolean(status.meta) && !status.corrupt && (!intelligence.rag_enabled || status.meta?.provider === intelligence.embedding_provider && status.meta?.model === expectedModel);
return {
present: status.present,
valid,
chunk_count: status.meta?.chunk_count ?? 0,
reason: status.present && !valid ? status.reason ?? "pattern vector index metadata does not match the current provider or model" : void 0
};
}
async rebuild(projectRoot, onProgress) {
const profile = readProjectProfile(projectRoot);
const intelligence = normalizeIntelligenceConfig(profile?.intelligence);
if (!intelligence.rag_enabled || !intelligence.embedding_provider) {
return;
}
const provider = await this.providerFactory(projectRoot, intelligence);
await provider.validate();
const patterns = await this.store.list();
const items = await this.embedPatterns(patterns, provider, onProgress);
await this.getIndex(projectRoot).replaceAll(homedir3(), items, {
provider: provider.name,
model: provider.model
});
}
async refresh(projectRoot, onProgress) {
const profile = readProjectProfile(projectRoot);
const intelligence = normalizeIntelligenceConfig(profile?.intelligence);
if (!intelligence.rag_enabled || !intelligence.embedding_provider) {
return;
}
const status = await this.getStatus(projectRoot);
if (!status.present || !status.valid) {
await this.rebuild(projectRoot, onProgress);
return;
}
const provider = await this.providerFactory(projectRoot, intelligence);
await provider.validate();
const index = this.getIndex(projectRoot);
const current = await index.load(homedir3());
if (!current) {
await this.rebuild(projectRoot, onProgress);
return;
}
const patterns = await this.store.list();
const nextById = new Map(
patterns.map((pattern) => [
pattern.id,
{
pattern,
fingerprint: fingerprint(pattern)
}
])
);
const changedPatterns = patterns.filter((pattern) => {
const currentItem = current.items.find((item) => item.id === pattern.id);
return !currentItem || currentItem.fingerprint !== fingerprint(pattern);
});
const removedIds = current.items.map((item) => item.id).filter((id) => !nextById.has(id));
if (changedPatterns.length === 0 && removedIds.length === 0) {
return;
}
onProgress?.(
`Refreshing pattern vectors (${changedPatterns.length} changed, ${removedIds.length} removed)`
);
const unchanged = current.items.filter(
(item) => !removedIds.includes(item.id) && !changedPatterns.some((pattern) => pattern.id === item.id)
);
const embedded = await this.embedPatterns(changedPatterns, provider, onProgress);
await index.replaceAll(homedir3(), [...unchanged, ...embedded], {
provider: provider.name,
model: provider.model
});
}
async createSemanticScorer(projectRoot, onProgress) {
const profile = readProjectProfile(projectRoot);
const intelligence = normalizeIntelligenceConfig(profile?.intelligence);
if (!intelligence.rag_enabled || !intelligence.embedding_provider) {
return void 0;
}
await this.refresh(projectRoot, onProgress);
const status = await this.getStatus(projectRoot);
if (!status.present || !status.valid) {
return void 0;
}
const provider = await this.providerFactory(projectRoot, intelligence);
const current = await this.getIndex(projectRoot).load(homedir3());
if (!current) {
return void 0;
}
const scoreCache = /* @__PURE__ */ new Map();
return async (pattern, keywords) => {
if (keywords.length === 0) {
return 0;
}
const query = keywords.join("\n");
let scores = scoreCache.get(query);
if (!scores) {
const [queryVector] = await provider.embed(query);
scores = new Map(
current.items.map((item) => [item.id, cosineSimilarity(queryVector, item.vector)])
);
scoreCache.set(query, scores);
}
return scores.get(pattern.id) ?? 0;
};
}
async createSuggester(projectRoot, relevanceThreshold = 0.3) {
const scorer = await this.createSemanticScorer(projectRoot);
return new PatternSuggester(this.store, relevanceThreshold, scorer);
}
async embedPatterns(patterns, provider, onProgress) {
if (patterns.length === 0) {
return [];
}
onProgress?.(`Embedding ${patterns.length} global patterns with ${provider.model}`);
const vectors = await provider.embed(patterns.map((pattern) => patternText(pattern)));
return patterns.map((pattern, index) => ({
id: pattern.id,
vector: vectors[index],
fingerprint: fingerprint(pattern),
category: pattern.category,
tags: pattern.tags,
problem_preview: pattern.problem.slice(0, 100)
}));
}
};
async function suggestPatternsForProject(projectRoot, keywords, domain, frameworks, limit = 3) {
const suggester = await new PatternVectorService().createSuggester(projectRoot);
return suggester.suggest(keywords, domain, frameworks, limit);
}
// src/session/resume-validator.ts
init_esm_shims();
// src/session/handoff-parser.ts
init_esm_shims();
import { readFile as readFile4 } from "fs/promises";
import { join as join7 } from "path";
var UnsupportedStructuredHandoffVersionError = class extends Error {
constructor(handoff) {
super(`Unsupported structured handoff version: ${handoff.version}`);
this.handoff = handoff;
this.name = "UnsupportedStructuredHandoffVersionError";
}
handoff;
};
function isStructuredHandoff(data) {
return typeof data === "object" && data !== null && data.version === 2;
}
var HandoffParser = class {
async parse(projectRoot) {
try {
const jsonPath = join7(projectRoot, ".paqad", "session", "handoff.json");
const raw = await readFile4(jsonPath, "utf8");
const data = JSON.parse(raw);
if (isStructuredHandoff(data)) {
return { version: 2, data };
}
throw new UnsupportedStructuredHandoffVersionError({
version: data.version,
data
});
} catch (error) {
if (error instanceof UnsupportedStructuredHandoffVersionError) {
throw error;
}
}
try {
const mdPath = join7(projectRoot, ".paqad", "session", "handoff.md");
const raw = await readFile4(mdPath, "utf8");
return { version: 1, data: raw };
} catch {
return null;
}
}
isStructured(handoff) {
return handoff.version === 2;
}
};
// src/session/resume-validator.ts
var SessionResumeValidator = class {
constructor(parser = new HandoffParser(), serviceFactory = async (projectRoot) => {
const { RagService: RagService2 } = await import("./service-TNCAKGBA.js");
return new RagService2(projectRoot);
}) {
this.parser = parser;
this.serviceFactory = serviceFactory;
}
parser;
serviceFactory;
async validate(projectRoot) {
const parsed = await this.parser.parse(projectRoot);
if (!parsed || !this.parser.isStructured(parsed) || !parsed.data.retrieval.rag_enabled) {
return {
rag_enabled: false,
embedding_provider: void 0,
index_valid: true,
rebuild_required: false
};
}
const status = await (await this.serviceFactory(projectRoot)).getStatus();
if (status.index_present && status.valid) {
return {
rag_enabled: true,
embedding_provider: parsed.data.retrieval.embedding_provider,
index_valid: true,
rebuild_required: false
};
}
return {
rag_enabled: true,
embedding_provider: parsed.data.retrieval.embedding_provider,
index_valid: false,
rebuild_required: true,
warning: `RAG index is unavailable or stale (${status.reason ?? "missing index"}). Run \`paqad-ai rag rebuild\` before resuming semantic retrieval.`
};
}
};
// src/rag/audit.ts
init_esm_shims();
import { appendFileSync, mkdirSync } from "fs";
import { dirname as dirname4, join as join8 } from "path";
// src/rag-ledger/audit-bridge.ts
init_esm_shims();
// src/rag-ledger/recorder.ts
init_esm_shims();
// src/rag-ledger/schema.ts
init_esm_shims();
var import_ajv = __toESM(require_ajv(), 1);
// src/rag-ledger/types.ts
init_esm_shims();
var RAG_EVIDENCE_DOC_TYPE = "paqad.rag-evidence";
var RAG_EVIDENCE_SCHEMA_VERSION = 1;
// src/rag-ledger/schema.ts
var nullableInt = { type: ["integer", "null"] };
var nullableString = { type: ["string", "null"] };
var RAG_EVIDENCE_SCHEMA = {
$id: "paqad://schemas/rag-evidence.json",
type: "object",
additionalProperties: false,
required: [
"schema_version",
"doc_type",
"kind",
"session_id",
"conversation_ordinal",
"ts",
"rag_enabled",
"adapter",
"content_hash"
],
properties: {
schema_version: { type: "integer", const: 1 },
doc_type: { const: RAG_EVIDENCE_DOC_TYPE },
kind: { enum: ["open", "refreshed", "called", "used", "fallback", "close"] },
session_id: { type: "string", minLength: 1 },
conversation_ordinal: { type: "integer", minimum: 1 },
ts: { type: "string", minLength: 1 },
rag_enabled: { type: "boolean" },
adapter: { type: "string", minLength: 1 },
refresh_kind: {
type: ["string", "null"],
enum: ["rebuild", "incremental-sync", "rule-context", "vision", "crs", "attachment", null]
},
changed_files: nullableInt,
chunks_embedded: nullableInt,
chunks_cached: nullableInt,
query_scope: { type: ["string", "null"], enum: ["docs", "code", "all", null] },
top_n: nullableInt,
candidates: nullableInt,
injected: { type: ["boolean", "null"] },
injected_sections: {
type: ["array", "null"],
items: { enum: ["rules", "memory", "retrieval", "drift"] }
},
slice_count: nullableInt,
pointer_count: nullableInt,
score_top: { type: ["number", "null"] },
bytes_injected: nullableInt,
fallback_reason: {
type: ["string", "null"],
enum: [
"rag-disabled",
"no-index",
"cold",
"below-floor",
"provider-mismatch",
"chunker-mismatch",
"error",
null
]
},
chunker_version: nullableString,
index_branch: nullableString,
latency_ms: nullableInt,
note: nullableString,
content_hash: { type: "string", minLength: 1 }
}
};
var ajv = new import_ajv.default({ allErrors: true, allowUnionTypes: true });
var compiled;
function validator() {
if (!compiled) {
compiled = ajv.compile(RAG_EVIDENCE_SCHEMA);
}
return compiled;
}
function validateRagEvidenceRow(row) {
const validate = validator();
if (validate(row)) {
return [];
}
return (validate.errors ?? []).map(
(error) => `${error.instancePath || "(root)"} ${error.message ?? "invalid"}`
);
}
// src/rag-ledger/recorder.ts
var APPEND_OPTS = (now) => ({
schemaVersion: RAG_EVIDENCE_SCHEMA_VERSION,
validate: (row) => validateRagEvidenceRow(row),
now
});
function openRagConversation(projectRoot, ctx) {
try {
const sessionId = resolveSessionId(projectRoot, ctx.sessionId);
const ordinal = openChatConversation(projectRoot, sessionId, ctx);
return { sessionId, ordinal };
} catch {
return null;
}
}
function openChatConversation(projectRoot, sessionId, ctx) {
const ordinal = allocateChatOrdinal(projectRoot, sessionId);
const openRow = stampSessionRow(
RAG_EVIDENCE_DOC_TYPE,
sessionId,
{
kind: "open",
conversation_ordinal: ordinal,
rag_enabled: ctx.ragEnabled,
adapter: ctx.adapter
},
APPEND_OPTS(ctx.now)
);
mirrorRagRow(projectRoot, sessionId, openRow);
return ordinal;
}
function recordRagEvidence(projectRoot, kind, fields, ctx) {
try {
const sessionId = resolveSessionId(projectRoot, ctx.sessionId);
const ordinal = resolveOrdinal(projectRoot, sessionId, ctx);
const note = typeof fields.note === "string" ? redactSecrets(fields.note, projectRoot) : fields.note ?? null;
const row = {
kind,
conversation_ordinal: ordinal,
rag_enabled: ctx.ragEnabled,
adapter: ctx.adapter,
...fields,
note
};
const stamped = stampSessionRow(RAG_EVIDENCE_DOC_TYPE, sessionId, row, APPEND_OPTS(ctx.now));
mirrorRagRow(projectRoot, sessionId, stamped);
return stamped;
} catch {
return null;
}
}
function resolveOrdinal(projectRoot, sessionId, ctx) {
if (ctx.ordinal && ctx.ordinal > 0) {
return ctx.ordinal;
}
const current = currentChatOrdinal(projectRoot, sessionId);
if (current > 0) {
return current;
}
return openChatConversation(projectRoot, sessionId, ctx);
}
// src/rag-ledger/audit-bridge.ts
function toInt(value) {
const n = Number(value);
return Number.isFinite(n) ? Math.trunc(n) : null;
}
function mapFallbackReason(reason) {
const text = String(reason ?? "").toLowerCase();
if (text.includes("disabled")) return "rag-disabled";
if (text.includes("provider") || text.includes("model")) return "provider-mismatch";
if (text.includes("chunker")) return "chunker-mismatch";
if (text.includes("below") || text.includes("floor")) return "below-floor";
if (text.includes("cold")) return "cold";
if (text.includes("missing-index") || text.includes("no-index") || text.includes("missing index"))
return "no-index";
return "error";
}
function mapAuditEventToEvidence(event, fields) {
switch (event) {
case "rag-build-completed":
return {
kind: "refreshed",
ragEnabled: true,
fields: { refresh_kind: "rebuild", chunks_embedded: toInt(fields.chunks) }
};
case "rag-build-failed":
case "rag-build-cancelled":
return {
kind: "fallback",
ragEnabled: true,
fields: { fallback_reason: "error", note: event }
};
case "rag-incremental-update":
return {
kind: "refreshed",
ragEnabled: true,
fields: {
refresh_kind: "incremental-sync",
changed_files: toInt(fields.changed_files),
chunks_embedded: toInt(fields.chunks)
}
};
case "rag-vision-ingested":
return { kind: "refreshed", ragEnabled: true, fields: { refresh_kind: "vision" } };
case "crs-reindexed":
return { kind: "refreshed", ragEnabled: true, fields: { refresh_kind: "crs" } };
case "rag-fallback": {
const reason = mapFallbackReason(fields.reason);
return {
kind: "fallback",
ragEnabled: reason !== "rag-disabled",
fields: { fallback_reason: reason }
};
}
case "rag-provider-mismatch":
return {
kind: "fallback",
ragEnabled: true,
fields: { fallback_reason: "provider-mismatch" }
};
case "rag-rerank-fallback":
return {
kind: "fallback",
ragEnabled: true,
fields: { fallback_reason: "error", note: "rerank" }
};
case "rag-resume-warning":
return {
kind: "fallback",
ragEnabled: true,
fields: { fallback_reason: "error", note: "resume-warning" }
};
case "rag-api-key-validation-failed":
return {
kind: "fallback",
ragEnabled: true,
fields: { fallback_reason: "error", note: "api-key" }
};
case "rag-enabled":
return { kind: "open", ragEnabled: true, fields: {} };
case "rag-cleared":
return { kind: "close", ragEnabled: false, fields: {} };
default:
if (event.startsWith("rag-attachment-index")) {
return { kind: "refreshed", ragEnabled: true, fields: { refresh_kind: "attachment" } };
}
return null;
}
}
function recordRagEvidenceFromAudit(projectRoot, event, fields) {
const mapped = mapAuditEventToEvidence(event, fields);
if (!mapped) {
return;
}
recordRagEvidence(projectRoot, mapped.kind, mapped.fields, {
ragEnabled: mapped.ragEnabled,
adapter: "engine"
});
}
// src/rag/audit.ts
function sanitize(value) {
return String(value).replace(/[\r\n"]/g, (match2) => {
if (match2 === '"') return "'";
return " ";
});
}
function appendRagAudit(projectRoot, level, event, fields = {}) {
const path2 = join8(projectRoot, PATHS.AUDIT_LOG);
mkdirSync(dirname4(path2), { recursive: true });
const ts = (/* @__PURE__ */ new Date()).toISOString();
const suffix = Object.entries(fields).map(([key, value]) => {
const redacted = redactSecrets(String(value), projectRoot);
return `${key}="${sanitize(redacted)}"`;
}).join(" ");
appendFileSync(path2, `[${ts}] ${level} ${event}${suffix ? ` ${suffix}` : ""}
`);
recordRagEvidenceFromAudit(projectRoot, event, fields);
}
// src/rag/crs-backlog.ts
init_esm_shims();
var DEFAULT_CRS_BACKLOG_CAP = 1e3;
var CrsBacklogQueue = class {
constructor(cap = DEFAULT_CRS_BACKLOG_CAP) {
this.cap = cap;
}
cap;
entries = [];
get size() {
return this.entries.length;
}
enqueue(chunks, collectionId) {
for (const chunk of chunks) {
this.entries.push({ collectionId, chunk });
}
let dropped = 0;
while (this.entries.length > this.cap) {
this.entries.shift();
dropped++;
}
if (dropped > 0) {
throw new EmbeddingBacklogOverflow(dropped);
}
}
async drain(persist) {
while (this.entries.length > 0) {
const collectionId = this.entries[0].collectionId;
const batch = [];
let count = 0;
for (const entry of this.entries) {
if (entry.collectionId !== collectionId) {
break;
}
batch.push(entry.chunk);
count++;
}
await persist(collectionId, batch);
this.entries.splice(0, count);
}
}
};
// src/rag/embedding-cache.ts
init_esm_shims();
import { createHash as createHash5 } from "crypto";
import { existsSync as existsSync4, readFileSync as readFileSync2 } from "fs";
import { join as join9 } from "path";
var EMBEDDING_CACHE_RELPATH = ".paqad/vectors/embedding-cache.json";
var CACHE_VERSION = 1;
function chunkHash(text) {
return createHash5("sha256").update(text).digest("hex");
}
var EmbeddingCache = class _EmbeddingCache {
constructor(path2, model) {
this.path = path2;
this.model = model;
}
path;
model;
entries = /* @__PURE__ */ new Map();
dirty = false;
static load(projectRoot, model) {
const path2 = join9(projectRoot, EMBEDDING_CACHE_RELPATH);
const cache2 = new _EmbeddingCache(path2, model);
if (!existsSync4(path2)) return cache2;
try {
const parsed = JSON.parse(readFileSync2(path2, "utf8"));
if (parsed.version === CACHE_VERSION && parsed.model === model && parsed.entries) {
for (const [hash, vector] of Object.entries(parsed.entries)) {
cache2.entries.set(hash, vector);
}
}
} catch {
}
return cache2;
}
get(text) {
return this.entries.get(chunkHash(text));
}
has(text) {
return this.entries.has(chunkHash(text));
}
set(text, vector) {
this.entries.set(chunkHash(text), vector);
this.dirty = true;
}
get size() {
return this.entries.size;
}
async flush() {
if (!this.dirty) return;
const file = {
version: CACHE_VERSION,
model: this.model,
entries: Object.fromEntries(this.entries)
};
await atomicWriteFile(this.path, JSON.stringify(file));
this.dirty = false;
}
};
// src/rag/rerank-order.ts
init_esm_shims();
function reorderByRankedIds(items, idOf, rankedIds) {
const byId = new Map(items.map((item) => [idOf(item), item]));
const seen = /* @__PURE__ */ new Set();
const ordered = [];
for (const id of rankedIds) {
const item = byId.get(id);
if (item && !seen.has(id)) {
ordered.push(item);
seen.add(id);
}
}
for (const item of items) {
if (!seen.has(idOf(item))) {
ordered.push(item);
}
}
return ordered;
}
// src/rag/rrf-fusion.ts
init_esm_shims();
var RRF_K = 60;
function reciprocalRankFusion(rankings, k = RRF_K) {
const scores = /* @__PURE__ */ new Map();
const firstSeen = /* @__PURE__ */ new Map();
let order = 0;
for (const ranking of rankings) {
for (let rank = 0; rank < ranking.length; rank++) {
const id = ranking[rank];
scores.set(id, (scores.get(id) ?? 0) + 1 / (k + rank + 1));
if (!firstSeen.has(id)) {
firstSeen.set(id, order++);
}
}
}
return [...scores.entries()].map(([id, score]) => ({ id, score })).sort((left, right) => {
if (right.score !== left.score) {
return right.score - left.score;
}
return (firstSeen.get(left.id) ?? 0) - (firstSeen.get(right.id) ?? 0);
});
}
// src/rag/file-filter.ts
init_esm_shims();
import { existsSync as existsSync5, readFileSync as readFileSync3 } from "fs";
import { lstat, open } from "fs/promises";
import { homedir as homedir4 } from "os";
import { isAbsolute, join as join10, relative } from "path";
var import_ajv2 = __toESM(require_ajv(), 1);
var import_fast_glob = __toESM(require_out(), 1);
// node_modules/.pnpm/minimatch@11.0.5/node_modules/minimatch/dist/esm/index.js
init_esm_shims();
// node_modules/.pnpm/brace-expansion@5.0.12/node_modules/brace-expansion/dist/esm/index.js
init_esm_shims();
// node_modules/.pnpm/balanced-match@4.0.4/node_modules/balanced-match/dist/esm/index.js
init_esm_shims();
var balanced = (a, b, str) => {
const ma = a instanceof RegExp ? maybeMatch(a, str) : a;
const mb = b instanceof RegExp ? maybeMatch(b, str) : b;
const r = ma !== null && mb != null && range(ma, mb, str);
return r && {
start: r[0],
end: r[1],
pre: str.slice(0, r[0]),
body: str.slice(r[0] + ma.length, r[1]),
post: str.slice(r[1] + mb.length)
};
};
var maybeMatch = (reg, str) => {
const m = str.match(reg);
return m ? m[0] : null;
};
var range = (a, b, str) => {
let begs, beg, left, right = void 0, result;
let ai = str.indexOf(a);
let bi = str.indexOf(b, ai + 1);
let i = ai;
if (ai >= 0 && bi > 0) {
if (a === b) {
return [ai, bi];
}
begs = [];
left = str.length;
while (i >= 0 && !result) {
if (i === ai) {
begs.push(i);
ai = str.indexOf(a, i + 1);
} else if (begs.length === 1) {
const r = begs.pop();
if (r !== void 0)
result = [r, bi];
} else {
beg = begs.pop();
if (beg !== void 0 && beg < left) {
left = beg;
right = bi;
}
bi = str.indexOf(b, i + 1);
}
i = ai < bi && ai >= 0 ? ai : bi;
}
if (begs.length && right !== void 0) {
result = [left, right];
}
}
return result;
};
// node_modules/.pnpm/brace-expansion@5.0.12/node_modules/brace-expansion/dist/esm/index.js
var escSlash = "\0SLASH" + Math.random() + "\0";
var escOpen = "\0OPEN" + Math.random() + "\0";
var escClose = "\0CLOSE" + Math.random() + "\0";
var escComma = "\0COMMA" + Math.random() + "\0";
var escPeriod = "\0PERIOD" + Math.random() + "\0";
var escSlashPattern = new RegExp(escSlash, "g");
var escOpenPattern = new RegExp(escOpen, "g");
var escClosePattern = new RegExp(escClose, "g");
var escCommaPattern = new RegExp(escComma, "g");
var escPeriodPattern = new RegExp(escPeriod, "g");
var slashPattern = /\\\\/g;
var openPattern = /\\{/g;
var closePattern = /\\}/g;
var commaPattern = /\\,/g;
var periodPattern = /\\\./g;
var EXPANSION_MAX = 1e5;
var EXPANSION_MAX_LENGTH = 4e6;
var EXPANSION_MAX_DEPTH = 1e3;
var EXPANSION_MAX_REWRITES = 1e3;
function numeric(str) {
return !isNaN(str) ? parseInt(str, 10) : str.charCodeAt(0);
}
function escapeBraces(str) {
return str.replace(slashPattern, escSlash).replace(openPattern, escOpen).replace(closePattern, escClose).replace(commaPattern, escComma).replace(periodPattern, escPeriod);
}
function unescapeBraces(str) {
return str.replace(escSlashPattern, "\\").replace(escOpenPattern, "{").replace(escClosePattern, "}").replace(escCommaPattern, ",").replace(escPeriodPattern, ".");
}
function pushAll(target, items) {
for (let i = 0; i < items.length; i++) {
target.push(items[i]);
}
}
function parseCommaParts(str) {
const parts = [];
let carry = "";
for (; ; ) {
const m = balanced("{", "}", str);
if (!m) {
const tail = str.split(",");
tail[0] = carry + tail[0];
pushAll(parts, tail);
return parts;
}
const { pre, body, post } = m;
const p = pre.split(",");
p[0] = carry + p[0];
p[p.length - 1] += "{" + body + "}";
if (!post.length) {
pushAll(parts, p);
return parts;
}
carry = p.pop();
pushAll(parts, p);
str = post;
}
}
function expand(str, options = {}) {
if (!str) {
return [];
}
const { max = EXPANSION_MAX, maxLength = EXPANSION_MAX_LENGTH, maxDepth = EXPANSION_MAX_DEPTH, maxRewrites = EXPANSION_MAX_REWRITES } = options;
if (str.slice(0, 2) === "{}") {
str = "\\{\\}" + str.slice(2);
}
return expand_(escapeBraces(str), max, maxLength, maxDepth, 0, maxRewrites, true).map(unescapeBraces);
}
function embrace(str) {
return "{" + str + "}";
}
function isPadded(el) {
return /^-?0\d/.test(el);
}
function lte(i, y) {
return i <= y;
}
function gte(i, y) {
return i >= y;
}
function combine(acc, pre, values, max, maxLength, dropEmpties) {
const out = [];
let length = 0;
for (let a = 0; a < acc.length; a++) {
for (let v = 0; v < values.length; v++) {
if (out.length >= max)
return out;
const expansion = acc[a] + pre + values[v];
if (dropEmpties && !expansion)
continue;
if (length + expansion.length > maxLength)
return out;
out.push(expansion);
length += expansion.length;
}
}
return out;
}
function expandSequence(body, isAlphaSequence, max, maxLength) {
const n = body.split(/\.\./);
const N = [];
if (n[0] === void 0 || n[1] === void 0) {
return N;
}
const x = numeric(n[0]);
const y = numeric(n[1]);
const width = Math.max(n[0].length, n[1].length);
let incr = n.length === 3 && n[2] !== void 0 ? Math.max(Math.abs(numeric(n[2])), 1) : 1;
let test = lte;
const reverse = y < x;
if (reverse) {
incr *= -1;
test = gte;
}
const pad = n.some(isPadded);
let length = 0;
for (let i = x; test(i, y) && N.length < max; i += incr) {
let c;
if (isAlphaSequence) {
c = String.fromCharCode(i);
if (c === "\\") {
c = "";
}
} else {
c = String(i);
if (pad) {
const need = width - c.length;
if (need > 0) {
const z = new Array(need + 1).join("0");
if (i < 0) {
c = "-" + z + c.slice(1);
} else {
c = z + c;
}
}
}
}
if (length + c.length > maxLength)
break;
N.push(c);
length += c.length;
}
return N;
}
function expand_(str, max, maxLength, maxDepth, depth, maxRewrites, isTop) {
if (depth > maxDepth) {
return [str];
}
let acc = [""];
let rewrites = 0;
let dropEmpties = false;
let firstGroup = true;
for (; ; ) {
const m = balanced("{", "}", str);
if (!m) {
return combine(acc, str, [""], max, maxLength, dropEmpties);
}
const pre = m.pre;
if (/\$$/.test(pre)) {
acc = combine(acc, pre + "{" + m.body + "}", [""], max, maxLength, dropEmpties && !m.post.length);
firstGroup = false;
if (!m.post.length)
break;
str = m.post;
continue;
}
const isNumericSequence = /^-?\d+\.\.-?\d+(?:\.\.-?\d+)?$/.test(m.body);
const isAlphaSequence = /^[a-zA-Z]\.\.[a-zA-Z](?:\.\.-?\d+)?$/.test(m.body);
const isSequence = isNumericSequence || isAlphaSequence;
const isOptions = m.body.indexOf(",") >= 0;
if (!isSequence && !isOptions) {
if (rewrites < maxRewrites && m.post.match(/,(?!,).*\}/)) {
rewrites++;
str = m.pre + "{" + m.body + escClose + m.post;
isTop = true;
continue;
}
return combine(acc, pre + "{" + m.body + "}" + m.post, [""], max, maxLength, dropEmpties);
}
if (firstGroup) {
dropEmpties = isTop && !isSequence;
firstGroup = false;
}
let values;
if (isSequence) {
values = expandSequence(m.body, isAlphaSequence, max, maxLength);
} else {
let n = parseCommaParts(m.body);
if (n.length === 1 && n[0] !== void 0) {
n = expand_(n[0], max, maxLength, maxDepth, depth + 1, maxRewrites, false).map(embrace);
if (n.length === 1) {
acc = combine(acc, pre + n[0], [""], max, maxLength, dropEmpties && !m.post.length);
if (!m.post.length)
break;
str = m.post;
continue;
}
}
let dropsEmpties = dropEmpties && !m.post.length && !pre;
for (let d = 0; dropsEmpties && d < acc.length; d++) {
if (acc[d]) {
dropsEmpties = false;
}
}
values = [];
let valuesLength = 0;
outer: for (let j = 0; j < n.length; j++) {
const expanded = expand_(n[j], max, maxLength, maxDepth, depth + 1, maxRewrites, false);
for (let k = 0; k < expanded.length; k++) {
const v = expanded[k];
if (dropsEmpties && !v)
continue;
if (values.length >= max || valuesLength + v.length > maxLength) {
break outer;
}
values.push(v);
valuesLength += v.length;
}
}
}
acc = combine(acc, pre, values, max, maxLength, dropEmpties && !m.post.length);
if (!m.post.length)
break;
str = m.post;
}
return acc;
}
// node_modules/.pnpm/@isaacs+nonrecursive-glob-match@1.1.0/node_modules/@isaacs/nonrecursive-glob-match/dist/esm/index.js
init_esm_shims();
// node_modules/.pnpm/@isaacs+nonrecursive-glob-match@1.1.0/node_modules/@isaacs/nonrecursive-glob-match/dist/esm/numeric-range.js
init_esm_shims();
var digit = (a, b) => a === b ? { t: "lit", ch: String(a) } : { t: "class", re: new RegExp(`^[${a}-${b}]$`) };
var prefix = (d, alts) => alts.map((s) => [digit(d, d), ...s]);
var fixed = (a, b) => {
if (!a)
return [[]];
let lo = +a[0];
let hi = +b[0];
const ra = a.slice(1);
const rb = b.slice(1);
if (lo === hi)
return prefix(lo, fixed(ra, rb));
const zeros = "0".repeat(ra.length);
const nines = "9".repeat(ra.length);
const out = [];
if (ra !== zeros)
out.push(...prefix(lo++, fixed(ra, nines)));
const last = rb === nines ? [] : prefix(hi--, fixed(zeros, rb));
if (lo <= hi) {
out.push([digit(lo, hi), ...Array.from(ra, () => digit(0, 9))]);
}
out.push(...last);
return out;
};
var nonneg = (a, b, width) => {
if (width) {
return fixed(String(a).padStart(width, "0"), String(b).padStart(width, "0"));
}
const out = [];
for (let len = String(a).length; len <= String(b).length; len++) {
const lo = Math.max(a, len === 1 ? 0 : 10 ** (len - 1));
const hi = Math.min(b, 10 ** len - 1);
out.push(...fixed(String(lo), String(hi)));
}
return out;
};
var numericRange = (loStr, hiStr) => {
let lo = Number(loStr);
let hi = Number(hiStr);
if (!Number.isSafeInteger(lo) || !Number.isSafeInteger(hi))
return;
if (lo > hi)
[lo, hi] = [hi, lo];
const padded = /^-?0\d/.test(loStr) || /^-?0\d/.test(hiStr);
const width = padded ? Math.max(loStr.length, hiStr.length) : 0;
const out = [];
if (lo < 0) {
const neg = nonneg(Math.max(-hi, 1), -lo, width && width - 1);
out.push(...neg.map((s) => [{ t: "lit", ch: "-" }, ...s]));
}
if (hi >= 0)
out.push(...nonneg(Math.max(lo, 0), hi, width));
return out;
};
// node_modules/.pnpm/@isaacs+nonrecursive-glob-match@1.1.0/node_modules/@isaacs/nonrecursive-glob-match/dist/esm/parse-class.js
init_esm_shims();
var posixClasses = {
"[:alnum:]": ["\\p{L}\\p{Nl}\\p{Nd}"],
"[:alpha:]": ["\\p{L}\\p{Nl}"],
"[:ascii:]": ["\\x00-\\x7f"],
"[:blank:]": ["\\p{Zs}\\t"],
"[:cntrl:]": ["\\p{Cc}"],
"[:digit:]": ["\\p{Nd}"],
"[:graph:]": ["\\p{Z}\\p{C}", true],
"[:lower:]": ["\\p{Ll}"],
"[:print:]": ["\\p{C}", true],
"[:punct:]": ["\\p{P}\\p{S}"],
"[:space:]": ["\\p{Z}\\t\\r\\n\\v\\f"],
"[:upper:]": ["\\p{Lu}"],
"[:word:]": ["\\p{L}\\p{Nl}\\p{Nd}\\p{Pc}"],
"[:xdigit:]": ["A-Fa-f0-9"]
};
var braceEscape = (s) => s.replace(/[[\]\\-]/g, "\\$&");
var noMatch = /$./;
var parseClass = (glob, position, nocase = false, form) => {
const norm = (c) => {
const n = form ? c.normalize(form) : c;
return [...n].length === 1 ? n : c;
};
const pos = position;
if (glob.charAt(pos) !== "[") {
throw new Error("not in a brace expression");
}
const ranges = [];
const negs = [];
let i = pos + 1;
let sawStart = false;
let escaping = false;
let negate = false;
let endPos = pos;
let rangeStart = "";
WHILE: while (i < glob.length) {
const c = String.fromCodePoint(glob.codePointAt(i));
if ((c === "!" || c === "^") && i === pos + 1) {
negate = true;
i++;
continue;
}
if (c === "]" && sawStart && !escaping) {
endPos = i + 1;
break;
}
sawStart = true;
if (c === "\\") {
if (!escaping) {
escaping = true;
i++;
continue;
}
}
if (c === "[" && !escaping) {
for (const [cls, [unip, neg]] of Object.entries(posixClasses)) {
if (glob.startsWith(cls, i)) {
if (rangeStart) {
return [noMatch, glob.length - pos];
}
i += cls.length;
if (neg)
negs.push(unip);
else
ranges.push(unip);
continue WHILE;
}
}
}
escaping = false;
const n = norm(c);
if (rangeStart) {
if (n.codePointAt(0) > rangeStart.codePointAt(0)) {
ranges.push(braceEscape(rangeStart) + "-" + braceEscape(n));
} else if (n === rangeStart) {
ranges.push(braceEscape(n));
}
rangeStart = "";
i += c.length;
continue;
}
if (glob.startsWith("-]", i + c.length)) {
ranges.push(braceEscape(n + "-"));
i += c.length + 1;
continue;
}
if (glob.startsWith("-", i + c.length)) {
rangeStart = n;
i += c.length + 1;
continue;
}
ranges.push(braceEscape(n));
i += c.length;
}
if (endPos < i) {
return [noMatch, 0];
}
if (!ranges.length && !negs.length) {
return [noMatch, glob.length - pos];
}
const sranges = "[" + (negate ? "^" : "") + ranges.join("") + "]";
const snegs = "[" + (negate ? "" : "^") + negs.join("") + "]";
const comb = "^" + (ranges.length && negs.length ? "(" + sranges + "|" + snegs + ")" : ranges.length ? sranges : snegs);
return [new RegExp(comb, nocase ? "iu" : "u"), endPos - pos];
};
// node_modules/.pnpm/@isaacs+nonrecursive-glob-match@1.1.0/node_modules/@isaacs/nonrecursive-glob-match/dist/esm/index.js
var MATCH = { t: "match", id: 0 };
var extTypes = "!?+*@";
var lit = (ch) => ({ t: "lit", ch });
var reEscape = (s) => s.replace(/[\\^$.*+?()[\]{}|/]/g, "\\$&");
var newFoldRe = (s) => new RegExp(`^${reEscape(s)}$`, "iu");
var foldCache = /* @__PURE__ */ new Map();
var foldRe = (ch) => {
let re = foldCache.get(ch);
if (!re) {
re = newFoldRe(ch);
if (foldCache.size >= 1e3)
foldCache.clear();
foldCache.set(ch, re);
}
return re;
};
var litMatch = (l, c) => {
if (c === l.ch)
return true;
if (!l.re)
return false;
const code = c.charCodeAt(0);
if (code >= 128)
return l.re.test(c);
const ascii = l.ascii ??= new Uint8Array(128);
ascii[code] ||= l.re.test(c) ? 1 : 2;
return ascii[code] === 1;
};
var tokenize = (p, nocase, noext, form, mark) => {
const toks = [];
for (let i = 0; i < p.length; ) {
const c = String.fromCodePoint(p.codePointAt(i));
if (mark) {
const m = c === "\\" && p.startsWith(mark, i + 1) ? 1 : 0;
if (m || c === mark) {
const start = i + m + mark.length;
const end = p.indexOf(mark, start);
const r = /^(-?\d+)\.\.(-?\d+)$/.exec(p.slice(start, end));
const alts = end !== -1 && r && numericRange(r[1], r[2]);
if (alts) {
toks.push({ t: "ext", type: "@", alts });
i = end + mark.length;
continue;
}
}
}
if (c === "\\") {
const e = p.codePointAt(i + 1);
const ch = e === void 0 ? c : String.fromCodePoint(e);
toks.push(lit(ch));
i += 1 + ch.length;
continue;
}
if (c === "[") {
const [re, consumed] = parseClass(p, i, nocase, form);
if (consumed) {
toks.push({ t: "class", re });
i += consumed;
continue;
}
}
if (!noext && extTypes.includes(c) && p[i + 1] === "(") {
toks.push({ t: "open", type: c });
i += 2;
continue;
}
toks.push(!noext && c === "|" ? { t: "bar" } : !noext && c === ")" ? { t: "close" } : c === "*" ? { t: "star" } : c === "?" ? { t: "any" } : lit(c));
i += c.length;
}
return toks;
};
var pair = (toks) => {
const pairs = /* @__PURE__ */ new Map();
const stack = [];
toks.forEach((tok, i) => {
if (tok.t === "open")
stack.push(i);
else if (tok.t === "close" && stack.length)
pairs.set(stack.pop(), i);
});
return pairs;
};
var build = (toks, pairs, start, end, top) => {
let seq = [];
const alts = [seq];
for (let i = start; i < end; i++) {
const tok = toks[i];
const close = pairs.get(i);
if (tok.t === "open" && close !== void 0) {
const sub = build(toks, pairs, i + 1, close, false);
seq.push({ t: "ext", type: tok.type, alts: sub });
i = close;
} else if (tok.t === "bar" && !top) {
alts.push(seq = []);
} else if (tok.t === "open") {
seq.push(lit(tok.type), lit("("));
} else if (tok.t === "bar") {
seq.push(lit("|"));
} else if (tok.t === "close") {
seq.push(lit(")"));
} else {
seq.push(tok);
}
}
return alts;
};
var normalize = (items, form) => {
const out = [];
let run = "";
const flush = () => {
for (const ch of run.normalize(form))
out.push(lit(ch));
run = "";
};
for (const it of items) {
if (it.t === "lit") {
run += it.ch;
continue;
}
flush();
out.push(it.t === "ext" ? { ...it, alts: it.alts.map((a) => normalize(a, form)) } : it);
}
flush();
return out;
};
var compile = (items, nocase) => {
let id = 1;
const split = (out) => ({ t: "split", id: id++, out });
const seq = (items2, next) => items2.reduceRight((next2, it) => item(it, next2), next);
const item = (it, next) => {
switch (it.t) {
case "lit":
const re = nocase ? foldRe(it.ch) : void 0;
return { t: "lit", id: id++, ch: it.ch, re, next };
case "any":
case "star":
return { t: it.t, id: id++, next };
case "class":
return { t: "class", id: id++, re: it.re, next };
}
const alts = (next2) => it.alts.map((a) => seq(a, next2));
switch (it.type) {
case "@":
return split(alts(next));
case "?":
return split([next, ...alts(next)]);
case "!":
return { t: "neg", id: id++, sub: split(alts(MATCH)), next };
}
const after = split([]);
const entry = split(alts(after));
after.out.push(next, entry);
return it.type === "*" ? after : entry;
};
const start = seq(items, MATCH);
return [start, id];
};
var show = (start) => {
const lines = [];
const seen = /* @__PURE__ */ new Set();
const ref = (n) => n.t === "match" ? "match" : String(n.id);
const walk = (n, indent) => {
if (n.t === "match" || seen.has(n))
return;
seen.add(n);
const line = (desc) => lines.push(`${indent}${n.id}: ${desc}`);
switch (n.t) {
case "split":
line(`split -> ${n.out.map(ref).join(", ")}`);
for (const o of n.out)
walk(o, indent);
return;
case "neg":
line(`!(${ref(n.sub)}) -> ${ref(n.next)}`);
walk(n.sub, indent + " ");
return walk(n.next, indent);
case "lit":
line(`${JSON.stringify(n.ch)} -> ${ref(n.next)}`);
break;
case "class":
line(`${n.re} -> ${ref(n.next)}`);
break;
default:
line(`${n.t === "any" ? "?" : "*"} -> ${ref(n.next)}`);
}
walk(n.next, indent);
};
walk(start, "");
return [`start -> ${ref(start)}`, ...lines].join("\n");
};
var State = class {
// set by the Sim, to mark the nodes that this state has
gen;
threads = [];
match = false;
runs = [];
// the sorted ids of the threads, with 0 for a match, once the
// state is done
ids = [];
constructor(gen) {
this.gen = gen;
}
// no threads, so nothing more can match
get dead() {
return !this.threads.length && !this.match && !this.runs.length;
}
};
var sameState = (a, b) => {
if (a.ids.length !== b.ids.length)
return false;
if (a.runs.length !== b.runs.length)
return false;
if (a.ids.some((id, i) => id !== b.ids[i]))
return false;
return a.runs.every((r) => b.runs.some((o) => o.neg === r.neg && sameState(o.state, r.state)));
};
var marks = (size) => ({
has: new Float64Array(size),
seen: new Float64Array(size),
gen: 0
});
var Sim = class {
// `.` and `..` only match literal dots, even with `dot:true`
dots;
// the string starts with `.`, and `dot:true` is not set
dotStart;
#marks;
constructor(dots, dotStart, marks2) {
this.dots = dots;
this.dotStart = dotStart;
this.#marks = marks2;
}
state() {
return new State(++this.#marks.gen);
}
// sort the ids of a state, once it has all of its threads
done(state) {
state.ids = state.threads.map((n) => n.id).sort((a, b) => a - b);
if (state.match)
state.ids.unshift(MATCH.id);
return state;
}
// true the first time that the state walks through the node, so
// that loops in the graph stop
#visit(state, node) {
const { seen } = this.#marks;
if (seen[node.id] === state.gen)
return false;
seen[node.id] = state.gen;
return true;
}
#addThread(state, node) {
const { has } = this.#marks;
if (has[node.id] === state.gen)
return false;
has[node.id] = state.gen;
state.threads.push(node);
return true;
}
// wildcards only match a leading dot if `dot:true`. In a negated
// sub-pattern, this does not apply, because the negation itself
// can not match a leading dot.
restricted(i, top) {
return top && (this.dots || this.dotStart && i === 0);
}
add(state, node, i, top) {
switch (node.t) {
case "split":
if (!this.#visit(state, node))
return;
for (const o of node.out)
this.add(state, o, i, top);
return;
case "neg":
if (!this.#visit(state, node))
return;
const sub = this.state();
this.add(sub, node.sub, i, false);
this.done(sub);
return this.addRun(state, { t: "run", neg: node, state: sub }, i, top);
case "star":
if (this.restricted(i, top))
return;
if (this.#addThread(state, node))
this.add(state, node.next, i, top);
return;
case "match":
state.match = true;
return;
default:
this.#addThread(state, node);
}
}
addRun(state, run, i, top) {
for (const r of state.runs) {
if (r.neg === run.neg && sameState(r.state, run.state))
return;
}
state.runs.push(run);
if (!run.state.match) {
this.add(state, run.neg.next, i, top);
}
}
// the threads after char `c`, at index `i`
step(state, c, i, top) {
const next = this.state();
for (const th of state.threads) {
switch (th.t) {
case "lit":
if (litMatch(th, c)) {
this.add(next, th.next, i + 1, top);
}
break;
case "any":
if (!this.restricted(i, top))
this.add(next, th.next, i + 1, top);
break;
case "class":
if (!this.restricted(i, top) && th.re.test(c)) {
this.add(next, th.next, i + 1, top);
}
break;
case "star":
if (!this.restricted(i, top))
this.add(next, th, i + 1, top);
}
}
if (!this.restricted(i, top)) {
for (const run of state.runs) {
if (run.state.dead) {
this.addRun(next, run, i + 1, top);
continue;
}
const sub = this.step(run.state, c, i, false);
this.addRun(next, { t: "run", neg: run.neg, state: sub }, i + 1, top);
}
}
return this.done(next);
}
};
var stateKey = (state) => {
const ids = state.ids.join();
if (!state.runs.length)
return ids;
const runs = state.runs.map((r) => `${r.neg.id}(${stateKey(r.state)})`);
return `${ids}|${runs.sort().join("|")}`;
};
var DFA_BUDGET = 1 << 18;
var Dfa = class {
#start;
#states = /* @__PURE__ */ new Map();
#size = 0;
#plain;
#dotStart;
#sim;
#dotSim;
constructor(start, marks2) {
this.#start = start;
this.#sim = new Sim(false, false, marks2);
this.#dotSim = new Sim(false, true, marks2);
}
#make(state) {
return {
state,
match: state.match,
dead: state.dead,
next: /* @__PURE__ */ new Map()
};
}
#intern(state) {
const key = stateKey(state);
const found = this.#states.get(key);
if (found)
return found;
if (this.#size > DFA_BUDGET)
this.#clear();
const d = this.#make(state);
this.#states.set(key, d);
this.#size += key.length + 1;
return d;
}
#clear() {
this.#states.clear();
this.#size = 0;
this.#plain = void 0;
this.#dotStart = void 0;
}
start(dotStart) {
if (dotStart) {
if (this.#dotStart)
return this.#dotStart;
const state2 = this.#dotSim.state();
this.#dotSim.add(state2, this.#start, 0, true);
return this.#dotStart = this.#make(this.#dotSim.done(state2));
}
if (this.#plain)
return this.#plain;
const state = this.#sim.state();
this.#sim.add(state, this.#start, 0, true);
return this.#plain = this.#intern(this.#sim.done(state));
}
// the next state, after a miss in `d.ascii`
next(d, c) {
const code = c.charCodeAt(0);
const found = code < 128 ? void 0 : d.next.get(c);
if (found)
return found;
const n = d === this.#dotStart ? this.#intern(this.#dotSim.step(d.state, c, 0, true)) : this.#intern(this.#sim.step(d.state, c, 1, true));
if (code < 128) {
if (!d.ascii) {
d.ascii = new Array(128);
this.#size += 128;
}
d.ascii[code] = n;
} else {
d.next.set(c, n);
}
this.#size++;
return n;
}
};
var linear = (start) => {
const nodes = [];
for (let n = start; n.t !== "match"; n = n.next) {
if (n.t === "split" || n.t === "neg")
return void 0;
nodes.push(n);
}
return nodes;
};
var testLinear = (nodes, chars, start, end) => {
let ni = 0;
let ci = start;
let starNi = -1;
let starCi = 0;
while (ci < end) {
const n = nodes[ni];
if (n?.t === "star") {
starNi = ni++;
starCi = ci;
continue;
}
const c = chars[ci];
if (n && (n.t === "any" || n.t === "lit" && litMatch(n, c) || n.t === "class" && n.re.test(c))) {
ni++;
ci++;
continue;
}
if (starNi === -1)
return false;
ni = starNi + 1;
ci = ++starCi;
}
while (nodes[ni]?.t === "star")
ni++;
return ni === nodes.length;
};
var lengths = (items) => {
let min = 0;
let max = 0;
for (const it of items) {
const [lo, hi] = itemLengths(it);
min += lo;
max += hi;
}
return [min, max];
};
var itemLengths = (it) => {
if (it.t === "star")
return [0, Infinity];
if (it.t !== "ext")
return [1, 1];
if (it.type === "!")
return [0, Infinity];
const alts = it.alts.map(lengths);
const min = Math.min(...alts.map((a) => a[0]));
const max = Math.max(...alts.map((a) => a[1]));
const loop = max === 0 ? 0 : Infinity;
return it.type === "@" ? [min, max] : it.type === "?" ? [0, max] : it.type === "*" ? [0, loop] : [min, loop];
};
var hasAffix = (lits, chars, start) => {
for (let j = 0; j < lits.length; j++) {
if (!litMatch(lits[j], chars[start + j]))
return false;
}
return true;
};
var slashOrSurrogate = /[/\ud800-\udfff]/;
var surrogate = /[\ud800-\udfff]/;
var codePoints = (s) => surrogate.test(s) ? [...s] : s;
var noSlash = (s) => {
if (s.includes("/")) {
throw new Error("can only match a single path portion, got: " + s);
}
};
var Matcher = class {
pattern;
options;
#start;
// the number of node ids
#size;
#marks;
// the Sim for strings that are only `.` or `..`
#dotsSim;
// the string to compare, if the pattern has no magic. With
// `nocase:true`, the RegExp that folds case is made when it is
// first used.
#literal;
#literalFold = false;
#literalRe;
// the nodes between the prefix and suffix, if the graph is a
// single chain
#linear;
// the linear nodes are only `*` and `?`, so the length bounds and
// the prefix and suffix decide the match
#wild = false;
// the least and most chars that a match can have
#min = 0;
#max = Infinity;
// the literals that a match must start and end with. Each one
// matches exactly one char, and they are distinct items in the
// pattern, so a string that does not have them can not match.
#prefix = [];
#suffix = [];
// the prefix and suffix as strings, if they do not fold case
#prefixStr;
#suffixStr;
// there is a prefix or suffix to check
#affix = false;
#dfa;
#form;
constructor(pattern, options = {}) {
if (pattern.includes("/")) {
throw new Error("can only match a single path portion, got pattern: " + pattern);
}
this.pattern = pattern;
this.options = options;
const { nocase = false, noext = false, normalize: n } = options;
this.#form = n === true ? "NFC" : n || void 0;
const literal2 = /^[?*+@]\(\)$/.test(pattern);
const toks = tokenize(pattern, nocase, noext || literal2, this.#form, options.rangeMark);
let [items] = build(toks, pair(toks), 0, toks.length, true);
if (this.#form)
items = normalize(items, this.#form);
const [start, size] = compile(items, nocase);
this.#start = start;
this.#size = size;
if (items.every((it) => it.t === "lit")) {
this.#literal = items.map((it) => it.ch).join("");
this.#literalFold = nocase;
} else {
const lit2 = (it) => it.t !== "lit" ? void 0 : nocase ? { ch: it.ch, re: foldRe(it.ch) } : { ch: it.ch };
for (const it of items) {
const l = lit2(it);
if (!l)
break;
this.#prefix.push(l);
}
for (let j = items.length - 1; j >= 0; j--) {
const l = lit2(items[j]);
if (!l)
break;
this.#suffix.unshift(l);
}
this.#affix = !!(this.#prefix.length || this.#suffix.length);
if (!nocase) {
this.#prefixStr = this.#prefix.map((l) => l.ch).join("");
this.#suffixStr = this.#suffix.map((l) => l.ch).join("");
}
this.#linear = linear(this.#start)?.slice(this.#prefix.length, -this.#suffix.length || void 0);
this.#wild = !!this.#linear?.every((n2) => n2.t === "star" || n2.t === "any");
const [min, max] = lengths(items);
this.#min = min;
this.#max = max;
}
}
test(s) {
if (this.#form) {
noSlash(s);
return this.#test(s.normalize(this.#form));
}
if (slashOrSurrogate.test(s)) {
noSlash(s);
return this.#test(s, [...s]);
}
return this.#test(s, s);
}
/**
* The same as {@link Matcher#test}, but the string is not checked
* for `/` chars, so the result for a string that has one is not
* defined. Only use it for a string that is known to be one path
* portion, such as a part of a path that was split on `/`. It can
* be faster, because some patterns do not need to look at each char
* of the string.
*/
testUnchecked(s) {
return this.#test(this.#form ? s.normalize(this.#form) : s);
}
// Test a string that has no `/`. `chars` indexes it by code point.
// If it is not given, it is only found if a step needs it.
#test(s, chars) {
if (this.#literal !== void 0) {
return this.#literalFold ? (this.#literalRe ??= newFoldRe(this.#literal)).test(s) : s === this.#literal;
}
const min = this.#min;
const max = this.#max;
if (chars === void 0) {
const most = s.length;
const least = most + 1 >> 1;
if (most < min || least > max)
return false;
if (least < min || most > max)
chars = codePoints(s);
}
if (chars !== void 0 && (chars.length < min || chars.length > max)) {
return false;
}
const prefix2 = this.#prefix.length;
if (this.#affix) {
if (this.#prefixStr !== void 0 && this.#suffixStr !== void 0) {
if (!s.startsWith(this.#prefixStr) || !s.endsWith(this.#suffixStr)) {
return false;
}
} else {
chars ??= codePoints(s);
const end = chars.length - this.#suffix.length;
if (!hasAffix(this.#prefix, chars, 0) || !hasAffix(this.#suffix, chars, end)) {
return false;
}
}
}
const dotStart = !this.options.dot && s.startsWith(".");
if (this.#linear) {
if (s === "." || s === "..")
return false;
if (dotStart && !prefix2)
return false;
if (this.#wild)
return true;
chars ??= codePoints(s);
const end = chars.length - this.#suffix.length;
return testLinear(this.#linear, chars, prefix2, end);
}
chars ??= codePoints(s);
if (s === "." || s === "..") {
this.#marks ??= marks(this.#size);
const sim = this.#dotsSim ??= new Sim(true, false, this.#marks);
const start = sim.state();
sim.add(start, this.#start, 0, true);
let state = sim.done(start);
for (let i = 0; i < chars.length; i++) {
state = sim.step(state, chars[i], i, true);
}
return state.match;
}
this.#marks ??= marks(this.#size);
const dfa = this.#dfa ??= new Dfa(this.#start, this.#marks);
let d = dfa.start(dotStart);
const str = chars === s;
for (let i = 0; i < chars.length; i++) {
if (d.dead)
return false;
const code = str ? s.charCodeAt(i) : chars[i].charCodeAt(0);
d = d.ascii?.[code] ?? dfa.next(d, chars[i]);
}
return d.match;
}
/** a readable listing of the state machine */
toString() {
return show(this.#start);
}
};
// node_modules/.pnpm/minimatch@11.0.5/node_modules/minimatch/dist/esm/assert-valid-pattern.js
init_esm_shims();
var MAX_PATTERN_LENGTH = 1024 * 64;
var assertValidPattern = (pattern) => {
if (typeof pattern !== "string") {
throw new TypeError("invalid pattern");
}
if (pattern.length > MAX_PATTERN_LENGTH) {
throw new TypeError("pattern is too long");
}
};
// node_modules/.pnpm/minimatch@11.0.5/node_modules/minimatch/dist/esm/escape.js
init_esm_shims();
var escape = (s, { windowsPathsNoEscape = false, magicalBraces = false } = {}) => {
if (magicalBraces) {
return windowsPathsNoEscape ? s.replace(/[?*()[\]{}]/g, "[$&]") : s.replace(/[?*()[\]\\{}]/g, "\\$&");
}
return windowsPathsNoEscape ? s.replace(/[?*()[\]]/g, "[$&]") : s.replace(/[?*()[\]\\]/g, "\\$&");
};
// node_modules/.pnpm/minimatch@11.0.5/node_modules/minimatch/dist/esm/unescape.js
init_esm_shims();
var unescape = (s, { windowsPathsNoEscape = false, magicalBraces = true } = {}) => {
if (magicalBraces) {
return windowsPathsNoEscape ? s.replace(/\[([^/\\])\]/g, "$1") : s.replace(/((?!\\).|^)\[([^/\\])\]/g, "$1$2").replace(/\\([^/])/g, "$1");
}
return windowsPathsNoEscape ? s.replace(/\[([^/\\{}])\]/g, "$1") : s.replace(/((?!\\).|^)\[([^/\\{}])\]/g, "$1$2").replace(/\\([^/{}])/g, "$1");
};
// node_modules/.pnpm/minimatch@11.0.5/node_modules/minimatch/dist/esm/index.js
var minimatch = (p, pattern, options = {}) => {
assertValidPattern(pattern);
if (!options.nocomment && pattern.charAt(0) === "#") {
return false;
}
return cachedMinimatch(pattern, options).match(p);
};
var cache = /* @__PURE__ */ new Map();
var cacheMax = 256;
var cachedMinimatch = (pattern, options) => {
const key = pattern + "\0" + JSON.stringify(options);
let mm = cache.get(key);
if (mm) {
cache.delete(key);
} else {
mm = new Minimatch(pattern, { ...options });
if (cache.size >= cacheMax) {
cache.delete(cache.keys().next().value);
}
}
cache.set(key, mm);
return mm;
};
var defaultPlatform = typeof process === "object" && process ? process.env?.__MINIMATCH_TESTING_PLATFORM__ || process.platform : "posix";
var path = {
win32: { sep: "\\" },
posix: { sep: "/" }
};
var sep = defaultPlatform === "win32" ? path.win32.sep : path.posix.sep;
minimatch.sep = sep;
var GLOBSTAR = /* @__PURE__ */ Symbol("globstar **");
minimatch.GLOBSTAR = GLOBSTAR;
var filter = (pattern, options = {}) => (p) => minimatch(p, pattern, options);
minimatch.filter = filter;
var ext = (a, b = {}) => Object.assign({}, a, b);
var defaults = (def) => {
if (!def || typeof def !== "object" || !Object.keys(def).length) {
return minimatch;
}
const orig = minimatch;
const m = (p, pattern, options = {}) => orig(p, pattern, ext(def, options));
return Object.assign(m, {
Minimatch: class Minimatch extends orig.Minimatch {
constructor(pattern, options = {}) {
super(pattern, ext(def, options));
}
static defaults(options) {
return orig.defaults(ext(def, options)).Minimatch;
}
},
unescape: (s, options = {}) => orig.unescape(s, ext(def, options)),
escape: (s, options = {}) => orig.escape(s, ext(def, options)),
filter: (pattern, options = {}) => orig.filter(pattern, ext(def, options)),
defaults: (options) => orig.defaults(ext(def, options)),
braceExpand: (pattern, options = {}) => orig.braceExpand(pattern, ext(def, options)),
match: (list, pattern, options = {}) => orig.match(list, pattern, ext(def, options)),
sep: orig.sep,
GLOBSTAR
});
};
minimatch.defaults = defaults;
var braceExpand = (pattern, options = {}) => {
assertValidPattern(pattern);
if (options.nobrace || !/\{(?:(?!\{).)*\}/.test(pattern)) {
return [pattern];
}
return expand(pattern, { max: options.braceExpandMax });
};
minimatch.braceExpand = braceExpand;
var RANGE_MIN = 100;
var rangeRe = /(?<![\\$])\{(-?\d+)\.\.(-?\d+)\}/g;
var match = (list, pattern, options = {}) => {
const mm = new Minimatch(pattern, options);
list = list.filter((f) => mm.match(f));
if (mm.options.nonull && !list.length) {
list.push(pattern);
}
return list;
};
minimatch.match = match;
var globMagic = /[?*]|[+@!]\(.*?\)|\[|\]/;
var classEnd = (pattern, start) => {
let i = start + 1;
if (pattern.charAt(i) === "!" || pattern.charAt(i) === "^")
i++;
let sawStart = false;
for (; i < pattern.length; i++) {
const c = pattern.charAt(i);
if (c === "]" && sawStart)
return i;
sawStart = true;
if (c === "\\")
i++;
}
return -1;
};
var literal = (pattern, noext) => {
if (!noext && (pattern === "+()" || pattern === "@()"))
return pattern;
let lit2 = "";
for (let i = 0; i < pattern.length; i++) {
const c = pattern.charAt(i);
if (c === "\\") {
lit2 += pattern.charAt(++i) || c;
} else if (c === "*" || c === "?") {
return void 0;
} else if (c === "[") {
const close = classEnd(pattern, i);
if (close === -1) {
lit2 += c;
continue;
}
let body = pattern.slice(i + 1, close);
if (body.startsWith("\\"))
body = body.slice(1);
if ([...body].length !== 1 || "!^".includes(pattern.charAt(i + 1))) {
return void 0;
}
lit2 += body;
i = close;
} else if (!noext && "+@!".includes(c) && pattern.charAt(i + 1) === "(" && pattern.includes(")", i + 2)) {
return void 0;
} else {
lit2 += c;
}
}
return lit2;
};
var extSafe = (pattern) => {
if (/^[?*+@]\(\)$/.test(pattern) || pattern.includes("[:"))
return false;
let depth = 0;
for (let i = 0; i < pattern.length; i++) {
const c = pattern.charAt(i);
if (c === "\\") {
if (++i === pattern.length)
return false;
} else if (c === "[") {
const close = classEnd(pattern, i);
if (close !== -1)
i = close;
} else if ("+@!?*".includes(c) && pattern.charAt(i + 1) === "(") {
depth++;
i++;
} else if (c === ")") {
if (!depth--)
return false;
} else if (c === "|" && !depth) {
return false;
}
}
return !depth;
};
var lastSplit;
var lastChecked;
var portionMatch = (p, f) => typeof p === "string" ? f === p : f !== "" && p.testUnchecked(f);
var endIndex = (p, last, lastStart, prev, prevStart) => portionMatch(p, last) ? lastStart : prev !== void 0 && portionMatch(p, prev) ? prevStart : -1;
var isDots = (p) => p === "." || p === "..";
var isDrive = (p) => typeof p === "string" && /^[a-z]:$/i.test(p);
var isLongUNC = (p) => typeof p === "string" && /^unc$/i.test(p);
var uncHost = (p) => {
if (p[0] !== "" || p[1] !== "")
return -1;
const h = p[2] !== "?" ? 2 : isLongUNC(p[3]) ? 4 : -1;
const host = p[h];
const share = p[h + 1];
return h !== -1 && host !== void 0 && host !== "" && host !== GLOBSTAR && share !== void 0 && share !== "" && share !== GLOBSTAR ? h : -1;
};
var walkable = (p, dot) => !isDots(p) && (dot || !p.startsWith("."));
var dotPortion = (f, start, end, dot, sep3) => {
for (let k = start; k < end; ) {
if (f.charCodeAt(k) === 46) {
if (!dot)
return true;
const n = f.charCodeAt(k + 1) === 46 ? k + 2 : k + 1;
if (n >= end || isSep(f.charCodeAt(n), sep3))
return true;
}
const s = nextSep(f, k, sep3);
if (s === -1)
return false;
k = s + 1;
while (k < end && isSep(f.charCodeAt(k), sep3))
k++;
}
return false;
};
var isSep = (c, sep3) => sep3 === "/" ? c === 47 : sep3 === "\\" ? c === 92 : c === 47 || c === 92;
var nextSep = (f, from, sep3) => {
if (sep3 !== "both")
return f.indexOf(sep3, from);
const s = f.indexOf("/", from);
const b = f.indexOf("\\", from);
return s === -1 ? b : b === -1 ? s : Math.min(s, b);
};
var lastSep = (f, from, sep3) => sep3 !== "both" ? f.lastIndexOf(sep3, from) : Math.max(f.lastIndexOf("/", from), f.lastIndexOf("\\", from));
var PENDING = {};
var Minimatch = class {
options;
set;
pattern;
windowsPathsNoEscape;
nonegate;
negate;
comment;
empty;
preserveMultipleSlashes;
partial;
globSet;
globParts;
nocase;
isWindows;
platform;
windowsNoMagicRoot;
maxGlobstarRecursion;
// the path can be checked against the last portion, and the string
// portions, of each row before it is split. See #canMatch.
#endCheck;
#literalCheck;
// level 2 resolves `.` and `..` portions in the path
#level2;
// the unicode normalization form, if any
#form;
// the string portions of each row in the set
#literals = [];
// the rows that #canMatch can match without splitting the path
#shapes = [];
// the rows whose last portion can be checked against the path's
#endRows = [];
// brace expansion can make the same portion in many rows, so they
// share one Matcher, and the states that it caches
#matchers = /* @__PURE__ */ new Map();
// parse() returns PENDING in place of a Matcher
#pending = false;
// the char that marks numeric ranges in globSet, if any
#rangeMark;
// the GlobstarParts of each pattern row, by start index and partial
#globstarParts = /* @__PURE__ */ new WeakMap();
constructor(pattern, options = {}) {
assertValidPattern(pattern);
options = options || {};
this.options = options;
this.maxGlobstarRecursion = options.maxGlobstarRecursion ?? 200;
this.pattern = pattern;
this.platform = options.platform || defaultPlatform;
this.isWindows = this.platform === "win32";
const awe = "allowWindowsEscape";
this.windowsPathsNoEscape = !!options.windowsPathsNoEscape || options[awe] === false;
if (this.windowsPathsNoEscape) {
this.pattern = this.pattern.replace(/\\/g, "/");
}
this.preserveMultipleSlashes = !!options.preserveMultipleSlashes;
this.negate = false;
this.nonegate = !!options.nonegate;
this.comment = false;
this.empty = false;
this.partial = !!options.partial;
this.nocase = !!this.options.nocase;
this.windowsNoMagicRoot = options.windowsNoMagicRoot !== void 0 ? options.windowsNoMagicRoot : !!(this.isWindows && this.nocase);
this.globSet = [];
this.globParts = [];
this.set = [];
this.#endCheck = !this.preserveMultipleSlashes;
this.#level2 = (options.optimizationLevel ?? 1) >= 2;
const n = options.normalize;
this.#form = n === true ? "NFC" : n || void 0;
this.make();
const drives = this.set.map((row) => this.#rootIndex(row));
this.#endRows = this.set.map((row, r) => this.#endCheck && row.length - 1 > drives[r]);
this.#literals = this.set.map((row, r) => {
const end = this.#endRows[r] ? row.length - 1 : row.length;
const lits = row.slice(drives[r] + 1, end).filter((p) => typeof p === "string" && !!p);
return [...new Set(lits)];
});
this.#literalCheck = this.#literals.some((l) => l.length);
this.#shapes = this.set.map((row, r) => {
const [p0, p1, p2] = row;
if (this.#endRows[r] && row.length === 2) {
if (p0 === GLOBSTAR && p1 !== GLOBSTAR)
return "tail";
}
if (this.#endRows[r] && options.matchBase && row.length === 1 && p0 !== GLOBSTAR && p0 !== "") {
return "base";
}
if (!this.#endCheck || drives[r] !== -1)
return void 0;
if (row.length === 3 && p0 === GLOBSTAR && p2 === GLOBSTAR && p1 !== GLOBSTAR && p1 !== "" && // the portion between is only matched below this limit
this.maxGlobstarRecursion > 0) {
return "mid";
}
if (row.length === 2 && p1 === GLOBSTAR && p0 !== GLOBSTAR && p0 !== "") {
return "head";
}
return void 0;
});
}
// a string portion of a pattern, normalized if the option is set
#norm(p) {
return this.#form ? p.normalize(this.#form) : p;
}
// On Windows, the index of the last root portion of a row, as
// #matchRow finds it: 0 for `c:/...`, 3 for `//?/c:/...` and
// `//host/share/...`, and 5 for `//?/UNC/host/share/...`. Otherwise
// -1.
#rootIndex(row) {
if (!this.isWindows)
return -1;
if (isDrive(row[0]))
return 0;
const uncDrive = row[0] === "" && row[1] === "" && row[2] === "?" && isDrive(row[3]);
if (uncDrive)
return 3;
const h = uncHost(row);
return h === -1 ? -1 : h + 1;
}
hasMagic() {
if (this.options.magicalBraces && this.set.length > 1) {
return true;
}
for (const pattern of this.set) {
for (const part of pattern) {
if (typeof part !== "string")
return true;
}
}
return false;
}
make() {
const pattern = this.pattern;
const options = this.options;
if (!options.nocomment && pattern.charAt(0) === "#") {
this.comment = true;
return;
}
if (!pattern) {
this.empty = true;
return;
}
this.parseNegate();
this.globSet = [...new Set(this.#expandBraces())];
const rawGlobParts = this.globSet.map((s) => this.#slashSplit(s));
this.globParts = this.preprocess(rawGlobParts);
this.#pending = true;
let set = this.globParts.map((s, _, __) => {
if (this.isWindows && this.windowsNoMagicRoot) {
const n = s[2] === "?" && isLongUNC(s[3]) ? 6 : 4;
const isUNC = s[0] === "" && s[1] === "" && (s[2] === "?" || !globMagic.test(s[2])) && s.slice(3, n).every((p) => !globMagic.test(p));
const isDrive2 = /^[a-z]:/i.test(s[0]);
if (isUNC) {
return [
...s.slice(0, n).map((p) => this.#norm(p)),
...s.slice(n).map((ss) => this.parse(ss))
];
} else if (isDrive2) {
return [
this.#norm(s[0]),
...s.slice(1).map((ss) => this.parse(ss))
];
}
}
return s.map((ss) => this.parse(ss));
});
this.set = set.filter((s) => s.indexOf(false) === -1);
if (this.isWindows) {
for (let i = 0; i < this.set.length; i++) {
const p = this.set[i];
if (p[0] === "" && p[1] === "" && this.globParts[i][2] === "?" && (isDrive(p[3]) || isLongUNC(this.globParts[i][3]))) {
if (isLongUNC(this.globParts[i][3]))
p[3] = "UNC";
p[2] = "?";
}
}
}
if (!options.noext)
this.mergeRows();
this.#pending = false;
this.set.forEach((row, i) => {
for (let k = 0; k < row.length; k++) {
if (row[k] === PENDING) {
row[k] = this.parse(this.globParts[i][k]);
}
}
});
}
// Merge rows that only differ in one portion, where each of them
// is a Matcher, into one row that matches any of them with
// `@(a|b|...)`. Then a path is only walked once for all of them,
// for example with `**/*.{js,ts}`.
mergeRows() {
let merged = true;
while (merged) {
merged = false;
const len = Math.max(0, ...this.set.map((r) => r.length));
for (let k = 0; k < len; k++) {
const groups = /* @__PURE__ */ new Map();
this.set.forEach((row, i) => {
if (!(row[k] instanceof Matcher || row[k] === PENDING) || !extSafe(this.globParts[i][k])) {
return;
}
const parts = [...this.globParts[i]];
parts[k] = "";
const key = `${row.length}/${parts.join("/")}`;
const g = groups.get(key);
if (g)
g.push(i);
else
groups.set(key, [i]);
});
const drop = /* @__PURE__ */ new Set();
for (const g of groups.values()) {
if (g.length < 2)
continue;
const [first, ...rest] = g;
const alts = g.map((i) => this.globParts[i][k]);
const glob = `@(${alts.join("|")})`;
this.globParts[first][k] = glob;
this.set[first][k] = this.parse(glob);
for (const i of rest)
drop.add(i);
}
if (drop.size) {
merged = true;
this.set = this.set.filter((_, i) => !drop.has(i));
this.globParts = this.globParts.filter((_, i) => !drop.has(i));
}
}
}
}
// various transforms to equivalent pattern sets that are
// faster to process in a filesystem walk. The goal is to
// eliminate what we can, and push all ** patterns as far
// to the right as possible, even if it increases the number
// of patterns that we have to process.
preprocess(globParts) {
if (this.options.noglobstar) {
for (const partset of globParts) {
for (let j = 0; j < partset.length; j++) {
if (partset[j] === "**") {
partset[j] = "*";
}
}
}
}
const { optimizationLevel = 1 } = this.options;
if (optimizationLevel >= 2) {
globParts = this.firstPhasePreProcess(globParts);
globParts = this.secondPhasePreProcess(globParts);
} else if (optimizationLevel >= 1) {
globParts = this.levelOneOptimize(globParts);
} else {
globParts = this.adjascentGlobstarOptimize(globParts);
}
return globParts;
}
// just get rid of adjascent ** portions
adjascentGlobstarOptimize(globParts) {
return globParts.map((parts) => {
let gs = -1;
while (-1 !== (gs = parts.indexOf("**", gs + 1))) {
let i = gs;
while (parts[i + 1] === "**") {
i++;
}
if (i !== gs) {
parts.splice(gs, i - gs);
}
}
return parts;
});
}
// get rid of adjascent ** and resolve .. portions
levelOneOptimize(globParts) {
return globParts.map((parts) => {
const root = this.#rootIndex(parts) + 1;
parts = parts.reduce((set, part) => {
const prev = set[set.length - 1];
if (part === "**" && prev === "**") {
return set;
}
if (part === "..") {
if (set.length > root && prev && prev !== ".." && prev !== "." && prev !== "**") {
set.pop();
return set;
}
}
set.push(part);
return set;
}, []);
return parts.length === 0 ? [""] : parts;
});
}
// Check the path against each row before it is split. 'no' if no
// row can match, so it does not need to be split. 'yes' if a row of
// `**` and one more portion matches. 'maybe' if it must be split to
// know. None of this applies with partial, where the path can end
// before the row does.
//
// On Windows, `\` is a separator in the path too, and the portions of
// a row up to its drive letter or UNC share are not checked (see
// #rootIndex).
//
// Each string portion of a row must be a portion of the path, so the
// path must contain it. Level 2 can only take portions out of the
// path, so this is still true then.
//
// The last portion of a row must match the last portion of the path,
// or the one before a trailing `/` (as a/* and a/**/* match a/b/).
// This does not apply to rows that end in **, rows that end in a
// root, or preserveMultipleSlashes (which can leave more
// than one '' at the end). At level 2, it does not apply if that
// portion is `.` or `..`, which can be
// resolved away. Level 2 only takes other portions out of the
// middle of the path.
//
// A row of `**/x` then matches if the ** can walk the portions
// before the one that x matches, the same as #matchGlobstar. At
// level 2, this is only known if none of them are `.` or `..`, so
// that the path does not change when it is resolved.
#canMatch(f) {
const sep3 = !this.isWindows || !f.includes("\\") ? "/" : f.includes("/") ? "both" : "\\";
let ends = this.#endCheck ? 0 : -1;
let last = "";
let lastStart = 0;
let prev;
let prevStart = 0;
const dot = !!this.options.dot;
ROWS: for (let r = 0; r < this.set.length; r++) {
const row = this.set[r];
const p = row[row.length - 1];
let end = ends !== -1 && this.#endRows[r] && p !== GLOBSTAR;
if (end && ends === 0) {
const i = lastSep(f, f.length, sep3);
lastStart = i + 1;
last = f.slice(lastStart);
if (last === "" && i >= 0) {
let j = i;
while (j > 0 && isSep(f.charCodeAt(j - 1), sep3))
j--;
prevStart = j === 0 ? 0 : lastSep(f, j - 1, sep3) + 1;
prev = f.slice(prevStart, j);
}
const resolves = this.#level2 && (isDots(last) || last === "" && prev !== void 0 && isDots(prev));
ends = resolves ? -1 : 1;
end = !resolves;
}
let tailStart = -1;
if (end && typeof p === "string") {
tailStart = endIndex(p, last, lastStart, prev, prevStart);
if (tailStart === -1)
continue;
}
if (this.#literalCheck) {
for (const l of this.#literals[r]) {
if (!f.includes(l))
continue ROWS;
}
}
if (end && typeof p === "object") {
tailStart = endIndex(p, last, lastStart, prev, prevStart);
if (tailStart === -1)
continue;
}
const shape = this.#shapes[r];
if (shape === "base" && end)
return "yes";
if (shape === "tail" && end) {
if (this.#level2 && dotPortion(f, 0, tailStart, true, sep3)) {
return "maybe";
}
if (!dotPortion(f, 0, tailStart, dot, sep3))
return "yes";
continue;
}
if (shape === "mid" || shape === "head") {
const m = this.#walk(f, row, shape === "head", dot, sep3);
if (m !== "no")
return m;
continue;
}
return "maybe";
}
return "no";
}
// Match a row of a known shape against the split path, the same as
// #matchGlobstar and #matchGlobStarBodySections do. None of these
// rows have a drive letter, so they start at the first portion.
#matchShape(file, row, shape) {
const dot = !!this.options.dot;
const n = file.length;
if (shape === "head") {
if (n < 2 || !portionMatch(row[0], file[0])) {
return false;
}
for (let i = 1; i < n; i++)
if (!walkable(file[i], dot))
return false;
return true;
}
const x = row[1];
if (shape === "tail") {
let t = n - 1;
if (!portionMatch(x, file[t])) {
if (file[t] !== "" || n === 1 || !portionMatch(x, file[--t])) {
return false;
}
}
for (let i = 0; i < t; i++)
if (!walkable(file[i], dot))
return false;
return true;
}
for (let k = 0; k < n; k++) {
if (portionMatch(x, file[k]) && k + 1 < n) {
let i = k + 1;
while (i < n && walkable(file[i], dot))
i++;
if (i === n)
return true;
}
if (!walkable(file[k], dot))
return false;
}
return false;
}
// Match a row of `**/x/**` or `x/**` against the path, the same as
// #matchGlobstar and #matchGlobStarBodySections do once it is split.
// x must match a portion (the first one, for `x/**`), the ** before
// it must be able to walk the portions before that one, and there
// must be a portion after it, which the ** after it can walk. At
// level 2, this is only known if no portion is `.` or `..`.
#walk(f, row, head, dot, sep3) {
if (this.#level2 && dotPortion(f, 0, f.length, true, sep3))
return "maybe";
const x = row[head ? 0 : 1];
let s = 0;
for (let e = nextSep(f, s, sep3); e !== -1; e = nextSep(f, s, sep3)) {
let next = e + 1;
while (next < f.length && isSep(f.charCodeAt(next), sep3))
next++;
const hit = typeof x === "string" ? e - s === x.length && f.startsWith(x, s) : e > s && x.testUnchecked(f.slice(s, e));
if (hit && !dotPortion(f, next, f.length, dot, sep3))
return "yes";
if (head || dotPortion(f, s, next, dot, sep3))
return "no";
s = next;
}
return "no";
}
// the last split, if it was the same path with the same split
// settings
#cachedSplit(f) {
const last = lastSplit;
return last?.f === f && last.isWindows === this.isWindows && last.preserveMultipleSlashes === this.preserveMultipleSlashes && last.normalize === this.#form ? last : void 0;
}
// split a path for match(), and keep the parts for the next call
#split(f) {
const { isWindows, preserveMultipleSlashes } = this;
let parts = this.#slashSplit(isWindows ? f.split("\\").join("/") : f);
const normalize2 = this.#form;
if (normalize2)
parts = parts.map((p) => p.normalize(normalize2));
lastSplit = {
f,
isWindows,
preserveMultipleSlashes,
normalize: normalize2,
parts
};
return lastSplit;
}
// resolve `.` and `..` portions of a path, for level 2
#levelTwoFileOptimize(file) {
const parts = [...file];
let didSomething = false;
do {
didSomething = false;
if (!this.preserveMultipleSlashes) {
for (let i = 1; i < parts.length - 1; i++) {
const p = parts[i];
if (i === 1 && p === "" && parts[0] === "")
continue;
if (p === "." || p === "") {
didSomething = true;
parts.splice(i, 1);
i--;
}
}
if (parts[0] === "." && parts.length === 2 && (parts[1] === "." || parts[1] === "")) {
didSomething = true;
parts.pop();
}
}
const root = this.#rootIndex(parts) + 1;
let dd = root;
while (-1 !== (dd = parts.indexOf("..", dd + 1))) {
const p = parts[dd - 1];
if (p && p !== "." && p !== ".." && p !== "**") {
didSomething = true;
parts.splice(dd - 1, 2);
dd = Math.max(dd - 2, root);
}
}
} while (didSomething);
return parts.length === 0 ? [""] : parts;
}
// First phase: single-pattern processing
// <pre> is 1 or more portions
// <rest> is 1 or more portions
// <p> is any portion other than ., .., '', or **
// <e> is . or ''
//
// **/.. is *brutal* for filesystem walking performance, because
// it effectively resets the recursive walk each time it occurs,
// and ** cannot be reduced out by a .. pattern part like a regexp
// or most strings (other than .., ., and '') can be.
//
// <pre>/**/../<p>/<p>/<rest> -> {<pre>/../<p>/<p>/<rest>,<pre>/**/<p>/<p>/<rest>}
// <pre>/<e>/<rest> -> <pre>/<rest>
// <pre>/<p>/../<rest> -> <pre>/<rest>
// **/**/<rest> -> **/<rest>
//
// **/*/<rest> -> */**/<rest> <== not valid because ** doesn't follow
// this WOULD be allowed if ** did follow symlinks, or * didn't
firstPhasePreProcess(globParts) {
const { braceExpandMax = 1e5 } = this.options;
let splitCells = 0;
const maxSplitCells = 1e6;
let didSomething = false;
do {
didSomething = false;
for (let parts of globParts) {
let gs = -1;
while (-1 !== (gs = parts.indexOf("**", gs + 1))) {
let gss = gs;
while (parts[gss + 1] === "**") {
gss++;
}
if (gss > gs) {
parts.splice(gs + 1, gss - gs);
}
let next = parts[gs + 1];
const p = parts[gs + 2];
const p2 = parts[gs + 3];
if (next !== "..")
continue;
if (!p || p === "." || p === ".." || !p2 || p2 === "." || p2 === "..") {
continue;
}
const addedCells = parts.length - 1;
if (globParts.length >= braceExpandMax || splitCells + addedCells > maxSplitCells) {
throw new RangeError(`optimizationLevel 2: too many patterns from '**/..'`);
}
didSomething = true;
parts.splice(gs, 1);
const other = parts.slice(0);
other[gs] = "**";
splitCells += addedCells;
globParts.push(other);
gs--;
}
if (!this.preserveMultipleSlashes) {
for (let i = 1; i < parts.length - 1; i++) {
const p = parts[i];
if (i === 1 && p === "" && parts[0] === "")
continue;
if (p === "." || p === "") {
didSomething = true;
parts.splice(i, 1);
i--;
}
}
if (parts[0] === "." && parts.length === 2 && (parts[1] === "." || parts[1] === "")) {
didSomething = true;
parts.pop();
}
}
const root = this.#rootIndex(parts) + 1;
let dd = root;
while (-1 !== (dd = parts.indexOf("..", dd + 1))) {
const p = parts[dd - 1];
if (p && p !== "." && p !== ".." && p !== "**") {
didSomething = true;
const needDot = dd === 1 && parts[dd + 1] === "**";
const splin = needDot ? ["."] : [];
parts.splice(dd - 1, 2, ...splin);
if (parts.length === 0)
parts.push("");
dd = Math.max(dd - 2, root);
}
}
}
} while (didSomething);
return globParts;
}
// second phase: multi-pattern dedupes
// {<pre>/<rest>,<pre>/<rest>} -> <pre>/<rest>
// {<pre>/*/<rest>,<pre>/<p>/<rest>} -> <pre>/*/<rest>
secondPhasePreProcess(globParts) {
const keys = /* @__PURE__ */ new Set();
const rows = globParts.filter((parts) => {
const key = parts.join("/");
if (keys.has(key))
return false;
keys.add(key);
return true;
});
const stars = /* @__PURE__ */ new Map();
for (const parts of rows) {
const s = [];
for (let i = 0; i < parts.length; i++) {
if (parts[i] === "*")
s.push(i);
}
if (s.length)
stars.set(s.join(","), s);
}
if (!stars.size || rows.length * stars.size > 1e5)
return rows;
const { dot } = this.options;
return rows.filter((parts) => {
for (const s of stars.values()) {
if (s[s.length - 1] >= parts.length)
continue;
const c = [...parts];
let ok = true;
let changed = false;
for (const i of s) {
const p = parts[i];
if (p === "*")
continue;
if (!p || p === "**" || !dot && p.startsWith(".")) {
ok = false;
break;
}
c[i] = "*";
changed = true;
}
if (ok && changed && keys.has(c.join("/")))
return false;
}
return true;
});
}
parseNegate() {
if (this.nonegate)
return;
const pattern = this.pattern;
let negate = false;
let negateOffset = 0;
for (let i = 0; i < pattern.length && pattern.charAt(i) === "!"; i++) {
negate = !negate;
negateOffset++;
}
if (negateOffset)
this.pattern = pattern.slice(negateOffset);
this.negate = negate;
}
// Match the parts of a path against one row of the set. `resolved`
// is the parts after level 2 resolves `.` and `..` in them, or the
// same parts below level 2. Set partial to true to test if, for
// example, "/a/b" matches the start of "/*/b/*/d". Partial means, if
// you run out of file before you run out of pattern, then that's
// fine, as long as all the parts match.
#matchRow(file, resolved, pattern, partial) {
let fileStartIndex = 0;
let patternStartIndex = 0;
const fh = this.isWindows ? uncHost(resolved) : -1;
const ph = fh === -1 ? -1 : uncHost(pattern);
if (ph !== -1) {
const host = pattern[ph];
const share = pattern[ph + 1];
if (typeof host !== "string" || typeof share !== "string") {
fileStartIndex = fh;
patternStartIndex = ph;
} else if (resolved[fh].toLowerCase() === host.toLowerCase() && resolved[fh + 1].toLowerCase() === share.toLowerCase()) {
fileStartIndex = fh + 2;
patternStartIndex = ph + 2;
}
} else if (this.isWindows) {
const fileDrive = typeof file[0] === "string" && /^[a-z]:$/i.test(file[0]);
const fileUNC = !fileDrive && file[0] === "" && file[1] === "" && file[2] === "?" && /^[a-z]:$/i.test(file[3]);
const patternDrive = typeof pattern[0] === "string" && /^[a-z]:$/i.test(pattern[0]);
const patternUNC = !patternDrive && pattern[0] === "" && pattern[1] === "" && pattern[2] === "?" && typeof pattern[3] === "string" && /^[a-z]:$/i.test(pattern[3]);
const fdi = fileUNC ? 3 : fileDrive ? 0 : void 0;
const pdi = patternUNC ? 3 : patternDrive ? 0 : void 0;
if (typeof fdi === "number" && typeof pdi === "number") {
const [fd, pd] = [
file[fdi],
pattern[pdi]
];
if (fd.toLowerCase() === pd.toLowerCase()) {
patternStartIndex = pdi + 1;
fileStartIndex = fdi + 1;
}
}
}
file = resolved;
if (pattern.includes(GLOBSTAR)) {
return this.#matchGlobstar(file, pattern, partial, fileStartIndex, patternStartIndex);
}
return this.#matchOne(file, pattern, partial, fileStartIndex, patternStartIndex);
}
#matchGlobstar(file, pattern, partial, fileIndex, patternIndex) {
const { head, body, tail, segments } = this.#getGlobstarParts(pattern, partial, patternIndex);
if (head.length) {
const headEnd = fileIndex + head.length;
if (!this.#matchOne(file, head, partial, fileIndex, 0, headEnd)) {
return false;
}
fileIndex += head.length;
patternIndex += head.length;
}
let fileTailMatch = 0;
if (tail.length) {
if (tail.length + fileIndex > file.length)
return false;
let tailStart = file.length - tail.length;
if (this.#matchOne(file, tail, partial, tailStart, 0)) {
fileTailMatch = tail.length;
} else {
if (file[file.length - 1] !== "" || fileIndex + tail.length === file.length) {
return false;
}
tailStart--;
if (!this.#matchOne(file, tail, partial, tailStart, 0)) {
return false;
}
fileTailMatch = tail.length + 1;
}
}
if (!body.length) {
let sawSome = !!fileTailMatch;
for (let i = fileIndex; i < file.length - fileTailMatch; i++) {
const f = String(file[i]);
sawSome = true;
if (f === "." || f === ".." || !this.options.dot && f.startsWith(".")) {
return false;
}
}
return partial || sawSome;
}
return !!this.#matchGlobStarBodySections(file, segments, file.length - fileTailMatch, fileIndex, 0, partial, 0, !!fileTailMatch, /* @__PURE__ */ new Set());
}
// Split the pattern into head, tail, and middle of ** delimited
// parts. The tail has to be at the end, and the others just have to
// be found in order from the head. These only depend on the pattern
// row, so they are kept for the next path.
#getGlobstarParts(pattern, partial, patternIndex) {
let byStart = this.#globstarParts.get(pattern);
if (!byStart) {
byStart = [];
this.#globstarParts.set(pattern, byStart);
}
const key = patternIndex * 2 + (partial ? 1 : 0);
const found = byStart[key];
if (found)
return found;
const firstgs = pattern.indexOf(GLOBSTAR, patternIndex);
const lastgs = pattern.lastIndexOf(GLOBSTAR);
const head = pattern.slice(patternIndex, firstgs);
const body = pattern.slice(firstgs + 1, partial ? void 0 : lastgs);
const tail = partial ? [] : pattern.slice(lastgs + 1);
const segments = [[[], 0]];
let current = segments[0];
let nonGsParts = 0;
const nonGsPartsSums = [0];
for (const b of body) {
if (b === GLOBSTAR) {
nonGsPartsSums.push(nonGsParts);
current = [[], 0];
segments.push(current);
} else {
current[0].push(b);
nonGsParts++;
}
}
segments.forEach((b, i) => {
b[1] = nonGsParts - nonGsPartsSums[i];
});
const parts = { head, body, tail, segments };
byStart[key] = parts;
return parts;
}
// return false for "nope, not matching"
// return null for "not matching, cannot keep trying"
#matchGlobStarBodySections(file, bodySegments, fileLength, fileIndex, bodyIndex, partial, globStarDepth, sawTail, tried) {
const bs = bodySegments[bodyIndex];
if (!bs) {
if (partial) {
return fileIndex >= fileLength || fileIndex === fileLength - 1 && file[fileIndex] === "";
}
for (let i = fileIndex; i < fileLength; i++) {
sawTail = true;
const f = file[i];
if (f === "." || f === ".." || !this.options.dot && f.startsWith(".")) {
return false;
}
}
return sawTail;
}
const [body, need] = bs;
const after = partial ? fileLength : fileLength - need;
while (fileIndex <= after) {
const key = bodyIndex * (file.length + 1) + fileIndex;
if (tried.has(key))
return false;
tried.add(key);
const m = this.#matchOne(file, body, partial, fileIndex, 0, fileIndex + body.length);
if (m && globStarDepth < this.maxGlobstarRecursion) {
const sub = this.#matchGlobStarBodySections(file, bodySegments, fileLength, fileIndex + body.length, bodyIndex + 1, partial, globStarDepth + 1, sawTail, tried);
if (sub !== false) {
return sub;
}
}
if (fileIndex === after)
break;
const f = file[fileIndex];
if (f === "." || f === ".." || !this.options.dot && f.startsWith(".")) {
return false;
}
fileIndex++;
}
return partial || null;
}
#matchOne(file, pattern, partial, fileIndex, patternIndex, fileEnd = file.length) {
let fi;
let pi;
let pl;
let fl;
for (fi = fileIndex, pi = patternIndex, fl = Math.min(fileEnd, file.length), pl = pattern.length; fi < fl && pi < pl; fi++, pi++) {
let p = pattern[pi];
let f = file[fi];
if (p === false || p === GLOBSTAR) {
return false;
}
if (!portionMatch(p, f))
return false;
}
if (fi === fl && pi === pl) {
return true;
} else if (fi === fl) {
return partial;
} else if (pi === pl) {
return fi === fl - 1 && file[fi] === "";
} else {
throw new Error("wtf?");
}
}
braceExpand() {
return braceExpand(this.pattern, this.options);
}
// Expand braces, except numeric ranges such as {0..999} with at
// least RANGE_MIN values. Each of those stays in its pattern as
// `<mark>0..999<mark>`, for the Matcher to test. If a range might
// be inside a [...] class, expand all of them instead.
#expandBraces() {
const { pattern } = this;
if (this.options.nobrace)
return this.braceExpand();
let code = 57344;
while (pattern.includes(String.fromCharCode(code))) {
if (++code > 63743)
return this.braceExpand();
}
const mark = String.fromCharCode(code);
let found = false;
const marked = pattern.replace(rangeRe, (m, lo, hi) => {
const a = Number(lo);
const b = Number(hi);
if (!Number.isSafeInteger(a) || !Number.isSafeInteger(b) || Math.abs(a - b) + 1 < RANGE_MIN) {
return m;
}
found = true;
return `${mark}${lo}..${hi}${mark}`;
});
if (!found)
return this.braceExpand();
const set = braceExpand(marked, this.options);
for (const p of set) {
for (const portion of p.split("/")) {
const c = portion.indexOf("[");
if (c !== -1 && c < portion.lastIndexOf(mark)) {
return this.braceExpand();
}
}
}
this.#rangeMark = mark;
return set;
}
parse(pattern) {
assertValidPattern(pattern);
const options = this.options;
if (pattern === "**")
return GLOBSTAR;
if (pattern === "")
return "";
const { nocase, nocaseMagicOnly, noext = false, dot, normalize: normalize2 } = options;
const rangeMark = this.#rangeMark;
const lit2 = rangeMark && pattern.includes(rangeMark) ? void 0 : literal(pattern, noext);
if (lit2 !== void 0 && !(nocase && !nocaseMagicOnly && pattern.toUpperCase() !== pattern.toLowerCase())) {
return this.#norm(lit2);
}
if (this.#pending)
return PENDING;
let m = this.#matchers.get(pattern);
if (!m) {
m = new Matcher(pattern, {
nocase,
noext,
dot,
normalize: normalize2,
...rangeMark && { rangeMark }
});
this.#matchers.set(pattern, m);
}
return m;
}
#slashSplit(p) {
if (this.preserveMultipleSlashes) {
return p.split("/");
} else if (this.isWindows && /^\/\/[^/]+/.test(p)) {
return ["", ...p.split(/\/+/)];
} else {
return p.split(/\/+/);
}
}
match(f, partial = this.partial) {
if (this.comment) {
return false;
}
if (this.empty) {
return f === "";
}
if (f === "/" && partial) {
return true;
}
const options = this.options;
let split = this.#cachedSplit(f);
if (!split) {
if (!partial && !this.#form && (this.#endCheck || this.#literalCheck) && lastChecked !== f) {
lastChecked = f;
const can = this.#canMatch(f);
if (can === "no")
return options.flipNegate ? false : this.negate;
if (can === "yes")
return options.flipNegate ? true : !this.negate;
}
split = this.#split(f);
}
const ff = split.parts;
const resolved = this.#level2 ? split.resolved ??= this.#levelTwoFileOptimize(ff) : ff;
const set = this.set;
let filename = ff[ff.length - 1];
if (!filename) {
for (let i = ff.length - 2; !filename && i >= 0; i--) {
filename = ff[i];
}
}
for (let r = 0; r < set.length; r++) {
const pattern = set[r];
const shape = partial ? void 0 : this.#shapes[r];
const hit = shape === "base" ? portionMatch(pattern[0], filename) : shape ? this.#matchShape(resolved, pattern, shape) : options.matchBase && pattern.length === 1 ? this.#matchRow([filename], [filename], pattern, partial) : this.#matchRow(ff, resolved, pattern, partial);
if (hit) {
if (options.flipNegate) {
return true;
}
return !this.negate;
}
}
if (options.flipNegate) {
return false;
}
return this.negate;
}
static defaults(def) {
return minimatch.defaults(def).Minimatch;
}
};
minimatch.Minimatch = Minimatch;
minimatch.escape = escape;
minimatch.unescape = unescape;
// src/rag/file-filter.ts
var import_yaml2 = __toESM(require_dist(), 1);
var BASE_EXTENSIONS = /* @__PURE__ */ new Set([".md", ".mdx", ".yaml", ".yml", ".json"]);
var BASE_BASENAME_INCLUDES = /* @__PURE__ */ new Set([
"Dockerfile",
"Makefile",
"Gemfile",
"Rakefile",
"Procfile",
"Vagrantfile",
"Brewfile",
"Justfile",
".dockerignore",
".editorconfig",
".npmrc",
".nvmrc",
".prettierrc",
".eslintignore",
".gitattributes"
]);
var HARD_LOCKFILE_EXCLUSIONS = /* @__PURE__ */ new Set([
"package-lock.json",
"pnpm-lock.yaml",
"yarn.lock",
"composer.lock",
"Gemfile.lock",
"bun.lockb",
"poetry.lock",
"uv.lock",
"gradle.lockfile",
"Cargo.lock",
"go.sum",
"Pipfile.lock"
]);
var AI_TOOL_DIRS = /* @__PURE__ */ new Set([
".claude",
".codex",
".cursor",
".junie",
".roo",
".cline",
".windsurf"
]);
var FALLBACK_ADAPTER_FILES = /* @__PURE__ */ new Set(["CLAUDE.md", "AGENTS.md", "GEMINI.md"]);
var FRAMEWORK_DEFAULT_EXCLUDE_DIRECTORIES = [
"node_modules",
"vendor",
".venv",
"venv",
"env",
"__pycache__",
".dart_tool",
"Pods",
".gradle",
".m2",
"target",
"pkg",
"_vendor",
"dist",
"build",
"out",
".next",
".nuxt",
".svelte-kit",
".output",
".turbo",
".vercel",
".netlify",
"bin",
"obj",
"DerivedData",
".pnpm-store",
".yarn",
".yarn-cache",
".npm",
".cache",
".sass-cache",
".parcel-cache",
".eslintcache",
".stylelintcache",
".rpt2_cache",
".git",
".svn",
".hg",
".bzr",
"coverage",
".nyc_output",
"htmlcov",
".pytest_cache",
".mypy_cache",
".tox",
"__snapshots__",
"_site",
"storybook-static",
".docusaurus",
"site",
".terraform",
".vagrant",
".idea",
".eclipse"
];
var DEFAULT_RAG_IGNORE_CONFIG = {
version: 1,
exclude: [],
include: [],
additional_extensions: [],
additional_basename_includes: [],
additional_named_file_exclusions: [],
use_project_ignore_files: true,
use_global_gitignore: false
};
var RAG_IGNORE_SCHEMA = {
type: "object",
additionalProperties: false,
required: ["version"],
properties: {
version: { type: "integer", const: 1 },
exclude: { type: "array", items: { type: "string" } },
include: { type: "array", items: { type: "string" } },
additional_extensions: { type: "array", items: { type: "string" } },
additional_basename_includes: { type: "array", items: { type: "string" } },
additional_named_file_exclusions: { type: "array", items: { type: "string" } },
use_project_ignore_files: { type: "boolean" },
use_global_gitignore: { type: "boolean" }
}
};
var ajv2 = new import_ajv2.default({ allErrors: true });
var validateRagIgnoreConfig = ajv2.compile(RAG_IGNORE_SCHEMA);
var RagFileFilter = class {
constructor(options) {
this.options = options;
this.ragConfig = readRagIgnoreConfig(options.projectRoot);
this.includeGlobs = compileConfigGlobs(this.ragConfig.include);
this.excludeGlobs = compileConfigGlobs(this.ragConfig.exclude);
this.extensionAllowlist = sortExtensionsDescending(
/* @__PURE__ */ new Set([
...BASE_EXTENSIONS,
...options.packs.flatMap((pack) => pack.manifest.ast?.file_extensions ?? []),
...this.ragConfig.additional_extensions
])
);
this.namedBasenameIncludes = /* @__PURE__ */ new Set([
...BASE_BASENAME_INCLUDES,
...options.packs.flatMap((pack) => pack.manifest.rag?.basename_includes ?? []),
...this.ragConfig.additional_basename_includes
]);
this.directoryExclusionSet = /* @__PURE__ */ new Set([
...FRAMEWORK_DEFAULT_EXCLUDE_DIRECTORIES,
...options.packs.flatMap((pack) => pack.manifest.rag?.exclude_directories ?? [])
]);
this.adapterFileExclusionSet = readAdapterFileExclusions(options.projectRoot);
this.hardNamedFileExclusionSet = /* @__PURE__ */ new Set([
...HARD_LOCKFILE_EXCLUSIONS,
...this.ragConfig.additional_named_file_exclusions,
...this.adapterFileExclusionSet
]);
this.diagnosticsSnapshot = {
extensionAllowlist: [...this.extensionAllowlist],
namedBasenameIncludes: [...this.namedBasenameIncludes].sort(),
directoryExclusionSet: [...this.directoryExclusionSet].sort(),
hardNamedFileExclusionSet: [...this.hardNamedFileExclusionSet].sort()
};
}
options;
ragConfig;
extensionAllowlist;
namedBasenameIncludes;
directoryExclusionSet;
hardNamedFileExclusionSet;
adapterFileExclusionSet;
diagnosticsSnapshot;
includeGlobs;
excludeGlobs;
_layer3RulesCache = null;
async discoverFiles(onProgress) {
onProgress?.({
phase: "build",
message: "Discovering repository files for RAG eligibility"
});
const discovered = await (0, import_fast_glob.default)("**/*", {
cwd: this.options.projectRoot,
absolute: true,
dot: true,
onlyFiles: true,
followSymbolicLinks: false
});
const sorted = [...discovered].sort();
const layer3IgnoreRules = await readIgnoreRules(this.options.projectRoot, this.ragConfig);
const files = [];
const stats = {
total_discovered: sorted.length,
excluded_layer1: 0,
excluded_layer2: 0,
excluded_layer3: 0,
excluded_layer4: 0,
passed: 0
};
onProgress?.({
phase: "build",
message: `Filtering ${sorted.length} discovered files with RAG rules`,
loaded: 0,
total: sorted.length,
percent: 0
});
const progressInterval = Math.max(Math.min(Math.floor(sorted.length / 20), 2500), 500);
for (const [index, absolutePath] of sorted.entries()) {
const probe = await this.evaluateFile(absolutePath, layer3IgnoreRules);
if (probe.excluded) {
if (probe.layer === 1) stats.excluded_layer1 += 1;
if (probe.layer === 2) stats.excluded_layer2 += 1;
if (probe.layer === 3) stats.excluded_layer3 += 1;
if (probe.layer === 4) stats.excluded_layer4 += 1;
} else {
stats.passed += 1;
files.push(toPosixPath(absolutePath));
}
const processed = index + 1;
if (processed % progressInterval === 0 || processed === sorted.length) {
onProgress?.({
phase: "build",
message: `Filtered ${processed}/${sorted.length} files; ${stats.passed} remain eligible`,
loaded: processed,
total: sorted.length,
percent: Math.round(processed / sorted.length * 100)
});
}
}
onProgress?.({
phase: "build",
message: `RAG file filtering kept ${stats.passed} eligible files`,
loaded: sorted.length,
total: sorted.length,
percent: 100
});
return files;
}
filterDiagnostics() {
return this.diagnosticsSnapshot;
}
async probeFile(absolutePath) {
const normalizedPath = normalizeAbsolutePath(absolutePath);
if (!existsSync5(normalizedPath)) {
return {
path: normalizedPath,
excluded: true,
layer: 4,
rule: "not-found"
};
}
const layer3IgnoreRules = await this.getLayer3Rules();
return this.evaluateFile(normalizedPath, layer3IgnoreRules);
}
getLayer3Rules() {
if (!this._layer3RulesCache) {
this._layer3RulesCache = readIgnoreRules(this.options.projectRoot, this.ragConfig);
}
return this._layer3RulesCache;
}
previewIndex() {
return this.discoverFiles();
}
async evaluateFile(absolutePath, layer3IgnoreRules) {
const basename = basenameOf(absolutePath);
const relativePath = toProjectRelativePath(this.options.projectRoot, absolutePath);
if (!isEligibleByLayer1(basename, this.extensionAllowlist, this.namedBasenameIncludes)) {
return {
path: absolutePath,
excluded: true,
layer: 1,
rule: "ineligible-extension-or-basename"
};
}
const layer2 = isExcludedByLayer2(
absolutePath,
basename,
this.options.projectRoot,
this.hardNamedFileExclusionSet,
this.adapterFileExclusionSet
);
if (layer2) {
return {
path: absolutePath,
excluded: true,
layer: 2,
rule: layer2
};
}
const layer3 = this.evaluateLayer3(relativePath, basename, layer3IgnoreRules);
if (layer3) {
return {
path: absolutePath,
excluded: true,
layer: 3,
rule: layer3
};
}
const layer4 = await this.evaluateLayer4(absolutePath);
if (layer4) {
return {
path: absolutePath,
excluded: true,
layer: 4,
rule: layer4
};
}
return {
path: absolutePath,
excluded: false
};
}
evaluateLayer3(relativePath, basename, ignoreRules) {
const includedByConfig = this.includeGlobs.some((glob) => glob.matches(relativePath));
const ignoreMatch = ignoreRules.find((rule) => rule.matches(relativePath));
if (ignoreMatch && !includedByConfig) {
return `project-ignore:${ignoreMatch.source}`;
}
const defaultDir = findMatchingDirectory(relativePath, FRAMEWORK_DEFAULT_EXCLUDE_DIRECTORIES);
if (defaultDir && !includedByConfig) {
return `framework-directory:${defaultDir}`;
}
const packDir = findMatchingDirectory(
relativePath,
this.options.packs.flatMap((pack) => pack.manifest.rag?.exclude_directories ?? [])
);
if (packDir && !includedByConfig) {
return `pack-directory:${packDir}`;
}
const configExclude = this.excludeGlobs.find((glob) => glob.matches(relativePath));
if (configExclude && !includedByConfig) {
return `config-exclude:${configExclude.pattern}`;
}
if (isSoftEnvExcluded(basename) && !includedByConfig) {
return "soft-env-exclusion";
}
return void 0;
}
async evaluateLayer4(absolutePath) {
let handle;
try {
handle = await open(absolutePath, "r");
} catch {
return await isSymlink(absolutePath) ? "symlink" : this.unreadable(absolutePath);
}
try {
if (await isSymlink(absolutePath)) {
return "symlink";
}
const fileStat = await handle.stat();
const maxFileSize = this.options.intelligence?.rag_max_file_size ?? 153600;
if (fileStat.size > maxFileSize) {
return "size-cap";
}
if (fileStat.size === 0) {
return "empty-file";
}
const preview = await handle.readFile();
const prefix2 = preview.subarray(0, Math.min(8192, preview.length));
if (prefix2.includes(0)) {
return "binary-nul-byte";
}
const utf8 = preview.toString("utf8");
if (utf8.includes("\uFFFD")) {
engineLog("warn", `Skipping non-UTF-8 file: ${absolutePath}`);
return "invalid-utf8";
}
const nonWhitespaceChars = utf8.replace(/\s/g, "").length;
if (nonWhitespaceChars < 50) {
return "below-minimum-content-threshold";
}
return void 0;
} catch {
return this.unreadable(absolutePath);
} finally {
await handle.close();
}
}
unreadable(absolutePath) {
engineLog("warn", `Skipping unreadable file: ${absolutePath}`);
return "unreadable";
}
};
async function isSymlink(absolutePath) {
try {
return (await lstat(absolutePath)).isSymbolicLink();
} catch {
return false;
}
}
function readRagIgnoreConfig(projectRoot) {
const configPath = join10(projectRoot, PATHS.RAG_IGNORE_CONFIG);
if (!existsSync5(configPath)) {
return { ...DEFAULT_RAG_IGNORE_CONFIG };
}
try {
const parsed = import_yaml2.default.parse(readFileSync3(configPath, "utf8"));
if (!validateRagIgnoreConfig(parsed)) {
engineLog("warn", `Invalid RAG ignore config at ${configPath}; using defaults`);
return { ...DEFAULT_RAG_IGNORE_CONFIG };
}
return {
version: 1,
exclude: [...parsed.exclude ?? []],
include: [...parsed.include ?? []],
additional_extensions: [...parsed.additional_extensions ?? []],
additional_basename_includes: [
...parsed.additional_basename_includes ?? []
],
additional_named_file_exclusions: [
...parsed.additional_named_file_exclusions ?? []
],
use_project_ignore_files: parsed.use_project_ignore_files ?? DEFAULT_RAG_IGNORE_CONFIG.use_project_ignore_files,
use_global_gitignore: parsed.use_global_gitignore ?? DEFAULT_RAG_IGNORE_CONFIG.use_global_gitignore
};
} catch {
engineLog("warn", `Failed to parse RAG ignore config at ${configPath}; using defaults`);
return { ...DEFAULT_RAG_IGNORE_CONFIG };
}
}
function readAdapterFileExclusions(projectRoot) {
const manifestPath = join10(projectRoot, PATHS.ONBOARDING_MANIFEST);
if (!existsSync5(manifestPath)) {
return new Set(FALLBACK_ADAPTER_FILES);
}
try {
const manifest = JSON.parse(readFileSync3(manifestPath, "utf8"));
const basenames = manifest.generated_artifacts.map((artifact) => basenameOf(artifact.path));
return /* @__PURE__ */ new Set([...basenames, ...FALLBACK_ADAPTER_FILES]);
} catch {
return new Set(FALLBACK_ADAPTER_FILES);
}
}
async function readIgnoreRules(projectRoot, config) {
if (!config.use_project_ignore_files) {
return [];
}
const projectIgnoreFiles = await (0, import_fast_glob.default)("**/.gitignore", {
cwd: projectRoot,
absolute: true,
dot: true,
onlyFiles: true,
followSymbolicLinks: false
});
const extraRootFiles = [".ignore", ".fdignore", ".rgignore"].map((name) => join10(projectRoot, name)).filter((path2) => existsSync5(path2));
const globalFiles = config.use_global_gitignore ? [join10(homedir4(), ".gitignore_global"), join10(homedir4(), ".config/git/ignore")].filter(
(path2) => existsSync5(path2)
) : [];
const rules = [];
for (const file of [...projectIgnoreFiles.sort(), ...extraRootFiles, ...globalFiles]) {
const fileRelativeDir = file.startsWith(projectRoot) ? toPosixPath(relative(projectRoot, file)).split("/").slice(0, -1).join("/") : "";
const baseDir = file.startsWith(projectRoot) ? fileRelativeDir : "";
const sourceName = file.startsWith(projectRoot) ? toPosixPath(relative(projectRoot, file)) : file;
const lines = readFileSync3(file, "utf8").split(/\r?\n/);
for (const rawLine of lines) {
const line = rawLine.trim();
if (!line || line.startsWith("#")) {
continue;
}
if (line.startsWith("!")) {
continue;
}
rules.push(
...resolveIgnorePattern(baseDir, line).map((glob) => ({
glob,
source: sourceName,
matches: compileProjectGlob(glob)
}))
);
}
}
return rules;
}
function isEligibleByLayer1(basename, extAllowlist, basenameIncludes) {
const lowerBasename = basename.toLowerCase();
for (const extension of extAllowlist) {
if (lowerBasename.endsWith(extension.toLowerCase())) {
return true;
}
}
if (basenameIncludes.has(basename)) {
return true;
}
const stem = basename.split(".")[0];
return stem.length > 0 && basenameIncludes.has(stem);
}
function isExcludedByLayer2(absolutePath, basename, projectRoot, hardExclusions, adapterFiles) {
const relativePath = toProjectRelativePath(projectRoot, absolutePath);
const segments = splitSegments(relativePath);
if (relativePath.startsWith(".paqad/") || segments.includes(".paqad")) {
return "framework-output:.paqad";
}
const aiToolDir = segments.find((segment) => AI_TOOL_DIRS.has(segment));
if (aiToolDir) {
return `ai-tool-directory:${aiToolDir}`;
}
if (hardExclusions.has(basename)) {
return adapterFiles.has(basename) ? `adapter-file:${basename}` : `hard-file:${basename}`;
}
return void 0;
}
function findMatchingDirectory(relativePath, directories) {
const segments = splitSegments(relativePath);
return directories.find((directory) => {
const normalized = trimSlashes(toPosixPath(directory));
if (!normalized) {
return false;
}
const dirSegments = normalized.split("/");
if (dirSegments.length === 1) {
return segments.includes(dirSegments[0]);
}
const relativeDir = dirnameSegments(relativePath);
return relativeDir === normalized || relativeDir.startsWith(`${normalized}/`) || relativeDir.includes(`/${normalized}/`) || relativeDir.endsWith(`/${normalized}`);
});
}
function isSoftEnvExcluded(basename) {
return basename === ".env" || basename.startsWith(".env.");
}
function resolveIgnorePattern(baseDir, pattern) {
const normalizedBase = trimSlashes(toPosixPath(baseDir));
const normalizedPattern = trimSlashes(toPosixPath(pattern.replace(/^\.\//, "")));
if (!normalizedPattern) {
return [];
}
const basePrefix = normalizedBase ? `${normalizedBase}/` : "";
const directoryPattern = pattern.endsWith("/");
const candidate = normalizedPattern.replace(/^\/+/, "");
if (!candidate.includes("/")) {
if (directoryPattern) {
return [`${basePrefix}**/${candidate}/**`, `${basePrefix}${candidate}/**`];
}
return [`${basePrefix}**/${candidate}`, `${basePrefix}${candidate}`];
}
if (directoryPattern) {
return [`${basePrefix}${candidate}/**`];
}
return [`${basePrefix}${candidate}`];
}
var PATH_MATCHES_GLOB_OPTIONS = {
nocase: process.platform === "darwin" || process.platform === "win32",
windowsPathsNoEscape: true,
nonegate: true,
nocomment: true,
optimizationLevel: 2,
platform: process.platform,
nocaseMagicOnly: true
};
function compileConfigGlobs(patterns) {
return patterns.map((pattern) => ({ pattern, matches: compileProjectGlob(pattern) }));
}
function compileProjectGlob(pattern) {
const rawPattern = toPosixPath(pattern);
const directoryPattern = rawPattern.endsWith("/");
const normalizedPattern = trimSlashes(rawPattern);
if (!normalizedPattern) {
return () => false;
}
const candidates = /* @__PURE__ */ new Set([normalizedPattern]);
if (!normalizedPattern.startsWith("**/")) {
candidates.add(`**/${normalizedPattern}`);
}
if (directoryPattern) {
candidates.add(`${normalizedPattern}/**`);
candidates.add(`**/${normalizedPattern}/**`);
}
const matchers = [...candidates].map(
(candidate) => new Minimatch(candidate, PATH_MATCHES_GLOB_OPTIONS)
);
return (relativePath) => {
const normalizedPath = toPosixPath(relativePath);
return matchers.some((matcher) => matcher.match(normalizedPath));
};
}
function basenameOf(path2) {
const normalized = toPosixPath(path2);
const segments = normalized.split("/");
return segments[segments.length - 1];
}
function dirnameSegments(path2) {
const segments = splitSegments(path2);
return segments.slice(0, -1).join("/");
}
function splitSegments(path2) {
return toPosixPath(path2).split("/").filter(Boolean);
}
function toProjectRelativePath(projectRoot, absolutePath) {
return toPosixPath(relative(projectRoot, absolutePath));
}
function normalizeAbsolutePath(path2) {
return isAbsolute(path2) ? path2 : join10(process.cwd(), path2);
}
function trimSlashes(value) {
let start = 0;
let end = value.length;
while (start < end && value[start] === "/") start += 1;
while (end > start && value[end - 1] === "/") end -= 1;
return value.slice(start, end);
}
function sortExtensionsDescending(extensions) {
return [...extensions].sort(
(left, right) => right.length - left.length || left.localeCompare(right)
);
}
// src/rag/service.ts
var VISION_MAX_CHUNK_CHARS = 2e3;
var CRS_EMBED_BATCH_SIZE = 32;
var HYBRID_FUSION_POOL_MULTIPLIER = 4;
var RELIEF_SLICE_CAP = 2;
var REMOTE_API_KEY_NAMES = { openai: "OPENAI_API_KEY" };
function toRetrievalResult(candidates, extra) {
return {
vector_scores: new Map(candidates.map((candidate) => [candidate.id, candidate.score])),
chunks_retrieved: candidates.length,
retrieved_chunk_ids: candidates.map((candidate) => candidate.id),
retrieved_source_files: candidates.map((candidate) => candidate.source_file),
retrieved_chunks: candidates.map((candidate) => ({
id: candidate.id,
source_file: candidate.source_file,
content: candidate.content
})),
best_score: extra.bestScore,
low_confidence: extra.lowConfidence
};
}
var CRS_REVERT_RETENTION_MS = 24 * 60 * 60 * 1e3;
var PARTIAL_VECTOR_INDEX = PATHS.VECTOR_INDEX.replace(/\.json$/, ".partial.json");
var PARTIAL_VECTOR_META = PATHS.VECTOR_META.replace(/\.json$/, ".partial.json");
function queryTextFromTask(taskDescription, keywords = [], targetFilePath, symbols = []) {
const parts = [taskDescription, ...keywords, targetFilePath, ...symbols].filter(Boolean);
return parts.join("\n");
}
function estimateEtaSeconds(startMs, loaded, total) {
if (loaded <= 0 || total <= loaded) {
return 0;
}
const elapsedMs = Math.max(Date.now() - startMs, 1);
const rate = loaded / elapsedMs;
return Math.max(Math.round((total - loaded) / rate / 1e3), 0);
}
var RagService = class {
constructor(projectRoot, providerFactory = createEmbeddingProvider, crsBacklog = new CrsBacklogQueue()) {
this.projectRoot = projectRoot;
this.providerFactory = providerFactory;
this.crsBacklog = crsBacklog;
this.indexManager = new ChunkIndexManager(projectRoot);
this.patternVectors = new PatternVectorService(void 0, providerFactory);
this.resumeValidator = new SessionResumeValidator(void 0, async () => this);
}
projectRoot;
providerFactory;
crsBacklog;
vectorIndex = new FileVectorIndex();
visionVectorIndex = new FileVectorIndex(
PATHS.VISION_VECTOR_INDEX,
PATHS.VISION_VECTOR_META
);
chunker = new AstChunker();
indexManager;
patternVectors;
resumeValidator;
resumeValidationPromise;
async getStatus() {
const profile = readProjectProfile(this.projectRoot);
const intelligence = normalizeIntelligenceConfig(profile?.intelligence);
const status = await this.vectorIndex.status(this.projectRoot);
const meta = status.meta;
const expectedModel = intelligence.embedding_provider ? intelligence.embedding_model : void 0;
const providerModelMatch = meta?.provider === intelligence.embedding_provider && meta?.model === expectedModel;
const chunkerMatch = meta?.chunker_version === CHUNKER_VERSION;
const staleMetadata = Boolean(meta) && !providerModelMatch;
const valid = Boolean(meta) && !status.corrupt && (!intelligence.rag_enabled || providerModelMatch && chunkerMatch);
let reason = status.reason;
if (!reason && status.present && meta && !providerModelMatch) {
reason = "configured provider/model does not match stored vector metadata";
} else if (!reason && status.present && meta && !chunkerMatch && intelligence.rag_enabled) {
reason = "stored index was built with a different chunker; a full rebuild is needed";
}
let visionChunkCount;
try {
visionChunkCount = (await this.visionVectorIndex.loadMeta(this.projectRoot))?.chunk_count;
} catch {
visionChunkCount = void 0;
}
return {
enabled: intelligence.rag_enabled,
configured_provider: intelligence.embedding_provider,
configured_model: intelligence.embedding_model,
index_present: status.present,
valid,
stale_metadata: staleMetadata || void 0,
built_at: meta?.built_at,
chunk_count: meta?.chunk_count ?? 0,
size_bytes: status.sizeBytes,
reason: status.present && (!valid || staleMetadata) ? reason : void 0,
vision_chunk_count: visionChunkCount
};
}
async configureAndBuild(partial, onProgress) {
const { profile, intelligence } = await this.buildIndexCore(partial, onProgress);
profile.intelligence = intelligence;
writeProjectProfile(this.projectRoot, profile);
syncFrameworkConfig(this.projectRoot, { intelligence });
return this.getStatus();
}
async buildIndexOnly(partial, onProgress) {
await this.buildIndexCore(partial, onProgress);
return this.getStatus();
}
async buildIndexCore(partial, onProgress) {
const profile = readProjectProfile(this.projectRoot);
if (!profile) {
throw new Error("Project profile not found");
}
const intelligence = normalizeIntelligenceConfig({
...profile.intelligence,
...partial,
rag_enabled: true
});
assertEmbeddingProviderAvailable(this.projectRoot, intelligence.embedding_provider);
if (intelligence.embedding_provider === void 0) {
throw new Error("Embedding provider is required when enabling RAG");
}
await this.rebuild({ intelligence, onProgress });
await this.patternVectors.refresh(
this.projectRoot,
(message) => onProgress?.({ phase: "build", message })
);
appendRagAudit(this.projectRoot, "INFO", "rag-enabled", {
provider: intelligence.embedding_provider,
model: intelligence.embedding_model
});
return { profile, intelligence };
}
async rebuild(options) {
const profile = readProjectProfile(this.projectRoot);
const intelligence = normalizeIntelligenceConfig(
options?.intelligence ?? profile?.intelligence
);
assertEmbeddingProviderAvailable(this.projectRoot, intelligence.embedding_provider);
if (!intelligence.rag_enabled || !intelligence.embedding_provider) {
throw new Error("RAG must be enabled and configured before rebuilding");
}
const start = Date.now();
const signal = options?.signal;
const runId = randomUUID();
let provider;
try {
this.throwIfAborted(signal);
provider = await this.providerFactory(this.projectRoot, intelligence, options?.onProgress);
try {
await provider.validate();
} catch (error) {
if (isEmbeddingProviderError(error) && error.code === "invalid_api_key") {
appendRagAudit(this.projectRoot, "WARN", "rag-api-key-validation-failed", {
provider: provider.name,
reason: error.message
});
}
throw error;
}
appendRagAudit(this.projectRoot, "INFO", "rag-build-started", {
provider: provider.name,
model: provider.model
});
const sourceFiles = await this.discoverSourceFiles(options?.onProgress);
options?.onProgress?.({
phase: "build",
message: `Chunking ${sourceFiles.length} source files`,
loaded: 0,
total: sourceFiles.length,
percent: 0
});
const chunkIndex = await this.indexManager.rebuild(sourceFiles, this.chunker);
options?.onProgress?.({
phase: "build",
message: `Chunked ${chunkIndex.entries.length}/${sourceFiles.length} files`,
loaded: chunkIndex.entries.length,
total: sourceFiles.length,
percent: sourceFiles.length === 0 ? 100 : Math.round(chunkIndex.entries.length / sourceFiles.length * 100)
});
const items = await this.embedChunks(provider, flattenChunks(chunkIndex), {
onProgress: options?.onProgress,
signal,
embedTextOf: this.buildEmbedContextualizer()
});
if (signal?.aborted) {
throw new CancelledError("RAG rebuild cancelled by consumer", {
partialChunks: items
});
}
await this.vectorIndex.replaceAll(
this.projectRoot,
items,
{
provider: provider.name,
model: provider.model,
chunker_version: CHUNKER_VERSION
},
intelligence.rag_base_branch
);
await this.patternVectors.refresh(
this.projectRoot,
(message) => options?.onProgress?.({ phase: "build", message })
);
appendRagAudit(this.projectRoot, "INFO", "rag-build-completed", {
provider: provider.name,
model: provider.model,
chunks: items.length,
duration_ms: Date.now() - start
});
} catch (error) {
if (isCancelledError(error)) {
const checkpointPath = await this.writePartialIndex(error, provider);
appendRagAudit(this.projectRoot, "WARN", "rag-build-cancelled", {
checkpoint_path: checkpointPath,
duration_ms: Date.now() - start
});
appendRunCancelledEvent(this.projectRoot, runId, {
reason: "rag-rebuild-cancelled",
checkpoint_path: checkpointPath
});
throw new CancelledError("RAG rebuild cancelled by consumer", {
checkpoint_path: checkpointPath
});
}
appendRagAudit(this.projectRoot, "WARN", "rag-build-failed", {
reason: error instanceof Error ? error.message : "unknown-error",
duration_ms: Date.now() - start
});
throw error;
}
}
async writePartialIndex(error, provider) {
const partial = error.details?.partialChunks ?? [];
if (partial.length === 0 || !provider) {
return void 0;
}
const partialIndex = new FileVectorIndex(
PARTIAL_VECTOR_INDEX,
PARTIAL_VECTOR_META
);
await partialIndex.replaceAll(this.projectRoot, partial, {
provider: provider.name,
model: provider.model
});
return PARTIAL_VECTOR_INDEX;
}
async clear() {
const profile = readProjectProfile(this.projectRoot);
if (profile) {
profile.intelligence = {
...profile.intelligence,
rag_enabled: false
};
writeProjectProfile(this.projectRoot, profile);
setConfigValue(this.projectRoot, "rag_enabled", "false");
}
await this.vectorIndex.clear(this.projectRoot);
appendRagAudit(this.projectRoot, "INFO", "rag-cleared");
}
async refreshContext() {
const profile = readProjectProfile(this.projectRoot);
const intelligence = normalizeIntelligenceConfig(profile?.intelligence);
this.refuseRemovedProvider(intelligence);
const sourceFiles = await this.discoverSourceFiles();
const syncResult = await this.indexManager.sync(sourceFiles, this.chunker);
if (!intelligence.rag_enabled) {
return syncResult;
}
const status = await this.getStatus();
if (!status.index_present || !status.valid || !intelligence.embedding_provider) {
if (!status.index_present) {
appendRagAudit(this.projectRoot, "WARN", "rag-fallback", {
reason: "missing-index-during-refresh"
});
} else if (!status.valid && status.reason?.includes("provider/model")) {
appendRagAudit(this.projectRoot, "WARN", "rag-provider-mismatch", {
reason: status.reason
});
} else if (!status.valid) {
appendRagAudit(this.projectRoot, "WARN", "rag-fallback", {
reason: status.reason ?? "invalid-index-during-refresh"
});
}
return syncResult;
}
await this.syncVectorIndex(syncResult, intelligence);
await this.patternVectors.refresh(this.projectRoot);
return syncResult;
}
async retrieve(syncResult, input, topN) {
return this.retrieveWithSyncPolicy(syncResult, input, topN, false);
}
async retrieveWithSyncPolicy(syncResult, input, topN, skipSync) {
const profile = readProjectProfile(this.projectRoot);
const intelligence = normalizeIntelligenceConfig(profile?.intelligence);
this.refuseRemovedProvider(intelligence);
if (!intelligence.rag_enabled || !intelligence.embedding_provider) {
return {
vector_scores: /* @__PURE__ */ new Map(),
chunks_retrieved: 0,
retrieved_chunk_ids: [],
retrieved_source_files: [],
retrieved_chunks: []
};
}
if (topN === 0) {
return {
vector_scores: /* @__PURE__ */ new Map(),
chunks_retrieved: 0,
retrieved_chunk_ids: [],
retrieved_source_files: [],
retrieved_chunks: []
};
}
await this.validateResumeState();
const status = await this.getStatus();
if (!status.index_present) {
appendRagAudit(this.projectRoot, "WARN", "rag-fallback", { reason: "missing-index" });
return {
vector_scores: /* @__PURE__ */ new Map(),
chunks_retrieved: 0,
retrieved_chunk_ids: [],
retrieved_source_files: [],
retrieved_chunks: [],
fallback_reason: "missing-index"
};
}
if (!status.valid) {
if (status.reason?.includes("provider/model")) {
appendRagAudit(this.projectRoot, "WARN", "rag-provider-mismatch", {
reason: status.reason
});
}
appendRagAudit(this.projectRoot, "WARN", "rag-fallback", {
reason: status.reason ?? "stale-or-mismatched-index"
});
return {
vector_scores: /* @__PURE__ */ new Map(),
chunks_retrieved: 0,
retrieved_chunk_ids: [],
retrieved_source_files: [],
retrieved_chunks: [],
fallback_reason: status.reason ?? "stale-or-mismatched-index"
};
}
try {
if (!skipSync && syncResult !== null) {
await this.syncVectorIndex(syncResult, intelligence);
}
const limit = topN ?? intelligence.rag_top_n;
const scored = await this.scoreCandidates(input, limit, intelligence);
const bestScore = scored.length > 0 ? scored[0].score : void 0;
const aboveFloor = scored.filter(
(candidate) => candidate.score >= intelligence.rag_similarity_threshold
);
if (aboveFloor.length > 0) {
return toRetrievalResult(aboveFloor, { bestScore, lowConfidence: false });
}
const relief = scored.filter((candidate) => candidate.score >= intelligence.rag_relief_floor).slice(0, RELIEF_SLICE_CAP);
if (relief.length > 0) {
return toRetrievalResult(relief, { bestScore, lowConfidence: true });
}
appendRagAudit(this.projectRoot, "WARN", "rag-fallback", {
reason: "below-similarity-threshold"
});
return {
vector_scores: /* @__PURE__ */ new Map(),
chunks_retrieved: 0,
retrieved_chunk_ids: [],
retrieved_source_files: [],
retrieved_chunks: [],
fallback_reason: "below-similarity-threshold",
best_score: bestScore
};
} catch (error) {
appendRagAudit(this.projectRoot, "WARN", "rag-fallback", {
reason: error instanceof Error ? error.message : "unknown-error"
});
return {
vector_scores: /* @__PURE__ */ new Map(),
chunks_retrieved: 0,
retrieved_chunk_ids: [],
retrieved_source_files: [],
retrieved_chunks: [],
fallback_reason: error instanceof Error ? error.message : "unknown-error"
};
}
}
async retrieveForEval(input, topN, options = {}) {
const syncResult = options.skipSync === true ? null : await this.refreshContext();
return this.retrieveWithSyncPolicy(syncResult, input, topN, true);
}
async scoreCandidates(input, limit, intelligence) {
const provider = await this.providerFactory(this.projectRoot, intelligence);
const queryText = queryTextFromTask(
input.taskDescription,
input.keywords,
input.targetFilePath,
input.symbolReferences ?? []
);
const [queryVector] = await provider.embed(queryText);
const pool = limit * HYBRID_FUSION_POOL_MULTIPLIER;
const [fileResults, visionResults] = await Promise.all([
this.vectorIndex.query(this.projectRoot, queryVector, pool),
this.visionVectorIndex.query(this.projectRoot, queryVector, pool)
]);
const dense = [...fileResults, ...visionResults].sort(
(left, right) => right.score - left.score
);
const fused = fuseHybridRanking(dense, queryText);
const reranked = await this.applyReranking(queryText, fused, intelligence.reranking);
return reranked.slice(0, limit).map((result) => ({
id: result.item.id,
source_file: result.item.source_file,
content: result.item.content,
score: result.score
}));
}
async probe(input, topN) {
const intelligence = normalizeIntelligenceConfig(
readProjectProfile(this.projectRoot)?.intelligence
);
this.refuseRemovedProvider(intelligence);
if (!intelligence.rag_enabled || !intelligence.embedding_provider) {
return [];
}
await this.refreshContext();
const status = await this.getStatus();
if (!status.index_present || !status.valid) {
return [];
}
return this.scoreCandidates(input, topN ?? intelligence.rag_top_n, intelligence);
}
async ingestExtractedText(input) {
if (!SUPPORTED_EXTRACTION_KINDS.includes(input.extractionKind)) {
throw new RagIngestError(
"unknown_extraction_kind",
`Unknown extraction kind: ${String(input.extractionKind)}`,
{ extraction_kind: input.extractionKind }
);
}
if (typeof input.text !== "string" || input.text.trim().length === 0) {
throw new RagIngestError("empty_extracted_text", "Extracted text is empty");
}
if (input.text.includes("\uFFFD")) {
throw new RagIngestError("text_not_utf8", "Extracted text is not valid UTF-8");
}
const extension = extname(input.sourcePath).toLowerCase();
if (!SUPPORTED_VISION_EXTENSIONS.includes(extension)) {
throw new RagIngestError(
"unsupported_file_type",
`Unsupported file type for vision ingest: ${extension || "(none)"}`,
{ source_path: input.sourcePath }
);
}
const resolvedRoot = resolve(this.projectRoot);
const resolvedPath = resolve(this.projectRoot, input.sourcePath);
if (!resolvedPath.startsWith(resolvedRoot + sep2)) {
throw new RagIngestError(
"path_outside_project",
`Source path resolves outside the project root: ${input.sourcePath}`,
{ source_path: input.sourcePath }
);
}
const profile = readProjectProfile(this.projectRoot);
const intelligence = normalizeIntelligenceConfig(profile?.intelligence);
this.refuseRemovedProvider(intelligence);
if (!intelligence.rag_enabled || !intelligence.embedding_provider) {
throw new Error("RAG must be enabled and configured before ingesting extracted text");
}
const sourceMissing = !existsSync6(resolvedPath);
const chunkTexts = this.splitTextIntoChunks(input.text, VISION_MAX_CHUNK_CHARS);
const chunks = chunkTexts.map((raw, index) => {
const content = raw.trim();
return {
id: createHash6("sha256").update(`${input.sourcePath}:${input.extractionKind}:${index}`).digest("hex"),
source_file: input.sourcePath,
ast_node_type: "fallback",
ast_node_path: "vision-extracted",
exported_symbols: [],
content,
char_count: content.replace(/\s/g, "").length,
content_hash: createHash6("sha256").update(content).digest("hex")
};
});
const provider = await this.providerFactory(this.projectRoot, intelligence);
const vectors = await provider.embed(chunks.map((chunk) => chunk.content));
const embedded = chunks.map((chunk, index) => ({
...chunk,
extraction_kind: input.extractionKind,
source_missing: sourceMissing,
vector: vectors[index]
}));
const current = await this.visionVectorIndex.load(this.projectRoot);
const retained = (current?.items ?? []).filter((item) => item.source_file !== input.sourcePath);
await this.visionVectorIndex.replaceAll(this.projectRoot, [...retained, ...embedded], {
provider: provider.name,
model: provider.model
});
appendRagAudit(this.projectRoot, "INFO", "rag-vision-ingested", {
source_path: input.sourcePath,
extraction_kind: input.extractionKind,
chunk_count: embedded.length,
source_missing: sourceMissing
});
return {
chunkCount: embedded.length,
sourcePath: input.sourcePath,
extractionKind: input.extractionKind
};
}
resolveApiKeyName(provider) {
return REMOTE_API_KEY_NAMES[provider];
}
storeApiKey(provider, value) {
return writeProjectSecret(this.projectRoot, this.resolveApiKeyName(provider), value);
}
hasApiKey(provider) {
return Boolean(getProjectSecret(this.projectRoot, this.resolveApiKeyName(provider)));
}
refuseRemovedProvider(intelligence) {
if (intelligence.rag_enabled) {
assertEmbeddingProviderAvailable(this.projectRoot, intelligence.embedding_provider);
}
}
localModelPath() {
return localModelsRoot();
}
localModelCached(model) {
try {
const path2 = model ? localModelDir(model) : this.localModelPath();
return statSync(path2).isDirectory();
} catch {
return false;
}
}
async writeChunks(chunks, collectionId) {
await FileVectorIndex.create(this.projectRoot, collectionId);
const intelligence = normalizeIntelligenceConfig(
readProjectProfile(this.projectRoot)?.intelligence
);
const provider = await this.providerFactory(this.projectRoot, intelligence);
if (this.crsBacklog.size > 0) {
await this.crsBacklog.drain(
(id, queued) => this.persistCrsChunks(provider, id, queued).then(() => void 0)
);
}
try {
return await this.persistCrsChunks(provider, collectionId, chunks);
} catch (error) {
this.crsBacklog.enqueue(chunks, collectionId);
throw error;
}
}
async retrieveCrs(query, collectionId, topK, confidenceThreshold = 0.5) {
const intelligence = normalizeIntelligenceConfig(
readProjectProfile(this.projectRoot)?.intelligence
);
const provider = await this.providerFactory(this.projectRoot, intelligence);
const [queryVector] = await provider.embed(query);
const { indexPath, metaPath } = crsCollectionPaths(collectionId);
const index = new FileVectorIndex(indexPath, metaPath);
const results = await index.query(this.projectRoot, queryVector, topK);
return results.filter((result) => result.score >= confidenceThreshold).map((result) => ({
chunk: result.item,
sourceSessionId: result.item.source_session_id,
sourceWorkspaceId: result.item.source_workspace_id,
score: result.score
}));
}
async reindex(provider, model, collectionId, onProgress) {
const layout = crsCollectionLayout(this.projectRoot, collectionId);
await this.sweepExpiredCrsReverts(collectionId);
const live = new FileVectorIndex(layout.indexPath, layout.metaPath);
const existing = await live.load(this.projectRoot);
const items = existing?.items ?? [];
const relRebuildDir = join11(PATHS.CRS_DIR, `${layout.escaped}.rebuilding`);
const absRebuildDir = join11(this.projectRoot, relRebuildDir);
await rm3(absRebuildDir, { recursive: true, force: true });
await mkdir4(absRebuildDir, { recursive: true });
const intelligence = normalizeIntelligenceConfig({
embedding_provider: provider,
embedding_model: model,
rag_enabled: true
});
const newProvider = await this.providerFactory(this.projectRoot, intelligence);
const rebuilt = await this.embedCrsForReindex(newProvider, items, collectionId, onProgress);
const rebuildIndex = new FileVectorIndex(
join11(relRebuildDir, "index.json"),
join11(relRebuildDir, "meta.json")
);
await rebuildIndex.replaceAll(this.projectRoot, rebuilt, {
provider: newProvider.name,
model: newProvider.model
});
if (existsSync6(layout.absDir)) {
const revertDir = join11(layout.crsRootAbs, `${layout.escaped}.revert.${Date.now()}`);
await rename2(layout.absDir, revertDir);
}
await rename2(absRebuildDir, layout.absDir);
appendRagAudit(this.projectRoot, "INFO", "crs-reindexed", {
collection_id: String(collectionId),
provider: newProvider.name,
model: newProvider.model,
chunks: rebuilt.length
});
}
async persistCrsChunks(provider, collectionId, chunks) {
const { indexPath, metaPath } = crsCollectionPaths(collectionId);
const index = new FileVectorIndex(indexPath, metaPath);
const embedded = await this.embedCrsChunks(provider, chunks);
const existing = await index.load(this.projectRoot);
const merged = [...existing?.items ?? [], ...embedded];
await index.replaceAll(this.projectRoot, merged, {
provider: provider.name,
model: provider.model
});
const event = {
session_id: chunks[0]?.source_session_id ?? "",
project_id: chunks[0]?.project_id ?? "",
chunk_count: embedded.length
};
appendRagAudit(this.projectRoot, "INFO", "crs.indexed_session", {
session_id: event.session_id,
project_id: event.project_id,
chunk_count: event.chunk_count
});
return event;
}
async embedCrsChunks(provider, chunks) {
if (chunks.length === 0) {
return [];
}
const out = [];
for (let offset = 0; offset < chunks.length; offset += CRS_EMBED_BATCH_SIZE) {
const batch = chunks.slice(offset, offset + CRS_EMBED_BATCH_SIZE);
const vectors = await provider.embed(batch.map((chunk) => chunk.content));
const stampedAt = (/* @__PURE__ */ new Date()).toISOString();
for (let index = 0; index < batch.length; index++) {
const chunk = batch[index];
out.push({
id: chunk.id,
vector: vectors[index],
content: chunk.content,
source_session_id: chunk.source_session_id,
source_workspace_id: chunk.source_workspace_id,
created_at: chunk.created_at,
project_id: chunk.project_id,
vector_timestamp: stampedAt
});
}
}
return out;
}
async embedCrsForReindex(provider, items, collectionId, onProgress) {
const total = items.length;
const currentCollection = String(collectionId);
if (total === 0) {
onProgress?.({ status_percent: 100, current_collection: currentCollection, est_time: 0 });
return [];
}
const out = [];
const start = Date.now();
for (let offset = 0; offset < total; offset += CRS_EMBED_BATCH_SIZE) {
const batch = items.slice(offset, offset + CRS_EMBED_BATCH_SIZE);
const vectors = await provider.embed(batch.map((item) => item.content));
const stampedAt = (/* @__PURE__ */ new Date()).toISOString();
for (let index = 0; index < batch.length; index++) {
out.push({ ...batch[index], vector: vectors[index], vector_timestamp: stampedAt });
}
const done = Math.min(offset + batch.length, total);
onProgress?.({
status_percent: Math.round(done / total * 100),
current_collection: currentCollection,
est_time: estimateEtaSeconds(start, done, total)
});
}
return out;
}
async sweepExpiredCrsReverts(collectionId) {
const { escaped, crsRootAbs } = crsCollectionLayout(this.projectRoot, collectionId);
let entries;
try {
entries = await readdir(crsRootAbs);
} catch {
return;
}
const prefix2 = `${escaped}.revert.`;
const now = Date.now();
for (const entry of entries) {
if (!entry.startsWith(prefix2)) {
continue;
}
const stampedAt = Number(entry.slice(prefix2.length));
if (!Number.isFinite(stampedAt)) {
continue;
}
if (now - stampedAt > CRS_REVERT_RETENTION_MS) {
await rm3(join11(crsRootAbs, entry), { recursive: true, force: true });
}
}
}
async applyReranking(query, hits, config) {
if (!config?.enabled || hits.length <= 1) {
return [...hits];
}
try {
const reranker = createReranker(config);
const { post_rerank_ids } = await reranker.rerank(
query,
hits.map((hit) => hit.item),
config.candidate_pool_size
);
return reorderByRankedIds(hits, (hit) => hit.item.id, post_rerank_ids);
} catch (error) {
appendRagAudit(this.projectRoot, "WARN", "rag-rerank-fallback", {
reason: error instanceof Error ? error.message : "unknown-error"
});
return [...hits];
}
}
async syncVectorIndex(syncResult, intelligence = normalizeIntelligenceConfig(readProjectProfile(this.projectRoot)?.intelligence)) {
const hasChanges = syncResult.changed_files.length > 0 || syncResult.added_files.length > 0 || syncResult.deleted_files.length > 0;
if (!hasChanges || !intelligence.embedding_provider) {
return;
}
const current = await this.vectorIndex.load(this.projectRoot);
const meta = await this.vectorIndex.loadMeta(this.projectRoot);
if (!current || !meta) {
return;
}
const provider = await this.providerFactory(this.projectRoot, intelligence);
const changedSources = /* @__PURE__ */ new Set([...syncResult.changed_files, ...syncResult.added_files]);
const unchanged = current.items.filter(
(item) => !changedSources.has(item.source_file) && !syncResult.deleted_files.includes(item.source_file)
);
const changedChunks = syncResult.index.entries.filter((entry) => changedSources.has(entry.source_file)).flatMap((entry) => entry.chunks);
const embedded = await this.embedChunks(provider, changedChunks, {
embedTextOf: this.buildEmbedContextualizer()
});
await this.vectorIndex.replaceAll(
this.projectRoot,
[...unchanged, ...embedded],
{
provider: provider.name,
model: provider.model,
chunker_version: CHUNKER_VERSION
},
intelligence.rag_base_branch
);
appendRagAudit(this.projectRoot, "INFO", "rag-incremental-update", {
changed_files: [...changedSources].length,
deleted_files: syncResult.deleted_files.length,
chunks: unchanged.length + embedded.length
});
}
splitTextIntoChunks(text, maxChunkChars) {
const paragraphs = text.split(/\n\s*\n/);
const chunks = [];
let buffer = "";
for (const para of paragraphs) {
const combined = buffer ? `${buffer}
${para}` : para;
const nonWhitespace = combined.replace(/\s/g, "").length;
if (nonWhitespace > maxChunkChars && buffer) {
chunks.push(buffer);
buffer = para;
} else {
buffer = combined;
}
}
if (buffer.trim()) {
chunks.push(buffer);
}
return chunks.length > 0 ? chunks : [text];
}
throwIfAborted(signal) {
if (signal?.aborted) {
throw new CancelledError("RAG rebuild cancelled by consumer");
}
}
buildEmbedContextualizer() {
const roleOf = buildModuleRoleResolver(this.projectRoot);
return (chunk) => contextualizeChunkText(chunk, { moduleRole: roleOf(chunk.source_file) });
}
async embedChunks(provider, chunks, options) {
if (chunks.length === 0) {
return [];
}
const onProgress = options.onProgress;
const signal = options.signal;
const embedTextOf = options.embedTextOf;
const cache2 = EmbeddingCache.load(this.projectRoot, provider.model);
const missIndices = [];
for (let index = 0; index < chunks.length; index++) {
if (!cache2.has(embedTextOf(chunks[index]))) {
missIndices.push(index);
}
}
const assemble = () => chunks.map((chunk) => {
const vector = cache2.get(embedTextOf(chunk));
return vector ? { ...chunk, vector } : null;
}).filter((item) => item !== null);
const cachedCount = chunks.length - missIndices.length;
const batchSize = 32;
const start = Date.now();
for (let offset = 0; offset < missIndices.length; offset += batchSize) {
if (signal?.aborted) {
await cache2.flush();
throw new CancelledError("RAG rebuild cancelled by consumer", {
partialChunks: assemble()
});
}
const batchIndices = missIndices.slice(offset, offset + batchSize);
const vectors = await provider.embed(batchIndices.map((index) => embedTextOf(chunks[index])));
for (let position = 0; position < batchIndices.length; position++) {
cache2.set(embedTextOf(chunks[batchIndices[position]]), vectors[position]);
}
const embeddedMisses = Math.min(offset + batchIndices.length, missIndices.length);
const loaded = cachedCount + embeddedMisses;
onProgress?.({
phase: "build",
message: `Embedded ${loaded}/${chunks.length} chunks with ${provider.model} (${cachedCount} cached, ETA ${estimateEtaSeconds(
start,
embeddedMisses,
missIndices.length
)}s)`,
loaded,
total: chunks.length,
percent: Math.round(loaded / chunks.length * 100)
});
}
if (missIndices.length === 0) {
onProgress?.({
phase: "build",
message: `Embedded ${chunks.length}/${chunks.length} chunks with ${provider.model} (all cached)`,
loaded: chunks.length,
total: chunks.length,
percent: 100
});
}
await cache2.flush();
return assemble();
}
async discoverSourceFiles(onProgress) {
const profile = readProjectProfile(this.projectRoot);
const frameworks = profile?.stack_profile?.frameworks ?? [];
const packs = getPacksForFrameworks(frameworks, this.projectRoot);
const intelligence = normalizeIntelligenceConfig(profile?.intelligence);
const filter2 = new RagFileFilter({
projectRoot: this.projectRoot,
packs,
intelligence
});
return filter2.discoverFiles(onProgress);
}
async validateResumeState() {
if (this.resumeValidationPromise) {
return this.resumeValidationPromise;
}
this.resumeValidationPromise = (async () => {
try {
const validation = await this.resumeValidator.validate(this.projectRoot);
if (!validation.warning) {
return;
}
appendRagAudit(this.projectRoot, "WARN", "rag-resume-warning", {
provider: validation.embedding_provider ?? "unknown",
reason: validation.warning
});
engineLog("warn", validation.warning);
} catch {
}
})();
return this.resumeValidationPromise;
}
};
function flattenChunks(index) {
return index.entries.flatMap((entry) => entry.chunks);
}
function lexicalDocumentText(item) {
if (!item.source_file) {
return item.content;
}
return contextualizeChunkText({
source_file: item.source_file,
ast_node_path: item.ast_node_path ?? "",
exported_symbols: item.exported_symbols ?? [],
content: item.content
});
}
function fuseHybridRanking(dense, queryText) {
if (dense.length <= 1) {
return [...dense];
}
const lexical = new Bm25Index(
dense.map((hit) => ({
id: hit.item.id,
content: lexicalDocumentText(hit.item)
}))
).search(queryText).map((hit) => hit.id);
if (lexical.length === 0) {
return [...dense];
}
const denseOrder = dense.map((hit) => hit.item.id);
const byId = new Map(dense.map((hit) => [hit.item.id, hit]));
const fused = reciprocalRankFusion([denseOrder, lexical]);
return fused.map((entry) => byId.get(entry.id)).filter((hit) => hit !== void 0);
}
export {
CHUNKER_VERSION,
AstChunker,
castMerge,
ChunkIndexManager,
buildModuleRoleResolver,
createReranker,
cosineSimilarity,
REMOVED_EMBEDDING_PROVIDER_MESSAGE,
findRemovedEmbeddingProvider,
resolveRemovedEmbeddingProvider,
assertEmbeddingProviderAvailable,
EmbeddingProviderError,
isEmbeddingProviderError,
SUPPORTED_EXTRACTION_KINDS,
SUPPORTED_VISION_EXTENSIONS,
RagIngestError,
isRagIngestError,
toCrsCollectionId,
EmbeddingBacklogOverflow,
isEmbeddingBacklogOverflow,
localModelsRoot,
localModelDir,
LocalEmbeddingProvider,
OpenAiEmbeddingProvider,
enableEmbeddingProviderCache,
resetEmbeddingProviderCache,
createEmbeddingProvider,
escapeCollectionId,
crsCollectionDir,
crsCollectionPaths,
crsCollectionLayout,
CorruptVectorIndexError,
FileVectorIndex,
PatternStore,
PatternSuggester,
getGlobalPatternVectorPaths,
PatternVectorService,
suggestPatternsForProject,
openRagConversation,
recordRagEvidence,
appendRagAudit,
DEFAULT_CRS_BACKLOG_CAP,
CrsBacklogQueue,
RagFileFilter,
compileProjectGlob,
RagService,
lexicalDocumentText
};