@riconext/hermes-repo 1.1.1 → 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/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.2.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 4e1a9f7: 新增 capture 后自动 flush 调度配置。开启 `consolidate.autoFlush.enabled` 后,capture 成功写入时会根据待处理 session 数量、距离上次 flush 的时间和待处理内容字符数,在后台触发 `hermes-repo flush`。
8
+
3
9
  ## 1.1.1
4
10
 
5
11
  ### Patch Changes
package/README.md CHANGED
@@ -272,6 +272,14 @@ npx @riconext/hermes-repo init
272
272
  "timeoutMs": 60000,
273
273
  "maxInputChars": 24000,
274
274
  "mode": "async"
275
+ },
276
+ "consolidate": {
277
+ "autoFlush": {
278
+ "enabled": false,
279
+ "minPendingSessions": 3,
280
+ "minIntervalMinutes": 30,
281
+ "maxPendingChars": 20000
282
+ }
275
283
  }
276
284
  }
277
285
  ```
@@ -285,6 +293,8 @@ npx @riconext/hermes-repo init
285
293
 
286
294
  非 DeepSeek 时:保持 `enabled: true`,将 `baseUrl`、`model` 换成你所用网关提供的 OpenAI 兼容地址与模型名即可。
287
295
 
296
+ `consolidate.autoFlush.enabled` 默认关闭。开启后,capture 成功写入时会在后台按阈值触发 `flush`:pending/stale session 数达到 `minPendingSessions`、距离上次 flush 超过 `minIntervalMinutes`,或待处理内容达到 `maxPendingChars` 时触发。
297
+
288
298
  **`init -y` 时**:会生成 `enabled: false` 的骨架;再次 `init` **不会覆盖**已有 `apiKey`,可安全补配。
289
299
 
290
300
  ### 安全说明
package/dist/cli.js CHANGED
@@ -113,8 +113,15 @@ function parseLlmConfig(raw) {
113
113
  }
114
114
  function parseConsolidateConfig(raw) {
115
115
  const c = raw.consolidate;
116
+ const autoFlush = c?.autoFlush && typeof c.autoFlush === "object" && !Array.isArray(c.autoFlush) ? c.autoFlush : {};
116
117
  return {
117
- 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
+ }
118
125
  };
119
126
  }
120
127
  function readConfigAtRepo(repoRoot) {
@@ -151,7 +158,15 @@ function readConfigAtRepo(repoRoot) {
151
158
  maxInputChars: 24e3,
152
159
  mode: "async"
153
160
  },
154
- consolidate: { autoArchiveDays: 30 }
161
+ consolidate: {
162
+ autoArchiveDays: 30,
163
+ autoFlush: {
164
+ enabled: false,
165
+ minPendingSessions: 3,
166
+ minIntervalMinutes: 30,
167
+ maxPendingChars: 2e4
168
+ }
169
+ }
155
170
  };
156
171
  }
157
172
  return null;
@@ -1106,6 +1121,11 @@ function needsLlm(session) {
1106
1121
  return false;
1107
1122
  }
1108
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
+
1109
1129
  // src/consolidate/state.ts
1110
1130
  import {
1111
1131
  existsSync as existsSync6,
@@ -1772,6 +1792,9 @@ function extractDomainsFromResults(files) {
1772
1792
  var CONSOLIDATE_LOCK_TTL_MS = 30 * 60 * 1e3;
1773
1793
 
1774
1794
  // src/consolidate/scheduleConsolidate.ts
1795
+ function cliPath2() {
1796
+ return join11(dirname5(fileURLToPath2(import.meta.url)), "..", "cli.js");
1797
+ }
1775
1798
  async function runFlushCommand(opts) {
1776
1799
  const ctx = loadRepoContext(opts.cwd);
1777
1800
  if (!ctx) {
@@ -1806,11 +1829,83 @@ async function runFlushCommand(opts) {
1806
1829
  debug: opts.debug ?? ctx.config.debug
1807
1830
  });
1808
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
+ }
1809
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
+ }
1810
1867
  const lock = readConsolidateLock(opts.repoRoot);
1811
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
+ );
1812
1884
  return;
1813
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
+ }
1814
1909
  }
1815
1910
 
1816
1911
  // src/capture/commitCapture.ts
@@ -1883,7 +1978,7 @@ async function commitCapture(opts) {
1883
1978
  // src/capture/claude-code/resolveSession.ts
1884
1979
  import { existsSync as existsSync9, readdirSync as readdirSync4, readFileSync as readFileSync11, statSync } from "fs";
1885
1980
  import { homedir } from "os";
1886
- 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";
1887
1982
  function encodeClaudeProjectDir(absPath) {
1888
1983
  return resolve3(absPath).replace(/\//g, "-");
1889
1984
  }
@@ -1897,20 +1992,20 @@ function resolveSessionJsonlPath(repoRoot, options = {}) {
1897
1992
  return resolve3(fromHook);
1898
1993
  }
1899
1994
  const sessionId = process.env.CLAUDE_SESSION_ID ?? process.env.CLAUDE_CODE_SESSION_ID ?? process.env.SESSION_ID;
1900
- const claudeHome = process.env.CLAUDE_CONFIG_DIR ? resolve3(process.env.CLAUDE_CONFIG_DIR) : join11(homedir(), ".claude");
1901
- const projectsRoot = join11(claudeHome, "projects");
1995
+ const claudeHome = process.env.CLAUDE_CONFIG_DIR ? resolve3(process.env.CLAUDE_CONFIG_DIR) : join12(homedir(), ".claude");
1996
+ const projectsRoot = join12(claudeHome, "projects");
1902
1997
  if (!existsSync9(projectsRoot)) {
1903
1998
  return null;
1904
1999
  }
1905
2000
  const cwd = resolve3(options.cwd ?? repoRoot);
1906
2001
  const preferredProjectDir = encodeClaudeProjectDir(cwd);
1907
- const preferredPath = join11(projectsRoot, preferredProjectDir);
2002
+ const preferredPath = join12(projectsRoot, preferredProjectDir);
1908
2003
  if (existsSync9(preferredPath)) {
1909
2004
  const hit = pickNewestJsonl(preferredPath, sessionId);
1910
2005
  if (hit) {
1911
2006
  return hit;
1912
2007
  }
1913
- const legacySessions = join11(preferredPath, "sessions");
2008
+ const legacySessions = join12(preferredPath, "sessions");
1914
2009
  if (existsSync9(legacySessions)) {
1915
2010
  const legacyHit = pickNewestJsonl(legacySessions, sessionId);
1916
2011
  if (legacyHit) {
@@ -1921,9 +2016,9 @@ function resolveSessionJsonlPath(repoRoot, options = {}) {
1921
2016
  const candidates = [];
1922
2017
  for (const projectDir of readdirSync4(projectsRoot, { withFileTypes: true })) {
1923
2018
  if (!projectDir.isDirectory()) continue;
1924
- const projectPath = join11(projectsRoot, projectDir.name);
2019
+ const projectPath = join12(projectsRoot, projectDir.name);
1925
2020
  collectJsonlCandidates(projectPath, sessionId, candidates);
1926
- const legacySessions = join11(projectPath, "sessions");
2021
+ const legacySessions = join12(projectPath, "sessions");
1927
2022
  if (existsSync9(legacySessions)) {
1928
2023
  collectJsonlCandidates(legacySessions, sessionId, candidates);
1929
2024
  }
@@ -1951,7 +2046,7 @@ function collectJsonlCandidates(dir, sessionId, out) {
1951
2046
  if (!entry.isFile() || !entry.name.endsWith(".jsonl")) {
1952
2047
  continue;
1953
2048
  }
1954
- const fullPath = join11(dir, entry.name);
2049
+ const fullPath = join12(dir, entry.name);
1955
2050
  if (sessionId && !entry.name.includes(sessionId) && basename2(entry.name, ".jsonl") !== sessionId) {
1956
2051
  continue;
1957
2052
  }
@@ -1986,7 +2081,7 @@ async function runClaudeCodeCapture(repoRoot, cwd, dryRun, options) {
1986
2081
  // src/capture/codebuddy/resolveSession.ts
1987
2082
  import { existsSync as existsSync10, readdirSync as readdirSync5, statSync as statSync2 } from "fs";
1988
2083
  import { homedir as homedir2 } from "os";
1989
- 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";
1990
2085
  function encodeCodebuddyProjectDir(absPath) {
1991
2086
  return resolve4(absPath).replace(/^\//, "").replace(/\//g, "-");
1992
2087
  }
@@ -2000,14 +2095,14 @@ function resolveCodebuddySessionJsonl(options) {
2000
2095
  return resolve4(fromHook);
2001
2096
  }
2002
2097
  const sessionId = process.env.CODEBUDDY_SESSION_ID ?? process.env.SESSION_ID;
2003
- const codebuddyHome = process.env.CODEBUDDY_CONFIG_DIR ? resolve4(process.env.CODEBUDDY_CONFIG_DIR) : join12(homedir2(), ".codebuddy");
2004
- const projectsRoot = join12(codebuddyHome, "projects");
2098
+ const codebuddyHome = process.env.CODEBUDDY_CONFIG_DIR ? resolve4(process.env.CODEBUDDY_CONFIG_DIR) : join13(homedir2(), ".codebuddy");
2099
+ const projectsRoot = join13(codebuddyHome, "projects");
2005
2100
  if (!existsSync10(projectsRoot)) {
2006
2101
  return null;
2007
2102
  }
2008
2103
  const cwd = resolve4(options.cwd ?? options.repoRoot);
2009
2104
  const preferredProjectDir = encodeCodebuddyProjectDir(cwd);
2010
- const preferredPath = join12(projectsRoot, preferredProjectDir);
2105
+ const preferredPath = join13(projectsRoot, preferredProjectDir);
2011
2106
  if (existsSync10(preferredPath)) {
2012
2107
  const hit = pickNewestJsonlRecursive(preferredPath, sessionId);
2013
2108
  if (hit) {
@@ -2019,7 +2114,7 @@ function resolveCodebuddySessionJsonl(options) {
2019
2114
  if (!projectDir.isDirectory()) {
2020
2115
  continue;
2021
2116
  }
2022
- collectJsonlRecursive(join12(projectsRoot, projectDir.name), sessionId, candidates);
2117
+ collectJsonlRecursive(join13(projectsRoot, projectDir.name), sessionId, candidates);
2023
2118
  }
2024
2119
  if (candidates.length === 0) {
2025
2120
  return null;
@@ -2041,7 +2136,7 @@ function collectJsonlRecursive(dir, sessionId, out) {
2041
2136
  return;
2042
2137
  }
2043
2138
  for (const entry of readdirSync5(dir, { withFileTypes: true })) {
2044
- const full = join12(dir, entry.name);
2139
+ const full = join13(dir, entry.name);
2045
2140
  if (entry.isDirectory()) {
2046
2141
  collectJsonlRecursive(full, sessionId, out);
2047
2142
  continue;
@@ -2091,7 +2186,7 @@ async function runCodebuddyCapture(repoRoot, cwd, dryRun, options) {
2091
2186
  // src/capture/cursor/resolveSession.ts
2092
2187
  import { existsSync as existsSync11, readdirSync as readdirSync6, statSync as statSync3 } from "fs";
2093
2188
  import { homedir as homedir3 } from "os";
2094
- import { join as join13, resolve as resolve5 } from "path";
2189
+ import { join as join14, resolve as resolve5 } from "path";
2095
2190
  function encodeCursorProjectDir(absPath) {
2096
2191
  return resolve5(absPath).replace(/^\//, "").replace(/\//g, "-");
2097
2192
  }
@@ -2100,25 +2195,25 @@ function resolveCursorSessionJsonl(options) {
2100
2195
  if (override && existsSync11(override)) {
2101
2196
  return resolve5(override);
2102
2197
  }
2103
- const cursorHome = process.env.CURSOR_CONFIG_DIR ? resolve5(process.env.CURSOR_CONFIG_DIR) : join13(homedir3(), ".cursor");
2104
- const projectsRoot = join13(cursorHome, "projects");
2198
+ const cursorHome = process.env.CURSOR_CONFIG_DIR ? resolve5(process.env.CURSOR_CONFIG_DIR) : join14(homedir3(), ".cursor");
2199
+ const projectsRoot = join14(cursorHome, "projects");
2105
2200
  if (!existsSync11(projectsRoot)) {
2106
2201
  return null;
2107
2202
  }
2108
2203
  const sessionId = options.hookInput?.sessionId ?? options.hookInput?.conversationId ?? process.env.CURSOR_SESSION_ID ?? process.env.CURSOR_AGENT_SESSION_ID;
2109
2204
  const workspace = options.hookInput?.workspaceRoots?.[0] ?? (options.cwd ? resolve5(options.cwd) : resolve5(options.repoRoot));
2110
2205
  const encoded = encodeCursorProjectDir(workspace);
2111
- const projectDir = join13(projectsRoot, encoded);
2112
- const transcriptsRoot = join13(projectDir, "agent-transcripts");
2206
+ const projectDir = join14(projectsRoot, encoded);
2207
+ const transcriptsRoot = join14(projectDir, "agent-transcripts");
2113
2208
  if (!existsSync11(transcriptsRoot)) {
2114
2209
  return pickNewestCursorJsonl(projectsRoot);
2115
2210
  }
2116
2211
  if (sessionId) {
2117
- const direct = join13(transcriptsRoot, sessionId, `${sessionId}.jsonl`);
2212
+ const direct = join14(transcriptsRoot, sessionId, `${sessionId}.jsonl`);
2118
2213
  if (existsSync11(direct)) {
2119
2214
  return direct;
2120
2215
  }
2121
- const nested = findJsonlUnderDir(join13(transcriptsRoot, sessionId), sessionId);
2216
+ const nested = findJsonlUnderDir(join14(transcriptsRoot, sessionId), sessionId);
2122
2217
  if (nested) {
2123
2218
  return nested;
2124
2219
  }
@@ -2129,16 +2224,16 @@ function findJsonlUnderDir(dir, sessionId) {
2129
2224
  if (!existsSync11(dir)) {
2130
2225
  return null;
2131
2226
  }
2132
- const direct = join13(dir, `${sessionId}.jsonl`);
2227
+ const direct = join14(dir, `${sessionId}.jsonl`);
2133
2228
  if (existsSync11(direct)) {
2134
2229
  return direct;
2135
2230
  }
2136
2231
  for (const entry of readdirSync6(dir, { withFileTypes: true })) {
2137
2232
  if (entry.isFile() && entry.name.endsWith(".jsonl")) {
2138
- return join13(dir, entry.name);
2233
+ return join14(dir, entry.name);
2139
2234
  }
2140
2235
  if (entry.isDirectory()) {
2141
- const found = findJsonlUnderDir(join13(dir, entry.name), sessionId);
2236
+ const found = findJsonlUnderDir(join14(dir, entry.name), sessionId);
2142
2237
  if (found) {
2143
2238
  return found;
2144
2239
  }
@@ -2160,7 +2255,7 @@ function collectJsonlRecursive2(dir, out) {
2160
2255
  return;
2161
2256
  }
2162
2257
  for (const entry of readdirSync6(dir, { withFileTypes: true })) {
2163
- const full = join13(dir, entry.name);
2258
+ const full = join14(dir, entry.name);
2164
2259
  if (entry.isDirectory()) {
2165
2260
  collectJsonlRecursive2(full, out);
2166
2261
  continue;
@@ -2386,7 +2481,7 @@ async function runFlushCommandCli(opts) {
2386
2481
 
2387
2482
  // src/inject/runInject.ts
2388
2483
  import { existsSync as existsSync12, readdirSync as readdirSync7, readFileSync as readFileSync12 } from "fs";
2389
- import { join as join14 } from "path";
2484
+ import { join as join15 } from "path";
2390
2485
 
2391
2486
  // src/inject/constants.ts
2392
2487
  var INJECT_MAX_CHARS = 8e3;
@@ -2466,7 +2561,7 @@ function readAllRules(repoRoot) {
2466
2561
  const parts = [];
2467
2562
  for (const file of files) {
2468
2563
  try {
2469
- const filePath = join14(rulesDir, file);
2564
+ const filePath = join15(rulesDir, file);
2470
2565
  const content = readFileSync12(filePath, "utf8").trim();
2471
2566
  if (!content) continue;
2472
2567
  parts.push(`### ${file}`, "", content, "");
@@ -2494,46 +2589,46 @@ import { resolve as resolve7 } from "path";
2494
2589
 
2495
2590
  // src/init/assistants/claude-code.ts
2496
2591
  import { mkdirSync as mkdirSync7, writeFileSync as writeFileSync6 } from "fs";
2497
- import { join as join18 } from "path";
2592
+ import { join as join19 } from "path";
2498
2593
 
2499
2594
  // src/init/mergeClaudeSettings.ts
2500
2595
  import { existsSync as existsSync14, readFileSync as readFileSync15 } from "fs";
2501
- import { join as join17 } from "path";
2596
+ import { join as join18 } from "path";
2502
2597
 
2503
2598
  // src/init/templateDir.ts
2504
2599
  import { existsSync as existsSync13, readFileSync as readFileSync14 } from "fs";
2505
- import { dirname as dirname6, join as join16 } from "path";
2506
- import { fileURLToPath as fileURLToPath3 } from "url";
2600
+ import { dirname as dirname7, join as join17 } from "path";
2601
+ import { fileURLToPath as fileURLToPath4 } from "url";
2507
2602
 
2508
2603
  // src/index.ts
2509
2604
  import { readFileSync as readFileSync13 } from "fs";
2510
- import { dirname as dirname5, join as join15 } from "path";
2511
- import { fileURLToPath as fileURLToPath2 } from "url";
2605
+ import { dirname as dirname6, join as join16 } from "path";
2606
+ import { fileURLToPath as fileURLToPath3 } from "url";
2512
2607
  var PACKAGE_NAME = "@riconext/hermes-repo";
2513
- var __dirname = dirname5(fileURLToPath2(import.meta.url));
2608
+ var __dirname = dirname6(fileURLToPath3(import.meta.url));
2514
2609
  function readPkgVersion() {
2515
- const pkgPath = join15(__dirname, "..", "package.json");
2610
+ const pkgPath = join16(__dirname, "..", "package.json");
2516
2611
  const pkg = JSON.parse(readFileSync13(pkgPath, "utf8"));
2517
2612
  return pkg.version;
2518
2613
  }
2519
2614
 
2520
2615
  // src/init/templateDir.ts
2521
2616
  function resolveTemplateDir() {
2522
- const here = dirname6(fileURLToPath3(import.meta.url));
2617
+ const here = dirname7(fileURLToPath4(import.meta.url));
2523
2618
  const candidates = [
2524
- join16(here, "templates"),
2525
- join16(here, "..", "..", "templates")
2619
+ join17(here, "templates"),
2620
+ join17(here, "..", "..", "templates")
2526
2621
  ];
2527
2622
  for (const dir of candidates) {
2528
2623
  if (existsSync13(dir)) {
2529
2624
  return dir;
2530
2625
  }
2531
2626
  }
2532
- return join16(here, "templates");
2627
+ return join17(here, "templates");
2533
2628
  }
2534
2629
  var templateDir = resolveTemplateDir();
2535
2630
  function resolveTemplatePath(name) {
2536
- return join16(templateDir, name);
2631
+ return join17(templateDir, name);
2537
2632
  }
2538
2633
  function readTemplate(name) {
2539
2634
  return readFileSync14(resolveTemplatePath(name), "utf8");
@@ -2546,7 +2641,7 @@ function renderTemplate(name) {
2546
2641
  // src/init/mergeClaudeSettings.ts
2547
2642
  var CLAUDE_SETTINGS_LOCAL_REL = ".claude/settings.local.json";
2548
2643
  function claudeSettingsLocalPath(repoRoot) {
2549
- return join17(repoRoot, ".claude", "settings.local.json");
2644
+ return join18(repoRoot, ".claude", "settings.local.json");
2550
2645
  }
2551
2646
  function mergeClaudeLocalSettings(repoRoot) {
2552
2647
  const settingsPath = claudeSettingsLocalPath(repoRoot);
@@ -2583,7 +2678,7 @@ var claudeCodeAdapter = {
2583
2678
  available: true,
2584
2679
  scaffoldPaths: [CLAUDE_SETTINGS_LOCAL_REL],
2585
2680
  write(ctx) {
2586
- mkdirSync7(join18(ctx.repoRoot, ".claude"), { recursive: true });
2681
+ mkdirSync7(join19(ctx.repoRoot, ".claude"), { recursive: true });
2587
2682
  const { content, action } = mergeClaudeLocalSettings(ctx.repoRoot);
2588
2683
  writeFileSync6(claudeSettingsLocalPath(ctx.repoRoot), content, "utf8");
2589
2684
  ctx.report.files.push({ path: CLAUDE_SETTINGS_LOCAL_REL, action });
@@ -2592,11 +2687,11 @@ var claudeCodeAdapter = {
2592
2687
 
2593
2688
  // src/init/assistants/codex.ts
2594
2689
  import { mkdirSync as mkdirSync8, writeFileSync as writeFileSync7 } from "fs";
2595
- import { join as join20 } from "path";
2690
+ import { join as join21 } from "path";
2596
2691
 
2597
2692
  // src/init/mergeCodexConfig.ts
2598
2693
  import { existsSync as existsSync15, readFileSync as readFileSync16 } from "fs";
2599
- import { join as join19 } from "path";
2694
+ import { join as join20 } from "path";
2600
2695
  var CODEX_CONFIG_REL = ".codex/config.toml";
2601
2696
  var CODEX_HERMES_START_MARKER = "# >>> hermes-repo codex (do not edit this block manually)";
2602
2697
  var CODEX_HERMES_END_MARKER = "# <<< hermes-repo codex";
@@ -2610,7 +2705,7 @@ function buildCodexHermesBlock() {
2610
2705
  ].join("\n");
2611
2706
  }
2612
2707
  function codexConfigPath(repoRoot) {
2613
- return join19(repoRoot, ".codex", "config.toml");
2708
+ return join20(repoRoot, ".codex", "config.toml");
2614
2709
  }
2615
2710
  function spliceHermesBlock(existing, block) {
2616
2711
  const startIdx = existing.indexOf(CODEX_HERMES_START_MARKER);
@@ -2657,7 +2752,7 @@ var codexAdapter = {
2657
2752
  available: true,
2658
2753
  scaffoldPaths: [CODEX_CONFIG_REL],
2659
2754
  write(ctx) {
2660
- mkdirSync8(join20(ctx.repoRoot, ".codex"), { recursive: true });
2755
+ mkdirSync8(join21(ctx.repoRoot, ".codex"), { recursive: true });
2661
2756
  const { content, action } = mergeCodexConfig(ctx.repoRoot);
2662
2757
  writeFileSync7(codexConfigPath(ctx.repoRoot), content, "utf8");
2663
2758
  ctx.report.files.push({ path: CODEX_CONFIG_REL, action });
@@ -2666,14 +2761,14 @@ var codexAdapter = {
2666
2761
 
2667
2762
  // src/init/assistants/codebuddy.ts
2668
2763
  import { mkdirSync as mkdirSync9, writeFileSync as writeFileSync8 } from "fs";
2669
- import { join as join22 } from "path";
2764
+ import { join as join23 } from "path";
2670
2765
 
2671
2766
  // src/init/mergeCodebuddySettings.ts
2672
2767
  import { existsSync as existsSync16, readFileSync as readFileSync17 } from "fs";
2673
- import { join as join21 } from "path";
2768
+ import { join as join22 } from "path";
2674
2769
  var CODEBUDDY_SETTINGS_LOCAL_REL = ".codebuddy/settings.local.json";
2675
2770
  function codebuddySettingsLocalPath(repoRoot) {
2676
- return join21(repoRoot, ".codebuddy", "settings.local.json");
2771
+ return join22(repoRoot, ".codebuddy", "settings.local.json");
2677
2772
  }
2678
2773
  function mergeCodebuddyLocalSettings(repoRoot) {
2679
2774
  const settingsPath = codebuddySettingsLocalPath(repoRoot);
@@ -2712,7 +2807,7 @@ var codebuddyAdapter = {
2712
2807
  available: true,
2713
2808
  scaffoldPaths: [CODEBUDDY_SETTINGS_LOCAL_REL],
2714
2809
  write(ctx) {
2715
- mkdirSync9(join22(ctx.repoRoot, ".codebuddy"), { recursive: true });
2810
+ mkdirSync9(join23(ctx.repoRoot, ".codebuddy"), { recursive: true });
2716
2811
  const { content, action } = mergeCodebuddyLocalSettings(ctx.repoRoot);
2717
2812
  writeFileSync8(codebuddySettingsLocalPath(ctx.repoRoot), content, "utf8");
2718
2813
  ctx.report.files.push({ path: CODEBUDDY_SETTINGS_LOCAL_REL, action });
@@ -2721,14 +2816,14 @@ var codebuddyAdapter = {
2721
2816
 
2722
2817
  // src/init/assistants/cursor.ts
2723
2818
  import { mkdirSync as mkdirSync10, writeFileSync as writeFileSync9 } from "fs";
2724
- import { join as join24 } from "path";
2819
+ import { join as join25 } from "path";
2725
2820
 
2726
2821
  // src/init/mergeCursorHooks.ts
2727
2822
  import { existsSync as existsSync17, readFileSync as readFileSync18 } from "fs";
2728
- import { join as join23 } from "path";
2823
+ import { join as join24 } from "path";
2729
2824
  var CURSOR_HOOKS_REL = ".cursor/hooks.json";
2730
2825
  function cursorHooksPath(repoRoot) {
2731
- return join23(repoRoot, ".cursor", "hooks.json");
2826
+ return join24(repoRoot, ".cursor", "hooks.json");
2732
2827
  }
2733
2828
  function mergeCursorHooks(repoRoot) {
2734
2829
  const hooksPath = cursorHooksPath(repoRoot);
@@ -2766,7 +2861,7 @@ var cursorAdapter = {
2766
2861
  available: true,
2767
2862
  scaffoldPaths: [CURSOR_HOOKS_REL],
2768
2863
  write(ctx) {
2769
- mkdirSync10(join24(ctx.repoRoot, ".cursor"), { recursive: true });
2864
+ mkdirSync10(join25(ctx.repoRoot, ".cursor"), { recursive: true });
2770
2865
  const { content, action } = mergeCursorHooks(ctx.repoRoot);
2771
2866
  writeFileSync9(cursorHooksPath(ctx.repoRoot), content, "utf8");
2772
2867
  ctx.report.files.push({ path: CURSOR_HOOKS_REL, action });
@@ -2829,16 +2924,16 @@ function validateAssistantSelection(ids) {
2829
2924
 
2830
2925
  // src/init/ensureDirs.ts
2831
2926
  import { mkdirSync as mkdirSync11, writeFileSync as writeFileSync10 } from "fs";
2832
- import { join as join25 } from "path";
2927
+ import { join as join26 } from "path";
2833
2928
  function ensureMemoryTree(repoRoot) {
2834
- const memoryRoot = join25(repoRoot, MEMORY_DIR);
2929
+ const memoryRoot = join26(repoRoot, MEMORY_DIR);
2835
2930
  mkdirSync11(memoryRoot, { recursive: true });
2836
2931
  for (const sub of MEMORY_SUBDIRS) {
2837
- mkdirSync11(join25(memoryRoot, sub), { recursive: true });
2932
+ mkdirSync11(join26(memoryRoot, sub), { recursive: true });
2838
2933
  }
2839
- mkdirSync11(join25(repoRoot, ".claude"), { recursive: true });
2934
+ mkdirSync11(join26(repoRoot, ".claude"), { recursive: true });
2840
2935
  for (const sub of GITKEEP_DIRS) {
2841
- const keepPath = join25(memoryRoot, sub, ".gitkeep");
2936
+ const keepPath = join26(memoryRoot, sub, ".gitkeep");
2842
2937
  writeFileSync10(keepPath, "", { flag: "a" });
2843
2938
  }
2844
2939
  }
@@ -2867,12 +2962,12 @@ function mergeAssistants(repoRoot, selected) {
2867
2962
 
2868
2963
  // src/init/mergeGitignore.ts
2869
2964
  import { existsSync as existsSync19, readFileSync as readFileSync20, writeFileSync as writeFileSync11 } from "fs";
2870
- import { join as join26 } from "path";
2965
+ import { join as join27 } from "path";
2871
2966
  var START_MARKER = "# >>> hermes-repo memory (do not edit this block manually)";
2872
2967
  var END_MARKER = "# <<< hermes-repo memory";
2873
2968
  function mergeHermesGitignore(repoRoot) {
2874
2969
  const block = readTemplate("gitignore-block.txt").trimEnd() + "\n";
2875
- const gitignorePath = join26(repoRoot, ".gitignore");
2970
+ const gitignorePath = join27(repoRoot, ".gitignore");
2876
2971
  const contentBefore = existsSync19(gitignorePath) ? readFileSync20(gitignorePath, "utf8") : "";
2877
2972
  const warnBroadMemoryIgnore = contentBefore.length > 0 && !contentBefore.includes(START_MARKER) && /(^|\n)\.memory\/\s*$/m.test(contentBefore);
2878
2973
  if (!existsSync19(gitignorePath)) {
@@ -2919,7 +3014,13 @@ var DEFAULT_LLM = {
2919
3014
  mode: "async"
2920
3015
  };
2921
3016
  var DEFAULT_CONSOLIDATE = {
2922
- autoArchiveDays: 30
3017
+ autoArchiveDays: 30,
3018
+ autoFlush: {
3019
+ enabled: false,
3020
+ minPendingSessions: 3,
3021
+ minIntervalMinutes: 30,
3022
+ maxPendingChars: 2e4
3023
+ }
2923
3024
  };
2924
3025
  function mergeConfigForInit(repoRoot, assistants) {
2925
3026
  const configPath = memoryPath(repoRoot, "config.json");
@@ -2935,6 +3036,7 @@ function mergeConfigForInit(repoRoot, assistants) {
2935
3036
  const prevStorage = existing.storage && typeof existing.storage === "object" && !Array.isArray(existing.storage) ? existing.storage : {};
2936
3037
  const prevLlm = existing.llm && typeof existing.llm === "object" && !Array.isArray(existing.llm) ? existing.llm : {};
2937
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 : {};
2938
3040
  const merged = {
2939
3041
  ...existing,
2940
3042
  version: 2,
@@ -2945,7 +3047,14 @@ function mergeConfigForInit(repoRoot, assistants) {
2945
3047
  assistants,
2946
3048
  debug: existing.debug === true,
2947
3049
  llm: { ...DEFAULT_LLM, ...prevLlm },
2948
- consolidate: { ...DEFAULT_CONSOLIDATE, ...prevConsolidate }
3050
+ consolidate: {
3051
+ ...DEFAULT_CONSOLIDATE,
3052
+ ...prevConsolidate,
3053
+ autoFlush: {
3054
+ ...DEFAULT_CONSOLIDATE.autoFlush,
3055
+ ...prevAutoFlush
3056
+ }
3057
+ }
2949
3058
  };
2950
3059
  return {
2951
3060
  content: `${JSON.stringify(merged, null, 2)}
@@ -2956,7 +3065,7 @@ function mergeConfigForInit(repoRoot, assistants) {
2956
3065
 
2957
3066
  // src/init/mergeAgentsMd.ts
2958
3067
  import { existsSync as existsSync21, readFileSync as readFileSync22, writeFileSync as writeFileSync12 } from "fs";
2959
- import { join as join27 } from "path";
3068
+ import { join as join28 } from "path";
2960
3069
  var HERMES_AGENTS_START_MARKER = "<!-- >>> hermes-repo agents (do not edit this block manually) -->";
2961
3070
  var HERMES_AGENTS_END_MARKER = "<!-- <<< hermes-repo agents -->";
2962
3071
  function buildHermesAgentsBlockBody() {
@@ -3012,7 +3121,7 @@ function spliceHermesBlock2(existing, block) {
3012
3121
  `;
3013
3122
  }
3014
3123
  function mergeAgentsMd(repoRoot, force) {
3015
- const agentsPath = join27(repoRoot, "AGENTS.md");
3124
+ const agentsPath = join28(repoRoot, "AGENTS.md");
3016
3125
  const block = buildHermesAgentsMarkedBlock();
3017
3126
  if (!existsSync21(agentsPath)) {
3018
3127
  writeFileSync12(agentsPath, buildNewAgentsMd(), "utf8");