open-walnut 0.3.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.
Files changed (155) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +339 -0
  3. package/bin/open-walnut.js +2 -0
  4. package/bin/walnut.js +2 -0
  5. package/dist/cli.js +137849 -0
  6. package/dist/daemon-binaries/acp-worker.js +44 -0
  7. package/dist/daemon-binaries/acp-worker.js.version +1 -0
  8. package/dist/data/skills/daily-standup/SKILL.md +16 -0
  9. package/dist/data/skills/install-plugin/SKILL.md +118 -0
  10. package/dist/data/skills/learn/SKILL.md +46 -0
  11. package/dist/data/skills/register-repo/SKILL.md +20 -0
  12. package/dist/data/skills/setup-cloud-companion/SKILL.md +326 -0
  13. package/dist/data/skills/triage/SKILL.md +23 -0
  14. package/dist/data/skills/walnut-calendar/SKILL.md +92 -0
  15. package/dist/data/skills/walnut-manage-repos/SKILL.md +107 -0
  16. package/dist/data/skills/walnut-peer-sessions/SKILL.md +61 -0
  17. package/dist/data/skills/walnut-register-repo/SKILL.md +72 -0
  18. package/dist/data/skills/walnut-register-repo/references/yaml-format.md +111 -0
  19. package/dist/data/skills/walnut-register-repo/scripts/validate-repo.py +177 -0
  20. package/dist/data/skills/walnut-routines/SKILL.md +65 -0
  21. package/dist/data/skills/weekly-review/SKILL.md +22 -0
  22. package/dist/data/walnut-calendar.swift +213 -0
  23. package/dist/data/walnut-extract.swift +80 -0
  24. package/dist/hooks/on-compact.js +976 -0
  25. package/dist/hooks/on-stop.js +1329 -0
  26. package/dist/integrations/jira/index.js +4200 -0
  27. package/dist/integrations/jira/manifest.json +26 -0
  28. package/dist/integrations/local/index.js +51 -0
  29. package/dist/integrations/local/manifest.json +10 -0
  30. package/dist/integrations/ms-todo/index.js +4672 -0
  31. package/dist/integrations/ms-todo/manifest.json +17 -0
  32. package/dist/session-server/index.js +1382 -0
  33. package/dist/web/server.js +135275 -0
  34. package/dist/web/static/assets/apl-B4CMkyY2.js +1 -0
  35. package/dist/web/static/assets/asciiarmor-Df11BRmG.js +1 -0
  36. package/dist/web/static/assets/asn1-EdZsLKOL.js +1 -0
  37. package/dist/web/static/assets/asterisk-B-8jnY81.js +1 -0
  38. package/dist/web/static/assets/brainfuck-C4LP7Hcl.js +1 -0
  39. package/dist/web/static/assets/clike-B9uivgTg.js +1 -0
  40. package/dist/web/static/assets/clojure-BMjYHr_A.js +1 -0
  41. package/dist/web/static/assets/cmake-BQqOBYOt.js +1 -0
  42. package/dist/web/static/assets/cobol-CWcv1MsR.js +1 -0
  43. package/dist/web/static/assets/coffeescript-S37ZYGWr.js +1 -0
  44. package/dist/web/static/assets/commonlisp-DBKNyK5s.js +1 -0
  45. package/dist/web/static/assets/crystal-SjHAIU92.js +1 -0
  46. package/dist/web/static/assets/css-BnMrqG3P.js +1 -0
  47. package/dist/web/static/assets/cypher-C_CwsFkJ.js +1 -0
  48. package/dist/web/static/assets/d-pRatUO7H.js +1 -0
  49. package/dist/web/static/assets/diff-DbItnlRl.js +1 -0
  50. package/dist/web/static/assets/dockerfile-BKs6k2Af.js +1 -0
  51. package/dist/web/static/assets/dtd-DF_7sFjM.js +1 -0
  52. package/dist/web/static/assets/dylan-DwRh75JA.js +1 -0
  53. package/dist/web/static/assets/ebnf-CDyGwa7X.js +1 -0
  54. package/dist/web/static/assets/ecl-Cabwm37j.js +1 -0
  55. package/dist/web/static/assets/eiffel-CnydiIhH.js +1 -0
  56. package/dist/web/static/assets/elm-vLlmbW-K.js +1 -0
  57. package/dist/web/static/assets/erlang-BNw1qcRV.js +1 -0
  58. package/dist/web/static/assets/factor-kuTfRLto.js +1 -0
  59. package/dist/web/static/assets/fcl-Kvtd6kyn.js +1 -0
  60. package/dist/web/static/assets/forth-Ffai-XNe.js +1 -0
  61. package/dist/web/static/assets/fortran-DYz_wnZ1.js +1 -0
  62. package/dist/web/static/assets/gas-Bneqetm1.js +1 -0
  63. package/dist/web/static/assets/gherkin-heZmZLOM.js +1 -0
  64. package/dist/web/static/assets/groovy-D9Dt4D0W.js +1 -0
  65. package/dist/web/static/assets/haskell-Cw1EW3IL.js +1 -0
  66. package/dist/web/static/assets/haxe-H-WmDvRZ.js +1 -0
  67. package/dist/web/static/assets/http-DBlCnlav.js +1 -0
  68. package/dist/web/static/assets/idl-BEugSyMb.js +1 -0
  69. package/dist/web/static/assets/index-B3K5yAdA.js +1 -0
  70. package/dist/web/static/assets/index-B7OvpXOs.js +632 -0
  71. package/dist/web/static/assets/index-C07UNJIJ.js +1 -0
  72. package/dist/web/static/assets/index-C8k-3wfu.js +2 -0
  73. package/dist/web/static/assets/index-CBXGsT3t.js +1 -0
  74. package/dist/web/static/assets/index-CUrO3qpX.js +1 -0
  75. package/dist/web/static/assets/index-CpTvBBAa.js +1 -0
  76. package/dist/web/static/assets/index-D4J6slTf.js +1 -0
  77. package/dist/web/static/assets/index-DUgC5K_q.js +3 -0
  78. package/dist/web/static/assets/index-DhqXE1wa.js +1 -0
  79. package/dist/web/static/assets/index-DkGqHTCk.js +1 -0
  80. package/dist/web/static/assets/index-DsxFiBic.js +1 -0
  81. package/dist/web/static/assets/index-Dz3umkfm.js +1 -0
  82. package/dist/web/static/assets/index-G2v0oaqp.js +1 -0
  83. package/dist/web/static/assets/index-GNDKJKwy.js +1 -0
  84. package/dist/web/static/assets/index-U1CVtNUH.css +32 -0
  85. package/dist/web/static/assets/index-ft4kpQXq.js +1 -0
  86. package/dist/web/static/assets/index-ls7h0u-p.js +1 -0
  87. package/dist/web/static/assets/index-nL5l5WPW.js +7 -0
  88. package/dist/web/static/assets/javascript-iXu5QeM3.js +1 -0
  89. package/dist/web/static/assets/julia-DuME0IfC.js +1 -0
  90. package/dist/web/static/assets/livescript-BwQOo05w.js +1 -0
  91. package/dist/web/static/assets/lua-VAEuO923.js +1 -0
  92. package/dist/web/static/assets/mathematica-DTrFuWx2.js +1 -0
  93. package/dist/web/static/assets/mbox-CNhZ1qSd.js +1 -0
  94. package/dist/web/static/assets/mirc-CjQqDB4T.js +1 -0
  95. package/dist/web/static/assets/mllike-CXdrOF99.js +1 -0
  96. package/dist/web/static/assets/modelica-Dc1JOy9r.js +1 -0
  97. package/dist/web/static/assets/mscgen-BA5vi2Kp.js +1 -0
  98. package/dist/web/static/assets/mumps-BT43cFF4.js +1 -0
  99. package/dist/web/static/assets/nginx-DdIZxoE0.js +1 -0
  100. package/dist/web/static/assets/nsis-LdVXkNf5.js +1 -0
  101. package/dist/web/static/assets/ntriples-BfvgReVJ.js +1 -0
  102. package/dist/web/static/assets/octave-Ck1zUtKM.js +1 -0
  103. package/dist/web/static/assets/oz-BzwKVEFT.js +1 -0
  104. package/dist/web/static/assets/pascal--L3eBynH.js +1 -0
  105. package/dist/web/static/assets/perl-CdXCOZ3F.js +1 -0
  106. package/dist/web/static/assets/pig-CevX1Tat.js +1 -0
  107. package/dist/web/static/assets/powershell-CFHJl5sT.js +1 -0
  108. package/dist/web/static/assets/properties-C78fOPTZ.js +1 -0
  109. package/dist/web/static/assets/protobuf-ChK-085T.js +1 -0
  110. package/dist/web/static/assets/pug-DeIclll2.js +1 -0
  111. package/dist/web/static/assets/puppet-DMA9R1ak.js +1 -0
  112. package/dist/web/static/assets/python-BuPzkPfP.js +1 -0
  113. package/dist/web/static/assets/q-pXgVlZs6.js +1 -0
  114. package/dist/web/static/assets/r-B6wPVr8A.js +1 -0
  115. package/dist/web/static/assets/rpm-CTu-6PCP.js +1 -0
  116. package/dist/web/static/assets/ruby-B2Rjki9n.js +1 -0
  117. package/dist/web/static/assets/sas-B4kiWyti.js +1 -0
  118. package/dist/web/static/assets/scheme-C41bIUwD.js +1 -0
  119. package/dist/web/static/assets/shell-CjFT_Tl9.js +1 -0
  120. package/dist/web/static/assets/sieve-C3Gn_uJK.js +1 -0
  121. package/dist/web/static/assets/simple-mode-GW_nhZxv.js +1 -0
  122. package/dist/web/static/assets/smalltalk-CnHTOXQT.js +1 -0
  123. package/dist/web/static/assets/solr-DehyRSwq.js +1 -0
  124. package/dist/web/static/assets/sparql-DkYu6x3z.js +1 -0
  125. package/dist/web/static/assets/spreadsheet-BCZA_wO0.js +1 -0
  126. package/dist/web/static/assets/sql-D0XecflT.js +1 -0
  127. package/dist/web/static/assets/stex-C3f8Ysf7.js +1 -0
  128. package/dist/web/static/assets/stylus-B533Al4x.js +1 -0
  129. package/dist/web/static/assets/swift-BzpIVaGY.js +1 -0
  130. package/dist/web/static/assets/tcl-DVfN8rqt.js +1 -0
  131. package/dist/web/static/assets/textile-CnDTJFAw.js +1 -0
  132. package/dist/web/static/assets/tiddlywiki-DO-Gjzrf.js +1 -0
  133. package/dist/web/static/assets/tiki-DGYXhP31.js +1 -0
  134. package/dist/web/static/assets/toml-Bm5Em-hy.js +1 -0
  135. package/dist/web/static/assets/troff-wAsdV37c.js +1 -0
  136. package/dist/web/static/assets/ttcn-CfJYG6tj.js +1 -0
  137. package/dist/web/static/assets/ttcn-cfg-B9xdYoR4.js +1 -0
  138. package/dist/web/static/assets/turtle-B1tBg_DP.js +1 -0
  139. package/dist/web/static/assets/vb-CmGdzxic.js +1 -0
  140. package/dist/web/static/assets/vbscript-BuJXcnF6.js +1 -0
  141. package/dist/web/static/assets/velocity-D8B20fx6.js +1 -0
  142. package/dist/web/static/assets/verilog-C6RDOZhf.js +1 -0
  143. package/dist/web/static/assets/vhdl-lSbBsy5d.js +1 -0
  144. package/dist/web/static/assets/webidl-ZXfAyPTL.js +1 -0
  145. package/dist/web/static/assets/xquery-DzFWVndE.js +1 -0
  146. package/dist/web/static/assets/yacas-BJ4BC0dw.js +1 -0
  147. package/dist/web/static/assets/z80-Hz9HOZM7.js +1 -0
  148. package/dist/web/static/index.html +22 -0
  149. package/dist/web/static/walnut-icon.png +0 -0
  150. package/dist/workers/git-compaction-worker.js +898 -0
  151. package/dist/workers/qmd-index-worker.js +97162 -0
  152. package/package.json +143 -0
  153. package/patches/@anthropic-ai+claude-agent-sdk+0.2.50.patch +13 -0
  154. package/scripts/patch-qmd.mjs +171 -0
  155. package/scripts/postinstall.mjs +120 -0
@@ -0,0 +1,898 @@
1
+ // src/logging/logger.ts
2
+ import fs2 from "fs";
3
+ import fsp from "fs/promises";
4
+ import path2 from "path";
5
+
6
+ // src/constants.ts
7
+ import path from "path";
8
+ import fs from "fs";
9
+ import os from "os";
10
+ import { fileURLToPath } from "url";
11
+ var WALNUT_HOME = resolveOpenWalnutHome();
12
+ var IS_EPHEMERAL = process.argv.includes("--_ephemeral-child");
13
+ var CLOUD_MODE = process.env.WALNUT_CLOUD_MODE === "1";
14
+ function resolveOpenWalnutHome() {
15
+ const envHome = process.env.OPEN_WALNUT_HOME;
16
+ const productionHome = path.join(os.homedir(), ".open-walnut");
17
+ const isTestEnv = !!(process.env.VITEST || process.env.VITEST_WORKER_ID || process.env.NODE_ENV === "test");
18
+ if (isTestEnv) {
19
+ if (envHome && envHome !== productionHome && !envHome.startsWith(productionHome + path.sep)) {
20
+ assertNotProductionPath(envHome);
21
+ return envHome;
22
+ }
23
+ const testHome = path.join(os.tmpdir(), `open-walnut-test-${process.pid}`);
24
+ process.env.OPEN_WALNUT_HOME = testHome;
25
+ return testHome;
26
+ }
27
+ if (!envHome) return productionHome;
28
+ if (process.argv.includes("--_ephemeral-child")) return envHome;
29
+ if (isEphemeralTmpDir(envHome)) {
30
+ process.stderr.write(
31
+ `WARNING: OPEN_WALNUT_HOME=${envHome} looks like a leaked ephemeral temp dir.
32
+ Overriding to ${productionHome}. (Only --_ephemeral-child processes may use one.)
33
+ `
34
+ );
35
+ process.env.OPEN_WALNUT_HOME = productionHome;
36
+ return productionHome;
37
+ }
38
+ return envHome;
39
+ }
40
+ function assertNotProductionPath(inputPath) {
41
+ const isTestEnv = !!(process.env.VITEST || process.env.VITEST_WORKER_ID || process.env.NODE_ENV === "test");
42
+ if (!isTestEnv) return;
43
+ const resolved = path.resolve(inputPath);
44
+ const prodHome = path.join(os.homedir(), ".open-walnut");
45
+ if (resolved === prodHome || resolved.startsWith(prodHome + path.sep)) {
46
+ throw new Error(
47
+ `SAFETY: Test process attempted to use production path: ${resolved}
48
+ This would destroy real user data in ~/.open-walnut/.
49
+ Set OPEN_WALNUT_HOME to a temp directory or let constants.ts auto-assign one.`
50
+ );
51
+ }
52
+ }
53
+ function isEphemeralTmpDir(inputPath) {
54
+ if (!/[\\/]open-walnut-[^\\/]+$/.test(inputPath)) return false;
55
+ const tmpDirs = /* @__PURE__ */ new Set();
56
+ tmpDirs.add(os.tmpdir());
57
+ try {
58
+ tmpDirs.add(fs.realpathSync(os.tmpdir()));
59
+ } catch {
60
+ }
61
+ try {
62
+ tmpDirs.add(fs.realpathSync("/tmp"));
63
+ } catch {
64
+ }
65
+ const parent = path.dirname(path.resolve(inputPath));
66
+ if (tmpDirs.has(parent)) return true;
67
+ try {
68
+ return tmpDirs.has(fs.realpathSync(parent));
69
+ } catch {
70
+ return false;
71
+ }
72
+ }
73
+ var TASKS_DIR = path.join(WALNUT_HOME, "tasks");
74
+ var TASKS_FILE = path.join(TASKS_DIR, "tasks.json");
75
+ var ARCHIVE_DIR = path.join(TASKS_DIR, "archive");
76
+ var MEMORY_DIR = path.join(WALNUT_HOME, "memory");
77
+ var SESSIONS_DIR = path.join(MEMORY_DIR, "sessions");
78
+ var PROJECTS_DIR = path.join(MEMORY_DIR, "projects");
79
+ var CONFIG_FILE = path.join(WALNUT_HOME, "config.yaml");
80
+ var SYNC_DIR = path.join(WALNUT_HOME, "sync");
81
+ var PLUGIN_STORES_DIR = path.join(WALNUT_HOME, "plugin-stores");
82
+ var SESSIONS_FILE = path.join(WALNUT_HOME, "sessions.json");
83
+ var CLAUDE_HOME = path.join(os.homedir(), ".claude");
84
+ var HISTORY_CACHE_DIR = path.join(WALNUT_HOME, "cache", "history");
85
+ var PROJECTION_CACHE_DIR = path.join(WALNUT_HOME, "cache", "projections");
86
+ var TRANSCRIPT_CACHE_DIR = path.join(WALNUT_HOME, "cache", "transcripts");
87
+ var TASK_QUEUE_DIR = path.join(WALNUT_HOME, "cache", "task-queue");
88
+ var HOST_MODEL_CATALOG_FILE = path.join(WALNUT_HOME, "cache", "host-model-catalogs.json");
89
+ var HOOK_LOG_FILE = path.join(WALNUT_HOME, "hook-errors.log");
90
+ var DAILY_DIR = path.join(MEMORY_DIR, "daily");
91
+ var MEMORY_FILE = path.join(MEMORY_DIR, "MEMORY.md");
92
+ var USER_FILE = path.join(MEMORY_DIR, "USER.md");
93
+ var LEGACY_MEMORY_FILE = path.join(WALNUT_HOME, "MEMORY.md");
94
+ var PROJECTS_MEMORY_DIR = path.join(MEMORY_DIR, "projects");
95
+ var CHAT_HISTORY_FILE = path.join(WALNUT_HOME, "chat-history.json");
96
+ var CONVERSATIONS_DIR = path.join(WALNUT_HOME, "conversations");
97
+ var GLOBAL_SKILLS_DIR = path.join(WALNUT_HOME, "skills");
98
+ var SKILL_SETTINGS_FILE = path.join(WALNUT_HOME, "skill-settings.json");
99
+ var CLAUDE_SKILLS_DIR = path.join(CLAUDE_HOME, "skills");
100
+ var CLAUDE_SETTINGS_FILE = path.join(CLAUDE_HOME, "settings.json");
101
+ var CLAUDE_PLUGINS_DIR = path.join(CLAUDE_HOME, "plugins");
102
+ var CLAUDE_CREDENTIALS_FILE = path.join(CLAUDE_HOME, ".credentials.json");
103
+ var CRON_FILE = path.join(WALNUT_HOME, "cron-jobs.json");
104
+ var USAGE_DB_FILE = path.join(WALNUT_HOME, "usage.sqlite");
105
+ var LOG_DIR = process.env.WALNUT_DAEMON_DIR || "/tmp/open-walnut";
106
+ var SESSION_STREAMS_DIR = path.join(LOG_DIR, "streams");
107
+ var DTACH_SOCKET_DIR = path.join(LOG_DIR, "term");
108
+ var SESSION_QUEUE_FILE = path.join(WALNUT_HOME, "session-message-queue.json");
109
+ var IMAGES_DIR = path.join(LOG_DIR, "images");
110
+ var REMOTE_IMAGES_DIR = path.join(IMAGES_DIR, "remote");
111
+ var PASTES_DIR = path.join(LOG_DIR, "pastes");
112
+ var HEARTBEAT_FILE = path.join(WALNUT_HOME, "HEARTBEAT.md");
113
+ var COMMANDS_DIR = path.join(WALNUT_HOME, "commands");
114
+ var BUILTIN_COMMANDS_DIR = (() => {
115
+ let dir = path.dirname(fileURLToPath(import.meta.url));
116
+ for (let i = 0; i < 5; i++) {
117
+ const candidate = path.join(dir, "data", "slash-commands");
118
+ try {
119
+ if (fs.statSync(candidate).isDirectory()) return candidate;
120
+ } catch {
121
+ }
122
+ dir = path.dirname(dir);
123
+ }
124
+ return path.join(path.dirname(fileURLToPath(import.meta.url)), "..", "data", "slash-commands");
125
+ })();
126
+ var BUILTIN_SKILLS_DIR = (() => {
127
+ let dir = path.dirname(fileURLToPath(import.meta.url));
128
+ for (let i = 0; i < 5; i++) {
129
+ const candidate = path.join(dir, "data", "skills");
130
+ try {
131
+ if (fs.statSync(candidate).isDirectory()) return candidate;
132
+ } catch {
133
+ }
134
+ dir = path.dirname(dir);
135
+ }
136
+ return path.join(path.dirname(fileURLToPath(import.meta.url)), "..", "data", "skills");
137
+ })();
138
+ var FREQUENT_DIRS_FILE = path.join(WALNUT_HOME, "frequent-directories.json");
139
+ var MENTION_DIRS_FILE = path.join(WALNUT_HOME, "mention-directories.json");
140
+ var NOTES_DIR = path.join(WALNUT_HOME, "notes");
141
+ var GLOBAL_NOTES_FILE = path.join(NOTES_DIR, "global-notes.md");
142
+ var NOTES_AGENTS_FILE = path.join(NOTES_DIR, "AGENTS.md");
143
+ var NOTES_CLAUDE_FILE = path.join(NOTES_DIR, "CLAUDE.md");
144
+ var REPOSITORIES_DIR = path.join(WALNUT_HOME, "repositories");
145
+ var REPOS_MEMORY_DIR = path.join(GLOBAL_SKILLS_DIR, "repos");
146
+ var TOPICS_DIR = path.join(MEMORY_DIR, "topics");
147
+ var COMPACTION_DIR = path.join(MEMORY_DIR, "compaction");
148
+ var MEMORY_INDEX_FILE = path.join(MEMORY_DIR, "index.md");
149
+ var WORKING_MEMORY_FILE = path.join(MEMORY_DIR, "working-memory.md");
150
+ var TIMELINE_DIR = path.join(WALNUT_HOME, "timeline");
151
+ var CONFIG_SHARE_DIR = path.join(WALNUT_HOME, "config", "share");
152
+ var UI_PREFS_FILE = path.join(CONFIG_SHARE_DIR, "ui-prefs.json");
153
+ var STT_VOCAB_FILE = path.join(CONFIG_SHARE_DIR, "stt-vocab.txt");
154
+ var TMP_DIR = path.join(WALNUT_HOME, "tmp");
155
+ var RECORDINGS_DIR = path.join(TMP_DIR, "recordings");
156
+ var LOG_PREFIX = "open-walnut-";
157
+ var DAEMON_BINARIES_DIR = (() => {
158
+ let dir = path.dirname(fileURLToPath(import.meta.url));
159
+ for (let i = 0; i < 5; i++) {
160
+ const candidate = path.join(dir, "dist", "daemon-binaries");
161
+ try {
162
+ if (fs.statSync(candidate).isDirectory()) return candidate;
163
+ } catch {
164
+ }
165
+ dir = path.dirname(dir);
166
+ }
167
+ return path.join(path.dirname(fileURLToPath(import.meta.url)), "..", "dist", "daemon-binaries");
168
+ })();
169
+ var WALNUT_INSTALL_DIR = (() => {
170
+ let dir = path.dirname(fileURLToPath(import.meta.url));
171
+ for (let i = 0; i < 6; i++) {
172
+ try {
173
+ const pkg = JSON.parse(fs.readFileSync(path.join(dir, "package.json"), "utf8"));
174
+ if (pkg.name === "open-walnut" && fs.statSync(path.join(dir, ".git")).isDirectory()) return dir;
175
+ } catch {
176
+ }
177
+ const parent = path.dirname(dir);
178
+ if (parent === dir) break;
179
+ dir = parent;
180
+ }
181
+ return null;
182
+ })();
183
+
184
+ // src/logging/redact.ts
185
+ var REDACTED = "[REDACTED]";
186
+ var patterns = [
187
+ // PEM private key blocks (multiline)
188
+ {
189
+ re: /-----BEGIN\s[\w\s]*PRIVATE KEY-----[\s\S]*?-----END\s[\w\s]*PRIVATE KEY-----/g,
190
+ replacement: REDACTED
191
+ },
192
+ // Bearer tokens: Authorization: Bearer <token>
193
+ {
194
+ re: /(Bearer\s+)\S+/gi,
195
+ replacement: `$1${REDACTED}`
196
+ },
197
+ // OpenAI / Anthropic style keys: sk-... (at least 20 chars after prefix)
198
+ {
199
+ re: /\bsk-[A-Za-z0-9_-]{20,}\b/g,
200
+ replacement: REDACTED
201
+ },
202
+ // AWS access key IDs: AKIA...
203
+ {
204
+ re: /\bAKIA[A-Z0-9]{12,}\b/g,
205
+ replacement: REDACTED
206
+ },
207
+ // AWS secret access key in key=value form
208
+ {
209
+ re: /(aws_secret_access_key\s*[=:]\s*)\S+/gi,
210
+ replacement: `$1${REDACTED}`
211
+ },
212
+ // AWS session token in key=value form
213
+ {
214
+ re: /(aws_session_token\s*[=:]\s*)\S+/gi,
215
+ replacement: `$1${REDACTED}`
216
+ },
217
+ // Credentials in URL userinfo: https://user:secret@host/...
218
+ // Motivating shape: execSync embeds the whole command in error.message, so a
219
+ // failed `git remote add origin https://walnut:<device-token>@host/git/data`
220
+ // otherwise logs the cloud companion's long-lived credential verbatim.
221
+ {
222
+ re: /(https?:\/\/[^/\s:@]+:)[^@/\s]+@/gi,
223
+ replacement: `$1${REDACTED}@`
224
+ },
225
+ // Generic secrets: password=, secret=, token=, api_key=, apikey=
226
+ {
227
+ re: /((?:password|secret|token|api_key|apikey)\s*[=:]\s*)\S+/gi,
228
+ replacement: `$1${REDACTED}`
229
+ }
230
+ ];
231
+ function redactSensitiveText(text) {
232
+ let result = text;
233
+ for (const { re, replacement } of patterns) {
234
+ result = result.replace(re, replacement);
235
+ }
236
+ return result;
237
+ }
238
+
239
+ // src/logging/logger.ts
240
+ var dirEnsured = false;
241
+ function ensureLogDir() {
242
+ if (dirEnsured) return;
243
+ try {
244
+ fs2.mkdirSync(LOG_DIR, { recursive: true });
245
+ dirEnsured = true;
246
+ } catch {
247
+ }
248
+ }
249
+ function todayFileName() {
250
+ const d = /* @__PURE__ */ new Date();
251
+ const yyyy = d.getFullYear();
252
+ const mm = String(d.getMonth() + 1).padStart(2, "0");
253
+ const dd = String(d.getDate()).padStart(2, "0");
254
+ return `${LOG_PREFIX}${yyyy}-${mm}-${dd}.log`;
255
+ }
256
+ function logFilePath() {
257
+ return path2.join(LOG_DIR, todayFileName());
258
+ }
259
+ function pruneOldLogs(maxAgeDays) {
260
+ let entries;
261
+ try {
262
+ entries = fs2.readdirSync(LOG_DIR);
263
+ } catch {
264
+ return;
265
+ }
266
+ const cutoff = Date.now() - maxAgeDays * 24 * 60 * 60 * 1e3;
267
+ for (const name of entries) {
268
+ if (!name.startsWith(LOG_PREFIX) || !name.endsWith(".log")) continue;
269
+ const full = path2.join(LOG_DIR, name);
270
+ try {
271
+ const stat = fs2.statSync(full);
272
+ if (stat.mtimeMs < cutoff) {
273
+ fs2.unlinkSync(full);
274
+ }
275
+ } catch {
276
+ }
277
+ }
278
+ }
279
+ var FLUSH_INTERVAL_MS = 2e3;
280
+ var buffer = [];
281
+ var flushTimer = null;
282
+ var flushing = false;
283
+ function startFlushTimer() {
284
+ if (flushTimer) return;
285
+ flushTimer = setInterval(() => {
286
+ void flushLogBuffer();
287
+ }, FLUSH_INTERVAL_MS);
288
+ if (flushTimer && typeof flushTimer === "object" && "unref" in flushTimer) {
289
+ flushTimer.unref();
290
+ }
291
+ }
292
+ async function flushLogBuffer() {
293
+ if (flushing || buffer.length === 0) return;
294
+ flushing = true;
295
+ const batch = buffer;
296
+ buffer = [];
297
+ try {
298
+ ensureLogDir();
299
+ await fsp.appendFile(logFilePath(), batch.join(""), "utf-8");
300
+ } catch {
301
+ } finally {
302
+ flushing = false;
303
+ }
304
+ }
305
+ function flushLogBufferSync() {
306
+ if (buffer.length === 0) return;
307
+ const batch = buffer;
308
+ buffer = [];
309
+ try {
310
+ ensureLogDir();
311
+ fs2.appendFileSync(logFilePath(), batch.join(""), "utf-8");
312
+ } catch {
313
+ }
314
+ }
315
+ process.on("beforeExit", flushLogBufferSync);
316
+ process.on("exit", flushLogBufferSync);
317
+ function initFileLogger() {
318
+ fs2.mkdirSync(LOG_DIR, { recursive: true });
319
+ dirEnsured = true;
320
+ pruneOldLogs(3);
321
+ }
322
+ function writeLogEntry(entry) {
323
+ const line = redactSensitiveText(JSON.stringify(entry)) + "\n";
324
+ buffer.push(line);
325
+ startFlushTimer();
326
+ }
327
+
328
+ // src/logging/subsystem.ts
329
+ import chalk from "chalk";
330
+
331
+ // src/logging/levels.ts
332
+ var LOG_LEVEL_ORDER = {
333
+ trace: 0,
334
+ debug: 1,
335
+ info: 2,
336
+ warn: 3,
337
+ error: 4,
338
+ fatal: 5
339
+ };
340
+ var DEFAULT_LEVEL = "info";
341
+ function resolveConfiguredLevel() {
342
+ const raw = process.env.WALNUT_LOG_LEVEL?.toLowerCase().trim();
343
+ if (raw && raw in LOG_LEVEL_ORDER) return raw;
344
+ return DEFAULT_LEVEL;
345
+ }
346
+ var CONFIGURED_LOG_LEVEL = resolveConfiguredLevel();
347
+ function shouldLog(messageLevel, configuredLevel = CONFIGURED_LOG_LEVEL) {
348
+ return LOG_LEVEL_ORDER[messageLevel] >= LOG_LEVEL_ORDER[configuredLevel];
349
+ }
350
+
351
+ // src/logging/subsystem.ts
352
+ var levelColor = {
353
+ trace: chalk.gray,
354
+ debug: chalk.cyan,
355
+ info: chalk.green,
356
+ warn: chalk.yellow,
357
+ error: chalk.red,
358
+ fatal: chalk.bgRed.white
359
+ };
360
+ var levelLabel = {
361
+ trace: "TRC",
362
+ debug: "DBG",
363
+ info: "INF",
364
+ warn: "WRN",
365
+ error: "ERR",
366
+ fatal: "FTL"
367
+ };
368
+ function timeStamp(d) {
369
+ const hh = String(d.getHours()).padStart(2, "0");
370
+ const mm = String(d.getMinutes()).padStart(2, "0");
371
+ const ss = String(d.getSeconds()).padStart(2, "0");
372
+ return `${hh}:${mm}:${ss}`;
373
+ }
374
+ function formatMeta(meta) {
375
+ if (!meta || Object.keys(meta).length === 0) return "";
376
+ return " " + chalk.dim(JSON.stringify(meta));
377
+ }
378
+ var ERROR_SINK_KEY = /* @__PURE__ */ Symbol.for("open-walnut.errorNotificationSink");
379
+ var inErrorSink = false;
380
+ function forwardToErrorSink(subsystem, message, meta) {
381
+ const sink = globalThis[ERROR_SINK_KEY];
382
+ if (!sink || inErrorSink) return;
383
+ if (subsystem.startsWith("notif")) return;
384
+ inErrorSink = true;
385
+ try {
386
+ sink({ subsystem, message, meta });
387
+ } catch {
388
+ } finally {
389
+ inErrorSink = false;
390
+ }
391
+ }
392
+ function emit(level, subsystem, message, meta) {
393
+ if (!shouldLog(level)) return;
394
+ const now = /* @__PURE__ */ new Date();
395
+ writeLogEntry({
396
+ time: now.toISOString(),
397
+ level,
398
+ subsystem,
399
+ message,
400
+ ...meta && Object.keys(meta).length > 0 ? meta : {}
401
+ });
402
+ if (level === "error" || level === "fatal") {
403
+ forwardToErrorSink(subsystem, message, meta);
404
+ }
405
+ try {
406
+ const colorFn = levelColor[level];
407
+ const tag = chalk.bold(`[${subsystem}]`);
408
+ const lbl = colorFn(levelLabel[level]);
409
+ const ts = chalk.dim(timeStamp(now));
410
+ const metaStr = formatMeta(meta);
411
+ const line = redactSensitiveText(`${ts} ${lbl} ${tag} ${message}${metaStr}
412
+ `);
413
+ process.stderr.write(line);
414
+ } catch {
415
+ }
416
+ }
417
+ function createSubsystemLogger(subsystem) {
418
+ const logger = {
419
+ trace: (msg, meta) => emit("trace", subsystem, msg, meta),
420
+ debug: (msg, meta) => emit("debug", subsystem, msg, meta),
421
+ info: (msg, meta) => emit("info", subsystem, msg, meta),
422
+ warn: (msg, meta) => emit("warn", subsystem, msg, meta),
423
+ error: (msg, meta) => emit("error", subsystem, msg, meta),
424
+ fatal: (msg, meta) => emit("fatal", subsystem, msg, meta),
425
+ child(subName) {
426
+ return createSubsystemLogger(`${subsystem}/${subName}`);
427
+ }
428
+ };
429
+ return logger;
430
+ }
431
+
432
+ // src/logging/index.ts
433
+ var log = {
434
+ bus: createSubsystemLogger("bus"),
435
+ agent: createSubsystemLogger("agent"),
436
+ session: createSubsystemLogger("session"),
437
+ subagent: createSubsystemLogger("subagent"),
438
+ web: createSubsystemLogger("web"),
439
+ ws: createSubsystemLogger("ws"),
440
+ hook: createSubsystemLogger("hook"),
441
+ task: createSubsystemLogger("task"),
442
+ memory: createSubsystemLogger("memory"),
443
+ cron: createSubsystemLogger("cron"),
444
+ notif: createSubsystemLogger("notif"),
445
+ usage: createSubsystemLogger("usage"),
446
+ heartbeat: createSubsystemLogger("heartbeat"),
447
+ git: createSubsystemLogger("git"),
448
+ browser: createSubsystemLogger("browser"),
449
+ audio: createSubsystemLogger("audio"),
450
+ stt: createSubsystemLogger("stt"),
451
+ obs: createSubsystemLogger("obs"),
452
+ calendar: createSubsystemLogger("calendar")
453
+ };
454
+ var initialized = false;
455
+ function initLogging() {
456
+ if (initialized) return;
457
+ initialized = true;
458
+ initFileLogger();
459
+ }
460
+
461
+ // src/integrations/git-compaction.ts
462
+ import fs4 from "fs";
463
+ import path4 from "path";
464
+
465
+ // src/integrations/git-sync.ts
466
+ import { execSync, exec, spawn } from "child_process";
467
+ import { promisify } from "util";
468
+ import fs3 from "fs";
469
+ import os2 from "os";
470
+ import path3 from "path";
471
+
472
+ // src/core/event-bus.ts
473
+ import { randomBytes } from "crypto";
474
+ var EVENT_HISTORY_SIZE = 200;
475
+ var eventHistory = [];
476
+ var KEY_BUS_EVENTS = /* @__PURE__ */ new Set([
477
+ "session:start",
478
+ "session:send",
479
+ "session:started",
480
+ "session:ended",
481
+ "session:deleted",
482
+ "session:content-updated",
483
+ "session:result",
484
+ "session:error",
485
+ "session:status-changed",
486
+ "subagent:start",
487
+ "subagent:result",
488
+ "subagent:error",
489
+ "task:created",
490
+ "task:updated",
491
+ "task:completed",
492
+ "task:deleted",
493
+ "task:unblocked"
494
+ ]);
495
+ var EventBus = class {
496
+ subscribers = /* @__PURE__ */ new Map();
497
+ /**
498
+ * Register a named subscriber.
499
+ * Events are delivered only when the subscriber's name is in the event's destinations
500
+ * (or destinations includes "*"), unless `global: true` is set — then the subscriber
501
+ * receives ALL events regardless of destinations.
502
+ */
503
+ subscribe(name, handler, filter) {
504
+ if (filter && typeof filter === "object" && ("global" in filter || "interest" in filter || "filter" in filter)) {
505
+ this.subscribers.set(name, { name, handler, filter: filter.filter, global: filter.global, interest: filter.interest });
506
+ } else {
507
+ this.subscribers.set(name, { name, handler, filter });
508
+ }
509
+ }
510
+ /**
511
+ * Remove a subscriber by name.
512
+ */
513
+ unsubscribe(name) {
514
+ this.subscribers.delete(name);
515
+ }
516
+ emit(name, data, destinations, options) {
517
+ const event = {
518
+ name,
519
+ data,
520
+ destinations,
521
+ urgency: options?.urgency ?? "normal",
522
+ timestamp: Date.now(),
523
+ source: options?.source ?? "unknown",
524
+ traceId: randomBytes(4).toString("hex"),
525
+ ...options?.reemit ? { reemit: true } : {}
526
+ };
527
+ eventHistory.push(event);
528
+ if (eventHistory.length > EVENT_HISTORY_SIZE) {
529
+ eventHistory.shift();
530
+ }
531
+ log.bus.debug(`emit ${name}`, { traceId: event.traceId, destinations, source: options?.source });
532
+ if (KEY_BUS_EVENTS.has(name)) {
533
+ log.bus.info(`emit ${name}`, { traceId: event.traceId, destinations, source: options?.source });
534
+ }
535
+ for (const [, subscriber] of this.subscribers) {
536
+ if (subscriber.global && event.reemit) continue;
537
+ if (!subscriber.global && !destinations.includes("*") && !destinations.includes(subscriber.name)) {
538
+ continue;
539
+ }
540
+ if (subscriber.global && subscriber.interest && !subscriber.interest.some((p) => name.startsWith(p))) {
541
+ continue;
542
+ }
543
+ if (subscriber.filter && !subscriber.filter(event)) {
544
+ continue;
545
+ }
546
+ try {
547
+ log.bus.debug("event delivered", { name, subscriber: subscriber.name, traceId: event.traceId });
548
+ const result = subscriber.handler(event);
549
+ if (result && typeof result.then === "function") {
550
+ result.catch((err) => {
551
+ log.bus.error(`subscriber "${subscriber.name}" threw on event "${name}" (async)`, {
552
+ eventName: name,
553
+ traceId: event.traceId,
554
+ error: err instanceof Error ? err.message : String(err),
555
+ stack: err instanceof Error ? err.stack : void 0
556
+ });
557
+ });
558
+ }
559
+ } catch (err) {
560
+ log.bus.error(`subscriber "${subscriber.name}" threw on event "${name}"`, {
561
+ eventName: name,
562
+ traceId: event.traceId,
563
+ error: err instanceof Error ? err.message : String(err),
564
+ stack: err instanceof Error ? err.stack : void 0
565
+ });
566
+ }
567
+ }
568
+ }
569
+ /**
570
+ * Check if a subscriber is registered.
571
+ */
572
+ has(name) {
573
+ return this.subscribers.has(name);
574
+ }
575
+ /**
576
+ * Remove all subscribers.
577
+ */
578
+ clear() {
579
+ this.subscribers.clear();
580
+ }
581
+ };
582
+ var bus = new EventBus();
583
+
584
+ // src/core/event-loop-monitor.ts
585
+ import { monitorEventLoopDelay } from "perf_hooks";
586
+ import { execFile } from "child_process";
587
+
588
+ // src/integrations/git-sync.ts
589
+ var LOCAL_TIMEOUT = 3e4;
590
+ var compactionInProgress = false;
591
+ function setCompactionInProgress(v) {
592
+ compactionInProgress = v;
593
+ }
594
+ var NETWORK_GIT_RE = /^(?:clone|fetch|pull|push|ls-remote)\b/;
595
+ var execAsync = promisify(exec);
596
+ function credentialGuardArgs(cwd) {
597
+ try {
598
+ const urls = execSync('git config --get-regexp "remote\\..*\\.url"', {
599
+ cwd: cwd ?? WALNUT_HOME,
600
+ timeout: LOCAL_TIMEOUT,
601
+ encoding: "utf-8",
602
+ stdio: ["pipe", "pipe", "pipe"]
603
+ });
604
+ if (/https?:\/\/[^/\s]+@/.test(urls)) return ["-c", "credential.helper="];
605
+ } catch {
606
+ }
607
+ return [];
608
+ }
609
+ function git(args, options) {
610
+ const guard = NETWORK_GIT_RE.test(args) ? credentialGuardArgs(options?.cwd).join(" ") : "";
611
+ return execSync(`git ${guard ? `${guard} ` : ""}${args}`, {
612
+ cwd: options?.cwd ?? WALNUT_HOME,
613
+ timeout: options?.timeout ?? LOCAL_TIMEOUT,
614
+ encoding: "utf-8",
615
+ stdio: ["pipe", "pipe", "pipe"],
616
+ env: options?.env ? { ...process.env, ...options.env } : void 0
617
+ }).trim();
618
+ }
619
+ function gitSafe(args, options) {
620
+ try {
621
+ return git(args, options);
622
+ } catch {
623
+ return null;
624
+ }
625
+ }
626
+ var TRACKED_COUNT_TTL_MS = 10 * 6e4;
627
+ var SAFE_MODE_RELOG_MS = 10 * 6e4;
628
+ var SURGERY_RELOG_MS = 10 * 6e4;
629
+ function clearStaleLock(maxAgeMs = 6e4) {
630
+ const gitDir = path3.join(WALNUT_HOME, ".git");
631
+ let removed = false;
632
+ const removeIfStale = (lockPath) => {
633
+ try {
634
+ const stat = fs3.statSync(lockPath);
635
+ if (Date.now() - stat.mtimeMs <= maxAgeMs) return;
636
+ fs3.unlinkSync(lockPath);
637
+ removed = true;
638
+ log.git.warn("git-sync removed stale lock", { lock: path3.relative(gitDir, lockPath) });
639
+ } catch {
640
+ }
641
+ };
642
+ removeIfStale(path3.join(gitDir, "index.lock"));
643
+ try {
644
+ for (const entry of fs3.readdirSync(gitDir)) {
645
+ if (entry !== "index.lock" && entry.endsWith(".lock")) {
646
+ removeIfStale(path3.join(gitDir, entry));
647
+ }
648
+ }
649
+ } catch {
650
+ }
651
+ const walkRefs = (dir, depth) => {
652
+ if (depth > 6) return;
653
+ let entries;
654
+ try {
655
+ entries = fs3.readdirSync(dir, { withFileTypes: true });
656
+ } catch {
657
+ return;
658
+ }
659
+ for (const entry of entries) {
660
+ const full = path3.join(dir, entry.name);
661
+ if (entry.isDirectory()) walkRefs(full, depth + 1);
662
+ else if (entry.name.endsWith(".lock")) removeIfStale(full);
663
+ }
664
+ };
665
+ walkRefs(path3.join(gitDir, "refs"), 0);
666
+ return removed;
667
+ }
668
+ var REPO_SIZE_WARN_BYTES = 3 * 1024 * 1024 * 1024;
669
+ var REPO_SIZE_CHECK_INTERVAL_MS = 6 * 60 * 60 * 1e3;
670
+
671
+ // src/integrations/git-compaction.ts
672
+ function statePath(repoDir) {
673
+ return path4.join(repoDir, ".git", "compaction-state.json");
674
+ }
675
+ function writeState(repoDir, state) {
676
+ fs4.writeFileSync(statePath(repoDir), JSON.stringify(state), "utf-8");
677
+ }
678
+ function removeState(repoDir) {
679
+ try {
680
+ fs4.unlinkSync(statePath(repoDir));
681
+ } catch {
682
+ }
683
+ }
684
+ function isoWeek(dateStr) {
685
+ const d = new Date(dateStr);
686
+ const jan4 = new Date(d.getFullYear(), 0, 4);
687
+ const dayOfYear = Math.floor((d.getTime() - new Date(d.getFullYear(), 0, 1).getTime()) / 864e5) + 1;
688
+ const dayOfWeek = d.getDay() || 7;
689
+ const weekNum = Math.floor((dayOfYear - dayOfWeek + 10) / 7);
690
+ if (weekNum < 1) {
691
+ return `${d.getFullYear() - 1}-W52`;
692
+ }
693
+ return `${d.getFullYear()}-W${String(weekNum).padStart(2, "0")}`;
694
+ }
695
+ function selectCommits(commits, config) {
696
+ const now = Date.now();
697
+ const DAY = 864e5;
698
+ const recent = [];
699
+ const dailyBuckets = /* @__PURE__ */ new Map();
700
+ const weeklyBuckets = /* @__PURE__ */ new Map();
701
+ for (const c of commits) {
702
+ const ageDays = (now - new Date(c.date).getTime()) / DAY;
703
+ if (ageDays < config.recentDays) {
704
+ recent.push(c);
705
+ } else if (ageDays < config.dailyDays) {
706
+ dailyBuckets.set(c.date.slice(0, 10), c);
707
+ } else {
708
+ weeklyBuckets.set(isoWeek(c.date), c);
709
+ }
710
+ }
711
+ return [
712
+ ...weeklyBuckets.values(),
713
+ ...dailyBuckets.values(),
714
+ ...recent
715
+ ];
716
+ }
717
+ function parseGitLog(raw) {
718
+ if (!raw.trim()) return [];
719
+ return raw.split("\n").map((line) => {
720
+ const spaceIdx1 = line.indexOf(" ");
721
+ const spaceIdx2 = line.indexOf(" ", spaceIdx1 + 1);
722
+ return {
723
+ hash: line.slice(0, spaceIdx1),
724
+ date: line.slice(spaceIdx1 + 1, spaceIdx2),
725
+ subject: line.slice(spaceIdx2 + 1)
726
+ };
727
+ });
728
+ }
729
+ var LOG_PAGE_SIZE = 5e3;
730
+ function collectCommitsPaged(repoDir) {
731
+ const opts = { cwd: repoDir };
732
+ const pages = [];
733
+ for (let skip = 0; ; skip += LOG_PAGE_SIZE) {
734
+ const raw = git(
735
+ `log --format="%H %aI %s" --max-count=${LOG_PAGE_SIZE} --skip=${skip}`,
736
+ opts
737
+ );
738
+ const page = parseGitLog(raw);
739
+ if (page.length === 0) break;
740
+ pages.push(page);
741
+ if (page.length < LOG_PAGE_SIZE) break;
742
+ }
743
+ return pages.flat().reverse();
744
+ }
745
+ function compactGitHistory(repoDir = WALNUT_HOME) {
746
+ const opts = { cwd: repoDir };
747
+ const branch = gitSafe("rev-parse --abbrev-ref HEAD", opts);
748
+ if (branch !== "main") {
749
+ git("checkout main", opts);
750
+ }
751
+ const hasRemote = (gitSafe("remote", opts) ?? "").length > 0;
752
+ let remoteHeadBeforeRewrite = null;
753
+ if (hasRemote) {
754
+ if (gitSafe("fetch origin main", opts) === null) {
755
+ log.git.warn("compaction deferred: remote unreachable (rewriting without pushing would wedge sync)");
756
+ return { skipped: true, before: 0, after: 0 };
757
+ }
758
+ remoteHeadBeforeRewrite = gitSafe("rev-parse origin/main", opts);
759
+ if (remoteHeadBeforeRewrite && gitSafe(`merge-base --is-ancestor ${remoteHeadBeforeRewrite} main`, opts) === null) {
760
+ log.git.warn("compaction deferred: origin/main has commits not merged locally \u2014 sync must catch up first");
761
+ return { skipped: true, before: 0, after: 0 };
762
+ }
763
+ }
764
+ const commits = collectCommitsPaged(repoDir);
765
+ if (commits.length < 50) {
766
+ return { skipped: true, before: commits.length, after: commits.length };
767
+ }
768
+ const selected = selectCommits(commits, { recentDays: 7, dailyDays: 30 });
769
+ if (selected.length >= commits.length * 0.9) {
770
+ return { skipped: true, before: commits.length, after: selected.length };
771
+ }
772
+ const backupName = `backup-${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10).replace(/-/g, "")}`;
773
+ gitSafe(`branch -D ${backupName}`, opts);
774
+ git(`branch ${backupName}`, opts);
775
+ writeState(repoDir, { phase: "building", backup: backupName, startedAt: (/* @__PURE__ */ new Date()).toISOString() });
776
+ try {
777
+ let parent = "";
778
+ let newHead = "";
779
+ for (const commit of selected) {
780
+ const safeSubject = commit.subject.replace(/"/g, '\\"');
781
+ const message = commit === selected[0] && selected.length < commits.length ? `compacted: ${safeSubject}` : safeSubject;
782
+ newHead = git(
783
+ `commit-tree ${commit.hash}^{tree}${parent ? ` -p ${parent}` : ""} -m "${message}"`,
784
+ {
785
+ ...opts,
786
+ env: {
787
+ GIT_AUTHOR_DATE: commit.date,
788
+ GIT_COMMITTER_DATE: commit.date
789
+ }
790
+ }
791
+ );
792
+ parent = newHead;
793
+ }
794
+ git(`update-ref refs/heads/compaction-wip ${newHead}`, opts);
795
+ writeState(repoDir, { phase: "verified", backup: backupName, startedAt: (/* @__PURE__ */ new Date()).toISOString() });
796
+ const wipTree = gitSafe("rev-parse compaction-wip^{tree}", opts);
797
+ const mainTree = gitSafe("rev-parse main^{tree}", opts);
798
+ if (!wipTree || wipTree !== mainTree) {
799
+ gitSafe("update-ref -d refs/heads/compaction-wip", opts);
800
+ removeState(repoDir);
801
+ return { before: commits.length, after: commits.length, error: "verification failed: trees differ" };
802
+ }
803
+ git(`update-ref refs/heads/main ${newHead}`, opts);
804
+ gitSafe("reset --mixed HEAD", opts);
805
+ gitSafe("update-ref -d refs/heads/compaction-wip", opts);
806
+ writeState(repoDir, { phase: "swapped", backup: backupName, startedAt: (/* @__PURE__ */ new Date()).toISOString() });
807
+ if (hasRemote) {
808
+ const lease = remoteHeadBeforeRewrite ? `--force-with-lease=refs/heads/main:${remoteHeadBeforeRewrite}` : "--force-with-lease";
809
+ if (gitSafe(`push ${lease} origin main`, { ...opts, timeout: 12e4 }) === null) {
810
+ log.git.warn("compaction: force-with-lease push failed \u2014 restoring pre-compaction main so sync stays consistent with the hub");
811
+ git(`update-ref refs/heads/main ${backupName}`, opts);
812
+ gitSafe("reset --mixed HEAD", opts);
813
+ removeState(repoDir);
814
+ return { before: commits.length, after: commits.length, error: "push of compacted history failed (lease lost or network) \u2014 rolled back, will retry next run" };
815
+ }
816
+ }
817
+ writeState(repoDir, { phase: "cleaning", backup: backupName, startedAt: (/* @__PURE__ */ new Date()).toISOString() });
818
+ try {
819
+ git("reflog expire --expire=now --all", opts);
820
+ git("gc --prune=now", opts);
821
+ } catch {
822
+ }
823
+ deleteOldBackups(repoDir, 2);
824
+ removeState(repoDir);
825
+ return { before: commits.length, after: selected.length };
826
+ } catch (err) {
827
+ const currentBranch = gitSafe("rev-parse --abbrev-ref HEAD", opts);
828
+ if (currentBranch !== "main") {
829
+ gitSafe("checkout main", opts);
830
+ }
831
+ const mainExists = gitSafe("rev-parse --verify main", opts);
832
+ if (!mainExists) {
833
+ gitSafe(`branch main ${backupName}`, opts);
834
+ gitSafe("checkout main", opts);
835
+ }
836
+ gitSafe("branch -D compaction-wip", opts);
837
+ removeState(repoDir);
838
+ throw err;
839
+ }
840
+ }
841
+ function deleteOldBackups(repoDir, keepCount) {
842
+ const opts = { cwd: repoDir };
843
+ const branches = gitSafe("branch --list backup-*", opts);
844
+ if (!branches) return;
845
+ const backupBranches = branches.split("\n").map((b) => b.trim()).filter((b) => b.startsWith("backup-")).sort();
846
+ const toDelete = backupBranches.slice(0, -keepCount);
847
+ for (const b of toDelete) {
848
+ gitSafe(`branch -D ${b}`, opts);
849
+ }
850
+ }
851
+ var LAST_COMPACTION_FILE = ".last-compaction";
852
+ var COMPACTION_INTERVAL_DAYS = 7;
853
+ function isCompactionDue(repoDir = WALNUT_HOME) {
854
+ const filePath = path4.join(repoDir, LAST_COMPACTION_FILE);
855
+ try {
856
+ const content = fs4.readFileSync(filePath, "utf-8").trim();
857
+ const lastDate = new Date(content);
858
+ const daysSince = (Date.now() - lastDate.getTime()) / 864e5;
859
+ return daysSince >= COMPACTION_INTERVAL_DAYS;
860
+ } catch {
861
+ return true;
862
+ }
863
+ }
864
+ function markCompactionDone(repoDir = WALNUT_HOME) {
865
+ fs4.writeFileSync(
866
+ path4.join(repoDir, LAST_COMPACTION_FILE),
867
+ (/* @__PURE__ */ new Date()).toISOString(),
868
+ "utf-8"
869
+ );
870
+ }
871
+ function runScheduledCompaction(repoDir = WALNUT_HOME) {
872
+ if (!isCompactionDue(repoDir)) return null;
873
+ setCompactionInProgress(true);
874
+ try {
875
+ clearStaleLock();
876
+ const result = compactGitHistory(repoDir);
877
+ if (!result.skipped && !result.error) {
878
+ markCompactionDone(repoDir);
879
+ }
880
+ return result;
881
+ } finally {
882
+ setCompactionInProgress(false);
883
+ }
884
+ }
885
+
886
+ // src/workers/git-compaction-worker.ts
887
+ initLogging();
888
+ try {
889
+ const result = runScheduledCompaction();
890
+ if (process.connected) process.send?.({ ok: true, result });
891
+ process.exit(0);
892
+ } catch (err) {
893
+ const message = err instanceof Error ? err.message : String(err);
894
+ log.git.warn("compaction worker failed", { error: message });
895
+ if (process.connected) process.send?.({ ok: false, error: message });
896
+ process.exit(1);
897
+ }
898
+ //# sourceMappingURL=git-compaction-worker.js.map