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.
- package/README.md +36 -192
- package/dist/application/use-cases/check-claimed-tests.js +81 -0
- package/dist/application/use-cases/detect-test-weakening.js +237 -0
- package/dist/application/use-cases/rerun-tests.js +110 -7
- package/dist/index.js +38 -1212
- package/dist/infrastructure/orchestration/artifacts-extractor.js +2 -323
- package/dist/presentation/cli/commands/check-command.js +178 -23
- package/dist/presentation/cli/commands/help-command.js +21 -285
- package/dist/ui.js +5 -236
- package/dist/utils/version.js +0 -6
- package/package.json +9 -38
- package/dist/application/services/verdict-service.js +0 -154
- package/dist/application/use-cases/assess-cross-vendor.js +0 -126
- package/dist/application/use-cases/build-cross-vendor-verdict.js +0 -118
- package/dist/application/use-cases/domain-trust.js +0 -110
- package/dist/application/use-cases/export-aat.js +0 -107
- package/dist/application/use-cases/generate-acceptance-criteria.js +0 -223
- package/dist/application/use-cases/generate-final-report.js +0 -259
- package/dist/application/use-cases/generate-narration.js +0 -130
- package/dist/application/use-cases/generate-thread-title.js +0 -181
- package/dist/application/use-cases/narrator-prompt-template.js +0 -158
- package/dist/application/use-cases/plan-auto-review.js +0 -57
- package/dist/application/use-cases/reconcile-audit.js +0 -100
- package/dist/application/use-cases/search-sessions.js +0 -70
- package/dist/application/use-cases/should-auto-review.js +0 -71
- package/dist/application/use-cases/thread-export.js +0 -245
- package/dist/application/use-cases/verify-claim.js +0 -456
- package/dist/application/use-cases/write-pass.js +0 -277
- package/dist/domain/audit.js +0 -11
- package/dist/domain/services/codebase-detector.js +0 -85
- package/dist/domain/task.js +0 -91
- package/dist/error-handler.js +0 -139
- package/dist/infrastructure/completion/bash-completion.js +0 -29
- package/dist/infrastructure/completion/fish-completion.js +0 -17
- package/dist/infrastructure/completion/zsh-completion.js +0 -25
- package/dist/infrastructure/config/config-manager.js +0 -174
- package/dist/infrastructure/config/product-context.js +0 -49
- package/dist/infrastructure/context/adapters/drawio-source.js +0 -157
- package/dist/infrastructure/context/adapters/figma-source.js +0 -198
- package/dist/infrastructure/context/adapters/github-issue-source.js +0 -160
- package/dist/infrastructure/context/adapters/linear-source.js +0 -190
- package/dist/infrastructure/context/context-resolver.js +0 -144
- package/dist/infrastructure/context/context-source.js +0 -114
- package/dist/infrastructure/context/github-api.js +0 -110
- package/dist/infrastructure/context/github-device-flow.js +0 -65
- package/dist/infrastructure/context/oauth-flow.js +0 -207
- package/dist/infrastructure/context/oauth-store.js +0 -122
- package/dist/infrastructure/context/registry-bootstrap.js +0 -95
- package/dist/infrastructure/context/write-pass-resolver.js +0 -106
- package/dist/infrastructure/cost/budget-config.js +0 -114
- package/dist/infrastructure/cost/cost-router.js +0 -97
- package/dist/infrastructure/cost/spend-aggregator.js +0 -222
- package/dist/infrastructure/cost/team-ledger.js +0 -355
- package/dist/infrastructure/discovery/discovery-log.js +0 -85
- package/dist/infrastructure/hooks/audit-tip-signature.js +0 -336
- package/dist/infrastructure/hooks/claude-sdk-hook.js +0 -1018
- package/dist/infrastructure/hooks/hook-mode.js +0 -87
- package/dist/infrastructure/hooks/mirror-tool-call-to-audit.js +0 -49
- package/dist/infrastructure/hooks/pre-prompt-scan.js +0 -122
- package/dist/infrastructure/hooks/session-analyzer.js +0 -177
- package/dist/infrastructure/observability/execution-tracer.js +0 -227
- package/dist/infrastructure/orchestration/agent-adapter.js +0 -13
- package/dist/infrastructure/orchestration/agent-installer.js +0 -101
- package/dist/infrastructure/orchestration/agents-config-seed.js +0 -68
- package/dist/infrastructure/orchestration/aider-adapter.js +0 -611
- package/dist/infrastructure/orchestration/cassette-recorder.js +0 -120
- package/dist/infrastructure/orchestration/claude-code-adapter.js +0 -592
- package/dist/infrastructure/orchestration/codex-adapter.js +0 -574
- package/dist/infrastructure/orchestration/conductor-adapter.js +0 -416
- package/dist/infrastructure/orchestration/context-handoff.js +0 -162
- package/dist/infrastructure/orchestration/control/codex-control-surface.js +0 -99
- package/dist/infrastructure/orchestration/control/control-surface.js +0 -15
- package/dist/infrastructure/orchestration/detect-ollama.js +0 -75
- package/dist/infrastructure/orchestration/failover-classifier.js +0 -151
- package/dist/infrastructure/orchestration/failover-config.js +0 -67
- package/dist/infrastructure/orchestration/failover-policy.js +0 -106
- package/dist/infrastructure/orchestration/gemini-adapter.js +0 -663
- package/dist/infrastructure/orchestration/generic-cli-adapter.js +0 -689
- package/dist/infrastructure/orchestration/graphify-context.js +0 -209
- package/dist/infrastructure/orchestration/memory-injector.js +0 -380
- package/dist/infrastructure/orchestration/mocks/mock-agent-repl.js +0 -388
- package/dist/infrastructure/orchestration/mocks/mock-codex-app-server.js +0 -73
- package/dist/infrastructure/orchestration/mocks/mock-synthesis.js +0 -83
- package/dist/infrastructure/orchestration/mocks/resolve-agent-spawn.js +0 -37
- package/dist/infrastructure/orchestration/opencode-adapter.js +0 -580
- package/dist/infrastructure/orchestration/progress-aggregator.js +0 -906
- package/dist/infrastructure/orchestration/readiness.js +0 -103
- package/dist/infrastructure/orchestration/session-manager.js +0 -1590
- package/dist/infrastructure/orchestration/session-persistence.js +0 -394
- package/dist/infrastructure/orchestration/thread-key-store.js +0 -127
- package/dist/infrastructure/orchestration/write-pass-store.js +0 -38
- package/dist/infrastructure/protocols/cloud-sync.js +0 -207
- package/dist/infrastructure/protocols/mcp-helpers.js +0 -25
- package/dist/infrastructure/protocols/mcp-server-impl.js +0 -3935
- package/dist/infrastructure/protocols/mcp-tools.js +0 -401
- package/dist/infrastructure/protocols/probe-port.js +0 -36
- package/dist/infrastructure/protocols/thread-phase.js +0 -105
- package/dist/infrastructure/protocols/ui/app-script-artifacts.js +0 -121
- package/dist/infrastructure/protocols/ui/app-script-audit-footer.js +0 -44
- package/dist/infrastructure/protocols/ui/app-script-audit-verify.js +0 -56
- package/dist/infrastructure/protocols/ui/app-script-composer-cost.js +0 -588
- package/dist/infrastructure/protocols/ui/app-script-context-sources.js +0 -168
- package/dist/infrastructure/protocols/ui/app-script-conversation.js +0 -67
- package/dist/infrastructure/protocols/ui/app-script-drawer.js +0 -115
- package/dist/infrastructure/protocols/ui/app-script-failover.js +0 -133
- package/dist/infrastructure/protocols/ui/app-script-github.js +0 -113
- package/dist/infrastructure/protocols/ui/app-script-observer.js +0 -104
- package/dist/infrastructure/protocols/ui/app-script-onboarding.js +0 -80
- package/dist/infrastructure/protocols/ui/app-script-overlay.js +0 -203
- package/dist/infrastructure/protocols/ui/app-script-recovery.js +0 -136
- package/dist/infrastructure/protocols/ui/app-script-render.js +0 -166
- package/dist/infrastructure/protocols/ui/app-script-share.js +0 -78
- package/dist/infrastructure/protocols/ui/app-script-sidebar.js +0 -194
- package/dist/infrastructure/protocols/ui/app-script-tour.js +0 -78
- package/dist/infrastructure/protocols/ui/app-script-wpass.js +0 -312
- package/dist/infrastructure/protocols/ui/app-script.js +0 -2998
- package/dist/infrastructure/protocols/ui/font-faces.js +0 -7
- package/dist/infrastructure/protocols/ui/styles.js +0 -2147
- package/dist/infrastructure/protocols/ui/tokens.js +0 -96
- package/dist/infrastructure/protocols/ui-page-v2.js +0 -320
- package/dist/infrastructure/protocols/verify-page.js +0 -1677
- package/dist/infrastructure/security/file-collection.js +0 -118
- package/dist/infrastructure/security/fix-code.js +0 -144
- package/dist/infrastructure/security/fix-secret.js +0 -89
- package/dist/infrastructure/security/permissions-config.js +0 -235
- package/dist/infrastructure/security/scan-types.js +0 -10
- package/dist/infrastructure/security/ship-scan.js +0 -195
- package/dist/infrastructure/security/trust-scorer.js +0 -724
- package/dist/infrastructure/security/vibe-checks.js +0 -99
- package/dist/infrastructure/storage/atomic-write.js +0 -86
- package/dist/infrastructure/storage/cross-tool-agents.js +0 -268
- package/dist/infrastructure/storage/project-thread-migration.js +0 -237
- package/dist/infrastructure/storage/project-thread-mirror.js +0 -671
- package/dist/infrastructure/storage/project-thread-reader.js +0 -409
- package/dist/infrastructure/storage/project-thread-title-backfill.js +0 -214
- package/dist/monitoring.js +0 -212
- package/dist/presentation/cli/commands/audit-command.js +0 -201
- package/dist/presentation/cli/commands/budget-command.js +0 -79
- package/dist/presentation/cli/commands/config-command.js +0 -125
- package/dist/presentation/cli/commands/context-source-command.js +0 -217
- package/dist/presentation/cli/commands/hook-command.js +0 -265
- package/dist/presentation/cli/commands/init-command.js +0 -590
- package/dist/presentation/cli/commands/memory-command.js +0 -1031
- package/dist/presentation/cli/commands/narrator-command.js +0 -101
- package/dist/presentation/cli/commands/permission-command.js +0 -105
- package/dist/presentation/cli/commands/protocols-builder.js +0 -254
- package/dist/presentation/cli/commands/protocols-command.js +0 -84
- package/dist/presentation/cli/commands/repo-map-builder.js +0 -247
- package/dist/presentation/cli/commands/report-command.js +0 -403
- package/dist/presentation/cli/commands/scan-command.js +0 -52
- package/dist/presentation/cli/commands/scan-tool-command.js +0 -110
- package/dist/presentation/cli/commands/search-command.js +0 -51
- package/dist/presentation/cli/commands/setup-command.js +0 -330
- package/dist/presentation/cli/commands/status-command.js +0 -614
- package/dist/presentation/cli/commands/team-command.js +0 -256
- package/dist/presentation/cli/commands/thread-command.js +0 -370
- package/dist/presentation/cli/commands/trust-command.js +0 -328
- package/dist/presentation/cli/commands/verify-command.js +0 -298
- package/dist/presentation/cli/error-display.js +0 -328
- package/dist/presentation/cli/repl.js +0 -805
- package/dist/presentation/cli/task-deliverable.js +0 -153
- package/dist/testing/cassette.js +0 -125
- 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
|