codehere 0.8.0 → 1.0.2

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 (119) hide show
  1. package/README.md +37 -184
  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 +329 -0
  5. package/dist/index.js +74 -1004
  6. package/dist/infrastructure/orchestration/artifacts-extractor.js +2 -265
  7. package/dist/presentation/cli/commands/check-command.js +251 -0
  8. package/dist/presentation/cli/commands/help-command.js +22 -286
  9. package/dist/ui.js +19 -438
  10. package/dist/utils/version.js +0 -12
  11. package/package.json +18 -27
  12. package/dist/application/use-cases/domain-trust.js +0 -110
  13. package/dist/application/use-cases/generate-final-report.js +0 -134
  14. package/dist/application/use-cases/generate-narration.js +0 -128
  15. package/dist/application/use-cases/generate-thread-title.js +0 -178
  16. package/dist/application/use-cases/narrator-prompt-template.js +0 -158
  17. package/dist/application/use-cases/thread-export.js +0 -155
  18. package/dist/application/use-cases/verify-claim.js +0 -274
  19. package/dist/application/use-cases/write-pass.js +0 -277
  20. package/dist/domain/entities/task.js +0 -61
  21. package/dist/domain/services/codebase-detector.js +0 -91
  22. package/dist/error-handler.js +0 -251
  23. package/dist/infrastructure/completion/bash-completion.js +0 -25
  24. package/dist/infrastructure/completion/fish-completion.js +0 -22
  25. package/dist/infrastructure/completion/zsh-completion.js +0 -30
  26. package/dist/infrastructure/config/config-manager.js +0 -173
  27. package/dist/infrastructure/config/product-context.js +0 -67
  28. package/dist/infrastructure/context/adapters/drawio-source.js +0 -157
  29. package/dist/infrastructure/context/adapters/figma-source.js +0 -163
  30. package/dist/infrastructure/context/adapters/github-issue-source.js +0 -159
  31. package/dist/infrastructure/context/adapters/linear-source.js +0 -190
  32. package/dist/infrastructure/context/context-resolver.js +0 -144
  33. package/dist/infrastructure/context/context-source.js +0 -114
  34. package/dist/infrastructure/context/oauth-flow.js +0 -207
  35. package/dist/infrastructure/context/oauth-store.js +0 -119
  36. package/dist/infrastructure/context/registry-bootstrap.js +0 -99
  37. package/dist/infrastructure/context/write-pass-resolver.js +0 -106
  38. package/dist/infrastructure/cost/budget-config.js +0 -113
  39. package/dist/infrastructure/cost/cost-router.js +0 -104
  40. package/dist/infrastructure/cost/spend-aggregator.js +0 -147
  41. package/dist/infrastructure/hooks/claude-sdk-hook.js +0 -888
  42. package/dist/infrastructure/hooks/hook-mode.js +0 -71
  43. package/dist/infrastructure/hooks/mirror-tool-call-to-audit.js +0 -43
  44. package/dist/infrastructure/hooks/pre-prompt-scan.js +0 -122
  45. package/dist/infrastructure/hooks/session-analyzer.js +0 -177
  46. package/dist/infrastructure/observability/execution-tracer.js +0 -227
  47. package/dist/infrastructure/orchestration/agent-adapter.js +0 -13
  48. package/dist/infrastructure/orchestration/agent-installer.js +0 -101
  49. package/dist/infrastructure/orchestration/agents-config-seed.js +0 -56
  50. package/dist/infrastructure/orchestration/aider-adapter.js +0 -447
  51. package/dist/infrastructure/orchestration/cassette-recorder.js +0 -120
  52. package/dist/infrastructure/orchestration/claude-code-adapter.js +0 -478
  53. package/dist/infrastructure/orchestration/codex-adapter.js +0 -538
  54. package/dist/infrastructure/orchestration/conductor-adapter.js +0 -377
  55. package/dist/infrastructure/orchestration/context-handoff.js +0 -144
  56. package/dist/infrastructure/orchestration/control/codex-control-surface.js +0 -99
  57. package/dist/infrastructure/orchestration/control/control-surface.js +0 -15
  58. package/dist/infrastructure/orchestration/failover-classifier.js +0 -151
  59. package/dist/infrastructure/orchestration/failover-config.js +0 -77
  60. package/dist/infrastructure/orchestration/failover-policy.js +0 -106
  61. package/dist/infrastructure/orchestration/gemini-adapter.js +0 -583
  62. package/dist/infrastructure/orchestration/generic-cli-adapter.js +0 -558
  63. package/dist/infrastructure/orchestration/graphify-context.js +0 -254
  64. package/dist/infrastructure/orchestration/memory-injector.js +0 -380
  65. package/dist/infrastructure/orchestration/mocks/mock-agent-repl.js +0 -233
  66. package/dist/infrastructure/orchestration/mocks/mock-codex-app-server.js +0 -73
  67. package/dist/infrastructure/orchestration/mocks/mock-synthesis.js +0 -83
  68. package/dist/infrastructure/orchestration/mocks/resolve-agent-spawn.js +0 -37
  69. package/dist/infrastructure/orchestration/opencode-adapter.js +0 -510
  70. package/dist/infrastructure/orchestration/progress-aggregator.js +0 -689
  71. package/dist/infrastructure/orchestration/readiness.js +0 -103
  72. package/dist/infrastructure/orchestration/session-manager.js +0 -1091
  73. package/dist/infrastructure/orchestration/session-persistence.js +0 -258
  74. package/dist/infrastructure/orchestration/thread-key-store.js +0 -127
  75. package/dist/infrastructure/orchestration/write-pass-store.js +0 -46
  76. package/dist/infrastructure/protocols/cloud-sync.js +0 -207
  77. package/dist/infrastructure/protocols/mcp-helpers.js +0 -25
  78. package/dist/infrastructure/protocols/mcp-server-impl.js +0 -2853
  79. package/dist/infrastructure/protocols/probe-port.js +0 -36
  80. package/dist/infrastructure/protocols/thread-phase.js +0 -105
  81. package/dist/infrastructure/protocols/ui-page-v2.js +0 -4418
  82. package/dist/infrastructure/protocols/ui-page.js +0 -2377
  83. package/dist/infrastructure/protocols/verify-page.js +0 -983
  84. package/dist/infrastructure/security/file-collection.js +0 -118
  85. package/dist/infrastructure/security/input-validator.js +0 -152
  86. package/dist/infrastructure/security/permissions-config.js +0 -234
  87. package/dist/infrastructure/security/trust-scorer.js +0 -657
  88. package/dist/infrastructure/storage/cross-tool-agents.js +0 -268
  89. package/dist/infrastructure/storage/embeddings-path.js +0 -37
  90. package/dist/infrastructure/storage/project-thread-migration.js +0 -237
  91. package/dist/infrastructure/storage/project-thread-mirror.js +0 -524
  92. package/dist/infrastructure/storage/project-thread-reader.js +0 -172
  93. package/dist/infrastructure/storage/project-thread-title-backfill.js +0 -214
  94. package/dist/infrastructure/storage/task-repository.js +0 -206
  95. package/dist/monitoring.js +0 -212
  96. package/dist/presentation/cli/commands/audit-command.js +0 -126
  97. package/dist/presentation/cli/commands/budget-command.js +0 -79
  98. package/dist/presentation/cli/commands/config-command.js +0 -125
  99. package/dist/presentation/cli/commands/context-source-command.js +0 -198
  100. package/dist/presentation/cli/commands/hook-command.js +0 -265
  101. package/dist/presentation/cli/commands/init-command.js +0 -578
  102. package/dist/presentation/cli/commands/memory-command.js +0 -1031
  103. package/dist/presentation/cli/commands/narrator-command.js +0 -101
  104. package/dist/presentation/cli/commands/permission-command.js +0 -116
  105. package/dist/presentation/cli/commands/protocols-builder.js +0 -254
  106. package/dist/presentation/cli/commands/protocols-command.js +0 -84
  107. package/dist/presentation/cli/commands/repo-map-builder.js +0 -247
  108. package/dist/presentation/cli/commands/report-command.js +0 -387
  109. package/dist/presentation/cli/commands/scan-tool-command.js +0 -94
  110. package/dist/presentation/cli/commands/setup-command.js +0 -307
  111. package/dist/presentation/cli/commands/status-command.js +0 -661
  112. package/dist/presentation/cli/commands/thread-command.js +0 -288
  113. package/dist/presentation/cli/commands/trust-command.js +0 -306
  114. package/dist/presentation/cli/commands/verify-command.js +0 -233
  115. package/dist/presentation/cli/error-display.js +0 -336
  116. package/dist/presentation/cli/repl.js +0 -797
  117. package/dist/presentation/cli/task-deliverable.js +0 -127
  118. package/dist/testing/cassette.js +0 -125
  119. package/dist/testing/replay-adapter.js +0 -166
@@ -1,258 +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
- function ensureDir() {
31
- const dir = dirname(sessionsFile());
32
- if (!existsSync(dir))
33
- mkdirSync(dir, { recursive: true });
34
- }
35
- export function appendSpawned(rec) {
36
- try {
37
- ensureDir();
38
- appendFileSync(sessionsFile(), JSON.stringify({ t: 'spawned', ...rec }) + '\n', 'utf-8');
39
- }
40
- catch {
41
- /* persistence is best-effort; never break the spawn path */
42
- }
43
- }
44
- export function appendCompleted(rec) {
45
- try {
46
- ensureDir();
47
- appendFileSync(sessionsFile(), JSON.stringify({ t: 'completed', ...rec }) + '\n', 'utf-8');
48
- }
49
- catch {
50
- /* best-effort */
51
- }
52
- }
53
- /**
54
- * v0.6.x — persist a Final Report so it survives daemon restart.
55
- *
56
- * Pre-fix: Final Reports lived only in SessionManager.finalReportBySession
57
- * (in-memory Map) and were lost on every daemon bounce. Founder hit this
58
- * 2026-05-03 after a Mac restart: completed sessions showed
59
- * "Generating report…" forever because there was no on-disk record + no
60
- * "report-unavailable" state in the UI.
61
- *
62
- * Append-only: Final Report fires AFTER the CompletedRecord (generateFinalReport
63
- * is async post-exit), so it gets its own record type. Reader merges by id.
64
- */
65
- export function appendFinalReport(rec) {
66
- try {
67
- ensureDir();
68
- appendFileSync(sessionsFile(), JSON.stringify({ t: 'finalReport', ...rec }) + '\n', 'utf-8');
69
- }
70
- catch {
71
- /* best-effort */
72
- }
73
- }
74
- /**
75
- * Walk the session log backward, pair spawned+completed records by id, return
76
- * the most recent `limit` completed pairs sorted newest-first.
77
- *
78
- * We only surface fully-completed sessions because an orphaned `spawned` with
79
- * no `completed` means either: (a) the daemon died mid-task (subprocess also
80
- * died, no artifacts), or (b) a concurrent daemon is mid-flight (not ours to
81
- * show). Either way, not useful in the UI.
82
- */
83
- /**
84
- * v0.7 — load a single completed session by id. Returns undefined if no
85
- * matching session exists or if the session is orphaned (spawned but never
86
- * completed). Used by `codehere verify <session-id>` (F-03 S2) and the
87
- * upcoming `codehere audit verify` CLI (F-05).
88
- *
89
- * Walks the whole file once. For 1k+ sessions this is fine; if it ever
90
- * becomes hot we can keep an in-memory index.
91
- *
92
- * Accepts a short-id prefix too (first 7+ chars), mirroring git's UX.
93
- * If the prefix is ambiguous we return the most recent match (newest-first
94
- * traversal). Returning undefined on ambiguity would surprise users at
95
- * the CLI · git's `--abbrev-commit` collisions are very rare in practice
96
- * for our id space.
97
- */
98
- export function loadSessionById(idOrPrefix) {
99
- const file = sessionsFile();
100
- if (!existsSync(file))
101
- return undefined;
102
- let content;
103
- try {
104
- content = readFileSync(file, 'utf-8');
105
- }
106
- catch {
107
- return undefined;
108
- }
109
- const spawned = new Map();
110
- const completed = [];
111
- const finalReports = new Map();
112
- for (const line of content.split('\n')) {
113
- const trimmed = line.trim();
114
- if (!trimmed)
115
- continue;
116
- let rec;
117
- try {
118
- rec = JSON.parse(trimmed);
119
- }
120
- catch {
121
- continue;
122
- }
123
- if (rec.t === 'spawned')
124
- spawned.set(rec.id, rec);
125
- else if (rec.t === 'completed')
126
- completed.push(rec);
127
- else if (rec.t === 'finalReport')
128
- finalReports.set(rec.id, rec);
129
- }
130
- // Newest-first to break prefix-collision ties toward the most recent
131
- for (let i = completed.length - 1; i >= 0; i--) {
132
- const c = completed[i];
133
- if (c.id !== idOrPrefix && !c.id.startsWith(idOrPrefix))
134
- continue;
135
- const s = spawned.get(c.id);
136
- if (!s)
137
- continue;
138
- const fr = finalReports.get(c.id);
139
- const failoverFrom = c.failoverFrom ?? s.failoverFrom;
140
- const failoverTo = c.failoverTo ?? s.failoverTo;
141
- const failoverReason = c.failoverReason ?? s.failoverReason;
142
- return {
143
- sessionId: s.id,
144
- agent: s.agent,
145
- prompt: s.prompt,
146
- cwd: s.cwd,
147
- startedAt: s.startedAt,
148
- status: c.status === 'running' && c.completedAt > 0 ? 'completed' : c.status,
149
- durationMs: c.durationMs,
150
- completedAt: c.completedAt,
151
- steps: c.steps,
152
- artifacts: c.artifacts,
153
- ...(c.turns ? { turns: c.turns } : {}),
154
- ...(fr ? { finalReport: { summary: fr.summary, generatedAt: fr.generatedAt, durationMs: fr.durationMs, source: fr.source } } : {}),
155
- ...(failoverFrom ? { failoverFrom } : {}),
156
- ...(failoverTo ? { failoverTo } : {}),
157
- ...(failoverReason ? { failoverReason } : {}),
158
- ...(s.failoverAt ? { failoverAt: s.failoverAt } : {}),
159
- ...(c.stalledReason ? { stalledReason: c.stalledReason } : {}),
160
- provenance: 'persisted',
161
- };
162
- }
163
- return undefined;
164
- }
165
- export function loadRecent(limit = 20) {
166
- const file = sessionsFile();
167
- if (!existsSync(file))
168
- return [];
169
- let content;
170
- try {
171
- content = readFileSync(file, 'utf-8');
172
- }
173
- catch {
174
- return [];
175
- }
176
- const spawned = new Map();
177
- const completed = [];
178
- /** v0.6.x — Final Reports keyed by sessionId. Last write wins
179
- * (rare in practice; reports fire once per session). */
180
- const finalReports = new Map();
181
- for (const line of content.split('\n')) {
182
- const trimmed = line.trim();
183
- if (!trimmed)
184
- continue;
185
- let rec;
186
- try {
187
- rec = JSON.parse(trimmed);
188
- }
189
- catch {
190
- continue;
191
- }
192
- if (rec.t === 'spawned')
193
- spawned.set(rec.id, rec);
194
- else if (rec.t === 'completed')
195
- completed.push(rec);
196
- else if (rec.t === 'finalReport')
197
- finalReports.set(rec.id, rec);
198
- }
199
- const merged = [];
200
- const includedIds = new Set();
201
- const build = (c, s, fr) => ({
202
- sessionId: s.id,
203
- agent: s.agent,
204
- prompt: s.prompt,
205
- cwd: s.cwd,
206
- startedAt: s.startedAt,
207
- // BUG 2 (May 1 2026) — defensive normalization for zombie records.
208
- // Pre-fix sessions persisted before commit <THIS> were written with
209
- // status='running' even when their completed-record carried real
210
- // durationMs/completedAt — the conductor adapter mirrored its status
211
- // AFTER emitting the exit event. The fix at the source (conductor-
212
- // adapter.ts) prevents new zombies; this normalization repairs old
213
- // records on read so the UI doesn't show a "running" badge on a
214
- // session that obviously finished. We have no ground truth on what
215
- // the actual exit code WAS for these old records, so 'completed' is
216
- // the safest default — they'd been displayed as completed in the UI
217
- // had this been a session-manager.ts bug. Mark explicitly via a
218
- // synthetic note so debug viewers can tell normalization happened.
219
- status: c.status === 'running' && c.completedAt > 0 ? 'completed' : c.status,
220
- durationMs: c.durationMs,
221
- completedAt: c.completedAt,
222
- steps: c.steps,
223
- artifacts: c.artifacts,
224
- ...(c.turns ? { turns: c.turns } : {}),
225
- ...(fr ? { finalReport: { summary: fr.summary, generatedAt: fr.generatedAt, durationMs: fr.durationMs, source: fr.source } } : {}),
226
- ...((c.failoverFrom ?? s.failoverFrom) ? { failoverFrom: c.failoverFrom ?? s.failoverFrom } : {}),
227
- ...((c.failoverTo ?? s.failoverTo) ? { failoverTo: c.failoverTo ?? s.failoverTo } : {}),
228
- ...((c.failoverReason ?? s.failoverReason) ? { failoverReason: c.failoverReason ?? s.failoverReason } : {}),
229
- ...(s.failoverAt ? { failoverAt: s.failoverAt } : {}),
230
- ...(c.stalledReason ? { stalledReason: c.stalledReason } : {}),
231
- provenance: 'persisted',
232
- });
233
- // Walk completed in reverse so we return newest-first, up to `limit`.
234
- for (let i = completed.length - 1; i >= 0 && merged.length < limit; i--) {
235
- const c = completed[i];
236
- const s = spawned.get(c.id);
237
- if (!s || includedIds.has(c.id))
238
- continue; // orphan / dup (harmless)
239
- merged.push(build(c, s, finalReports.get(c.id)));
240
- includedIds.add(c.id);
241
- }
242
- // Always include seeded demo-* sessions even past the recent-N cap. They're
243
- // pinned to FEATURED in the dashboard and are the fixtures /verify observes —
244
- // dropping them off the list once you have >limit real runs is a bug (it's
245
- // what made /verify report seeded fixtures as "not rendered"). Newest-first.
246
- for (let i = completed.length - 1; i >= 0; i--) {
247
- const c = completed[i];
248
- if (!c.id.startsWith('demo-') || includedIds.has(c.id))
249
- continue;
250
- const s = spawned.get(c.id);
251
- if (!s)
252
- continue;
253
- merged.push(build(c, s, finalReports.get(c.id)));
254
- includedIds.add(c.id);
255
- }
256
- return merged;
257
- }
258
- //# 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
- 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,46 +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
- /** Drop a pass · for tests + the "discard" admin action. */
39
- export function discard(passId) {
40
- return store.delete(passId);
41
- }
42
- /** Test-only · clear the store between cases. */
43
- export function _resetForTests() {
44
- store.clear();
45
- }
46
- //# sourceMappingURL=write-pass-store.js.map
@@ -1,207 +0,0 @@
1
- /**
2
- * Cloud sync bridge for codehere remote dashboard.
3
- *
4
- * Pushes session state to codehere.uk/api/sync every 30s.
5
- * Pulls task assignments from cloud.
6
- *
7
- * What leaves the machine:
8
- * - Session metadata (id, title, status, agent, project, tags, duration, tools, files, trustScore)
9
- * - Machine identity (name, token hash)
10
- *
11
- * What stays local:
12
- * - Code, file contents, API keys, git repos, full conversation threads
13
- */
14
- import { randomUUID } from 'crypto';
15
- import { readFileSync, existsSync, writeFileSync, mkdirSync } from 'fs';
16
- import { join, dirname } from 'path';
17
- import { homedir, hostname } from 'os';
18
- const CLOUD_URL = 'https://codehere.uk/api/sync';
19
- const TOKEN_PATH = join(process.env.CODEHERE_HOME || homedir(), '.codehere', 'cloud-token');
20
- const SESSIONS_FILE = join(process.env.CODEHERE_HOME || homedir(), '.codehere', 'sessions.jsonl');
21
- // Lazy-loaded session manager — imported once to avoid circular deps
22
- let _sessionManager = null;
23
- async function getSessionManager() {
24
- if (!_sessionManager) {
25
- const { SessionManager } = await import('../orchestration/session-manager.js');
26
- _sessionManager = SessionManager.getInstance();
27
- }
28
- return _sessionManager;
29
- }
30
- // ── Token management ─────────────────────────────────────────────────────────
31
- export function getOrCreateToken() {
32
- if (existsSync(TOKEN_PATH)) {
33
- return readFileSync(TOKEN_PATH, 'utf-8').trim();
34
- }
35
- const token = randomUUID();
36
- const dir = dirname(TOKEN_PATH);
37
- if (!existsSync(dir)) {
38
- mkdirSync(dir, { recursive: true });
39
- }
40
- writeFileSync(TOKEN_PATH, token);
41
- return token;
42
- }
43
- function formatDuration(ms) {
44
- const seconds = Math.floor(ms / 1000);
45
- if (seconds < 60)
46
- return `${seconds}s`;
47
- const minutes = Math.floor(seconds / 60);
48
- const remainingSeconds = seconds % 60;
49
- return `${minutes}m ${remainingSeconds}s`;
50
- }
51
- function extractProjectFromCwd(cwd) {
52
- // Extract project name from cwd path (last directory component)
53
- const parts = cwd.split('/').filter(Boolean);
54
- return parts[parts.length - 1] || 'unknown';
55
- }
56
- function extractTrustScore(artifacts) {
57
- return artifacts?.trustScore;
58
- }
59
- export function collectSessionMetadata() {
60
- if (!existsSync(SESSIONS_FILE)) {
61
- return [];
62
- }
63
- try {
64
- const content = readFileSync(SESSIONS_FILE, 'utf-8');
65
- const lines = content.split('\n').filter((l) => l.trim().length > 0);
66
- // Parse records and pair spawned + completed
67
- const spawnedMap = new Map();
68
- const completedMap = new Map();
69
- for (const line of lines) {
70
- try {
71
- const record = JSON.parse(line);
72
- if (record.t === 'spawned') {
73
- spawnedMap.set(record.id, record);
74
- }
75
- else if (record.t === 'completed') {
76
- completedMap.set(record.id, record);
77
- }
78
- }
79
- catch {
80
- // Skip malformed lines
81
- }
82
- }
83
- const sessions = [];
84
- for (const [id, spawned] of spawnedMap.entries()) {
85
- const completed = completedMap.get(id);
86
- const status = completed
87
- ? completed.status === 'completed'
88
- ? 'done'
89
- : completed.status === 'failed'
90
- ? 'failed'
91
- : 'done'
92
- : 'running';
93
- const durationMs = completed?.durationMs ?? Date.now() - spawned.startedAt;
94
- const tools = completed?.steps?.length ?? 0;
95
- const files = completed?.artifacts?.filesChanged?.length ?? 0;
96
- const trustScore = extractTrustScore(completed?.artifacts);
97
- // Extract title from prompt (first line or first 80 chars)
98
- const title = spawned.prompt.split('\n')[0].slice(0, 80) || 'Untitled task';
99
- sessions.push({
100
- id,
101
- title,
102
- status,
103
- agent: spawned.agent || 'Unknown',
104
- project: extractProjectFromCwd(spawned.cwd || ''),
105
- tags: [], // Tags not yet implemented in session schema
106
- duration: formatDuration(durationMs),
107
- tools,
108
- files,
109
- ...(trustScore !== undefined ? { trustScore } : {}),
110
- });
111
- }
112
- return sessions.sort((a, b) => {
113
- // Running first, then by duration descending
114
- if (a.status === 'running' && b.status !== 'running')
115
- return -1;
116
- if (a.status !== 'running' && b.status === 'running')
117
- return 1;
118
- return 0;
119
- });
120
- }
121
- catch {
122
- return [];
123
- }
124
- }
125
- // ── Sync loop ────────────────────────────────────────────────────────────────
126
- let syncInterval = null;
127
- export function startCloudSync(machineName) {
128
- const token = getOrCreateToken();
129
- // Use hostname + first 8 chars of token as machine ID — unique per machine
130
- const machineId = machineName || `${hostname()}-${token.slice(0, 8)}`;
131
- console.log(` Cloud sync enabled — pushing to codehere.uk/app`);
132
- console.log(` Machine: ${machineId}`);
133
- console.log(` Token: ${TOKEN_PATH}`);
134
- const push = async () => {
135
- try {
136
- const sessions = collectSessionMetadata();
137
- const res = await fetch(CLOUD_URL, {
138
- method: 'POST',
139
- headers: { 'Content-Type': 'application/json' },
140
- body: JSON.stringify({
141
- machineId,
142
- name: machineId,
143
- token,
144
- sessions,
145
- timestamp: Date.now(),
146
- }),
147
- });
148
- if (!res.ok) {
149
- const body = await res.text();
150
- console.error(` Cloud sync failed: ${res.status} ${body}`);
151
- }
152
- else {
153
- const data = (await res.json());
154
- if (data.sessionsPushed && data.sessionsPushed > 0) {
155
- console.log(` Cloud sync: ${data.sessionsPushed} session(s) pushed`);
156
- }
157
- }
158
- }
159
- catch (err) {
160
- console.error(` Cloud sync error: ${err instanceof Error ? err.message : String(err)}`);
161
- }
162
- };
163
- const pullTasks = async () => {
164
- try {
165
- const res = await fetch(`${CLOUD_URL}?action=pull-tasks&machineId=${machineId}`);
166
- if (!res.ok)
167
- return;
168
- const data = (await res.json());
169
- const tasks = data.tasks || [];
170
- for (const task of tasks) {
171
- if (task.prompt) {
172
- console.log(` Cloud task received: ${task.prompt.slice(0, 60)}...`);
173
- try {
174
- const manager = await getSessionManager();
175
- manager.spawn(task.prompt, task.agent || 'claude-code');
176
- // Mark task as claimed
177
- await fetch(`${CLOUD_URL}?action=claim-task&machineId=${machineId}&taskId=${task.id}`, {
178
- method: 'POST',
179
- headers: { 'Content-Type': 'application/json' },
180
- body: JSON.stringify({ machineId, token, taskId: task.id }),
181
- });
182
- }
183
- catch (err) {
184
- console.error(` Failed to spawn cloud task: ${err instanceof Error ? err.message : String(err)}`);
185
- }
186
- }
187
- }
188
- }
189
- catch {
190
- // Silent fail for task pull — not critical
191
- }
192
- };
193
- // Push immediately, then every 30s
194
- push();
195
- pullTasks();
196
- syncInterval = setInterval(() => {
197
- push();
198
- pullTasks();
199
- }, 30000);
200
- }
201
- export function stopCloudSync() {
202
- if (syncInterval) {
203
- clearInterval(syncInterval);
204
- syncInterval = null;
205
- }
206
- }
207
- //# sourceMappingURL=cloud-sync.js.map
@@ -1,25 +0,0 @@
1
- /**
2
- * MCP helper functions — shared between MCP server and trust command.
3
- * Extracted to avoid circular imports.
4
- */
5
- import { readFileSync } from 'fs';
6
- import { collectCodeFiles } from '../security/file-collection.js';
7
- // ── File collection ──────────────────────────────────────────────────────────
8
- // Uses the shared walker — same exclusion rules as trust and report.
9
- export function collectFilesForMCP(target) {
10
- const paths = collectCodeFiles(target);
11
- const result = [];
12
- for (const filepath of paths) {
13
- try {
14
- result.push({ filepath, code: readFileSync(filepath, 'utf-8') });
15
- }
16
- catch {
17
- // skip unreadable files (iCloud-evicted, permissions, too large)
18
- }
19
- }
20
- return result;
21
- }
22
- // ── Scoring ──────────────────────────────────────────────────────────────────
23
- // Re-export score function using the shared scorer
24
- export { scoreFile as scoreFileForMCP } from '../security/trust-scorer.js';
25
- //# sourceMappingURL=mcp-helpers.js.map