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,401 +0,0 @@
1
- // MCP tool layer extracted from mcp-server-impl.ts (god-file split, 2026-06-29).
2
- // The JSON-RPC surface: tool definitions + the read-only handlers + the dispatcher.
3
- // The HTTP daemon (startMCPServer) stays in mcp-server-impl.ts and imports dispatch+TOOLS.
4
- import { existsSync, readFileSync } from 'fs';
5
- import { relative, resolve as resolvePath, sep } from 'path';
6
- import { computeSpendToday } from '../cost/spend-aggregator.js';
7
- import { loadBudget, budgetUsedPct } from '../cost/budget-config.js';
8
- import { loadTeamConfig, writeMyShare, buildTeamView } from '../cost/team-ledger.js';
9
- import { collectFilesForMCP, scoreFileForMCP, aggregateTrust } from './mcp-helpers.js';
10
- import { scanPayload, calculateRiskScore, AUDIT_FILE, verifyAuditChain } from '../hooks/claude-sdk-hook.js';
11
- import { getVersion } from '../../utils/version.js';
12
- // ── Tool definitions ─────────────────────────────────────────────────────────
13
- // Tool annotations follow the MCP spec (the github/github-mcp-server pattern:
14
- // toolsets + an explicit read-only declaration). Every codehere MCP tool is
15
- // ANALYSIS-ONLY — none writes files, spawns agents, or mutates state — and the
16
- // annotations SAY so at the protocol level (readOnlyHint), so a cautious client
17
- // can mount codehere's MCP knowing the whole surface is inspect-only. The
18
- // 'toolset' grouping mirrors gh-mcp's toolset concept (security · audit).
19
- // NOTE: readOnlyHint/title are MCP-spec (rev 2025-03-26) fields — we still
20
- // declare protocolVersion 2024-11-05, where clients ignore unknown fields, so
21
- // this is additive; 'toolset' is a CUSTOM extension (gh-mcp's toolsets are a
22
- // server-side flag, not a wire field) and annotations are self-declarations,
23
- // not proofs. If a WRITE tool is ever added, it must carry readOnlyHint:false
24
- // + its own destructiveHint — and the README line "Exposes 4 read-only tools"
25
- // (README.md §MCP) must change in the same commit (binding honesty rule).
26
- export const TOOLS = [
27
- {
28
- name: 'codehere_trust',
29
- annotations: { title: 'Trust-score a file or snippet', readOnlyHint: true, toolset: 'security' },
30
- description: 'Scan a file or code snippet for security vulnerabilities. Returns a trust score 0-100, grade, and plain-English findings. Zero API calls — runs entirely locally.',
31
- inputSchema: {
32
- type: 'object',
33
- properties: {
34
- filepath: { type: 'string', description: 'Path to file (relative to cwd)' },
35
- code: { type: 'string', description: 'Code content to scan (alternative to filepath)' },
36
- verbose: { type: 'boolean', description: 'Include all findings with line numbers' },
37
- },
38
- },
39
- },
40
- {
41
- name: 'codehere_scan',
42
- annotations: { title: 'Scan text for prompt-injection / threats', readOnlyHint: true, toolset: 'security' },
43
- description: 'Scan an instruction, prompt, or command text for injection attacks, credential theft attempts, destructive operations, and other threats. Returns block/allow decision with findings.',
44
- inputSchema: {
45
- type: 'object',
46
- required: ['text'],
47
- properties: {
48
- text: { type: 'string', description: 'Text to scan (instruction, prompt, command)' },
49
- },
50
- },
51
- },
52
- {
53
- name: 'codehere_review',
54
- annotations: { title: 'Review a directory, rank riskiest files', readOnlyHint: true, toolset: 'security' },
55
- description: 'Review all files in a directory and return trust scores. Identifies the riskiest files to focus review effort.',
56
- inputSchema: {
57
- type: 'object',
58
- properties: {
59
- path: { type: 'string', description: 'Directory path (default: current directory)' },
60
- topN: { type: 'number', description: 'Return top N riskiest files (default: 10)' },
61
- },
62
- },
63
- },
64
- {
65
- name: 'codehere_audit',
66
- annotations: { title: 'Read / verify the hash-chained audit log', readOnlyHint: true, toolset: 'audit' },
67
- description: 'Read recent hook audit log entries — shows which tool calls codehere blocked, allowed, or overrode (fail-open paths) in Claude Code sessions. Pass verify:true to instead validate the SHA-256 hash chain and return any tampering detected.',
68
- inputSchema: {
69
- type: 'object',
70
- properties: {
71
- limit: { type: 'number', description: 'Number of recent entries to return (default: 20)' },
72
- blockedOnly: { type: 'boolean', description: 'Only return blocked events' },
73
- overrideOnly: {
74
- type: 'boolean',
75
- description: 'Only return trust-override events — cases where the scanner was bypassed (stdin failure, parse failure, --log-only, scan exception). Per CC frontier Q1 (auditable permission trails) — surface every bypass for review.',
76
- },
77
- verify: {
78
- type: 'boolean',
79
- description: 'Verify the SHA-256 hash chain instead of returning entries. Returns { valid, totalEntries, chainedEntries, legacyEntries, errors[] } where errors lists every detected tampering point (modification / deletion / insertion / parse failure). Per CC frontier Q1 (cryptographic commitment + tamper evidence).',
80
- },
81
- },
82
- },
83
- },
84
- {
85
- name: 'codehere_spend',
86
- annotations: { title: "Today's estimated spend by agent and project", readOnlyHint: true, toolset: 'cost' },
87
- description: "Today's estimated coding-agent spend on this machine, broken down by agent and project, against the daily budget. So you can answer 'what have we spent today' from inside the session. Estimate from real token counts at published rates, not a provider invoice.",
88
- inputSchema: { type: 'object', properties: {} },
89
- },
90
- {
91
- name: 'codehere_team',
92
- annotations: { title: 'Merged team cost + record across machines', readOnlyHint: true, toolset: 'cost' },
93
- description: "The team's merged cost and integrity-checked record across every machine that shares the team git repo (spend by machine, total recorded actions, any broken audit chains). Falls back to this machine's solo view when no team is configured. Pass strict:true to REFUSE a total when any share is tampered or its audit chain is broken, instead of reporting a number that can't be trusted.",
94
- inputSchema: {
95
- type: 'object',
96
- properties: {
97
- strict: {
98
- type: 'boolean',
99
- description: 'When true, if any share is tampered or its audit chain is broken, return a refusal naming the untrustworthy machines instead of a team total.',
100
- },
101
- },
102
- },
103
- },
104
- ];
105
- /** codehere_spend MCP tool: today's estimated spend, structured numbers plus a
106
- * one-line summary the agent can quote. Reads local state only. */
107
- function handleSpend(_params) {
108
- const s = computeSpendToday();
109
- const cfg = loadBudget();
110
- const fmt = (n) => '$' + (Number(n) || 0).toFixed(2);
111
- const pct = budgetUsedPct(s.totalUsd, cfg);
112
- const head = cfg.dailyUsd != null
113
- ? `codehere spend today: ${fmt(s.totalUsd)} of ${fmt(cfg.dailyUsd)} daily budget (${pct ?? 0}% used), across ${s.sessionCount} session${s.sessionCount === 1 ? '' : 's'}.`
114
- : `codehere spend today: ${fmt(s.totalUsd)} across ${s.sessionCount} session${s.sessionCount === 1 ? '' : 's'}, no budget set.`;
115
- const agents = Object.entries(s.byAgent || {})
116
- .map(([a, v]) => ` ${a}: ${fmt(v.usd)} (${v.sessions} session${v.sessions === 1 ? '' : 's'})`)
117
- .join('\n') || ' (no agent spend today)';
118
- const projects = Object.entries(s.byProject || {})
119
- .map(([p, v]) => ` ${p}: ${fmt(v.usd)}`)
120
- .join('\n') || ' (no project spend today)';
121
- const raw = JSON.stringify({
122
- totalUsd: s.totalUsd,
123
- sessionCount: s.sessionCount,
124
- dailyBudgetUsd: cfg.dailyUsd,
125
- pctUsed: pct,
126
- byAgent: s.byAgent,
127
- byProject: s.byProject,
128
- tokens: s.tokens,
129
- });
130
- return `${head}\nEstimate from real token counts at published rates, not a provider invoice.\n\nBy agent:\n${agents}\n\nBy project:\n${projects}\n\nRaw: ${raw}`;
131
- }
132
- /** codehere_team MCP tool: the merged team view, or this machine's solo view when
133
- * no team git repo is configured. Reads local + the team dir only. */
134
- function handleTeam(_params) {
135
- const strict = Boolean(_params?.strict);
136
- const fmt = (n) => '$' + (Number(n) || 0).toFixed(2);
137
- const cfg = loadTeamConfig();
138
- if (!cfg.dir) {
139
- const s = computeSpendToday();
140
- return `No team configured. This machine today: ${fmt(s.totalUsd)} across ${s.sessionCount} session${s.sessionCount === 1 ? '' : 's'}. Run "codehere team set-dir <a git repo>" on each machine to merge the whole team, local-first.`;
141
- }
142
- try {
143
- writeMyShare(cfg.dir);
144
- }
145
- catch {
146
- /* dir not writable; still read what is there */
147
- }
148
- const v = buildTeamView(cfg.dir, cfg.dailyUsd);
149
- const count = (v.machines || []).length;
150
- const machines = (v.machines || []).map((m) => ` ${m.machineId}: ${fmt(m.totalUsd)}`).join('\n') || ' (none)';
151
- const agents = Object.entries(v.perAgent || {}).sort((a, b) => b[1] - a[1]);
152
- const byAgent = agents.length
153
- ? '\n\nBy agent (cost · what each did):\n' +
154
- agents
155
- .map(([a, usd]) => {
156
- const s = v.perAgentSessions?.[a] ?? 0;
157
- return ` ${a}: ${fmt(usd)} · ${s} session${s === 1 ? '' : 's'}`;
158
- })
159
- .join('\n')
160
- : '';
161
- const broken = v.brokenChains || [];
162
- const brokenLine = broken.length
163
- ? `\nBROKEN audit chain on: ${broken.join(', ')}. Investigate before trusting that record.`
164
- : '';
165
- const tampered = v.tamperedShares || [];
166
- const unsigned = v.unsignedShares || [];
167
- const sigLine = tampered.length
168
- ? `\nSIGNATURE INVALID on: ${tampered.join(', ')} — that share was altered or re-signed with a different key than first seen. Do not trust it.`
169
- : count
170
- ? `\nAll shares signed; keys match the ones first seen (no post-write change detected).`
171
- : '';
172
- const unsignedLine = unsigned.length ? `\nUnsigned, can't verify: ${unsigned.join(', ')}.` : '';
173
- if (strict) {
174
- const bad = [...new Set([...tampered, ...broken])];
175
- if (bad.length) {
176
- return `REFUSED (strict): the team record can't be trusted — ${bad.join(', ')} ${bad.length === 1 ? 'is' : 'are'} tampered or ${bad.length === 1 ? 'has' : 'have'} a broken audit chain. Not reporting a total. Investigate that machine's share before trusting the record.\n\nRaw: ${JSON.stringify({ strict: true, ok: false, untrustworthy: bad, tamperedShares: tampered, brokenChains: broken })}`;
177
- }
178
- }
179
- const head = `codehere team today: ${fmt(v.teamTotal)}${v.dailyUsd != null ? ` of ${fmt(v.dailyUsd)} team budget` : ''} across ${count} machine${count === 1 ? '' : 's'}, ${(v.totalActions || 0).toLocaleString()} recorded actions.`;
180
- const practiceLine = v.practice && v.practice.sessions > 0
181
- ? `\nPractice runs: ${v.practice.sessions} session${v.practice.sessions === 1 ? '' : 's'} · ${fmt(v.practice.usd)} fabricated · not in the totals.`
182
- : '';
183
- return `${head}\nEstimate from real tokens at published rates, not a provider invoice.${practiceLine}${brokenLine}${sigLine}${unsignedLine}\n\nBy machine:\n${machines}${byAgent}\n\nRaw: ${JSON.stringify(v)}`;
184
- }
185
- function handleTrust(params) {
186
- const { filepath, code, verbose } = params;
187
- let content;
188
- let label;
189
- if (code) {
190
- content = code;
191
- label = 'provided code';
192
- }
193
- else if (filepath) {
194
- // MED #31 (atomic audit) · scope the inspect-only trust tool to the cwd:
195
- // '../../etc/passwd' walked out of the workspace. Read-only, so the
196
- // blast radius was disclosure-shaped — still, the tool's contract says
197
- // "scores YOUR project's file".
198
- const resolved = resolvePath(process.cwd(), filepath);
199
- if (resolved !== process.cwd() && !resolved.startsWith(process.cwd() + sep)) {
200
- return JSON.stringify({ error: `Path escapes the workspace: ${filepath}` });
201
- }
202
- if (!existsSync(resolved)) {
203
- return JSON.stringify({ error: `File not found: ${filepath}` });
204
- }
205
- content = readFileSync(resolved, 'utf-8');
206
- label = filepath;
207
- }
208
- else {
209
- return JSON.stringify({ error: 'Provide either filepath or code' });
210
- }
211
- const result = scoreFileForMCP(label, content);
212
- if (!verbose) {
213
- // Compact response
214
- return JSON.stringify({
215
- score: result.score,
216
- grade: result.grade,
217
- summary: result.summary,
218
- criticalCount: result.findings.filter((f) => f.severity === 'critical').length,
219
- highCount: result.findings.filter((f) => f.severity === 'high').length,
220
- recommendation: result.score >= 75
221
- ? 'Safe — minor issues can be addressed in follow-up'
222
- : result.score >= 60
223
- ? 'Review recommended before merge'
224
- : 'Block — resolve critical/high findings first',
225
- });
226
- }
227
- return JSON.stringify(result, null, 2);
228
- }
229
- function handleScan(params) {
230
- const text = String(params.text ?? '');
231
- const findings = scanPayload(text);
232
- const riskScore = calculateRiskScore(findings);
233
- const criticalFindings = findings.filter((f) => f.severity === 'critical');
234
- const blocked = criticalFindings.length > 0 || riskScore >= 60;
235
- return JSON.stringify({
236
- blocked,
237
- riskScore,
238
- decision: blocked ? 'block' : 'allow',
239
- reason: blocked
240
- ? criticalFindings.length > 0
241
- ? `${criticalFindings[0].category}: ${criticalFindings[0].description}`
242
- : `Risk score ${riskScore}/100 exceeds threshold`
243
- : 'No threats detected',
244
- findings: findings.slice(0, 10), // cap at 10 to keep response small
245
- });
246
- }
247
- /**
248
- * Coerce a client-supplied numeric param to a bounded integer. Daemon
249
- * endpoints take topN/limit straight from request bodies; a negative or NaN
250
- * value silently corrupts slice() math (limit=-1 → slice keeps all-but-last,
251
- * NaN → slice returns everything). Clamp to a sane range instead of trusting
252
- * the wire.
253
- */
254
- export function clampInt(v, def, min, max) {
255
- // null/undefined = absent → default. Number(null) is 0 (finite), so without
256
- // this guard a literal null would coerce to the min instead of the default.
257
- if (v === null || v === undefined)
258
- return def;
259
- const n = Math.floor(Number(v));
260
- if (!Number.isFinite(n))
261
- return def;
262
- return Math.max(min, Math.min(n, max));
263
- }
264
- function handleReview(params) {
265
- const targetPath = String(params.path ?? process.cwd());
266
- const topN = clampInt(params.topN, 10, 1, 100);
267
- try {
268
- const files = collectFilesForMCP(targetPath);
269
- if (files.length === 0) {
270
- return JSON.stringify({ message: 'No code files found', path: targetPath });
271
- }
272
- const results = files.map((f) => scoreFileForMCP(relative(targetPath, f.filepath), f.code));
273
- results.sort((a, b) => a.score - b.score); // worst first
274
- const top = results.slice(0, topN);
275
- // Use the shared honesty floor (a critical finding caps the grade at F) so the
276
- // MCP grade can never disagree with the CLI's grade for the same directory.
277
- const { overallScore, grade } = aggregateTrust(results);
278
- return JSON.stringify({
279
- overallScore,
280
- grade,
281
- totalFiles: results.length,
282
- riskiestFiles: top.map((r) => ({
283
- filepath: r.filepath,
284
- score: r.score,
285
- grade: r.grade,
286
- summary: r.summary,
287
- criticalCount: r.findings.filter((f) => f.severity === 'critical').length,
288
- highCount: r.findings.filter((f) => f.severity === 'high').length,
289
- })),
290
- });
291
- }
292
- catch (e) {
293
- return JSON.stringify({ error: String(e) });
294
- }
295
- }
296
- function handleAudit(params) {
297
- const limit = clampInt(params.limit, 20, 1, 500);
298
- const blockedOnly = Boolean(params.blockedOnly);
299
- const overrideOnly = Boolean(params.overrideOnly);
300
- const verify = Boolean(params.verify);
301
- if (verify) {
302
- // Tamper-evidence verification — walks the file, recomputes every hash,
303
- // checks the chain. Returns full report including ALL break points.
304
- const result = verifyAuditChain(AUDIT_FILE);
305
- return JSON.stringify(result, null, 2);
306
- }
307
- if (!existsSync(AUDIT_FILE)) {
308
- return JSON.stringify({
309
- message: 'No audit log yet — install hooks first with: codehere hook install',
310
- entries: [],
311
- });
312
- }
313
- const lines = readFileSync(AUDIT_FILE, 'utf-8')
314
- .split('\n')
315
- .filter(Boolean)
316
- .slice(-Math.min(limit * 3, 500)); // read more than needed to account for filter
317
- const entries = lines
318
- .map((l) => {
319
- try {
320
- return JSON.parse(l);
321
- }
322
- catch {
323
- return null;
324
- }
325
- })
326
- .filter(Boolean)
327
- .filter((e) => {
328
- if (overrideOnly)
329
- return Boolean(e.overrideReason);
330
- if (blockedOnly)
331
- return Boolean(e.blocked);
332
- return true;
333
- })
334
- .slice(-limit);
335
- return JSON.stringify({ entries, total: entries.length });
336
- }
337
- // ── JSON-RPC dispatcher ──────────────────────────────────────────────────────
338
- export function dispatch(req) {
339
- const id = req.id;
340
- const params = (req.params ?? {});
341
- try {
342
- switch (req.method) {
343
- case 'initialize': {
344
- // Echo the client's requested protocol version so Streamable-HTTP
345
- // clients (Codex on 2025-x) negotiate; fall back to the baseline.
346
- const clientPv = typeof params.protocolVersion === 'string' ? params.protocolVersion : '2024-11-05';
347
- return {
348
- jsonrpc: '2.0',
349
- id,
350
- result: {
351
- protocolVersion: clientPv,
352
- capabilities: { tools: {} },
353
- serverInfo: { name: 'codehere', version: getVersion() },
354
- },
355
- };
356
- }
357
- case 'tools/list':
358
- return { jsonrpc: '2.0', id, result: { tools: TOOLS } };
359
- case 'tools/call': {
360
- const toolName = String(params.name ?? '');
361
- const toolParams = (params.arguments ?? {});
362
- let content;
363
- switch (toolName) {
364
- case 'codehere_trust':
365
- content = handleTrust(toolParams);
366
- break;
367
- case 'codehere_scan':
368
- content = handleScan(toolParams);
369
- break;
370
- case 'codehere_review':
371
- content = handleReview(toolParams);
372
- break;
373
- case 'codehere_audit':
374
- content = handleAudit(toolParams);
375
- break;
376
- case 'codehere_spend':
377
- content = handleSpend(toolParams);
378
- break;
379
- case 'codehere_team':
380
- content = handleTeam(toolParams);
381
- break;
382
- default:
383
- return { jsonrpc: '2.0', id, error: { code: -32601, message: `Unknown tool: ${toolName}` } };
384
- }
385
- return {
386
- jsonrpc: '2.0',
387
- id,
388
- result: { content: [{ type: 'text', text: content }] },
389
- };
390
- }
391
- case 'ping':
392
- return { jsonrpc: '2.0', id, result: {} };
393
- default:
394
- return { jsonrpc: '2.0', id, error: { code: -32601, message: `Method not found: ${req.method}` } };
395
- }
396
- }
397
- catch (e) {
398
- return { jsonrpc: '2.0', id, error: { code: -32603, message: String(e) } };
399
- }
400
- }
401
- //# sourceMappingURL=mcp-tools.js.map
@@ -1,36 +0,0 @@
1
- /**
2
- * Detect whether a given host:port is already serving a codehere instance.
3
- *
4
- * Used by `codehere start` when the listen() fails with EADDRINUSE: we'd
5
- * rather give the user a friendly "codehere already running, opening browser"
6
- * than a Node stack trace. If something ELSE holds the port we tell them to
7
- * pick a different one.
8
- *
9
- * Approach: hit the existing server's /health endpoint. The codehere MCP
10
- * server returns a JSON body with `server: 'codehere-mcp'` — no other tool
11
- * uses that exact identifier. Generous 1.5-second timeout so a slow loopback
12
- * doesn't false-negative.
13
- */
14
- export async function isCodehereOnPort(host, port, timeoutMs = 1500) {
15
- const ac = new AbortController();
16
- const timer = setTimeout(() => ac.abort(), timeoutMs);
17
- try {
18
- const res = await fetch(`http://${host}:${port}/health`, { signal: ac.signal });
19
- if (!res.ok)
20
- return { isCodehere: false };
21
- const body = (await res.json());
22
- if (body?.server === 'codehere-mcp') {
23
- return { isCodehere: true, version: body.version };
24
- }
25
- return { isCodehere: false };
26
- }
27
- catch {
28
- // Connection refused, timeout, malformed response — anything that's not
29
- // a positive codehere identification. Treat as "not codehere."
30
- return { isCodehere: false };
31
- }
32
- finally {
33
- clearTimeout(timer);
34
- }
35
- }
36
- //# sourceMappingURL=probe-port.js.map
@@ -1,105 +0,0 @@
1
- /**
2
- * Thread phase classifier · the demo v12 lifecycle spine.
3
- *
4
- * A thread reads as a phased narrative (research → build → ship → audit → live).
5
- * Steps don't carry phase labels, so classify by the dominant signal in the
6
- * step LABEL (the output is noisy — agent names like "claude-code", file lists —
7
- * and misfires the keywords).
8
- *
9
- * This logic used to live inline in the ui-page-v2 template string, where it was
10
- * untestable — and a `\bcode\b` regex matched "claude-code" and mislabeled read
11
- * steps as BUILD (caught by eye, not a test). Extracted here so it's unit-tested,
12
- * and its SOURCE is injected back into the browser template via
13
- * PHASE_CLASSIFIER_SRC — one source of truth, not a duplicated twin.
14
- */
15
- export const PHASE_ORDER = ['research', 'build', 'ship', 'audit', 'live'];
16
- export const PHASE_META = {
17
- research: { n: 1, label: 'research', done: 'research locked', verb: 'explored' },
18
- build: { n: 2, label: 'build', done: 'build done', verb: 'built' },
19
- ship: { n: 3, label: 'ship', done: 'shipped', verb: 'tested & shipped' },
20
- audit: { n: 4, label: 'audit', done: 'audited', verb: 'reviewed' },
21
- live: { n: 5, label: 'live', done: 'live', verb: 'recovered' },
22
- };
23
- // Classify on the LABEL only · keep keywords action-specific, NOT domain words.
24
- // "rate-limit" is a FEATURE many tasks build — it must NOT signal LIVE, or every
25
- // rate-limit task mislabels its build steps as live. "claude-code" must NOT
26
- // match \bcode\b → that was the original bug.
27
- export function stepPhase(step) {
28
- const s = (step.label || '').toLowerCase();
29
- if (/failover|crashed|recover|orphan|hydrat|checkpoint|fell back|failed over/.test(s))
30
- return 'live';
31
- if (/verif|audit|final report|trust grade|claim vs|w-?pass|write-pass|chain entry/.test(s))
32
- return 'audit';
33
- if (/\btest\b|\btests\b|\bpush\b|commit|deploy|benchmark|\bship\b|\bmerge\b/.test(s))
34
- return 'ship';
35
- if (/\bedit\b|\bwrite\b|writes |writing|implement|refactor|patch|\bbuild\b|dispatch/.test(s))
36
- return 'build';
37
- if (/pre-prompt|scan|\bread\b|grep|search|analyz|research|context|\broute|explor|\bmap\b/.test(s))
38
- return 'research';
39
- return null;
40
- }
41
- // Assign every step a phase · unknown steps inherit the running phase so the
42
- // timeline never fragments. Phases only ADVANCE, never regress — once a task has
43
- // shipped, a stray "read" stays in ship rather than yanking the narrative back.
44
- // The regress check is against the OLD running phase, THEN advance.
45
- export function classifySteps(steps) {
46
- const out = [];
47
- let cur = 'research';
48
- for (let i = 0; i < steps.length; i++) {
49
- let p = stepPhase(steps[i]);
50
- if (!p)
51
- p = cur;
52
- if (PHASE_META[p].n < PHASE_META[cur].n)
53
- p = cur;
54
- cur = p;
55
- out.push(p);
56
- }
57
- return out;
58
- }
59
- /**
60
- * EMERGENT grouping for the unified thread timeline. A segment is a run of
61
- * CONSECUTIVE steps that share a phase — so the sections are however many the
62
- * actual activity produced, not a fixed pipeline. No monotonic advance, no
63
- * "phase N of 5", no forced final stage: a one-read task yields one segment, a
64
- * sprawling task yields several. Unknown steps inherit the running phase so a
65
- * stray unclassified step doesn't fragment a run; leading unknowns (before any
66
- * classified step) produce no segment.
67
- *
68
- * This replaces classifySteps' fixed-5 framing for the timeline. stepPhase (the
69
- * per-step activity classifier) is reused unchanged.
70
- */
71
- export function segmentSteps(steps) {
72
- const segs = [];
73
- let cur = null;
74
- for (let i = 0; i < steps.length; i++) {
75
- let p = stepPhase(steps[i]);
76
- if (!p)
77
- p = cur;
78
- if (!p)
79
- continue;
80
- if (p !== cur) {
81
- segs.push({ phase: p, verb: PHASE_META[p].verb, from: i, count: 1 });
82
- cur = p;
83
- }
84
- else {
85
- segs[segs.length - 1].count++;
86
- }
87
- }
88
- return segs;
89
- }
90
- /**
91
- * Browser-JS source for injection into the ui-page-v2 template literal. Emits
92
- * the same PHASE_META / stepPhase / segmentSteps the tests cover, so the
93
- * rendered thread uses the EXACT logic that's unit-tested. PHASE_ORDER +
94
- * classifySteps are still emitted during the timeline-restructure transition
95
- * (renderD's old phase-position line uses them); they go once renderD switches
96
- * to segmentSteps.
97
- */
98
- export const PHASE_CLASSIFIER_SRC = [
99
- 'var PHASE_ORDER=' + JSON.stringify(PHASE_ORDER) + ';',
100
- 'var PHASE_META=' + JSON.stringify(PHASE_META) + ';',
101
- stepPhase.toString(),
102
- classifySteps.toString(),
103
- segmentSteps.toString(),
104
- ].join('\n');
105
- //# sourceMappingURL=thread-phase.js.map