elepha 0.1.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 (290) hide show
  1. package/LICENSE.md +375 -0
  2. package/README.md +135 -0
  3. package/bin/elepha.js +2 -0
  4. package/dist/adapters/base.js +600 -0
  5. package/dist/adapters/base.js.map +1 -0
  6. package/dist/adapters/claude-code.js +260 -0
  7. package/dist/adapters/claude-code.js.map +1 -0
  8. package/dist/adapters/codex.js +499 -0
  9. package/dist/adapters/codex.js.map +1 -0
  10. package/dist/adapters/discriminators.js +39 -0
  11. package/dist/adapters/discriminators.js.map +1 -0
  12. package/dist/adapters/index.js +6 -0
  13. package/dist/adapters/index.js.map +1 -0
  14. package/dist/adapters/internal-command.js +3 -0
  15. package/dist/adapters/internal-command.js.map +1 -0
  16. package/dist/cli/backup-wizard.js +97 -0
  17. package/dist/cli/backup-wizard.js.map +1 -0
  18. package/dist/cli/capture-service.js +80 -0
  19. package/dist/cli/capture-service.js.map +1 -0
  20. package/dist/cli/commands/backfills.js +336 -0
  21. package/dist/cli/commands/backfills.js.map +1 -0
  22. package/dist/cli/commands/backup.js +234 -0
  23. package/dist/cli/commands/backup.js.map +1 -0
  24. package/dist/cli/commands/config.js +66 -0
  25. package/dist/cli/commands/config.js.map +1 -0
  26. package/dist/cli/commands/consent.js +237 -0
  27. package/dist/cli/commands/consent.js.map +1 -0
  28. package/dist/cli/commands/daemon-control.js +76 -0
  29. package/dist/cli/commands/daemon-control.js.map +1 -0
  30. package/dist/cli/commands/doctor.js +31 -0
  31. package/dist/cli/commands/doctor.js.map +1 -0
  32. package/dist/cli/commands/hook.js +24 -0
  33. package/dist/cli/commands/hook.js.map +1 -0
  34. package/dist/cli/commands/import.js +611 -0
  35. package/dist/cli/commands/import.js.map +1 -0
  36. package/dist/cli/commands/init.js +19 -0
  37. package/dist/cli/commands/init.js.map +1 -0
  38. package/dist/cli/commands/inspect.js +54 -0
  39. package/dist/cli/commands/inspect.js.map +1 -0
  40. package/dist/cli/commands/install.js +49 -0
  41. package/dist/cli/commands/install.js.map +1 -0
  42. package/dist/cli/commands/internal.js +16 -0
  43. package/dist/cli/commands/internal.js.map +1 -0
  44. package/dist/cli/commands/mcp.js +12 -0
  45. package/dist/cli/commands/mcp.js.map +1 -0
  46. package/dist/cli/commands/projects.js +26 -0
  47. package/dist/cli/commands/projects.js.map +1 -0
  48. package/dist/cli/commands/purge.js +216 -0
  49. package/dist/cli/commands/purge.js.map +1 -0
  50. package/dist/cli/commands/reingest.js +80 -0
  51. package/dist/cli/commands/reingest.js.map +1 -0
  52. package/dist/cli/commands/rekey.js +58 -0
  53. package/dist/cli/commands/rekey.js.map +1 -0
  54. package/dist/cli/commands/restore.js +376 -0
  55. package/dist/cli/commands/restore.js.map +1 -0
  56. package/dist/cli/commands/rollup.js +130 -0
  57. package/dist/cli/commands/rollup.js.map +1 -0
  58. package/dist/cli/commands/sanitize.js +72 -0
  59. package/dist/cli/commands/sanitize.js.map +1 -0
  60. package/dist/cli/commands/segment.js +153 -0
  61. package/dist/cli/commands/segment.js.map +1 -0
  62. package/dist/cli/commands/self-update.js +26 -0
  63. package/dist/cli/commands/self-update.js.map +1 -0
  64. package/dist/cli/commands/start.js +53 -0
  65. package/dist/cli/commands/start.js.map +1 -0
  66. package/dist/cli/commands/stats.js +42 -0
  67. package/dist/cli/commands/stats.js.map +1 -0
  68. package/dist/cli/commands/status.js +73 -0
  69. package/dist/cli/commands/status.js.map +1 -0
  70. package/dist/cli/commands/uninstall.js +23 -0
  71. package/dist/cli/commands/uninstall.js.map +1 -0
  72. package/dist/cli/config-wizard.js +104 -0
  73. package/dist/cli/config-wizard.js.map +1 -0
  74. package/dist/cli/destructive-op.js +32 -0
  75. package/dist/cli/destructive-op.js.map +1 -0
  76. package/dist/cli/import-wizard.js +89 -0
  77. package/dist/cli/import-wizard.js.map +1 -0
  78. package/dist/cli/index.js +74 -0
  79. package/dist/cli/index.js.map +1 -0
  80. package/dist/cli/init-wizard.js +80 -0
  81. package/dist/cli/init-wizard.js.map +1 -0
  82. package/dist/cli/init.js +159 -0
  83. package/dist/cli/init.js.map +1 -0
  84. package/dist/cli/launcher-probe.js +71 -0
  85. package/dist/cli/launcher-probe.js.map +1 -0
  86. package/dist/cli/project-path.js +16 -0
  87. package/dist/cli/project-path.js.map +1 -0
  88. package/dist/cli/purge-wizard.js +200 -0
  89. package/dist/cli/purge-wizard.js.map +1 -0
  90. package/dist/cli/restore-wizard.js +101 -0
  91. package/dist/cli/restore-wizard.js.map +1 -0
  92. package/dist/cli/shared.js +128 -0
  93. package/dist/cli/shared.js.map +1 -0
  94. package/dist/cli/status.js +22 -0
  95. package/dist/cli/status.js.map +1 -0
  96. package/dist/cli/wordmark.js +15 -0
  97. package/dist/cli/wordmark.js.map +1 -0
  98. package/dist/config/constants.js +115 -0
  99. package/dist/config/constants.js.map +1 -0
  100. package/dist/config/env.js +168 -0
  101. package/dist/config/env.js.map +1 -0
  102. package/dist/config/memory-config.js +63 -0
  103. package/dist/config/memory-config.js.map +1 -0
  104. package/dist/config/paths.js +343 -0
  105. package/dist/config/paths.js.map +1 -0
  106. package/dist/config/settings.js +132 -0
  107. package/dist/config/settings.js.map +1 -0
  108. package/dist/config/version.js +12 -0
  109. package/dist/config/version.js.map +1 -0
  110. package/dist/daemon/failure-window.js +38 -0
  111. package/dist/daemon/failure-window.js.map +1 -0
  112. package/dist/daemon/heartbeat.js +40 -0
  113. package/dist/daemon/heartbeat.js.map +1 -0
  114. package/dist/daemon/index.js +873 -0
  115. package/dist/daemon/index.js.map +1 -0
  116. package/dist/daemon/log-rotation.js +27 -0
  117. package/dist/daemon/log-rotation.js.map +1 -0
  118. package/dist/daemon/readability-guard.js +89 -0
  119. package/dist/daemon/readability-guard.js.map +1 -0
  120. package/dist/daemon/rollup-service.js +239 -0
  121. package/dist/daemon/rollup-service.js.map +1 -0
  122. package/dist/daemon/update-check.js +87 -0
  123. package/dist/daemon/update-check.js.map +1 -0
  124. package/dist/daemon/work-queue.js +28 -0
  125. package/dist/daemon/work-queue.js.map +1 -0
  126. package/dist/discovery/session-projects.js +242 -0
  127. package/dist/discovery/session-projects.js.map +1 -0
  128. package/dist/hooks/codex-trust.js +80 -0
  129. package/dist/hooks/codex-trust.js.map +1 -0
  130. package/dist/hooks/common.js +81 -0
  131. package/dist/hooks/common.js.map +1 -0
  132. package/dist/hooks/hook-log.js +38 -0
  133. package/dist/hooks/hook-log.js.map +1 -0
  134. package/dist/hooks/installer.js +200 -0
  135. package/dist/hooks/installer.js.map +1 -0
  136. package/dist/hooks/session-start.js +321 -0
  137. package/dist/hooks/session-start.js.map +1 -0
  138. package/dist/hooks/user-prompt-submit.js +248 -0
  139. package/dist/hooks/user-prompt-submit.js.map +1 -0
  140. package/dist/install/binary.js +72 -0
  141. package/dist/install/binary.js.map +1 -0
  142. package/dist/install/config-file.js +119 -0
  143. package/dist/install/config-file.js.map +1 -0
  144. package/dist/install/daemon-health.js +48 -0
  145. package/dist/install/daemon-health.js.map +1 -0
  146. package/dist/install/daemon-service.js +309 -0
  147. package/dist/install/daemon-service.js.map +1 -0
  148. package/dist/install/doctor.js +180 -0
  149. package/dist/install/doctor.js.map +1 -0
  150. package/dist/install/health-checks.js +90 -0
  151. package/dist/install/health-checks.js.map +1 -0
  152. package/dist/install/installer.js +411 -0
  153. package/dist/install/installer.js.map +1 -0
  154. package/dist/install/launchd-backend.js +261 -0
  155. package/dist/install/launchd-backend.js.map +1 -0
  156. package/dist/install/launcher.js +224 -0
  157. package/dist/install/launcher.js.map +1 -0
  158. package/dist/install/markers.js +14 -0
  159. package/dist/install/markers.js.map +1 -0
  160. package/dist/install/platform.js +29 -0
  161. package/dist/install/platform.js.map +1 -0
  162. package/dist/install/present-tools.js +16 -0
  163. package/dist/install/present-tools.js.map +1 -0
  164. package/dist/install/self-update.js +120 -0
  165. package/dist/install/self-update.js.map +1 -0
  166. package/dist/install/service-backend.js +36 -0
  167. package/dist/install/service-backend.js.map +1 -0
  168. package/dist/install/service-manifest.js +36 -0
  169. package/dist/install/service-manifest.js.map +1 -0
  170. package/dist/install/status.js +58 -0
  171. package/dist/install/status.js.map +1 -0
  172. package/dist/install/systemd-backend.js +225 -0
  173. package/dist/install/systemd-backend.js.map +1 -0
  174. package/dist/markers.js +5 -0
  175. package/dist/markers.js.map +1 -0
  176. package/dist/mcp/installer.js +131 -0
  177. package/dist/mcp/installer.js.map +1 -0
  178. package/dist/mcp/server.js +81 -0
  179. package/dist/mcp/server.js.map +1 -0
  180. package/dist/mcp/tools/get-context.js +3 -0
  181. package/dist/mcp/tools/get-context.js.map +1 -0
  182. package/dist/mcp/tools/list-projects.js +3 -0
  183. package/dist/mcp/tools/list-projects.js.map +1 -0
  184. package/dist/mcp/tools/save-memory.js +3 -0
  185. package/dist/mcp/tools/save-memory.js.map +1 -0
  186. package/dist/mcp/tools.js +288 -0
  187. package/dist/mcp/tools.js.map +1 -0
  188. package/dist/rendering/raw-turn-renderer.js +85 -0
  189. package/dist/rendering/raw-turn-renderer.js.map +1 -0
  190. package/dist/security/file-permissions.js +15 -0
  191. package/dist/security/file-permissions.js.map +1 -0
  192. package/dist/security/provider-transcript.js +74 -0
  193. package/dist/security/provider-transcript.js.map +1 -0
  194. package/dist/security/sanitize.js +210 -0
  195. package/dist/security/sanitize.js.map +1 -0
  196. package/dist/security/self-ingestion.js +47 -0
  197. package/dist/security/self-ingestion.js.map +1 -0
  198. package/dist/security/sentinel.js +19 -0
  199. package/dist/security/sentinel.js.map +1 -0
  200. package/dist/security/subprocess-allowlist.js +421 -0
  201. package/dist/security/subprocess-allowlist.js.map +1 -0
  202. package/dist/serving/instructions.js +33 -0
  203. package/dist/serving/instructions.js.map +1 -0
  204. package/dist/serving/lexical-recall.js +249 -0
  205. package/dist/serving/lexical-recall.js.map +1 -0
  206. package/dist/serving/session-reader.js +302 -0
  207. package/dist/serving/session-reader.js.map +1 -0
  208. package/dist/storage/backfill-runner.js +34 -0
  209. package/dist/storage/backfill-runner.js.map +1 -0
  210. package/dist/storage/backup.js +46 -0
  211. package/dist/storage/backup.js.map +1 -0
  212. package/dist/storage/candidate-validator.js +77 -0
  213. package/dist/storage/candidate-validator.js.map +1 -0
  214. package/dist/storage/consent-store.js +198 -0
  215. package/dist/storage/consent-store.js.map +1 -0
  216. package/dist/storage/custom-title-backfill.js +68 -0
  217. package/dist/storage/custom-title-backfill.js.map +1 -0
  218. package/dist/storage/db.js +312 -0
  219. package/dist/storage/db.js.map +1 -0
  220. package/dist/storage/external-agent-import-purge.js +182 -0
  221. package/dist/storage/external-agent-import-purge.js.map +1 -0
  222. package/dist/storage/first-prompt-search-backfill.js +76 -0
  223. package/dist/storage/first-prompt-search-backfill.js.map +1 -0
  224. package/dist/storage/first-prompt-search.js +7 -0
  225. package/dist/storage/first-prompt-search.js.map +1 -0
  226. package/dist/storage/injection-store.js +37 -0
  227. package/dist/storage/injection-store.js.map +1 -0
  228. package/dist/storage/memory-store.js +428 -0
  229. package/dist/storage/memory-store.js.map +1 -0
  230. package/dist/storage/project-resolver.js +294 -0
  231. package/dist/storage/project-resolver.js.map +1 -0
  232. package/dist/storage/project-store.js +104 -0
  233. package/dist/storage/project-store.js.map +1 -0
  234. package/dist/storage/rendered-chars-backfill.js +85 -0
  235. package/dist/storage/rendered-chars-backfill.js.map +1 -0
  236. package/dist/storage/resegmentation.js +582 -0
  237. package/dist/storage/resegmentation.js.map +1 -0
  238. package/dist/storage/rollup-store.js +246 -0
  239. package/dist/storage/rollup-store.js.map +1 -0
  240. package/dist/storage/root-commit-backfill.js +32 -0
  241. package/dist/storage/root-commit-backfill.js.map +1 -0
  242. package/dist/storage/sanitize-backfill.js +184 -0
  243. package/dist/storage/sanitize-backfill.js.map +1 -0
  244. package/dist/storage/segmentation.js +87 -0
  245. package/dist/storage/segmentation.js.map +1 -0
  246. package/dist/storage/session-fields-backfill.js +196 -0
  247. package/dist/storage/session-fields-backfill.js.map +1 -0
  248. package/dist/storage/session-read-model.js +130 -0
  249. package/dist/storage/session-read-model.js.map +1 -0
  250. package/dist/storage/session-store.js +200 -0
  251. package/dist/storage/session-store.js.map +1 -0
  252. package/dist/storage/session-title-backfill.js +75 -0
  253. package/dist/storage/session-title-backfill.js.map +1 -0
  254. package/dist/storage/session-title.js +71 -0
  255. package/dist/storage/session-title.js.map +1 -0
  256. package/dist/storage/shown-session-list-store.js +33 -0
  257. package/dist/storage/shown-session-list-store.js.map +1 -0
  258. package/dist/storage/stats.js +30 -0
  259. package/dist/storage/stats.js.map +1 -0
  260. package/dist/storage/turn-store.js +202 -0
  261. package/dist/storage/turn-store.js.map +1 -0
  262. package/dist/storage/ulid.js +17 -0
  263. package/dist/storage/ulid.js.map +1 -0
  264. package/dist/summarizer/anthropic-call.js +76 -0
  265. package/dist/summarizer/anthropic-call.js.map +1 -0
  266. package/dist/summarizer/call-log.js +120 -0
  267. package/dist/summarizer/call-log.js.map +1 -0
  268. package/dist/summarizer/haiku-provider.js +136 -0
  269. package/dist/summarizer/haiku-provider.js.map +1 -0
  270. package/dist/summarizer/pricing.js +8 -0
  271. package/dist/summarizer/pricing.js.map +1 -0
  272. package/dist/summarizer/prompt-cache.js +6 -0
  273. package/dist/summarizer/prompt-cache.js.map +1 -0
  274. package/dist/summarizer/prompt.js +47 -0
  275. package/dist/summarizer/prompt.js.map +1 -0
  276. package/dist/summarizer/provider-config.js +25 -0
  277. package/dist/summarizer/provider-config.js.map +1 -0
  278. package/dist/summarizer/rollup-prompt.js +225 -0
  279. package/dist/summarizer/rollup-prompt.js.map +1 -0
  280. package/dist/summarizer/rollup-provider.js +224 -0
  281. package/dist/summarizer/rollup-provider.js.map +1 -0
  282. package/dist/types/index.js +10 -0
  283. package/dist/types/index.js.map +1 -0
  284. package/dist/util/error.js +5 -0
  285. package/dist/util/error.js.map +1 -0
  286. package/dist/util/fs.js +97 -0
  287. package/dist/util/fs.js.map +1 -0
  288. package/dist/util/relative-time.js +12 -0
  289. package/dist/util/relative-time.js.map +1 -0
  290. package/package.json +81 -0
@@ -0,0 +1,294 @@
1
+ // Projects are captured per cwd, so one repository can occupy many
2
+ // project rows. This is a read-only resolver: it computes ProjectSets without
3
+ // merging, rekeying, or otherwise changing the store.
4
+ import { existsSync, realpathSync } from 'node:fs';
5
+ import path from 'node:path';
6
+ import { isWithin, normalizeForCompare, samePath } from '../config/paths.js';
7
+ import { gitRevParseShowToplevel } from '../security/subprocess-allowlist.js';
8
+ function groupingIdentity(resolvedRow) {
9
+ const identity = resolvedRow.row.git_remote || resolvedRow.row.git_root_commit || resolvedRow.resolvedGitRoot;
10
+ return identity === '' ? null : identity;
11
+ }
12
+ function shallowestFirst(a, b) {
13
+ const aDepth = normalizeForCompare(a.row.path).split('/').length;
14
+ const bDepth = normalizeForCompare(b.row.path).split('/').length;
15
+ return aDepth - bDepth || normalizeForCompare(a.row.path).localeCompare(normalizeForCompare(b.row.path));
16
+ }
17
+ function commonRemote(members) {
18
+ const remotes = members.map((member) => member.row.git_remote).filter((remote) => remote !== null && remote !== '');
19
+ if (remotes.length === 0) {
20
+ return null;
21
+ }
22
+ const [first] = remotes;
23
+ if (first === undefined) {
24
+ return null;
25
+ }
26
+ return remotes.every((remote) => remote === first) ? first : null;
27
+ }
28
+ /**
29
+ * Groups no-root rows only when one stored path contains the other. Siblings
30
+ * merely sharing a filesystem ancestor remain separate: broadening to an
31
+ * invented ancestor would silently over-merge unrelated projects.
32
+ */
33
+ function prefixGroups(rows) {
34
+ const groups = [];
35
+ for (const row of [...rows].sort(shallowestFirst)) {
36
+ const containing = groups
37
+ .filter((group) => group.members.some((member) => isWithin(member.row.path, row.row.path)))
38
+ .sort((a, b) => {
39
+ const firstA = a.members[0];
40
+ const firstB = b.members[0];
41
+ return firstA && firstB ? shallowestFirst(firstA, firstB) : 0;
42
+ });
43
+ if (containing.length === 0) {
44
+ groups.push({ members: [row], gitRoot: null });
45
+ continue;
46
+ }
47
+ // A shallowest ancestor group is the explicit LCA anchor. Any other
48
+ // containing group can only exist because of case variants, which
49
+ // normalize to the same path and must remain one set.
50
+ const [target, ...duplicates] = containing;
51
+ if (!target) {
52
+ continue;
53
+ }
54
+ target.members.push(row);
55
+ for (const duplicate of duplicates) {
56
+ target.members.push(...duplicate.members);
57
+ groups.splice(groups.indexOf(duplicate), 1);
58
+ }
59
+ }
60
+ return groups;
61
+ }
62
+ export class ProjectResolver {
63
+ db;
64
+ resolveGitRoot;
65
+ listMemo;
66
+ listStoredMemo;
67
+ constructor(db, options = {}) {
68
+ this.db = db;
69
+ this.resolveGitRoot = options.resolveGitRoot ?? gitRevParseShowToplevel;
70
+ }
71
+ /** Uses captured identity unless consent is supplied, then live-resolves only approved paths. */
72
+ list(consent) {
73
+ if (consent === undefined) {
74
+ return this.listStored();
75
+ }
76
+ if (this.listMemo?.consent === consent) {
77
+ return this.listMemo.projects;
78
+ }
79
+ const rows = this.db.prepare('SELECT * FROM projects ORDER BY id').all();
80
+ const roots = new Map();
81
+ const storedRoots = new Map(rows.map((row) => [normalizeForCompare(row.path), row.git_root]));
82
+ const projects = this.buildProjectSets(rows, roots, (projectPath) => {
83
+ const key = normalizeForCompare(projectPath);
84
+ const cached = roots.get(key);
85
+ if (cached !== undefined || roots.has(key)) {
86
+ return cached ?? null;
87
+ }
88
+ const resolved = consent.consentState(projectPath) === 'approved' && existsSync(projectPath)
89
+ ? this.resolveGitRoot(projectPath)
90
+ : (storedRoots.get(key) ?? null);
91
+ roots.set(key, resolved);
92
+ return resolved;
93
+ });
94
+ this.listMemo = { consent, projects };
95
+ return projects;
96
+ }
97
+ /**
98
+ * Hook-budget enumeration groups rows by their captured identity without
99
+ * probing Git. A moved checkout without a remote or root commit may remain
100
+ * path-grouped here; consent-aware callers that need live checkout precision
101
+ * use list(consent).
102
+ */
103
+ listStored() {
104
+ if (this.listStoredMemo !== undefined) {
105
+ return this.listStoredMemo;
106
+ }
107
+ const rows = this.db.prepare('SELECT * FROM projects ORDER BY id').all();
108
+ const storedRoots = new Map(rows.map((row) => [normalizeForCompare(row.path), row.git_root]));
109
+ this.listStoredMemo = this.buildProjectSets(rows, new Map(), (projectPath) => storedRoots.get(normalizeForCompare(projectPath)) ?? null);
110
+ return this.listStoredMemo;
111
+ }
112
+ resolve(query) {
113
+ const rows = this.db.prepare('SELECT * FROM projects ORDER BY id').all();
114
+ const roots = new Map();
115
+ // Git resolves a symlinked stored path to its physical toplevel, so the
116
+ // candidate scope must compare physical forms or a symlink-stored row
117
+ // that list() groups into the checkout silently falls out of resolve().
118
+ const physical = new Map();
119
+ const physicalPath = (candidate) => {
120
+ const key = normalizeForCompare(candidate);
121
+ const cached = physical.get(key);
122
+ if (cached !== undefined) {
123
+ return cached;
124
+ }
125
+ let resolved = candidate;
126
+ try {
127
+ resolved = realpathSync(candidate);
128
+ }
129
+ catch {
130
+ // Missing paths keep their stored form; they cannot be symlinks to anything live.
131
+ }
132
+ physical.set(key, resolved);
133
+ return resolved;
134
+ };
135
+ const related = (a, b) => this.pathsRelated(a, b) || this.pathsRelated(physicalPath(a), physicalPath(b));
136
+ const resolveRoot = (projectPath) => {
137
+ const key = normalizeForCompare(projectPath);
138
+ const cached = roots.get(key);
139
+ if (cached !== undefined || roots.has(key)) {
140
+ return cached ?? null;
141
+ }
142
+ // Missing paths deliberately stay rootless so only recorded paths group them.
143
+ const resolved = existsSync(projectPath) ? this.resolveGitRoot(projectPath) : null;
144
+ roots.set(key, resolved);
145
+ return resolved;
146
+ };
147
+ // Resolve the live query root first. This preserves moved-checkout
148
+ // correctness without trusting the capture-time git_root column.
149
+ const queryGitRoot = resolveRoot(query);
150
+ const normalizedQuery = normalizeForCompare(query);
151
+ const lowerQuery = query.toLowerCase();
152
+ const directlyMatched = rows.filter((row) => related(row.path, query) ||
153
+ row.git_remote === query ||
154
+ row.display_name?.trim().toLowerCase() === lowerQuery ||
155
+ row.display_name?.toLowerCase().includes(lowerQuery) ||
156
+ normalizeForCompare(row.path).includes(normalizedQuery));
157
+ // A remote/name/path match can name a child row. Its live root expands
158
+ // the candidate scope to every stored row in that checkout, while Git is
159
+ // still called only for candidate paths rather than every project row.
160
+ const anchors = [query, queryGitRoot, ...directlyMatched.map((row) => resolveRoot(row.path))].filter((anchor) => anchor !== null);
161
+ const directIds = new Set(directlyMatched.map((row) => row.id));
162
+ const candidates = rows.filter((row) => directIds.has(row.id) || anchors.some((anchor) => related(row.path, anchor)));
163
+ const sets = this.buildProjectSets(candidates, roots, resolveRoot);
164
+ return this.match(query, sets);
165
+ }
166
+ /** Restricts resolution to sets with an approved member path and no denied member path. */
167
+ resolveConsented(query, consent) {
168
+ // An existing unconsented caller path must never become a Git subprocess cwd.
169
+ // Consented paths and loose names may proceed to consent-checked or stored candidate paths.
170
+ if (existsSync(query) && consent.consentState(query) !== 'approved') {
171
+ return { project: null };
172
+ }
173
+ const resolved = this.resolve(query);
174
+ if ('project' in resolved) {
175
+ return resolved.project !== null && this.isConsented(resolved.project, consent) ? resolved : { project: null };
176
+ }
177
+ const projects = this.listConsented(consent);
178
+ const candidates = resolved.candidates.filter((candidate) => projects.some((project) => project.paths[0] === candidate.path));
179
+ if (candidates.length === 0) {
180
+ return { project: null };
181
+ }
182
+ if (candidates.length === 1) {
183
+ const [candidate] = candidates;
184
+ const project = projects.find((set) => set.paths[0] === candidate?.path);
185
+ if (project !== undefined) {
186
+ return { project };
187
+ }
188
+ }
189
+ return { ambiguous: true, candidates };
190
+ }
191
+ listConsented(consent) {
192
+ return this.list(consent).filter((project) => this.isConsented(project, consent));
193
+ }
194
+ listConsentedStored(consent) {
195
+ return this.listStored().filter((project) => this.isConsented(project, consent));
196
+ }
197
+ buildProjectSets(rows, roots, resolveRoot = (projectPath) => {
198
+ const key = normalizeForCompare(projectPath);
199
+ const cached = roots.get(key);
200
+ if (cached !== undefined || roots.has(key)) {
201
+ return cached ?? null;
202
+ }
203
+ const resolved = existsSync(projectPath) ? this.resolveGitRoot(projectPath) : null;
204
+ roots.set(key, resolved);
205
+ return resolved;
206
+ }) {
207
+ const resolved = rows.map((row) => ({ row, resolvedGitRoot: resolveRoot(row.path) }));
208
+ const byIdentity = new Map();
209
+ const rootless = [];
210
+ for (const resolvedRow of resolved) {
211
+ const identity = groupingIdentity(resolvedRow);
212
+ if (identity === null) {
213
+ rootless.push(resolvedRow);
214
+ continue;
215
+ }
216
+ const key = normalizeForCompare(identity);
217
+ const group = byIdentity.get(key);
218
+ if (group) {
219
+ group.members.push(resolvedRow);
220
+ group.gitRoot ??= resolvedRow.resolvedGitRoot;
221
+ }
222
+ else {
223
+ byIdentity.set(key, { members: [resolvedRow], gitRoot: resolvedRow.resolvedGitRoot });
224
+ }
225
+ }
226
+ return [...byIdentity.values(), ...prefixGroups(rootless)]
227
+ .map((group) => this.toProjectSet(group))
228
+ .sort((a, b) => normalizeForCompare(a.paths[0] ?? '').localeCompare(normalizeForCompare(b.paths[0] ?? '')));
229
+ }
230
+ match(query, sets) {
231
+ const normalizedQuery = normalizeForCompare(query);
232
+ const exact = sets.filter((set) => set.paths.some((memberPath) => samePath(memberPath, query)));
233
+ if (exact.length > 0) {
234
+ return this.singleOrAmbiguous(exact);
235
+ }
236
+ const byGitRoot = sets.filter((set) => set.gitRoot !== null && normalizeForCompare(set.gitRoot) === normalizedQuery);
237
+ if (byGitRoot.length > 0) {
238
+ return this.singleOrAmbiguous(byGitRoot);
239
+ }
240
+ const byRemote = sets.filter((set) => set.gitRemote !== null && set.gitRemote === query);
241
+ if (byRemote.length > 0) {
242
+ return this.singleOrAmbiguous(byRemote);
243
+ }
244
+ const byDisplayName = sets.filter((set) => set.displayName.toLowerCase() === query.toLowerCase());
245
+ if (byDisplayName.length > 0) {
246
+ return this.singleOrAmbiguous(byDisplayName);
247
+ }
248
+ const substring = sets.filter((set) => set.displayName.toLowerCase().includes(query.toLowerCase()) ||
249
+ set.paths.some((memberPath) => normalizeForCompare(memberPath).includes(normalizedQuery)));
250
+ return substring.length === 0 ? { project: null } : this.singleOrAmbiguous(substring);
251
+ }
252
+ pathsRelated(a, b) {
253
+ return samePath(a, b) || isWithin(a, b) || isWithin(b, a);
254
+ }
255
+ isConsented(project, consent) {
256
+ return (project.paths.some((projectPath) => consent.isConsented(projectPath)) &&
257
+ project.paths.every((projectPath) => consent.consentState(projectPath) !== 'denied'));
258
+ }
259
+ toProjectSet(group) {
260
+ const members = [...group.members].sort(shallowestFirst);
261
+ const [shallowest] = members;
262
+ if (!shallowest) {
263
+ throw new Error('ProjectSet cannot be built without project rows');
264
+ }
265
+ const gitRemote = commonRemote(members);
266
+ const gitRootCommit = members.map((member) => member.row.git_root_commit).find((commit) => commit !== null && commit !== '');
267
+ return {
268
+ key: gitRemote ?? gitRootCommit ?? group.gitRoot ?? shallowest.row.path,
269
+ displayName: shallowest.row.display_name?.trim() || path.basename(shallowest.row.path),
270
+ paths: members.map((member) => member.row.path),
271
+ projectIds: members.map((member) => member.row.id),
272
+ gitRoot: group.gitRoot,
273
+ gitRemote,
274
+ };
275
+ }
276
+ singleOrAmbiguous(matches) {
277
+ if (matches.length === 1) {
278
+ const [project] = matches;
279
+ if (project) {
280
+ return { project };
281
+ }
282
+ }
283
+ return { ambiguous: true, candidates: matches.map((set) => this.candidate(set)) };
284
+ }
285
+ candidate(set) {
286
+ const placeholders = set.projectIds.map(() => '?').join(',');
287
+ const activity = this.db
288
+ .prepare(`SELECT MAX(last_ingested_at) AS last_activity, COUNT(*) AS sessions
289
+ FROM sessions WHERE project_id IN (${placeholders})`)
290
+ .get(...set.projectIds);
291
+ return { name: set.displayName, path: set.paths[0] ?? set.key, last_activity: activity.last_activity, sessions: activity.sessions };
292
+ }
293
+ }
294
+ //# sourceMappingURL=project-resolver.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"project-resolver.js","sourceRoot":"","sources":["../../src/storage/project-resolver.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,104 @@
1
+ import path from 'node:path';
2
+ import { gitRemoteGetUrlOrigin, gitRevParseShowToplevel, gitRootCommit } from '../security/subprocess-allowlist.js';
3
+ export class ProjectStore {
4
+ db;
5
+ resolveGitRoot;
6
+ resolveGitRemote;
7
+ resolveGitRootCommit;
8
+ /** Git discovery is per-cwd input, so null results must be cached as well. */
9
+ gitRootsByCwd = new Map();
10
+ stmts;
11
+ constructor(db, options = {}) {
12
+ this.db = db;
13
+ this.resolveGitRoot = options.resolveGitRoot ?? gitRevParseShowToplevel;
14
+ this.resolveGitRemote = options.resolveGitRemote ?? gitRemoteGetUrlOrigin;
15
+ this.resolveGitRootCommit = options.resolveGitRootCommit ?? gitRootCommit;
16
+ this.stmts = {
17
+ findProjectByPath: db.prepare('SELECT * FROM projects WHERE path = ?'),
18
+ findProjectByGitRoot: db.prepare('SELECT * FROM projects WHERE git_root = ? ORDER BY id LIMIT 1'),
19
+ insertProject: db.prepare(`INSERT INTO projects (path, display_name, git_root, git_remote, git_root_commit, first_seen_at, last_seen_at)
20
+ VALUES (@path, @display_name, @git_root, @git_remote, @git_root_commit, @now, @now)
21
+ ON CONFLICT(path) DO UPDATE SET
22
+ git_root = excluded.git_root,
23
+ git_remote = excluded.git_remote,
24
+ git_root_commit = excluded.git_root_commit,
25
+ last_seen_at = excluded.last_seen_at
26
+ RETURNING id`),
27
+ touchProject: db.prepare('UPDATE projects SET last_seen_at = ? WHERE id = ?'),
28
+ };
29
+ }
30
+ /**
31
+ * Finds or creates the project row for a captured cwd. Git-backed projects
32
+ * are keyed by their repository root; rootless directories retain the
33
+ * historical cwd key. Resolution is cached for this store lifetime so the
34
+ * daemon does not execute git for every turn from the same cwd.
35
+ */
36
+ upsertProject(projectPath, identity = this.resolveProjectIdentity(projectPath)) {
37
+ const existing = identity.gitRoot
38
+ ? (this.stmts.findProjectByGitRoot.get(identity.gitRoot) ?? undefined)
39
+ : this.stmts.findProjectByPath.get(projectPath);
40
+ const now = new Date().toISOString();
41
+ if (existing) {
42
+ this.stmts.touchProject.run(now, existing.id);
43
+ return { ...existing, last_seen_at: now };
44
+ }
45
+ const projectKey = identity.gitRoot ?? projectPath;
46
+ const inserted = this.stmts.insertProject.get({
47
+ path: projectKey,
48
+ display_name: path.basename(projectKey),
49
+ git_root: identity.gitRoot,
50
+ git_remote: identity.gitRemote,
51
+ git_root_commit: identity.gitRootCommit,
52
+ now,
53
+ });
54
+ // biome-ignore lint/style/noNonNullAssertion: row was just inserted or updated above, lookup by its returned id can't miss
55
+ return this.getProjectById(inserted.id);
56
+ }
57
+ /**
58
+ * Resolves the values needed by upsertProject before its caller opens a
59
+ * write transaction. Stored identities and the per-cwd cache avoid git on
60
+ * the common path; a stored rootless row is deliberately re-probed by a
61
+ * new store so it can adopt a repository created since first capture.
62
+ */
63
+ resolveProjectIdentity(projectPath) {
64
+ const byPath = this.stmts.findProjectByPath.get(projectPath);
65
+ if (byPath?.git_root) {
66
+ this.gitRootsByCwd.set(projectPath, byPath.git_root);
67
+ return this.identityFromProject(byPath);
68
+ }
69
+ const gitRoot = this.gitRootFor(projectPath);
70
+ const existing = gitRoot ? (this.stmts.findProjectByGitRoot.get(gitRoot) ?? undefined) : byPath;
71
+ if (existing) {
72
+ return this.identityFromProject(existing);
73
+ }
74
+ return {
75
+ gitRoot,
76
+ gitRemote: gitRoot ? this.resolveGitRemote(gitRoot) : null,
77
+ gitRootCommit: gitRoot ? this.resolveGitRootCommit(gitRoot) : null,
78
+ };
79
+ }
80
+ getProjectById(id) {
81
+ return this.db.prepare('SELECT * FROM projects WHERE id = ?').get(id);
82
+ }
83
+ findProject(query) {
84
+ const rows = this.listProjects();
85
+ return rows.find((r) => r.path === query) ?? rows.find((r) => r.path.includes(query) || r.display_name?.includes(query));
86
+ }
87
+ listProjects() {
88
+ return this.db.prepare('SELECT * FROM projects ORDER BY last_seen_at DESC').all();
89
+ }
90
+ gitRootFor(projectPath) {
91
+ if (!this.gitRootsByCwd.has(projectPath)) {
92
+ this.gitRootsByCwd.set(projectPath, this.resolveGitRoot(projectPath));
93
+ }
94
+ return this.gitRootsByCwd.get(projectPath) ?? null;
95
+ }
96
+ identityFromProject(project) {
97
+ return {
98
+ gitRoot: project.git_root,
99
+ gitRemote: project.git_remote,
100
+ gitRootCommit: project.git_root_commit,
101
+ };
102
+ }
103
+ }
104
+ //# sourceMappingURL=project-store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"project-store.js","sourceRoot":"","sources":["../../src/storage/project-store.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,85 @@
1
+ // Recomputes sessions.rendered_chars and sessions.rendered_turns from the same
2
+ // raw-turn renderer used at ingestion. It is transcript-only: no summarizer
3
+ // call and no retained text.
4
+ import { isReadableProviderSource } from '../config/paths.js';
5
+ import { renderedChars, renderedTurns } from '../rendering/raw-turn-renderer.js';
6
+ import { applyBackfill, planBackfill } from './backfill-runner.js';
7
+ async function countSession(session, adapter, turnIndexes) {
8
+ if (!isReadableProviderSource(session.tool, session.source_path)) {
9
+ return null;
10
+ }
11
+ const turns = [];
12
+ try {
13
+ for await (const turn of adapter.parseTurns(session.source_path, undefined, { closeTrailingOnIdle: true })) {
14
+ if (turnIndexes.has(turn.turnIndex)) {
15
+ turns.push(turn);
16
+ }
17
+ }
18
+ }
19
+ catch {
20
+ return null;
21
+ }
22
+ // A retained row the current adapter cannot re-produce is an unavailable
23
+ // transcript, not a legitimate zero-byte render. NULL keeps that visible.
24
+ if (turns.length !== turnIndexes.size) {
25
+ return null;
26
+ }
27
+ turns.sort((left, right) => left.turnIndex - right.turnIndex);
28
+ return { renderedChars: renderedChars(turns), renderedTurns: renderedTurns(turns) };
29
+ }
30
+ const deriver = {
31
+ load(db) {
32
+ const sessions = db
33
+ .prepare('SELECT id, tool, native_id, source_path, rendered_chars, rendered_turns FROM sessions ORDER BY id')
34
+ .all();
35
+ const turnRows = db.prepare('SELECT session_id, turn_index FROM memories ORDER BY session_id, turn_index').all();
36
+ const indexesBySession = new Map();
37
+ for (const row of turnRows) {
38
+ const indexes = indexesBySession.get(row.session_id) ?? new Set();
39
+ indexes.add(row.turn_index);
40
+ indexesBySession.set(row.session_id, indexes);
41
+ }
42
+ return { sessions, state: indexesBySession };
43
+ },
44
+ async derive({ adapters, session, state }) {
45
+ const counted = await countSession(session, adapters[session.tool], state.get(session.id) ?? new Set());
46
+ const transcriptMissing = counted === null;
47
+ if (!transcriptMissing && session.rendered_chars === counted.renderedChars && session.rendered_turns === counted.renderedTurns) {
48
+ return undefined;
49
+ }
50
+ return {
51
+ sessionId: session.id,
52
+ tool: session.tool,
53
+ nativeId: session.native_id,
54
+ sourcePath: session.source_path,
55
+ beforeRenderedChars: session.rendered_chars,
56
+ beforeRenderedTurns: session.rendered_turns,
57
+ renderedChars: counted?.renderedChars ?? null,
58
+ renderedTurns: counted?.renderedTurns ?? null,
59
+ transcriptMissing,
60
+ };
61
+ },
62
+ shouldWrite: () => true,
63
+ write(db, change) {
64
+ // The daemon may append while this transcript-only pass is reading.
65
+ // Never overwrite a row whose stats changed after the plan snapshot;
66
+ // post-verification will surface it for the next bounded retry.
67
+ const result = db
68
+ .prepare(`UPDATE sessions SET rendered_chars = ?, rendered_turns = ?
69
+ WHERE id = ? AND rendered_chars IS ? AND rendered_turns IS ?`)
70
+ .run(change.renderedChars, change.renderedTurns, change.sessionId, change.beforeRenderedChars, change.beforeRenderedTurns);
71
+ return { sessionsSkippedConcurrent: result.changes === 0 ? 1 : 0 };
72
+ },
73
+ recordConcurrentSkips(plan, skipped) {
74
+ plan.sessionsSkippedConcurrent = skipped;
75
+ },
76
+ };
77
+ export async function planRenderedCharsBackfill(db, adapters) {
78
+ const plan = await planBackfill(db, adapters, deriver);
79
+ return { ...plan, sessionsSkippedConcurrent: 0 };
80
+ }
81
+ export async function applyRenderedCharsBackfill(db, adapters) {
82
+ const plan = await applyBackfill(db, adapters, deriver);
83
+ return { ...plan, sessionsSkippedConcurrent: plan.sessionsSkippedConcurrent ?? 0 };
84
+ }
85
+ //# sourceMappingURL=rendered-chars-backfill.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rendered-chars-backfill.js","sourceRoot":"","sources":["../../src/storage/rendered-chars-backfill.ts"],"names":[],"mappings":"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"}