codehere 1.0.1 → 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 (163) hide show
  1. package/README.md +36 -192
  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,355 +0,0 @@
1
- import { writeFileAtomic } from '../storage/atomic-write.js';
2
- /**
3
- * The local-first team cost+audit ledger: the shared data layer.
4
- *
5
- * codehere is per-machine by design (code never leaves the box). A small team
6
- * is several boxes, so each machine drops its daily spend + audit roll-up into a
7
- * directory the team controls (their OWN git repo / shared drive, NOT a
8
- * codehere relay), and any machine can merge them into one team view. This
9
- * module is the single owner of that logic so BOTH surfaces read the same core:
10
- * the CLI (`codehere team`) and the daemon (`GET /api/team`). Spend is the same
11
- * ESTIMATE the rest of codehere uses (tokens x published rate), never a real
12
- * invoice.
13
- */
14
- import { existsSync, mkdirSync, readFileSync, writeFileSync, readdirSync } from 'fs';
15
- import { execFileSync } from 'child_process';
16
- import { sign as cryptoSign, verify as cryptoVerify, createPublicKey } from 'crypto';
17
- import { join, isAbsolute, resolve } from 'path';
18
- import { homedir, hostname } from 'os';
19
- import { computeSpendToday } from './spend-aggregator.js';
20
- import { verifyAuditChain } from '../hooks/claude-sdk-hook.js';
21
- import { loadOrCreateKeypair } from '../orchestration/thread-key-store.js';
22
- import { getOrCreateToken } from '../protocols/cloud-sync.js';
23
- export function codehereHome() {
24
- return join(process.env.CODEHERE_HOME || homedir(), '.codehere');
25
- }
26
- export function machineId() {
27
- return `${hostname()}-${getOrCreateToken().slice(0, 8)}`;
28
- }
29
- export function todayStr() {
30
- const d = new Date();
31
- const p = (n) => String(n).padStart(2, '0');
32
- return `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())}`;
33
- }
34
- export function loadTeamConfig() {
35
- const cfg = { dir: null, dailyUsd: null };
36
- const path = join(codehereHome(), 'team.yaml');
37
- if (!existsSync(path))
38
- return cfg;
39
- try {
40
- for (const rawLine of readFileSync(path, 'utf-8').split('\n')) {
41
- const line = rawLine.replace(/#.*$/, '').trim();
42
- const idx = line.indexOf(':');
43
- if (idx === -1)
44
- continue;
45
- const key = line.slice(0, idx).trim();
46
- const val = line.slice(idx + 1).trim();
47
- // A team dir must be ABSOLUTE: a relative value silently resolves against
48
- // whatever cwd the daemon/CLI happens to run from — the record then lives
49
- // wherever you stood. Found live 2026-07-07: a client bug wrote the literal
50
- // string "undefined" on Jul 1 and six days of shares landed in agent/undefined/.
51
- if (key === 'dir' && val && val !== 'undefined' && val !== 'null' && isAbsolute(val))
52
- cfg.dir = val;
53
- if (key === 'daily_usd') {
54
- const num = Number(val);
55
- cfg.dailyUsd = Number.isFinite(num) && num > 0 ? num : null;
56
- }
57
- }
58
- }
59
- catch {
60
- /* corrupt file -> defaults; never break the caller */
61
- }
62
- return cfg;
63
- }
64
- export function saveTeamConfig(cfg) {
65
- const dir = codehereHome();
66
- if (!existsSync(dir))
67
- mkdirSync(dir, { recursive: true });
68
- // Normalize on write: a dir persists only as an absolute path (see the
69
- // loadTeamConfig guard for the live incident this closes).
70
- const dirLine = cfg.dir && cfg.dir !== 'undefined' && cfg.dir !== 'null'
71
- ? `dir: ${resolve(cfg.dir)}`
72
- : '# dir: /path/to/team-shared-dir';
73
- const lines = [
74
- '# codehere team spend ledger. The shared dir is yours (keep it in your own git repo).',
75
- '# codehere never sends this anywhere, it only reads/writes the folder below.',
76
- dirLine,
77
- cfg.dailyUsd !== null ? `daily_usd: ${cfg.dailyUsd}` : '# daily_usd: 50',
78
- '',
79
- ];
80
- writeFileAtomic(join(codehereHome(), 'team.yaml'), lines.join('\n'));
81
- }
82
- /** Pure merge of every machine's daily spend into one team view. */
83
- export function mergeTeamSpend(shares) {
84
- const perAgent = {};
85
- const perAgentSessions = {};
86
- const perProject = {};
87
- const practice = { usd: 0, sessions: 0 };
88
- let teamTotal = 0;
89
- for (const m of shares) {
90
- teamTotal += m.totalUsd;
91
- for (const [agent, usd] of Object.entries(m.byAgent))
92
- perAgent[agent] = (perAgent[agent] ?? 0) + usd;
93
- for (const [agent, n] of Object.entries(m.byAgentSessions ?? {}))
94
- perAgentSessions[agent] = (perAgentSessions[agent] ?? 0) + n;
95
- for (const [proj, usd] of Object.entries(m.byProject ?? {}))
96
- perProject[proj] = (perProject[proj] ?? 0) + usd;
97
- practice.usd += m.practiceUsd ?? 0;
98
- practice.sessions += m.practiceSessions ?? 0;
99
- }
100
- return { teamTotal, perAgent, perAgentSessions, perProject, practice };
101
- }
102
- /** Pure merge of the audit roll-up: total recorded actions + any machine whose
103
- * chain failed integrity (a broken chain is the one thing a team must see). */
104
- export function mergeTeamActivity(shares) {
105
- let totalActions = 0;
106
- const broken = [];
107
- for (const m of shares) {
108
- if (!m.audit)
109
- continue;
110
- totalActions += m.audit.actions;
111
- if (!m.audit.chainOk)
112
- broken.push(m.machineId);
113
- }
114
- return { totalActions, broken };
115
- }
116
- /** This machine's current spend + audit snapshot (computed live, not read). */
117
- export function myShare() {
118
- const spend = computeSpendToday();
119
- const byAgent = {};
120
- const byAgentSessions = {};
121
- for (const [agent, v] of Object.entries(spend.byAgent)) {
122
- byAgent[agent] = v.usd;
123
- byAgentSessions[agent] = v.sessions;
124
- }
125
- const byProject = {};
126
- for (const [proj, v] of Object.entries(spend.byProject))
127
- byProject[proj] = v.usd;
128
- const chain = verifyAuditChain();
129
- return {
130
- machineId: machineId(),
131
- date: todayStr(),
132
- totalUsd: spend.totalUsd,
133
- byAgent,
134
- byAgentSessions,
135
- byProject,
136
- practiceUsd: spend.practice.usd,
137
- practiceSessions: spend.practice.sessions,
138
- updatedAt: new Date().toISOString(),
139
- audit: { actions: chain.totalEntries, chainOk: chain.valid },
140
- };
141
- }
142
- /** Deterministic bytes of a share for signing/verifying: everything EXCEPT the
143
- * signature field, with object keys sorted at every level so sign-time and
144
- * read-time produce identical input regardless of property order. */
145
- function canonicalShareBytes(m) {
146
- const { signature: _omit, ...rest } = m;
147
- return JSON.stringify(rest, (_k, v) => v && typeof v === 'object' && !Array.isArray(v)
148
- ? Object.fromEntries(Object.keys(v)
149
- .sort()
150
- .map((k) => [k, v[k]]))
151
- : v);
152
- }
153
- /** Verdict for one share's signature. 'unsigned' (no sig) ≠ 'tampered' (sig
154
- * present but doesn't match) — BLOCKED-vs-FAIL discipline. */
155
- export function verifyShareSignature(m) {
156
- if (!m.signature || m.signature.alg !== 'ed25519')
157
- return 'unsigned';
158
- try {
159
- const pubKey = createPublicKey({ key: Buffer.from(m.signature.pubkey, 'base64'), format: 'der', type: 'spki' });
160
- const ok = cryptoVerify(null, Buffer.from(canonicalShareBytes(m), 'utf-8'), pubKey, Buffer.from(m.signature.sig, 'base64'));
161
- return ok ? 'signed' : 'tampered';
162
- }
163
- catch {
164
- return 'tampered'; // malformed sig/key reads as not-verifiable, fail-closed
165
- }
166
- }
167
- /** Trust anchor (known_hosts / TOFU model): machineId → the pubkey first seen for
168
- * it. A self-signature alone is defeatable (an attacker edits a share, makes a
169
- * fresh key, re-signs, swaps the embedded pubkey → self-verifies). Pinning the
170
- * first-seen key per machine turns a later KEY CHANGE into the tamper signal.
171
- * Stored on the VIEWER's machine, so each teammate pins what they've seen. */
172
- function knownKeysPath() {
173
- return join(codehereHome(), 'team-known-keys.json');
174
- }
175
- export function loadKnownKeys() {
176
- try {
177
- return JSON.parse(readFileSync(knownKeysPath(), 'utf-8'));
178
- }
179
- catch {
180
- return {};
181
- }
182
- }
183
- function saveKnownKeys(keys) {
184
- try {
185
- writeFileAtomic(knownKeysPath(), JSON.stringify(keys, null, 2));
186
- }
187
- catch {
188
- /* best-effort pin store */
189
- }
190
- }
191
- /** Per-team signature roll-up against the trust anchor:
192
- * - unsigned: no signature (legacy / signing unavailable) — BLOCKED, not FAIL.
193
- * - tampered: signature doesn't self-verify, OR a KNOWN machine's key changed
194
- * (re-signed by someone else) — the real forgery catch.
195
- * - firstSeen: signed + first sight of this machine's key → caller should pin.
196
- * Pure: knownKeys in, firstSeen out; buildTeamView does the I/O. */
197
- export function mergeTeamSignatures(shares, knownKeys = {}) {
198
- const tampered = [];
199
- const unsigned = [];
200
- const firstSeen = {};
201
- for (const m of shares) {
202
- const v = verifyShareSignature(m);
203
- if (v === 'unsigned') {
204
- unsigned.push(m.machineId);
205
- continue;
206
- }
207
- if (v === 'tampered') {
208
- tampered.push(m.machineId);
209
- continue;
210
- }
211
- // self-verifies — now check it against the key we first pinned for this machine
212
- const pubkey = m.signature.pubkey;
213
- const pinned = knownKeys[m.machineId];
214
- if (pinned && pinned !== pubkey)
215
- tampered.push(m.machineId); // key changed = re-signed forgery
216
- else if (!pinned)
217
- firstSeen[m.machineId] = pubkey; // TOFU: trust on first sight
218
- }
219
- return { tampered, unsigned, firstSeen };
220
- }
221
- /** Write this machine's snapshot into the team dir's spend/ folder, ed25519-signed
222
- * with the machine's persisted key so a teammate can detect post-write edits. */
223
- export function writeMyShare(dir) {
224
- const mine = myShare();
225
- try {
226
- const keys = loadOrCreateKeypair();
227
- const sig = cryptoSign(null, Buffer.from(canonicalShareBytes(mine), 'utf-8'), keys.privateKey);
228
- const pubRaw = keys.publicKey.export({ format: 'der', type: 'spki' });
229
- mine.signature = { alg: 'ed25519', pubkey: pubRaw.toString('base64'), sig: sig.toString('base64') };
230
- }
231
- catch {
232
- /* signing is best-effort — an unsigned share is flagged on read, never a crash */
233
- }
234
- const spendDir = join(dir, 'spend');
235
- if (!existsSync(spendDir))
236
- mkdirSync(spendDir, { recursive: true });
237
- writeFileAtomic(join(spendDir, `${mine.machineId}.json`), JSON.stringify(mine, null, 2));
238
- return mine;
239
- }
240
- /** Create a shareable team dir: ensure spend/, git-init it (best-effort), and a
241
- * README. Shared by `codehere team init` (CLI) and POST /api/team/init (daemon)
242
- * so both surfaces create a team the same way. */
243
- export function initTeamDir(dir) {
244
- mkdirSync(join(dir, 'spend'), { recursive: true });
245
- if (!isGitRepo(dir)) {
246
- try {
247
- execFileSync('git', ['init', '-q', '--', dir]);
248
- }
249
- catch {
250
- /* git missing — still usable as a plain shared folder */
251
- }
252
- }
253
- const readme = join(dir, 'README.md');
254
- if (!existsSync(readme)) {
255
- writeFileSync(readme, '# codehere team ledger\n\nShared cost + integrity record for this team. Each machine writes its own\nsnapshot under `spend/`. Keep this folder in a git repo your team controls —\ncodehere only reads/writes here; nothing leaves your machines.\n');
256
- }
257
- }
258
- /** Read every machine's TODAY snapshot from the team dir, corruption-tolerant. */
259
- export function readAllShares(dir) {
260
- const spendDir = join(dir, 'spend');
261
- if (!existsSync(spendDir))
262
- return [];
263
- const out = [];
264
- for (const file of readdirSync(spendDir)) {
265
- if (!file.endsWith('.json'))
266
- continue;
267
- try {
268
- const m = JSON.parse(readFileSync(join(spendDir, file), 'utf-8'));
269
- if (m && m.date === todayStr() && typeof m.totalUsd === 'number')
270
- out.push(m);
271
- }
272
- catch {
273
- /* skip a corrupt teammate file rather than fail the whole view */
274
- }
275
- }
276
- return out;
277
- }
278
- /** Assemble the merged TeamView from what's currently in the dir (read-only). */
279
- export function buildTeamView(dir, dailyUsd) {
280
- const machines = readAllShares(dir).sort((a, b) => b.totalUsd - a.totalUsd);
281
- const { teamTotal, perAgent, perAgentSessions, perProject, practice } = mergeTeamSpend(machines);
282
- const { totalActions, broken } = mergeTeamActivity(machines);
283
- const known = loadKnownKeys();
284
- const { tampered, unsigned, firstSeen } = mergeTeamSignatures(machines, known);
285
- if (Object.keys(firstSeen).length)
286
- saveKnownKeys({ ...known, ...firstSeen }); // pin newly-seen teammate keys
287
- return {
288
- date: todayStr(),
289
- myMachineId: machineId(),
290
- machines,
291
- teamTotal,
292
- perAgent,
293
- perAgentSessions,
294
- perProject,
295
- practice,
296
- totalActions,
297
- brokenChains: broken,
298
- tamperedShares: tampered,
299
- unsignedShares: unsigned,
300
- dailyUsd,
301
- };
302
- }
303
- // ── git sync ─────────────────────────────────────────────────────────────────
304
- // The team dir should be a DEDICATED git repo the team controls. Single-owner
305
- // per-machine files (spend/<id>.json) never conflict. All git ops are
306
- // best-effort: any failure (no remote, offline, not a repo) degrades to plain
307
- // shared-dir mode and never breaks the view.
308
- function git(dir, args, timeout = 20_000) {
309
- execFileSync('git', args, { cwd: dir, stdio: 'ignore', timeout });
310
- }
311
- export function isGitRepo(dir) {
312
- try {
313
- git(dir, ['rev-parse', '--is-inside-work-tree'], 5_000);
314
- return true;
315
- }
316
- catch {
317
- return false;
318
- }
319
- }
320
- export function gitPull(dir) {
321
- try {
322
- git(dir, ['pull', '--quiet', '--no-edit']);
323
- return true;
324
- }
325
- catch {
326
- return false;
327
- }
328
- }
329
- /** Stage + commit ONLY this machine's snapshot (never `git add -A`), then push. */
330
- export function gitShareMine(dir, relFile) {
331
- try {
332
- git(dir, ['add', relFile]);
333
- let hasChange = false;
334
- try {
335
- git(dir, ['diff', '--cached', '--quiet']); // exit 0 = nothing staged
336
- }
337
- catch {
338
- hasChange = true; // exit 1 = staged change present
339
- }
340
- if (!hasChange)
341
- return 'nochange';
342
- git(dir, ['commit', '-q', '-m', `team snapshot: ${machineId()}`]);
343
- try {
344
- git(dir, ['push', '--quiet']);
345
- return 'pushed';
346
- }
347
- catch {
348
- return 'committed-local'; // no remote / offline; still recorded locally
349
- }
350
- }
351
- catch {
352
- return 'failed';
353
- }
354
- }
355
- //# sourceMappingURL=team-ledger.js.map
@@ -1,85 +0,0 @@
1
- import { appendFileSync, existsSync, mkdirSync, readFileSync } from 'fs';
2
- import { homedir, hostname } from 'os';
3
- import { dirname, join } from 'path';
4
- // Continuous-discovery instrument (Teresa Torres / producttalk.org method).
5
- // Two jobs, both local-first and best-effort:
6
- // 1. record a behavioural signal, "the record was opened on this machine", deduped
7
- // per local day so a polling dashboard can't inflate it. Honest scope: each member
8
- // runs their own local daemon, so this is a PER-MACHINE-LOCAL signal (the operator's
9
- // own opens); it is NOT yet aggregated across the team into one cross-machine number.
10
- // 2. expose the in-product recruiting link (the scheduler) for the interview hook.
11
- // Never throws into a request handler; state lives on disk under ~/.codehere.
12
- function discoveryFile() {
13
- return join(process.env.CODEHERE_HOME || homedir(), '.codehere', 'discovery.jsonl');
14
- }
15
- function localDate(now) {
16
- const y = now.getFullYear();
17
- const m = String(now.getMonth() + 1).padStart(2, '0');
18
- const d = String(now.getDate()).padStart(2, '0');
19
- return `${y}-${m}-${d}`;
20
- }
21
- function readEvents() {
22
- try {
23
- if (!existsSync(discoveryFile()))
24
- return [];
25
- return readFileSync(discoveryFile(), 'utf-8')
26
- .split('\n')
27
- .filter(Boolean)
28
- .map((l) => {
29
- try {
30
- return JSON.parse(l);
31
- }
32
- catch {
33
- return null;
34
- }
35
- })
36
- .filter((e) => !!e && e.t === 'record-open' && typeof e.machine === 'string' && typeof e.date === 'string');
37
- }
38
- catch {
39
- return [];
40
- }
41
- }
42
- /**
43
- * Record that the shared record was opened on this machine, once per local day.
44
- * A per-machine-local behavioural proxy (each member's daemon records its own opens;
45
- * not yet aggregated across the team). Deduped so polling can't inflate it. Best-effort.
46
- */
47
- export function recordOpenOncePerDay(machine = hostname(), now = new Date()) {
48
- try {
49
- const date = localDate(now);
50
- for (const e of readEvents()) {
51
- if (e.machine === machine && e.date === date)
52
- return; // already counted today
53
- }
54
- mkdirSync(dirname(discoveryFile()), { recursive: true });
55
- const entry = { t: 'record-open', machine, date, at: now.toISOString() };
56
- appendFileSync(discoveryFile(), JSON.stringify(entry) + '\n');
57
- }
58
- catch {
59
- /* best-effort: a discovery metric must never break a request */
60
- }
61
- }
62
- export function discoverySummary(now = new Date()) {
63
- const events = readEvents();
64
- if (!events.length)
65
- return { recordOpenDays: 0, lastOpen: null, weeklyActive: false };
66
- const days = new Set(events.map((e) => e.date));
67
- const times = events
68
- .map((e) => Date.parse(e.at))
69
- .filter((n) => !Number.isNaN(n))
70
- .sort((a, b) => b - a);
71
- const lastOpen = times.length ? new Date(times[0]).toISOString() : null;
72
- const weeklyActive = times.length ? times[0] >= now.getTime() - 7 * 24 * 60 * 60 * 1000 : false;
73
- return { recordOpenDays: days.size, lastOpen, weeklyActive };
74
- }
75
- /**
76
- * The interview scheduler link for the in-product recruiting hook.
77
- * Opt-in: empty string hides the hook entirely (so the dashboard is unchanged by
78
- * default). Set CODEHERE_DISCOVERY_URL to a scheduler (Calendly-style) to enable it.
79
- */
80
- export function recruitUrl() {
81
- const url = (process.env.CODEHERE_DISCOVERY_URL || '').trim();
82
- // Only http(s) — block javascript:/data: so the in-product link can never execute.
83
- return /^https?:\/\//i.test(url) ? url : '';
84
- }
85
- //# sourceMappingURL=discovery-log.js.map