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.
@@ -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-DV8biUkH.js"></script>
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">
@@ -44,7 +44,12 @@ export class Bootstrap {
44
44
  // 0.5 确保 PathGuard 已配置(如果调用方未提前配置)
45
45
  // MCP 服务器会在 initialize() 之前配置,但 CLI/测试可能跳过
46
46
  if (!pathGuard.configured) {
47
- const projectRoot = process.env.ASD_PROJECT_DIR || process.cwd();
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 { isAutoSnippetDevRepo } from '../shared/isOwnDevRepo.js';
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
- if (isAutoSnippetDevRepo(this.projectRoot)) {
86
- throw new Error('[SetupService] 检测到当前目录是 AutoSnippet 源码开发仓库,' +
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 || process.cwd();
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
- const knowledgeService = ctx.container.get('knowledgeService');
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
- const createdIds = [];
389
+ totalGenerated += candidates.length;
391
390
  for (const candidate of candidates) {
392
- try {
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: createdIds.length,
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}": ${createdIds.length}/${candidates.length} candidates, ${durationMs}ms`);
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 完成: ${totalCreated} 个候选知识已生成`,
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 — ${totalCreated} candidates from ${dimensions.length} dimensions`);
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 { isAutoSnippetDevRepo } from '../../shared/isOwnDevRepo.js';
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 即将落地到 AutoSnippet 源码仓库内 → 重定向到临时目录
35
- // 同时检查 projectRoot(MCP 模式)和 resolvedDbPath 的父目录(测试/CLI 模式)
33
+ // ── 排除项目保护 ──────────────────────────────────────────
34
+ // 检测 DB 即将落地到不适合创建知识库的项目 → 重定向到临时目录
35
+ // 包括:AutoSnippet 源码仓库、生态项目(autosnippet-book 等)、.autosnippet-skip 标记项目
36
36
  const effectiveRoot = projectRoot || path.resolve('.');
37
- if (isAutoSnippetDevRepo(effectiveRoot)) {
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] Dev repo detected — DB redirected to ${resolvedDbPath}\n`);
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
- return allEntries;
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 是 AutoSnippet 自身源码仓库,
180
- // 禁止写入 .autosnippet/ 和知识库目录(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, 'Dev repo 保护: 禁止在 AutoSnippet 源码仓库内创建 .autosnippet/ 运行时数据');
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, `Dev repo 保护: 禁止在 AutoSnippet 源码仓库内创建 ${kbDir}/ 知识库数据`);
194
+ throw new PathGuardError(resolved, this.#projectRoot, `排除项目保护 (${this.#excludeReason}): 禁止创建 ${kbDir}/ 知识库数据`);
189
195
  }
190
- // 开发仓库内允许写入 .cursor/.vscode/.github 等 IDE 配置
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, `Dev repo 保护: "${relative}" 不在允许范围内`);
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 — 检测当前 projectRoot 是否是 AutoSnippet 自身的开发仓库
2
+ * isOwnDevRepo — 检测 projectRoot 是否应排除 AutoSnippet 运行时数据创建
3
3
  *
4
- * 用于防止 MCP 服务器 / CLI 在开发环境中把源码仓库当做用户项目,
5
- * 避免在开发仓库内创建 `.autosnippet/` 和 `AutoSnippet/candidates/` 等运行时数据。
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 — 检测当前 projectRoot 是否是 AutoSnippet 自身的开发仓库
2
+ * isOwnDevRepo — 检测 projectRoot 是否应排除 AutoSnippet 运行时数据创建
3
3
  *
4
- * 用于防止 MCP 服务器 / CLI 在开发环境中把源码仓库当做用户项目,
5
- * 避免在开发仓库内创建 `.autosnippet/` 和 `AutoSnippet/candidates/` 等运行时数据。
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
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "autosnippet",
3
- "version": "3.3.8",
3
+ "version": "3.3.9",
4
4
  "description": "Extract code patterns into a knowledge base for AI coding assistants",
5
5
  "type": "module",
6
6
  "main": "dist/lib/bootstrap.js",