codehere 1.0.1 → 1.0.3

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 (163) hide show
  1. package/README.md +35 -193
  2. package/dist/application/use-cases/check-claimed-tests.js +81 -0
  3. package/dist/application/use-cases/detect-test-weakening.js +237 -0
  4. package/dist/application/use-cases/rerun-tests.js +110 -7
  5. package/dist/index.js +38 -1212
  6. package/dist/infrastructure/orchestration/artifacts-extractor.js +2 -323
  7. package/dist/presentation/cli/commands/check-command.js +178 -23
  8. package/dist/presentation/cli/commands/help-command.js +21 -285
  9. package/dist/ui.js +5 -236
  10. package/dist/utils/version.js +0 -6
  11. package/package.json +9 -38
  12. package/dist/application/services/verdict-service.js +0 -154
  13. package/dist/application/use-cases/assess-cross-vendor.js +0 -126
  14. package/dist/application/use-cases/build-cross-vendor-verdict.js +0 -118
  15. package/dist/application/use-cases/domain-trust.js +0 -110
  16. package/dist/application/use-cases/export-aat.js +0 -107
  17. package/dist/application/use-cases/generate-acceptance-criteria.js +0 -223
  18. package/dist/application/use-cases/generate-final-report.js +0 -259
  19. package/dist/application/use-cases/generate-narration.js +0 -130
  20. package/dist/application/use-cases/generate-thread-title.js +0 -181
  21. package/dist/application/use-cases/narrator-prompt-template.js +0 -158
  22. package/dist/application/use-cases/plan-auto-review.js +0 -57
  23. package/dist/application/use-cases/reconcile-audit.js +0 -100
  24. package/dist/application/use-cases/search-sessions.js +0 -70
  25. package/dist/application/use-cases/should-auto-review.js +0 -71
  26. package/dist/application/use-cases/thread-export.js +0 -245
  27. package/dist/application/use-cases/verify-claim.js +0 -456
  28. package/dist/application/use-cases/write-pass.js +0 -277
  29. package/dist/domain/audit.js +0 -11
  30. package/dist/domain/services/codebase-detector.js +0 -85
  31. package/dist/domain/task.js +0 -91
  32. package/dist/error-handler.js +0 -139
  33. package/dist/infrastructure/completion/bash-completion.js +0 -29
  34. package/dist/infrastructure/completion/fish-completion.js +0 -17
  35. package/dist/infrastructure/completion/zsh-completion.js +0 -25
  36. package/dist/infrastructure/config/config-manager.js +0 -174
  37. package/dist/infrastructure/config/product-context.js +0 -49
  38. package/dist/infrastructure/context/adapters/drawio-source.js +0 -157
  39. package/dist/infrastructure/context/adapters/figma-source.js +0 -198
  40. package/dist/infrastructure/context/adapters/github-issue-source.js +0 -160
  41. package/dist/infrastructure/context/adapters/linear-source.js +0 -190
  42. package/dist/infrastructure/context/context-resolver.js +0 -144
  43. package/dist/infrastructure/context/context-source.js +0 -114
  44. package/dist/infrastructure/context/github-api.js +0 -110
  45. package/dist/infrastructure/context/github-device-flow.js +0 -65
  46. package/dist/infrastructure/context/oauth-flow.js +0 -207
  47. package/dist/infrastructure/context/oauth-store.js +0 -122
  48. package/dist/infrastructure/context/registry-bootstrap.js +0 -95
  49. package/dist/infrastructure/context/write-pass-resolver.js +0 -106
  50. package/dist/infrastructure/cost/budget-config.js +0 -114
  51. package/dist/infrastructure/cost/cost-router.js +0 -97
  52. package/dist/infrastructure/cost/spend-aggregator.js +0 -222
  53. package/dist/infrastructure/cost/team-ledger.js +0 -355
  54. package/dist/infrastructure/discovery/discovery-log.js +0 -85
  55. package/dist/infrastructure/hooks/audit-tip-signature.js +0 -336
  56. package/dist/infrastructure/hooks/claude-sdk-hook.js +0 -1018
  57. package/dist/infrastructure/hooks/hook-mode.js +0 -87
  58. package/dist/infrastructure/hooks/mirror-tool-call-to-audit.js +0 -49
  59. package/dist/infrastructure/hooks/pre-prompt-scan.js +0 -122
  60. package/dist/infrastructure/hooks/session-analyzer.js +0 -177
  61. package/dist/infrastructure/observability/execution-tracer.js +0 -227
  62. package/dist/infrastructure/orchestration/agent-adapter.js +0 -13
  63. package/dist/infrastructure/orchestration/agent-installer.js +0 -101
  64. package/dist/infrastructure/orchestration/agents-config-seed.js +0 -68
  65. package/dist/infrastructure/orchestration/aider-adapter.js +0 -611
  66. package/dist/infrastructure/orchestration/cassette-recorder.js +0 -120
  67. package/dist/infrastructure/orchestration/claude-code-adapter.js +0 -592
  68. package/dist/infrastructure/orchestration/codex-adapter.js +0 -574
  69. package/dist/infrastructure/orchestration/conductor-adapter.js +0 -416
  70. package/dist/infrastructure/orchestration/context-handoff.js +0 -162
  71. package/dist/infrastructure/orchestration/control/codex-control-surface.js +0 -99
  72. package/dist/infrastructure/orchestration/control/control-surface.js +0 -15
  73. package/dist/infrastructure/orchestration/detect-ollama.js +0 -75
  74. package/dist/infrastructure/orchestration/failover-classifier.js +0 -151
  75. package/dist/infrastructure/orchestration/failover-config.js +0 -67
  76. package/dist/infrastructure/orchestration/failover-policy.js +0 -106
  77. package/dist/infrastructure/orchestration/gemini-adapter.js +0 -663
  78. package/dist/infrastructure/orchestration/generic-cli-adapter.js +0 -689
  79. package/dist/infrastructure/orchestration/graphify-context.js +0 -209
  80. package/dist/infrastructure/orchestration/memory-injector.js +0 -380
  81. package/dist/infrastructure/orchestration/mocks/mock-agent-repl.js +0 -388
  82. package/dist/infrastructure/orchestration/mocks/mock-codex-app-server.js +0 -73
  83. package/dist/infrastructure/orchestration/mocks/mock-synthesis.js +0 -83
  84. package/dist/infrastructure/orchestration/mocks/resolve-agent-spawn.js +0 -37
  85. package/dist/infrastructure/orchestration/opencode-adapter.js +0 -580
  86. package/dist/infrastructure/orchestration/progress-aggregator.js +0 -906
  87. package/dist/infrastructure/orchestration/readiness.js +0 -103
  88. package/dist/infrastructure/orchestration/session-manager.js +0 -1590
  89. package/dist/infrastructure/orchestration/session-persistence.js +0 -394
  90. package/dist/infrastructure/orchestration/thread-key-store.js +0 -127
  91. package/dist/infrastructure/orchestration/write-pass-store.js +0 -38
  92. package/dist/infrastructure/protocols/cloud-sync.js +0 -207
  93. package/dist/infrastructure/protocols/mcp-helpers.js +0 -25
  94. package/dist/infrastructure/protocols/mcp-server-impl.js +0 -3935
  95. package/dist/infrastructure/protocols/mcp-tools.js +0 -401
  96. package/dist/infrastructure/protocols/probe-port.js +0 -36
  97. package/dist/infrastructure/protocols/thread-phase.js +0 -105
  98. package/dist/infrastructure/protocols/ui/app-script-artifacts.js +0 -121
  99. package/dist/infrastructure/protocols/ui/app-script-audit-footer.js +0 -44
  100. package/dist/infrastructure/protocols/ui/app-script-audit-verify.js +0 -56
  101. package/dist/infrastructure/protocols/ui/app-script-composer-cost.js +0 -588
  102. package/dist/infrastructure/protocols/ui/app-script-context-sources.js +0 -168
  103. package/dist/infrastructure/protocols/ui/app-script-conversation.js +0 -67
  104. package/dist/infrastructure/protocols/ui/app-script-drawer.js +0 -115
  105. package/dist/infrastructure/protocols/ui/app-script-failover.js +0 -133
  106. package/dist/infrastructure/protocols/ui/app-script-github.js +0 -113
  107. package/dist/infrastructure/protocols/ui/app-script-observer.js +0 -104
  108. package/dist/infrastructure/protocols/ui/app-script-onboarding.js +0 -80
  109. package/dist/infrastructure/protocols/ui/app-script-overlay.js +0 -203
  110. package/dist/infrastructure/protocols/ui/app-script-recovery.js +0 -136
  111. package/dist/infrastructure/protocols/ui/app-script-render.js +0 -166
  112. package/dist/infrastructure/protocols/ui/app-script-share.js +0 -78
  113. package/dist/infrastructure/protocols/ui/app-script-sidebar.js +0 -194
  114. package/dist/infrastructure/protocols/ui/app-script-tour.js +0 -78
  115. package/dist/infrastructure/protocols/ui/app-script-wpass.js +0 -312
  116. package/dist/infrastructure/protocols/ui/app-script.js +0 -2998
  117. package/dist/infrastructure/protocols/ui/font-faces.js +0 -7
  118. package/dist/infrastructure/protocols/ui/styles.js +0 -2147
  119. package/dist/infrastructure/protocols/ui/tokens.js +0 -96
  120. package/dist/infrastructure/protocols/ui-page-v2.js +0 -320
  121. package/dist/infrastructure/protocols/verify-page.js +0 -1677
  122. package/dist/infrastructure/security/file-collection.js +0 -118
  123. package/dist/infrastructure/security/fix-code.js +0 -144
  124. package/dist/infrastructure/security/fix-secret.js +0 -89
  125. package/dist/infrastructure/security/permissions-config.js +0 -235
  126. package/dist/infrastructure/security/scan-types.js +0 -10
  127. package/dist/infrastructure/security/ship-scan.js +0 -195
  128. package/dist/infrastructure/security/trust-scorer.js +0 -724
  129. package/dist/infrastructure/security/vibe-checks.js +0 -99
  130. package/dist/infrastructure/storage/atomic-write.js +0 -86
  131. package/dist/infrastructure/storage/cross-tool-agents.js +0 -268
  132. package/dist/infrastructure/storage/project-thread-migration.js +0 -237
  133. package/dist/infrastructure/storage/project-thread-mirror.js +0 -671
  134. package/dist/infrastructure/storage/project-thread-reader.js +0 -409
  135. package/dist/infrastructure/storage/project-thread-title-backfill.js +0 -214
  136. package/dist/monitoring.js +0 -212
  137. package/dist/presentation/cli/commands/audit-command.js +0 -201
  138. package/dist/presentation/cli/commands/budget-command.js +0 -79
  139. package/dist/presentation/cli/commands/config-command.js +0 -125
  140. package/dist/presentation/cli/commands/context-source-command.js +0 -217
  141. package/dist/presentation/cli/commands/hook-command.js +0 -265
  142. package/dist/presentation/cli/commands/init-command.js +0 -590
  143. package/dist/presentation/cli/commands/memory-command.js +0 -1031
  144. package/dist/presentation/cli/commands/narrator-command.js +0 -101
  145. package/dist/presentation/cli/commands/permission-command.js +0 -105
  146. package/dist/presentation/cli/commands/protocols-builder.js +0 -254
  147. package/dist/presentation/cli/commands/protocols-command.js +0 -84
  148. package/dist/presentation/cli/commands/repo-map-builder.js +0 -247
  149. package/dist/presentation/cli/commands/report-command.js +0 -403
  150. package/dist/presentation/cli/commands/scan-command.js +0 -52
  151. package/dist/presentation/cli/commands/scan-tool-command.js +0 -110
  152. package/dist/presentation/cli/commands/search-command.js +0 -51
  153. package/dist/presentation/cli/commands/setup-command.js +0 -330
  154. package/dist/presentation/cli/commands/status-command.js +0 -614
  155. package/dist/presentation/cli/commands/team-command.js +0 -256
  156. package/dist/presentation/cli/commands/thread-command.js +0 -370
  157. package/dist/presentation/cli/commands/trust-command.js +0 -328
  158. package/dist/presentation/cli/commands/verify-command.js +0 -298
  159. package/dist/presentation/cli/error-display.js +0 -328
  160. package/dist/presentation/cli/repl.js +0 -805
  161. package/dist/presentation/cli/task-deliverable.js +0 -153
  162. package/dist/testing/cassette.js +0 -125
  163. package/dist/testing/replay-adapter.js +0 -147
@@ -1,394 +0,0 @@
1
- /**
2
- * Session persistence — survives daemon restart.
3
- *
4
- * Beta.3 dogfood surfaced a reliability gap: after `ch start` exited and was
5
- * relaunched, the task UI was empty even when completed sessions had produced
6
- * valuable final reports. The audit log (hook-events.jsonl) persisted every
7
- * tool call but not the session-level PM state (prompt, artifacts, steps).
8
- *
9
- * This module adds a minimal two-record schema:
10
- * { t: 'spawned', id, agent, prompt, cwd, startedAt }
11
- * { t: 'completed', id, status, durationMs, steps, artifacts, completedAt }
12
- *
13
- * On spawn, we write the first line. On exit, we write the second. On daemon
14
- * start, we read the last N pairs and surface them as read-only "persisted"
15
- * sessions (no kill button, no live stream; the subprocess died with the
16
- * previous daemon).
17
- *
18
- * Write path stays append-only — never rewrites history. Lock-free because
19
- * single writer (the daemon). If two daemons ran concurrently the worst case
20
- * is interleaved lines, which the reader tolerates.
21
- */
22
- import { existsSync, mkdirSync, appendFileSync, readFileSync } from 'fs';
23
- import { homedir } from 'os';
24
- import { join, dirname } from 'path';
25
- function sessionsFile() {
26
- // Honor CODEHERE_HOME like the rest of the codebase (config, audit, threads)
27
- // so a scratch home isolates sessions for dogfooding / the mock harness.
28
- return join(process.env.CODEHERE_HOME || homedir(), '.codehere', 'sessions.jsonl');
29
- }
30
- /**
31
- * A session's records must all land in the SAME file: the one resolved when it
32
- * was spawned. sessionsFile() reads env per-call, so a write that fires after
33
- * the environment moved on (async exit handlers; test suites that swap
34
- * CODEHERE_HOME per test) would otherwise scatter a session across homes —
35
- * the failover-skip flake forensics caught exactly this shape (a slow
36
- * real-claude exit record from another test's home, spawned-record absent —
37
- * full record in commit message). Spawn binds the
38
- * path; completed/finalReport reuse it. FIFO-capped — eviction only means a
39
- * later write falls back to the env-resolved path (prod-identical: the
40
- * daemon's home never changes mid-run).
41
- */
42
- const sessionFileById = new Map();
43
- const SESSION_FILE_CAP = 500;
44
- function bindSessionFile(id) {
45
- const file = sessionsFile();
46
- if (sessionFileById.size >= SESSION_FILE_CAP) {
47
- const oldest = sessionFileById.keys().next().value;
48
- if (oldest !== undefined)
49
- sessionFileById.delete(oldest);
50
- }
51
- sessionFileById.set(id, file);
52
- return file;
53
- }
54
- function fileForSession(id) {
55
- return sessionFileById.get(id) ?? sessionsFile();
56
- }
57
- function ensureDirFor(file) {
58
- const dir = dirname(file);
59
- if (!existsSync(dir))
60
- mkdirSync(dir, { recursive: true });
61
- }
62
- export function appendSpawned(rec) {
63
- try {
64
- const file = bindSessionFile(rec.id);
65
- ensureDirFor(file);
66
- // S18 · practice is a property of the RUN, stamped once on the durable
67
- // record at spawn — never re-derived from a later daemon's env. Seeded
68
- // demo-* showcases keep their FEATURED meaning even if seeded under mock.
69
- const mockDaemon = process.env.CODEHERE_MOCK === '1' || process.env.CODEHERE_MOCK === 'true';
70
- const mockRun = mockDaemon && !rec.id.startsWith('demo-');
71
- appendFileSync(file, JSON.stringify({ t: 'spawned', ...rec, mockRun }) + '\n', 'utf-8');
72
- }
73
- catch {
74
- /* persistence is best-effort; never break the spawn path */
75
- }
76
- }
77
- export function appendCompleted(rec) {
78
- try {
79
- const file = fileForSession(rec.id);
80
- ensureDirFor(file);
81
- appendFileSync(file, JSON.stringify({ t: 'completed', ...rec }) + '\n', 'utf-8');
82
- }
83
- catch {
84
- /* best-effort */
85
- }
86
- }
87
- /**
88
- * v0.6.x — persist a Final Report so it survives daemon restart.
89
- *
90
- * Pre-fix: Final Reports lived only in SessionManager.finalReportBySession
91
- * (in-memory Map) and were lost on every daemon bounce. Founder hit this
92
- * 2026-05-03 after a Mac restart: completed sessions showed
93
- * "Generating report…" forever because there was no on-disk record + no
94
- * "report-unavailable" state in the UI.
95
- *
96
- * Append-only: Final Report fires AFTER the CompletedRecord (generateFinalReport
97
- * is async post-exit), so it gets its own record type. Reader merges by id.
98
- */
99
- /** S3 · persist the human's "mark resolved" ack so it sticks across reload.
100
- * Append-only like every other record; reader merges by id (last wins). */
101
- export function appendResolved(rec) {
102
- try {
103
- const file = fileForSession(rec.id);
104
- ensureDirFor(file);
105
- appendFileSync(file, JSON.stringify({ t: 'resolved', ...rec }) + '\n', 'utf-8');
106
- }
107
- catch {
108
- /* best-effort */
109
- }
110
- }
111
- export function appendFinalReport(rec) {
112
- try {
113
- const file = fileForSession(rec.id);
114
- ensureDirFor(file);
115
- appendFileSync(file, JSON.stringify({ t: 'finalReport', ...rec }) + '\n', 'utf-8');
116
- }
117
- catch {
118
- /* best-effort */
119
- }
120
- }
121
- /** Tier 2.2 · persist the audit↔disk reconciliation so the OBSERVED signal
122
- * survives a daemon restart (mirrors appendFinalReport's async-post-exit shape). */
123
- export function appendAuditRecon(id, recon) {
124
- try {
125
- const file = fileForSession(id);
126
- ensureDirFor(file);
127
- appendFileSync(file, JSON.stringify({ t: 'audit-recon', id, recon }) + '\n', 'utf-8');
128
- }
129
- catch {
130
- /* best-effort */
131
- }
132
- }
133
- /**
134
- * Walk the session log backward, pair spawned+completed records by id, return
135
- * the most recent `limit` completed pairs sorted newest-first.
136
- *
137
- * We only surface fully-completed sessions because an orphaned `spawned` with
138
- * no `completed` means either: (a) the daemon died mid-task (subprocess also
139
- * died, no artifacts), or (b) a concurrent daemon is mid-flight (not ours to
140
- * show). Either way, not useful in the UI.
141
- */
142
- /**
143
- * v0.7 — load a single completed session by id. Returns undefined if no
144
- * matching session exists or if the session is orphaned (spawned but never
145
- * completed). Used by `codehere verify <session-id>` (F-03 S2) and the
146
- * upcoming `codehere audit verify` CLI (F-05).
147
- *
148
- * Walks the whole file once. For 1k+ sessions this is fine; if it ever
149
- * becomes hot we can keep an in-memory index.
150
- *
151
- * Accepts a short-id prefix too (first 7+ chars), mirroring git's UX.
152
- * If the prefix is ambiguous we return the most recent match (newest-first
153
- * traversal). Returning undefined on ambiguity would surprise users at
154
- * the CLI · git's `--abbrev-commit` collisions are very rare in practice
155
- * for our id space.
156
- */
157
- /** S18 · classification for the joined record. The run-time stamp wins. For
158
- * LEGACY records written before the stamp existed, the mock adapter leaves
159
- * two durable on-record signals, and either proves the run was practice:
160
- * (1) it mints its own resume tokens "mock-…" (mock-agent-repl.ts) — a real
161
- * vendor CLI never does; (2) its ONLY edit artifact is the hardcoded,
162
- * gitignored `codehere-mock-change.md` (MOCK_EDIT_FILE) — conductor practice
163
- * runs carry no resume token but their steps touched it. Classified on read
164
- * so pre-stamp practice never renders as the founder's real work. demo-*
165
- * showcases keep their FEATURED meaning. Records with neither stamp nor
166
- * signal stay undefined (the reader's env fallback — genuinely no signal). */
167
- function recordedMockRun(s, c) {
168
- if (s.mockRun !== undefined)
169
- return s.mockRun;
170
- if (s.id.startsWith('demo-'))
171
- return undefined;
172
- if (c.nativeSessionId?.startsWith('mock-'))
173
- return true;
174
- if (c.steps?.some((st) => st.label?.includes('codehere-mock-change.md')))
175
- return true;
176
- return undefined;
177
- }
178
- export function loadSessionById(idOrPrefix) {
179
- const file = sessionsFile();
180
- if (!existsSync(file))
181
- return undefined;
182
- let content;
183
- try {
184
- content = readFileSync(file, 'utf-8');
185
- }
186
- catch {
187
- return undefined;
188
- }
189
- const spawned = new Map();
190
- const completed = [];
191
- const finalReports = new Map();
192
- const resolved = new Map();
193
- const auditRecons = new Map();
194
- for (const line of content.split('\n')) {
195
- const trimmed = line.trim();
196
- if (!trimmed)
197
- continue;
198
- let rec;
199
- try {
200
- rec = JSON.parse(trimmed);
201
- }
202
- catch {
203
- continue;
204
- }
205
- if (rec.t === 'spawned')
206
- spawned.set(rec.id, rec);
207
- else if (rec.t === 'completed')
208
- completed.push(rec);
209
- else if (rec.t === 'finalReport')
210
- finalReports.set(rec.id, rec);
211
- else if (rec.t === 'resolved')
212
- resolved.set(rec.id, rec.resolvedAt);
213
- else if (rec.t === 'audit-recon')
214
- auditRecons.set(rec.id, rec.recon);
215
- }
216
- // Newest-first to break prefix-collision ties toward the most recent
217
- for (let i = completed.length - 1; i >= 0; i--) {
218
- const c = completed[i];
219
- if (c.id !== idOrPrefix && !c.id.startsWith(idOrPrefix))
220
- continue;
221
- const s = spawned.get(c.id);
222
- if (!s)
223
- continue;
224
- const fr = finalReports.get(c.id);
225
- const mockRun = recordedMockRun(s, c);
226
- const failoverFrom = c.failoverFrom ?? s.failoverFrom;
227
- const failoverTo = c.failoverTo ?? s.failoverTo;
228
- const failoverReason = c.failoverReason ?? s.failoverReason;
229
- return {
230
- sessionId: s.id,
231
- agent: s.agent,
232
- prompt: s.prompt,
233
- cwd: s.cwd,
234
- startedAt: s.startedAt,
235
- status: c.status === 'running' && c.completedAt > 0 ? 'completed' : c.status,
236
- durationMs: c.durationMs,
237
- completedAt: c.completedAt,
238
- steps: c.steps,
239
- artifacts: c.artifacts,
240
- ...(c.turns ? { turns: c.turns } : {}),
241
- ...(fr
242
- ? {
243
- finalReport: {
244
- summary: fr.summary,
245
- generatedAt: fr.generatedAt,
246
- durationMs: fr.durationMs,
247
- source: fr.source,
248
- },
249
- }
250
- : {}),
251
- ...(failoverFrom ? { failoverFrom } : {}),
252
- ...(failoverTo ? { failoverTo } : {}),
253
- ...(failoverReason ? { failoverReason } : {}),
254
- ...(s.failoverAt ? { failoverAt: s.failoverAt } : {}),
255
- ...(c.stalledReason ? { stalledReason: c.stalledReason } : {}),
256
- ...(s.batchId ? { batchId: s.batchId } : {}),
257
- ...(c.delegatedTo ? { delegatedTo: c.delegatedTo } : {}),
258
- ...(c.nativeSessionId ? { nativeSessionId: c.nativeSessionId } : {}),
259
- ...(c.delegateSessionId ? { delegateSessionId: c.delegateSessionId } : {}),
260
- ...(auditRecons.get(c.id) ? { auditRecon: auditRecons.get(c.id) } : {}),
261
- ...(s.importedFrom ? { importedFrom: s.importedFrom } : {}),
262
- ...(s.importAudit ? { importAudit: s.importAudit } : {}),
263
- ...(s.parentSessionId ? { parentSessionId: s.parentSessionId } : {}),
264
- ...(s.isReview ? { isReview: true } : {}),
265
- ...(resolved.has(c.id) ? { resolvedAt: resolved.get(c.id) } : {}),
266
- ...(mockRun !== undefined ? { mockRun } : {}),
267
- provenance: 'persisted',
268
- };
269
- }
270
- return undefined;
271
- }
272
- export function loadRecent(limit = 20) {
273
- const file = sessionsFile();
274
- if (!existsSync(file))
275
- return [];
276
- let content;
277
- try {
278
- content = readFileSync(file, 'utf-8');
279
- }
280
- catch {
281
- return [];
282
- }
283
- const spawned = new Map();
284
- const completed = [];
285
- /** v0.6.x — Final Reports keyed by sessionId. Last write wins
286
- * (rare in practice; reports fire once per session). */
287
- const finalReports = new Map();
288
- const resolved = new Map();
289
- const auditRecons = new Map();
290
- for (const line of content.split('\n')) {
291
- const trimmed = line.trim();
292
- if (!trimmed)
293
- continue;
294
- let rec;
295
- try {
296
- rec = JSON.parse(trimmed);
297
- }
298
- catch {
299
- continue;
300
- }
301
- if (rec.t === 'spawned')
302
- spawned.set(rec.id, rec);
303
- else if (rec.t === 'completed')
304
- completed.push(rec);
305
- else if (rec.t === 'finalReport')
306
- finalReports.set(rec.id, rec);
307
- else if (rec.t === 'resolved')
308
- resolved.set(rec.id, rec.resolvedAt);
309
- else if (rec.t === 'audit-recon')
310
- auditRecons.set(rec.id, rec.recon);
311
- }
312
- const merged = [];
313
- const includedIds = new Set();
314
- const build = (c, s, fr) => {
315
- const mockRun = recordedMockRun(s, c);
316
- return {
317
- sessionId: s.id,
318
- agent: s.agent,
319
- prompt: s.prompt,
320
- cwd: s.cwd,
321
- startedAt: s.startedAt,
322
- // BUG 2 (May 1 2026) — defensive normalization for zombie records.
323
- // Pre-fix sessions persisted before commit <THIS> were written with
324
- // status='running' even when their completed-record carried real
325
- // durationMs/completedAt — the conductor adapter mirrored its status
326
- // AFTER emitting the exit event. The fix at the source (conductor-
327
- // adapter.ts) prevents new zombies; this normalization repairs old
328
- // records on read so the UI doesn't show a "running" badge on a
329
- // session that obviously finished. We have no ground truth on what
330
- // the actual exit code WAS for these old records, so 'completed' is
331
- // the safest default — they'd been displayed as completed in the UI
332
- // had this been a session-manager.ts bug. Mark explicitly via a
333
- // synthetic note so debug viewers can tell normalization happened.
334
- status: c.status === 'running' && c.completedAt > 0 ? 'completed' : c.status,
335
- durationMs: c.durationMs,
336
- completedAt: c.completedAt,
337
- steps: c.steps,
338
- artifacts: c.artifacts,
339
- ...(c.turns ? { turns: c.turns } : {}),
340
- ...(fr
341
- ? {
342
- finalReport: {
343
- summary: fr.summary,
344
- generatedAt: fr.generatedAt,
345
- durationMs: fr.durationMs,
346
- source: fr.source,
347
- },
348
- }
349
- : {}),
350
- ...((c.failoverFrom ?? s.failoverFrom) ? { failoverFrom: c.failoverFrom ?? s.failoverFrom } : {}),
351
- ...((c.failoverTo ?? s.failoverTo) ? { failoverTo: c.failoverTo ?? s.failoverTo } : {}),
352
- ...((c.failoverReason ?? s.failoverReason) ? { failoverReason: c.failoverReason ?? s.failoverReason } : {}),
353
- ...(s.failoverAt ? { failoverAt: s.failoverAt } : {}),
354
- ...(c.stalledReason ? { stalledReason: c.stalledReason } : {}),
355
- ...(s.batchId ? { batchId: s.batchId } : {}),
356
- ...(c.delegatedTo ? { delegatedTo: c.delegatedTo } : {}),
357
- ...(c.nativeSessionId ? { nativeSessionId: c.nativeSessionId } : {}),
358
- ...(c.delegateSessionId ? { delegateSessionId: c.delegateSessionId } : {}),
359
- ...(auditRecons.get(c.id) ? { auditRecon: auditRecons.get(c.id) } : {}),
360
- ...(s.importedFrom ? { importedFrom: s.importedFrom } : {}),
361
- ...(s.importAudit ? { importAudit: s.importAudit } : {}),
362
- ...(s.parentSessionId ? { parentSessionId: s.parentSessionId } : {}),
363
- ...(s.isReview ? { isReview: true } : {}),
364
- ...(resolved.has(c.id) ? { resolvedAt: resolved.get(c.id) } : {}),
365
- ...(mockRun !== undefined ? { mockRun } : {}),
366
- provenance: 'persisted',
367
- };
368
- };
369
- // Walk completed in reverse so we return newest-first, up to `limit`.
370
- for (let i = completed.length - 1; i >= 0 && merged.length < limit; i--) {
371
- const c = completed[i];
372
- const s = spawned.get(c.id);
373
- if (!s || includedIds.has(c.id))
374
- continue; // orphan / dup (harmless)
375
- merged.push(build(c, s, finalReports.get(c.id)));
376
- includedIds.add(c.id);
377
- }
378
- // Always include seeded demo-* sessions even past the recent-N cap. They're
379
- // pinned to FEATURED in the dashboard and are the fixtures /verify observes —
380
- // dropping them off the list once you have >limit real runs is a bug (it's
381
- // what made /verify report seeded fixtures as "not rendered"). Newest-first.
382
- for (let i = completed.length - 1; i >= 0; i--) {
383
- const c = completed[i];
384
- if (!c.id.startsWith('demo-') || includedIds.has(c.id))
385
- continue;
386
- const s = spawned.get(c.id);
387
- if (!s)
388
- continue;
389
- merged.push(build(c, s, finalReports.get(c.id)));
390
- includedIds.add(c.id);
391
- }
392
- return merged;
393
- }
394
- //# sourceMappingURL=session-persistence.js.map
@@ -1,127 +0,0 @@
1
- /**
2
- * Thread-export key storage · v0.8 S4.
3
- *
4
- * Two responsibilities:
5
- * 1. Per-machine Ed25519 keypair · generated on first export · persisted
6
- * at ~/.codehere/keys/thread-signing.{pem,pub.pem}. Used to SIGN
7
- * bundles. 0600 on private file.
8
- * 2. Trusted-keys registry · ~/.codehere/keys/trusted.json maps base64
9
- * public keys to { label, addedAt }. When importing a bundle whose
10
- * key is in the trust store, replay proceeds silently. Otherwise the
11
- * caller (CLI or web UI) prompts the user to trust + continue.
12
- *
13
- * Both files live ENTIRELY on the user's machine. Codehere never sends
14
- * keys anywhere. The trust model is "users trust their friends' keys" ·
15
- * same shape as SSH known_hosts.
16
- */
17
- import { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';
18
- import { homedir } from 'os';
19
- import { join } from 'path';
20
- import { generateKeypair, exportPrivatePem, exportPublicPem, importPrivatePem, importPublicPem, publicKeyToBase64, } from '../../application/use-cases/thread-export.js';
21
- export function keysDir() {
22
- const home = process.env.CODEHERE_HOME || homedir();
23
- return join(home, '.codehere', 'keys');
24
- }
25
- function privPath() {
26
- return join(keysDir(), 'thread-signing.pem');
27
- }
28
- function pubPath() {
29
- return join(keysDir(), 'thread-signing.pub.pem');
30
- }
31
- function trustedPath() {
32
- return join(keysDir(), 'trusted.json');
33
- }
34
- /**
35
- * Load (or generate) the local signing keypair. First call creates a new
36
- * one and persists it; subsequent calls re-read from disk.
37
- */
38
- export function loadOrCreateKeypair() {
39
- mkdirSync(keysDir(), { recursive: true });
40
- if (existsSync(privPath()) && existsSync(pubPath())) {
41
- const priv = readFileSync(privPath(), 'utf-8');
42
- const pub = readFileSync(pubPath(), 'utf-8');
43
- return {
44
- privateKey: importPrivatePem(priv),
45
- publicKey: importPublicPem(pub),
46
- };
47
- }
48
- const fresh = generateKeypair();
49
- writeFileSync(privPath(), exportPrivatePem(fresh.privateKey), 'utf-8');
50
- writeFileSync(pubPath(), exportPublicPem(fresh.publicKey), 'utf-8');
51
- try {
52
- chmodSync(privPath(), 0o600);
53
- }
54
- catch { }
55
- return fresh;
56
- }
57
- /**
58
- * Return the local key's base64 string (used in bundle equality + UI display).
59
- * Doesn't generate · only reads. Returns null if no key exists yet.
60
- */
61
- export function localPublicKeyBase64() {
62
- if (!existsSync(pubPath()))
63
- return null;
64
- try {
65
- const pub = readFileSync(pubPath(), 'utf-8');
66
- return publicKeyToBase64(importPublicPem(pub));
67
- }
68
- catch {
69
- return null;
70
- }
71
- }
72
- export function loadTrustedKeys() {
73
- const path = trustedPath();
74
- const local = localPublicKeyBase64();
75
- const seed = { schema: 1, keys: {} };
76
- if (local)
77
- seed.keys[local] = { label: 'this machine', addedAt: Date.now() };
78
- if (!existsSync(path))
79
- return seed;
80
- try {
81
- const raw = JSON.parse(readFileSync(path, 'utf-8'));
82
- if (raw && typeof raw === 'object' && raw.schema === 1 && typeof raw.keys === 'object') {
83
- // Always include the local key implicitly even if the trusted file
84
- // doesn't list it (avoids "can't import my own export" if file is
85
- // wiped or corrupted).
86
- if (local && !raw.keys[local]) {
87
- raw.keys[local] = { label: 'this machine', addedAt: Date.now() };
88
- }
89
- return raw;
90
- }
91
- }
92
- catch {
93
- /* fall through to seed */
94
- }
95
- return seed;
96
- }
97
- export function saveTrustedKeys(file) {
98
- mkdirSync(keysDir(), { recursive: true });
99
- writeFileSync(trustedPath(), JSON.stringify(file, null, 2) + '\n', 'utf-8');
100
- try {
101
- chmodSync(trustedPath(), 0o600);
102
- }
103
- catch { }
104
- }
105
- export function isKeyTrusted(publicKeyB64) {
106
- return !!loadTrustedKeys().keys[publicKeyB64];
107
- }
108
- export function trustKey(publicKeyB64, label) {
109
- const file = loadTrustedKeys();
110
- file.keys[publicKeyB64] = { label, addedAt: Date.now() };
111
- saveTrustedKeys(file);
112
- }
113
- export function untrustKey(publicKeyB64) {
114
- const file = loadTrustedKeys();
115
- const local = localPublicKeyBase64();
116
- if (local && publicKeyB64 === local)
117
- return false; // can't untrust own key
118
- if (!file.keys[publicKeyB64])
119
- return false;
120
- delete file.keys[publicKeyB64];
121
- saveTrustedKeys(file);
122
- return true;
123
- }
124
- export function trustedKeySet() {
125
- return new Set(Object.keys(loadTrustedKeys().keys));
126
- }
127
- //# sourceMappingURL=thread-key-store.js.map
@@ -1,38 +0,0 @@
1
- /**
2
- * Write-pass store · v0.9 S6 part B · in-memory holding tank for pending
3
- * write-passes. Lives only in the daemon process · cleared on restart by
4
- * design. The audit chain is the durable record · this store just holds
5
- * the intent between emission and decision so the UI can poll it.
6
- *
7
- * Scope decisions:
8
- * - In-memory only. A pending write that survives a daemon restart is
9
- * a half-thought · the agent should re-emit. Persistence would invite
10
- * replay attacks (an old approval against a payload the user no longer
11
- * remembers).
12
- * - Per-id Map. O(1) get/set. No iteration ordering guarantees · the
13
- * UI lists them by intent.emittedAt for display.
14
- * - Status transitions go through applyDecision / applyResponse in
15
- * write-pass.ts so the store stays dumb · it's just a typed Map.
16
- */
17
- const store = new Map();
18
- /** Place a freshly-validated intent in the store as 'pending'. */
19
- export function putPending(intent) {
20
- const status = { kind: 'pending', intent };
21
- store.set(intent.id, status);
22
- return status;
23
- }
24
- export function getById(passId) {
25
- return store.get(passId);
26
- }
27
- export function setStatus(status) {
28
- store.set(status.intent.id, status);
29
- }
30
- export function listPending() {
31
- return [...store.values()].filter((s) => s.kind === 'pending');
32
- }
33
- export function listForSession(sessionId) {
34
- return [...store.values()]
35
- .filter((s) => s.intent.sessionId === sessionId)
36
- .sort((a, b) => a.intent.emittedAt - b.intent.emittedAt);
37
- }
38
- //# sourceMappingURL=write-pass-store.js.map