codehere 0.3.0-rc.9 → 0.3.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 +43 -35
- package/dist/domain/services/codebase-detector.js +1 -1
- package/dist/index.js +52 -5
- package/dist/infrastructure/hooks/session-analyzer.js +14 -5
- package/dist/infrastructure/orchestration/conductor-adapter.js +16 -5
- package/dist/infrastructure/orchestration/generic-cli-adapter.js +0 -3
- package/dist/infrastructure/protocols/mcp-server-impl.js +33 -3
- package/dist/infrastructure/security/file-collection.js +3 -0
- package/dist/presentation/cli/commands/init-command.js +245 -0
- package/dist/presentation/cli/commands/report-command.js +56 -11
- package/dist/presentation/cli/commands/status-command.js +32 -10
- package/dist/presentation/cli/repl.js +644 -319
- package/dist/ui.js +0 -3
- package/dist/utils/typo-guard.js +73 -0
- package/dist/utils/version.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -4,55 +4,63 @@
|
|
|
4
4
|
|
|
5
5
|
[](https://www.npmjs.com/package/codehere)
|
|
6
6
|
[](../LICENSE)
|
|
7
|
-
[]()
|
|
8
8
|
[]()
|
|
9
9
|
|
|
10
|
-
codehere is **a PM for your AI coding agents.** Delegate a task. codehere picks the right specialist (Claude Code, Codex, Aider, or OpenCode),
|
|
10
|
+
codehere is **a PM for your AI coding agents.** Delegate a task. codehere picks the right specialist (Claude Code, Codex, Aider, or OpenCode), spawns it, streams its work back to you, and hands you a plain-English Final Report with a **trust score on every file changed**. Today, Claude Code gets full pre-execution safety-hook enforcement on every tool call; other adapters are session-traced and reported, with per-tool hook parity planned for v0.4.
|
|
11
11
|
|
|
12
|
-
One manager, many specialists,
|
|
12
|
+
One manager, many specialists, every action recorded.
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
---
|
|
15
15
|
|
|
16
|
-
|
|
17
|
-
npm install -g codehere
|
|
18
|
-
```
|
|
16
|
+
## Recipe — setup to first audit in 30 minutes
|
|
19
17
|
|
|
20
|
-
|
|
18
|
+
> One folder convention, one prompt formula, four ready-to-use prompts.
|
|
19
|
+
> Full version: **[docs/RECIPE.md](../docs/RECIPE.md)**.
|
|
21
20
|
|
|
22
|
-
|
|
21
|
+
### Setup (once)
|
|
23
22
|
|
|
24
|
-
|
|
23
|
+
| | | |
|
|
24
|
+
|---|---|---|
|
|
25
|
+
| 1 | **Install** | `npm install -g codehere` (Node 20+) |
|
|
26
|
+
| 2 | **Detect environment** | `codehere setup` — checks API key, finds your repo |
|
|
27
|
+
| 3 | **Install the safety hook** | `codehere hook install` — every Claude Code tool call now audited |
|
|
28
|
+
| 4 | **Install at least one specialist** | Claude Code: `npm i -g @anthropic-ai/claude-code` (Codex / Aider / OpenCode also supported) |
|
|
29
|
+
| 5 | **Scaffold this repo** | `cd /your/repo && codehere init` — creates `.codehere/CONTEXT.md`, `HANDOVER.md`, `PROMPTS.md` |
|
|
30
|
+
| 6 | **Fill in CONTEXT.md** | 5 minutes. The agent will follow what's written there. |
|
|
25
31
|
|
|
26
|
-
|
|
32
|
+
You're done. `codehere start` opens the web UI at `:3773`; you delegate from there.
|
|
27
33
|
|
|
28
|
-
|
|
29
|
-
|---|---|
|
|
30
|
-
| Claude Code | `npm install -g @anthropic-ai/claude-code` |
|
|
31
|
-
| Codex CLI | `npm install -g @openai/codex` |
|
|
32
|
-
| Aider | `brew install aider` |
|
|
33
|
-
| OpenCode | `curl -fsSL https://opencode.ai/install \| bash` |
|
|
34
|
+
### The prompt formula
|
|
34
35
|
|
|
35
|
-
Then:
|
|
36
|
-
|
|
37
|
-
```bash
|
|
38
|
-
codehere start # opens http://127.0.0.1:3773 in your browser
|
|
39
36
|
```
|
|
37
|
+
Context + End State + Constraints
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
- **Context** — *"Read .codehere/CONTEXT.md"* + any specific files
|
|
41
|
+
- **End State** — the user-visible change, the test that passes, the file that exists
|
|
42
|
+
- **Constraints** — files not to touch, dependencies not to add, patterns not to use
|
|
43
|
+
|
|
44
|
+
### Folder convention (what `codehere init` writes)
|
|
45
|
+
|
|
46
|
+
| File | Purpose | Committed? |
|
|
47
|
+
|---|---|---|
|
|
48
|
+
| `.codehere/CONTEXT.md` | What this repo is. Conventions. Style. Constraints. | yes |
|
|
49
|
+
| `.codehere/HANDOVER.md` | Cross-session continuity (until F3 memory ships in v0.5). | **no** |
|
|
50
|
+
| `.codehere/PROMPTS.md` | Your library of recurring task prompts. | yes |
|
|
40
51
|
|
|
41
|
-
|
|
52
|
+
---
|
|
42
53
|
|
|
43
|
-
|
|
44
|
-
- **A task intake** with three starter prompts you can click
|
|
45
|
-
- **A cwd field** pre-filled with where you ran `codehere start`
|
|
46
|
-
- **An agent dropdown** showing which of the four are installed (and a one-click Install button for the others)
|
|
54
|
+
## What you see when you delegate a task
|
|
47
55
|
|
|
48
|
-
Pick an agent, type a task, hit **Assign**. codehere:
|
|
56
|
+
`codehere start` opens `http://127.0.0.1:3773`. Pick an agent, type a task, hit **Assign**. codehere:
|
|
49
57
|
|
|
50
58
|
1. Spawns the agent with the right headless flags
|
|
51
59
|
2. Streams the steps back as readable progress · *"Read src/auth.ts · Edit src/auth.ts · Run npm test"*
|
|
52
|
-
3.
|
|
60
|
+
3. For Claude Code tasks: routes every tool call through the codehere safety hook — you see *"🛡 5 safe · 0 blocked"* grow live. For Codex / Aider / OpenCode tasks: emits the agent's native event stream (no per-tool blocking yet)
|
|
53
61
|
4. On completion, hands you a **Final report**: files changed with `+/-` lines, trust score per file, test pass/fail, token usage
|
|
54
62
|
|
|
55
|
-
|
|
63
|
+
Claude Code tool calls are logged to `~/.codehere/audit/hook-events.jsonl`; spawned-task lifecycle (any adapter) is logged to `~/.codehere/sessions.jsonl`. Open-source MIT. Node 20+.
|
|
56
64
|
|
|
57
65
|
### Just the safety hook (no agents required)
|
|
58
66
|
|
|
@@ -65,7 +73,7 @@ codehere report # generates a shareable markdown audit report
|
|
|
65
73
|
codehere status # quick dashboard · sessions, blocks, trust trends
|
|
66
74
|
```
|
|
67
75
|
|
|
68
|
-
The hook intercepts every `Bash`, `Edit`, `Write`, `MultiEdit`, and `NotebookEdit` call from any Claude Code session running on your machine, runs it through the trust-scorer pattern bank in under 5 ms, blocks anything matching the destructive / credential / injection patterns, and appends every decision to
|
|
76
|
+
The hook intercepts every `Bash`, `Edit`, `Write`, `MultiEdit`, and `NotebookEdit` call from any Claude Code session running on your machine, runs it through the trust-scorer pattern bank in under 5 ms, blocks anything matching the destructive / credential / injection patterns, and appends every decision to an append-only JSONL audit log at `~/.codehere/audit/`. (Hook coverage today is Claude Code-only — Codex / Aider / OpenCode adapters spawn through their native CLIs and emit to `~/.codehere/sessions.jsonl`; full hook parity across all adapters is v0.4 work.)
|
|
69
77
|
|
|
70
78
|
## Commands
|
|
71
79
|
|
|
@@ -90,14 +98,14 @@ codehere scan-tool · internal · used by the hook system, not by users
|
|
|
90
98
|
|
|
91
99
|
## What it catches
|
|
92
100
|
|
|
93
|
-
The trust scorer + safety hook detect
|
|
101
|
+
The trust scorer + safety hook detect ~36 patterns across:
|
|
94
102
|
|
|
95
|
-
- SQL injection · XSS · command injection · path traversal
|
|
96
|
-
- Hardcoded secrets · weak crypto · JWT bypass
|
|
103
|
+
- SQL injection · XSS · command injection · path traversal
|
|
104
|
+
- Hardcoded secrets · API keys · weak crypto · JWT bypass
|
|
97
105
|
- Eval injection · prompt injection · credential theft
|
|
98
|
-
- Destructive operations · supply-chain patterns
|
|
106
|
+
- Destructive shell operations · supply-chain patterns
|
|
99
107
|
|
|
100
|
-
Every blocked call is logged with the reason; every allowed call is logged with the trust score.
|
|
108
|
+
Every blocked call is logged with the reason; every allowed call is logged with the trust score. Pattern bank lives in `agent/src/infrastructure/security/trust-scorer.ts`.
|
|
101
109
|
|
|
102
110
|
## MCP server
|
|
103
111
|
|
|
@@ -9,7 +9,7 @@ import { join, dirname, resolve } from 'path';
|
|
|
9
9
|
*/
|
|
10
10
|
export function findCodebaseRoot(startPath = process.cwd()) {
|
|
11
11
|
const resolvedStart = resolve(startPath);
|
|
12
|
-
|
|
12
|
+
const currentDir = resolvedStart;
|
|
13
13
|
// Try to find git root first
|
|
14
14
|
const gitRoot = findGitRoot(currentDir);
|
|
15
15
|
if (gitRoot) {
|
package/dist/index.js
CHANGED
|
@@ -15,6 +15,7 @@ import { config } from 'dotenv';
|
|
|
15
15
|
import { join, dirname } from 'path';
|
|
16
16
|
import { fileURLToPath } from 'url';
|
|
17
17
|
import { executeSetupCommand } from './presentation/cli/commands/setup-command.js';
|
|
18
|
+
import { executeInitCommand } from './presentation/cli/commands/init-command.js';
|
|
18
19
|
import { executeConfigCommand } from './presentation/cli/commands/config-command.js';
|
|
19
20
|
import { executeHelpCommand } from './presentation/cli/commands/help-command.js';
|
|
20
21
|
import { executeStatusCommand } from './presentation/cli/commands/status-command.js';
|
|
@@ -37,12 +38,19 @@ catch {
|
|
|
37
38
|
}
|
|
38
39
|
config();
|
|
39
40
|
import { getVersion } from './utils/version.js';
|
|
41
|
+
import { checkTypo } from './utils/typo-guard.js';
|
|
40
42
|
const program = new Command();
|
|
41
43
|
program
|
|
42
44
|
.name('codehere')
|
|
43
45
|
.description('A PM for your AI coding agents. Delegate a task, watch it run, get a plain-English report with a trust score on every file.')
|
|
44
46
|
.version(getVersion())
|
|
45
|
-
.configureHelp({ helpWidth: 80, sortSubcommands: true })
|
|
47
|
+
.configureHelp({ helpWidth: 80, sortSubcommands: true })
|
|
48
|
+
// rc.18: many users hit `codehere --help` first and get Commander's flat
|
|
49
|
+
// list. The grouped `codehere help` is more useful for discovery. Surface
|
|
50
|
+
// it inline so muscle-memory `--help` users find the better page.
|
|
51
|
+
// (Pin: no raw ANSI in --help output — existing commands.test.ts asserts
|
|
52
|
+
// this, see test/cli/commands.test.ts "no raw ANSI escape codes in --help".)
|
|
53
|
+
.addHelpText('after', '\nTip: `codehere help` shows commands grouped by use case (Delegate · Safety & Trust · Configuration).');
|
|
46
54
|
// Default action · show welcome, then drop into the interactive REPL on TTY.
|
|
47
55
|
// codehere offers TWO surfaces, both first-class: the CLI REPL (here) and the
|
|
48
56
|
// web UI (`codehere start`). Users pick whichever fits their workflow.
|
|
@@ -52,8 +60,23 @@ program
|
|
|
52
60
|
// One-shot delegate: `codehere "review src/auth.ts"` runs that task and exits.
|
|
53
61
|
// Useful for scripting or quick one-offs without entering the REPL.
|
|
54
62
|
if (query && query.trim()) {
|
|
63
|
+
const trimmed = query.trim();
|
|
64
|
+
// Typo guard — see src/utils/typo-guard.ts for heuristics.
|
|
65
|
+
// Returns suggestions when input looks like a typo; null falls through.
|
|
66
|
+
const known = program.commands.map((c) => c.name());
|
|
67
|
+
const suggestions = checkTypo(trimmed, known);
|
|
68
|
+
if (suggestions) {
|
|
69
|
+
console.error(`codehere: unknown command '${trimmed}'.`);
|
|
70
|
+
console.error('');
|
|
71
|
+
console.error(' Did you mean:');
|
|
72
|
+
for (const s of suggestions)
|
|
73
|
+
console.error(` codehere ${s}`);
|
|
74
|
+
console.error('');
|
|
75
|
+
console.error(` If you meant to ask a question, quote it: codehere "${trimmed}"`);
|
|
76
|
+
process.exit(1);
|
|
77
|
+
}
|
|
55
78
|
const { runOneShot } = await import('./presentation/cli/repl.js');
|
|
56
|
-
await runOneShot(
|
|
79
|
+
await runOneShot(trimmed);
|
|
57
80
|
return;
|
|
58
81
|
}
|
|
59
82
|
if (!process.stdin.isTTY) {
|
|
@@ -71,6 +94,15 @@ program
|
|
|
71
94
|
.action(async (options) => {
|
|
72
95
|
await executeSetupCommand(options.force || false);
|
|
73
96
|
});
|
|
97
|
+
// ── init ─────────────────────────────────────────────────────────────────────
|
|
98
|
+
program
|
|
99
|
+
.command('init')
|
|
100
|
+
.description('Scaffold .codehere/ in this repo · CONTEXT, HANDOVER, PROMPTS')
|
|
101
|
+
.option('-f, --force', 'Overwrite existing files in .codehere/')
|
|
102
|
+
.option('--no-gitignore', 'Skip updating .gitignore')
|
|
103
|
+
.action((options) => {
|
|
104
|
+
executeInitCommand({ force: options.force, noGitignore: !options.gitignore });
|
|
105
|
+
});
|
|
74
106
|
// ── config ───────────────────────────────────────────────────────────────────
|
|
75
107
|
program
|
|
76
108
|
.command('config')
|
|
@@ -103,8 +135,23 @@ program
|
|
|
103
135
|
.command('health')
|
|
104
136
|
.description('Check system health and metrics')
|
|
105
137
|
.option('-m, --metrics', 'Show detailed metrics')
|
|
138
|
+
.option('--json', 'Machine-readable JSON (for monitoring/CI)')
|
|
106
139
|
.action((options) => {
|
|
107
140
|
const health = performHealthCheck();
|
|
141
|
+
if (options.json) {
|
|
142
|
+
// Machine-readable: status + checks (+ metrics when --metrics).
|
|
143
|
+
// Stdout is the contract — never mix with the human banner.
|
|
144
|
+
// Use process.exitCode (not process.exit) so a piped consumer sees the
|
|
145
|
+
// full JSON before the process tears down (rc.16: Codex flagged the old
|
|
146
|
+
// process.exit call as a truncation hazard under stdout redirection).
|
|
147
|
+
const payload = { status: health.status, checks: health.checks };
|
|
148
|
+
if (options.metrics)
|
|
149
|
+
payload.metrics = getMetrics();
|
|
150
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
151
|
+
if (health.status === 'unhealthy')
|
|
152
|
+
process.exitCode = 1;
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
108
155
|
if (isMinimalStyle()) {
|
|
109
156
|
printMinimalHeader('health', '');
|
|
110
157
|
}
|
|
@@ -202,9 +249,9 @@ program
|
|
|
202
249
|
.command('report')
|
|
203
250
|
.description('Generate a shareable security audit report — narrative, not just a score')
|
|
204
251
|
.argument('[target]', 'File or directory to audit (default: current directory)')
|
|
205
|
-
.option('--out <file>', 'Write report to a specific file (default: codehere-audit.md)')
|
|
206
|
-
.option('--stdout', 'Print the report to stdout
|
|
207
|
-
.option('--json', 'Output machine-readable JSON
|
|
252
|
+
.option('--out <file>', 'Write report to a specific file (default: codehere-audit.md for markdown)')
|
|
253
|
+
.option('--stdout', 'Print the report to stdout (already the default for --json)')
|
|
254
|
+
.option('--json', 'Output machine-readable JSON to stdout (pipe to jq, etc.)')
|
|
208
255
|
.option('--simple', 'Plain English output — no jargon, for non-technical stakeholders')
|
|
209
256
|
.addHelpText('after', `
|
|
210
257
|
Examples:
|
|
@@ -76,17 +76,26 @@ export function detectAmplificationAlerts(sessions) {
|
|
|
76
76
|
continue;
|
|
77
77
|
let message = '';
|
|
78
78
|
const risk = session.amplificationRisk;
|
|
79
|
-
|
|
80
|
-
|
|
79
|
+
// rc.17 framing: don't accuse "injection" by default — that wording scares
|
|
80
|
+
// users on perfectly normal long attended sessions. Reserve the strong
|
|
81
|
+
// language for cases where the *blocked-call ratio* is high (real signal
|
|
82
|
+
// of prompts trying things they shouldn't), not just call volume.
|
|
83
|
+
// UX_OBSERVATIONS.md P1.
|
|
84
|
+
const blockRatio = session.totalCalls > 0 ? session.blockedCalls / session.totalCalls : 0;
|
|
85
|
+
if (session.blockedCalls > 0 && session.totalCalls > 20 && blockRatio > 0.05) {
|
|
86
|
+
message = `${session.totalCalls} tool calls, ${session.blockedCalls} blocked (${(blockRatio * 100).toFixed(0)}%) — high block ratio, review for injection`;
|
|
87
|
+
}
|
|
88
|
+
else if (session.blockedCalls > 0 && session.totalCalls > 20) {
|
|
89
|
+
message = `${session.totalCalls} tool calls, ${session.blockedCalls} blocked — long session, review if unattended`;
|
|
81
90
|
}
|
|
82
91
|
else if (session.totalCalls > 50) {
|
|
83
|
-
message = `${session.totalCalls} tool calls in one session —
|
|
92
|
+
message = `${session.totalCalls} tool calls in one session — high density, review if unattended`;
|
|
84
93
|
}
|
|
85
94
|
else if ((session.toolMix['Bash'] ?? 0) > 10) {
|
|
86
|
-
message = `${session.toolMix['Bash']} Bash calls in one session — elevated
|
|
95
|
+
message = `${session.toolMix['Bash']} Bash calls in one session — elevated tool-mix`;
|
|
87
96
|
}
|
|
88
97
|
else {
|
|
89
|
-
message = `${session.totalCalls} calls, ${session.blockedCalls} blocked — worth
|
|
98
|
+
message = `${session.totalCalls} calls, ${session.blockedCalls} blocked — worth a glance`;
|
|
90
99
|
}
|
|
91
100
|
alerts.push({
|
|
92
101
|
sessionId: session.sessionId,
|
|
@@ -91,7 +91,12 @@ function tokenize(prompt) {
|
|
|
91
91
|
// "add tests" "add-tests" "add_tests" "add/tests" "add:tests" "add.tests"
|
|
92
92
|
// Note: must NOT collapse the leading/trailing spaces — they're load-bearing
|
|
93
93
|
// for the boundary check below (`.includes(' ' + t + ' ')`).
|
|
94
|
-
const normalized = ' ' +
|
|
94
|
+
const normalized = ' ' +
|
|
95
|
+
prompt
|
|
96
|
+
.toLowerCase()
|
|
97
|
+
.replace(/[^a-z0-9]+/g, ' ')
|
|
98
|
+
.trim() +
|
|
99
|
+
' ';
|
|
95
100
|
return { tokens, normalized };
|
|
96
101
|
}
|
|
97
102
|
/**
|
|
@@ -240,7 +245,6 @@ function spawnConductor(prompt, options, lookup) {
|
|
|
240
245
|
},
|
|
241
246
|
};
|
|
242
247
|
buffer.push(errEvent);
|
|
243
|
-
queueMicrotask(() => emitter.emit('event', errEvent));
|
|
244
248
|
return synthesizeSession({ sessionId, startedAt, prompt, statusValue, emitter, buffer });
|
|
245
249
|
}
|
|
246
250
|
// Emit the routing-decision event FIRST so the audit log records WHY this
|
|
@@ -255,8 +259,11 @@ function spawnConductor(prompt, options, lookup) {
|
|
|
255
259
|
promptExcerpt: prompt.slice(0, 200),
|
|
256
260
|
},
|
|
257
261
|
};
|
|
262
|
+
// Buffer replay on subscribe() handles delivery to the first subscriber. Do
|
|
263
|
+
// NOT also queueMicrotask(emit) — the microtask fires AFTER subscribe attaches
|
|
264
|
+
// its handler, so the handler would receive routingEvent twice (rc.13 bug:
|
|
265
|
+
// duplicate "routing → ..." line in the REPL).
|
|
258
266
|
buffer.push(routingEvent);
|
|
259
|
-
queueMicrotask(() => emitter.emit('event', routingEvent));
|
|
260
267
|
// Find the chosen specialist adapter and spawn against it.
|
|
261
268
|
const specialist = lookup().find((a) => a.id === decision.agentId);
|
|
262
269
|
if (!specialist) {
|
|
@@ -269,7 +276,6 @@ function spawnConductor(prompt, options, lookup) {
|
|
|
269
276
|
data: { message: `Conductor picked '${decision.agentId}' but no adapter with that ID is registered.` },
|
|
270
277
|
};
|
|
271
278
|
buffer.push(errEvent);
|
|
272
|
-
queueMicrotask(() => emitter.emit('event', errEvent));
|
|
273
279
|
return synthesizeSession({ sessionId, startedAt, prompt, statusValue, emitter, buffer });
|
|
274
280
|
}
|
|
275
281
|
// Spawn the specialist session and proxy every event through.
|
|
@@ -284,7 +290,12 @@ function spawnConductor(prompt, options, lookup) {
|
|
|
284
290
|
stepsSnapshot = specialistSession.steps;
|
|
285
291
|
if (specialistSession.artifacts)
|
|
286
292
|
artifactsSnapshot = specialistSession.artifacts;
|
|
287
|
-
|
|
293
|
+
// Specialist adapters terminate with an 'exit' event — never emit
|
|
294
|
+
// 'codehere:complete' / 'codehere:error'. Without mirroring on 'exit',
|
|
295
|
+
// session.status stayed 'running' forever and the REPL printed
|
|
296
|
+
// "✗ └─ running" (rc.13 bug). Also keep the legacy types for safety in
|
|
297
|
+
// case a future adapter emits them.
|
|
298
|
+
if (evt.type === 'exit' || evt.type === 'codehere:complete' || evt.type === 'codehere:error') {
|
|
288
299
|
statusValue = specialistSession.status;
|
|
289
300
|
}
|
|
290
301
|
});
|
|
@@ -478,7 +478,6 @@ export function compileSafePattern(source, fallback, agentId) {
|
|
|
478
478
|
// Layer 1 · static shape check — this is the only fully reliable defense.
|
|
479
479
|
const danger = detectRedosShape(source);
|
|
480
480
|
if (danger) {
|
|
481
|
-
// eslint-disable-next-line no-console
|
|
482
481
|
console.warn(`[codehere] Regex for "${agentId}" rejected (${danger}) — using fallback`);
|
|
483
482
|
return fallback;
|
|
484
483
|
}
|
|
@@ -487,7 +486,6 @@ export function compileSafePattern(source, fallback, agentId) {
|
|
|
487
486
|
candidate = new RegExp(source);
|
|
488
487
|
}
|
|
489
488
|
catch {
|
|
490
|
-
// eslint-disable-next-line no-console
|
|
491
489
|
console.warn(`[codehere] Invalid regex in agents.yaml for "${agentId}" — using fallback`);
|
|
492
490
|
return fallback;
|
|
493
491
|
}
|
|
@@ -496,7 +494,6 @@ export function compileSafePattern(source, fallback, agentId) {
|
|
|
496
494
|
const start = Date.now();
|
|
497
495
|
candidate.test('a'.repeat(1000));
|
|
498
496
|
if (Date.now() - start > 100) {
|
|
499
|
-
// eslint-disable-next-line no-console
|
|
500
497
|
console.warn(`[codehere] Regex for "${agentId}" too slow (likely ReDoS) — using fallback`);
|
|
501
498
|
return fallback;
|
|
502
499
|
}
|
|
@@ -20,7 +20,8 @@
|
|
|
20
20
|
*/
|
|
21
21
|
import http from 'http';
|
|
22
22
|
import { readFileSync, existsSync, statSync, watchFile, unwatchFile } from 'fs';
|
|
23
|
-
import { join, relative } from 'path';
|
|
23
|
+
import { join, relative, dirname } from 'path';
|
|
24
|
+
import { fileURLToPath } from 'url';
|
|
24
25
|
import { collectFilesForMCP, scoreFileForMCP } from './mcp-helpers.js';
|
|
25
26
|
import { scanPayload, calculateRiskScore, AUDIT_FILE } from '../hooks/claude-sdk-hook.js';
|
|
26
27
|
import { getVersion } from '../../utils/version.js';
|
|
@@ -589,8 +590,14 @@ export function startMCPServer(options = {}) {
|
|
|
589
590
|
res.end(JSON.stringify({ error: 'Method not allowed for this path' }));
|
|
590
591
|
return;
|
|
591
592
|
}
|
|
592
|
-
// MCP endpoint
|
|
593
|
-
|
|
593
|
+
// MCP endpoint — POST jsonrpc; reject other verbs with 405 + Allow header
|
|
594
|
+
// (rc.17: was 404, which is wrong HTTP semantic — UX_OBSERVATIONS P3).
|
|
595
|
+
if (req.url === '/mcp') {
|
|
596
|
+
if (req.method !== 'POST') {
|
|
597
|
+
res.writeHead(405, { 'Content-Type': 'application/json', Allow: 'POST' });
|
|
598
|
+
res.end(JSON.stringify({ error: 'MCP endpoint accepts POST only', method: req.method }));
|
|
599
|
+
return;
|
|
600
|
+
}
|
|
594
601
|
let body = '';
|
|
595
602
|
req.on('data', (chunk) => {
|
|
596
603
|
body += chunk;
|
|
@@ -612,6 +619,29 @@ export function startMCPServer(options = {}) {
|
|
|
612
619
|
});
|
|
613
620
|
return;
|
|
614
621
|
}
|
|
622
|
+
// /api/version — small completeness item for ops monitoring (rc.17).
|
|
623
|
+
// ESM-friendly: resolve package.json relative to the compiled module's
|
|
624
|
+
// own URL via fileURLToPath. Works in both src/ (during dev) and dist/
|
|
625
|
+
// because the relative path up to package.json is the same in both.
|
|
626
|
+
if (req.url === '/api/version' && req.method === 'GET') {
|
|
627
|
+
try {
|
|
628
|
+
const moduleDir = dirname(fileURLToPath(import.meta.url));
|
|
629
|
+
const pkgPath = join(moduleDir, '..', '..', '..', 'package.json');
|
|
630
|
+
const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
|
|
631
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
632
|
+
res.end(JSON.stringify({ version: pkg.version, name: pkg.name }));
|
|
633
|
+
}
|
|
634
|
+
catch (e) {
|
|
635
|
+
// rc.19: don't leak fs paths to the public HTTP surface.
|
|
636
|
+
// rc.20: but DO log the actual error context for the operator (Codex
|
|
637
|
+
// rc.19 review caught: previous version dropped `e` and logged only a
|
|
638
|
+
// static string, costing diagnosability).
|
|
639
|
+
console.error('[/api/version] failed to read package metadata:', e);
|
|
640
|
+
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
641
|
+
res.end(JSON.stringify({ error: 'Failed to read package metadata' }));
|
|
642
|
+
}
|
|
643
|
+
return;
|
|
644
|
+
}
|
|
615
645
|
// 404
|
|
616
646
|
res.writeHead(404);
|
|
617
647
|
res.end();
|
|
@@ -62,6 +62,9 @@ export const IGNORE_DIRS = new Set([
|
|
|
62
62
|
'.cache',
|
|
63
63
|
'.codehere',
|
|
64
64
|
'.codehere-cache',
|
|
65
|
+
// Cloned reference repos used for dogfooding / comparison — not the user's code.
|
|
66
|
+
// Codehere convention; mirrors how `.git` is hidden tooling-data, not source.
|
|
67
|
+
'.oss-repos',
|
|
65
68
|
// Test fixtures / mocks (intentionally-vulnerable code used as scanner inputs)
|
|
66
69
|
'fixtures',
|
|
67
70
|
'test-fixtures',
|