@co-engram/core 0.1.0 → 0.1.1

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.
Files changed (113) hide show
  1. package/dist/i18n/en.d.ts.map +1 -1
  2. package/dist/i18n/en.js +1 -0
  3. package/dist/i18n/en.js.map +1 -1
  4. package/dist/i18n/zh.d.ts +1 -0
  5. package/dist/i18n/zh.d.ts.map +1 -1
  6. package/dist/i18n/zh.js +1 -0
  7. package/dist/i18n/zh.js.map +1 -1
  8. package/dist/index.d.ts +1 -0
  9. package/dist/index.d.ts.map +1 -1
  10. package/dist/index.js +1 -0
  11. package/dist/index.js.map +1 -1
  12. package/dist/merge/anomaly-detector.d.ts +63 -0
  13. package/dist/merge/anomaly-detector.d.ts.map +1 -0
  14. package/dist/merge/anomaly-detector.js +128 -0
  15. package/dist/merge/anomaly-detector.js.map +1 -0
  16. package/dist/merge/arbitration.d.ts +18 -0
  17. package/dist/merge/arbitration.d.ts.map +1 -0
  18. package/dist/merge/arbitration.js +27 -0
  19. package/dist/merge/arbitration.js.map +1 -0
  20. package/dist/merge/auto-onboard.d.ts +56 -0
  21. package/dist/merge/auto-onboard.d.ts.map +1 -0
  22. package/dist/merge/auto-onboard.js +81 -0
  23. package/dist/merge/auto-onboard.js.map +1 -0
  24. package/dist/merge/backup.d.ts +27 -0
  25. package/dist/merge/backup.d.ts.map +1 -0
  26. package/dist/merge/backup.js +49 -0
  27. package/dist/merge/backup.js.map +1 -0
  28. package/dist/merge/content.d.ts +50 -0
  29. package/dist/merge/content.d.ts.map +1 -0
  30. package/dist/merge/content.js +137 -0
  31. package/dist/merge/content.js.map +1 -0
  32. package/dist/merge/cross-file-coordinator.d.ts +52 -0
  33. package/dist/merge/cross-file-coordinator.d.ts.map +1 -0
  34. package/dist/merge/cross-file-coordinator.js +222 -0
  35. package/dist/merge/cross-file-coordinator.js.map +1 -0
  36. package/dist/merge/data-root.d.ts +9 -0
  37. package/dist/merge/data-root.d.ts.map +1 -0
  38. package/dist/merge/data-root.js +42 -0
  39. package/dist/merge/data-root.js.map +1 -0
  40. package/dist/merge/driver-llm.d.ts +92 -0
  41. package/dist/merge/driver-llm.d.ts.map +1 -0
  42. package/dist/merge/driver-llm.js +174 -0
  43. package/dist/merge/driver-llm.js.map +1 -0
  44. package/dist/merge/driver-main.d.ts +29 -0
  45. package/dist/merge/driver-main.d.ts.map +1 -0
  46. package/dist/merge/driver-main.js +220 -0
  47. package/dist/merge/driver-main.js.map +1 -0
  48. package/dist/merge/evidence-union.d.ts +35 -0
  49. package/dist/merge/evidence-union.d.ts.map +1 -0
  50. package/dist/merge/evidence-union.js +88 -0
  51. package/dist/merge/evidence-union.js.map +1 -0
  52. package/dist/merge/frontmatter-rules.d.ts +38 -0
  53. package/dist/merge/frontmatter-rules.d.ts.map +1 -0
  54. package/dist/merge/frontmatter-rules.js +77 -0
  55. package/dist/merge/frontmatter-rules.js.map +1 -0
  56. package/dist/merge/frontmatter.d.ts +52 -0
  57. package/dist/merge/frontmatter.d.ts.map +1 -0
  58. package/dist/merge/frontmatter.js +211 -0
  59. package/dist/merge/frontmatter.js.map +1 -0
  60. package/dist/merge/index.d.ts +31 -0
  61. package/dist/merge/index.d.ts.map +1 -0
  62. package/dist/merge/index.js +31 -0
  63. package/dist/merge/index.js.map +1 -0
  64. package/dist/merge/llm-arbiter.d.ts +85 -0
  65. package/dist/merge/llm-arbiter.d.ts.map +1 -0
  66. package/dist/merge/llm-arbiter.js +177 -0
  67. package/dist/merge/llm-arbiter.js.map +1 -0
  68. package/dist/merge/llm-contract.d.ts +86 -0
  69. package/dist/merge/llm-contract.d.ts.map +1 -0
  70. package/dist/merge/llm-contract.js +134 -0
  71. package/dist/merge/llm-contract.js.map +1 -0
  72. package/dist/merge/llm-prompt.d.ts +25 -0
  73. package/dist/merge/llm-prompt.d.ts.map +1 -0
  74. package/dist/merge/llm-prompt.js +73 -0
  75. package/dist/merge/llm-prompt.js.map +1 -0
  76. package/dist/merge/merge-engram.d.ts +33 -0
  77. package/dist/merge/merge-engram.d.ts.map +1 -0
  78. package/dist/merge/merge-engram.js +164 -0
  79. package/dist/merge/merge-engram.js.map +1 -0
  80. package/dist/merge/merge-stats.d.ts +68 -0
  81. package/dist/merge/merge-stats.d.ts.map +1 -0
  82. package/dist/merge/merge-stats.js +152 -0
  83. package/dist/merge/merge-stats.js.map +1 -0
  84. package/dist/merge/onboard.d.ts +72 -0
  85. package/dist/merge/onboard.d.ts.map +1 -0
  86. package/dist/merge/onboard.js +146 -0
  87. package/dist/merge/onboard.js.map +1 -0
  88. package/dist/merge/post-merge-hook.d.ts +77 -0
  89. package/dist/merge/post-merge-hook.d.ts.map +1 -0
  90. package/dist/merge/post-merge-hook.js +211 -0
  91. package/dist/merge/post-merge-hook.js.map +1 -0
  92. package/dist/merge/resolution-state.d.ts +41 -0
  93. package/dist/merge/resolution-state.d.ts.map +1 -0
  94. package/dist/merge/resolution-state.js +100 -0
  95. package/dist/merge/resolution-state.js.map +1 -0
  96. package/dist/merge/synapse-merger.d.ts +63 -0
  97. package/dist/merge/synapse-merger.d.ts.map +1 -0
  98. package/dist/merge/synapse-merger.js +277 -0
  99. package/dist/merge/synapse-merger.js.map +1 -0
  100. package/dist/merge/synapse-rules.d.ts +66 -0
  101. package/dist/merge/synapse-rules.d.ts.map +1 -0
  102. package/dist/merge/synapse-rules.js +112 -0
  103. package/dist/merge/synapse-rules.js.map +1 -0
  104. package/dist/merge/version.d.ts +10 -0
  105. package/dist/merge/version.d.ts.map +1 -0
  106. package/dist/merge/version.js +10 -0
  107. package/dist/merge/version.js.map +1 -0
  108. package/dist/merge-driver.cjs +9371 -0
  109. package/dist/observability/audit-log.d.ts +4 -1
  110. package/dist/observability/audit-log.d.ts.map +1 -1
  111. package/dist/observability/audit-log.js +3 -0
  112. package/dist/observability/audit-log.js.map +1 -1
  113. package/package.json +6 -2
@@ -0,0 +1,174 @@
1
+ /**
2
+ * Driver-side LLM client bootstrap (spec §5.1).
3
+ *
4
+ * 问题:host (claude-code-mcp / openclaw-plugin) 启动时已经构造了 LlmClient 实例,
5
+ * 但 git merge driver 是 git 直接 spawn 的独立进程,host 无法注入对象引用。
6
+ *
7
+ * 解决:host 把 LLM 配置(endpoint / apiKey / model)序列化到
8
+ * `~/.co-engram/llm-config.json`,driver 启动时读出来用 fetch 构造 HTTP client。
9
+ *
10
+ * 协议选 OpenAI-compatible(/chat/completions) —— 最通用:
11
+ * - OpenAI / Azure / 通义 / 智谱 / DeepSeek / 月之暗面 / ollama 原生支持
12
+ * - Anthropic 也有 OpenAI 兼容端点(https://api.anthropic.com/v1/openai/v1)
13
+ *
14
+ * @module @co-engram/core/merge
15
+ */
16
+ import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync, } from "node:fs";
17
+ import { dirname, join } from "node:path";
18
+ import { tmpdir, homedir } from "node:os";
19
+ /** 文件名(相对于 dataRoot/.co-engram/) */
20
+ const CONFIG_FILENAME = "llm-config.json";
21
+ /**
22
+ * 解析 config 文件路径。
23
+ *
24
+ * 优先级:
25
+ * 1. `$CO_ENGRAM_LLM_CONFIG`(绝对路径,测试用)
26
+ * 2. `$HOME/.co-engram/llm-config.json`(默认)
27
+ * 3. 退化:OS tmpdir(不推荐,仅当 HOME 缺失时)
28
+ */
29
+ export function resolveLlmConfigPath() {
30
+ const override = process.env.CO_ENGRAM_LLM_CONFIG;
31
+ if (override)
32
+ return override;
33
+ const home = process.env.HOME ?? process.env.USERPROFILE ?? homedir();
34
+ if (home)
35
+ return join(home, ".co-engram", CONFIG_FILENAME);
36
+ return join(tmpdir(), ".co-engram", CONFIG_FILENAME);
37
+ }
38
+ /**
39
+ * 写 LLM 配置到磁盘(host 启动时调用)。
40
+ *
41
+ * 不抛错 —— 写失败只警告,host 不应因 LLM 配置写盘失败而崩溃。
42
+ */
43
+ export function writeLlmClientConfig(config) {
44
+ try {
45
+ const path = resolveLlmConfigPath();
46
+ mkdirSync(dirname(path), { recursive: true });
47
+ const full = {
48
+ ...config,
49
+ writtenAt: new Date().toISOString(),
50
+ };
51
+ writeFileSync(path, JSON.stringify(full, null, 2), "utf8");
52
+ return { ok: true, path };
53
+ }
54
+ catch (e) {
55
+ return {
56
+ ok: false,
57
+ error: e instanceof Error ? e.message : String(e),
58
+ };
59
+ }
60
+ }
61
+ /**
62
+ * 读 LLM 配置(driver 启动时调用)。
63
+ *
64
+ * 不抛错 —— 读失败/文件缺失都返回 undefined,driver 退化为 no-LLM 模式。
65
+ */
66
+ export function readLlmClientConfig() {
67
+ try {
68
+ const path = resolveLlmConfigPath();
69
+ if (!existsSync(path))
70
+ return undefined;
71
+ const raw = readFileSync(path, "utf8");
72
+ const parsed = JSON.parse(raw);
73
+ if (typeof parsed.endpoint !== "string" ||
74
+ typeof parsed.apiKey !== "string" ||
75
+ typeof parsed.model !== "string") {
76
+ return undefined;
77
+ }
78
+ return parsed;
79
+ }
80
+ catch {
81
+ return undefined;
82
+ }
83
+ }
84
+ /**
85
+ * 删除 LLM 配置(uninstall 时调用)。
86
+ *
87
+ * 用于 host 完全卸载 merge driver 时清理敏感数据。
88
+ * 失败不抛错,只返回 status。
89
+ */
90
+ export function clearLlmClientConfig() {
91
+ try {
92
+ const path = resolveLlmConfigPath();
93
+ if (!existsSync(path))
94
+ return { ok: true, path };
95
+ rmSync(path, { force: true });
96
+ return { ok: true, path };
97
+ }
98
+ catch (e) {
99
+ return {
100
+ ok: false,
101
+ error: e instanceof Error ? e.message : String(e),
102
+ };
103
+ }
104
+ }
105
+ /**
106
+ * 把任意 endpoint 规范化为完整 chat completions URL。
107
+ *
108
+ * - 已是 /chat/completions 结尾 → 不变
109
+ * - 是 base URL(如 https://api.openai.com/v1)→ 自动追加 /chat/completions
110
+ */
111
+ function normalizeEndpoint(endpoint) {
112
+ const trimmed = endpoint.replace(/\/+$/, "");
113
+ if (/\/chat\/completions$/.test(trimmed))
114
+ return trimmed;
115
+ return `${trimmed}/chat/completions`;
116
+ }
117
+ /**
118
+ * 创建 OpenAI-compatible HTTP LlmClient(基于 fetch)。
119
+ *
120
+ * 任何抛错都让调用方(LlmArbiter)捕获并降级到 escalate。
121
+ */
122
+ export function createHttpLlmClient(config) {
123
+ const endpoint = normalizeEndpoint(config.endpoint);
124
+ return {
125
+ async complete(prompt, opts = {}) {
126
+ const headers = {
127
+ "Content-Type": "application/json",
128
+ Authorization: `Bearer ${config.apiKey}`,
129
+ ...(config.headers ?? {}),
130
+ };
131
+ const body = {
132
+ model: config.model,
133
+ temperature: opts.temperature ?? 0.1,
134
+ max_tokens: opts.maxTokens ?? 300,
135
+ messages: [{ role: "user", content: prompt }],
136
+ };
137
+ const resp = await fetch(endpoint, {
138
+ method: "POST",
139
+ headers,
140
+ body: JSON.stringify(body),
141
+ signal: AbortSignal.timeout(opts.timeoutMs ?? 15_000),
142
+ });
143
+ if (!resp.ok) {
144
+ const text = await resp.text();
145
+ throw new Error(`LLM call failed (${resp.status}): ${text.slice(0, 200)}`);
146
+ }
147
+ const json = (await resp.json());
148
+ const choice = json.choices?.[0]?.message;
149
+ // reasoning 模型 fallback:某些 endpoint 把内容塞到 reasoning_content
150
+ const content = choice?.content ?? choice?.reasoning_content ?? "";
151
+ if (!content) {
152
+ throw new Error("LLM returned empty content");
153
+ }
154
+ return content;
155
+ },
156
+ };
157
+ }
158
+ /**
159
+ * Driver 启动时的便捷入口:读 config + 构造 client。
160
+ *
161
+ * 失败返回 null,driver 退化为 no-LLM 模式(机械规则解决不了的冲突 → marker)。
162
+ */
163
+ export function createDriverLlmClient() {
164
+ const config = readLlmClientConfig();
165
+ if (!config)
166
+ return null;
167
+ try {
168
+ return { client: createHttpLlmClient(config), config };
169
+ }
170
+ catch {
171
+ return null;
172
+ }
173
+ }
174
+ //# sourceMappingURL=driver-llm.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"driver-llm.js","sourceRoot":"","sources":["../../src/merge/driver-llm.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,29 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Git merge driver CLI entry.
4
+ *
5
+ * Git invokes: `node driver.js %O %A %B %L %P`
6
+ * %O = base (common ancestor) argv[2]
7
+ * %A = ours (also the output target) argv[3]
8
+ * %B = theirs argv[4]
9
+ * %L = conflict marker size argv[5]
10
+ * %P = repo-relative path argv[6]
11
+ *
12
+ * Routing:
13
+ * - isEngramFile(content) → mergeEngramFile
14
+ * - synapse path (starts with 'synapses/' + ends '.yaml') → mergeSynapseFile
15
+ * - everything else → transparent git merge-file fallback
16
+ *
17
+ * Behavior:
18
+ * - Escalation → write conflict markers + exit 1.
19
+ * - Any thrown error → write conflict markers + exit 1.
20
+ *
21
+ * @module @co-engram/core/merge
22
+ */
23
+ import { DRIVER_BUNDLE_VERSION } from "./version.js";
24
+ export declare function runDriver(argv: string[]): Promise<{
25
+ exitCode: number;
26
+ stderr?: string;
27
+ }>;
28
+ export { DRIVER_BUNDLE_VERSION };
29
+ //# sourceMappingURL=driver-main.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"driver-main.d.ts","sourceRoot":"","sources":["../../src/merge/driver-main.ts"],"names":[],"mappings":";AACA;;;;;;;;;;;;;;;;;;;;GAoBG;AASH,OAAO,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAC;AAqBrD,wBAAsB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC;IACvD,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,CAAC,CAoHD;AAoGD,OAAO,EAAE,qBAAqB,EAAE,CAAC"}
@@ -0,0 +1,220 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Git merge driver CLI entry.
4
+ *
5
+ * Git invokes: `node driver.js %O %A %B %L %P`
6
+ * %O = base (common ancestor) argv[2]
7
+ * %A = ours (also the output target) argv[3]
8
+ * %B = theirs argv[4]
9
+ * %L = conflict marker size argv[5]
10
+ * %P = repo-relative path argv[6]
11
+ *
12
+ * Routing:
13
+ * - isEngramFile(content) → mergeEngramFile
14
+ * - synapse path (starts with 'synapses/' + ends '.yaml') → mergeSynapseFile
15
+ * - everything else → transparent git merge-file fallback
16
+ *
17
+ * Behavior:
18
+ * - Escalation → write conflict markers + exit 1.
19
+ * - Any thrown error → write conflict markers + exit 1.
20
+ *
21
+ * @module @co-engram/core/merge
22
+ */
23
+ import { readFileSync, writeFileSync } from "node:fs";
24
+ import { spawnSync } from "node:child_process";
25
+ import { isEngramFile } from "../storage/engram-store.js";
26
+ import { mergeEngramFile } from "./merge-engram.js";
27
+ import { mergeSynapseFile, mergeSynapseFileAsync } from "./synapse-merger.js";
28
+ import { findDataRoot } from "./data-root.js";
29
+ import { AuditLog } from "../observability/audit-log.js";
30
+ import { DRIVER_BUNDLE_VERSION } from "./version.js";
31
+ import { LlmArbiter } from "./llm-arbiter.js";
32
+ import { createDriverLlmClient } from "./driver-llm.js";
33
+ const isMain = typeof require !== "undefined" &&
34
+ require.main &&
35
+ typeof __filename !== "undefined" &&
36
+ require.main.filename === __filename;
37
+ const USAGE = "usage: co-engram-merge-driver %O %A %B %L %P";
38
+ /** Heuristic: synapse files live under synapses/ and have .yaml extension. */
39
+ function isSynapsePath(relPath) {
40
+ const normalized = relPath.replace(/\\/g, "/");
41
+ return normalized.startsWith("synapses/") && normalized.endsWith(".yaml");
42
+ }
43
+ export async function runDriver(argv) {
44
+ const args = argv.slice(2);
45
+ if (args.length < 5) {
46
+ return { exitCode: 1, stderr: USAGE };
47
+ }
48
+ const [baseP, oursP, theirsP, markerSizeStr, pathArg] = args;
49
+ const markerSize = parseInt(markerSizeStr, 10) || 7;
50
+ let baseRaw, oursRaw, theirsRaw;
51
+ try {
52
+ baseRaw = readFileSync(baseP, "utf8");
53
+ oursRaw = readFileSync(oursP, "utf8");
54
+ theirsRaw = readFileSync(theirsP, "utf8");
55
+ }
56
+ catch (e) {
57
+ return {
58
+ exitCode: 1,
59
+ stderr: `co-engram-merge-driver: failed to read input files: ${e instanceof Error ? e.message : String(e)}`,
60
+ };
61
+ }
62
+ // Route: engram vs synapse vs non-engram
63
+ const isEngram = isEngramFile(oursRaw) || isEngramFile(baseRaw) || isEngramFile(theirsRaw);
64
+ if (!isEngram && isSynapsePath(pathArg)) {
65
+ return await runSynapseMerge({
66
+ baseP,
67
+ oursP,
68
+ baseRaw,
69
+ oursRaw,
70
+ theirsRaw,
71
+ pathArg,
72
+ });
73
+ }
74
+ if (!isEngram) {
75
+ // Transparent fallback: let git merge-file do its thing
76
+ const result = spawnSync("git", [
77
+ "merge-file",
78
+ "-p",
79
+ `--marker-size=${markerSize}`,
80
+ oursP,
81
+ baseP,
82
+ theirsP,
83
+ ], { encoding: "utf8" });
84
+ if (typeof result.stdout === "string") {
85
+ writeFileSync(oursP, result.stdout, "utf8");
86
+ }
87
+ // exit status: 0 = clean, >0 = conflict count, null = error
88
+ return { exitCode: result.status ?? 1 };
89
+ }
90
+ // Engram merge
91
+ let dataRoot = null;
92
+ try {
93
+ dataRoot = findDataRoot(oursP);
94
+ }
95
+ catch {
96
+ dataRoot = null;
97
+ }
98
+ const auditLog = dataRoot ? new AuditLog(dataRoot) : undefined;
99
+ // Construct LLM arbiter if config available (Layer B, spec §5.6)
100
+ const llmBootstrap = createDriverLlmClient();
101
+ const llmArbiter = llmBootstrap
102
+ ? new LlmArbiter({
103
+ client: llmBootstrap.client,
104
+ auditLog,
105
+ providerName: llmBootstrap.config.model,
106
+ })
107
+ : undefined;
108
+ try {
109
+ const result = await mergeEngramFile({
110
+ baseRaw,
111
+ oursRaw,
112
+ theirsRaw,
113
+ relPath: pathArg,
114
+ dataRoot: dataRoot ?? undefined,
115
+ auditLog,
116
+ llmArbiter,
117
+ });
118
+ writeFileSync(oursP, result.mergedContent, "utf8");
119
+ if (result.escalated) {
120
+ auditLog?.append({
121
+ actor: "system",
122
+ action: "merge_conflict_escalated",
123
+ metadata: {
124
+ path: pathArg,
125
+ reason: result.strategy,
126
+ },
127
+ });
128
+ return { exitCode: 1 };
129
+ }
130
+ return { exitCode: 0 };
131
+ }
132
+ catch (e) {
133
+ return handleDriverError(e, {
134
+ oursP,
135
+ oursRaw,
136
+ theirsRaw,
137
+ pathArg,
138
+ auditLog,
139
+ });
140
+ }
141
+ }
142
+ async function runSynapseMerge(params) {
143
+ const { oursP, baseRaw, oursRaw, theirsRaw, pathArg } = params;
144
+ let dataRoot = null;
145
+ try {
146
+ dataRoot = findDataRoot(params.baseP);
147
+ }
148
+ catch {
149
+ dataRoot = null;
150
+ }
151
+ const auditLog = dataRoot ? new AuditLog(dataRoot) : undefined;
152
+ // Construct LLM arbiter if config available (Layer B)
153
+ const llmBootstrap = createDriverLlmClient();
154
+ const llmArbiter = llmBootstrap
155
+ ? new LlmArbiter({
156
+ client: llmBootstrap.client,
157
+ auditLog,
158
+ providerName: llmBootstrap.config.model,
159
+ })
160
+ : undefined;
161
+ try {
162
+ const result = llmArbiter
163
+ ? await mergeSynapseFileAsync({
164
+ baseRaw,
165
+ oursRaw,
166
+ theirsRaw,
167
+ arbiter: llmArbiter,
168
+ path: pathArg,
169
+ })
170
+ : mergeSynapseFile({ baseRaw, oursRaw, theirsRaw });
171
+ writeFileSync(oursP, result.mergedContent, "utf8");
172
+ if (result.escalated) {
173
+ auditLog?.append({
174
+ actor: "system",
175
+ action: "merge_conflict_escalated",
176
+ metadata: { path: pathArg, reason: result.strategy },
177
+ });
178
+ return { exitCode: 1 };
179
+ }
180
+ auditLog?.append({
181
+ actor: "system",
182
+ action: "merge_resolved",
183
+ metadata: {
184
+ path: pathArg,
185
+ reason: result.strategy,
186
+ },
187
+ });
188
+ return { exitCode: 0 };
189
+ }
190
+ catch (e) {
191
+ return handleDriverError(e, {
192
+ oursP,
193
+ oursRaw,
194
+ theirsRaw,
195
+ pathArg,
196
+ auditLog,
197
+ });
198
+ }
199
+ }
200
+ function handleDriverError(e, params) {
201
+ const { oursP, oursRaw, theirsRaw, pathArg, auditLog } = params;
202
+ const msg = e instanceof Error ? e.message : String(e);
203
+ const wrapped = `<<<<<<< ours\n${oursRaw}\n=======\n${theirsRaw}\n>>>>>>> theirs\n`;
204
+ writeFileSync(oursP, wrapped, "utf8");
205
+ auditLog?.append({
206
+ actor: "system",
207
+ action: "merge_conflict_escalated",
208
+ metadata: { path: pathArg, reason: `driver-error: ${msg}` },
209
+ });
210
+ return { exitCode: 1, stderr: `co-engram-merge-driver: ${msg}` };
211
+ }
212
+ // Entry point (CJS-compatible after esbuild bundle).
213
+ if (isMain) {
214
+ runDriver(process.argv).then(({ exitCode }) => {
215
+ process.exitCode = exitCode;
216
+ });
217
+ }
218
+ // Re-export for introspection by the bundle
219
+ export { DRIVER_BUNDLE_VERSION };
220
+ //# sourceMappingURL=driver-main.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"driver-main.js","sourceRoot":"","sources":["../../src/merge/driver-main.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Evidence union algorithm (spec §6.2).
3
+ *
4
+ * Evidence is append-only — both sides' additions should be preserved.
5
+ *
6
+ * Dedupe key: `${description}::${addedBy}`. When the same person adds the
7
+ * same description multiple times, keep the entry with the latest addedAt.
8
+ *
9
+ * Contradictory evidence ("because X" vs "because not X") produces
10
+ * DIFFERENT keys, so both are preserved — caller (LLM arbiter) decides.
11
+ *
12
+ * @module @co-engram/core/merge
13
+ */
14
+ import type { SynapseEvidence } from "../types/synapse.js";
15
+ /**
16
+ * Compute the dedupe key for an evidence entry.
17
+ *
18
+ * Spec §6.4 edge case: if addedBy is missing (legacy data), the key
19
+ * degenerates to `${description}::` so we still dedupe by description alone.
20
+ */
21
+ export declare function evidenceKey(e: SynapseEvidence): string;
22
+ /**
23
+ * Merge three evidence arrays via spec §6.2 union algorithm:
24
+ *
25
+ * 1. Compute oursAdded = diffByDescAndAuthor(ours, base)
26
+ * 2. Compute theirsAdded = diffByDescAndAuthor(theirs, base)
27
+ * 3. all = [...base, ...oursAdded, ...theirsAdded]
28
+ * 4. dedupe: same key → keep latest addedAt
29
+ */
30
+ export declare function mergeEvidence(params: {
31
+ base: readonly SynapseEvidence[];
32
+ ours: readonly SynapseEvidence[];
33
+ theirs: readonly SynapseEvidence[];
34
+ }): SynapseEvidence[];
35
+ //# sourceMappingURL=evidence-union.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"evidence-union.d.ts","sourceRoot":"","sources":["../../src/merge/evidence-union.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAE3D;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,CAAC,EAAE,eAAe,GAAG,MAAM,CAEtD;AAsCD;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE;IACpC,IAAI,EAAE,SAAS,eAAe,EAAE,CAAC;IACjC,IAAI,EAAE,SAAS,eAAe,EAAE,CAAC;IACjC,MAAM,EAAE,SAAS,eAAe,EAAE,CAAC;CACpC,GAAG,eAAe,EAAE,CA2BpB"}
@@ -0,0 +1,88 @@
1
+ /**
2
+ * Evidence union algorithm (spec §6.2).
3
+ *
4
+ * Evidence is append-only — both sides' additions should be preserved.
5
+ *
6
+ * Dedupe key: `${description}::${addedBy}`. When the same person adds the
7
+ * same description multiple times, keep the entry with the latest addedAt.
8
+ *
9
+ * Contradictory evidence ("because X" vs "because not X") produces
10
+ * DIFFERENT keys, so both are preserved — caller (LLM arbiter) decides.
11
+ *
12
+ * @module @co-engram/core/merge
13
+ */
14
+ /**
15
+ * Compute the dedupe key for an evidence entry.
16
+ *
17
+ * Spec §6.4 edge case: if addedBy is missing (legacy data), the key
18
+ * degenerates to `${description}::` so we still dedupe by description alone.
19
+ */
20
+ export function evidenceKey(e) {
21
+ return `${e.description}::${e.addedBy ?? ""}`;
22
+ }
23
+ /**
24
+ * Diff `next` against `prev` by evidence key, returning only entries
25
+ * that are new (not present in prev, or present but with older addedAt).
26
+ */
27
+ function diffByDescAndAuthor(next, prev) {
28
+ const prevMap = new Map();
29
+ for (const e of prev)
30
+ prevMap.set(evidenceKey(e), e);
31
+ const added = [];
32
+ for (const e of next) {
33
+ const existing = prevMap.get(evidenceKey(e));
34
+ if (!existing) {
35
+ added.push(e);
36
+ }
37
+ else if ((e.addedAt ?? "") > (existing.addedAt ?? "") &&
38
+ // Don't emit if value is otherwise identical (avoid no-op writes)
39
+ !evidenceEqual(e, existing)) {
40
+ added.push(e);
41
+ }
42
+ }
43
+ return added;
44
+ }
45
+ function evidenceEqual(a, b) {
46
+ return (a.description === b.description &&
47
+ a.addedBy === b.addedBy &&
48
+ a.addedAt === b.addedAt &&
49
+ a.source === b.source &&
50
+ a.confidence === b.confidence);
51
+ }
52
+ /**
53
+ * Merge three evidence arrays via spec §6.2 union algorithm:
54
+ *
55
+ * 1. Compute oursAdded = diffByDescAndAuthor(ours, base)
56
+ * 2. Compute theirsAdded = diffByDescAndAuthor(theirs, base)
57
+ * 3. all = [...base, ...oursAdded, ...theirsAdded]
58
+ * 4. dedupe: same key → keep latest addedAt
59
+ */
60
+ export function mergeEvidence(params) {
61
+ const { base, ours, theirs } = params;
62
+ const oursAdded = diffByDescAndAuthor(ours, base);
63
+ const theirsAdded = diffByDescAndAuthor(theirs, base);
64
+ const all = [...base, ...oursAdded, ...theirsAdded];
65
+ const byKey = new Map();
66
+ for (const e of all) {
67
+ const key = evidenceKey(e);
68
+ const existing = byKey.get(key);
69
+ if (!existing) {
70
+ byKey.set(key, e);
71
+ continue;
72
+ }
73
+ // Same key → keep entry with newer addedAt (or first-seen if tie)
74
+ if ((e.addedAt ?? "") > (existing.addedAt ?? "")) {
75
+ byKey.set(key, e);
76
+ }
77
+ }
78
+ // Sort for stable output: by addedAt ascending, then description, then key.
79
+ // This decouples result order from input order (spec §6.4 — field-order
80
+ // differences between serializers must not produce false conflicts).
81
+ return Array.from(byKey.values()).sort((a, b) => {
82
+ const byTime = (a.addedAt ?? "").localeCompare(b.addedAt ?? "");
83
+ if (byTime !== 0)
84
+ return byTime;
85
+ return evidenceKey(a).localeCompare(evidenceKey(b));
86
+ });
87
+ }
88
+ //# sourceMappingURL=evidence-union.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"evidence-union.js","sourceRoot":"","sources":["../../src/merge/evidence-union.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Frontmatter 字段分类 + 单字段合并规则
3
+ *
4
+ * 字段语义分类见 spec §4.2。每种分类有独立的合并规则。
5
+ * updatedAt_arbitrated 字段不在此处处理(由 arbitration.ts 接管)。
6
+ *
7
+ * @module @co-engram/core/merge
8
+ */
9
+ export type FieldClass = "immutable" | "additive" | "max" | "updatedAt_arbitrated" | "recomputed" | "legacy_derived";
10
+ export declare function classifyField(fieldName: string): FieldClass;
11
+ export interface SimpleMergeResult {
12
+ readonly value: unknown;
13
+ readonly changed: boolean;
14
+ }
15
+ export declare class ImmutableViolationError extends Error {
16
+ readonly fieldName: string;
17
+ readonly base: unknown;
18
+ readonly ours: unknown;
19
+ readonly theirs: unknown;
20
+ constructor(fieldName: string, base: unknown, ours: unknown, theirs: unknown);
21
+ }
22
+ export declare function mergeImmutableField(params: {
23
+ base: unknown;
24
+ ours: unknown;
25
+ theirs: unknown;
26
+ fieldName: string;
27
+ }): SimpleMergeResult;
28
+ export declare function mergeAdditiveField(params: {
29
+ base: number | undefined;
30
+ ours: number | undefined;
31
+ theirs: number | undefined;
32
+ }): SimpleMergeResult;
33
+ export declare function mergeMaxField(params: {
34
+ base: number | string;
35
+ ours: number | string;
36
+ theirs: number | string;
37
+ }): SimpleMergeResult;
38
+ //# sourceMappingURL=frontmatter-rules.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"frontmatter-rules.d.ts","sourceRoot":"","sources":["../../src/merge/frontmatter-rules.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,MAAM,MAAM,UAAU,GAClB,WAAW,GACX,UAAU,GACV,KAAK,GACL,sBAAsB,GACtB,YAAY,GACZ,gBAAgB,CAAC;AAuBrB,wBAAgB,aAAa,CAAC,SAAS,EAAE,MAAM,GAAG,UAAU,CAO3D;AAED,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;IACxB,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;CAC3B;AAED,qBAAa,uBAAwB,SAAQ,KAAK;aAE9B,SAAS,EAAE,MAAM;aACjB,IAAI,EAAE,OAAO;aACb,IAAI,EAAE,OAAO;aACb,MAAM,EAAE,OAAO;gBAHf,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,OAAO,EACb,IAAI,EAAE,OAAO,EACb,MAAM,EAAE,OAAO;CAOlC;AAED,wBAAgB,mBAAmB,CAAC,MAAM,EAAE;IAC1C,IAAI,EAAE,OAAO,CAAC;IACd,IAAI,EAAE,OAAO,CAAC;IACd,MAAM,EAAE,OAAO,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;CACnB,GAAG,iBAAiB,CAMpB;AAED,wBAAgB,kBAAkB,CAAC,MAAM,EAAE;IACzC,IAAI,EAAE,MAAM,GAAG,SAAS,CAAC;IACzB,IAAI,EAAE,MAAM,GAAG,SAAS,CAAC;IACzB,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC;CAC5B,GAAG,iBAAiB,CAOpB;AAED,wBAAgB,aAAa,CAAC,MAAM,EAAE;IACpC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC;IACtB,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC;IACtB,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC;CACzB,GAAG,iBAAiB,CAKpB"}