codehere 0.8.0 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (119) hide show
  1. package/README.md +37 -184
  2. package/dist/application/use-cases/check-claimed-tests.js +81 -0
  3. package/dist/application/use-cases/detect-test-weakening.js +237 -0
  4. package/dist/application/use-cases/rerun-tests.js +329 -0
  5. package/dist/index.js +74 -1004
  6. package/dist/infrastructure/orchestration/artifacts-extractor.js +2 -265
  7. package/dist/presentation/cli/commands/check-command.js +251 -0
  8. package/dist/presentation/cli/commands/help-command.js +22 -286
  9. package/dist/ui.js +19 -438
  10. package/dist/utils/version.js +0 -12
  11. package/package.json +18 -27
  12. package/dist/application/use-cases/domain-trust.js +0 -110
  13. package/dist/application/use-cases/generate-final-report.js +0 -134
  14. package/dist/application/use-cases/generate-narration.js +0 -128
  15. package/dist/application/use-cases/generate-thread-title.js +0 -178
  16. package/dist/application/use-cases/narrator-prompt-template.js +0 -158
  17. package/dist/application/use-cases/thread-export.js +0 -155
  18. package/dist/application/use-cases/verify-claim.js +0 -274
  19. package/dist/application/use-cases/write-pass.js +0 -277
  20. package/dist/domain/entities/task.js +0 -61
  21. package/dist/domain/services/codebase-detector.js +0 -91
  22. package/dist/error-handler.js +0 -251
  23. package/dist/infrastructure/completion/bash-completion.js +0 -25
  24. package/dist/infrastructure/completion/fish-completion.js +0 -22
  25. package/dist/infrastructure/completion/zsh-completion.js +0 -30
  26. package/dist/infrastructure/config/config-manager.js +0 -173
  27. package/dist/infrastructure/config/product-context.js +0 -67
  28. package/dist/infrastructure/context/adapters/drawio-source.js +0 -157
  29. package/dist/infrastructure/context/adapters/figma-source.js +0 -163
  30. package/dist/infrastructure/context/adapters/github-issue-source.js +0 -159
  31. package/dist/infrastructure/context/adapters/linear-source.js +0 -190
  32. package/dist/infrastructure/context/context-resolver.js +0 -144
  33. package/dist/infrastructure/context/context-source.js +0 -114
  34. package/dist/infrastructure/context/oauth-flow.js +0 -207
  35. package/dist/infrastructure/context/oauth-store.js +0 -119
  36. package/dist/infrastructure/context/registry-bootstrap.js +0 -99
  37. package/dist/infrastructure/context/write-pass-resolver.js +0 -106
  38. package/dist/infrastructure/cost/budget-config.js +0 -113
  39. package/dist/infrastructure/cost/cost-router.js +0 -104
  40. package/dist/infrastructure/cost/spend-aggregator.js +0 -147
  41. package/dist/infrastructure/hooks/claude-sdk-hook.js +0 -888
  42. package/dist/infrastructure/hooks/hook-mode.js +0 -71
  43. package/dist/infrastructure/hooks/mirror-tool-call-to-audit.js +0 -43
  44. package/dist/infrastructure/hooks/pre-prompt-scan.js +0 -122
  45. package/dist/infrastructure/hooks/session-analyzer.js +0 -177
  46. package/dist/infrastructure/observability/execution-tracer.js +0 -227
  47. package/dist/infrastructure/orchestration/agent-adapter.js +0 -13
  48. package/dist/infrastructure/orchestration/agent-installer.js +0 -101
  49. package/dist/infrastructure/orchestration/agents-config-seed.js +0 -56
  50. package/dist/infrastructure/orchestration/aider-adapter.js +0 -447
  51. package/dist/infrastructure/orchestration/cassette-recorder.js +0 -120
  52. package/dist/infrastructure/orchestration/claude-code-adapter.js +0 -478
  53. package/dist/infrastructure/orchestration/codex-adapter.js +0 -538
  54. package/dist/infrastructure/orchestration/conductor-adapter.js +0 -377
  55. package/dist/infrastructure/orchestration/context-handoff.js +0 -144
  56. package/dist/infrastructure/orchestration/control/codex-control-surface.js +0 -99
  57. package/dist/infrastructure/orchestration/control/control-surface.js +0 -15
  58. package/dist/infrastructure/orchestration/failover-classifier.js +0 -151
  59. package/dist/infrastructure/orchestration/failover-config.js +0 -77
  60. package/dist/infrastructure/orchestration/failover-policy.js +0 -106
  61. package/dist/infrastructure/orchestration/gemini-adapter.js +0 -583
  62. package/dist/infrastructure/orchestration/generic-cli-adapter.js +0 -558
  63. package/dist/infrastructure/orchestration/graphify-context.js +0 -254
  64. package/dist/infrastructure/orchestration/memory-injector.js +0 -380
  65. package/dist/infrastructure/orchestration/mocks/mock-agent-repl.js +0 -233
  66. package/dist/infrastructure/orchestration/mocks/mock-codex-app-server.js +0 -73
  67. package/dist/infrastructure/orchestration/mocks/mock-synthesis.js +0 -83
  68. package/dist/infrastructure/orchestration/mocks/resolve-agent-spawn.js +0 -37
  69. package/dist/infrastructure/orchestration/opencode-adapter.js +0 -510
  70. package/dist/infrastructure/orchestration/progress-aggregator.js +0 -689
  71. package/dist/infrastructure/orchestration/readiness.js +0 -103
  72. package/dist/infrastructure/orchestration/session-manager.js +0 -1091
  73. package/dist/infrastructure/orchestration/session-persistence.js +0 -258
  74. package/dist/infrastructure/orchestration/thread-key-store.js +0 -127
  75. package/dist/infrastructure/orchestration/write-pass-store.js +0 -46
  76. package/dist/infrastructure/protocols/cloud-sync.js +0 -207
  77. package/dist/infrastructure/protocols/mcp-helpers.js +0 -25
  78. package/dist/infrastructure/protocols/mcp-server-impl.js +0 -2853
  79. package/dist/infrastructure/protocols/probe-port.js +0 -36
  80. package/dist/infrastructure/protocols/thread-phase.js +0 -105
  81. package/dist/infrastructure/protocols/ui-page-v2.js +0 -4418
  82. package/dist/infrastructure/protocols/ui-page.js +0 -2377
  83. package/dist/infrastructure/protocols/verify-page.js +0 -983
  84. package/dist/infrastructure/security/file-collection.js +0 -118
  85. package/dist/infrastructure/security/input-validator.js +0 -152
  86. package/dist/infrastructure/security/permissions-config.js +0 -234
  87. package/dist/infrastructure/security/trust-scorer.js +0 -657
  88. package/dist/infrastructure/storage/cross-tool-agents.js +0 -268
  89. package/dist/infrastructure/storage/embeddings-path.js +0 -37
  90. package/dist/infrastructure/storage/project-thread-migration.js +0 -237
  91. package/dist/infrastructure/storage/project-thread-mirror.js +0 -524
  92. package/dist/infrastructure/storage/project-thread-reader.js +0 -172
  93. package/dist/infrastructure/storage/project-thread-title-backfill.js +0 -214
  94. package/dist/infrastructure/storage/task-repository.js +0 -206
  95. package/dist/monitoring.js +0 -212
  96. package/dist/presentation/cli/commands/audit-command.js +0 -126
  97. package/dist/presentation/cli/commands/budget-command.js +0 -79
  98. package/dist/presentation/cli/commands/config-command.js +0 -125
  99. package/dist/presentation/cli/commands/context-source-command.js +0 -198
  100. package/dist/presentation/cli/commands/hook-command.js +0 -265
  101. package/dist/presentation/cli/commands/init-command.js +0 -578
  102. package/dist/presentation/cli/commands/memory-command.js +0 -1031
  103. package/dist/presentation/cli/commands/narrator-command.js +0 -101
  104. package/dist/presentation/cli/commands/permission-command.js +0 -116
  105. package/dist/presentation/cli/commands/protocols-builder.js +0 -254
  106. package/dist/presentation/cli/commands/protocols-command.js +0 -84
  107. package/dist/presentation/cli/commands/repo-map-builder.js +0 -247
  108. package/dist/presentation/cli/commands/report-command.js +0 -387
  109. package/dist/presentation/cli/commands/scan-tool-command.js +0 -94
  110. package/dist/presentation/cli/commands/setup-command.js +0 -307
  111. package/dist/presentation/cli/commands/status-command.js +0 -661
  112. package/dist/presentation/cli/commands/thread-command.js +0 -288
  113. package/dist/presentation/cli/commands/trust-command.js +0 -306
  114. package/dist/presentation/cli/commands/verify-command.js +0 -233
  115. package/dist/presentation/cli/error-display.js +0 -336
  116. package/dist/presentation/cli/repl.js +0 -797
  117. package/dist/presentation/cli/task-deliverable.js +0 -127
  118. package/dist/testing/cassette.js +0 -125
  119. package/dist/testing/replay-adapter.js +0 -166
package/README.md CHANGED
@@ -1,214 +1,67 @@
1
1
  # codehere
2
2
 
3
- > **Your AI agents shouldn't lock you in.**
4
- > Codehere keeps your memory, audit, safety, and reports portable across every coding agent you use.
3
+ > **Re-runs the tests an AI says passed, and tells you whether they did.**
4
+ > `codehere check` on your machine, or the GitHub Action on every pull request. No account.
5
5
 
6
- [![npm](https://img.shields.io/npm/v/codehere)](https://www.npmjs.com/package/codehere)
7
- [![License: MIT](https://img.shields.io/badge/License-MIT-yellow)](../LICENSE)
8
- [![Internal tests](https://img.shields.io/badge/internal_tests-1200%2F1200-brightgreen)]()
9
- [![Honest status](https://img.shields.io/badge/v0.7--honest--reframe-2026--05--19-blue)](../docs/TRUTHFUL_STATE_2026-05-19.md)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow)](https://github.com/muhammadegaa/codehere/blob/main/LICENSE)
10
7
 
11
- **Codehere is the vendor-neutral layer between you and the coding agents you already use** — Claude Code, Codex, Aider, Gemini, OpenCode. One `npm install`. Local-first, OSS, no account, no IAM, no compliance prereqs. Switch agent, keep your memory. End the day with one Final Report covering everything they did.
8
+ `codehere check` re-runs your project's tests itself and reports the real result, so you do not have to take an agent's "all tests pass" on trust. It detects `npm test`, `node --test`, `pytest`, `go test`, `cargo test` and vitest or jest configs.
12
9
 
13
- All 5 adapters (Claude Code, Codex, Aider, Gemini, OpenCode) ship adapter code for session-tracing, Final Reports, and trust scoring. Claude Code additionally gets full pre-execution safety-hook enforcement on every tool call via the Claude Agent SDK. The *memory*, *audit log*, *failover*, and *Final Report* surfaces are wired across all 5 adapters today.
14
-
15
- > **Honest status (2026-05-19)** — The 1200 internal tests verify codehere's code against codehere's own assumptions about what each adapter emits. They do **not** systematically verify against real-adapter outputs in production. Real-adapter contract verification is the v0.7 release work — see [`docs/TRUTHFUL_STATE_2026-05-19.md`](../docs/TRUTHFUL_STATE_2026-05-19.md) for the verified / assumed / unverified classification of every surface.
16
-
17
- The enterprise tier is owned by [OpenAI Frontier](https://openai.com/index/introducing-openai-frontier/) (8-14 weeks of deploy prereqs, sales-team gated) and [Anthropic Cowork](https://www.anthropic.com/product/claude-cowork) (paid Claude subscription, single-vendor). Codehere fills the gap they structurally can't reach: **the individual / small-team / no-IT-department tier, vendor-neutral**.
18
-
19
- > See [`docs/STRATEGIC_VISION.md`](../docs/STRATEGIC_VISION.md) Appendix C and [`docs/figures/positioning-matrix.svg`](../docs/figures/positioning-matrix.svg) for the full positioning chart.
20
-
21
- One layer, every agent, your work stays portable.
22
-
23
- ---
24
-
25
- ## Recipe — setup to first audit in 30 minutes
26
-
27
- > One folder convention, one prompt formula, four ready-to-use prompts.
28
- > Full version: **[docs/RECIPE.md](../docs/RECIPE.md)**.
29
-
30
- ### Setup (once)
31
-
32
- | | | |
33
- |---|---|---|
34
- | 1 | **Install** | `npm install -g codehere` (Node 20+) — v0.6.0 is the current stable |
35
- | 2 | **Detect environment** | `codehere setup` — checks API key, finds your repo |
36
- | 3 | **Install the safety hook** | `codehere hook install` — every Claude Code tool call now audited |
37
- | 4 | **Install at least one specialist** | Claude Code: `npm i -g @anthropic-ai/claude-code` (Codex / Aider / OpenCode / Gemini also supported · see [docs/integrations/](../docs/integrations/) for Cursor MCP setup) |
38
- | 5 | **Scaffold this repo** | `cd /your/repo && codehere init` — creates `.codehere/` with the F3 memory tree (working / episodic / semantic / personal / candidates) plus `AGENTS.md` (project briefing) and `skills/` (prompt templates) |
39
- | 6 | **Fill in AGENTS.md** | 5 minutes. The agent will follow what's written there. |
40
- | 7 | **Build the symbol map (optional)** | `codehere memory repo-map` — JSON map of every top-level export in your repo, ranked by reference count. Spawned agents read it on entry instead of cold-grepping. |
41
-
42
- You're done. `codehere start` opens the web UI at `:3773`; you delegate from there.
43
-
44
- ### The prompt formula
10
+ ## Install
45
11
 
46
12
  ```
47
- Context + End State + Constraints
48
- ```
49
-
50
- - **Context** — *"Read .codehere/CONTEXT.md"* + any specific files
51
- - **End State** — the user-visible change, the test that passes, the file that exists
52
- - **Constraints** — files not to touch, dependencies not to add, patterns not to use
53
-
54
- ### Folder convention (what `codehere init` writes)
55
-
56
- The v0.6 layout — F3 project memory:
57
-
58
- | Path | Purpose | Committed? |
59
- |---|---|---|
60
- | `.codehere/AGENTS.md` *(or legacy `CONTEXT.md`)* | What this repo is. Conventions. Style. Constraints. | yes |
61
- | `.codehere/memory/working/session.md` *(or legacy `HANDOVER.md`)* | What's in flight in the current session. | **no** |
62
- | `.codehere/memory/episodic/AGENT_LEARNINGS.jsonl` | Symlink to your full session history at `~/.codehere/sessions.jsonl`. | (symlink) |
63
- | `.codehere/memory/episodic/repo_map.json` | Ranked symbol map. Built by `codehere memory repo-map`. | yes |
64
- | `.codehere/memory/semantic/lessons.jsonl` | Project-specific lessons promoted from episodic patterns. | yes |
65
- | `.codehere/memory/personal/preferences.json` | Per-developer prefs. | **no** |
66
- | `.codehere/skills/<name>/SKILL.md` *(or legacy flat `PROMPTS.md`)* | Prompt templates — one folder per skill. | yes |
67
- | `.codehere/protocols/permissions.md` + `hook_patterns.json` | Safety policy for the PreToolUse hook. | yes |
68
-
69
- Upgrading from v0.4.1? Run `codehere memory init` once — it migrates `CONTEXT/HANDOVER/PROMPTS` into the new layout and leaves symlinks at the old paths so back-compat holds. `--dry-run` shows what would move first.
70
-
71
- ---
72
-
73
- ## What you see when you delegate a task
74
-
75
- `codehere start` opens `http://127.0.0.1:3773`. Pick an agent, type a task, hit **Assign**. codehere:
76
-
77
- 1. Spawns the agent with the right headless flags
78
- 2. Streams the steps back as readable progress · *"Read src/auth.ts · Edit src/auth.ts · Run npm test"*
79
- 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 / Gemini tasks: emits the agent's native event stream (no per-tool blocking yet)
80
- 4. On completion, hands you a **Final report**: files changed with `+/-` lines, trust score per file, test pass/fail, token usage
81
-
82
- 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+.
83
-
84
- ### Just the safety hook (no agents required)
85
-
86
- If you only want the audit-and-block layer for AI tools you already use:
87
-
88
- ```bash
89
- codehere hook install # activates the PreToolUse hook in your global ~/.claude/settings.json
90
- codehere trust . # static trust scan of the current directory
91
- codehere report # generates a shareable markdown audit report
92
- codehere status # quick dashboard · sessions, blocks, trust trends
13
+ npx codehere check
93
14
  ```
94
15
 
95
- 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/`. Codex, Aider, OpenCode, and Gemini adapters spawn through their native CLIs and emit to `~/.codehere/sessions.jsonl` with per-tool tracing via the cross-vendor mirror hook.
96
-
97
- ## Memory · F3 (v0.6)
98
-
99
- Once you've run `codehere init` in a project, every spawned agent — Claude Code, Codex, Aider, OpenCode, or Gemini — automatically reads the project's memory on entry. The agent starts warm with the briefing, recent lessons, and the most-referenced symbols in your codebase, instead of cold-grepping every session.
16
+ The npm release is 1.0.1, which has `check` with `--json` and `--proof`. The options `--base` and `--claim` below are in this repository (version 1.0.2) and are not published yet. To use them now, build from source: `cd agent && npm ci && npm run build && node dist/index.js check`.
100
17
 
101
- **What gets injected, in priority order:**
102
- 1. **`.codehere/AGENTS.md`** — your project briefing (conventions, style, constraints)
103
- 2. **`.codehere/memory/working/session.md`** — what's in flight right now (per-machine, gitignored)
104
- 3. **`.codehere/memory/semantic/lessons.jsonl`** — graduated project-wide lessons
105
- 4. **`.codehere/memory/episodic/repo_map.json`** — top-N most-referenced symbols (run `codehere memory repo-map` to build / refresh)
18
+ ## The four commands
106
19
 
107
- The injection is token-budget-aware (default ~3000 tokens, drops lower-priority sections first to fit; AGENTS.md is never dropped). Pass `--no-memory-prefix` if you want the bare prompt for a one-off task.
108
-
109
- **Inspect what would inject** before spawning anything:
110
- ```bash
111
- codehere memory show injection
112
20
  ```
113
-
114
- **Add a lesson** so future spawns learn from it:
115
- ```bash
116
- codehere memory graduate --add "auth tests live alongside source files"
21
+ codehere check # re-run the tests here
22
+ codehere check --base origin/main # also read everything this branch changed
23
+ codehere check --claim "<the agent's summary>" # flag test files it named that do not exist
24
+ codehere check --proof # a receipt you can paste into a pull request
117
25
  ```
118
26
 
119
- **See everything codehere has accumulated:**
120
- ```bash
121
- codehere memory show # one-line summary per layer
122
- codehere memory show semantic # numbered lesson list
123
- codehere memory show episodic # session count + top symbols
124
- codehere memory show protocols # safety patterns by category
125
- ```
27
+ `codehere help check` lists the options. `--json` adds a `verdict` (`pass`, `fail`, `unverified` or `skipped`) and the receipt text.
126
28
 
127
- **Other memory commands:**
128
- - `codehere memory init` — migrate the v0.4.1 layout (`CONTEXT.md` / `HANDOVER.md` / `PROMPTS.md`) into the F3 layout (one shot, leaves back-compat symlinks; `--dry-run` previews)
129
- - `codehere memory repo-map [--rebuild]` — build the symbol map; currently TS-native MVP
130
- - `codehere protocols export [--rebuild]` — write the trust-scorer pattern bank to `.codehere/protocols/hook_patterns.json` for inspection / cross-tool ingestion
29
+ Exit codes:
131
30
 
132
- ## Going deeper
31
+ - **0**: the tests ran and passed, no test was weakened, no named test file is missing, and the weakened-test scan finished.
32
+ - **1**: the tests failed; the runner reported no test that ran (0 tests, or every test skipped); the change made a test check nothing (an assertion swapped for `assert True` or `assert.ok(true)`, a skip marker, an assertion commented out, or a `return` above it); or a test file named in `--claim` is not in the repository. Usage errors (an unknown `--base`, a missing folder, an unknown option) also exit 1, with the message on stderr and nothing on stdout, and so does an internal error. Pass the claim as `--claim=<text>`: in `--claim --json` the claim is the text `--json`.
33
+ - **2**: nothing could be verified: the run timed out, no test command was recognized, the runner is not installed, the re-run was turned off, or the weakened-test scan could not run (git is not installed, git could not read the repository, or the diff could not be read).
133
34
 
134
- For technical readers — the living documents that describe codehere as a system, not just a CLI:
35
+ Settings, as environment variables: `CODEHERE_TEST_CMD` replaces the detected test command (split on whitespace, no shell); `CODEHERE_RERUN_TIMEOUT_MS` sets the time limit in whole milliseconds (default 120000, two minutes, also used for a value that is not a whole number from 1 to 2147483647); `CODEHERE_RERUN_TESTS=0` turns the re-run off. Detection covers `npm test` (a `test` script in `package.json`), `go test ./...`, `cargo test`, `pytest` (with `pytest.ini`, `conftest.py` or a pytest section in `pyproject.toml` or `setup.cfg`), `vitest.config.ts`/`.js`, `jest.config.js`/`.ts`, and `node --test` for `*.test.js` files.
135
36
 
136
- | Doc | What it is |
137
- |---|---|
138
- | [`docs/decisions/`](../docs/decisions/) | ADR registry — every locked architectural decision (audit-first moat, supervisor-agent positioning, local-first, F3 memory layout, generic-CLI adapter, ...) numbered, dated, with rejected alternatives + rollback triggers |
139
- | [`docs/THREAT_MODEL.md`](../docs/THREAT_MODEL.md) | Every attack surface, current mitigation, residual risk, planned hardening — npm supply chain, hook bypass, symlink redirect, audit log tampering, MCP server, prompt injection |
140
- | [`docs/CAPABILITIES.md`](../docs/CAPABILITIES.md) | Honest matrix of what each adapter can and cannot do today — Claude Code, Codex, Aider, OpenCode, Gemini, Generic CLI — across spawn, stream, per-tool block, audit, F3 memory, auth |
141
- | [`agent/REPO_MAP.md`](./REPO_MAP.md) | One-line-per-file index of the 54 source files with their top-level exports — generated, regenerated via `npm run repomap` after renames |
37
+ "No test ran" is read from the runner's own summary for node:test, pytest, jest, vitest and Go's `[no test files]`. A Go package with test files but no tests (`[no tests to run]`), `cargo test` with 0 tests, jest with `--passWithNoTests`, mocha, and a test script that only prints "No tests found" read as a pass. A test script that leaves a background process holding its output open is reported as timed out, even when the tests passed.
142
38
 
143
- ## Commands
39
+ ## GitHub Action
144
40
 
145
- ```
146
- codehere start · launch the task intake web UI at :3773
147
- codehere task <prompt> · spawn a task from the CLI (requires `start` running)
148
- codehere kill <sessionId> · kill a running task
149
- codehere serve · start as MCP server for Claude Desktop, Cursor, etc.
150
- codehere status · dashboard · sessions, blocked calls, audit trends
151
- codehere trust [path] · static trust scan · score 0-100, grade A-F
152
- codehere report · generate a shareable markdown audit report
153
- codehere hook · install/uninstall/inspect the PreToolUse safety hook
154
- codehere permission · manage this folder's allow-list (skip false-positive blocks)
155
- codehere budget · daily-spend ceiling · `set <usd>` blocks paid spawns at the cap
156
- codehere narrator · customize the PM narrator prompt (~/.codehere/narrator-prompt.md)
157
- codehere setup · interactive first-run setup wizard
158
- codehere config · manage configuration and preferences
159
- codehere health · system health check
160
- codehere help · show help (use --all for hidden commands)
161
- codehere completion <shell> · generate bash/zsh/fish completion script
162
- codehere scan-tool · internal · used by the hook system, not by users
163
- ```
41
+ A GitHub Action that posts the receipt on each pull request is in [`action/`](https://github.com/muhammadegaa/codehere/tree/main/action). It runs `npx codehere@<version> check --json --base origin/<base> --claim=<PR body>`. Its default version is 1.0.2, which is not on npm yet, so until 1.0.2 is published the Action reports `unverified` and the job stays green with a warning. The 120-second default time limit applies, and the Action has no input to change it. With the default `fail-on-unverified: false`, a pull request that makes the suite unrunnable (removes the test script, breaks the runner) gets `unverified` and a green job with a warning, and a `skipped` verdict (the re-run turned off in the workflow) gets a green job with no warning. Set `fail-on-unverified: true` to fail the job on `unverified`. It is not published to the marketplace.
164
42
 
165
- `codehere --help` for full options on each. `codehere status --weekly` produces a shareable 7-day agent-activity recap. `codehere status --export jsonl > audit.jsonl` exports the raw audit feed.
43
+ ## What it does not catch
166
44
 
167
- ## What it catches
45
+ - **Weakened tests.** The detector reads Python (`test_*.py`, `*_test.py`), JavaScript and TypeScript (`*.test.*`, `*.spec.*`), Go (`*_test.go`) and PHP (`*Test.php`) test files, and files under `test`, `tests` or `__tests__`. It flags an assertion replaced by an always-true one (`assert True`, `assert.ok(true)`, `assertTrue(True)`, a value compared with itself), a skip marker (including `skipif(True)`, `it.only(`, `test.only(` or `describe.only(` outside strings and `//` comments (including `test.describe.only(`), and in JavaScript, TypeScript and Vue files `fit(` or `fdescribe(` at the start of a statement, after `=>`, or after `await`, `return` or `void`; variants such as `test.only.each` are not flagged, a JavaScript helper or method named `fit` at the start of a statement is flagged, and `.vue` files are read only under `test`, `tests` or `__tests__`), a commented-out assertion and an early `return`. It does not flag a deleted assertion, a deleted test or test file, a loosened expectation, an always-true check written another way, or an edited test script. A very small test file that is renamed and changed in the same commit can read to git as a new file, and a new file is not scanned. Without `--base` it sees only uncommitted changes, so a weakened test that is already committed passes; with `--base <ref>` it sees everything since the branch left that ref. Only test files are diffed; if their diff cannot be read (over 64 MB), the run is reported as unverified. In a folder with no git repository and no `--base`, or before the first commit, the scan is skipped and the output says so. When git is missing or fails on a repository that is there, or `--base` is given outside a repository, the run is reported as unverified.
46
+ - **`--claim`** finds test files named in the summary in plain text, in quotes or backticks, in markdown bold or italics, in brackets, in `path::test` form and with a `:line` suffix. It ignores absolute paths, Windows backslash paths and Java `*Test.java` names. A file counts as present when any file with the same name exists in the checkout (outside `node_modules`, `dist`, `build`, `vendor`, `target` and virtual environments), so a file in the wrong folder is not reported. The search goes 8 folders deep and stops after 50,000 entries, so a named file deeper than that, or in a larger checkout, is reported as missing.
47
+ - **A passing suite with a broken build, and code the tests do not cover.** A receipt says what the test suite showed, not that the software is correct.
48
+ - **Who controls the tests.** `check` runs the project's own test command, so whoever controls that command controls the result, and whoever controls the environment can point the weakened-test scan at another tree (`GIT_DIR`, `GIT_WORK_TREE`). For a pull request from someone you do not trust, a receipt is not evidence. In the Action, use the `pull_request` trigger, not `pull_request_target`.
168
49
 
169
- The trust scorer + safety hook detect ~36 patterns across:
50
+ ## What has been measured
170
51
 
171
- - SQL injection · XSS · command injection · path traversal
172
- - Hardcoded secrets · API keys · weak crypto · JWT bypass
173
- - Eval injection · prompt injection · credential theft
174
- - Destructive shell operations · supply-chain patterns
52
+ - **False-done eval, 22 cases.** 14 false "tests pass" or "all green" claims (6 reconstructed from public posts, 8 planted) and 8 true ones. `codehere check` caught 12 of the 14 false claims, missed 2 (a green suite with a broken build, and green tests with a broken production job), and raised 0 false alarms on the 8 true ones. Result: [`agent/eval/false-done-result.json`](https://github.com/muhammadegaa/codehere/blob/main/agent/eval/false-done-result.json), produced by `node eval/false-done.mjs`. The cases were written to fit the detectors, so this is a small set and not a general rate.
53
+ - **How often agent pull requests carry a failing claim: not measured.** A pre-registered study (S11) had two models label 299 public agent pull requests. They disagreed (Cohen's kappa 0.092), so the study reports no rate. The report, with the method and its limits: [`agent/eval/promise-verification/studies/S11-demand/REPORT.md`](https://github.com/muhammadegaa/codehere/blob/main/agent/eval/promise-verification/studies/S11-demand/REPORT.md).
175
54
 
176
- 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`.
55
+ ## Development
177
56
 
178
- ## MCP server
179
-
180
- ```bash
181
- codehere serve
182
57
  ```
183
-
184
- Exposes 4 read-only tools to any MCP client (Claude Desktop, Cursor, Zed, Continue, Cline, etc.):
185
-
186
- - `codehere_trust` · score a file or code snippet
187
- - `codehere_scan` · scan text for injection / threat patterns
188
- - `codehere_review` · scan a directory and return riskiest files
189
- - `codehere_audit` · read the local hook audit log
190
-
191
- `codehere serve --show-config` prints the JSON block to paste into Claude Desktop's MCP settings.
192
-
193
- ## Architecture, story, and contribution
194
-
195
- For depth beyond this README:
196
-
197
- - **[docs/STRATEGIC_VISION.md](../docs/STRATEGIC_VISION.md)** · 20-year invariants + v4 positioning (individual-tier vendor-neutral) · figures in `docs/figures/`
198
- - **[docs/PRD.md](../docs/PRD.md)** · master decision document · features, architecture, roadmap
199
- - **[docs/SECURITY_AUDIT_2026-05-01.md](../docs/SECURITY_AUDIT_2026-05-01.md)** · first-party security audit · OWASP Top 10 + STRIDE · 0 critical / 0 high · 2 medium fixed inline · methodology disclosed · reproducible
200
- - **[docs/archive/](../docs/archive/)** · earlier living docs (CODEHERE spec, EXPERIENCE story, AUDIT) preserved for history
201
- - **[CONTRIBUTING.md](../CONTRIBUTING.md)** · how to contribute · setup, commit discipline, testing philosophy
202
- - **[SECURITY.md](../SECURITY.md)** · responsible disclosure · `security@codehere.uk`
203
- - **[CODE_OF_CONDUCT.md](../CODE_OF_CONDUCT.md)** · community standards
204
- - **[CHANGELOG.md](../CHANGELOG.md)** · per-version release notes
205
-
206
- ## Requirements
207
-
208
- - **Node.js 20+** (pinned in `.nvmrc` and `engines.node`)
209
- - **macOS or Linux** · Windows via WSL works for development
210
- - **Optional but recommended:** at least one supported agent (Claude Code, Codex, Aider, OpenCode, or Gemini) installed before running `codehere start`
58
+ cd agent
59
+ npm ci
60
+ npm test # build, then vitest
61
+ npm run lint
62
+ node eval/false-done.mjs
63
+ ```
211
64
 
212
65
  ## License
213
66
 
214
- MIT · see [LICENSE](../LICENSE).
67
+ MIT. See [`LICENSE`](https://github.com/muhammadegaa/codehere/blob/main/LICENSE).
@@ -0,0 +1,81 @@
1
+ /**
2
+ * Did the agent name a test file that is not in the repo?
3
+ *
4
+ * "The webhook tests passed" while the file never existed is a lie a green re-run cannot see:
5
+ * the suite passes because it never contained the test. Given the agent's own words, this lists
6
+ * the test files it named that exist nowhere in the checkout. A file counts as present when any
7
+ * file with the same name exists (so a wrong folder is not flagged). Deterministic, no model.
8
+ * It cannot tell that a named test never ran, only that it is not there.
9
+ */
10
+ import { readdirSync } from 'node:fs';
11
+ import { basename, join } from 'node:path';
12
+ const TEST_FILE = /^(?:[\w.@-]+\/)*(?:test_[\w.-]+\.py|[\w.-]+_test\.(?:py|go)|[\w.-]+\.(?:test|spec)\.[cm]?[jt]sx?|[\w-]+Test\.php)$/;
13
+ const SKIP_DIRS = new Set([
14
+ 'node_modules',
15
+ '.git',
16
+ 'dist',
17
+ 'build',
18
+ '.venv',
19
+ 'venv',
20
+ '__pycache__',
21
+ 'vendor',
22
+ 'target',
23
+ ]);
24
+ const MAX_DEPTH = 8;
25
+ const MAX_ENTRIES = 50_000;
26
+ /** Separators: whitespace, quotes, backticks, brackets of every kind, markdown emphasis and table pipes. */
27
+ const SEPARATORS = /[\s`'"(),;[\]<>{}|*]+/;
28
+ /** `tests/test_x.py::test_y` → `tests/test_x.py`; `a.spec.ts:42:3` → `a.spec.ts`; `_a.test.js_` → `a.test.js`; trailing punctuation dropped. */
29
+ function cleanToken(raw) {
30
+ let t = raw.replace(/^\.\//, '').replace(/::.*$/, '');
31
+ t = t.replace(/[.:…!?]+$/, '').replace(/(:\d+)+$/, '');
32
+ if (t.length > 2 && t.startsWith('_') && t.endsWith('_'))
33
+ t = t.slice(1, -1);
34
+ return t;
35
+ }
36
+ export function extractClaimedTestFiles(text) {
37
+ const found = [];
38
+ for (const raw of text.split(SEPARATORS)) {
39
+ const token = cleanToken(raw);
40
+ if (token.startsWith('/') || token.split('/').includes('..'))
41
+ continue;
42
+ if (TEST_FILE.test(token) && !found.includes(token))
43
+ found.push(token);
44
+ }
45
+ return found;
46
+ }
47
+ function namesInRepo(cwd) {
48
+ const names = new Set();
49
+ let seen = 0;
50
+ const walk = (dir, depth) => {
51
+ if (depth > MAX_DEPTH || seen > MAX_ENTRIES)
52
+ return;
53
+ let entries;
54
+ try {
55
+ entries = readdirSync(dir, { withFileTypes: true });
56
+ }
57
+ catch {
58
+ return;
59
+ }
60
+ for (const e of entries) {
61
+ seen++;
62
+ if (e.isDirectory()) {
63
+ if (!SKIP_DIRS.has(e.name))
64
+ walk(join(dir, e.name), depth + 1);
65
+ }
66
+ else {
67
+ names.add(e.name);
68
+ }
69
+ }
70
+ };
71
+ walk(cwd, 0);
72
+ return names;
73
+ }
74
+ export function findMissingClaimedTests(cwd, claim) {
75
+ const claimed = extractClaimedTestFiles(claim);
76
+ if (!claimed.length)
77
+ return [];
78
+ const present = namesInRepo(cwd);
79
+ return claimed.filter((p) => !present.has(basename(p)));
80
+ }
81
+ //# sourceMappingURL=check-claimed-tests.js.map
@@ -0,0 +1,237 @@
1
+ /**
2
+ * Did the change make a test pass without checking anything?
3
+ *
4
+ * A green re-run proves nothing when the agent got there by editing the test: swapping a failing
5
+ * assertion for `assert True`, skipping the test, commenting the assertion out, or returning above
6
+ * it. This reads `git diff` against HEAD and flags such lines added to test files that already
7
+ * existed. It is a deterministic diff read (no model), so it is measured evidence (DESIGN.md §1).
8
+ * By default it reads uncommitted changes against HEAD. With a base ref it reads everything since
9
+ * the branch left that base (committed or not), which is the view a pull request needs.
10
+ */
11
+ import { execFileSync } from 'node:child_process';
12
+ import { existsSync, readFileSync } from 'node:fs';
13
+ import { dirname, join, resolve } from 'node:path';
14
+ const TEST_PATH = /(^|\/)(test_[^/]*\.py|[^/]*_test\.py|[^/]*\.(test|spec)\.[cm]?[jt]sx?|[^/]*_test\.go|[^/]*Test\.php)$|(^|\/)(__tests__|tests?)\//;
15
+ const TRIVIAL = [
16
+ // `assert True`, `assert(True)`, `assert True, "message"`
17
+ /^assert\s*\(?\s*(True|1|not\s+False)\s*\)?\s*(,.*)?(#.*)?$/,
18
+ /^assert\s+([\w.'"]+)\s*==\s*\1\s*(#.*)?$/,
19
+ /\bexpect\(\s*(true|1)\s*\)\s*\.\s*(toBe|toEqual|toStrictEqual)\(\s*(true|1)\s*\)/,
20
+ /\bexpect\(\s*([\w.'"]+)\s*\)\s*\.\s*(toBe|toEqual|toStrictEqual)\(\s*\1\s*\)/,
21
+ /\bexpect\(\s*true\s*\)\s*\.\s*toBeTruthy\(\)/,
22
+ /\bassert(\.ok)?\(\s*true\s*\)/,
23
+ /\bassert\.(equal|strictEqual|deepEqual|deepStrictEqual)\(\s*([\w.'"]+)\s*,\s*\2\s*\)/,
24
+ /\bassert(Equal|Equals|Same)\(\s*([\w.'"]+)\s*,\s*\2\s*\)/,
25
+ /\bassertTrue\(\s*(true|True)\s*\)/,
26
+ ];
27
+ const SKIP = [
28
+ /@pytest\.mark\.(skip|xfail)\b/,
29
+ /@pytest\.mark\.skipif\(\s*True\b/,
30
+ /\bpytest\.(skip|xfail)\(/,
31
+ /@unittest\.skip/,
32
+ /\b(it|test|describe)\.(skip|todo)\(/,
33
+ /\b(xit|xtest|xdescribe)\(/,
34
+ /\bmarkTest(Skipped|Incomplete)\(/,
35
+ /\bt\.Skip(f|Now)?\(/,
36
+ // node:test options: { skip: true } / { skip: 'reason' } / { todo: true }; a conditional skip is left alone
37
+ /\b(skip|todo)\s*:\s*(true\b|['"`])/,
38
+ ];
39
+ /** `.only` makes jest, mocha and Playwright skip every other test: the original pattern, on any test file. */
40
+ const ONLY = /\b(it|test|describe)\.only\(/;
41
+ /** `fit` and `fdescribe` do the same, but `fit` is also an ordinary function name: JS, TS and Vue files, at a statement
42
+ * start, after `=>`, or after `void`, `await` or `return`. */
43
+ const JS_FILE = /\.([cm]?[jt]sx?|vue)$/i;
44
+ const JS_FOCUS = /(^|[;{}]\s*|=>\s*|\b(void|await|return)\s+)(fit|fdescribe)\(/;
45
+ /** The line with quoted text emptied and a trailing `//` comment dropped, so neither can pose as a call. */
46
+ const jsCode = (text) => text.replace(/(["'`])(?:\\.|(?!\1).)*\1/g, '""').replace(/\/\/.*$/, '');
47
+ const ASSERTION = /\b(assert\w*|expect|should|self\.assert\w*|this->assert\w*|t\.(Error|Fatal)\w*)\s*[.(]|^\s*assert\s+\S/;
48
+ const BARE_RETURN = /^return\s*;?$/;
49
+ /** A line that opens a branch: a return under it is conditional, not an unconditional skip. */
50
+ const CONDITIONAL = /^(\}\s*)?(if|else|for|while|switch|case|default|catch|try|finally)\b/;
51
+ /** How far below a bare return an assertion still counts as the one it hides. */
52
+ const RETURN_LOOKAHEAD = 8;
53
+ function uncomment(text) {
54
+ const m = /^(?:\/\/+|#+|\/\*+|\*)\s?(.*?)\s*(?:\*\/)?$/.exec(text);
55
+ return m ? m[1].trim() : null;
56
+ }
57
+ /** The commented text is an assertion that the same change removed (alone on its line, or inside a one-line test). */
58
+ function wasCommentedOut(body, removed) {
59
+ const core = body.replace(/;$/, '');
60
+ return core.length >= 12 && ASSERTION.test(core) && removed.some((r) => r.includes(core));
61
+ }
62
+ /** `b/path` or git's C-quoted `"b/p\"ath"` form → `path`. */
63
+ function headerPath(raw) {
64
+ // git appends a tab to the header when the path contains a space.
65
+ const v = raw.slice(4).replace(/\t$/, '');
66
+ if (v.startsWith('b/'))
67
+ return v.slice(2);
68
+ if (v.startsWith('"b/') && v.endsWith('"')) {
69
+ return v.slice(3, -1).replace(/\\(["\\tn])/g, (_, c) => (c === 't' ? '\t' : c === 'n' ? '\n' : c));
70
+ }
71
+ return null;
72
+ }
73
+ export function parseTestWeakening(diff, readLines) {
74
+ const found = [];
75
+ let file = null;
76
+ let existing = false;
77
+ let line = 0;
78
+ let removed = [];
79
+ // File headers (`--- a/...`, `+++ b/...`) only appear between `diff --git` and the first hunk, so an added line
80
+ // whose text starts with `++ ` cannot pose as a header.
81
+ let inHeader = false;
82
+ for (const raw of diff.split('\n')) {
83
+ if (raw.startsWith('diff --git ')) {
84
+ file = null;
85
+ existing = true;
86
+ removed = [];
87
+ inHeader = true;
88
+ }
89
+ else if (inHeader && raw.startsWith('--- ')) {
90
+ existing = raw !== '--- /dev/null';
91
+ }
92
+ else if (inHeader && raw.startsWith('+++ ')) {
93
+ const path = headerPath(raw);
94
+ file = path && existing && TEST_PATH.test(path) ? path : null;
95
+ }
96
+ else if (raw.startsWith('@@')) {
97
+ inHeader = false;
98
+ const m = /\+(\d+)/.exec(raw);
99
+ line = m ? Number(m[1]) : 0;
100
+ removed = [];
101
+ }
102
+ else if (raw.startsWith('-')) {
103
+ removed.push(raw.slice(1).trim());
104
+ }
105
+ else if (raw.startsWith('+') && file) {
106
+ const text = raw.slice(1).trim();
107
+ const body = uncomment(text);
108
+ let why = null;
109
+ if (TRIVIAL.some((re) => re.test(text)))
110
+ why = 'trivial-assertion';
111
+ else if (SKIP.some((re) => re.test(text)) ||
112
+ ONLY.test(jsCode(text)) ||
113
+ (JS_FILE.test(file) && JS_FOCUS.test(jsCode(text))))
114
+ why = 'skipped';
115
+ else if (body !== null && wasCommentedOut(body, removed))
116
+ why = 'commented-out';
117
+ else if (BARE_RETURN.test(text)) {
118
+ const lines = readLines?.(file);
119
+ const above = (lines?.[line - 2] ?? '').trim();
120
+ const below = lines?.slice(line, line + RETURN_LOOKAHEAD) ?? [];
121
+ if (!CONDITIONAL.test(above) && below.some((l) => ASSERTION.test(l)))
122
+ why = 'unreachable-assertion';
123
+ }
124
+ if (why)
125
+ found.push({ file, line, why, text: text.slice(0, 80) });
126
+ line++;
127
+ }
128
+ }
129
+ return found;
130
+ }
131
+ /** The commit the current branch left `base` at, so changes made on `base` since are not blamed on the branch. */
132
+ function resolveBase(cwd, base) {
133
+ if (base.startsWith('-'))
134
+ throw new Error(`Invalid --base "${base}": a ref cannot start with "-".`);
135
+ try {
136
+ return execFileSync('git', ['merge-base', base, 'HEAD'], {
137
+ cwd,
138
+ encoding: 'utf8',
139
+ stdio: ['ignore', 'pipe', 'ignore'],
140
+ }).trim();
141
+ }
142
+ catch {
143
+ throw new Error(`Unknown --base "${base}": not a ref in this repository.`);
144
+ }
145
+ }
146
+ const MAX_DIFF = 64 * 1024 * 1024;
147
+ /** git with literal paths and unquoted non-ASCII names. */
148
+ function git(cwd, args) {
149
+ // GIT_DIFF_OPTS overrides --unified=0, and context lines would shift every line number after them.
150
+ const env = { ...process.env };
151
+ delete env.GIT_DIFF_OPTS;
152
+ return execFileSync('git', ['-c', 'core.quotepath=false', '--literal-pathspecs', ...args], {
153
+ cwd,
154
+ env,
155
+ encoding: 'utf8',
156
+ stdio: ['ignore', 'pipe', 'ignore'],
157
+ maxBuffer: MAX_DIFF,
158
+ });
159
+ }
160
+ function hasGitAbove(cwd) {
161
+ for (let d = resolve(cwd);; d = dirname(d)) {
162
+ if (existsSync(join(d, '.git')))
163
+ return true;
164
+ if (dirname(d) === d)
165
+ return false;
166
+ }
167
+ }
168
+ /**
169
+ * Changes in `cwd` against HEAD, or against where the branch left `base` when it is given. Only test files are
170
+ * diffed, so a large change elsewhere cannot push the test diff past the read limit.
171
+ */
172
+ export function scanTestWeakening(cwd, base) {
173
+ try {
174
+ git(cwd, ['rev-parse', '--is-inside-work-tree']);
175
+ }
176
+ catch (e) {
177
+ if (e.code === 'ENOENT')
178
+ return { findings: [], error: 'git is not installed' };
179
+ // Skipped only when there is no repository at all. Any other git failure (GIT_DIR, ownership checks, a broken
180
+ // .git) means the scan should have run and did not; with --base a repository is required.
181
+ if (!base && !hasGitAbove(cwd))
182
+ return { findings: [], skipped: 'not a git repository' };
183
+ return { findings: [], error: 'git could not read this repository; run `git status` here to see why' };
184
+ }
185
+ if (!base) {
186
+ try {
187
+ git(cwd, ['rev-parse', '--verify', '--quiet', 'HEAD']);
188
+ }
189
+ catch {
190
+ return { findings: [], skipped: 'no commit to compare against yet' };
191
+ }
192
+ }
193
+ const from = base ? resolveBase(cwd, base) : 'HEAD';
194
+ // Fixed diff options, so a user's or the change's own git config cannot alter what the parser reads: paths
195
+ // relative to `cwd` (a package folder inside a repository), a/ and b/ prefixes, no external diff or textconv,
196
+ // binary attributes ignored, renames paired.
197
+ const opts = ['--relative', '--no-ext-diff', '--no-textconv', '--text', '--src-prefix=a/', '--dst-prefix=b/', '-M'];
198
+ let tests;
199
+ try {
200
+ // -z name-status: status, path (and for renames and copies a second path), NUL-separated. Both paths of a
201
+ // rename go into the pathspec so git can pair them; a test renamed and weakened is still an existing test.
202
+ const fields = git(cwd, ['diff', ...opts, '--name-status', '-z', from]).split('\0');
203
+ tests = [];
204
+ for (let i = 0; i < fields.length - 1;) {
205
+ const status = fields[i++];
206
+ const paths = /^[RC]/.test(status) ? [fields[i++], fields[i++]] : [fields[i++]];
207
+ if (paths.some((p) => p && TEST_PATH.test(p)))
208
+ tests.push(...paths.filter(Boolean));
209
+ }
210
+ }
211
+ catch {
212
+ return { findings: [], error: 'could not read the diff (the list of changed files)' };
213
+ }
214
+ if (!tests.length)
215
+ return { findings: [] };
216
+ let diff;
217
+ try {
218
+ diff = git(cwd, ['diff', ...opts, '--unified=0', '--no-color', from, '--', ...tests]);
219
+ }
220
+ catch {
221
+ return {
222
+ findings: [],
223
+ error: `could not read the diff of ${tests.length} changed test file(s); it may be larger than 64 MB`,
224
+ };
225
+ }
226
+ return {
227
+ findings: parseTestWeakening(diff, (f) => {
228
+ try {
229
+ return readFileSync(join(cwd, f), 'utf8').split('\n');
230
+ }
231
+ catch {
232
+ return null;
233
+ }
234
+ }),
235
+ };
236
+ }
237
+ //# sourceMappingURL=detect-test-weakening.js.map