autosnippet 3.3.8 → 3.3.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dashboard/dist/assets/{index-DV8biUkH.js → index-DEU4tJtP.js} +3 -3
- package/dashboard/dist/index.html +1 -1
- package/dist/lib/bootstrap.js +6 -1
- package/dist/lib/cli/SetupService.js +5 -4
- package/dist/lib/external/mcp/McpServer.js +19 -2
- package/dist/lib/external/mcp/handlers/bootstrap/pipeline/mock-pipeline.js +10 -29
- package/dist/lib/infrastructure/database/DatabaseConnection.js +7 -6
- package/dist/lib/service/delivery/CursorDeliveryPipeline.js +15 -1
- package/dist/lib/shared/PathGuard.js +16 -10
- package/dist/lib/shared/isOwnDevRepo.d.ts +29 -4
- package/dist/lib/shared/isOwnDevRepo.js +64 -4
- package/package.json +1 -1
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
|
6
6
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
7
7
|
<title>AutoSnippet Dashboard</title>
|
|
8
|
-
<script type="module" crossorigin src="/assets/index-
|
|
8
|
+
<script type="module" crossorigin src="/assets/index-DEU4tJtP.js"></script>
|
|
9
9
|
<link rel="modulepreload" crossorigin href="/assets/yaml-qRaU8Ldn.js">
|
|
10
10
|
<link rel="modulepreload" crossorigin href="/assets/vendor-BZEJEVBn.js">
|
|
11
11
|
<link rel="modulepreload" crossorigin href="/assets/icons-BMNb0V6L.js">
|
package/dist/lib/bootstrap.js
CHANGED
|
@@ -44,7 +44,12 @@ export class Bootstrap {
|
|
|
44
44
|
// 0.5 确保 PathGuard 已配置(如果调用方未提前配置)
|
|
45
45
|
// MCP 服务器会在 initialize() 之前配置,但 CLI/测试可能跳过
|
|
46
46
|
if (!pathGuard.configured) {
|
|
47
|
-
const
|
|
47
|
+
const isMcpMode = process.env.ASD_MCP_MODE === '1';
|
|
48
|
+
const projectRoot = process.env.ASD_PROJECT_DIR || (isMcpMode ? undefined : process.cwd());
|
|
49
|
+
if (!projectRoot) {
|
|
50
|
+
throw new Error('[Bootstrap] MCP 模式下缺少 ASD_PROJECT_DIR 环境变量,' +
|
|
51
|
+
'且 PathGuard 未提前配置。请在 .vscode/mcp.json 中设置 ASD_PROJECT_DIR。');
|
|
52
|
+
}
|
|
48
53
|
Bootstrap.configurePathGuard(projectRoot);
|
|
49
54
|
}
|
|
50
55
|
// 1. 加载配置
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
import { execSync } from 'node:child_process';
|
|
47
47
|
import { copyFileSync, cpSync, existsSync, mkdirSync, readdirSync, renameSync, rmdirSync, rmSync, writeFileSync, } from 'node:fs';
|
|
48
48
|
import { join, resolve } from 'node:path';
|
|
49
|
-
import {
|
|
49
|
+
import { isExcludedProject } from '../shared/isOwnDevRepo.js';
|
|
50
50
|
import { DEFAULT_KNOWLEDGE_BASE_DIR, DEFAULT_SUB_REPO_DIR, isGitRepo, } from '../shared/ProjectMarkers.js';
|
|
51
51
|
import { PACKAGE_ROOT } from '../shared/package-root.js';
|
|
52
52
|
import { FileDeployer } from './deploy/FileDeployer.js';
|
|
@@ -81,9 +81,10 @@ export class SetupService {
|
|
|
81
81
|
this.seed = options.seed || false;
|
|
82
82
|
this.subRepoDir = options.subRepoDir || DEFAULT_SUB_REPO_DIR;
|
|
83
83
|
this.subRepoUrl = options.subRepoUrl;
|
|
84
|
-
// ──
|
|
85
|
-
|
|
86
|
-
|
|
84
|
+
// ── 排除项目保护 ──────────────────────────────────
|
|
85
|
+
const exclusion = isExcludedProject(this.projectRoot);
|
|
86
|
+
if (exclusion.excluded) {
|
|
87
|
+
throw new Error(`[SetupService] 检测到当前目录是排除项目(${exclusion.reason}),` +
|
|
87
88
|
'拒绝执行 setup 以避免创建 .autosnippet/ 和 AutoSnippet/ 运行时数据。' +
|
|
88
89
|
'\n提示: 请在用户项目目录中运行 asd setup。');
|
|
89
90
|
}
|
|
@@ -83,8 +83,25 @@ export class McpServer {
|
|
|
83
83
|
async initialize() {
|
|
84
84
|
if (!this.container) {
|
|
85
85
|
const { default: Bootstrap } = await import('../../bootstrap.js');
|
|
86
|
-
//
|
|
87
|
-
const projectRoot = process.env.ASD_PROJECT_DIR
|
|
86
|
+
// MCP 模式必须显式指定项目目录 — process.cwd() 在多根工作区中不可靠
|
|
87
|
+
const projectRoot = process.env.ASD_PROJECT_DIR;
|
|
88
|
+
if (!projectRoot) {
|
|
89
|
+
const msg = `[MCP] 缺少 ASD_PROJECT_DIR 环境变量。MCP server 拒绝启动。\n` +
|
|
90
|
+
`在多根工作区中 process.cwd() 可能指向任意子目录,不能作为项目根目录。\n` +
|
|
91
|
+
`请在 .vscode/mcp.json 的 env 中设置 ASD_PROJECT_DIR 为目标项目的绝对路径。`;
|
|
92
|
+
process.stderr.write(`${msg}\n`);
|
|
93
|
+
throw new Error(msg);
|
|
94
|
+
}
|
|
95
|
+
// ── 排除项目检查 — 防止误配置 ASD_PROJECT_DIR 到不该创建运行时数据的目录 ──
|
|
96
|
+
const { isExcludedProject } = await import('../../shared/isOwnDevRepo.js');
|
|
97
|
+
const exclusion = isExcludedProject(projectRoot);
|
|
98
|
+
if (exclusion.excluded) {
|
|
99
|
+
const msg = `[MCP] projectRoot "${projectRoot}" 是排除项目(${exclusion.reason}),` +
|
|
100
|
+
`MCP server 拒绝在此目录创建运行时数据。\n` +
|
|
101
|
+
`提示: 在 .vscode/mcp.json 的 env 中设置正确的 ASD_PROJECT_DIR。`;
|
|
102
|
+
process.stderr.write(`${msg}\n`);
|
|
103
|
+
throw new Error(msg);
|
|
104
|
+
}
|
|
88
105
|
// 切换工作目录到项目根 — 确保 DB 等相对路径正确解析
|
|
89
106
|
if (projectRoot !== process.cwd()) {
|
|
90
107
|
process.chdir(projectRoot);
|
|
@@ -374,8 +374,7 @@ export async function fillDimensionsMock(view, dimensions) {
|
|
|
374
374
|
message: '🧪 Mock AI 模式 — 基于代码结构自动生成知识候选(非 AI 深度分析)',
|
|
375
375
|
mockMode: true,
|
|
376
376
|
});
|
|
377
|
-
|
|
378
|
-
let totalCreated = 0;
|
|
377
|
+
let totalGenerated = 0;
|
|
379
378
|
for (const dim of dimensions) {
|
|
380
379
|
const dimStartTime = Date.now();
|
|
381
380
|
const dimLabel = dim.label ?? dim.id;
|
|
@@ -385,48 +384,30 @@ export async function fillDimensionsMock(view, dimensions) {
|
|
|
385
384
|
mockMode: true,
|
|
386
385
|
});
|
|
387
386
|
logger.info(`[MockPipeline] ── Dimension "${dim.id}" (${dimLabel}) ──`);
|
|
388
|
-
//
|
|
387
|
+
// 生成候选(仅在内存中,不写入数据库)
|
|
389
388
|
const candidates = generateDimensionCandidates(dim.id, dimLabel, allFiles, primaryLang, projectName, astStats, repFiles);
|
|
390
|
-
|
|
389
|
+
totalGenerated += candidates.length;
|
|
391
390
|
for (const candidate of candidates) {
|
|
392
|
-
|
|
393
|
-
const entry = await knowledgeService.create({
|
|
394
|
-
...candidate,
|
|
395
|
-
source: 'mock-bootstrap',
|
|
396
|
-
}, { userId: 'mock-ai' });
|
|
397
|
-
createdIds.push(entry.id);
|
|
398
|
-
totalCreated++;
|
|
399
|
-
// 尝试自动评分(非关键路径)
|
|
400
|
-
try {
|
|
401
|
-
await knowledgeService.updateQuality?.(entry.id, { userId: 'mock-ai' });
|
|
402
|
-
}
|
|
403
|
-
catch {
|
|
404
|
-
/* best effort */
|
|
405
|
-
}
|
|
406
|
-
logger.info(`[MockPipeline] ✅ Created: "${candidate.title}" → ${entry.id}`);
|
|
407
|
-
}
|
|
408
|
-
catch (err) {
|
|
409
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
410
|
-
logger.warn(`[MockPipeline] ⚠️ Failed to create "${candidate.title}": ${msg}`);
|
|
411
|
-
}
|
|
391
|
+
logger.info(`[MockPipeline] 📝 Generated (not persisted): "${candidate.title}"`);
|
|
412
392
|
}
|
|
413
393
|
const durationMs = Date.now() - dimStartTime;
|
|
414
394
|
emitter.emitDimensionComplete(dim.id, {
|
|
415
395
|
type: 'candidate',
|
|
416
396
|
extracted: candidates.length,
|
|
417
|
-
created:
|
|
397
|
+
created: 0,
|
|
418
398
|
status: 'mock-pipeline-complete',
|
|
419
399
|
degraded: false,
|
|
420
400
|
durationMs,
|
|
421
401
|
toolCallCount: 0,
|
|
422
402
|
source: 'mock-pipeline',
|
|
423
403
|
});
|
|
424
|
-
logger.info(`[MockPipeline] ✅ "${dim.id}": ${
|
|
404
|
+
logger.info(`[MockPipeline] ✅ "${dim.id}": ${candidates.length} candidates generated (mock-only, not persisted), ${durationMs}ms`);
|
|
425
405
|
}
|
|
426
406
|
emitter.emitProgress('bootstrap:mock-complete', {
|
|
427
|
-
message: `🧪 Mock Bootstrap 完成: ${
|
|
428
|
-
totalCreated,
|
|
407
|
+
message: `🧪 Mock Bootstrap 完成: ${totalGenerated} 个候选知识已生成(仅预览,未写入数据库)`,
|
|
408
|
+
totalCreated: 0,
|
|
409
|
+
totalGenerated,
|
|
429
410
|
mockMode: true,
|
|
430
411
|
});
|
|
431
|
-
logger.info(`[MockPipeline] ═══ Mock bootstrap complete — ${
|
|
412
|
+
logger.info(`[MockPipeline] ═══ Mock bootstrap complete — ${totalGenerated} candidates generated (not persisted) from ${dimensions.length} dimensions`);
|
|
432
413
|
}
|
|
@@ -2,7 +2,7 @@ import fs from 'node:fs';
|
|
|
2
2
|
import os from 'node:os';
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
import Database from 'better-sqlite3';
|
|
5
|
-
import {
|
|
5
|
+
import { isExcludedProject } from '../../shared/isOwnDevRepo.js';
|
|
6
6
|
import pathGuard from '../../shared/PathGuard.js';
|
|
7
7
|
import { initDrizzle } from './drizzle/index.js';
|
|
8
8
|
const __dirname = import.meta.dirname;
|
|
@@ -30,17 +30,18 @@ export class DatabaseConnection {
|
|
|
30
30
|
let resolvedDbPath = projectRoot && !path.isAbsolute(dbPath)
|
|
31
31
|
? path.resolve(projectRoot, dbPath)
|
|
32
32
|
: path.resolve(dbPath);
|
|
33
|
-
// ──
|
|
34
|
-
// 检测 DB
|
|
35
|
-
//
|
|
33
|
+
// ── 排除项目保护 ──────────────────────────────────────────
|
|
34
|
+
// 检测 DB 即将落地到不适合创建知识库的项目 → 重定向到临时目录
|
|
35
|
+
// 包括:AutoSnippet 源码仓库、生态项目(autosnippet-book 等)、.autosnippet-skip 标记项目
|
|
36
36
|
const effectiveRoot = projectRoot || path.resolve('.');
|
|
37
|
-
|
|
37
|
+
const exclusion = isExcludedProject(effectiveRoot);
|
|
38
|
+
if (exclusion.excluded) {
|
|
38
39
|
const devDbDir = path.join(os.tmpdir(), 'autosnippet-dev');
|
|
39
40
|
if (!fs.existsSync(devDbDir)) {
|
|
40
41
|
fs.mkdirSync(devDbDir, { recursive: true });
|
|
41
42
|
}
|
|
42
43
|
resolvedDbPath = path.join(devDbDir, 'autosnippet.db');
|
|
43
|
-
process.stderr.write(`[AutoSnippet]
|
|
44
|
+
process.stderr.write(`[AutoSnippet] Excluded project detected (${exclusion.reason}) — DB redirected to ${resolvedDbPath}\n`);
|
|
44
45
|
}
|
|
45
46
|
else {
|
|
46
47
|
// 路径安全检查 — 防止 DB 文件创建到项目允许范围外
|
|
@@ -170,7 +170,21 @@ export class CursorDeliveryPipeline {
|
|
|
170
170
|
catch (e) {
|
|
171
171
|
this.logger.warn?.(`[CursorDelivery] Failed to load pending entries: ${e.message}`);
|
|
172
172
|
}
|
|
173
|
-
|
|
173
|
+
// 过滤掉历史遗留的 mock 条目(source=mock-bootstrap/mock-pipeline 或 createdBy=mock-ai)
|
|
174
|
+
const MOCK_SOURCES = new Set(['mock-bootstrap', 'mock-pipeline']);
|
|
175
|
+
const filtered = allEntries.filter((e) => {
|
|
176
|
+
if (MOCK_SOURCES.has(e.source)) {
|
|
177
|
+
return false;
|
|
178
|
+
}
|
|
179
|
+
if (e.createdBy === 'mock-ai') {
|
|
180
|
+
return false;
|
|
181
|
+
}
|
|
182
|
+
return true;
|
|
183
|
+
});
|
|
184
|
+
if (filtered.length < allEntries.length) {
|
|
185
|
+
this.logger.info?.(`[CursorDelivery] Filtered out ${allEntries.length - filtered.length} mock entries`);
|
|
186
|
+
}
|
|
187
|
+
return filtered;
|
|
174
188
|
}
|
|
175
189
|
/**
|
|
176
190
|
* 从 KnowledgeService.list() 返回值提取条目数组
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
* - 错误不静默:越界写操作抛出 PathGuardError
|
|
27
27
|
*/
|
|
28
28
|
import path from 'node:path';
|
|
29
|
-
import { isAutoSnippetDevRepo } from './isOwnDevRepo.js';
|
|
29
|
+
import { isAutoSnippetDevRepo, isExcludedProject } from './isOwnDevRepo.js';
|
|
30
30
|
import { DEFAULT_KNOWLEDGE_BASE_DIR, detectKnowledgeBaseDir, } from './ProjectMarkers.js';
|
|
31
31
|
export class PathGuardError extends Error {
|
|
32
32
|
projectRoot;
|
|
@@ -74,6 +74,10 @@ class PathGuard {
|
|
|
74
74
|
#configured = false;
|
|
75
75
|
/** projectRoot 是否是 AutoSnippet 自身的开发仓库 */
|
|
76
76
|
#isDevRepo = false;
|
|
77
|
+
/** projectRoot 是否是应排除的项目(开发仓库、生态项目等) */
|
|
78
|
+
#isExcludedProject = false;
|
|
79
|
+
/** 排除原因 */
|
|
80
|
+
#excludeReason = '';
|
|
77
81
|
/**
|
|
78
82
|
* 配置 PathGuard(每个进程执行一次)
|
|
79
83
|
* @param opts.projectRoot 用户项目根目录(绝对路径)
|
|
@@ -89,6 +93,9 @@ class PathGuard {
|
|
|
89
93
|
this.#packageRoot = packageRoot ? path.resolve(packageRoot) : null;
|
|
90
94
|
this.#knowledgeBaseDir = knowledgeBaseDir || null; // 延迟解析
|
|
91
95
|
this.#isDevRepo = isAutoSnippetDevRepo(this.#projectRoot);
|
|
96
|
+
const exclusion = isExcludedProject(this.#projectRoot);
|
|
97
|
+
this.#isExcludedProject = exclusion.excluded;
|
|
98
|
+
this.#excludeReason = exclusion.reason;
|
|
92
99
|
// 默认白名单:全局缓存 + 平台 Snippets 目录
|
|
93
100
|
const HOME = process.env.HOME || process.env.USERPROFILE || '';
|
|
94
101
|
if (HOME) {
|
|
@@ -175,19 +182,18 @@ class PathGuard {
|
|
|
175
182
|
// 计算相对于 projectRoot 的路径
|
|
176
183
|
const relative = path.relative(this.#projectRoot, resolved);
|
|
177
184
|
const firstSegment = relative.split(path.sep)[0];
|
|
178
|
-
// ──
|
|
179
|
-
// 如果 projectRoot
|
|
180
|
-
// 禁止写入 .autosnippet/
|
|
181
|
-
|
|
182
|
-
if (this.#isDevRepo) {
|
|
185
|
+
// ── 排除项目保护 ──────────────────────────────────
|
|
186
|
+
// 如果 projectRoot 是排除项目(开发仓库、生态项目等),
|
|
187
|
+
// 禁止写入 .autosnippet/ 和知识库目录
|
|
188
|
+
if (this.#isExcludedProject) {
|
|
183
189
|
if (firstSegment === '.autosnippet') {
|
|
184
|
-
throw new PathGuardError(resolved, this.#projectRoot,
|
|
190
|
+
throw new PathGuardError(resolved, this.#projectRoot, `排除项目保护 (${this.#excludeReason}): 禁止创建 .autosnippet/ 运行时数据`);
|
|
185
191
|
}
|
|
186
192
|
const kbDir = this.#resolveKnowledgeBaseDir();
|
|
187
193
|
if (kbDir && firstSegment === kbDir) {
|
|
188
|
-
throw new PathGuardError(resolved, this.#projectRoot,
|
|
194
|
+
throw new PathGuardError(resolved, this.#projectRoot, `排除项目保护 (${this.#excludeReason}): 禁止创建 ${kbDir}/ 知识库数据`);
|
|
189
195
|
}
|
|
190
|
-
//
|
|
196
|
+
// 排除项目内仍允许写入 .cursor/.vscode/.github 等 IDE 配置
|
|
191
197
|
for (const prefix of PROJECT_WRITE_SCOPE_PREFIXES) {
|
|
192
198
|
if (prefix !== '.autosnippet' && firstSegment === prefix) {
|
|
193
199
|
return;
|
|
@@ -196,7 +202,7 @@ class PathGuard {
|
|
|
196
202
|
if (PROJECT_ROOT_WRITABLE_FILES.includes(relative)) {
|
|
197
203
|
return;
|
|
198
204
|
}
|
|
199
|
-
throw new PathGuardError(resolved, this.#projectRoot,
|
|
205
|
+
throw new PathGuardError(resolved, this.#projectRoot, `排除项目保护 (${this.#excludeReason}): "${relative}" 不在允许范围内`);
|
|
200
206
|
}
|
|
201
207
|
// 检查是否在允许的前缀中
|
|
202
208
|
for (const prefix of PROJECT_WRITE_SCOPE_PREFIXES) {
|
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* isOwnDevRepo —
|
|
2
|
+
* isOwnDevRepo — 检测 projectRoot 是否应排除 AutoSnippet 运行时数据创建
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* 三层保护:
|
|
5
|
+
* 1. isAutoSnippetDevRepo — AutoSnippet 自身源码仓库
|
|
6
|
+
* 2. isAutoSnippetEcosystemRepo — AutoSnippet 生态项目(autosnippet-book 等)
|
|
7
|
+
* 3. isExcludedProject — 综合判定:不适合创建知识库的项目
|
|
6
8
|
*
|
|
7
|
-
*
|
|
9
|
+
* 用于防止 MCP 服务器 / CLI 在不当目录创建 `.autosnippet/` 运行时数据。
|
|
10
|
+
*
|
|
11
|
+
* isAutoSnippetDevRepo 检测条件(三者同时满足):
|
|
8
12
|
* 1. projectRoot/package.json 的 name === 'autosnippet'
|
|
9
13
|
* 2. projectRoot/lib/bootstrap.ts 存在(源码标记)
|
|
10
14
|
* 3. projectRoot/SOUL.md 存在(项目灵魂文档)
|
|
@@ -14,5 +18,26 @@
|
|
|
14
18
|
* 结果按 dir 缓存,避免重复 IO
|
|
15
19
|
*/
|
|
16
20
|
export declare function isAutoSnippetDevRepo(dir: string): boolean;
|
|
21
|
+
/**
|
|
22
|
+
* 判断 dir 是否是 AutoSnippet 生态项目(不应创建运行时数据)
|
|
23
|
+
*
|
|
24
|
+
* 检测条件:package.json 的 name 以 'autosnippet-' 开头
|
|
25
|
+
* 例如 autosnippet-book、autosnippet-examples 等
|
|
26
|
+
*/
|
|
27
|
+
export declare function isAutoSnippetEcosystemRepo(dir: string): boolean;
|
|
28
|
+
/**
|
|
29
|
+
* 综合判定:项目是否应排除创建 .autosnippet/ 运行时数据
|
|
30
|
+
*
|
|
31
|
+
* 当前排除:
|
|
32
|
+
* 1. AutoSnippet 源码仓库本身
|
|
33
|
+
* 2. AutoSnippet 生态项目(autosnippet-book 等)
|
|
34
|
+
* 3. 存在 .autosnippet-skip 标记文件的项目(用户手动排除)
|
|
35
|
+
*
|
|
36
|
+
* @returns { excluded: boolean; reason: string }
|
|
37
|
+
*/
|
|
38
|
+
export declare function isExcludedProject(dir: string): {
|
|
39
|
+
excluded: boolean;
|
|
40
|
+
reason: string;
|
|
41
|
+
};
|
|
17
42
|
/** 重置缓存(仅用于测试) */
|
|
18
43
|
export declare function _resetDevRepoCache(): void;
|
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* isOwnDevRepo —
|
|
2
|
+
* isOwnDevRepo — 检测 projectRoot 是否应排除 AutoSnippet 运行时数据创建
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* 三层保护:
|
|
5
|
+
* 1. isAutoSnippetDevRepo — AutoSnippet 自身源码仓库
|
|
6
|
+
* 2. isAutoSnippetEcosystemRepo — AutoSnippet 生态项目(autosnippet-book 等)
|
|
7
|
+
* 3. isExcludedProject — 综合判定:不适合创建知识库的项目
|
|
6
8
|
*
|
|
7
|
-
*
|
|
9
|
+
* 用于防止 MCP 服务器 / CLI 在不当目录创建 `.autosnippet/` 运行时数据。
|
|
10
|
+
*
|
|
11
|
+
* isAutoSnippetDevRepo 检测条件(三者同时满足):
|
|
8
12
|
* 1. projectRoot/package.json 的 name === 'autosnippet'
|
|
9
13
|
* 2. projectRoot/lib/bootstrap.ts 存在(源码标记)
|
|
10
14
|
* 3. projectRoot/SOUL.md 存在(项目灵魂文档)
|
|
@@ -13,6 +17,8 @@ import fs from 'node:fs';
|
|
|
13
17
|
import path from 'node:path';
|
|
14
18
|
/** 多路径缓存(同一进程可能检测多个目录) */
|
|
15
19
|
const _cache = new Map();
|
|
20
|
+
/** 排除项目缓存 */
|
|
21
|
+
const _excludeCache = new Map();
|
|
16
22
|
/**
|
|
17
23
|
* 判断 dir 是否是 AutoSnippet 自身的源码开发仓库
|
|
18
24
|
* 结果按 dir 缓存,避免重复 IO
|
|
@@ -44,7 +50,61 @@ export function isAutoSnippetDevRepo(dir) {
|
|
|
44
50
|
_cache.set(resolved, result);
|
|
45
51
|
return result;
|
|
46
52
|
}
|
|
53
|
+
/**
|
|
54
|
+
* 判断 dir 是否是 AutoSnippet 生态项目(不应创建运行时数据)
|
|
55
|
+
*
|
|
56
|
+
* 检测条件:package.json 的 name 以 'autosnippet-' 开头
|
|
57
|
+
* 例如 autosnippet-book、autosnippet-examples 等
|
|
58
|
+
*/
|
|
59
|
+
export function isAutoSnippetEcosystemRepo(dir) {
|
|
60
|
+
const resolved = path.resolve(dir);
|
|
61
|
+
try {
|
|
62
|
+
const pkgPath = path.join(resolved, 'package.json');
|
|
63
|
+
if (fs.existsSync(pkgPath)) {
|
|
64
|
+
const raw = fs.readFileSync(pkgPath, 'utf-8');
|
|
65
|
+
const pkg = JSON.parse(raw);
|
|
66
|
+
return typeof pkg.name === 'string' && pkg.name.startsWith('autosnippet-');
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
// 读取失败 → 不是
|
|
71
|
+
}
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* 综合判定:项目是否应排除创建 .autosnippet/ 运行时数据
|
|
76
|
+
*
|
|
77
|
+
* 当前排除:
|
|
78
|
+
* 1. AutoSnippet 源码仓库本身
|
|
79
|
+
* 2. AutoSnippet 生态项目(autosnippet-book 等)
|
|
80
|
+
* 3. 存在 .autosnippet-skip 标记文件的项目(用户手动排除)
|
|
81
|
+
*
|
|
82
|
+
* @returns { excluded: boolean; reason: string }
|
|
83
|
+
*/
|
|
84
|
+
export function isExcludedProject(dir) {
|
|
85
|
+
const resolved = path.resolve(dir);
|
|
86
|
+
const cached = _excludeCache.get(resolved);
|
|
87
|
+
if (cached !== undefined) {
|
|
88
|
+
return cached;
|
|
89
|
+
}
|
|
90
|
+
let result;
|
|
91
|
+
if (isAutoSnippetDevRepo(resolved)) {
|
|
92
|
+
result = { excluded: true, reason: 'AutoSnippet 源码开发仓库' };
|
|
93
|
+
}
|
|
94
|
+
else if (isAutoSnippetEcosystemRepo(resolved)) {
|
|
95
|
+
result = { excluded: true, reason: 'AutoSnippet 生态项目' };
|
|
96
|
+
}
|
|
97
|
+
else if (fs.existsSync(path.join(resolved, '.autosnippet-skip'))) {
|
|
98
|
+
result = { excluded: true, reason: '项目包含 .autosnippet-skip 标记' };
|
|
99
|
+
}
|
|
100
|
+
else {
|
|
101
|
+
result = { excluded: false, reason: '' };
|
|
102
|
+
}
|
|
103
|
+
_excludeCache.set(resolved, result);
|
|
104
|
+
return result;
|
|
105
|
+
}
|
|
47
106
|
/** 重置缓存(仅用于测试) */
|
|
48
107
|
export function _resetDevRepoCache() {
|
|
49
108
|
_cache.clear();
|
|
109
|
+
_excludeCache.clear();
|
|
50
110
|
}
|