UNPKG

autosnippet

Version:

Extract code patterns into a knowledge base for AI coding assistants

140 lines (139 loc) 5.35 kB
/** * ProjectMarkers — 统一项目探测标准(核心库 & VSCode 扩展共用) * * 所有模块判断「当前目录是否是 AutoSnippet 项目」时,都应该使用此模块提供的常量和函数, * 避免各处硬编码不同的标记目录名导致探测标准不一致。 * * 探测优先级: * 1. 存在 AutoSnippet.boxspec.json 的一级子目录 → 知识库根目录 * 2. 存在 AutoSnippet/ 目录 → 默认知识库根目录 * 3. 存在 .autosnippet/ 目录 → 运行时目录(至少说明曾初始化过) * * 子仓库(Sub-Repo): * 子仓库指通过独立 git 管理的知识数据目录,默认为 `AutoSnippet/recipes/`。 * 可通过 `.autosnippet/config.json` 中的 `core.subRepoDir` 自定义。 * 支持形式:git submodule、git subtree、独立 git init、或无 git(跳过权限探测)。 */ import fs from 'node:fs'; import path from 'node:path'; // ─── 目录名常量 ────────────────────────────────────────────── /** 默认知识库顶级目录名 */ export const DEFAULT_KNOWLEDGE_BASE_DIR = 'AutoSnippet'; /** 默认子仓库相对路径(相对于 projectRoot) */ export const DEFAULT_SUB_REPO_DIR = 'AutoSnippet/recipes'; /** 运行时配置目录名 */ export const RUNTIME_DIR = '.autosnippet'; /** Boxspec 文件名 — 知识库目录标记 */ export const SPEC_FILENAME = 'AutoSnippet.boxspec.json'; /** * 项目标记目录列表(任一存在即视为 AutoSnippet 项目) * 顺序即优先级:AutoSnippet/ > .autosnippet/ */ export const PROJECT_MARKER_DIRS = [DEFAULT_KNOWLEDGE_BASE_DIR, RUNTIME_DIR]; // ─── 探测函数 ──────────────────────────────────────────────── /** * 判断一个目录是否是 AutoSnippet 项目 * 条件:存在 `AutoSnippet/` 或 `.autosnippet/` 子目录 */ export function isAutoSnippetProject(folderPath) { return PROJECT_MARKER_DIRS.some((dir) => fs.existsSync(path.join(folderPath, dir))); } /** * 探测知识库目录名 * 优先查找含 boxspec.json 的一级子目录,fallback 到默认值 'AutoSnippet' */ export function detectKnowledgeBaseDir(projectRoot) { try { const entries = fs.readdirSync(projectRoot, { withFileTypes: true }); for (const e of entries) { if (e.isDirectory() && !e.name.startsWith('.')) { if (fs.existsSync(path.join(projectRoot, e.name, SPEC_FILENAME))) { return e.name; } } } } catch { /* ignore */ } return DEFAULT_KNOWLEDGE_BASE_DIR; } /** * 从 `.autosnippet/config.json` 读取子仓库路径配置 * @returns 子仓库相对路径(相对于 projectRoot),如 'AutoSnippet/recipes' */ export function readSubRepoDirFromConfig(projectRoot) { try { const configPath = path.join(projectRoot, RUNTIME_DIR, 'config.json'); if (!fs.existsSync(configPath)) { return null; } const raw = fs.readFileSync(configPath, 'utf-8'); const config = JSON.parse(raw); return config.core?.subRepoDir || null; } catch { return null; } } /** * 从 `.autosnippet/config.json` 读取子仓库远程 URL 配置 * @returns 远程仓库 URL,如 'https://github.com/team/recipes.git';未配置则返回 null */ export function readSubRepoUrlFromConfig(projectRoot) { try { const configPath = path.join(projectRoot, RUNTIME_DIR, 'config.json'); if (!fs.existsSync(configPath)) { return null; } const raw = fs.readFileSync(configPath, 'utf-8'); const config = JSON.parse(raw); return config.core?.subRepoUrl || null; } catch { return null; } } /** * 解析子仓库的绝对路径 * * 优先级: * 1. 传入的 explicitPath 参数 * 2. `.autosnippet/config.json` 中 `core.subRepoDir` * 3. 默认 `AutoSnippet/recipes` * * @param projectRoot 项目根目录 * @param explicitPath 显式指定的子仓库路径(绝对或相对于 projectRoot) * @returns 子仓库绝对路径 */ export function resolveSubRepoPath(projectRoot, explicitPath) { if (explicitPath) { return path.isAbsolute(explicitPath) ? explicitPath : path.resolve(projectRoot, explicitPath); } const fromConfig = readSubRepoDirFromConfig(projectRoot); const relDir = fromConfig || DEFAULT_SUB_REPO_DIR; return path.resolve(projectRoot, relDir); } /** 检测路径是否为 git 仓库(含 submodule) */ export function isGitRepo(dirPath) { // 独立 git 仓库 if (fs.existsSync(path.join(dirPath, '.git'))) { return true; } // git submodule(.git 是一个文件而非目录) const gitPath = path.join(dirPath, '.git'); try { const stat = fs.statSync(gitPath); if (stat.isFile()) { return true; // submodule 的 .git 是一个指向 ../.git/modules/xxx 的文件 } } catch { /* not exist */ } // 父目录有 .gitmodules 且当前目录是其子 if (fs.existsSync(path.join(dirPath, '..', '.gitmodules'))) { return true; } return false; }