@riconext/hermes-repo 1.1.0 → 1.2.0

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/dist/cli.js CHANGED
@@ -102,14 +102,26 @@ function parseLlmConfig(raw) {
102
102
  const llm = raw.llm;
103
103
  return {
104
104
  enabled: typeof llm?.enabled === "boolean" ? llm.enabled : false,
105
+ provider: typeof llm?.provider === "string" ? llm.provider : "openai",
105
106
  baseUrl: typeof llm?.baseUrl === "string" ? llm.baseUrl : "https://api.openai.com/v1",
106
- model: typeof llm?.model === "string" ? llm.model : "gpt-4o"
107
+ model: typeof llm?.model === "string" ? llm.model : "gpt-4o",
108
+ apiKey: typeof llm?.apiKey === "string" ? llm.apiKey : "",
109
+ timeoutMs: typeof llm?.timeoutMs === "number" && llm.timeoutMs > 0 ? llm.timeoutMs : 6e4,
110
+ maxInputChars: typeof llm?.maxInputChars === "number" && llm.maxInputChars > 0 ? llm.maxInputChars : 24e3,
111
+ mode: llm?.mode === "sync" ? "sync" : "async"
107
112
  };
108
113
  }
109
114
  function parseConsolidateConfig(raw) {
110
115
  const c = raw.consolidate;
116
+ const autoFlush = c?.autoFlush && typeof c.autoFlush === "object" && !Array.isArray(c.autoFlush) ? c.autoFlush : {};
111
117
  return {
112
- autoArchiveDays: typeof c?.autoArchiveDays === "number" ? c.autoArchiveDays : 30
118
+ autoArchiveDays: typeof c?.autoArchiveDays === "number" ? c.autoArchiveDays : 30,
119
+ autoFlush: {
120
+ enabled: autoFlush.enabled === true,
121
+ minPendingSessions: typeof autoFlush.minPendingSessions === "number" && autoFlush.minPendingSessions > 0 ? autoFlush.minPendingSessions : 3,
122
+ minIntervalMinutes: typeof autoFlush.minIntervalMinutes === "number" && autoFlush.minIntervalMinutes > 0 ? autoFlush.minIntervalMinutes : 30,
123
+ maxPendingChars: typeof autoFlush.maxPendingChars === "number" && autoFlush.maxPendingChars > 0 ? autoFlush.maxPendingChars : 2e4
124
+ }
113
125
  };
114
126
  }
115
127
  function readConfigAtRepo(repoRoot) {
@@ -136,8 +148,25 @@ function readConfigAtRepo(repoRoot) {
136
148
  storage: { backend: "file" },
137
149
  assistants,
138
150
  debug: raw.debug === true,
139
- llm: { enabled: false, baseUrl: "https://api.openai.com/v1", model: "gpt-4o" },
140
- consolidate: { autoArchiveDays: 30 }
151
+ llm: {
152
+ enabled: false,
153
+ provider: "openai",
154
+ baseUrl: "https://api.openai.com/v1",
155
+ model: "gpt-4o",
156
+ apiKey: "",
157
+ timeoutMs: 6e4,
158
+ maxInputChars: 24e3,
159
+ mode: "async"
160
+ },
161
+ consolidate: {
162
+ autoArchiveDays: 30,
163
+ autoFlush: {
164
+ enabled: false,
165
+ minPendingSessions: 3,
166
+ minIntervalMinutes: 30,
167
+ maxPendingChars: 2e4
168
+ }
169
+ }
141
170
  };
142
171
  }
143
172
  return null;
@@ -158,12 +187,10 @@ function loadRepoContext(cwd) {
158
187
  }
159
188
 
160
189
  // src/capture/runLlmJob.ts
161
- import { existsSync as existsSync5, readFileSync as readFileSync6, renameSync, writeFileSync as writeFileSync3 } from "fs";
190
+ import { existsSync as existsSync4, readFileSync as readFileSync5, renameSync, writeFileSync as writeFileSync3 } from "fs";
162
191
  import { join as join6 } from "path";
163
192
 
164
193
  // src/config/llmConfig.ts
165
- var DEFAULT_LLM_TIMEOUT_MS = 6e4;
166
- var DEFAULT_LLM_MAX_INPUT_CHARS = 24e3;
167
194
  function isLlmAvailable(cfg) {
168
195
  if (!cfg?.enabled) {
169
196
  return false;
@@ -171,52 +198,11 @@ function isLlmAvailable(cfg) {
171
198
  return Boolean(cfg.apiKey?.trim()) && Boolean(cfg.baseUrl?.trim()) && Boolean(cfg.model?.trim());
172
199
  }
173
200
  function effectiveLlmMode(cfg) {
174
- const forceSync = process.env.HERMES_LLM_SYNC;
175
- if (forceSync === "1" || forceSync === "true") {
176
- return "sync";
177
- }
178
201
  return cfg.mode === "sync" ? "sync" : "async";
179
202
  }
180
- function parseLlmConfigRaw(raw) {
181
- if (raw.enabled !== true && raw.enabled !== false) {
182
- return null;
183
- }
184
- const baseUrl = typeof raw.baseUrl === "string" ? raw.baseUrl : "";
185
- const model = typeof raw.model === "string" ? raw.model : "";
186
- const apiKey = typeof raw.apiKey === "string" ? raw.apiKey : "";
187
- const timeoutMs = typeof raw.timeoutMs === "number" && raw.timeoutMs > 0 ? raw.timeoutMs : DEFAULT_LLM_TIMEOUT_MS;
188
- const maxInputChars = typeof raw.maxInputChars === "number" && raw.maxInputChars > 0 ? raw.maxInputChars : DEFAULT_LLM_MAX_INPUT_CHARS;
189
- const mode = raw.mode === "sync" ? "sync" : "async";
190
- const provider = typeof raw.provider === "string" ? raw.provider : "openai";
191
- return {
192
- enabled: raw.enabled,
193
- provider,
194
- baseUrl,
195
- model,
196
- apiKey,
197
- timeoutMs,
198
- maxInputChars,
199
- mode
200
- };
201
- }
202
-
203
- // src/config/readLlmConfig.ts
204
- import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
205
- function readLlmConfigAtRepo(repoRoot) {
206
- const llmPath = memoryPath(repoRoot, "llm.json");
207
- if (!existsSync2(llmPath)) {
208
- return null;
209
- }
210
- try {
211
- const raw = JSON.parse(readFileSync2(llmPath, "utf8"));
212
- return parseLlmConfigRaw(raw);
213
- } catch {
214
- return null;
215
- }
216
- }
217
203
 
218
204
  // src/capture/claude-code/parseJsonl.ts
219
- import { readFileSync as readFileSync3 } from "fs";
205
+ import { readFileSync as readFileSync2 } from "fs";
220
206
  import { basename } from "path";
221
207
  var FILE_CHANGE_TOOLS = /^(Write|Edit|MultiEdit|NotebookEdit|write|edit)$/i;
222
208
  var SKIP_LINE_TYPES = /* @__PURE__ */ new Set([
@@ -318,7 +304,7 @@ function countNestedTools(record) {
318
304
  }
319
305
  function parseJsonlFile(jsonlPath) {
320
306
  const sessionId = basename(jsonlPath, ".jsonl");
321
- const raw = readFileSync3(jsonlPath, "utf8");
307
+ const raw = readFileSync2(jsonlPath, "utf8");
322
308
  const messages = [];
323
309
  let fileChanges = 0;
324
310
  let toolCalls = 0;
@@ -362,10 +348,10 @@ function parseJsonlFile(jsonlPath) {
362
348
  // src/capture/enqueueLlmJob.ts
363
349
  import { spawn } from "child_process";
364
350
  import {
365
- existsSync as existsSync3,
351
+ existsSync as existsSync2,
366
352
  mkdirSync as mkdirSync2,
367
353
  readdirSync,
368
- readFileSync as readFileSync4,
354
+ readFileSync as readFileSync3,
369
355
  rmSync,
370
356
  writeFileSync
371
357
  } from "fs";
@@ -383,7 +369,7 @@ function makeJobId(sessionId) {
383
369
  }
384
370
  function removeStaleJobsForSession(repoRoot, sessionId) {
385
371
  const dir = pendingDir(repoRoot);
386
- if (!existsSync3(dir)) {
372
+ if (!existsSync2(dir)) {
387
373
  return;
388
374
  }
389
375
  for (const name of readdirSync(dir)) {
@@ -392,7 +378,7 @@ function removeStaleJobsForSession(repoRoot, sessionId) {
392
378
  }
393
379
  try {
394
380
  const raw = JSON.parse(
395
- readFileSync4(join4(dir, name), "utf8")
381
+ readFileSync3(join4(dir, name), "utf8")
396
382
  );
397
383
  if (raw.sessionId === sessionId) {
398
384
  rmSync(join4(dir, name), { force: true });
@@ -436,24 +422,24 @@ function enqueueLlmJob(opts) {
436
422
  }
437
423
  function readLlmJob(repoRoot, jobId) {
438
424
  const path = join4(pendingDir(repoRoot), `${jobId}.json`);
439
- if (!existsSync3(path)) {
425
+ if (!existsSync2(path)) {
440
426
  return null;
441
427
  }
442
428
  try {
443
- return JSON.parse(readFileSync4(path, "utf8"));
429
+ return JSON.parse(readFileSync3(path, "utf8"));
444
430
  } catch {
445
431
  return null;
446
432
  }
447
433
  }
448
434
  function deleteLlmJob(repoRoot, jobId) {
449
435
  const path = join4(pendingDir(repoRoot), `${jobId}.json`);
450
- if (existsSync3(path)) {
436
+ if (existsSync2(path)) {
451
437
  rmSync(path, { force: true });
452
438
  }
453
439
  }
454
440
  function listPendingJobs(repoRoot) {
455
441
  const dir = pendingDir(repoRoot);
456
- if (!existsSync3(dir)) {
442
+ if (!existsSync2(dir)) {
457
443
  return [];
458
444
  }
459
445
  const jobs = [];
@@ -463,7 +449,7 @@ function listPendingJobs(repoRoot) {
463
449
  }
464
450
  try {
465
451
  jobs.push(
466
- JSON.parse(readFileSync4(join4(dir, name), "utf8"))
452
+ JSON.parse(readFileSync3(join4(dir, name), "utf8"))
467
453
  );
468
454
  } catch {
469
455
  }
@@ -693,7 +679,7 @@ async function llmFormat(session, assistant, llm) {
693
679
  }
694
680
 
695
681
  // src/capture/writeCapture.ts
696
- import { existsSync as existsSync4, mkdirSync as mkdirSync3, readFileSync as readFileSync5, writeFileSync as writeFileSync2 } from "fs";
682
+ import { existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync4, writeFileSync as writeFileSync2 } from "fs";
697
683
  import { join as join5 } from "path";
698
684
  function isoNow() {
699
685
  return (/* @__PURE__ */ new Date()).toISOString();
@@ -746,7 +732,7 @@ function resolveSessionFile(repoRoot, sessionId) {
746
732
  const filename = `session-${sessionId}.md`;
747
733
  const absolutePath = memoryPath(repoRoot, "captures", "raw", filename);
748
734
  const relativePath = join5(".memory", "captures", "raw", filename);
749
- return { absolutePath, relativePath, exists: existsSync4(absolutePath) };
735
+ return { absolutePath, relativePath, exists: existsSync3(absolutePath) };
750
736
  }
751
737
  function renderCaptureMarkdown(formatted, date) {
752
738
  const tagsStr = formatted.tags.map((t) => JSON.stringify(t)).join(", ");
@@ -793,7 +779,7 @@ function appendCaptureToSession(repoRoot, formatted) {
793
779
  previousStatus: null
794
780
  };
795
781
  }
796
- const existingContent = readFileSync5(absolutePath, "utf8");
782
+ const existingContent = readFileSync4(absolutePath, "utf8");
797
783
  const existingFm = parseSessionFileFrontmatter(existingContent);
798
784
  if (!existingFm) {
799
785
  const fm = {
@@ -847,7 +833,7 @@ function appendCaptureToSession(repoRoot, formatted) {
847
833
  function markSessionConsolidated(repoRoot, sessionId) {
848
834
  const { absolutePath, exists } = resolveSessionFile(repoRoot, sessionId);
849
835
  if (!exists) return;
850
- const content = readFileSync5(absolutePath, "utf8");
836
+ const content = readFileSync4(absolutePath, "utf8");
851
837
  const fm = parseSessionFileFrontmatter(content);
852
838
  if (!fm) return;
853
839
  const updatedFm = {
@@ -867,10 +853,10 @@ function markSessionConsolidated(repoRoot, sessionId) {
867
853
  // src/capture/runLlmJob.ts
868
854
  function captureAlreadyUpgraded(repoRoot, captureFile) {
869
855
  const path = join6(repoRoot, captureFile);
870
- if (!existsSync5(path)) {
856
+ if (!existsSync4(path)) {
871
857
  return false;
872
858
  }
873
- const text = readFileSync6(path, "utf8");
859
+ const text = readFileSync5(path, "utf8");
874
860
  return /llmUpgradedAt:/.test(text);
875
861
  }
876
862
  async function runLlmJob(repoRoot, job, debug) {
@@ -878,11 +864,11 @@ async function runLlmJob(repoRoot, job, debug) {
878
864
  deleteLlmJob(repoRoot, job.jobId);
879
865
  return { ok: true, reason: "already-upgraded" };
880
866
  }
881
- const llm = readLlmConfigAtRepo(repoRoot);
867
+ const llm = readConfigAtRepo(repoRoot)?.llm ?? null;
882
868
  if (!isLlmAvailable(llm)) {
883
869
  return { ok: false, reason: "llm not available" };
884
870
  }
885
- if (!existsSync5(job.jsonlPath)) {
871
+ if (!existsSync4(job.jsonlPath)) {
886
872
  return { ok: false, reason: "jsonl missing" };
887
873
  }
888
874
  const session = parseJsonlFile(job.jsonlPath);
@@ -965,7 +951,7 @@ function runCaptureLlmCommand(opts) {
965
951
  }
966
952
 
967
953
  // src/capture/hookInput.ts
968
- import { existsSync as existsSync6, readFileSync as readFileSync7 } from "fs";
954
+ import { existsSync as existsSync5, readFileSync as readFileSync6 } from "fs";
969
955
  import { resolve as resolve2 } from "path";
970
956
  function pickString(obj, ...keys) {
971
957
  for (const key of keys) {
@@ -1003,7 +989,7 @@ function parseHookInputJson(raw) {
1003
989
  try {
1004
990
  const parsed = JSON.parse(trimmed);
1005
991
  const transcriptRaw = pickString(parsed, "transcript_path", "transcriptPath");
1006
- const transcriptPath = transcriptRaw && existsSync6(transcriptRaw) ? resolve2(transcriptRaw) : void 0;
992
+ const transcriptPath = transcriptRaw && existsSync5(transcriptRaw) ? resolve2(transcriptRaw) : void 0;
1007
993
  return {
1008
994
  transcriptPath,
1009
995
  transcriptPathRaw: transcriptRaw,
@@ -1022,7 +1008,7 @@ function readHookInputSync() {
1022
1008
  return null;
1023
1009
  }
1024
1010
  try {
1025
- const raw = readFileSync7(0, "utf8");
1011
+ const raw = readFileSync6(0, "utf8");
1026
1012
  return parseHookInputJson(raw);
1027
1013
  } catch {
1028
1014
  return null;
@@ -1135,11 +1121,16 @@ function needsLlm(session) {
1135
1121
  return false;
1136
1122
  }
1137
1123
 
1124
+ // src/consolidate/scheduleConsolidate.ts
1125
+ import { spawn as spawn2 } from "child_process";
1126
+ import { dirname as dirname5, join as join11 } from "path";
1127
+ import { fileURLToPath as fileURLToPath2 } from "url";
1128
+
1138
1129
  // src/consolidate/state.ts
1139
1130
  import {
1140
- existsSync as existsSync7,
1131
+ existsSync as existsSync6,
1141
1132
  mkdirSync as mkdirSync4,
1142
- readFileSync as readFileSync8,
1133
+ readFileSync as readFileSync7,
1143
1134
  rmSync as rmSync2,
1144
1135
  writeFileSync as writeFileSync4
1145
1136
  } from "fs";
@@ -1161,11 +1152,11 @@ function consolidateLockPath(repoRoot) {
1161
1152
  }
1162
1153
  function readConsolidateState(repoRoot) {
1163
1154
  const path = consolidateStatePath(repoRoot);
1164
- if (!existsSync7(path)) {
1155
+ if (!existsSync6(path)) {
1165
1156
  return { ...EMPTY_STATE, processedSessions: {} };
1166
1157
  }
1167
1158
  try {
1168
- const raw = JSON.parse(readFileSync8(path, "utf8"));
1159
+ const raw = JSON.parse(readFileSync7(path, "utf8"));
1169
1160
  if (typeof raw === "object" && raw !== null) {
1170
1161
  const obj = raw;
1171
1162
  if (obj.version === 2 && typeof obj.processedSessions === "object") {
@@ -1189,11 +1180,11 @@ function writeConsolidateState(repoRoot, state) {
1189
1180
  }
1190
1181
  function readConsolidateLock(repoRoot) {
1191
1182
  const path = consolidateLockPath(repoRoot);
1192
- if (!existsSync7(path)) {
1183
+ if (!existsSync6(path)) {
1193
1184
  return null;
1194
1185
  }
1195
1186
  try {
1196
- return JSON.parse(readFileSync8(path, "utf8"));
1187
+ return JSON.parse(readFileSync7(path, "utf8"));
1197
1188
  } catch {
1198
1189
  return null;
1199
1190
  }
@@ -1213,7 +1204,7 @@ function writeConsolidateLock(repoRoot) {
1213
1204
  }
1214
1205
  function releaseConsolidateLock(repoRoot) {
1215
1206
  const path = consolidateLockPath(repoRoot);
1216
- if (existsSync7(path)) {
1207
+ if (existsSync6(path)) {
1217
1208
  rmSync2(path, { force: true });
1218
1209
  }
1219
1210
  }
@@ -1226,7 +1217,7 @@ function isLockStale(lock, ttlMs) {
1226
1217
  }
1227
1218
 
1228
1219
  // src/consolidate/sessionScanner.ts
1229
- import { readdirSync as readdirSync2, readFileSync as readFileSync9 } from "fs";
1220
+ import { readdirSync as readdirSync2, readFileSync as readFileSync8 } from "fs";
1230
1221
  import { join as join7 } from "path";
1231
1222
  function parseSessionFrontmatter(content) {
1232
1223
  const match = content.match(/^---\n([\s\S]*?)\n---/);
@@ -1257,7 +1248,7 @@ function scanAllSessions(repoRoot) {
1257
1248
  for (const file of files) {
1258
1249
  const absolutePath = join7(rawDir, file);
1259
1250
  try {
1260
- const content = readFileSync9(absolutePath, "utf8");
1251
+ const content = readFileSync8(absolutePath, "utf8");
1261
1252
  const fm = parseSessionFrontmatter(content);
1262
1253
  if (!fm || !fm.sessionId) continue;
1263
1254
  const fmEndIndex = content.indexOf("---", 4);
@@ -1286,7 +1277,7 @@ function filterPendingSessions(sessions) {
1286
1277
  }
1287
1278
 
1288
1279
  // src/consolidate/llmConsolidateV2.ts
1289
- import { readFileSync as readFileSync10, readdirSync as readdirSync3 } from "fs";
1280
+ import { readFileSync as readFileSync9, readdirSync as readdirSync3 } from "fs";
1290
1281
  import { join as join8 } from "path";
1291
1282
  var CONSOLIDATE_SYSTEM_PROMPT = `\u4F60\u662F\u4E00\u4E2A\u9879\u76EE\u77E5\u8BC6\u6574\u7406\u4E13\u5BB6\u3002\u4F60\u7684\u4EFB\u52A1\u662F\u4ECE AI \u7F16\u7A0B\u52A9\u624B\u7684\u5BF9\u8BDD\u8BB0\u5F55\u4E2D\u63D0\u70BC\u51FA\u7ED3\u6784\u5316\u7684\u77E5\u8BC6\u5E93\u3002
1292
1283
 
@@ -1395,7 +1386,7 @@ function scanMarkdownDirectory(absoluteDir, relativePrefix, type, domain, result
1395
1386
  }
1396
1387
  for (const file of files) {
1397
1388
  try {
1398
- const content = readFileSync10(join8(absoluteDir, file), "utf8");
1389
+ const content = readFileSync9(join8(absoluteDir, file), "utf8");
1399
1390
  const fmMatch = content.match(/^---\n([\s\S]*?)\n---/);
1400
1391
  let title = file.replace(/\.md$/, "");
1401
1392
  if (fmMatch) {
@@ -1421,7 +1412,7 @@ function buildLlmConsolidateInput(repoRoot, sessions) {
1421
1412
  const pendingSessions = sessions.map((s) => ({
1422
1413
  sessionId: s.sessionId,
1423
1414
  status: s.frontmatter.status,
1424
- content: readFileSync10(s.absolutePath, "utf8"),
1415
+ content: readFileSync9(s.absolutePath, "utf8"),
1425
1416
  captureCount: s.frontmatter.captureCount,
1426
1417
  createdAt: s.frontmatter.createdAt
1427
1418
  }));
@@ -1429,7 +1420,7 @@ function buildLlmConsolidateInput(repoRoot, sessions) {
1429
1420
  const memoryPathAbs = memoryPath(repoRoot, "MEMORY.md");
1430
1421
  let currentMemoryMd = null;
1431
1422
  try {
1432
- currentMemoryMd = readFileSync10(memoryPathAbs, "utf8");
1423
+ currentMemoryMd = readFileSync9(memoryPathAbs, "utf8");
1433
1424
  } catch {
1434
1425
  }
1435
1426
  return {
@@ -1439,15 +1430,14 @@ function buildLlmConsolidateInput(repoRoot, sessions) {
1439
1430
  };
1440
1431
  }
1441
1432
  async function callLlmConsolidate(input2, llmConfig) {
1442
- const apiKey = process.env.HERMES_LLM_API_KEY;
1443
- if (!apiKey) {
1433
+ if (!llmConfig.enabled) {
1444
1434
  throw new Error(
1445
- "LLM \u672A\u914D\u7F6E\uFF1A\u8BF7\u8BBE\u7F6E HERMES_LLM_API_KEY \u73AF\u5883\u53D8\u91CF"
1435
+ "LLM \u672A\u542F\u7528\uFF1A\u8BF7\u5728 config.json \u4E2D\u8BBE\u7F6E llm.enabled = true"
1446
1436
  );
1447
1437
  }
1448
- if (!llmConfig.enabled) {
1438
+ if (!llmConfig.apiKey.trim() || !llmConfig.baseUrl.trim() || !llmConfig.model.trim()) {
1449
1439
  throw new Error(
1450
- "LLM \u672A\u542F\u7528\uFF1A\u8BF7\u5728 config.json \u4E2D\u8BBE\u7F6E llm.enabled = true"
1440
+ "LLM \u672A\u914D\u7F6E\uFF1A\u8BF7\u5728 config.json \u4E2D\u8BBE\u7F6E llm.apiKey\u3001llm.baseUrl \u548C llm.model"
1451
1441
  );
1452
1442
  }
1453
1443
  const url = `${llmConfig.baseUrl.replace(/\/$/, "")}/chat/completions`;
@@ -1459,7 +1449,7 @@ async function callLlmConsolidate(input2, llmConfig) {
1459
1449
  method: "POST",
1460
1450
  headers: {
1461
1451
  "Content-Type": "application/json",
1462
- Authorization: `Bearer ${apiKey}`
1452
+ Authorization: `Bearer ${llmConfig.apiKey}`
1463
1453
  },
1464
1454
  body: JSON.stringify({
1465
1455
  model: llmConfig.model,
@@ -1551,7 +1541,7 @@ function isSkippedEntry(raw) {
1551
1541
  }
1552
1542
 
1553
1543
  // src/consolidate/writeKnowledge.ts
1554
- import { existsSync as existsSync8, mkdirSync as mkdirSync5, writeFileSync as writeFileSync5 } from "fs";
1544
+ import { existsSync as existsSync7, mkdirSync as mkdirSync5, writeFileSync as writeFileSync5 } from "fs";
1555
1545
  import { dirname as dirname4 } from "path";
1556
1546
  function writeKnowledgeFiles(repoRoot, files) {
1557
1547
  const result = {
@@ -1565,7 +1555,7 @@ function writeKnowledgeFiles(repoRoot, files) {
1565
1555
  const dir = dirname4(absolutePath);
1566
1556
  mkdirSync5(dir, { recursive: true });
1567
1557
  const content = serializeKnowledgeFile(kf.frontmatter, kf.body);
1568
- const alreadyExists = existsSync8(absolutePath);
1558
+ const alreadyExists = existsSync7(absolutePath);
1569
1559
  if (kf.action === "create" && alreadyExists) {
1570
1560
  result.updated.push(kf.targetPath);
1571
1561
  } else if (kf.action === "create") {
@@ -1585,7 +1575,7 @@ function writeKnowledgeFiles(repoRoot, files) {
1585
1575
  }
1586
1576
  function writeMemoryMd(repoRoot, memoryMd) {
1587
1577
  const memoryPathAbs = memoryPath(repoRoot, "MEMORY.md");
1588
- if (existsSync8(memoryPathAbs)) {
1578
+ if (existsSync7(memoryPathAbs)) {
1589
1579
  const existing = __require("fs").readFileSync(memoryPathAbs, "utf8");
1590
1580
  const preserved = extractUserEditedSections(existing);
1591
1581
  if (preserved.length > 0) {
@@ -1802,6 +1792,9 @@ function extractDomainsFromResults(files) {
1802
1792
  var CONSOLIDATE_LOCK_TTL_MS = 30 * 60 * 1e3;
1803
1793
 
1804
1794
  // src/consolidate/scheduleConsolidate.ts
1795
+ function cliPath2() {
1796
+ return join11(dirname5(fileURLToPath2(import.meta.url)), "..", "cli.js");
1797
+ }
1805
1798
  async function runFlushCommand(opts) {
1806
1799
  const ctx = loadRepoContext(opts.cwd);
1807
1800
  if (!ctx) {
@@ -1836,11 +1829,83 @@ async function runFlushCommand(opts) {
1836
1829
  debug: opts.debug ?? ctx.config.debug
1837
1830
  });
1838
1831
  }
1832
+ function shouldAutoFlush(sessions, consolidate, lastConsolidatedAt) {
1833
+ const autoFlush = consolidate.autoFlush;
1834
+ if (!autoFlush.enabled || sessions.length === 0) {
1835
+ return false;
1836
+ }
1837
+ if (sessions.length >= autoFlush.minPendingSessions) {
1838
+ return true;
1839
+ }
1840
+ const pendingChars = sessions.reduce(
1841
+ (sum, session) => sum + session.bodyContent.length,
1842
+ 0
1843
+ );
1844
+ if (pendingChars >= autoFlush.maxPendingChars) {
1845
+ return true;
1846
+ }
1847
+ const last = Date.parse(lastConsolidatedAt);
1848
+ if (Number.isNaN(last)) {
1849
+ return true;
1850
+ }
1851
+ const minIntervalMs = autoFlush.minIntervalMinutes * 60 * 1e3;
1852
+ return Date.now() - last >= minIntervalMs;
1853
+ }
1839
1854
  function maybeScheduleConsolidate(opts) {
1855
+ const ctx = loadRepoContext(opts.repoRoot);
1856
+ if (!ctx) {
1857
+ return;
1858
+ }
1859
+ const autoFlush = ctx.config.consolidate.autoFlush;
1860
+ if (!autoFlush.enabled) {
1861
+ return;
1862
+ }
1863
+ if (!isLlmAvailable(ctx.config.llm)) {
1864
+ debugLog(opts.debug === true, "consolidate", "auto flush skipped: llm not available");
1865
+ return;
1866
+ }
1840
1867
  const lock = readConsolidateLock(opts.repoRoot);
1841
1868
  if (lock && !isLockStale(lock, CONSOLIDATE_LOCK_TTL_MS)) {
1869
+ debugLog(opts.debug === true, "consolidate", "auto flush skipped: lock held");
1870
+ return;
1871
+ }
1872
+ const pendingSessions = filterPendingSessions(scanAllSessions(opts.repoRoot));
1873
+ const state = readConsolidateState(opts.repoRoot);
1874
+ if (!shouldAutoFlush(
1875
+ pendingSessions,
1876
+ ctx.config.consolidate,
1877
+ state.lastConsolidatedAt
1878
+ )) {
1879
+ debugLog(
1880
+ opts.debug === true,
1881
+ "consolidate",
1882
+ `auto flush skipped: ${pendingSessions.length} pending session(s) below thresholds`
1883
+ );
1842
1884
  return;
1843
1885
  }
1886
+ try {
1887
+ const child = spawn2(
1888
+ process.execPath,
1889
+ [cliPath2(), "flush", "-C", opts.repoRoot],
1890
+ {
1891
+ detached: true,
1892
+ stdio: "ignore",
1893
+ cwd: opts.repoRoot
1894
+ }
1895
+ );
1896
+ child.unref();
1897
+ debugLog(
1898
+ opts.debug === true,
1899
+ "consolidate",
1900
+ `auto flush scheduled: ${pendingSessions.length} pending session(s)`
1901
+ );
1902
+ } catch (err) {
1903
+ debugLog(
1904
+ opts.debug === true,
1905
+ "consolidate",
1906
+ `auto flush spawn failed: ${err instanceof Error ? err.message : String(err)}`
1907
+ );
1908
+ }
1844
1909
  }
1845
1910
 
1846
1911
  // src/capture/commitCapture.ts
@@ -1863,7 +1928,7 @@ async function commitCapture(opts) {
1863
1928
  }
1864
1929
  const result = appendCaptureToSession(repoRoot, formatted);
1865
1930
  const captureFile = result.relativePath;
1866
- const llm = readLlmConfigAtRepo(repoRoot);
1931
+ const llm = readConfigAtRepo(repoRoot)?.llm ?? null;
1867
1932
  if (!isLlmAvailable(llm) || !needsLlm(session)) {
1868
1933
  debugLog(debug === true, "capture", `ok: ${captureFile} (format=simple)`);
1869
1934
  return finishCapture(repoRoot, debug, {
@@ -1911,37 +1976,37 @@ async function commitCapture(opts) {
1911
1976
  }
1912
1977
 
1913
1978
  // src/capture/claude-code/resolveSession.ts
1914
- import { existsSync as existsSync10, readdirSync as readdirSync4, readFileSync as readFileSync12, statSync } from "fs";
1979
+ import { existsSync as existsSync9, readdirSync as readdirSync4, readFileSync as readFileSync11, statSync } from "fs";
1915
1980
  import { homedir } from "os";
1916
- import { basename as basename2, join as join11, resolve as resolve3 } from "path";
1981
+ import { basename as basename2, join as join12, resolve as resolve3 } from "path";
1917
1982
  function encodeClaudeProjectDir(absPath) {
1918
1983
  return resolve3(absPath).replace(/\//g, "-");
1919
1984
  }
1920
1985
  function resolveSessionJsonlPath(repoRoot, options = {}) {
1921
1986
  const override = process.env.HERMES_SESSION_JSONL;
1922
- if (override && existsSync10(override)) {
1987
+ if (override && existsSync9(override)) {
1923
1988
  return resolve3(override);
1924
1989
  }
1925
1990
  const fromHook = options.transcriptPath;
1926
- if (fromHook && existsSync10(fromHook)) {
1991
+ if (fromHook && existsSync9(fromHook)) {
1927
1992
  return resolve3(fromHook);
1928
1993
  }
1929
1994
  const sessionId = process.env.CLAUDE_SESSION_ID ?? process.env.CLAUDE_CODE_SESSION_ID ?? process.env.SESSION_ID;
1930
- const claudeHome = process.env.CLAUDE_CONFIG_DIR ? resolve3(process.env.CLAUDE_CONFIG_DIR) : join11(homedir(), ".claude");
1931
- const projectsRoot = join11(claudeHome, "projects");
1932
- if (!existsSync10(projectsRoot)) {
1995
+ const claudeHome = process.env.CLAUDE_CONFIG_DIR ? resolve3(process.env.CLAUDE_CONFIG_DIR) : join12(homedir(), ".claude");
1996
+ const projectsRoot = join12(claudeHome, "projects");
1997
+ if (!existsSync9(projectsRoot)) {
1933
1998
  return null;
1934
1999
  }
1935
2000
  const cwd = resolve3(options.cwd ?? repoRoot);
1936
2001
  const preferredProjectDir = encodeClaudeProjectDir(cwd);
1937
- const preferredPath = join11(projectsRoot, preferredProjectDir);
1938
- if (existsSync10(preferredPath)) {
2002
+ const preferredPath = join12(projectsRoot, preferredProjectDir);
2003
+ if (existsSync9(preferredPath)) {
1939
2004
  const hit = pickNewestJsonl(preferredPath, sessionId);
1940
2005
  if (hit) {
1941
2006
  return hit;
1942
2007
  }
1943
- const legacySessions = join11(preferredPath, "sessions");
1944
- if (existsSync10(legacySessions)) {
2008
+ const legacySessions = join12(preferredPath, "sessions");
2009
+ if (existsSync9(legacySessions)) {
1945
2010
  const legacyHit = pickNewestJsonl(legacySessions, sessionId);
1946
2011
  if (legacyHit) {
1947
2012
  return legacyHit;
@@ -1951,10 +2016,10 @@ function resolveSessionJsonlPath(repoRoot, options = {}) {
1951
2016
  const candidates = [];
1952
2017
  for (const projectDir of readdirSync4(projectsRoot, { withFileTypes: true })) {
1953
2018
  if (!projectDir.isDirectory()) continue;
1954
- const projectPath = join11(projectsRoot, projectDir.name);
2019
+ const projectPath = join12(projectsRoot, projectDir.name);
1955
2020
  collectJsonlCandidates(projectPath, sessionId, candidates);
1956
- const legacySessions = join11(projectPath, "sessions");
1957
- if (existsSync10(legacySessions)) {
2021
+ const legacySessions = join12(projectPath, "sessions");
2022
+ if (existsSync9(legacySessions)) {
1958
2023
  collectJsonlCandidates(legacySessions, sessionId, candidates);
1959
2024
  }
1960
2025
  }
@@ -1974,14 +2039,14 @@ function pickNewestJsonl(dir, sessionId) {
1974
2039
  return candidates[0]?.path ?? null;
1975
2040
  }
1976
2041
  function collectJsonlCandidates(dir, sessionId, out) {
1977
- if (!existsSync10(dir)) {
2042
+ if (!existsSync9(dir)) {
1978
2043
  return;
1979
2044
  }
1980
2045
  for (const entry of readdirSync4(dir, { withFileTypes: true })) {
1981
2046
  if (!entry.isFile() || !entry.name.endsWith(".jsonl")) {
1982
2047
  continue;
1983
2048
  }
1984
- const fullPath = join11(dir, entry.name);
2049
+ const fullPath = join12(dir, entry.name);
1985
2050
  if (sessionId && !entry.name.includes(sessionId) && basename2(entry.name, ".jsonl") !== sessionId) {
1986
2051
  continue;
1987
2052
  }
@@ -2014,31 +2079,31 @@ async function runClaudeCodeCapture(repoRoot, cwd, dryRun, options) {
2014
2079
  }
2015
2080
 
2016
2081
  // src/capture/codebuddy/resolveSession.ts
2017
- import { existsSync as existsSync11, readdirSync as readdirSync5, statSync as statSync2 } from "fs";
2082
+ import { existsSync as existsSync10, readdirSync as readdirSync5, statSync as statSync2 } from "fs";
2018
2083
  import { homedir as homedir2 } from "os";
2019
- import { basename as basename3, join as join12, resolve as resolve4 } from "path";
2084
+ import { basename as basename3, join as join13, resolve as resolve4 } from "path";
2020
2085
  function encodeCodebuddyProjectDir(absPath) {
2021
2086
  return resolve4(absPath).replace(/^\//, "").replace(/\//g, "-");
2022
2087
  }
2023
2088
  function resolveCodebuddySessionJsonl(options) {
2024
2089
  const override = process.env.HERMES_CODEBUDDY_SESSION;
2025
- if (override && existsSync11(override)) {
2090
+ if (override && existsSync10(override)) {
2026
2091
  return resolve4(override);
2027
2092
  }
2028
2093
  const fromHook = options.transcriptPath;
2029
- if (fromHook && existsSync11(fromHook)) {
2094
+ if (fromHook && existsSync10(fromHook)) {
2030
2095
  return resolve4(fromHook);
2031
2096
  }
2032
2097
  const sessionId = process.env.CODEBUDDY_SESSION_ID ?? process.env.SESSION_ID;
2033
- const codebuddyHome = process.env.CODEBUDDY_CONFIG_DIR ? resolve4(process.env.CODEBUDDY_CONFIG_DIR) : join12(homedir2(), ".codebuddy");
2034
- const projectsRoot = join12(codebuddyHome, "projects");
2035
- if (!existsSync11(projectsRoot)) {
2098
+ const codebuddyHome = process.env.CODEBUDDY_CONFIG_DIR ? resolve4(process.env.CODEBUDDY_CONFIG_DIR) : join13(homedir2(), ".codebuddy");
2099
+ const projectsRoot = join13(codebuddyHome, "projects");
2100
+ if (!existsSync10(projectsRoot)) {
2036
2101
  return null;
2037
2102
  }
2038
2103
  const cwd = resolve4(options.cwd ?? options.repoRoot);
2039
2104
  const preferredProjectDir = encodeCodebuddyProjectDir(cwd);
2040
- const preferredPath = join12(projectsRoot, preferredProjectDir);
2041
- if (existsSync11(preferredPath)) {
2105
+ const preferredPath = join13(projectsRoot, preferredProjectDir);
2106
+ if (existsSync10(preferredPath)) {
2042
2107
  const hit = pickNewestJsonlRecursive(preferredPath, sessionId);
2043
2108
  if (hit) {
2044
2109
  return hit;
@@ -2049,7 +2114,7 @@ function resolveCodebuddySessionJsonl(options) {
2049
2114
  if (!projectDir.isDirectory()) {
2050
2115
  continue;
2051
2116
  }
2052
- collectJsonlRecursive(join12(projectsRoot, projectDir.name), sessionId, candidates);
2117
+ collectJsonlRecursive(join13(projectsRoot, projectDir.name), sessionId, candidates);
2053
2118
  }
2054
2119
  if (candidates.length === 0) {
2055
2120
  return null;
@@ -2067,11 +2132,11 @@ function pickNewestJsonlRecursive(dir, sessionId) {
2067
2132
  return candidates[0]?.path ?? null;
2068
2133
  }
2069
2134
  function collectJsonlRecursive(dir, sessionId, out) {
2070
- if (!existsSync11(dir)) {
2135
+ if (!existsSync10(dir)) {
2071
2136
  return;
2072
2137
  }
2073
2138
  for (const entry of readdirSync5(dir, { withFileTypes: true })) {
2074
- const full = join12(dir, entry.name);
2139
+ const full = join13(dir, entry.name);
2075
2140
  if (entry.isDirectory()) {
2076
2141
  collectJsonlRecursive(full, sessionId, out);
2077
2142
  continue;
@@ -2119,36 +2184,36 @@ async function runCodebuddyCapture(repoRoot, cwd, dryRun, options) {
2119
2184
  }
2120
2185
 
2121
2186
  // src/capture/cursor/resolveSession.ts
2122
- import { existsSync as existsSync12, readdirSync as readdirSync6, statSync as statSync3 } from "fs";
2187
+ import { existsSync as existsSync11, readdirSync as readdirSync6, statSync as statSync3 } from "fs";
2123
2188
  import { homedir as homedir3 } from "os";
2124
- import { join as join13, resolve as resolve5 } from "path";
2189
+ import { join as join14, resolve as resolve5 } from "path";
2125
2190
  function encodeCursorProjectDir(absPath) {
2126
2191
  return resolve5(absPath).replace(/^\//, "").replace(/\//g, "-");
2127
2192
  }
2128
2193
  function resolveCursorSessionJsonl(options) {
2129
2194
  const override = process.env.HERMES_CURSOR_SESSION;
2130
- if (override && existsSync12(override)) {
2195
+ if (override && existsSync11(override)) {
2131
2196
  return resolve5(override);
2132
2197
  }
2133
- const cursorHome = process.env.CURSOR_CONFIG_DIR ? resolve5(process.env.CURSOR_CONFIG_DIR) : join13(homedir3(), ".cursor");
2134
- const projectsRoot = join13(cursorHome, "projects");
2135
- if (!existsSync12(projectsRoot)) {
2198
+ const cursorHome = process.env.CURSOR_CONFIG_DIR ? resolve5(process.env.CURSOR_CONFIG_DIR) : join14(homedir3(), ".cursor");
2199
+ const projectsRoot = join14(cursorHome, "projects");
2200
+ if (!existsSync11(projectsRoot)) {
2136
2201
  return null;
2137
2202
  }
2138
2203
  const sessionId = options.hookInput?.sessionId ?? options.hookInput?.conversationId ?? process.env.CURSOR_SESSION_ID ?? process.env.CURSOR_AGENT_SESSION_ID;
2139
2204
  const workspace = options.hookInput?.workspaceRoots?.[0] ?? (options.cwd ? resolve5(options.cwd) : resolve5(options.repoRoot));
2140
2205
  const encoded = encodeCursorProjectDir(workspace);
2141
- const projectDir = join13(projectsRoot, encoded);
2142
- const transcriptsRoot = join13(projectDir, "agent-transcripts");
2143
- if (!existsSync12(transcriptsRoot)) {
2206
+ const projectDir = join14(projectsRoot, encoded);
2207
+ const transcriptsRoot = join14(projectDir, "agent-transcripts");
2208
+ if (!existsSync11(transcriptsRoot)) {
2144
2209
  return pickNewestCursorJsonl(projectsRoot);
2145
2210
  }
2146
2211
  if (sessionId) {
2147
- const direct = join13(transcriptsRoot, sessionId, `${sessionId}.jsonl`);
2148
- if (existsSync12(direct)) {
2212
+ const direct = join14(transcriptsRoot, sessionId, `${sessionId}.jsonl`);
2213
+ if (existsSync11(direct)) {
2149
2214
  return direct;
2150
2215
  }
2151
- const nested = findJsonlUnderDir(join13(transcriptsRoot, sessionId), sessionId);
2216
+ const nested = findJsonlUnderDir(join14(transcriptsRoot, sessionId), sessionId);
2152
2217
  if (nested) {
2153
2218
  return nested;
2154
2219
  }
@@ -2156,19 +2221,19 @@ function resolveCursorSessionJsonl(options) {
2156
2221
  return pickNewestCursorJsonl(transcriptsRoot);
2157
2222
  }
2158
2223
  function findJsonlUnderDir(dir, sessionId) {
2159
- if (!existsSync12(dir)) {
2224
+ if (!existsSync11(dir)) {
2160
2225
  return null;
2161
2226
  }
2162
- const direct = join13(dir, `${sessionId}.jsonl`);
2163
- if (existsSync12(direct)) {
2227
+ const direct = join14(dir, `${sessionId}.jsonl`);
2228
+ if (existsSync11(direct)) {
2164
2229
  return direct;
2165
2230
  }
2166
2231
  for (const entry of readdirSync6(dir, { withFileTypes: true })) {
2167
2232
  if (entry.isFile() && entry.name.endsWith(".jsonl")) {
2168
- return join13(dir, entry.name);
2233
+ return join14(dir, entry.name);
2169
2234
  }
2170
2235
  if (entry.isDirectory()) {
2171
- const found = findJsonlUnderDir(join13(dir, entry.name), sessionId);
2236
+ const found = findJsonlUnderDir(join14(dir, entry.name), sessionId);
2172
2237
  if (found) {
2173
2238
  return found;
2174
2239
  }
@@ -2186,11 +2251,11 @@ function pickNewestCursorJsonl(root) {
2186
2251
  return candidates[0]?.path ?? null;
2187
2252
  }
2188
2253
  function collectJsonlRecursive2(dir, out) {
2189
- if (!existsSync12(dir)) {
2254
+ if (!existsSync11(dir)) {
2190
2255
  return;
2191
2256
  }
2192
2257
  for (const entry of readdirSync6(dir, { withFileTypes: true })) {
2193
- const full = join13(dir, entry.name);
2258
+ const full = join14(dir, entry.name);
2194
2259
  if (entry.isDirectory()) {
2195
2260
  collectJsonlRecursive2(full, out);
2196
2261
  continue;
@@ -2415,8 +2480,8 @@ async function runFlushCommandCli(opts) {
2415
2480
  }
2416
2481
 
2417
2482
  // src/inject/runInject.ts
2418
- import { existsSync as existsSync13, readdirSync as readdirSync7, readFileSync as readFileSync13 } from "fs";
2419
- import { join as join14 } from "path";
2483
+ import { existsSync as existsSync12, readdirSync as readdirSync7, readFileSync as readFileSync12 } from "fs";
2484
+ import { join as join15 } from "path";
2420
2485
 
2421
2486
  // src/inject/constants.ts
2422
2487
  var INJECT_MAX_CHARS = 8e3;
@@ -2476,9 +2541,9 @@ function rulesPathOnDisk(repoRoot) {
2476
2541
  }
2477
2542
  function readMemoryMd(repoRoot) {
2478
2543
  const path = memoryPathOnDisk(repoRoot);
2479
- if (!existsSync13(path)) return null;
2544
+ if (!existsSync12(path)) return null;
2480
2545
  try {
2481
- const content = readFileSync13(path, "utf8");
2546
+ const content = readFileSync12(path, "utf8");
2482
2547
  return content.trim() || null;
2483
2548
  } catch {
2484
2549
  return null;
@@ -2496,8 +2561,8 @@ function readAllRules(repoRoot) {
2496
2561
  const parts = [];
2497
2562
  for (const file of files) {
2498
2563
  try {
2499
- const filePath = join14(rulesDir, file);
2500
- const content = readFileSync13(filePath, "utf8").trim();
2564
+ const filePath = join15(rulesDir, file);
2565
+ const content = readFileSync12(filePath, "utf8").trim();
2501
2566
  if (!content) continue;
2502
2567
  parts.push(`### ${file}`, "", content, "");
2503
2568
  } catch {
@@ -2524,49 +2589,49 @@ import { resolve as resolve7 } from "path";
2524
2589
 
2525
2590
  // src/init/assistants/claude-code.ts
2526
2591
  import { mkdirSync as mkdirSync7, writeFileSync as writeFileSync6 } from "fs";
2527
- import { join as join18 } from "path";
2592
+ import { join as join19 } from "path";
2528
2593
 
2529
2594
  // src/init/mergeClaudeSettings.ts
2530
- import { existsSync as existsSync15, readFileSync as readFileSync16 } from "fs";
2531
- import { join as join17 } from "path";
2595
+ import { existsSync as existsSync14, readFileSync as readFileSync15 } from "fs";
2596
+ import { join as join18 } from "path";
2532
2597
 
2533
2598
  // src/init/templateDir.ts
2534
- import { existsSync as existsSync14, readFileSync as readFileSync15 } from "fs";
2535
- import { dirname as dirname6, join as join16 } from "path";
2536
- import { fileURLToPath as fileURLToPath3 } from "url";
2599
+ import { existsSync as existsSync13, readFileSync as readFileSync14 } from "fs";
2600
+ import { dirname as dirname7, join as join17 } from "path";
2601
+ import { fileURLToPath as fileURLToPath4 } from "url";
2537
2602
 
2538
2603
  // src/index.ts
2539
- import { readFileSync as readFileSync14 } from "fs";
2540
- import { dirname as dirname5, join as join15 } from "path";
2541
- import { fileURLToPath as fileURLToPath2 } from "url";
2604
+ import { readFileSync as readFileSync13 } from "fs";
2605
+ import { dirname as dirname6, join as join16 } from "path";
2606
+ import { fileURLToPath as fileURLToPath3 } from "url";
2542
2607
  var PACKAGE_NAME = "@riconext/hermes-repo";
2543
- var __dirname = dirname5(fileURLToPath2(import.meta.url));
2608
+ var __dirname = dirname6(fileURLToPath3(import.meta.url));
2544
2609
  function readPkgVersion() {
2545
- const pkgPath = join15(__dirname, "..", "package.json");
2546
- const pkg = JSON.parse(readFileSync14(pkgPath, "utf8"));
2610
+ const pkgPath = join16(__dirname, "..", "package.json");
2611
+ const pkg = JSON.parse(readFileSync13(pkgPath, "utf8"));
2547
2612
  return pkg.version;
2548
2613
  }
2549
2614
 
2550
2615
  // src/init/templateDir.ts
2551
2616
  function resolveTemplateDir() {
2552
- const here = dirname6(fileURLToPath3(import.meta.url));
2617
+ const here = dirname7(fileURLToPath4(import.meta.url));
2553
2618
  const candidates = [
2554
- join16(here, "templates"),
2555
- join16(here, "..", "..", "templates")
2619
+ join17(here, "templates"),
2620
+ join17(here, "..", "..", "templates")
2556
2621
  ];
2557
2622
  for (const dir of candidates) {
2558
- if (existsSync14(dir)) {
2623
+ if (existsSync13(dir)) {
2559
2624
  return dir;
2560
2625
  }
2561
2626
  }
2562
- return join16(here, "templates");
2627
+ return join17(here, "templates");
2563
2628
  }
2564
2629
  var templateDir = resolveTemplateDir();
2565
2630
  function resolveTemplatePath(name) {
2566
- return join16(templateDir, name);
2631
+ return join17(templateDir, name);
2567
2632
  }
2568
2633
  function readTemplate(name) {
2569
- return readFileSync15(resolveTemplatePath(name), "utf8");
2634
+ return readFileSync14(resolveTemplatePath(name), "utf8");
2570
2635
  }
2571
2636
  function renderTemplate(name) {
2572
2637
  const raw = readTemplate(name);
@@ -2576,16 +2641,16 @@ function renderTemplate(name) {
2576
2641
  // src/init/mergeClaudeSettings.ts
2577
2642
  var CLAUDE_SETTINGS_LOCAL_REL = ".claude/settings.local.json";
2578
2643
  function claudeSettingsLocalPath(repoRoot) {
2579
- return join17(repoRoot, ".claude", "settings.local.json");
2644
+ return join18(repoRoot, ".claude", "settings.local.json");
2580
2645
  }
2581
2646
  function mergeClaudeLocalSettings(repoRoot) {
2582
2647
  const settingsPath = claudeSettingsLocalPath(repoRoot);
2583
- const existed = existsSync15(settingsPath);
2648
+ const existed = existsSync14(settingsPath);
2584
2649
  const templateParsed = JSON.parse(renderTemplate("hooks.json.tpl"));
2585
2650
  let existing = {};
2586
2651
  if (existed) {
2587
2652
  try {
2588
- existing = JSON.parse(readFileSync16(settingsPath, "utf8"));
2653
+ existing = JSON.parse(readFileSync15(settingsPath, "utf8"));
2589
2654
  } catch {
2590
2655
  existing = {};
2591
2656
  }
@@ -2613,7 +2678,7 @@ var claudeCodeAdapter = {
2613
2678
  available: true,
2614
2679
  scaffoldPaths: [CLAUDE_SETTINGS_LOCAL_REL],
2615
2680
  write(ctx) {
2616
- mkdirSync7(join18(ctx.repoRoot, ".claude"), { recursive: true });
2681
+ mkdirSync7(join19(ctx.repoRoot, ".claude"), { recursive: true });
2617
2682
  const { content, action } = mergeClaudeLocalSettings(ctx.repoRoot);
2618
2683
  writeFileSync6(claudeSettingsLocalPath(ctx.repoRoot), content, "utf8");
2619
2684
  ctx.report.files.push({ path: CLAUDE_SETTINGS_LOCAL_REL, action });
@@ -2622,11 +2687,11 @@ var claudeCodeAdapter = {
2622
2687
 
2623
2688
  // src/init/assistants/codex.ts
2624
2689
  import { mkdirSync as mkdirSync8, writeFileSync as writeFileSync7 } from "fs";
2625
- import { join as join20 } from "path";
2690
+ import { join as join21 } from "path";
2626
2691
 
2627
2692
  // src/init/mergeCodexConfig.ts
2628
- import { existsSync as existsSync16, readFileSync as readFileSync17 } from "fs";
2629
- import { join as join19 } from "path";
2693
+ import { existsSync as existsSync15, readFileSync as readFileSync16 } from "fs";
2694
+ import { join as join20 } from "path";
2630
2695
  var CODEX_CONFIG_REL = ".codex/config.toml";
2631
2696
  var CODEX_HERMES_START_MARKER = "# >>> hermes-repo codex (do not edit this block manually)";
2632
2697
  var CODEX_HERMES_END_MARKER = "# <<< hermes-repo codex";
@@ -2640,7 +2705,7 @@ function buildCodexHermesBlock() {
2640
2705
  ].join("\n");
2641
2706
  }
2642
2707
  function codexConfigPath(repoRoot) {
2643
- return join19(repoRoot, ".codex", "config.toml");
2708
+ return join20(repoRoot, ".codex", "config.toml");
2644
2709
  }
2645
2710
  function spliceHermesBlock(existing, block) {
2646
2711
  const startIdx = existing.indexOf(CODEX_HERMES_START_MARKER);
@@ -2663,7 +2728,7 @@ ${after}` : ""}
2663
2728
  }
2664
2729
  function mergeCodexConfig(repoRoot) {
2665
2730
  const configPath = codexConfigPath(repoRoot);
2666
- const existed = existsSync16(configPath);
2731
+ const existed = existsSync15(configPath);
2667
2732
  const block = buildCodexHermesBlock();
2668
2733
  if (!existed) {
2669
2734
  return {
@@ -2672,7 +2737,7 @@ function mergeCodexConfig(repoRoot) {
2672
2737
  action: "created"
2673
2738
  };
2674
2739
  }
2675
- const existing = readFileSync17(configPath, "utf8");
2740
+ const existing = readFileSync16(configPath, "utf8");
2676
2741
  const hasBlock = existing.includes(CODEX_HERMES_START_MARKER) && existing.includes(CODEX_HERMES_END_MARKER);
2677
2742
  return {
2678
2743
  content: spliceHermesBlock(existing, block),
@@ -2687,7 +2752,7 @@ var codexAdapter = {
2687
2752
  available: true,
2688
2753
  scaffoldPaths: [CODEX_CONFIG_REL],
2689
2754
  write(ctx) {
2690
- mkdirSync8(join20(ctx.repoRoot, ".codex"), { recursive: true });
2755
+ mkdirSync8(join21(ctx.repoRoot, ".codex"), { recursive: true });
2691
2756
  const { content, action } = mergeCodexConfig(ctx.repoRoot);
2692
2757
  writeFileSync7(codexConfigPath(ctx.repoRoot), content, "utf8");
2693
2758
  ctx.report.files.push({ path: CODEX_CONFIG_REL, action });
@@ -2696,25 +2761,25 @@ var codexAdapter = {
2696
2761
 
2697
2762
  // src/init/assistants/codebuddy.ts
2698
2763
  import { mkdirSync as mkdirSync9, writeFileSync as writeFileSync8 } from "fs";
2699
- import { join as join22 } from "path";
2764
+ import { join as join23 } from "path";
2700
2765
 
2701
2766
  // src/init/mergeCodebuddySettings.ts
2702
- import { existsSync as existsSync17, readFileSync as readFileSync18 } from "fs";
2703
- import { join as join21 } from "path";
2767
+ import { existsSync as existsSync16, readFileSync as readFileSync17 } from "fs";
2768
+ import { join as join22 } from "path";
2704
2769
  var CODEBUDDY_SETTINGS_LOCAL_REL = ".codebuddy/settings.local.json";
2705
2770
  function codebuddySettingsLocalPath(repoRoot) {
2706
- return join21(repoRoot, ".codebuddy", "settings.local.json");
2771
+ return join22(repoRoot, ".codebuddy", "settings.local.json");
2707
2772
  }
2708
2773
  function mergeCodebuddyLocalSettings(repoRoot) {
2709
2774
  const settingsPath = codebuddySettingsLocalPath(repoRoot);
2710
- const existed = existsSync17(settingsPath);
2775
+ const existed = existsSync16(settingsPath);
2711
2776
  const templateParsed = JSON.parse(
2712
2777
  renderTemplate("hooks.codebuddy.json.tpl")
2713
2778
  );
2714
2779
  let existing = {};
2715
2780
  if (existed) {
2716
2781
  try {
2717
- existing = JSON.parse(readFileSync18(settingsPath, "utf8"));
2782
+ existing = JSON.parse(readFileSync17(settingsPath, "utf8"));
2718
2783
  } catch {
2719
2784
  existing = {};
2720
2785
  }
@@ -2742,7 +2807,7 @@ var codebuddyAdapter = {
2742
2807
  available: true,
2743
2808
  scaffoldPaths: [CODEBUDDY_SETTINGS_LOCAL_REL],
2744
2809
  write(ctx) {
2745
- mkdirSync9(join22(ctx.repoRoot, ".codebuddy"), { recursive: true });
2810
+ mkdirSync9(join23(ctx.repoRoot, ".codebuddy"), { recursive: true });
2746
2811
  const { content, action } = mergeCodebuddyLocalSettings(ctx.repoRoot);
2747
2812
  writeFileSync8(codebuddySettingsLocalPath(ctx.repoRoot), content, "utf8");
2748
2813
  ctx.report.files.push({ path: CODEBUDDY_SETTINGS_LOCAL_REL, action });
@@ -2751,23 +2816,23 @@ var codebuddyAdapter = {
2751
2816
 
2752
2817
  // src/init/assistants/cursor.ts
2753
2818
  import { mkdirSync as mkdirSync10, writeFileSync as writeFileSync9 } from "fs";
2754
- import { join as join24 } from "path";
2819
+ import { join as join25 } from "path";
2755
2820
 
2756
2821
  // src/init/mergeCursorHooks.ts
2757
- import { existsSync as existsSync18, readFileSync as readFileSync19 } from "fs";
2758
- import { join as join23 } from "path";
2822
+ import { existsSync as existsSync17, readFileSync as readFileSync18 } from "fs";
2823
+ import { join as join24 } from "path";
2759
2824
  var CURSOR_HOOKS_REL = ".cursor/hooks.json";
2760
2825
  function cursorHooksPath(repoRoot) {
2761
- return join23(repoRoot, ".cursor", "hooks.json");
2826
+ return join24(repoRoot, ".cursor", "hooks.json");
2762
2827
  }
2763
2828
  function mergeCursorHooks(repoRoot) {
2764
2829
  const hooksPath = cursorHooksPath(repoRoot);
2765
- const existed = existsSync18(hooksPath);
2830
+ const existed = existsSync17(hooksPath);
2766
2831
  const templateParsed = JSON.parse(renderTemplate("hooks.cursor.json.tpl"));
2767
2832
  let existing = {};
2768
2833
  if (existed) {
2769
2834
  try {
2770
- existing = JSON.parse(readFileSync19(hooksPath, "utf8"));
2835
+ existing = JSON.parse(readFileSync18(hooksPath, "utf8"));
2771
2836
  } catch {
2772
2837
  existing = {};
2773
2838
  }
@@ -2796,7 +2861,7 @@ var cursorAdapter = {
2796
2861
  available: true,
2797
2862
  scaffoldPaths: [CURSOR_HOOKS_REL],
2798
2863
  write(ctx) {
2799
- mkdirSync10(join24(ctx.repoRoot, ".cursor"), { recursive: true });
2864
+ mkdirSync10(join25(ctx.repoRoot, ".cursor"), { recursive: true });
2800
2865
  const { content, action } = mergeCursorHooks(ctx.repoRoot);
2801
2866
  writeFileSync9(cursorHooksPath(ctx.repoRoot), content, "utf8");
2802
2867
  ctx.report.files.push({ path: CURSOR_HOOKS_REL, action });
@@ -2859,29 +2924,29 @@ function validateAssistantSelection(ids) {
2859
2924
 
2860
2925
  // src/init/ensureDirs.ts
2861
2926
  import { mkdirSync as mkdirSync11, writeFileSync as writeFileSync10 } from "fs";
2862
- import { join as join25 } from "path";
2927
+ import { join as join26 } from "path";
2863
2928
  function ensureMemoryTree(repoRoot) {
2864
- const memoryRoot = join25(repoRoot, MEMORY_DIR);
2929
+ const memoryRoot = join26(repoRoot, MEMORY_DIR);
2865
2930
  mkdirSync11(memoryRoot, { recursive: true });
2866
2931
  for (const sub of MEMORY_SUBDIRS) {
2867
- mkdirSync11(join25(memoryRoot, sub), { recursive: true });
2932
+ mkdirSync11(join26(memoryRoot, sub), { recursive: true });
2868
2933
  }
2869
- mkdirSync11(join25(repoRoot, ".claude"), { recursive: true });
2934
+ mkdirSync11(join26(repoRoot, ".claude"), { recursive: true });
2870
2935
  for (const sub of GITKEEP_DIRS) {
2871
- const keepPath = join25(memoryRoot, sub, ".gitkeep");
2936
+ const keepPath = join26(memoryRoot, sub, ".gitkeep");
2872
2937
  writeFileSync10(keepPath, "", { flag: "a" });
2873
2938
  }
2874
2939
  }
2875
2940
 
2876
2941
  // src/init/mergeAssistants.ts
2877
- import { existsSync as existsSync19, readFileSync as readFileSync20 } from "fs";
2942
+ import { existsSync as existsSync18, readFileSync as readFileSync19 } from "fs";
2878
2943
  function readExistingAssistants(repoRoot) {
2879
2944
  const configPath = memoryPath(repoRoot, "config.json");
2880
- if (!existsSync19(configPath)) {
2945
+ if (!existsSync18(configPath)) {
2881
2946
  return [];
2882
2947
  }
2883
2948
  try {
2884
- const config = JSON.parse(readFileSync20(configPath, "utf8"));
2949
+ const config = JSON.parse(readFileSync19(configPath, "utf8"));
2885
2950
  if (!Array.isArray(config.assistants)) {
2886
2951
  return [];
2887
2952
  }
@@ -2896,16 +2961,16 @@ function mergeAssistants(repoRoot, selected) {
2896
2961
  }
2897
2962
 
2898
2963
  // src/init/mergeGitignore.ts
2899
- import { existsSync as existsSync20, readFileSync as readFileSync21, writeFileSync as writeFileSync11 } from "fs";
2900
- import { join as join26 } from "path";
2964
+ import { existsSync as existsSync19, readFileSync as readFileSync20, writeFileSync as writeFileSync11 } from "fs";
2965
+ import { join as join27 } from "path";
2901
2966
  var START_MARKER = "# >>> hermes-repo memory (do not edit this block manually)";
2902
2967
  var END_MARKER = "# <<< hermes-repo memory";
2903
2968
  function mergeHermesGitignore(repoRoot) {
2904
2969
  const block = readTemplate("gitignore-block.txt").trimEnd() + "\n";
2905
- const gitignorePath = join26(repoRoot, ".gitignore");
2906
- const contentBefore = existsSync20(gitignorePath) ? readFileSync21(gitignorePath, "utf8") : "";
2970
+ const gitignorePath = join27(repoRoot, ".gitignore");
2971
+ const contentBefore = existsSync19(gitignorePath) ? readFileSync20(gitignorePath, "utf8") : "";
2907
2972
  const warnBroadMemoryIgnore = contentBefore.length > 0 && !contentBefore.includes(START_MARKER) && /(^|\n)\.memory\/\s*$/m.test(contentBefore);
2908
- if (!existsSync20(gitignorePath)) {
2973
+ if (!existsSync19(gitignorePath)) {
2909
2974
  writeFileSync11(gitignorePath, `${block}
2910
2975
  `, "utf8");
2911
2976
  return { action: "created", warnBroadMemoryIgnore: false };
@@ -2937,22 +3002,33 @@ function mergeHermesGitignore(repoRoot) {
2937
3002
  import { copyFileSync, mkdirSync as mkdirSync12, writeFileSync as writeFileSync14 } from "fs";
2938
3003
 
2939
3004
  // src/init/mergeConfig.ts
2940
- import { existsSync as existsSync21, readFileSync as readFileSync22 } from "fs";
3005
+ import { existsSync as existsSync20, readFileSync as readFileSync21 } from "fs";
2941
3006
  var DEFAULT_LLM = {
2942
3007
  enabled: false,
3008
+ provider: "openai",
2943
3009
  baseUrl: "https://api.openai.com/v1",
2944
- model: "gpt-4o"
3010
+ model: "gpt-4o",
3011
+ apiKey: "",
3012
+ timeoutMs: 6e4,
3013
+ maxInputChars: 24e3,
3014
+ mode: "async"
2945
3015
  };
2946
3016
  var DEFAULT_CONSOLIDATE = {
2947
- autoArchiveDays: 30
3017
+ autoArchiveDays: 30,
3018
+ autoFlush: {
3019
+ enabled: false,
3020
+ minPendingSessions: 3,
3021
+ minIntervalMinutes: 30,
3022
+ maxPendingChars: 2e4
3023
+ }
2948
3024
  };
2949
3025
  function mergeConfigForInit(repoRoot, assistants) {
2950
3026
  const configPath = memoryPath(repoRoot, "config.json");
2951
- const existed = existsSync21(configPath);
3027
+ const existed = existsSync20(configPath);
2952
3028
  let existing = {};
2953
3029
  if (existed) {
2954
3030
  try {
2955
- existing = JSON.parse(readFileSync22(configPath, "utf8"));
3031
+ existing = JSON.parse(readFileSync21(configPath, "utf8"));
2956
3032
  } catch {
2957
3033
  existing = {};
2958
3034
  }
@@ -2960,6 +3036,7 @@ function mergeConfigForInit(repoRoot, assistants) {
2960
3036
  const prevStorage = existing.storage && typeof existing.storage === "object" && !Array.isArray(existing.storage) ? existing.storage : {};
2961
3037
  const prevLlm = existing.llm && typeof existing.llm === "object" && !Array.isArray(existing.llm) ? existing.llm : {};
2962
3038
  const prevConsolidate = existing.consolidate && typeof existing.consolidate === "object" && !Array.isArray(existing.consolidate) ? existing.consolidate : {};
3039
+ const prevAutoFlush = prevConsolidate.autoFlush && typeof prevConsolidate.autoFlush === "object" && !Array.isArray(prevConsolidate.autoFlush) ? prevConsolidate.autoFlush : {};
2963
3040
  const merged = {
2964
3041
  ...existing,
2965
3042
  version: 2,
@@ -2970,7 +3047,14 @@ function mergeConfigForInit(repoRoot, assistants) {
2970
3047
  assistants,
2971
3048
  debug: existing.debug === true,
2972
3049
  llm: { ...DEFAULT_LLM, ...prevLlm },
2973
- consolidate: { ...DEFAULT_CONSOLIDATE, ...prevConsolidate }
3050
+ consolidate: {
3051
+ ...DEFAULT_CONSOLIDATE,
3052
+ ...prevConsolidate,
3053
+ autoFlush: {
3054
+ ...DEFAULT_CONSOLIDATE.autoFlush,
3055
+ ...prevAutoFlush
3056
+ }
3057
+ }
2974
3058
  };
2975
3059
  return {
2976
3060
  content: `${JSON.stringify(merged, null, 2)}
@@ -2980,8 +3064,8 @@ function mergeConfigForInit(repoRoot, assistants) {
2980
3064
  }
2981
3065
 
2982
3066
  // src/init/mergeAgentsMd.ts
2983
- import { existsSync as existsSync22, readFileSync as readFileSync23, writeFileSync as writeFileSync12 } from "fs";
2984
- import { join as join27 } from "path";
3067
+ import { existsSync as existsSync21, readFileSync as readFileSync22, writeFileSync as writeFileSync12 } from "fs";
3068
+ import { join as join28 } from "path";
2985
3069
  var HERMES_AGENTS_START_MARKER = "<!-- >>> hermes-repo agents (do not edit this block manually) -->";
2986
3070
  var HERMES_AGENTS_END_MARKER = "<!-- <<< hermes-repo agents -->";
2987
3071
  function buildHermesAgentsBlockBody() {
@@ -3037,13 +3121,13 @@ function spliceHermesBlock2(existing, block) {
3037
3121
  `;
3038
3122
  }
3039
3123
  function mergeAgentsMd(repoRoot, force) {
3040
- const agentsPath = join27(repoRoot, "AGENTS.md");
3124
+ const agentsPath = join28(repoRoot, "AGENTS.md");
3041
3125
  const block = buildHermesAgentsMarkedBlock();
3042
- if (!existsSync22(agentsPath)) {
3126
+ if (!existsSync21(agentsPath)) {
3043
3127
  writeFileSync12(agentsPath, buildNewAgentsMd(), "utf8");
3044
3128
  return "created";
3045
3129
  }
3046
- const content = readFileSync23(agentsPath, "utf8");
3130
+ const content = readFileSync22(agentsPath, "utf8");
3047
3131
  if (agentsMdHasHermesBlock(content)) {
3048
3132
  if (!force) {
3049
3133
  return "skipped";
@@ -3059,9 +3143,9 @@ function mergeAgentsMd(repoRoot, force) {
3059
3143
  }
3060
3144
 
3061
3145
  // src/init/scaffoldWrite.ts
3062
- import { existsSync as existsSync23, writeFileSync as writeFileSync13 } from "fs";
3146
+ import { existsSync as existsSync22, writeFileSync as writeFileSync13 } from "fs";
3063
3147
  function shouldWriteFile(absolutePath, force) {
3064
- if (!existsSync23(absolutePath)) {
3148
+ if (!existsSync22(absolutePath)) {
3065
3149
  return { write: true, action: "created" };
3066
3150
  }
3067
3151
  if (force) {
@@ -3151,15 +3235,15 @@ function writeScaffoldFiles(repoRoot, opts, report) {
3151
3235
 
3152
3236
  // src/init/prompts.ts
3153
3237
  import { checkbox, confirm, input } from "@inquirer/prompts";
3154
- import { existsSync as existsSync24, readFileSync as readFileSync24 } from "fs";
3238
+ import { existsSync as existsSync23, readFileSync as readFileSync23 } from "fs";
3155
3239
  import { resolve as resolve6 } from "path";
3156
3240
  function isInitialized(targetDir) {
3157
3241
  const configPath = memoryPath(targetDir, "config.json");
3158
- if (!existsSync24(configPath)) {
3242
+ if (!existsSync23(configPath)) {
3159
3243
  return false;
3160
3244
  }
3161
3245
  try {
3162
- const config = JSON.parse(readFileSync24(configPath, "utf8"));
3246
+ const config = JSON.parse(readFileSync23(configPath, "utf8"));
3163
3247
  return typeof config.version === "number" && config.version >= 1;
3164
3248
  } catch {
3165
3249
  return false;