add-coder 0.3.37 → 0.3.38

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/README.en.md CHANGED
@@ -301,8 +301,22 @@ npx add-coder init
301
301
  | Plan | Description |
302
302
  |------|-------------|
303
303
  | Demo Repo | A full example repository showcasing end-to-end closed-loop practice of Policy-Update-Loop and the Report system |
304
- | MCP Restructure | MCP toolchain architecture upgrade, improving audit and gateway tool extensibility and standalone deployment capability |
305
- | Memory Enhancement | ✅ **Delivered in v0.3.35**: idempotent evidence collection + deterministic waypoint recall + hybrid FTS×vector recall (RRF fusion + governed rerank) + Handoff Digest candidates + a ranking calibration foundation; defaults to `shadow` mode (recall runs and is audited, not injected yet) |
304
+ | ~~MCP Restructure~~ | ✅ v0.2.9 MCP toolchain architecture upgrade, improving audit and gateway tool extensibility and standalone deployment capability |
305
+ | ~~Memory Enhancement~~ | ✅ **v0.3.35→v0.3.37 Governance memory**: idempotent evidence collection + deterministic waypoint recall + hybrid FTS×vector recall (RRF fusion + governed rerank) + Handoff Digest candidates + a ranking calibration foundation; native layer (SQLite FTS5 tables + triggers) auto-applied by `init`/`db-ensure` with `memory:reindex` self-heal; defaults to `shadow` mode (recall runs and is audited, not injected yet) |
306
+
307
+ ---
308
+
309
+ ## 🙏 Acknowledgements
310
+
311
+ A paradigm is worth what people make of it — including the problems they find in it. The **contributor wall** lives in **[CONTRIBUTING.md §贡献者墙](./CONTRIBUTING.md#-贡献者墙)**; the item-by-item detail (in Chinese) lives in **[docs/ACKNOWLEDGEMENTS.md](./docs/ACKNOWLEDGEMENTS.md)**.
312
+
313
+ | Contributor | Contribution | What it left behind |
314
+ |-------------|--------------|---------------------|
315
+ | [@iopzhu](https://github.com/iopzhu) | Memory rotation on the `memory_cache` branch (`357b215`, 48 files / +5877 −33) | The retrieval and tooling backbone of the memory loop: `retrieval/pipeline`, RRF fusion, governed rerank, dual FTS channels, evidence/snapshot jobs, `tools/memory.ts` (609 lines) plus a 909-line recall evaluation set |
316
+ | [@Milkycoffees](https://github.com/Milkycoffees) | Ecosystem work: [`add-coder-flash`](https://www.npmjs.com/package/add-coder-flash) (npm `1.0.4`, 5 releases, plus a [public GitHub repo](https://github.com/Milkycoffees/add-coder-flash)) | A zero-dependency lightweight distribution: pure-`.mjs` hooks, no database required, assets laid down by `npm install`; auto-generates `.qoder-cn/mcp.json` and fed real constraints back (QoderCN config location, pnpm `allowBuilds`, MCP name length) |
317
+ | [@albertm88](https://github.com/albertm88) | Relentless issue reporting (#5–#7, #10–#20 — 14 issues, all closed) | Cross-session memory, 429 storms, Windows stability, native Codex adaptation, port drift, non-interactive hangs, first-run HITL approval… walking the edge cases so nobody else has to |
318
+
319
+ > Want on the wall? The criteria live in [CONTRIBUTING.md §贡献者墙](./CONTRIBUTING.md#-贡献者墙) — file an [issue](https://github.com/xiaomingming92/add-coder/issues) (reproducible problems, real logs, platform differences) or open a [PR](https://github.com/xiaomingming92/add-coder/pulls); both count as hard contribution.
306
320
 
307
321
  ---
308
322
  <details id="chinese-readme">
package/README.md CHANGED
@@ -499,10 +499,10 @@ Tasks (实验性) 双向 ✅ 已实现 长任务持久化
499
499
  | 计划 | 说明 |备注|
500
500
  |------|------|------|
501
501
  | Demo 仓库演示 | 提供完整示例仓库,展示 Policy-Update-Loop 与 Report 体系的端到端闭环实践 |--|
502
- | MCP 能力重构 | ✅ v0.2.9 MCP 工具链架构升级,提升审计与门禁工具的可扩展性和独立部署能力 | 2026-07/23/add-coder-mcp-restructure-plan-v1.md |
503
- | Hook 通知升级 | ✅ v0.2.9 Hook 拦截事件 jsonl → fs.watch → record_dev_operation 落库 + Notification + 治理信号 | 2026-07/24/add-coder-hook-notify-upgrade-plan-v1.md |
502
+ | ~~MCP 能力重构~~ | ✅ v0.2.9 MCP 工具链架构升级,提升审计与门禁工具的可扩展性和独立部署能力 | 2026-07/23/add-coder-mcp-restructure-plan-v1.md |
503
+ | ~~Hook 通知升级~~ | ✅ v0.2.9 Hook 拦截事件 jsonl → fs.watch → record_dev_operation 落库 + Notification + 治理信号 | 2026-07/24/add-coder-hook-notify-upgrade-plan-v1.md |
504
504
  | ide插件 | 解耦ADD范式代码和被治理项目的代码 |在做了,大家拭目以待吧,让编程更有趣,我的目标其实不在于IDE,我的工作顺手的事情 |
505
- | 对话记忆增强 | ✅ **v0.3.35 记忆闭环落地**:幂等采证 + 位点确定性召回 + FTS×向量混合召回(RRF 融合 + 治理重排)+ Handoff Digest 候选 + 排序权重校准基座;默认 `shadow` 模式(召回照跑照审计、暂不注入) | 门槛未下调:Hybrid MRR@5 0.4867 < 0.75,由校准线程以数据逼近 |
505
+ | ~~对话记忆增强~~ | ✅ **v0.3.35→v0.3.37 记忆闭环落地**:幂等采证 + 位点确定性召回 + FTS×向量混合召回(RRF 融合 + 治理重排)+ Handoff Digest 候选 + 排序权重校准基座;默认 `shadow` 模式(召回照跑照审计、暂不注入);v0.3.37 起原生层(SQLite FTS5 虚表 + 触发器)由 init/db-ensure 自动应用,`memory:reindex` 自助修复 | 门槛未下调:Hybrid MRR@5 0.4867 < 0.75,由校准线程以数据逼近 |
506
506
 
507
507
  ---
508
508
 
@@ -970,10 +970,24 @@ npx add-coder stack --clear # clear tech-stack setting (neutral)
970
970
  | Demo Repo | Full example repository showcasing Policy-Update-Loop with Report system end-to-end | 📋 |
971
971
  | ~~MCP Restructure~~ | ✅ v0.2.9 3,467-line monolith → 27 modules covering all six MCP primitives | 2026-07/23/... |
972
972
  | ~~Hook Notification Upgrade~~ | ✅ v0.2.9 ~36 files: hook intercept → jsonl → fs.watch → record_dev_operation | 2026-07/24/... |
973
- | Memory Enhancement | Long-term project knowledge memory and plan-level sparse memory | 📋 |
973
+ | ~~Memory Enhancement~~ | v0.3.35→v0.3.37 **Governance memory**: idempotent evidence collection + deterministic waypoint recall + hybrid FTS×vector recall (RRF fusion + governed rerank) + Handoff Digest candidates + ranking-calibration foundation; SQLite FTS5 native layer auto-applied by `init`/`db-ensure` + `memory:reindex` self-heal; defaults to `shadow` mode | 2026-09/15/... |
974
974
 
975
975
  ---
976
976
 
977
977
  My hometown
978
978
 
979
979
  <img width="720" alt="hometown outside" src="https://github.com/user-attachments/assets/7aad93de-9cea-4194-b09d-cbd892d61cc2" />
980
+
981
+ ---
982
+
983
+ ## 🙏 致谢 / Acknowledgements
984
+
985
+ 范式的价值来自把它用出问题的人。**贡献者墙**见 **[CONTRIBUTING.md §贡献者墙](./CONTRIBUTING.md#-贡献者墙)**,逐项明细与证据见 **[docs/ACKNOWLEDGEMENTS.md](./docs/ACKNOWLEDGEMENTS.md)**。
986
+
987
+ | 贡献者 | 贡献形态 | 留下了什么 |
988
+ |--------|---------|-----------|
989
+ | [@iopzhu](https://github.com/iopzhu) | 记忆轮动(`memory_cache` 分支 `357b215`,48 文件 / +5877 −33) | 记忆闭环的检索层与工具层主体:`retrieval/pipeline`、RRF 融合、治理重排、FTS 双通道、采证与快照作业、`tools/memory.ts`(609 行)+ 评测标注集(909 行) |
990
+ | [@Milkycoffees](https://github.com/Milkycoffees) | 生态贡献:[`add-coder-flash`](https://www.npmjs.com/package/add-coder-flash)(npm `1.0.4` / 5 个版本 + [GitHub 开源](https://github.com/Milkycoffees/add-coder-flash)) | 零依赖轻量发行版:hooks 纯 `.mjs`、不需要数据库,`npm install` 即铺设;自动生成 `.qoder-cn/mcp.json`,并回吐 QoderCN 加载位置、pnpm `allowBuilds`、MCP 名长度等真实约束 |
991
+ | [@albertm88](https://github.com/albertm88) | 持续输出 issue(#5–#7、#10–#20,14 条全部闭环) | 跨会话记忆、429 并发、Windows 稳定性、Codex 原生适配、端口漂移、非交互挂起、HITL 首次审批……把边界条件替所有人先踩了一遍 |
992
+
993
+ > 想上墙?上榜标准在 [CONTRIBUTING.md §贡献者墙](./CONTRIBUTING.md#-贡献者墙)——提 [Issue](https://github.com/xiaomingming92/add-coder/issues)(可复现的问题、实测日志、平台差异)或 [PR](https://github.com/xiaomingming92/add-coder/pulls) 都算硬贡献。
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/cli/index.ts
4
- import { readFileSync as readFileSync13 } from "fs";
4
+ import { readFileSync as readFileSync14 } from "fs";
5
5
  import { Command } from "commander";
6
6
 
7
7
  // src/caijuehub/strategies/detect.strategy.ts
@@ -60,6 +60,24 @@ function magicDirFor(adapter) {
60
60
  if (!m) throw new Error(`\u672A\u77E5 adapter: ${adapter}`);
61
61
  return m;
62
62
  }
63
+ function here() {
64
+ return import.meta.dirname;
65
+ }
66
+ function resolveMagicDir(cwd) {
67
+ const fromEnv = process.env.MAGIC_DIR;
68
+ if (fromEnv) return fromEnv;
69
+ const base = cwd ?? here();
70
+ const ide = detectIDE2(base);
71
+ if (ide !== "auto") {
72
+ const m = MAGIC_DIR_MAP[ide];
73
+ if (m && findUpSync(m, { cwd: base, type: "directory" })) return m;
74
+ }
75
+ for (const magic of Object.values(MAGIC_DIR_MAP)) {
76
+ const hit = findUpSync(magic, { cwd: base, type: "directory" });
77
+ if (hit) return magic;
78
+ }
79
+ return MAGIC_DIR_MAP.qoder;
80
+ }
63
81
 
64
82
  // src/cli/config-loader.ts
65
83
  import { existsSync as existsSync2, readFileSync } from "fs";
@@ -200,7 +218,7 @@ async function selectFiles(projectRoot, files) {
200
218
  process.stdout.write(renderList(items, selected, scrollIdx, VISIBLE));
201
219
  }
202
220
  draw();
203
- return new Promise((resolve12) => {
221
+ return new Promise((resolve14) => {
204
222
  input.on("keypress", (_char, key) => {
205
223
  if (!key) return;
206
224
  if (key.name === "up") {
@@ -241,7 +259,7 @@ async function selectFiles(projectRoot, files) {
241
259
  if (selected.has(idx)) result.set(relPath, content);
242
260
  idx++;
243
261
  }
244
- resolve12(result);
262
+ resolve14(result);
245
263
  });
246
264
  });
247
265
  }
@@ -1530,9 +1548,9 @@ async function injectPrisma2(projectRoot, options = {}) {
1530
1548
  }
1531
1549
 
1532
1550
  // src/cli/commands/init.ts
1533
- import { readFileSync as readFileSync10, writeFileSync as writeFileSync6, existsSync as existsSync12, mkdirSync as mkdirSync6, copyFileSync as copyFileSync2, readdirSync as readdirSync5 } from "fs";
1551
+ import { readFileSync as readFileSync10, writeFileSync as writeFileSync6, existsSync as existsSync13, mkdirSync as mkdirSync6, copyFileSync as copyFileSync2, readdirSync as readdirSync5 } from "fs";
1534
1552
  import { createHash } from "crypto";
1535
- import { resolve as resolve8, dirname as dirname3, join as pathJoin } from "path";
1553
+ import { resolve as resolve9, dirname as dirname3, join as pathJoin } from "path";
1536
1554
  import { homedir as homedir2 } from "os";
1537
1555
  import { createConnection as createConnection2 } from "net";
1538
1556
 
@@ -1612,6 +1630,42 @@ async function ensureEmbeddingModel(options) {
1612
1630
  return { status: "downloaded", model, cacheDir };
1613
1631
  }
1614
1632
 
1633
+ // src/lib/memory-expected-state.ts
1634
+ import { existsSync as existsSync12 } from "fs";
1635
+ import { join as join9, resolve as resolve8 } from "path";
1636
+ function expectedStateSqlRelPath(magicDir) {
1637
+ return join9(magicDir, "scripts", "mcp-server", "shared", "memory", "retrieval", "fts", "sqlite-fts5.sql");
1638
+ }
1639
+ function applyMemoryExpectedState(opts) {
1640
+ const { projectRoot, magicDir, engine } = opts;
1641
+ const relSql = expectedStateSqlRelPath(magicDir);
1642
+ const manualCmd = `npx prisma db execute --file ${relSql}`;
1643
+ if (engine !== "sqlite") return { applicable: false, ok: true, relSql, manualCmd };
1644
+ const absSql = resolve8(projectRoot, relSql);
1645
+ if (!existsSync12(absSql)) {
1646
+ return {
1647
+ applicable: true,
1648
+ ok: false,
1649
+ relSql,
1650
+ manualCmd,
1651
+ detail: `\u671F\u671B\u6001 SQL \u672A\u627E\u5230\uFF08${relSql}\uFF09\u2014\u2014\u8BF7\u5148\u6267\u884C add-coder sync \u540E\u91CD\u8DD1`
1652
+ };
1653
+ }
1654
+ const pm = detectPm(projectRoot);
1655
+ const argv = pm === "pnpm" ? ["exec", "prisma", "db", "execute", "--file", absSql] : ["exec", "prisma", "--", "db", "execute", "--file", absSql];
1656
+ const run = opts.run ?? ((a, cwd) => runCommand(pm, a, { cwd, timeout: opts.timeoutMs ?? 6e4 }));
1657
+ try {
1658
+ const r = run(argv, projectRoot);
1659
+ if (r.status !== 0) {
1660
+ const why = (r.stderr || "").trim().split("\n").slice(0, 2).join(" | ");
1661
+ return { applicable: true, ok: false, relSql, manualCmd, detail: `\u9000\u51FA\u7801 ${r.status}${why ? `: ${why}` : ""}` };
1662
+ }
1663
+ return { applicable: true, ok: true, relSql, manualCmd };
1664
+ } catch (e) {
1665
+ return { applicable: true, ok: false, relSql, manualCmd, detail: e instanceof Error ? e.message : String(e) };
1666
+ }
1667
+ }
1668
+
1615
1669
  // src/cli/commands/init.ts
1616
1670
  var ADAPTER_RENDERERS = {
1617
1671
  claude: renderAdapter,
@@ -1678,7 +1732,7 @@ async function handleCodexMcpConfig(options) {
1678
1732
  console.log(`[dry-run] \u5C06\u5199\u5165 ${userConfigPath}`);
1679
1733
  return;
1680
1734
  }
1681
- if (existsSync12(userConfigPath) && readFileSync10(userConfigPath, "utf-8").includes("mcp_servers.add_coder")) {
1735
+ if (existsSync13(userConfigPath) && readFileSync10(userConfigPath, "utf-8").includes("mcp_servers.add_coder")) {
1682
1736
  console.log(`\u26A0\uFE0F ${userConfigPath} \u5DF2\u5B58\u5728 mcp_servers.add_coder \u914D\u7F6E\uFF0C\u8DF3\u8FC7\uFF08\u907F\u514D\u91CD\u590D\uFF09`);
1683
1737
  return;
1684
1738
  }
@@ -1687,7 +1741,7 @@ async function handleCodexMcpConfig(options) {
1687
1741
  console.log("\u5DF2\u53D6\u6D88\u5199\u5165");
1688
1742
  return;
1689
1743
  }
1690
- if (existsSync12(userConfigPath)) {
1744
+ if (existsSync13(userConfigPath)) {
1691
1745
  const backup = `${userConfigPath}.bak-${Date.now()}`;
1692
1746
  copyFileSync2(userConfigPath, backup);
1693
1747
  console.log(`\u5DF2\u5907\u4EFD\u539F\u914D\u7F6E \u2192 ${backup}`);
@@ -1838,8 +1892,8 @@ networks:
1838
1892
  `;
1839
1893
  }
1840
1894
  function writeSqliteExportScript(projectRoot, dryRun) {
1841
- const scriptsDir = resolve8(projectRoot, "scripts");
1842
- const scriptPath = resolve8(scriptsDir, "export-db.ts");
1895
+ const scriptsDir = resolve9(projectRoot, "scripts");
1896
+ const scriptPath = resolve9(scriptsDir, "export-db.ts");
1843
1897
  const content = `import { PrismaClient } from "@prisma/client";
1844
1898
  import { writeFileSync, mkdirSync, existsSync } from "fs";
1845
1899
  import { resolve } from "path";
@@ -1863,13 +1917,13 @@ main().catch((e) => { console.error(e); process.exit(1); });
1863
1917
  console.log(`[dry-run] \u5C06\u5199\u5165 ${scriptPath}`);
1864
1918
  return;
1865
1919
  }
1866
- if (!existsSync12(scriptsDir)) mkdirSync6(scriptsDir, { recursive: true });
1920
+ if (!existsSync13(scriptsDir)) mkdirSync6(scriptsDir, { recursive: true });
1867
1921
  writeFileSync6(scriptPath, content, "utf-8");
1868
1922
  console.log("\u5DF2\u751F\u6210 scripts/export-db.ts");
1869
1923
  }
1870
1924
  function injectDbExportScript(projectRoot, dryRun) {
1871
- const pkgPath = resolve8(projectRoot, "package.json");
1872
- if (!existsSync12(pkgPath)) return;
1925
+ const pkgPath = resolve9(projectRoot, "package.json");
1926
+ if (!existsSync13(pkgPath)) return;
1873
1927
  if (dryRun) {
1874
1928
  console.log("[dry-run] \u5C06\u6CE8\u5165 db:export");
1875
1929
  return;
@@ -1921,14 +1975,14 @@ function writeComposeEnv(ctx) {
1921
1975
  if (db.engine !== "postgresql" || !db.container || db.container === "manual") return;
1922
1976
  if (!db.reuseExisting) {
1923
1977
  const composeName = db.container === "podman" ? "podman-compose.add.yml" : "docker-compose.add.yml";
1924
- const composePath = resolve8(projectRoot, composeName);
1925
- if (!options.dryRun && (!existsSync12(composePath) || options.force)) {
1978
+ const composePath = resolve9(projectRoot, composeName);
1979
+ if (!options.dryRun && (!existsSync13(composePath) || options.force)) {
1926
1980
  writeFileSync6(composePath, composeContent(config.projectName || "add-project"), "utf-8");
1927
1981
  console.log(`\u5DF2\u521B\u5EFA ${composeName}`);
1928
1982
  }
1929
1983
  }
1930
- const devEnvPath = resolve8(projectRoot, ".env.development");
1931
- if (!options.dryRun && existsSync12(devEnvPath)) {
1984
+ const devEnvPath = resolve9(projectRoot, ".env.development");
1985
+ if (!options.dryRun && existsSync13(devEnvPath)) {
1932
1986
  const existing = readFileSync10(devEnvPath, "utf-8");
1933
1987
  if (!/^DATABASE_USER=/m.test(existing)) {
1934
1988
  writeFileSync6(devEnvPath, existing + `
@@ -1975,8 +2029,8 @@ async function renderAndWrite(ctx) {
1975
2029
  }
1976
2030
  const reviewTargets = target === "codex" ? [magicDir] : [ADD_DIR, magicDir];
1977
2031
  for (const d of reviewTargets) {
1978
- const reviewsDir = resolve8(projectRoot, d, "reviews");
1979
- if (!existsSync12(reviewsDir)) {
2032
+ const reviewsDir = resolve9(projectRoot, d, "reviews");
2033
+ if (!existsSync13(reviewsDir)) {
1980
2034
  if (dry) {
1981
2035
  console.log(`[dry-run] \u5C06\u521B\u5EFA ${reviewsDir}/`);
1982
2036
  } else {
@@ -1988,16 +2042,16 @@ async function renderAndWrite(ctx) {
1988
2042
  const hashMap = {};
1989
2043
  let npmVer = "";
1990
2044
  try {
1991
- npmVer = JSON.parse(readFileSync10(resolve8(projectRoot, "node_modules", "add-coder", "templates", ".add-coder-src-hash.json"), "utf-8"))._version ?? "";
2045
+ npmVer = JSON.parse(readFileSync10(resolve9(projectRoot, "node_modules", "add-coder", "templates", ".add-coder-src-hash.json"), "utf-8"))._version ?? "";
1992
2046
  } catch {
1993
2047
  }
1994
2048
  for (const [rp, c] of allFiles) {
1995
2049
  hashMap[rp] = createHash("sha256").update(c).digest("hex").slice(0, 8);
1996
2050
  }
1997
- const hashOut = resolve8(projectRoot, magicDir, ".add-coder-hash.json");
2051
+ const hashOut = resolve9(projectRoot, magicDir, ".add-coder-hash.json");
1998
2052
  writeFileSync6(hashOut, JSON.stringify(hashMap, null, 2) + "\n", "utf-8");
1999
2053
  if (npmVer) {
2000
- writeFileSync6(resolve8(projectRoot, magicDir, ".add-coder-version"), npmVer + "\n", "utf-8");
2054
+ writeFileSync6(resolve9(projectRoot, magicDir, ".add-coder-version"), npmVer + "\n", "utf-8");
2001
2055
  }
2002
2056
  console.log(`hash: ${Object.keys(hashMap).length} entries \u2192 ${magicDir}/.add-coder-hash.json`);
2003
2057
  }
@@ -2008,7 +2062,7 @@ async function deployDatabase(ctx) {
2008
2062
  if (options.dryRun) return null;
2009
2063
  let fail = null;
2010
2064
  if (db.engine === "postgresql" && db.container && db.container !== "manual") {
2011
- const dbScript = resolve8(projectRoot, magicDir, "scripts", "db-ensure.sh");
2065
+ const dbScript = resolve9(projectRoot, magicDir, "scripts", "db-ensure.sh");
2012
2066
  const dbEnv = { ...process.env, DATABASE_USER: db.user, DATABASE_PASSWORD: db.password, DATABASE_PORT: db.port, PROJECT_NAME: config.projectName };
2013
2067
  const mode = db.reuseExisting ? "manual" : db.container;
2014
2068
  console.log(db.reuseExisting ? "\u590D\u7528\u5DF2\u6709 PostgreSQL ..." : `\u90E8\u7F72\u6570\u636E\u5E93 (${db.container}) ...`);
@@ -2026,8 +2080,8 @@ ${bashRun.stderr.trim().split("\n").slice(0, 5).join("\n")}` : ""}`;
2026
2080
  }
2027
2081
  }
2028
2082
  if (db.engine === "postgresql" && db.container === "manual") {
2029
- const dbScript = resolve8(projectRoot, magicDir, "scripts", "db-ensure.sh");
2030
- if (existsSync12(dbScript)) {
2083
+ const dbScript = resolve9(projectRoot, magicDir, "scripts", "db-ensure.sh");
2084
+ if (existsSync13(dbScript)) {
2031
2085
  try {
2032
2086
  const bashRun = runCommand("bash", [dbScript, "postgresql", "manual"], { cwd: projectRoot, stdio: "inherit" });
2033
2087
  if (bashRun.status !== 0) fail = `db-ensure.sh \u9000\u51FA\u7801: ${bashRun.status}${bashRun.stderr ? `
@@ -2062,6 +2116,14 @@ ${bashRun.stderr.trim().split("\n").slice(0, 5).join("\n")}` : ""}`;
2062
2116
  injectDbExportScript(projectRoot, false);
2063
2117
  try {
2064
2118
  await injectPrisma2(projectRoot, { force: !!options.force, datasource: "sqlite" });
2119
+ const expected = applyMemoryExpectedState({ projectRoot, magicDir, engine: db.engine });
2120
+ if (expected.applicable && !expected.ok) {
2121
+ console.warn(`\u26A0\uFE0F \u8BB0\u5FC6 FTS5 \u671F\u671B\u6001\u5E94\u7528\u5931\u8D25\uFF08\u4E0D\u963B\u65AD init\uFF09\uFF1A${expected.detail ?? "\u672A\u77E5\u539F\u56E0"}`);
2122
+ console.warn(` \u540E\u7EED recall_memory \u4F1A\u62A5 sqlite-fts \u4E0D\u53EF\u7528\uFF1B\u624B\u5DE5\u6062\u590D: ${expected.manualCmd}`);
2123
+ console.warn(` \u6216\u8FD0\u884C: add-coder memory:reindex --apply`);
2124
+ } else if (expected.applicable) {
2125
+ console.log(`\u2705 \u8BB0\u5FC6 FTS5 \u671F\u671B\u6001\u5DF2\u5E94\u7528\uFF08${expected.relSql}\uFF09`);
2126
+ }
2065
2127
  } catch (e) {
2066
2128
  fail = `SQLite \u540C\u6B65\u5931\u8D25: ${e instanceof Error ? e.message : String(e)}`;
2067
2129
  }
@@ -2072,17 +2134,17 @@ function deployDocs(ctx) {
2072
2134
  const { projectRoot, options, config } = ctx;
2073
2135
  if (options.dryRun) return;
2074
2136
  const pn = config.projectName || "add-project";
2075
- const docsBase = resolve8(projectRoot, "docs", pn, "knowledge");
2076
- const groundingSrc = resolve8(import.meta.dirname, "../templates/core/templates");
2137
+ const docsBase = resolve9(projectRoot, "docs", pn, "knowledge");
2138
+ const groundingSrc = resolve9(import.meta.dirname, "../templates/core/templates");
2077
2139
  for (const d of ["00-\u9700\u6C42", "01-\u67B6\u6784", "02-\u89C4\u8303"]) {
2078
- const srcDir = resolve8(groundingSrc, d);
2079
- const destDir = resolve8(docsBase, d);
2080
- if (!existsSync12(destDir)) mkdirSync6(destDir, { recursive: true });
2081
- if (!existsSync12(srcDir)) continue;
2140
+ const srcDir = resolve9(groundingSrc, d);
2141
+ const destDir = resolve9(docsBase, d);
2142
+ if (!existsSync13(destDir)) mkdirSync6(destDir, { recursive: true });
2143
+ if (!existsSync13(srcDir)) continue;
2082
2144
  for (const f of readdirSync5(srcDir)) {
2083
- const src = resolve8(srcDir, f);
2084
- const dest = resolve8(destDir, f);
2085
- if (existsSync12(dest)) continue;
2145
+ const src = resolve9(srcDir, f);
2146
+ const dest = resolve9(destDir, f);
2147
+ if (existsSync13(dest)) continue;
2086
2148
  try {
2087
2149
  copyFileSync2(src, dest);
2088
2150
  } catch {
@@ -2098,7 +2160,7 @@ function finalize(ctx, result, dbFail) {
2098
2160
  return;
2099
2161
  }
2100
2162
  if (db.engine === "sqlite") console.log("\u6570\u636E\u5907\u4EFD: npm run db:export \u2192 data/exports/");
2101
- const pkg = JSON.parse(readFileSync10(resolve8(import.meta.dirname, "../package.json"), "utf-8"));
2163
+ const pkg = JSON.parse(readFileSync10(resolve9(import.meta.dirname, "../package.json"), "utf-8"));
2102
2164
  const peerNames = Object.keys(pkg.peerDependencies || {});
2103
2165
  if (peerNames.length > 0) {
2104
2166
  console.log(`
@@ -2131,8 +2193,8 @@ function finalize(ctx, result, dbFail) {
2131
2193
  }
2132
2194
 
2133
2195
  // src/cli/commands/sync.ts
2134
- import { existsSync as existsSync13, readFileSync as readFileSync11, writeFileSync as writeFileSync7, mkdirSync as mkdirSync7 } from "fs";
2135
- import { resolve as resolve9, dirname as dirname4 } from "path";
2196
+ import { existsSync as existsSync14, readFileSync as readFileSync11, writeFileSync as writeFileSync7, mkdirSync as mkdirSync7 } from "fs";
2197
+ import { resolve as resolve10, dirname as dirname4 } from "path";
2136
2198
  import { createHash as createHash2 } from "crypto";
2137
2199
 
2138
2200
  // src/lib/path-normalize.ts
@@ -2205,7 +2267,7 @@ function resolvePatchConflictMode(yes = false, isTTY = Boolean(process.stdin.isT
2205
2267
  }
2206
2268
  function loadHashFile(root, magic) {
2207
2269
  try {
2208
- const raw = JSON.parse(readFileSync11(resolve9(root, magic, SYNC_CONFIG.HASH_OUTPUT_FILE), "utf-8"));
2270
+ const raw = JSON.parse(readFileSync11(resolve10(root, magic, SYNC_CONFIG.HASH_OUTPUT_FILE), "utf-8"));
2209
2271
  const normalized = {};
2210
2272
  for (const [k, v] of Object.entries(raw)) normalized[normalizeRelPath(k)] = v;
2211
2273
  return normalized;
@@ -2215,19 +2277,19 @@ function loadHashFile(root, magic) {
2215
2277
  }
2216
2278
  function loadVersionFile(root, magic) {
2217
2279
  try {
2218
- return readFileSync11(resolve9(root, magic, SYNC_CONFIG.VERSION_SENTINEL), "utf-8").trim();
2280
+ return readFileSync11(resolve10(root, magic, SYNC_CONFIG.VERSION_SENTINEL), "utf-8").trim();
2219
2281
  } catch {
2220
2282
  return "";
2221
2283
  }
2222
2284
  }
2223
2285
  function saveVersionFile(root, magic, version2) {
2224
- writeFileSync7(resolve9(root, magic, SYNC_CONFIG.VERSION_SENTINEL), version2 + "\n", "utf-8");
2286
+ writeFileSync7(resolve10(root, magic, SYNC_CONFIG.VERSION_SENTINEL), version2 + "\n", "utf-8");
2225
2287
  }
2226
2288
  function loadTemplateVersion(root) {
2227
2289
  const candidates = [
2228
- resolve9(root, "node_modules", "add-coder", "templates", ".add-coder-src-hash.json"),
2229
- resolve9(import.meta.dirname, "../templates/.add-coder-src-hash.json"),
2230
- resolve9(import.meta.dirname, "../../../templates/.add-coder-src-hash.json")
2290
+ resolve10(root, "node_modules", "add-coder", "templates", ".add-coder-src-hash.json"),
2291
+ resolve10(import.meta.dirname, "../templates/.add-coder-src-hash.json"),
2292
+ resolve10(import.meta.dirname, "../../../templates/.add-coder-src-hash.json")
2231
2293
  ];
2232
2294
  for (const candidate of candidates) {
2233
2295
  try {
@@ -2240,7 +2302,7 @@ function loadTemplateVersion(root) {
2240
2302
  function saveHashFile(root, magic, files) {
2241
2303
  const m = {};
2242
2304
  for (const [p, c] of files) m[p] = c;
2243
- writeFileSync7(resolve9(root, magic, SYNC_CONFIG.HASH_OUTPUT_FILE), JSON.stringify(m, null, 2) + "\n", "utf-8");
2305
+ writeFileSync7(resolve10(root, magic, SYNC_CONFIG.HASH_OUTPUT_FILE), JSON.stringify(m, null, 2) + "\n", "utf-8");
2244
2306
  }
2245
2307
  function mergeFullHash(outHash, candidates, readDiskHash, preserveKeys = /* @__PURE__ */ new Set()) {
2246
2308
  const finalHash = /* @__PURE__ */ new Map();
@@ -2341,8 +2403,8 @@ async function syncCommand(options = {}) {
2341
2403
  let sameCount = 0;
2342
2404
  for (const [relPath, content] of candidates) {
2343
2405
  const key = normalizeRelPath(relPath);
2344
- const absPath = resolve9(projectRoot, relPath);
2345
- if (!existsSync13(absPath)) {
2406
+ const absPath = resolve10(projectRoot, relPath);
2407
+ if (!existsSync14(absPath)) {
2346
2408
  missingFiles.set(key, content);
2347
2409
  } else if (establishBaseline) {
2348
2410
  missingFiles.set(key, content);
@@ -2388,8 +2450,8 @@ async function syncCommand(options = {}) {
2388
2450
  }
2389
2451
  let finalHash = mergeFullHash(
2390
2452
  outHash,
2391
- [...candidates].map(([relPath]) => ({ relPath, absPath: resolve9(projectRoot, relPath) })),
2392
- (absPath) => existsSync13(absPath) ? hash8(readFileSync11(absPath, "utf-8")) : null,
2453
+ [...candidates].map(([relPath]) => ({ relPath, absPath: resolve10(projectRoot, relPath) })),
2454
+ (absPath) => existsSync14(absPath) ? hash8(readFileSync11(absPath, "utf-8")) : null,
2393
2455
  unresolvedConflictKeys
2394
2456
  );
2395
2457
  if (target === "codex") {
@@ -2404,7 +2466,7 @@ async function syncCommand(options = {}) {
2404
2466
  }
2405
2467
  const missing = /* @__PURE__ */ new Map();
2406
2468
  for (const [relPath, content] of allFiles) {
2407
- if (!existsSync13(resolve9(projectRoot, relPath))) {
2469
+ if (!existsSync14(resolve10(projectRoot, relPath))) {
2408
2470
  missing.set(relPath, content);
2409
2471
  }
2410
2472
  }
@@ -2428,7 +2490,7 @@ async function syncCommand(options = {}) {
2428
2490
  }
2429
2491
  function printMigrateGuidance(targetPath, changed) {
2430
2492
  const g = SYNC_PRISMA_CONFIG.POST_SYNC;
2431
- const isManaged = existsSync13(resolve9(dirname4(targetPath), "migrations"));
2493
+ const isManaged = existsSync14(resolve10(dirname4(targetPath), "migrations"));
2432
2494
  const actions = isManaged ? g.MANAGED_ACTIONS : g.UNMANAGED_ACTIONS;
2433
2495
  console.log(` \u25B6 \u5DF2\u5199\u5165 ${changed} \u5904\u53D8\u66F4\uFF0C${g.HEADER}`);
2434
2496
  for (const a of actions) {
@@ -2467,7 +2529,7 @@ async function ensureAtlasCapability(projectRoot, yes = false) {
2467
2529
  console.log(" \u6587\u6863: README.md \u2192 \u7AE0\u8282\u300CAtlas \u6570\u636E\u5E93\u540C\u6B65\u80FD\u529B\u300D/ DEVELOPMENT.md \xA7\u4E5D");
2468
2530
  return;
2469
2531
  }
2470
- const pm = existsSync13(resolve9(projectRoot, "pnpm-lock.yaml")) ? "pnpm" : "npm";
2532
+ const pm = existsSync14(resolve10(projectRoot, "pnpm-lock.yaml")) ? "pnpm" : "npm";
2471
2533
  console.log(` \u5B89\u88C5 ${pm} add -D @ariga/atlas ...`);
2472
2534
  const r = runCommand(pm, ["add", "-D", "@ariga/atlas"], { cwd: projectRoot, timeout: 18e4 });
2473
2535
  if (r.status !== 0) {
@@ -2479,8 +2541,8 @@ async function ensureAtlasCapability(projectRoot, yes = false) {
2479
2541
  console.log(" \u2705 @ariga/atlas \u5DF2\u5B89\u88C5\uFF0CAtlas \u80FD\u529B\u5C31\u7EEA\uFF08node_modules/.bin/atlas\uFF09");
2480
2542
  }
2481
2543
  function checkHostAtlasSegment(projectRoot) {
2482
- const script = resolve9(projectRoot, "scripts", "db-ensure.sh");
2483
- if (!existsSync13(script)) return;
2544
+ const script = resolve10(projectRoot, "scripts", "db-ensure.sh");
2545
+ if (!existsSync14(script)) return;
2484
2546
  const content = readFileSync11(script, "utf-8");
2485
2547
  if (content.includes("atlas_sync")) return;
2486
2548
  console.warn(" \u26A0\uFE0F \u5BBF\u4E3B scripts/db-ensure.sh \u672A\u5305\u542B Atlas \u540C\u6B65\u6BB5\uFF08\u65E5\u5E38 db-ensure \u5C06\u7F3A\u5C11 ADD \u6CBB\u7406\u6A21\u578B\u540C\u6B65\uFF09");
@@ -2493,9 +2555,9 @@ function checkHostAtlasSegment(projectRoot) {
2493
2555
  }
2494
2556
  async function checkPrismaDiff(projectRoot, options) {
2495
2557
  if (!options.patch) return;
2496
- const basePath = resolve9(projectRoot, SYNC_PRISMA_CONFIG.BASE_SCHEMA);
2497
- const targetPath = resolve9(projectRoot, SYNC_PRISMA_CONFIG.TARGET_PATTERN);
2498
- if (!existsSync13(basePath)) {
2558
+ const basePath = resolve10(projectRoot, SYNC_PRISMA_CONFIG.BASE_SCHEMA);
2559
+ const targetPath = resolve10(projectRoot, SYNC_PRISMA_CONFIG.TARGET_PATTERN);
2560
+ if (!existsSync14(basePath)) {
2499
2561
  console.log(`
2500
2562
  \u26A0\uFE0F \u57FA\u51C6 schema \u4E0D\u5B58\u5728: ${basePath}`);
2501
2563
  console.log(` \u8BF7\u786E\u4FDD add-coder \u5DF2\u6B63\u786E\u5B89\u88C5\u3002`);
@@ -2509,7 +2571,7 @@ async function checkPrismaDiff(projectRoot, options) {
2509
2571
  \u2705 Prisma schema \u4E0E add-coder \u6807\u51C6\u4E00\u81F4\uFF0C\u65E0\u9700\u540C\u6B65\u3002`);
2510
2572
  return;
2511
2573
  }
2512
- const targetExists = existsSync13(targetPath);
2574
+ const targetExists = existsSync14(targetPath);
2513
2575
  let modifiedCount = 0;
2514
2576
  console.log(`
2515
2577
  \u26A0\uFE0F Prisma schema \u5DEE\u5F02\u68C0\u6D4B:`);
@@ -2759,8 +2821,8 @@ function overwriteFieldLines(targetPath, basePath, modelName, conflicts) {
2759
2821
  }
2760
2822
 
2761
2823
  // src/cli/commands/status.ts
2762
- import { existsSync as existsSync14 } from "fs";
2763
- import { resolve as resolve10 } from "path";
2824
+ import { existsSync as existsSync15 } from "fs";
2825
+ import { resolve as resolve11 } from "path";
2764
2826
  async function statusCommand() {
2765
2827
  const projectRoot = process.cwd();
2766
2828
  const config = await loadConfig(projectRoot);
@@ -2769,7 +2831,7 @@ async function statusCommand() {
2769
2831
  const missing = [];
2770
2832
  const present = [];
2771
2833
  for (const [relPath] of coreFiles) {
2772
- if (existsSync14(resolve10(projectRoot, relPath))) {
2834
+ if (existsSync15(resolve11(projectRoot, relPath))) {
2773
2835
  present.push(relPath);
2774
2836
  } else {
2775
2837
  missing.push(relPath);
@@ -2787,14 +2849,14 @@ async function statusCommand() {
2787
2849
  }
2788
2850
 
2789
2851
  // src/cli/commands/stack.ts
2790
- import { readdirSync as readdirSync6, readFileSync as readFileSync12, writeFileSync as writeFileSync8, existsSync as existsSync15, mkdirSync as mkdirSync8 } from "fs";
2791
- import { resolve as resolve11, join as join9 } from "path";
2852
+ import { readdirSync as readdirSync6, readFileSync as readFileSync12, writeFileSync as writeFileSync8, existsSync as existsSync16, mkdirSync as mkdirSync8 } from "fs";
2853
+ import { resolve as resolve12, join as join10 } from "path";
2792
2854
  import { createHash as createHash3 } from "crypto";
2793
2855
  var HASH_OUTPUT_FILE = ".add-coder-hash.json";
2794
2856
  function hash82(c) {
2795
2857
  return createHash3("sha256").update(c).digest("hex").slice(0, 8);
2796
2858
  }
2797
- function resolveMagicDir(projectRoot, specified) {
2859
+ function resolveMagicDir2(projectRoot, specified) {
2798
2860
  if (specified) {
2799
2861
  return magicDirFor(specified);
2800
2862
  }
@@ -2803,18 +2865,18 @@ function resolveMagicDir(projectRoot, specified) {
2803
2865
  return magicDirFor(adapter);
2804
2866
  }
2805
2867
  function listCustomProfiles(projectRoot, magicDir, registryNames) {
2806
- const dir = resolve11(projectRoot, magicDir, "rules", "profiles");
2807
- if (!existsSync15(dir)) return [];
2868
+ const dir = resolve12(projectRoot, magicDir, "rules", "profiles");
2869
+ if (!existsSync16(dir)) return [];
2808
2870
  return readdirSync6(dir).filter((f) => f.endsWith(".md") && !registryNames.has(f.replace(/-profile\.md$/, ""))).sort();
2809
2871
  }
2810
2872
  function profileExists(projectRoot, magicDir, name) {
2811
2873
  const registry = loadProfileRegistry();
2812
2874
  if (registry.some((p) => p.name === name)) return true;
2813
- return existsSync15(resolve11(projectRoot, magicDir, "rules", "profiles", `${name}-profile.md`));
2875
+ return existsSync16(resolve12(projectRoot, magicDir, "rules", "profiles", `${name}-profile.md`));
2814
2876
  }
2815
2877
  function stackCommand(sub, name, options = {}) {
2816
2878
  const projectRoot = process.cwd();
2817
- const magicDir = resolveMagicDir(projectRoot, options.adapter);
2879
+ const magicDir = resolveMagicDir2(projectRoot, options.adapter);
2818
2880
  const registry = loadProfileRegistry();
2819
2881
  if (options.clear) {
2820
2882
  saveStack(projectRoot, magicDir, "");
@@ -2847,12 +2909,12 @@ function stackCommand(sub, name, options = {}) {
2847
2909
  console.log("\u6280\u672F\u6808: \u672A\u8BBE\u7F6E\uFF08\u4E2D\u6027\uFF0C\u65E0\u6280\u672F\u6808\u5047\u8BBE\uFF09");
2848
2910
  return;
2849
2911
  }
2850
- const profilePath = resolve11(projectRoot, magicDir, "rules", "profiles", `${current}-profile.md`);
2851
- const stat = existsSync15(profilePath) ? readFileSync12(profilePath, "utf-8").length : 0;
2912
+ const profilePath = resolve12(projectRoot, magicDir, "rules", "profiles", `${current}-profile.md`);
2913
+ const stat = existsSync16(profilePath) ? readFileSync12(profilePath, "utf-8").length : 0;
2852
2914
  console.log(`\u6280\u672F\u6808: ${current}`);
2853
- console.log(`profile \u6587\u4EF6: ${profilePath}${existsSync15(profilePath) ? ` (${stat} \u5B57\u7B26)` : "\uFF08\u7F3A\u5931\uFF09"}`);
2915
+ console.log(`profile \u6587\u4EF6: ${profilePath}${existsSync16(profilePath) ? ` (${stat} \u5B57\u7B26)` : "\uFF08\u7F3A\u5931\uFF09"}`);
2854
2916
  try {
2855
- const raw = JSON.parse(readFileSync12(resolve11(projectRoot, magicDir, "stack.json"), "utf-8"));
2917
+ const raw = JSON.parse(readFileSync12(resolve12(projectRoot, magicDir, "stack.json"), "utf-8"));
2856
2918
  if (raw.updatedAt) console.log(`\u66F4\u65B0\u65F6\u95F4: ${raw.updatedAt}`);
2857
2919
  } catch {
2858
2920
  }
@@ -2877,8 +2939,8 @@ function buildConfig(projectRoot, magicDir, stack) {
2877
2939
  stack
2878
2940
  };
2879
2941
  try {
2880
- const pkgPath = resolve11(projectRoot, "package.json");
2881
- if (existsSync15(pkgPath)) {
2942
+ const pkgPath = resolve12(projectRoot, "package.json");
2943
+ if (existsSync16(pkgPath)) {
2882
2944
  const pkg = JSON.parse(readFileSync12(pkgPath, "utf-8"));
2883
2945
  if (pkg.name) config.projectName = pkg.name;
2884
2946
  }
@@ -2906,13 +2968,13 @@ function applyStack(projectRoot, magicDir, name) {
2906
2968
  }
2907
2969
  const hashMap = {};
2908
2970
  try {
2909
- Object.assign(hashMap, JSON.parse(readFileSync12(resolve11(projectRoot, magicDir, HASH_OUTPUT_FILE), "utf-8")));
2971
+ Object.assign(hashMap, JSON.parse(readFileSync12(resolve12(projectRoot, magicDir, HASH_OUTPUT_FILE), "utf-8")));
2910
2972
  } catch {
2911
2973
  }
2912
2974
  let written = 0;
2913
2975
  for (const [relPath, content] of stackRelated) {
2914
- const targetPath = resolve11(projectRoot, relPath);
2915
- mkdirSync8(join9(targetPath, ".."), { recursive: true });
2976
+ const targetPath = resolve12(projectRoot, relPath);
2977
+ mkdirSync8(join10(targetPath, ".."), { recursive: true });
2916
2978
  writeFileSync8(targetPath, content, "utf-8");
2917
2979
  hashMap[relPath] = hash82(content);
2918
2980
  written++;
@@ -2923,24 +2985,331 @@ function applyStack(projectRoot, magicDir, name) {
2923
2985
  };
2924
2986
  const registry = loadProfileRegistry();
2925
2987
  const isBuiltin = registry.some((p) => p.name === name);
2926
- const profilePathMagic = resolve11(projectRoot, magicDir, "rules", "profiles", `${name}-profile.md`);
2927
- const profilePathAdd = resolve11(projectRoot, ADD_DIR, "rules", "profiles", `${name}-profile.md`);
2928
- const projectRulesPath = resolve11(projectRoot, magicDir, "rules", "project_rules.md");
2929
- const projectRulesContent = existsSync15(projectRulesPath) ? readFileSync12(projectRulesPath, "utf-8") : "";
2988
+ const profilePathMagic = resolve12(projectRoot, magicDir, "rules", "profiles", `${name}-profile.md`);
2989
+ const profilePathAdd = resolve12(projectRoot, ADD_DIR, "rules", "profiles", `${name}-profile.md`);
2990
+ const projectRulesPath = resolve12(projectRoot, magicDir, "rules", "project_rules.md");
2991
+ const projectRulesContent = existsSync16(projectRulesPath) ? readFileSync12(projectRulesPath, "utf-8") : "";
2930
2992
  if (written === 0) fail(`\u672A\u6E32\u67D3\u4EFB\u4F55 stack \u76F8\u5173\u6587\u4EF6\uFF08${name}\uFF09\u2014\u2014Windows \u8DEF\u5F84\u5339\u914D\u5931\u6548\u9057\u7559\u95EE\u9898`);
2931
- if (magicDir !== magicDirFor("codex") && isBuiltin && !existsSync15(profilePathAdd)) fail(`profile \u672A\u5199\u5165 ${ADD_DIR}: ${profilePathAdd}`);
2932
- if (!existsSync15(profilePathMagic)) fail(`profile \u672A\u5199\u5165 ${magicDir}: ${profilePathMagic}`);
2993
+ if (magicDir !== magicDirFor("codex") && isBuiltin && !existsSync16(profilePathAdd)) fail(`profile \u672A\u5199\u5165 ${ADD_DIR}: ${profilePathAdd}`);
2994
+ if (!existsSync16(profilePathMagic)) fail(`profile \u672A\u5199\u5165 ${magicDir}: ${profilePathMagic}`);
2933
2995
  if (!projectRulesContent.includes("**\u5F53\u524D\u6280\u672F\u6808**") || !projectRulesContent.includes(name)) fail(`project_rules.md \u672A\u5305\u542B ${name} \u5F15\u7528`);
2934
- writeFileSync8(resolve11(projectRoot, magicDir, HASH_OUTPUT_FILE), JSON.stringify(hashMap, null, 2) + "\n", "utf-8");
2996
+ writeFileSync8(resolve12(projectRoot, magicDir, HASH_OUTPUT_FILE), JSON.stringify(hashMap, null, 2) + "\n", "utf-8");
2935
2997
  console.log(`\u2705 \u6280\u672F\u6808\u5DF2\u8BBE\u7F6E\u4E3A ${name}`);
2936
2998
  console.log(` ${magicDir}/stack.json \u2192 ${name}`);
2937
- console.log(` ${magicDir}/rules/profiles/${name}-profile.md ${existsSync15(resolve11(projectRoot, magicDir, "rules", "profiles", `${name}-profile.md`)) ? "\u5DF2\u5C31\u4F4D" : "\uFF08\u81EA\u5B9A\u4E49 profile\uFF0C\u9879\u76EE\u4FA7\u6587\u4EF6\uFF09"}`);
2999
+ console.log(` ${magicDir}/rules/profiles/${name}-profile.md ${existsSync16(resolve12(projectRoot, magicDir, "rules", "profiles", `${name}-profile.md`)) ? "\u5DF2\u5C31\u4F4D" : "\uFF08\u81EA\u5B9A\u4E49 profile\uFF0C\u9879\u76EE\u4FA7\u6587\u4EF6\uFF09"}`);
2938
3000
  console.log(` project_rules.md \u5F15\u7528\u884C\u5DF2\u66F4\u65B0 + hash \u5DF2\u5237\u65B0\uFF08${written} \u4E2A\u6587\u4EF6\uFF09`);
2939
3001
  }
2940
3002
 
3003
+ // src/lib/memory-fts-objects.ts
3004
+ var PG_FTS_OBJECTS = [
3005
+ { name: "pg_trgm", kind: "extension", ddl: `CREATE EXTENSION IF NOT EXISTS pg_trgm;` },
3006
+ {
3007
+ name: "AddMemory_topic_trgm_idx",
3008
+ kind: "index",
3009
+ ddl: `CREATE INDEX IF NOT EXISTS "AddMemory_topic_trgm_idx" ON "public"."AddMemory" USING GIN ("topic" gin_trgm_ops);`
3010
+ },
3011
+ {
3012
+ name: "AddMemory_content_trgm_idx",
3013
+ kind: "index",
3014
+ ddl: `CREATE INDEX IF NOT EXISTS "AddMemory_content_trgm_idx" ON "public"."AddMemory" USING GIN ("content" gin_trgm_ops);`
3015
+ },
3016
+ {
3017
+ name: "AddMemoryEvidence_excerpt_trgm_idx",
3018
+ kind: "index",
3019
+ ddl: `CREATE INDEX IF NOT EXISTS "AddMemoryEvidence_excerpt_trgm_idx" ON "public"."AddMemoryEvidence" USING GIN ("excerpt" gin_trgm_ops);`
3020
+ }
3021
+ ];
3022
+ var SQLITE_FTS_OBJECTS = [
3023
+ {
3024
+ name: "add_memory_fts",
3025
+ kind: "table",
3026
+ ddl: `CREATE VIRTUAL TABLE IF NOT EXISTS add_memory_fts USING fts5(memory_id UNINDEXED, topic, content, tokenize = 'trigram');`
3027
+ },
3028
+ {
3029
+ name: "add_memory_fts_ai",
3030
+ kind: "trigger",
3031
+ ddl: `CREATE TRIGGER IF NOT EXISTS add_memory_fts_ai AFTER INSERT ON "AddMemory" BEGIN INSERT INTO add_memory_fts(memory_id, topic, content) VALUES (new.id, new.topic, new.content); END;`
3032
+ },
3033
+ {
3034
+ name: "add_memory_fts_au",
3035
+ kind: "trigger",
3036
+ ddl: `CREATE TRIGGER IF NOT EXISTS add_memory_fts_au AFTER UPDATE ON "AddMemory" BEGIN DELETE FROM add_memory_fts WHERE memory_id = old.id; INSERT INTO add_memory_fts(memory_id, topic, content) VALUES (new.id, new.topic, new.content); END;`
3037
+ },
3038
+ {
3039
+ name: "add_memory_fts_ad",
3040
+ kind: "trigger",
3041
+ ddl: `CREATE TRIGGER IF NOT EXISTS add_memory_fts_ad AFTER DELETE ON "AddMemory" BEGIN DELETE FROM add_memory_fts WHERE memory_id = old.id; END;`
3042
+ }
3043
+ ];
3044
+ function resolveFtsObjects(backend) {
3045
+ return backend === "postgres" ? PG_FTS_OBJECTS : SQLITE_FTS_OBJECTS;
3046
+ }
3047
+ function detectBackend(databaseUrl) {
3048
+ const url = (databaseUrl ?? "").trim().toLowerCase();
3049
+ if (url.startsWith("postgres://") || url.startsWith("postgresql://")) return "postgres";
3050
+ if (url.startsWith("sqlite:") || url.startsWith("file:") || url.endsWith(".db") || url.endsWith(".sqlite")) {
3051
+ return "sqlite";
3052
+ }
3053
+ throw new Error(`\u65E0\u6CD5\u8BC6\u522B\u7684 DATABASE_URL \u540E\u7AEF: ${databaseUrl}`);
3054
+ }
3055
+ var PG_MISSING_MARKER = "ADDCODER_FTS_MISSING:";
3056
+ function renderPgProbeSql(expected) {
3057
+ const values = expected.map((s) => ` ('${s.name.replace(/'/g, "''")}', '${s.kind === "extension" ? "extension" : "relation"}')`).join(",\n");
3058
+ return `-- \u7531 add-coder memory:reindex \u751F\u6210\uFF08\u771F\u6E90\uFF1Asrc/lib/memory-fts-objects.ts \u7684 PG_FTS_OBJECTS\uFF09
3059
+ DO $$
3060
+ DECLARE missing text;
3061
+ BEGIN
3062
+ SELECT string_agg(n.name, ',') INTO missing
3063
+ FROM (VALUES
3064
+ ${values}
3065
+ ) AS n(name, kind)
3066
+ WHERE NOT (
3067
+ (n.kind = 'extension' AND EXISTS (SELECT 1 FROM pg_extension e WHERE e.extname = n.name))
3068
+ OR (n.kind = 'relation' AND EXISTS (SELECT 1 FROM pg_indexes i WHERE i.indexname = n.name))
3069
+ );
3070
+ IF missing IS NOT NULL THEN
3071
+ RAISE EXCEPTION '${PG_MISSING_MARKER} %', missing;
3072
+ END IF;
3073
+ END $$;
3074
+ `;
3075
+ }
3076
+ function parsePgProbeMissing(stderr, expected) {
3077
+ const at = (stderr ?? "").indexOf(PG_MISSING_MARKER);
3078
+ if (at < 0) return [];
3079
+ const tail = stderr.slice(at + PG_MISSING_MARKER.length);
3080
+ const known = new Set(expected.map((s) => s.name));
3081
+ return tail.split(/[\s"',\n]+/).map((s) => s.trim()).filter((s) => known.has(s));
3082
+ }
3083
+
3084
+ // src/lib/memory-fts-runtime.ts
3085
+ import { existsSync as existsSync17, mkdtempSync, readFileSync as readFileSync13, rmSync as rmSync2, writeFileSync as writeFileSync9 } from "fs";
3086
+ import { createRequire } from "module";
3087
+ import { tmpdir } from "os";
3088
+ import { isAbsolute, join as join11, resolve as resolve13 } from "path";
3089
+ var requireBuiltin = createRequire(import.meta.url);
3090
+ function loadNodeSqlite() {
3091
+ return requireBuiltin("node:sqlite");
3092
+ }
3093
+ async function probeVia(adapter, expected) {
3094
+ const specs = expected ?? resolveFtsObjects(adapter.backend);
3095
+ const existing = await adapter.listExisting();
3096
+ const missing = specs.filter((s) => !existing.has(s.name)).map((s) => s.name);
3097
+ const present = specs.length - missing.length;
3098
+ return {
3099
+ backend: adapter.backend,
3100
+ source: adapter.source,
3101
+ missing,
3102
+ rebuilt: [],
3103
+ present,
3104
+ total: specs.length,
3105
+ progress: specs.length === 0 ? 100 : Math.round(present / specs.length * 100)
3106
+ };
3107
+ }
3108
+ async function reindexVia(adapter, expected) {
3109
+ const specs = expected ?? resolveFtsObjects(adapter.backend);
3110
+ const first = await probeVia(adapter, specs);
3111
+ const rebuilt = [];
3112
+ for (const name of first.missing) {
3113
+ const spec = specs.find((s) => s.name === name);
3114
+ if (!spec) continue;
3115
+ await adapter.apply(spec.ddl);
3116
+ rebuilt.push(spec.name);
3117
+ }
3118
+ const after = await probeVia(adapter, specs);
3119
+ return { ...after, rebuilt };
3120
+ }
3121
+ function sqlitePathCandidates(databaseUrl, projectRoot) {
3122
+ const raw = (databaseUrl ?? "").replace(/^file:/, "").replace(/^sqlite:/, "").split("?")[0];
3123
+ if (!raw) return [];
3124
+ if (isAbsolute(raw)) return [raw];
3125
+ return [resolve13(projectRoot, raw), resolve13(projectRoot, "prisma", raw)];
3126
+ }
3127
+ function createSqliteAdapter(opts) {
3128
+ const candidates = opts.dbPath ? [opts.dbPath] : sqlitePathCandidates(opts.databaseUrl, opts.projectRoot);
3129
+ const dbPath = candidates.find((p) => existsSync17(p));
3130
+ const source = dbPath ?? `${opts.databaseUrl}\uFF08\u672A\u627E\u5230\u5E93\u6587\u4EF6\uFF0C\u5DF2\u8BD5\uFF1A${candidates.join(" / ")}\uFF09`;
3131
+ function withDb(fn) {
3132
+ if (!dbPath) {
3133
+ throw new Error(
3134
+ `SQLite \u5E93\u6587\u4EF6\u4E0D\u5B58\u5728\uFF1A${opts.databaseUrl}\u3002\u5148\u8FD0\u884C add-coder init\uFF08\u6216\u786E\u8BA4 .env.development \u7684 DATABASE_URL\uFF09\u2014\u2014\u5DF2\u8BD5\uFF1A${candidates.join(" / ")}`
3135
+ );
3136
+ }
3137
+ let sqlite;
3138
+ try {
3139
+ sqlite = loadNodeSqlite();
3140
+ } catch (e) {
3141
+ throw new Error(
3142
+ `\u5F53\u524D Node \u4E0D\u652F\u6301 node:sqlite\uFF08\u9700 \u226522.5\uFF0C\u5B9E\u6D4B ${process.version}\uFF09\uFF1A${e instanceof Error ? e.message : String(e)}\uFF1B\u53EF\u6539\u7528 SQL \u6587\u4EF6\u624B\u5DE5\u5E94\u7528\uFF08npx prisma db execute --file <magicDir>/scripts/mcp-server/shared/memory/retrieval/fts/sqlite-fts5.sql --schema prisma/schema.prisma\uFF09`
3143
+ );
3144
+ }
3145
+ const db = new sqlite.DatabaseSync(dbPath);
3146
+ try {
3147
+ return fn(db);
3148
+ } finally {
3149
+ db.close();
3150
+ }
3151
+ }
3152
+ return {
3153
+ backend: "sqlite",
3154
+ source,
3155
+ listExisting: () => Promise.resolve().then(
3156
+ () => withDb((db) => {
3157
+ const rows = db.prepare("SELECT name FROM sqlite_master WHERE type IN ('table','trigger')").all();
3158
+ return new Set(rows.map((r) => r.name));
3159
+ })
3160
+ ),
3161
+ apply: (ddl) => Promise.resolve().then(
3162
+ () => withDb((db) => {
3163
+ db.exec(ddl);
3164
+ })
3165
+ )
3166
+ };
3167
+ }
3168
+ function readPrismaDatasourceProvider(projectRoot) {
3169
+ for (const rel of ["prisma/schema.prisma", "prisma/add.prisma"]) {
3170
+ const p = resolve13(projectRoot, rel);
3171
+ if (!existsSync17(p)) continue;
3172
+ const m = readFileSync13(p, "utf-8").match(/datasource\s+\w+\s*\{[\s\S]*?provider\s*=\s*"([^"]+)"/);
3173
+ if (m) return m[1];
3174
+ }
3175
+ return null;
3176
+ }
3177
+ function backendForProvider(provider) {
3178
+ const p = provider.trim().toLowerCase();
3179
+ if (p === "postgresql" || p === "postgres") return "postgres";
3180
+ if (p === "sqlite") return "sqlite";
3181
+ throw new Error(`\u4E0D\u652F\u6301\u7684 datasource provider: ${provider}\uFF08\u672C\u547D\u4EE4\u4EC5\u652F\u6301 postgresql / sqlite\uFF09`);
3182
+ }
3183
+ function resolveProjectDatabaseUrl(projectRoot, env = process.env) {
3184
+ if (env.DATABASE_URL) return env.DATABASE_URL;
3185
+ for (const rel of [".env.development", ".env"]) {
3186
+ const p = resolve13(projectRoot, rel);
3187
+ if (!existsSync17(p)) continue;
3188
+ const m = readFileSync13(p, "utf-8").match(/^\s*DATABASE_URL\s*=\s*["']?([^"'\n]+)["']?\s*$/m);
3189
+ if (m) return m[1].trim();
3190
+ }
3191
+ return null;
3192
+ }
3193
+ function createPrismaCliAdapter(opts) {
3194
+ const expected = opts.expected ?? PG_FTS_OBJECTS;
3195
+ const pm = detectPm(opts.projectRoot);
3196
+ const base = pm === "pnpm" ? ["exec", "prisma"] : ["exec", "prisma", "--"];
3197
+ const run = opts.run ?? ((argv, cwd) => runCommand(pm, argv, { cwd, timeout: opts.timeoutMs ?? 12e4 }));
3198
+ const source = opts.describeSource ?? "prisma db execute\uFF08\u9879\u76EE DATABASE_URL\uFF09";
3199
+ function executeSql(sql, tag) {
3200
+ const dir = mkdtempSync(join11(tmpdir(), "add-coder-fts-"));
3201
+ const file = join11(dir, `${tag}.sql`);
3202
+ writeFileSync9(file, sql, "utf-8");
3203
+ try {
3204
+ const r = run([...base, "db", "execute", "--file", file], opts.projectRoot);
3205
+ return { status: r.status, stderr: r.stderr ?? "" };
3206
+ } finally {
3207
+ rmSync2(dir, { recursive: true, force: true });
3208
+ }
3209
+ }
3210
+ return {
3211
+ backend: "postgres",
3212
+ source,
3213
+ listExisting: () => {
3214
+ const r = executeSql(renderPgProbeSql(expected), "probe");
3215
+ if (r.status === 0) return Promise.resolve(new Set(expected.map((s) => s.name)));
3216
+ const missing = parsePgProbeMissing(r.stderr, expected);
3217
+ if (missing.length > 0) {
3218
+ return Promise.resolve(new Set(expected.map((s) => s.name).filter((n) => !missing.includes(n))));
3219
+ }
3220
+ return Promise.reject(
3221
+ new Error(
3222
+ `PG FTS \u63A2\u6D4B\u5931\u8D25\uFF08\u9000\u51FA\u7801 ${r.status}\uFF09\uFF1A${r.stderr.trim().split("\n").slice(0, 3).join(" | ") || "\u65E0 stderr"}`
3223
+ )
3224
+ );
3225
+ },
3226
+ apply: (ddl) => {
3227
+ const r = executeSql(`${ddl}
3228
+ `, "apply");
3229
+ if (r.status === 0) return Promise.resolve();
3230
+ return Promise.reject(
3231
+ new Error(
3232
+ `PG FTS \u671F\u671B\u6001\u5E94\u7528\u5931\u8D25\uFF08\u9000\u51FA\u7801 ${r.status}\uFF09\uFF1A${r.stderr.trim().split("\n").slice(0, 3).join(" | ") || "\u65E0 stderr"}`
3233
+ )
3234
+ );
3235
+ }
3236
+ };
3237
+ }
3238
+
3239
+ // src/cli/commands/memory-reindex.ts
3240
+ function resolveAdapter3(projectRoot, options, deps) {
3241
+ const databaseUrl = resolveProjectDatabaseUrl(projectRoot, deps.env ?? process.env);
3242
+ let backend;
3243
+ if (options.backend) {
3244
+ backend = options.backend;
3245
+ } else {
3246
+ const provider = readPrismaDatasourceProvider(projectRoot);
3247
+ if (provider) backend = backendForProvider(provider);
3248
+ else if (databaseUrl) backend = detectBackend(databaseUrl);
3249
+ else {
3250
+ throw new Error(
3251
+ "\u65E0\u6CD5\u5224\u5B9A\u540E\u7AEF\uFF1A\u672A\u6307\u5B9A --backend\uFF0C\u4E14\u9879\u76EE\u5185\u65E2\u65E0 prisma/schema.prisma \u7684 datasource \u58F0\u660E\uFF0C\u4E5F\u65E0 .env.development \u7684 DATABASE_URL"
3252
+ );
3253
+ }
3254
+ }
3255
+ const magicDir = resolveMagicDir(projectRoot);
3256
+ if (backend === "sqlite") {
3257
+ if (!databaseUrl) {
3258
+ throw new Error(`SQLite \u540E\u7AEF\u9700\u8981 DATABASE_URL\uFF08.env.development \u6216\u8FDB\u7A0B\u73AF\u5883\uFF09\uFF1BmagicDir=${magicDir}`);
3259
+ }
3260
+ return createSqliteAdapter({ databaseUrl, projectRoot });
3261
+ }
3262
+ return createPrismaCliAdapter({
3263
+ projectRoot,
3264
+ run: deps.run,
3265
+ describeSource: databaseUrl ? `prisma db execute\uFF08DATABASE_URL=${maskUrl(databaseUrl)}\uFF09` : "prisma db execute\uFF08schema \u5185 env\uFF09"
3266
+ });
3267
+ }
3268
+ function maskUrl(url) {
3269
+ return url.replace(/:\/\/([^:/@]+):[^@]*@/, "://$1:***@");
3270
+ }
3271
+ function printReport(report, mode, io) {
3272
+ const head = mode === "apply" ? "\u8BB0\u5FC6 FTS \u671F\u671B\u6001 \xB7 \u91CD\u5EFA" : "\u8BB0\u5FC6 FTS \u671F\u671B\u6001 \xB7 \u63A2\u6D4B";
3273
+ io.log(`${head}\uFF08backend=${report.backend}\uFF0Csource=${report.source}\uFF09`);
3274
+ io.log(` \u5B8C\u6210\u5EA6 ${report.present}/${report.total}\uFF08${report.progress}%\uFF09`);
3275
+ if (mode === "apply" && report.rebuilt.length > 0) {
3276
+ io.log(` \u672C\u6B21\u91CD\u5EFA\uFF08${report.rebuilt.length}\uFF09\uFF1A${report.rebuilt.join(", ")}`);
3277
+ }
3278
+ if (report.missing.length > 0) {
3279
+ io.log(` \u7F3A\u5931\uFF08${report.missing.length}\uFF09\uFF1A${report.missing.join(", ")}`);
3280
+ io.log(mode === "apply" ? " \u274C \u91CD\u5EFA\u540E\u4ECD\u7F3A\u5931\u2014\u2014\u8BF7\u68C0\u67E5 DDL \u662F\u5426\u88AB\u62D2\uFF08\u6743\u9650/\u6269\u5C55\u4E0D\u53EF\u7528\uFF09" : " \u2192 \u4FEE\u590D\uFF1Aadd-coder memory:reindex --apply");
3281
+ } else {
3282
+ io.log(" \u2705 \u671F\u671B\u6001\u5B8C\u6574");
3283
+ }
3284
+ }
3285
+ async function memoryReindexCommand(options, deps = {}) {
3286
+ const io = deps.io ?? console;
3287
+ const projectRoot = options.cwd ?? process.cwd();
3288
+ if (options.apply && options.probe) {
3289
+ io.error(JSON.stringify({ ok: false, mode: "n/a", reason: "flags-conflict", detail: "--probe \u4E0E --apply \u4E92\u65A5\uFF08\u9ED8\u8BA4 probe\uFF09", exitCode: 2 }));
3290
+ return 2;
3291
+ }
3292
+ const mode = options.apply ? "apply" : "probe";
3293
+ try {
3294
+ const adapter = resolveAdapter3(projectRoot, options, deps);
3295
+ const report = mode === "apply" ? await reindexVia(adapter) : await probeVia(adapter);
3296
+ if (options.json) io.log(JSON.stringify(report, null, 2));
3297
+ else printReport(report, mode, io);
3298
+ if (mode === "probe") return 0;
3299
+ return report.missing.length > 0 ? 1 : 0;
3300
+ } catch (e) {
3301
+ const detail = e instanceof Error ? e.message : String(e);
3302
+ const context = { ok: false, mode, backend: options.backend ?? "auto", exitCode: 2, error: detail };
3303
+ io.error(
3304
+ options.json ? JSON.stringify(context) : `\u8BB0\u5FC6 FTS ${mode === "apply" ? "\u91CD\u5EFA" : "\u63A2\u6D4B"}\u5931\u8D25\uFF1A${detail}\uFF08backend=${context.backend}, exitCode=2\uFF09`
3305
+ );
3306
+ return 2;
3307
+ }
3308
+ }
3309
+
2941
3310
  // src/cli/index.ts
2942
3311
  var { version } = JSON.parse(
2943
- readFileSync13(new URL("../package.json", import.meta.url), "utf-8")
3312
+ readFileSync14(new URL("../package.json", import.meta.url), "utf-8")
2944
3313
  );
2945
3314
  async function modelDownloadCommand(options) {
2946
3315
  try {
@@ -2962,4 +3331,13 @@ program.command("stack").description("\u7BA1\u7406\u6280\u672F\u6808\u7EA6\u675F
2962
3331
  (sub, name, options) => stackCommand(sub, name, options)
2963
3332
  );
2964
3333
  program.command("model:download").description("\u9884\u4E0B\u8F7D embedding \u6A21\u578B\uFF08\u9996\u6B21 DPS \u8C03\u7528\u4F1A\u81EA\u52A8\u4E0B\u8F7D\uFF0C\u672C\u547D\u4EE4\u63D0\u524D\u62C9\u53D6\uFF09").option("--force", "\u5F3A\u5236\u91CD\u65B0\u4E0B\u8F7D\uFF08\u5373\u4F7F\u7F13\u5B58\u5DF2\u5B58\u5728\uFF09").action(modelDownloadCommand);
3334
+ program.command("memory:reindex").description("\u8BB0\u5FC6 FTS \u671F\u671B\u6001\u63A2\u6D4B/\u91CD\u5EFA\uFF08\u81EA\u52A9\u4FEE\u590D\u5165\u53E3\uFF1A\u7F3A\u5BF9\u8C61\u5219\u8865\u9F50\uFF0C\u53CC\u540E\u7AEF postgres/sqlite\uFF09").option("--probe", "\u53EA\u8BFB\u63A2\u6D4B\u7F3A\u5931\u5BF9\u8C61\uFF08\u9ED8\u8BA4\u884C\u4E3A\uFF09").option("--apply", "\u5E94\u7528\u7F3A\u5931\u5BF9\u8C61\u5E76\u590D\u63A2\uFF08\u4ECD\u6709\u7F3A\u5931\u5219\u9000\u51FA\u7801 1\uFF09").option("--backend <type>", "\u540E\u7AEF: postgres | sqlite\uFF08\u7F3A\u7701\u6309\u9879\u76EE Prisma datasource \u81EA\u52A8\u5224\u5B9A\uFF09").option("--json", "\u4EE5 JSON \u8F93\u51FA\u62A5\u544A\uFF08\u4F9B\u81EA\u52A8\u5316\u6D88\u8D39\uFF09").action(async (options) => {
3335
+ const backend = options.backend === "postgres" || options.backend === "sqlite" ? options.backend : void 0;
3336
+ if (options.backend && !backend) {
3337
+ console.error(`\u672A\u77E5 backend: ${options.backend}\uFF08\u53EF\u9009 postgres | sqlite\uFF09`);
3338
+ process.exitCode = 2;
3339
+ return;
3340
+ }
3341
+ process.exitCode = await memoryReindexCommand({ ...options, backend });
3342
+ });
2965
3343
  program.parse();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "add-coder",
3
- "version": "0.3.37",
3
+ "version": "0.3.38",
4
4
  "private": false,
5
5
  "description": "[codein2027](https://github.com/xiaomingming92/codein2027) - A complete scaffolding for building the ADD programming paradigm, the implementation layer for AI code governance. Core principle: Audit as Infrastructure. Breaks the black-box programming process and cross-session amnesia, evolving programming paradigms into an auditable, traceable, and convergent new era. npx-ready.\n\n[codein2027](https://github.com/xiaomingming92/codein2027) 快速构建 ADD 编程范式的完整脚手架——AI 代码治理的落地方案。以「审计即基础设施」为核心,彻底打破编程过程黑盒与跨轮失忆,让编程范式进化为可审计、可追溯、可收敛的新时代。npx 即用,人人可体验。",
6
6
  "repository": {
@@ -112,9 +112,13 @@
112
112
  "lint": "eslint src/",
113
113
  "lint:fix": "eslint src/ --fix",
114
114
  "generate": "tsx src/caijuehub/transcribe/index.ts",
115
+ "contributors": "tsx scripts/gen-contributors.ts --write",
116
+ "contributors:check": "tsx scripts/gen-contributors.ts --check",
117
+ "contributors:audit": "tsx scripts/gen-contributors.ts --check --audit",
115
118
  "podman-add-coder-up": "podman-compose --env-file .env.development -f podman-compose.add.yml up -d",
116
119
  "podman-add-coder-down": "podman-compose --env-file .env.development -f podman-compose.add.yml down",
117
120
  "test": "vitest run",
118
- "generate:check": "tsx scripts/generate-idempotency-check.ts"
121
+ "generate:check": "tsx scripts/generate-idempotency-check.ts",
122
+ "release:preflight": "tsx scripts/release-preflight.ts"
119
123
  }
120
124
  }
@@ -1,5 +1,5 @@
1
1
  {
2
- "_version": "0.3.37",
2
+ "_version": "0.3.38",
3
3
  "adapters/claude/hooks/doc-format-guard.mjs": "b21350a0",
4
4
  "adapters/claude/hooks/doc-format-guard.ts": "2abf8013",
5
5
  "adapters/claude/hooks/lib/claude-env.ts": "170b40ca",
@@ -229,7 +229,7 @@
229
229
  "core/rules/profiles/webapp-profile.md": "e493ae7b",
230
230
  "core/rules/project_rules.md": "094821ad",
231
231
  "core/rules/theory-practice-map.toml": "b1db99fd",
232
- "core/scripts/db-ensure.sh": "8f7ba569",
232
+ "core/scripts/db-ensure.sh": "d46a43c1",
233
233
  "core/scripts/gen-plan-index.sh": "47836bec",
234
234
  "core/scripts/mcp-server/elicitation/confirm.ts": "924857b8",
235
235
  "core/scripts/mcp-server/elicitation/index.ts": "44c5dc3f",
@@ -280,7 +280,7 @@
280
280
  "core/scripts/mcp-server/shared/memory/metrics/stage-words.ts": "a81db64b",
281
281
  "core/scripts/mcp-server/shared/memory/retrieval/context-builder.ts": "ea83c131",
282
282
  "core/scripts/mcp-server/shared/memory/retrieval/fts/pg.ts": "1275eef4",
283
- "core/scripts/mcp-server/shared/memory/retrieval/fts/sqlite-fts5.sql": "8fade96f",
283
+ "core/scripts/mcp-server/shared/memory/retrieval/fts/sqlite-fts5.sql": "1a439c1a",
284
284
  "core/scripts/mcp-server/shared/memory/retrieval/fts/sqlite.ts": "09a6cf59",
285
285
  "core/scripts/mcp-server/shared/memory/retrieval/fusion.ts": "c3aa6a4f",
286
286
  "core/scripts/mcp-server/shared/memory/retrieval/pipeline.ts": "dbe4a472",
@@ -323,8 +323,8 @@
323
323
  "core/scripts/mcp-server/tools/gateway/check_add_route_status.ts": "4caf552c",
324
324
  "core/scripts/mcp-server/tools/gateway/check_doc_similarity.ts": "2cba51c5",
325
325
  "core/scripts/mcp-server/tools/gateway/check_dps.ts": "f52ef32c",
326
- "core/scripts/mcp-server/tools/gateway/check_rahs.ts": "88d2c549",
327
- "core/scripts/mcp-server/tools/gateway/check_spec_sync.ts": "75c0d1e3",
326
+ "core/scripts/mcp-server/tools/gateway/check_rahs.ts": "a786744d",
327
+ "core/scripts/mcp-server/tools/gateway/check_spec_sync.ts": "adad0b31",
328
328
  "core/scripts/mcp-server/tools/gateway/helpers.ts": "f8794a92",
329
329
  "core/scripts/mcp-server/tools/gateway/index.ts": "cffe9783",
330
330
  "core/scripts/mcp-server/tools/gateway.backup": "65eae393",
@@ -37,8 +37,24 @@ EOF
37
37
  echo ">>> 已创建 .env.development"
38
38
  fi
39
39
 
40
- # ── SQLite:无需容器 ──
41
- if [ "$ENGINE" = "sqlite" ]; then exit 0; fi
40
+ # ── SQLite:无需容器;但 FTS5 原生层不在 Prisma schema 内,必须显式应用期望态 ──
41
+ # 背景(Plan add-coder-sqlite-memory-flow §3.2):`prisma db push` 只建 Prisma 表,
42
+ # `add_memory_fts` 虚表 + 3 个同步触发器是原生 DDL → 此前 sqlite 项目 recall_memory 直接
43
+ # `no such table`。此处与 init.ts(src/lib/memory-expected-state.ts)消费**同一份** SQL 真源。
44
+ # 失败语义:仅告警不阻断(exit 0 语义不变)——记忆检索是能力项,不该让环境准备硬失败。
45
+ if [ "$ENGINE" = "sqlite" ]; then
46
+ MAGIC_DIR="${MAGIC_DIR:-.codex}"
47
+ FTS_SQL="$MAGIC_DIR/scripts/mcp-server/shared/memory/retrieval/fts/sqlite-fts5.sql"
48
+ if [ -f "$PROJECT_DIR/$FTS_SQL" ]; then
49
+ echo ">>> 应用记忆 FTS5 期望态($FTS_SQL)..."
50
+ # Prisma 7:db execute 的 datasource 由项目 prisma.config.ts 提供,`--schema` 已移除(带=unknown option)
51
+ (cd "$PROJECT_DIR" && npm exec prisma -- db execute --file "$PROJECT_DIR/$FTS_SQL") \
52
+ || echo "!!! 记忆 FTS5 期望态应用失败(不阻断):recall_memory 将按 fts-only 降级;修复入口 add-coder memory:reindex --apply"
53
+ else
54
+ echo ">>> 未找到 $FTS_SQL(跳过,不阻断)——请先执行 add-coder sync"
55
+ fi
56
+ exit 0
57
+ fi
42
58
 
43
59
  # ── 自行管理 PostgreSQL ──
44
60
  if [ "$CONTAINER" = "none" ] || [ "$CONTAINER" = "manual" ]; then
@@ -1,3 +1,6 @@
1
+ -- 本文件由 scripts/memory/gen-sqlite-fts-sql.ts 生成(真源:src/lib/memory-fts-objects.ts 的 SQLITE_FTS_OBJECTS)
2
+ -- 请勿手工编辑;新增/修改 DDL 请改真源后重跑生成脚本。
3
+ --
1
4
  -- Agent Memory FTS 原生层(SQLite 后端)
2
5
  -- Plan §8.3 + §17-3 定案:FTS5 trigram 分词器(CJK 友好,≥3 字符 n-gram 匹配;
3
6
  -- 短于 3 字符的查询由 adapter 层回退 LIKE —— 见 retrieval/fts/sqlite.ts)
@@ -6,24 +9,10 @@
6
9
  -- (其要求 INTEGER rowid),故采用独立 FTS 表 + 触发器同步。
7
10
  -- 幂等:全部 IF NOT EXISTS,可重复应用。
8
11
 
9
- CREATE VIRTUAL TABLE IF NOT EXISTS add_memory_fts USING fts5(
10
- memory_id UNINDEXED,
11
- topic,
12
- content,
13
- tokenize = 'trigram'
14
- );
12
+ CREATE VIRTUAL TABLE IF NOT EXISTS add_memory_fts USING fts5(memory_id UNINDEXED, topic, content, tokenize = 'trigram');
15
13
 
16
- CREATE TRIGGER IF NOT EXISTS add_memory_fts_ai AFTER INSERT ON "AddMemory" BEGIN
17
- INSERT INTO add_memory_fts(memory_id, topic, content)
18
- VALUES (new.id, new.topic, new.content);
19
- END;
14
+ CREATE TRIGGER IF NOT EXISTS add_memory_fts_ai AFTER INSERT ON "AddMemory" BEGIN INSERT INTO add_memory_fts(memory_id, topic, content) VALUES (new.id, new.topic, new.content); END;
20
15
 
21
- CREATE TRIGGER IF NOT EXISTS add_memory_fts_au AFTER UPDATE ON "AddMemory" BEGIN
22
- DELETE FROM add_memory_fts WHERE memory_id = old.id;
23
- INSERT INTO add_memory_fts(memory_id, topic, content)
24
- VALUES (new.id, new.topic, new.content);
25
- END;
16
+ CREATE TRIGGER IF NOT EXISTS add_memory_fts_au AFTER UPDATE ON "AddMemory" BEGIN DELETE FROM add_memory_fts WHERE memory_id = old.id; INSERT INTO add_memory_fts(memory_id, topic, content) VALUES (new.id, new.topic, new.content); END;
26
17
 
27
- CREATE TRIGGER IF NOT EXISTS add_memory_fts_ad AFTER DELETE ON "AddMemory" BEGIN
28
- DELETE FROM add_memory_fts WHERE memory_id = old.id;
29
- END;
18
+ CREATE TRIGGER IF NOT EXISTS add_memory_fts_ad AFTER DELETE ON "AddMemory" BEGIN DELETE FROM add_memory_fts WHERE memory_id = old.id; END;
@@ -154,7 +154,8 @@ export function registerCheckRahs(server: ToolRegistrar) {
154
154
  arContent.match(/`[^`]+\.(ts|js|sh|md|tsx|json|yml|yaml)`/g) ||
155
155
  []
156
156
  ).map((f: string) => f.replace(/`/g, "").toLowerCase());
157
- const diff = runCommand("git", ["diff", "--name-only"], {
157
+ // core.quotepath=false:避免非 ASCII 路径被八进制转义后与附录清单比不中(与 check_spec_sync 同修)
158
+ const diff = runCommand("git", ["-c", "core.quotepath=false", "diff", "--name-only"], {
158
159
  cwd: PROJECT_ROOT,
159
160
  timeout: 5000,
160
161
  });
@@ -77,14 +77,18 @@ export function registerCheckSpecSync(server: ToolRegistrar) {
77
77
  lines.push(`add-route: ${arFile}`);
78
78
  const arContent = (await readFileSafe(join(plansDir, arFile))) || "";
79
79
  // 提取 add-route 附录文件清单(toml 纳入:sync-magic-rules.toml 等控制面文件,2026-08-18 修复)
80
- const appendixFiles = (arContent.match(/`[^`]+\.(ts|js|sh|md|tsx|json|yml|yaml|toml)`/g) || [])
80
+ // sql/prisma 纳入(2026-09-16 修复):原生 DDL 与 schema 是常见交付物,此前不在白名单 →
81
+ // 附录里明明登记了 `.../sqlite-fts5.sql` 仍被判"不在附录中"(假告警)。
82
+ const appendixFiles = (arContent.match(/`[^`]+\.(ts|tsx|js|jsx|sh|sql|prisma|md|json|yml|yaml|toml)`/g) || [])
81
83
  .map((f: string) => f.replace(/`/g, ""));
82
84
  lines.push(`附录文件: ${appendixFiles.length} 个`);
83
85
 
84
86
  // git diff 变更文件(win32 下 git 为 .cmd → runCommand 自动解析,issue #10 跨端修复)
85
87
  let diffFiles: string[] = [];
86
88
  try {
87
- const diff = runCommand("git", ["diff", "--name-only"], {
89
+ // core.quotepath=false:默认 git 会把非 ASCII 路径转成 "\346\236..." 八进制转义 →
90
+ // 与附录里的真实中文路径永远比不中(2026-09-16 修复:架构文档类路径的假告警)。
91
+ const diff = runCommand("git", ["-c", "core.quotepath=false", "diff", "--name-only"], {
88
92
  cwd: PROJECT_ROOT,
89
93
  timeout: 5000,
90
94
  });